PHP扩展加载过程
在PHP的配置文件中,添加一行extension=xxxx.so,就可以使PHP加载xxx这个扩展,那么这个扩展具体是怎么被加载到PHP中的?可以看一下。
鸟哥之前写了一篇Blog,介绍了PHP扩展的载入过程:深入理解PHP原理之扩展载入过程。篇中使用了apache1的例子。
再看看apache2,在apache2handler/mod_php5.c中:
AP_MODULE_DECLARE_DATA module php5_module = {
STANDARD20_MODULE_STUFF,
create_php_config, /* create per-directory config structure */
merge_php_config, /* merge per-directory config structures */
NULL, /* create per-server config structure */
NULL, /* merge per-server config structures */
php_dir_cmds, /* command apr_table_t */
php_ap2_register_hook /* register hooks */
};
这中通过php_ap2_register_hook这个函数进行Apache的注入。
看一下php_ap2_register_hook这个函数:
void php_ap2_register_hook(apr_pool_t *p)
{
ap_hook_pre_config(php_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_post_config(php_apache_server_startup, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_handler(php_handler, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_child_init(php_apache_child_init, NULL, NULL, APR_HOOK_MIDDLE);
}
在post_config阶段会调用php_apache_server_startup:
而在php_apache_server_startup中,依次调用了sapi_startup和apache2_sapi_module.startup:
static int
php_apache_server_startup(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
{
//...
sapi_startup(&apache2_sapi_module);
apache2_sapi_module.startup(&apache2_sapi_module);
//...
}
而apache2_sapi_module.startup指向的函数是:
static int php_apache2_startup(sapi_module_struct *sapi_module)
{
if (php_module_startup(sapi_module, &php_apache_module, 1)==FAILURE) {
return FAILURE;
}
return SUCCESS;
}
static sapi_module_struct apache2_sapi_module = {
"apache2handler",
"Apache 2.0 Handler",
php_apache2_startup, /* startup */
//...
}
在php_apache2_startup中,调用了php_module_start函数,下面的逻辑就和鸟哥的博文一样了。
再看看最后的php_dl里做了些啥:(php_dl函数中调用了php_load_extension,所以就直接看php_load_extension这个函数,这个函数和鸟哥中的php_dl的实现是一样的)位置在 {PHP_SRC}/ext/standard/dl.c
PHPAPI int php_load_extension(char *filename, int type, int start_now TSRMLS_DC)
{
void *handle;
char *libpath;
zend_module_entry *module_entry;
zend_module_entry *(*get_module)(void);
int error_type;
char *extension_dir;
//省略一大段代码,就是计算出最终扩展文件的路径
//...
//这里打开这个动态库,DL_LOAD是一个宏,在Linux下就是dlopen,在Windows下就是LoadLibrary.
/* load dynamic symbol */
handle = DL_LOAD(libpath);
//一些错误处理
//...
//从动态库中获取get_module函数的入口
get_module = (zend_module_entry *(*)(void)) DL_FETCH_SYMBOL(handle, "get_module");
/* Some OS prepend _ to symbol names while their dynamic linker
* does not do that automatically. Thus we check manually for
* _get_module. */
if (!get_module) {
get_module = (zend_module_entry *(*)(void)) DL_FETCH_SYMBOL(handle, "_get_module");
}
if (!get_module) {
if (DL_FETCH_SYMBOL(handle, "zend_extension_entry") || DL_FETCH_SYMBOL(handle, "_zend_extension_entry")) {
DL_UNLOAD(handle);
php_error_docref(NULL TSRMLS_CC, error_type, "Invalid library (appears to be a Zend Extension, try loading using zend_extension=%s from php.ini)", filename);
return FAILURE;
}
DL_UNLOAD(handle);
php_error_docref(NULL TSRMLS_CC, error_type, "Invalid library (maybe not a PHP library) '%s'", filename);
return FAILURE;
}
//调用目标动态库的get_module()函数,获取module_entry
module_entry = get_module();
//进行一些判断和转换,主要是版本的比对
//...
module_entry->type = type;
module_entry->module_number = zend_next_free_module();
module_entry->handle = handle;
//调用zend_register_module_ex进行注册
if ((module_entry = zend_register_module_ex(module_entry TSRMLS_CC)) == NULL) {
DL_UNLOAD(handle);
return FAILURE;
}
if ((type == MODULE_TEMPORARY || start_now) && zend_startup_module_ex(module_entry TSRMLS_CC) == FAILURE) {
DL_UNLOAD(handle);
return FAILURE;
}
if ((type == MODULE_TEMPORARY || start_now) && module_entry->request_startup_func) {
if (module_entry->request_startup_func(type, module_entry->module_number TSRMLS_CC) == FAILURE) {
php_error_docref(NULL TSRMLS_CC, error_type, "Unable to initialize module '%s'", module_entry->name);
DL_UNLOAD(handle);
return FAILURE;
}
}
return SUCCESS;
}
在zend_register_module_ex中,再有一些具体的工作,包括把扩展中定义的函数都复制到全局的funtcion_table中等等。
这样,一个扩展就被加载到PHP中了。