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中了。