ini文件解析,php 扩展开发(二)
运行环境
- PHP Version 7.0.0-dev (
./configure --prefix=/root/php7d --without-pear --enable-fpm --enable-debug
) - Linux version 2.6.32-504.1.3.el6.x86_64 (gcc version 4.4.7 20120313 (Red Hat 4.4.7-11) (GCC) )
- swoole (1.7.8 stable for php extension)
前言:
我们都知道大多数扩展都有自己的配置项,那么它的怎么从配置文件中读取出来,又是怎么传给扩展自身的呢?今天就让我们一探究竟。
目录:
- 配置文件解析
- 模块扩展的配置
- 总结
1.配置文件解析
在php cli执行流程一文中,我们了解到php.ini文件解析是通过php_module_startup函数中的php_init_config实现的。php_init_config的实现如下:
PHP-SRC/main/php_ini.c:384
int php_init_config(void)
{
//...
zend_hash_init(&configuration_hash, 8, NULL, config_zval_dtor, 1);/*首先初始化configuration_hash全局变量*/
/*调用zend_parse_ini_file解析文件,并将解析的结果保存到configuration_hash中*/
zend_parse_ini_file(&fh, 1, ZEND_INI_SCANNER_NORMAL, (zend_ini_parser_cb_t) php_ini_parser_cb, &configuration_hash);
{
zval tmp;
ZVAL_NEW_STR(&tmp, zend_string_init(fh.filename, strlen(fh.filename), 1));
/*更新cfg_file_path这个key*/
zend_hash_str_update(&configuration_hash, "cfg_file_path", sizeof("cfg_file_path")-1, &tmp);
if (php_ini_opened_path) {
efree(php_ini_opened_path);
}
php_ini_opened_path = zend_strndup(Z_STRVAL(tmp), Z_STRLEN(tmp));
}
/*确定其他配置文件的目录*/
/* Check for PHP_INI_SCAN_DIR environment variable to override/set config file scan directory */
php_ini_scanned_path = getenv("PHP_INI_SCAN_DIR");
if (!php_ini_scanned_path) {
/* Or fall back using possible --with-config-file-scan-dir setting (defaults to empty string!) */
php_ini_scanned_path = PHP_CONFIG_FILE_SCAN_DIR;
}
php_ini_scanned_path_len = (int)strlen(php_ini_scanned_path);
/*遍历目录,并获取所有文件*/
bufpath = estrdup(php_ini_scanned_path);
for (debpath = bufpath ; debpath ; debpath=endpath) {
endpath = strchr(debpath, DEFAULT_DIR_SEPARATOR);
if (endpath) {
*(endpath++) = 0;
}
if (!debpath[0]) {
/* empty string means default builtin value
to allow "/foo/phd.d:" or ":/foo/php.d" */
debpath = PHP_CONFIG_FILE_SCAN_DIR;
}
lenpath = (int)strlen(debpath);
if (lenpath > 0 && (ndir = php_scandir(debpath, &namelist, 0, php_alphasort)) > 0) {
for (i = 0; i < ndir; i++) {
/* check for any file with .ini extension */
if (!(p = strrchr(namelist[i]->d_name, '.')) || (p && strcmp(p, ".ini"))) {
free(namelist[i]);
continue;
}
/* Reset active ini section */
RESET_ACTIVE_INI_HASH();
if (IS_SLASH(debpath[lenpath - 1])) {
snprintf(ini_file, MAXPATHLEN, "%s%s", debpath, namelist[i]->d_name);
} else {
snprintf(ini_file, MAXPATHLEN, "%s%c%s", debpath, DEFAULT_SLASH, namelist[i]->d_name);
}
if (VCWD_STAT(ini_file, &sb) == 0) {
if (S_ISREG(sb.st_mode)) {
if ((fh2.handle.fp = VCWD_FOPEN(ini_file, "r"))) {
fh2.filename = ini_file;
fh2.type = ZEND_HANDLE_FP;
/*调用zend_parse_ini_file解析文件并将解析结果添加到configuration_hash中*/
if (zend_parse_ini_file(&fh2, 1, ZEND_INI_SCANNER_NORMAL,
(zend_ini_parser_cb_t) php_ini_parser_cb, &configuration_hash) == SUCCESS) {
/* Here, add it to the list of ini files read */
l = (int)strlen(ini_file);
total_l += l + 2;
p = estrndup(ini_file, l);
zend_llist_add_element(&scanned_ini_list, &p);
}
}
}
}
free(namelist[i]);
}
free(namelist);
}
}
efree(bufpath);
/...
return SUCCESS;
}
通过gdb查看configuration_hash的内容
(gdb) p configuration_hash
$1 = {u = {v = {flags = 11 '\v', nApplyCount = 0 '\000', reserve = 0}, flags = 11}, nTableSize = 8,
nTableMask = 7, nNumUsed = 2, nNumOfElements = 2, nInternalPointer = 0, nNextFreeElement = 0,
arData = 0xfd1270, arHash = 0xfd1370, pDestructor = 0x79dc13 <config_zval_dtor>}
/*可以看出Hash表中只用两个元素*/
(gdb) p (*configuration_hash.arData[0].key.val)@20
$2 = "report_zend_debug\000\000"
/*第一个key是report_zend_debug*/
(gdb) p (*configuration_hash.arData[1].key.val)@20
$3 = "display_errors\000\000A\002\000"
/*第二个key是display_errors*/
通过在编写$HOME/.gdbinit文件,实现一个用来打印php hashTable变量的命令,其内容如下
define print_zval
printf " "
if $arg0.u1.v.type == 0
printf "IS_UNDEF\n"
end
if $arg0.u1.v.type == 1
printf "IS_NULL\n"
end
if $arg0.u1.v.type == 2
printf "IS_FALSE\n"
end
if $arg0.u1.v.type == 3
printf "IS_TRUE\n"
end
if $arg0.u1.v.type == 4
printf "IS_LONG\n"
end
if $arg0.u1.v.type == 5
printf "IS_DOUBLE\n"
end
if $arg0.u1.v.type == 6
printf "IS_STRING %s\n",$arg0.value.str.val
end
if $arg0.u1.v.type == 7
printf "IS_ARRAY\n"
end
if $arg0.u1.v.type == 8
printf "IS_OBJECT\n"
end
if $arg0.u1.v.type >= 9
printf "%d\n",$arg0.u1.v.type
end
end
define print_hash
set $i = 0
set $num = 0
set $len = $arg0.nTableSize - 1
while $i < $len
if $arg0.arData[$i].key.len > 0
printf "%s",$arg0.arData[$i].key.val
print_zval $arg0.arData[$i].val
set $num = $num + 1
end
set $i = $i + 1
end
printf "total:%d\n",$num
end
调试php_init_config函数,在PHP-SRC/main/php_ini.c:595前停止
591 if (fh.handle.fp) {
(gdb)
592 fh.type = ZEND_HANDLE_FP;
(gdb)
593 RESET_ACTIVE_INI_HASH();
(gdb)
595 zend_parse_ini_file(&fh, 1, ZEND_INI_SCANNER_NORMAL, (zend_ini_parser_cb_t)
php_ini_parser_cb, &configuration_hash);
(gdb) print_hash configuration_hash
report_zend_debug IS_STRING 0
display_errors IS_STRING 1
Cannot access memory at address 0x10
(gdb) n
600 ZVAL_NEW_STR(&tmp, zend_string_init(fh.filename, strlen(fh.filename), 1));
(gdb) print_hash configuration_hash
report_zend_debug IS_STRING 0
display_errors IS_STRING 1
engine IS_STRING 1
short_open_tag IS_STRING
precision IS_STRING 14
//...
url_rewriter.tags IS_STRING a=href,area=href,frame=src,input=src,form=fakeentry
mssql.allow_persistent IS_STRING 1
mssql.max_persistent IS_STRING -1
mssql.max_links IS_STRING -1
mssql.min_error_severity IS_STRING 10
mssql.min_message_severity IS_STRING 10
mssql.compatibility_mode IS_STRING
mssql.secure_connection IS_STRING
tidy.clean_output IS_STRING
soap.wsdl_cache_enabled IS_STRING 1
soap.wsdl_cache_dir IS_STRING /tmp
soap.wsdl_cache_ttl IS_STRING 86400
soap.wsdl_cache_limit IS_STRING 5
ldap.max_links IS_STRING -1
Cannot access memory at address 0x1700000048
(gdb)
我们可以发现,所有的元素都是STRING类型的,并且on、off已经被转换成1、-1了。
2.模块扩展的配置
配置完configuration_hash后,紧接着就进行扩展的配置。
if (php_init_config() == FAILURE) {
return FAILURE;
}
/* Register PHP core ini entries */
REGISTER_INI_ENTRIES();
REGISTER_INI_ENTRIES宏展开
#define REGISTER_INI_ENTRIES() zend_register_ini_entries(ini_entries, module_number)
ini_entries是一个zend_ini_entry_def结构体
typedef struct _zend_ini_entry_def {
const char *name;
ZEND_INI_MH((*on_modify)); /*配置项注册或修改的时候会调用*/
void *mh_arg1;
void *mh_arg2;
void *mh_arg3;
const char *value;
void (*displayer)(zend_ini_entry *ini_entry, int type);
int modifiable;
uint name_length;
uint value_length;
} zend_ini_entry_def;
#define ZEND_INI_MH(name) int name(zend_ini_entry *entry,
zend_string *new_value, void *mh_arg1, void *mh_arg2, void *mh_arg3, int stage)
通过分析,得出zend_register_ini_entries的ini_entries来自main.c:524的PHP_INI_BEGIN()宏
#define PHP_INI_BEGIN ZEND_INI_BEGIN
#define ZEND_INI_BEGIN() static const zend_ini_entry_def ini_entries[] = {
#define ZEND_INI_END() { NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, 0, 0} };
zend_register_ini_entries实现如下
ZEND_API int zend_register_ini_entries(const zend_ini_entry_def *ini_entry, int module_number) /* { { { */
{
zend_ini_entry *p;
zval *default_value;
HashTable *directives = registered_zend_ini_directives;
//...
/*通过ZEND_INI_END可以看出第一个元素的name属性是NULL*/
while (ini_entry->name) {
p = pemalloc(sizeof(zend_ini_entry), 1);
p->name = zend_string_init(ini_entry->name, ini_entry->name_length, 1);
p->on_modify = ini_entry->on_modify;
p->mh_arg1 = ini_entry->mh_arg1;
p->mh_arg2 = ini_entry->mh_arg2;
p->mh_arg3 = ini_entry->mh_arg3;
p->value = NULL;
p->orig_value = NULL;
p->displayer = ini_entry->displayer;
p->modifiable = ini_entry->modifiable;
p->orig_modifiable = 0;
p->modified = 0;
p->module_number = module_number;
if (zend_hash_add_ptr(directives, p->name, (void*)p) == NULL) {
if (p->name) {
zend_string_release(p->name);
}
zend_unregister_ini_entries(module_number);
return FAILURE;
}
if (((default_value = zend_get_configuration_directive(p->name)) != NULL) &&
(!p->on_modify || p->on_modify(p, Z_STR_P(default_value), p->mh_arg1, p->mh_arg2, p->mh_arg3, ZEND_INI_STAGE_STARTUP) == SUCCESS)) {
p->value = zend_string_copy(Z_STR_P(default_value));
} else {
p->value = ini_entry->value ?
zend_string_init(ini_entry->value, ini_entry->value_length, 1) : NULL;
if (p->on_modify) {
p->on_modify(p, p->value, p->mh_arg1, p->mh_arg2, p->mh_arg3, ZEND_INI_STAGE_STARTUP);
}
}
ini_entry++;
}
return SUCCESS;
}
zend_get_configuration_directive用来获取配置参数
zend_get_configuration_directive_p = utility_functions->get_configuration_directive;
:main.c:2097
zuf.get_configuration_directive = php_get_configuration_directive_for_zend;
static zval *php_get_configuration_directive_for_zend(zend_string *name)
{
return cfg_get_entry_ex(name);
}
PHPAPI zval *cfg_get_entry_ex(zend_string *name)
{
return zend_hash_find(&configuration_hash, name);
}
最后我们可以看到,是获取configuration_hash中的值
/*如果配置文件中存在配置项,那么就使用配置文件中的值,否则的话,就使用模块硬编码的默认值*/
if (((default_value = zend_get_configuration_directive(p->name)) != NULL) &&
(!p->on_modify || p->on_modify(p, Z_STR_P(default_value), p->mh_arg1, p->mh_arg2, p->mh_arg3, ZEND_INI_STAGE_STARTUP) == SUCCESS)) {
p->value = zend_string_copy(Z_STR_P(default_value));
}
这里初始化了ini_entries的值,现在我们对比一下扩展,以bcmath为例
php_bcmath.h
PHP_INI_BEGIN()
STD_PHP_INI_ENTRY("bcmath.scale", "0", PHP_INI_ALL, OnUpdateLongGEZero, bc_precision, zend_bcmath_globals, bcmath_globals)
PHP_INI_END()
PHP_MINIT_FUNCTION(bcmath)
{
REGISTER_INI_ENTRIES();
return SUCCESS;
}
在MINIT函数中,调用REGISTER_INI_ENTRIES,读取configuration_hash中配置文件的内容,然后更新到自己的ini_entries变量中 这个同main.c中的ini_entries由于是不同文件的全局静态变量,所以没有联系。
同理,看一下swoole的实现
PHP_INI_BEGIN()
STD_PHP_INI_ENTRY("swoole.aio_thread_num", "2", PHP_INI_ALL, OnUpdateLong, aio_thread_num, zend_swoole_globals, swoole_globals)
STD_PHP_INI_ENTRY("swoole.display_errors", "2", PHP_INI_ALL, OnUpdateBool, display_errors, zend_swoole_globals, swoole_globals)
STD_PHP_INI_ENTRY("swoole.message_queue_key", "0", PHP_INI_ALL, OnUpdateString, message_queue_key, zend_swoole_globals, swoole_globals)
/**
* Unix socket buffer size
*/
STD_PHP_INI_ENTRY("swoole.unixsock_buffer_size", "8388608", PHP_INI_ALL, OnUpdateLong, unixsock_buffer_size, zend_swoole_globals, swoole_globals)
PHP_INI_END()
PHP_MINIT_FUNCTION(swoole)
{
ZEND_INIT_MODULE_GLOBALS(swoole, php_swoole_init_globals, NULL);
/*同样调用这个宏,让配置文件中的配置值覆盖硬编码的默认值*/
REGISTER_INI_ENTRIES();
其中的ZEND_INIT_MODULE_GLOBALS展示
#define ZEND_INIT_MODULE_GLOBALS(module_name, globals_ctor, globals_dtor) \
globals_ctor(&module_name##_globals);
#endif
即
php_swoole_init_globals(&swoole_globals);
static void php_swoole_init_globals(zend_swoole_globals *swoole_globals)
{
swoole_globals->message_queue_key = 0;
swoole_globals->aio_thread_num = SW_AIO_THREAD_NUM_DEFAULT;
swoole_globals->socket_buffer_size = SW_SOCKET_BUFFER_SIZE;
swoole_globals->display_errors = 1;
}
3.总结
- 解析配置文件,把解析的结果添加到configuration_hash变量中
- 每个模块调用EGISTER_INI_ENTRIES();宏,来读取配置文件中用户设置的值,来覆盖自己的默认值