文档章节

深入理解PHP原理之扩展载入过程

mickelfeng
 mickelfeng
发布于 2015/08/27 15:30
字数 1374
阅读 652
收藏 6

why xdebug extension must be loaded as a zend extension?

what is zend extension and what are the differents between regular php extension and zend extension?

let’s start from that the extension loading process.

PHP是可以被扩展的, PHP的核心引擎Zend Engine也是可以被扩展的, 如果你也对Apache Module的编写也有所了解的话, 那么, 你就会对如下的结构很熟悉了:

struct _zend_extension {
        char *name;
        char *version;
        char *author;
        char *URL;
        char *copyright;
        startup_func_t startup;
        shutdown_func_t shutdown;
        activate_func_t activate;
        deactivate_func_t deactivate;
        message_handler_func_t message_handler;
        op_array_handler_func_t op_array_handler;
        statement_handler_func_t statement_handler;
        fcall_begin_handler_func_t fcall_begin_handler;
        fcall_end_handler_func_t fcall_end_handler;
        op_array_ctor_func_t op_array_ctor;
        op_array_dtor_func_t op_array_dtor;
        int (*api_no_check)(int api_no);
        void *reserved2;
        void *reserved3;
        void *reserved4;
        void *reserved5;
        void *reserved6;
        void *reserved7;
        void *reserved8;
        DL_HANDLE handle;
        int resource_number;
};

然后, 让我们对比下PHP extension的module entry:

struct _zend_module_entry {
          unsigned short size;
          unsigned int zend_api;
          unsigned char zend_debug;
          unsigned char zts;
          struct _zend_ini_entry *ini_entry;
          struct _zend_module_dep *deps;
          char *name;
          struct _zend_function_entry *functions;
          int (*module_startup_func)(INIT_FUNC_ARGS);
          int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);
          int (*request_startup_func)(INIT_FUNC_ARGS);
          int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);
          void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS);
          char *version;
          size_t globals_size;
     #ifdef ZTS
          ts_rsrc_id* globals_id_ptr;
     #else
          void* globals_ptr;
     #endif
          void (*globals_ctor)(void *global TSRMLS_DC);
          void (*globals_dtor)(void *global TSRMLS_DC);
          int (*post_deactivate_func)(void);
          int module_started;
          unsigned char type;
          void *handle;
          int module_number;
     };

上面的结构, 可以结合我之前的文章用C/C++扩展你的PHP来帮助理解.

恩,回到主题:既然Xdebug要以Zend Extension方式加载, 那么它必然有基于Zend Extension的需求, 会是什么呢?

恩, 我们知道Xdebug有profile PHP的功能, 对, 就是statement_handler:
the statement handler callback inserts an additional opcode at the end of every statement in a script in which the callback is called. One of the primary uses for this sort of callback is to implement per-line profiling, stepping debuggers, or code-coverage utilities.

并且,因为Xdebug也提供了给用户脚本使用的函数, 所以, 它也会有部分PHP extension的实现, 并且由于它要以ZendExt方式载入的原因,所以它必须自己实现本身PHPExt部分的载入过程.

最后, 将PHP Extension的载入过程罗列如下(我会慢慢加上注释), 当然, 如果你等不及想知道, 也欢迎你直接在我的博客风雪之隅留言探讨.

以apache/mod_php5.c为例

1. 在mod_php5.c中,定义了Apache模块结构:

module MODULE_VAR_EXPORT php5_module =
     {
          STANDARD_MODULE_STUFF,
          php_init_handler,           /* initializer */
          php_create_dir,             /* per-directory config creator */
          php_merge_dir,              /* dir merger */
          NULL,                       /* per-server config creator */
          NULL,                       /* merge server config */
          php_commands,               /* command table */
          php_handlers,               /* handlers */
          NULL,                       /* filename translation */
          NULL,                       /* check_user_id */
          NULL,                       /* check auth */
          NULL,                       /* check access */
          NULL,                       /* type_checker */
          NULL,                       /* fixups */
          NULL                        /* logger */
     #if MODULE_MAGIC_NUMBER >= 19970103
          , NULL                      /* header parser */
     #endif
     #if MODULE_MAGIC_NUMBER >= 19970719
          , NULL                      /* child_init */
     #endif
     #if MODULE_MAGIC_NUMBER >= 19970728
          , php_child_exit_handler        /* child_exit */
     #endif
     #if MODULE_MAGIC_NUMBER >= 19970902
          , NULL                      /* post read-request */
     #endif
     };
/* }}} */

可见, 最开始被调用的将会是php_init_handler,

static void php_init_handler(server_rec *s, pool *p)
{
    register_cleanup(p, NULL, (void (*)(void *))apache_php_module_shutdown_wrapper, (void (*)(void *))php_module_shutdown_for_exec);
    if (!apache_php_initialized) {
        apache_php_initialized = 1;
#ifdef ZTS
        tsrm_startup(1, 1, 0, NULL);
#endif
        sapi_startup(&apache_sapi_module);
        php_apache_startup(&apache_sapi_module);
    }
#if MODULE_MAGIC_NUMBER >= 19980527
    {
        TSRMLS_FETCH();
        if (PG(expose_php)) {
            ap_add_version_component("PHP/" PHP_VERSION);
        }
    }
#endif
}

这里, 调用了sapi_startup, 这部分是初始化php的apache sapi,
然后是调用,php_apache_startup:

static int php_apache_startup(sapi_module_struct *sapi_module)
{
    if (php_module_startup(sapi_module, &apache_module_entry, 1) == FAILURE) {
        return FAILURE;
    } else {
        return SUCCESS;
    }
}

这个时候,调用了php_module_startup, 其中有:

/* this will read in php.ini, set up the configuration parameters,
       load zend extensions and register php function extensions
       to be loaded later */
    if (php_init_config(TSRMLS_C) == FAILURE) {
        return FAILURE;
    }

调用了php_init_config, 这部分读取所有的php.ini和关联的ini文件, 然后对于每一条配置指令调用:

....
 if (sapi_module.ini_entries) {
        zend_parse_ini_string(sapi_module.ini_entries, 1, php_config_ini_parser_cb, &extension_lists);
    }
然后在php_config_ini_parser_cb中:
               if (!strcasecmp(Z_STRVAL_P(arg1), "extension")) { /* load function module */
                    zval copy;
 
                    copy = *arg2;
                    zval_copy_ctor(&copy);
                    copy.refcount = 0;
                    zend_llist_add_element(&extension_lists.functions, &copy);
                } else if (!strcasecmp(Z_STRVAL_P(arg1), ZEND_EXTENSION_TOKEN)) { /* load Zend extension */
                    char *extension_name = estrndup(Z_STRVAL_P(arg2), Z_STRLEN_P(arg2));
 
                    zend_llist_add_element(&extension_lists.engine, &extension_name);
                } else {
                    zend_hash_update(&configuration_hash, Z_STRVAL_P(arg1), Z_STRLEN_P(arg1) + 1, arg2, sizeof(zval), (void **) &entry);
                    Z_STRVAL_P(entry) = zend_strndup(Z_STRVAL_P(entry), Z_STRLEN_P(entry));
                }

这里记录下来所有要载入的php extension和zend extension,
然后, 让我们回到php_module_startup, 后面有调用到了
php_ini_register_extensions(TSRMLS_C);

void php_ini_register_extensions(TSRMLS_D)
{
    zend_llist_apply(&extension_lists.engine, php_load_zend_extension_cb TSRMLS_CC);
    zend_llist_apply(&extension_lists.functions, php_load_function_extension_cb TSRMLS_CC);
 
    zend_llist_destroy(&extension_lists.engine);
    zend_llist_destroy(&extension_lists.functions);
}

我们可以看到, 对于每一个扩展记录, 都调用了一个回叫函数, 我们这里只看php_load_function_extension_cb:

static void php_load_function_extension_cb(void *arg TSRMLS_DC)
{
    zval *extension = (zval *) arg;
    zval zval;
 
    php_dl(extension, MODULE_PERSISTENT, &zval, 0 TSRMLS_CC);
}

最后, 就是核心的载入逻辑了:

void php_dl(zval *file, int type, zval *return_value, 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;
 
        if (type == MODULE_PERSISTENT) {
                extension_dir = INI_STR("extension_dir");
        } else {
                extension_dir = PG(extension_dir);
        }
 
        if (type == MODULE_TEMPORARY) {
                error_type = E_WARNING;
        } else {
                error_type = E_CORE_WARNING;
        }
 
        if (extension_dir && extension_dir[0]){
                int extension_dir_len = strlen(extension_dir);
 
                if (type == MODULE_TEMPORARY) {
                        if (strchr(Z_STRVAL_P(file), '/') != NULL || strchr(Z_STRVAL_P(file), DEFAULT_SLASH) != NULL) {
                                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Temporary module name should contain only filename");
                                RETURN_FALSE;
                        }
                }
 
                if (IS_SLASH(extension_dir[extension_dir_len-1])) {
                        spprintf(&libpath, 0, "%s%s", extension_dir, Z_STRVAL_P(file));
                } else {
                        spprintf(&libpath, 0, "%s%c%s", extension_dir, DEFAULT_SLASH, Z_STRVAL_P(file));
                }
        } else {
                libpath = estrndup(Z_STRVAL_P(file), Z_STRLEN_P(file));
        }
 
        /* load dynamic symbol */
        handle = DL_LOAD(libpath);
        if (!handle) {
                php_error_docref(NULL TSRMLS_CC, error_type, "Unable to load dynamic library '%s' - %s", libpath, GET_DL_ERROR());
                GET_DL_ERROR(); /* free the buffer storing the error */
                efree(libpath);
                RETURN_FALSE;
        }
 
        efree(libpath);
 
        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) {
                DL_UNLOAD(handle);
                php_error_docref(NULL TSRMLS_CC, error_type, "Invalid library (maybe not a PHP library) '%s' ", Z_STRVAL_P(file));
                RETURN_FALSE;
        }
        module_entry = get_module();
        if ((module_entry->zend_debug != ZEND_DEBUG) || (module_entry->zts != USING_ZTS)
                || (module_entry->zend_api != ZEND_MODULE_API_NO)) {
                /* Check for pre-4.1.0 module which has a slightly different module_entry structure :(  */
                        struct pre_4_1_0_module_entry {
                                  char *name;
                                  zend_function_entry *functions;
                                  int (*module_startup_func)(INIT_FUNC_ARGS);
                                  int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);
                                  int (*request_startup_func)(INIT_FUNC_ARGS);
                                  int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);
                                  void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS);
                                  int (*global_startup_func)(void);
                                  int (*global_shutdown_func)(void);
                                  int globals_id;
                                  int module_started;
                                  unsigned char type;
                                  void *handle;
                                  int module_number;
                                  unsigned char zend_debug;
                                  unsigned char zts;
                                  unsigned int zend_api;
                        };
 
                        char *name;
                        int zend_api;
                        unsigned char zend_debug, zts;
 
                        if ((((struct pre_4_1_0_module_entry *)module_entry)->zend_api > 20000000) &&
                                (((struct pre_4_1_0_module_entry *)module_entry)->zend_api < 20010901)
                        ) {
                                name       = ((struct pre_4_1_0_module_entry *)module_entry)->name;
                                zend_api   = ((struct pre_4_1_0_module_entry *)module_entry)->zend_api;
                                zend_debug = ((struct pre_4_1_0_module_entry *)module_entry)->zend_debug;
                                zts        = ((struct pre_4_1_0_module_entry *)module_entry)->zts;
                        } else {
                                name       = module_entry->name;
                                zend_api   = module_entry->zend_api;
                                zend_debug = module_entry->zend_debug;
                                zts        = module_entry->zts;
                        }
 
                        php_error_docref(NULL TSRMLS_CC, error_type,
                                          "%s: Unable to initialize module\n"
                                          "Module compiled with module API=%d, debug=%d, thread-safety=%d\n"
                                          "PHP    compiled with module API=%d, debug=%d, thread-safety=%d\n"
                                          "These options need to match\n",
                                          name, zend_api, zend_debug, zts,
                                          ZEND_MODULE_API_NO, ZEND_DEBUG, USING_ZTS);
                        DL_UNLOAD(handle);
                        RETURN_FALSE;
        }
        module_entry->type = type;
        module_entry->module_number = zend_next_free_module();
        module_entry->handle = handle;
 
        if ((module_entry = zend_register_module_ex(module_entry TSRMLS_CC)) == NULL) {
                DL_UNLOAD(handle);
                RETURN_FALSE;
        }
 
        if ((type == MODULE_TEMPORARY || start_now) && zend_startup_module_ex(module_entry TSRMLS_CC) == FAILURE) {
                DL_UNLOAD(handle);
                RETURN_FALSE;
        }
 
        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_FALSE;
                }
        }
        RETURN_TRUE;
}

本文转载自:http://www.laruence.com/2009/06/14/945.html

mickelfeng

mickelfeng

粉丝 237
博文 2801
码字总数 604377
作品 0
成都
高级程序员
私信 提问
深入理解PHP内存管理之谁动了我的内存

本文地址: http://www.laruence.com/2011/03/04/1894.html 转载请注明出处 首先让我们看一个问题: 如下代码的输出, var_dump(memory_get_usage()); $a = "laruence"; var_dump(memory_get_us......

晨曦之光
2012/03/09
144
0
PHP开发程序员的学习路线

兄弟连PHP培训,简单为大家梳理了每个阶段PHP程序员的技术要求,来帮助很多PHP程序做对照设定学习成长目标。 第一阶段:基础阶段(基础PHP程序员) 重点:把LNMP搞熟练(核心是安装配置基本操...

xdl刘涛
2016/08/11
23
0
php&go&python&node

2016 第二届 PHP 全球开发者大会回顾(文末附演讲嘉宾所有资料下载) 继前年的 “PHP7 初探”、去年的“高性能的 PHP ” 主题后,2017 第三届 PHP 全球开发者大会的活动主题是“高可用的 PH...

掘金官方
2017/12/20
0
0
PHP 程序员的技术成长规划

按照了解的很多PHP/LNMP程序员的发展轨迹,结合个人经验体会,抽象出很多程序员对未来的迷漫,特别对技术学习的盲目和慌乱,简单梳理了这个每个阶段PHP程序员的技术要求,来帮助很多PHP程序做...

oschina
2016/05/16
4.8K
34
php自学提升进阶路线

一、实战演练 熟悉语法&api -> 阅读zend、ci、wordpress源码 -> 用PHP独立开发项目 -> 深入了解PHP引擎实现细节、扩展编写、性能优化等 (1周)HTML+CSS HTML5+CSS3 (2-3天)留言板或记事本...

stone_
2016/07/11
292
0

没有更多内容

加载失败,请刷新页面

加载更多

golang-字符串-地址分析

demo package mainimport "fmt"func main() {str := "map.baidu.com"fmt.Println(&str, str)str = str[0:5]fmt.Println(&str, str)str = "abc"fmt.Println(&s......

李琼涛
今天
4
0
Spring Boot WebFlux 增删改查完整实战 demo

03:WebFlux Web CRUD 实践 前言 上一篇基于功能性端点去创建一个简单服务,实现了 Hello 。这一篇用 Spring Boot WebFlux 的注解控制层技术创建一个 CRUD WebFlux 应用,让开发更方便。这里...

泥瓦匠BYSocket
今天
6
0
从0开始学FreeRTOS-(列表与列表项)-3

FreeRTOS列表&列表项的源码解读 第一次看列表与列表项的时候,感觉很像是链表,虽然我自己的链表也不太会,但是就是感觉很像。 在FreeRTOS中,列表与列表项使用得非常多,是FreeRTOS的一个数...

杰杰1号
今天
8
0
Java反射

Java 反射 反射是框架设计的灵魂(使用的前提条件:必须先得到代表的字节码的 Class,Class 类 用于表示.class 文件(字节码)) 一、反射的概述 定义:JAVA 反射机制是在运行状态中,对于任...

zzz1122334
今天
6
0
聊聊nacos的LocalConfigInfoProcessor

序 本文主要研究一下nacos的LocalConfigInfoProcessor LocalConfigInfoProcessor nacos-1.1.3/client/src/main/java/com/alibaba/nacos/client/config/impl/LocalConfigInfoProcessor.java p......

go4it
昨天
9
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部