周梦康 发表于 2016-03-05 7000 次浏览 标签 : PHP 扩展开发

PHP 扩展开发的文章,我均已更新至《TIPI》(下面的博文可能已经过时,以 TIPI 上的内容为准)。

我们的很多配置都是从php.ini里读取的,这个想必大家都非常熟悉了。

什么时候需要 INI 指令

比如某个扩展的连接池的初始连接数的配置。因为一个使用该扩展的机器配置各不一样,数据量也不一样,所以在装载该扩展的时候,每台机器上希望的初始连接数不尽一样。就需要在php.ini里配置。如果你觉得为什么不在我们自己的代码里配置(PHP语言层),那就还是需要了解PHP的生命周期了,因为扩展的装载是在PHP脚本执行之前的,需要在MINT阶段配置,而PHP脚本执行是在RINIT

当然 INI 配置项也可以用于PHP执行过程中的默认全局变量。而INI配置一般不参与逻辑运算,这是与PHP5 在扩展里使用全局变量不同的地方。

准备

[root@localhost ext]# cat ini.proto
string get_demo_init_value()
[root@localhost ext]# ./ext_skel --extname=tipi_ini_demo --proto=ini.proto

添加 INI 指令

添加 INI 指令有两种方式,一种是直接添加,另一种是配合全局变量

直接添加

1. 打开tipi_ini_demo.cPHP_INI_BEGIN宏和PHP_INI_END宏的注释。这里仅演示一个配置项

PHP_INI_BEGIN()
    PHP_INI_ENTRY("tipi_ini_demo.global_value", "42", PHP_INI_ALL, NULL)
//    STD_PHP_INI_ENTRY("tipi_ini_demo.global_value", "42", PHP_INI_ALL, OnUpdateLong, global_value, zend_tipi_ini_demo_globals, tipi_ini_demo_globals)
PHP_INI_END()

2. 在PHP_MINIT_FUNCTION注册

PHP_MINIT_FUNCTION(tipi_ini_demo)
{
   REGISTER_INI_ENTRIES();
   return SUCCESS;
}

3. 在PHP_MSHUTDOWN_FUNCTION注销

PHP_MSHUTDOWN_FUNCTION(tipi_ini_demo)
{
   UNREGISTER_INI_ENTRIES();
   return SUCCESS;
}

4. 在原型里定义的函数中获取设置的 INI 指令值

PHP_FUNCTION(get_demo_init_value)
{
   RETURN_LONG(INI_INT("tipi_ini_demo.global_value"));
}

5. 在 php.ini 中添加指令设置

tipi_ini_demo.global_value = 100

6. 编译并测试

编译请参考http://mengkang.net/673.html 测试

/usr/local/php5/bin/php -d"extension=tipi_ini_demo.so" -r "var_dump(get_demo_init_value());"

得到了我们在php.ini里配置的值100

配合全局变量 

完整代码INI指令代码2

首先要开启全局变量的一些设置,可以复习下PHP5 在扩展里使用全局变量,全局变量的设置我这里就不再复制并详细说明了,请对照我上传的源代码进行下面的阅读。

1. 打开tipi_ini_demo.cPHP_INI_BEGIN宏和PHP_INI_END宏的注释。这里仅演示一个配置项

PHP_INI_BEGIN()
    STD_PHP_INI_ENTRY("tipi_ini_demo.global_value", "42", PHP_INI_ALL, OnUpdateLong, global_value, zend_tipi_ini_demo_globals, tipi_ini_demo_globals)
PHP_INI_END()

2. 在PHP_MINIT_FUNCTION注册

PHP_MINIT_FUNCTION(tipi_ini_demo)
{
   REGISTER_INI_ENTRIES();
   return SUCCESS;
}

3. 在PHP_MSHUTDOWN_FUNCTION注销

PHP_MSHUTDOWN_FUNCTION(tipi_ini_demo)
{
   UNREGISTER_INI_ENTRIES();
   return SUCCESS;
}

4. 在原型里定义的函数中获取设置的 INI 指令值

PHP_FUNCTION(get_demo_init_value)
{
   RETURN_LONG(TIPI_INI_DEMO_G(global_value));
}

或者是

PHP_FUNCTION(get_demo_init_value)
{
   RETURN_LONG(INI_INT("tipi_ini_demo.global_value"));
}

5. 在 php.ini 中添加指令设置

tipi_ini_demo.global_value = 100

6. 编译并测试

编译请参考http://mengkang.net/673.html 测试

/usr/local/php5/bin/php -d"extension=tipi_ini_demo.so" -r "var_dump(get_demo_init_value());"

如果你在PHP_RINIT_FUNCTION初始化设置了global_value的值,那么最后得到的结果将是那个值。

既不php.ini里设置的值,也不是STD_PHP_INI_ENTRY宏里设置的默认值,因为RINIT是最后执行环节,被覆盖了。

所以请将PHP_RINIT_FUNCTION里对 INI 指令对应的全局变量值的初始化去掉。

原理分析

这里仅仅分析 INI 指令的初始化,其他情况类似,不再分析。

首先贴上zend_ini_entry结构和初始化函数zend_register_ini_entries

typedef struct _zend_ini_entry zend_ini_entry;
struct _zend_ini_entry {
   int module_number;
   int modifiable;
   char *name;
   uint name_length;
   ZEND_INI_MH((*on_modify));
   void *mh_arg1;
   void *mh_arg2;
   void *mh_arg3;

   char *value;
   uint value_length;

   char *orig_value;
   uint orig_value_length;
   int orig_modifiable;
   int modified;

   void (*displayer)(zend_ini_entry *ini_entry, int type);
};
ZEND_API int zend_register_ini_entries(const zend_ini_entry *ini_entry, int module_number TSRMLS_DC) /* {{{ */
{
   const zend_ini_entry *p = ini_entry;
   zend_ini_entry *hashed_ini_entry;
   zval default_value;
   HashTable *directives = registered_zend_ini_directives;
   zend_bool config_directive_success = 0;

#ifdef ZTS
   /* if we are called during the request, eg: from dl(),
    * then we should not touch the global directives table,
    * and should update the per-(request|thread) version instead.
    * This solves two problems: one is that ini entries for dl()'d
    * extensions will now work, and the second is that updating the
    * global hash here from dl() is not mutex protected and can
    * lead to death.
    */
   if (directives != EG(ini_directives)) {
      directives = EG(ini_directives);
   }
#endif

   while (p->name) {
      config_directive_success = 0;
      if (zend_hash_add(directives, p->name, p->name_length, (void*)p, sizeof(zend_ini_entry), (void **) &hashed_ini_entry) == FAILURE) {
         zend_unregister_ini_entries(module_number TSRMLS_CC);
         return FAILURE;
      }
      hashed_ini_entry->module_number = module_number;
      if ((zend_get_configuration_directive(p->name, p->name_length, &default_value)) == SUCCESS) {
         if (!hashed_ini_entry->on_modify
            || hashed_ini_entry->on_modify(hashed_ini_entry, Z_STRVAL(default_value), Z_STRLEN(default_value), hashed_ini_entry->mh_arg1, hashed_ini_entry->mh_arg2, hashed_ini_entry->mh_arg3, ZEND_INI_STAGE_STARTUP TSRMLS_CC) == SUCCESS) {
            hashed_ini_entry->value = Z_STRVAL(default_value);
            hashed_ini_entry->value_length = Z_STRLEN(default_value);
            config_directive_success = 1;
         }
      }

      if (!config_directive_success && hashed_ini_entry->on_modify) {
         hashed_ini_entry->on_modify(hashed_ini_entry, hashed_ini_entry->value, hashed_ini_entry->value_length, hashed_ini_entry->mh_arg1, hashed_ini_entry->mh_arg2, hashed_ini_entry->mh_arg3, ZEND_INI_STAGE_STARTUP TSRMLS_CC);
      }
      p++;
   }
   return SUCCESS;
}

直接添加

该情况下代码比较清晰易懂

PHP_INI_BEGIN()
    PHP_INI_ENTRY("tipi_ini_demo.global_value", "42", PHP_INI_ALL, NULL)
PHP_INI_END()

展开之后

static const zend_ini_entry ini_entries[] = {
	{ 0, PHP_INI_ALL, "tipi_ini_demo.global_value", sizeof("tipi_ini_demo.global_value"), NULL, NULL, NULL, NULL, "42", sizeof("42")-1, NULL, 0, 0, 0, NULL },
	{ 0, 0, NULL, 0, NULL, NULL, NULL, NULL, NULL, 0, NULL, 0, 0, 0, NULL }
};

相当于注册了一个zend_ini_entry,仅仅设置了名称、默认值和可以什么时机修改这三个值。

由上面的zend_register_ini_entries函数可知,直接将设置默认值存入了zend_ini_entry

hashed_ini_entry->value = Z_STRVAL(default_value);
hashed_ini_entry->value_length = Z_STRLEN(default_value);

在初始化的时候没看到modifiable的修改,原理比较简单。

通过valuevalue_length作为索引依据。

PHP5 在扩展里使用 INI 指令(直接添加和配合全局变量两种方式)

查找的时候以长整型为例

ZEND_API long zend_ini_long(char *name, uint name_length, int orig)
{
	zend_ini_entry *ini_entry;
	TSRMLS_FETCH();

	if (zend_hash_find(EG(ini_directives), name, name_length, (void **) &ini_entry) == SUCCESS) {
		if (orig && ini_entry->modified) {
			return (ini_entry->orig_value ? strtol(ini_entry->orig_value, NULL, 0) : 0);
		} else {
			return (ini_entry->value      ? strtol(ini_entry->value, NULL, 0)      : 0);
		}
	}

	return 0;
}

实际还是通过名称和名称的长度来查找的

zend_hash_find(EG(ini_directives), name, name_length, (void **) &ini_entry)

配合全局变量

1. PHP_INI_BEGIN宏和PHP_INI_END

#define PHP_INI_BEGIN		ZEND_INI_BEGIN
#define PHP_INI_END		ZEND_INI_END

#define ZEND_INI_BEGIN()	static const zend_ini_entry ini_entries[] = {
#define ZEND_INI_END()		{ 0, 0, NULL, 0, NULL, NULL, NULL, NULL, NULL, 0, NULL, 0, 0, 0, NULL } };

逐步展开 STD_PHP_INI_ENTRY宏,发现我们上面定义的

PHP_INI_BEGIN()
    STD_PHP_INI_ENTRY("tipi_ini_demo.global_value", "42", PHP_INI_ALL, OnUpdateLong, global_value, zend_tipi_ini_demo_globals, tipi_ini_demo_globals)
PHP_INI_END()

等价于

static const zend_ini_entry ini_entries[] = {
	{ 0, PHP_INI_ALL, "tipi_ini_demo.global_value", sizeof("tipi_ini_demo.global_value"), OnUpdateLong, global_value, zend_tipi_ini_demo_globals, tipi_ini_demo_globals, "42", sizeof("42")-1, NULL, 0, 0, 0, NULL },
	{ 0, 0, NULL, 0, NULL, NULL, NULL, NULL, NULL, 0, NULL, 0, 0, 0, NULL }
};

zend_ini_entry中使用了ZEND_INI_MH宏,其定义为

#define ZEND_INI_MH(name) int name(zend_ini_entry *entry, char *new_value, uint new_value_length, void *mh_arg1, void *mh_arg2, void *mh_arg3, int stage TSRMLS_DC)

我们上面的例子中nameOnUpdateLong,其定义为

ZEND_API ZEND_INI_MH(OnUpdateLong)
{
	long *p;
#ifndef ZTS
	char *base = (char *) mh_arg2;
#else
	char *base;

	base = (char *) ts_resource(*((int *) mh_arg2));
#endif

	p = (long *) (base+(size_t) mh_arg1);

	*p = zend_atol(new_value, new_value_length);
	return SUCCESS;
}

在非线程安全情况下展开之后为

int OnUpdateLong(zend_ini_entry *entry, char *new_value, uint new_value_length, void *mh_arg1, void *mh_arg2, void *mh_arg3, int stage TSRMLS_DC)
{
	long *p;

	char *base = (char *) mh_arg2;

	p = (long *) (base+(size_t) mh_arg1);

	*p = zend_atol(new_value, new_value_length);
	return SUCCESS;
}

再看zend_register_ini_entries函数中的调用

hashed_ini_entry->on_modify(hashed_ini_entry, Z_STRVAL(default_value), Z_STRLEN(default_value), hashed_ini_entry->mh_arg1, hashed_ini_entry->mh_arg2, hashed_ini_entry->mh_arg3, ZEND_INI_STAGE_STARTUP TSRMLS_CC)


附录

STD_PHP_INI_ENTRY宏参数解释

参数含义
nameINI条目名
default_value如果没有在INI文件中指定,条目的默认值。默认值始终是一个字符串。
modifiable设定在何种环境下INI条目可以被更改的位域。可以的值是:
• PHP_INI_SYSTEM. 能够在php.ini或http.conf等系统文件更改
• PHP_INI_PERDIR. 能够在 .htaccess中更改
• PHP_INI_USER. 能够被用户脚本更改
• PHP_INI_ALL.  能够在所有地方更改
on_modify处理INI条目更改的回调函数。你不需自己编写处理程序,使用下面提供的函数。包括:
• OnUpdateInt
• OnUpdateString
• OnUpdateBool
• OnUpdateStringUnempty
• OnUpdateReal
property_name应当被更新的变量名
struct_type变量驻留的结构类型。因为通常使用全局变量机制,所以这个类型自动被定义。
struct_ptr全局结构名。

property_name,struct_type,struct_ptrzend_ini_entry对应的字段是mh_arg1,mh_arg2,mh_arg3,是on_modify函数的参数。

评论列表

回复 路人甲 2016-04-14 08:57:49
喜欢你的博客
回复 康哥 2016-04-14 12:19:21
回复路人甲: 谢谢,相互学习交流,喜欢样式的话,可以拿走,哈哈。