to inspire confidence in somebody.

0%

PHP扩展加载过程

在PHP的配置文件中,添加一行extension=xxxx.so,就可以使PHP加载xxx这个扩展,那么这个扩展具体是怎么被加载到PHP中的?可以看一下。

鸟哥之前写了一篇Blog,介绍了PHP扩展的载入过程:深入理解PHP原理之扩展载入过程。篇中使用了apache1的例子。

再看看apache2,在apache2handler/mod_php5.c中:

1
AP_MODULE_DECLARE_DATA module php5_module = {
2
    STANDARD20_MODULE_STUFF,
3
    create_php_config,      /* create per-directory config structure */
4
    merge_php_config,       /* merge per-directory config structures */
5
    NULL,                   /* create per-server config structure */
6
    NULL,                   /* merge per-server config structures */
7
    php_dir_cmds,           /* command apr_table_t */
8
    php_ap2_register_hook   /* register hooks */
9
};

这中通过php_ap2_register_hook这个函数进行Apache的注入。
看一下php_ap2_register_hook这个函数:

1
void php_ap2_register_hook(apr_pool_t *p)
2
{
3
    ap_hook_pre_config(php_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
4
    ap_hook_post_config(php_apache_server_startup, NULL, NULL, APR_HOOK_MIDDLE);
5
    ap_hook_handler(php_handler, NULL, NULL, APR_HOOK_MIDDLE);
6
    ap_hook_child_init(php_apache_child_init, NULL, NULL, APR_HOOK_MIDDLE);
7
}

在post_config阶段会调用php_apache_server_startup:
而在php_apache_server_startup中,依次调用了sapi_startup和apache2_sapi_module.startup:

1
static int
2
php_apache_server_startup(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
3
{
4
    //...
5
    sapi_startup(&apache2_sapi_module);
6
    apache2_sapi_module.startup(&apache2_sapi_module);
7
    //...
8
}
而apache2_sapi_module.startup指向的函数是:
1
static int php_apache2_startup(sapi_module_struct *sapi_module)
2
{
3
    if (php_module_startup(sapi_module, &php_apache_module, 1)==FAILURE) {
4
        return FAILURE;
5
    }
6
    return SUCCESS;
7
}
8
9
static sapi_module_struct apache2_sapi_module = {
10
    "apache2handler",
11
    "Apache 2.0 Handler",
12
13
    php_apache2_startup,                /* startup */
14
    //...
15
}

在php_apache2_startup中,调用了php_module_start函数,下面的逻辑就和鸟哥的博文一样了。

再看看最后的php_dl里做了些啥:(php_dl函数中调用了php_load_extension,所以就直接看php_load_extension这个函数,这个函数和鸟哥中的php_dl的实现是一样的)位置在 {PHP_SRC}/ext/standard/dl.c

1
PHPAPI int php_load_extension(char *filename, int type, int start_now TSRMLS_DC)
2
{
3
    void *handle;
4
    char *libpath;
5
    zend_module_entry *module_entry;
6
    zend_module_entry *(*get_module)(void);
7
    int error_type;
8
    char *extension_dir;
9
    
10
    //省略一大段代码,就是计算出最终扩展文件的路径
11
    //...
12
    
13
14
    //这里打开这个动态库,DL_LOAD是一个宏,在Linux下就是dlopen,在Windows下就是LoadLibrary.
15
    /* load dynamic symbol */
16
    handle = DL_LOAD(libpath);
17
    
18
    //一些错误处理
19
    //...
20
    
21
    //从动态库中获取get_module函数的入口
22
    get_module = (zend_module_entry *(*)(void)) DL_FETCH_SYMBOL(handle, "get_module");
23
24
    /* Some OS prepend _ to symbol names while their dynamic linker
25
     * does not do that automatically. Thus we check manually for
26
     * _get_module. */
27
28
    if (!get_module) {
29
        get_module = (zend_module_entry *(*)(void)) DL_FETCH_SYMBOL(handle, "_get_module");
30
    }
31
32
    if (!get_module) {
33
        if (DL_FETCH_SYMBOL(handle, "zend_extension_entry") || DL_FETCH_SYMBOL(handle, "_zend_extension_entry")) {
34
            DL_UNLOAD(handle);
35
            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);
36
            return FAILURE;
37
        }
38
        DL_UNLOAD(handle);
39
        php_error_docref(NULL TSRMLS_CC, error_type, "Invalid library (maybe not a PHP library) '%s'", filename);
40
        return FAILURE;
41
    }
42
43
    //调用目标动态库的get_module()函数,获取module_entry
44
    module_entry = get_module();
45
    
46
    //进行一些判断和转换,主要是版本的比对
47
    //...
48
    
49
    module_entry->type = type;
50
    module_entry->module_number = zend_next_free_module();
51
    module_entry->handle = handle;
52
53
    //调用zend_register_module_ex进行注册
54
    if ((module_entry = zend_register_module_ex(module_entry TSRMLS_CC)) == NULL) {
55
        DL_UNLOAD(handle);
56
        return FAILURE;
57
    }
58
59
    if ((type == MODULE_TEMPORARY || start_now) && zend_startup_module_ex(module_entry TSRMLS_CC) == FAILURE) {
60
        DL_UNLOAD(handle);
61
        return FAILURE;
62
    }
63
64
    if ((type == MODULE_TEMPORARY || start_now) && module_entry->request_startup_func) {
65
        if (module_entry->request_startup_func(type, module_entry->module_number TSRMLS_CC) == FAILURE) {
66
            php_error_docref(NULL TSRMLS_CC, error_type, "Unable to initialize module '%s'", module_entry->name);
67
            DL_UNLOAD(handle);
68
            return FAILURE;
69
        }
70
    }
71
    return SUCCESS;
72
}

在zend_register_module_ex中,再有一些具体的工作,包括把扩展中定义的函数都复制到全局的funtcion_table中等等。

这样,一个扩展就被加载到PHP中了。