PHP扩展

扩展的实现

前面已经讲了,PHP 扩展需要实现一个结构体——zend_module_entry,现在我们来研究下这个结构体。

struct _zend_module_entry {
    unsigned short size;
    unsigned int zend_api;
    unsigned char zend_debug;
    unsigned char zts;
    const struct _zend_ini_entry *ini_entry;
    const struct _zend_module_dep *deps;
    const char *name;
    const 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);
    const char *version;
    size_t globals_size;
    void* globals_ptr;
    void (*globals_ctor)(void *global);
    void (*globals_dtor)(void *global);
    int (*post_deactivate_func)(void);
    int module_started;
    unsigned char type;
    void *handle;
    int module_number;
    const char *build_id;
};

主要字段说明如下。

  • size:此结构体的大小,即 sizeof(struct _zend_module_entry)

  • zend_api:PHP 扩展版本号。

  • zend_debug:是否开启 debug 模式。

  • zts:线程是否安全。

  • ini_entry:php.ini 相关,详情请看第 8 章。

  • name:扩展名称,不得与其他扩展名相同。

  • functions:扩展提供的内部函数数组。

  • module_startup_func:模块初始化阶段回调的钩子函数。

  • module_shutdown_func:模块关闭阶段回调的钩子函数。

  • request_startup_func:请求初始化阶段回调的钩子函数。

  • request_shutdown_func:请求关闭阶段回调的钩子函数。

  • info_func:被 php_info 函数调用的展示扩展信息的函数。

  • version:扩展的版本号。

如果当前扩展是以动态链接库的方式来使用的,那么扩展除了要实现了 struct_zend_module_entry 结构外,还需要实现 get_module 函数来让内核获得当前扩展结构。此函数的实现有统一的规范,通过宏来实现:

#define ZEND_GET_MODULE(name) \
    BEGIN_EXTERN_C()\
    ZEND_DLEXPORT  zend_module_entry  *get_module(void)  {  return  &name##_module_entry; }\
    END_EXTERN_C()

由此可以看出,实现的函数名为 get_module,如果此扩展名为 ***,那么定义扩展的变量名必须为 ***_module_entry。例如,JSON 扩展为 json_module_entry。从 8.4 节中知道,解析 php.ini 时,PHP 扩展和 Zend 扩展相关配置都会被加载到全局变量 extension_lists 中,PHP 扩展保存在 extension_lists.functions 中,而 Zend 扩展保存在 extension_lists.engine 中。在 PHP_MINIT 阶段,通过 php_ini_register_extensions 函数完成扩展的注册:

void php_ini_register_extensions(void)
{
    // Zend扩展
    zend_llist_apply(&extension_lists.engine, php_load_zend_extension_cb);
    // PHP扩展
    zend_llist_apply(&extension_lists.functions, php_load_php_extension_cb);

    zend_llist_destroy(&extension_lists.engine);
    zend_llist_destroy(&extension_lists.functions);
}

在这个函数中,依次遍历 extension_lists.engineextension_lists.functions,调用 php_load_php_extension_cb 函数完成对 PHP 扩展的加载,调用 php_load_zend_extension_cb 函数完成对 Zend 扩展的加载:

static void php_load_php_extension_cb(void *arg)
{
#ifdef HAVE_LIBDL
    php_load_extension(*((char **) arg), MODULE_PERSISTENT, 0);
#endif
}

由源码可知,PHP 扩展加载的关键步骤在 php_load_extension 中完成,现在仔细研究下这个函数:

  1. 获取配置文件定义的扩展目录 extension_dir

  2. 调用 dlopen 打开对应的动态链接库 .so 文件。

  3. 调用 dlsym 找到 get_module 函数的地址,通过 get_module 方法获得此扩展的 struct_zend_module_entry 结构。

  4. zend api 版本校验。

  5. 检查扩展依赖,没有错误的时候将扩展添加到全局变量 module_registry 中;如果存在内部函数,则将这些函数注册到全局变量 EG(function_table) 中。至此完成了 PHP 扩展的加载。

JSON扩展

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。JSON 采用完全独立于语言的文本格式,各类语言都有对其实现,包括 C、C++、C#、Java、JavaScript、Perl、Python 等,因此 JSON 格式是比较理想的数据交换语言。对象是一个无序的键/值对集合。一个对象以 "{"(左括号)开始,从 "}"(右括号)结束。每个 “名称” 后跟一个 ":"(冒号),名称/值对之间使用 ","(逗号)分隔。数组是值(value)的有序集合。一个数组以 "["(左中括号)开始,以 "]"(右中括号)结束。值之间使用 ","(逗号)分隔,值可以是双引号括起来的字符串(string)、数值(number)、truefalsenull、对象(object)或者数组(array)。这些结构可以嵌套。字符串(string)是由双引号括起来的任意数量 unicode 字符的集合,使用 "\"(反斜杠)转义。一个字符(character)即一个单独的字符串(character string)。

除去未曾使用的八进制与十六进制格式,数值(number)与 C 或者 Java 语言的数值非常相似。我们来看下 PHP 中的 JSON 扩展,其扩展定义如下:

zend_module_entry json_module_entry = {
    STANDARD_MODULE_HEADER,
    "json",
    json_functions,
    PHP_MINIT(json),
    NULL,
    NULL,
    NULL,
    PHP_MINFO(json),
    PHP_JSON_VERSION,
    PHP_MODULE_GLOBALS(json),
    PHP_GINIT(json),
    NULL,
    NULL,
    STANDARD_MODULE_PROPERTIES_EX
};

此结构体的前 6 个字段是用宏 STANDARD_MODULE_HEADER 来定义的。多读几个 PHP 自带的 PHP 扩展源码,会发现绝大部分扩展用这个宏来定义前 6 个字段,实际结构如下:

sizeof(zend_module_entry), ZEND_MODULE_API_NO, ZEND_DEBUG, USING_ZTS, NULL, NULL

当前 JSON 扩展实际展开后如下(注意,部分参数根据实际的编译参数可能有所不同,如是否开启 debug):

sizeof(zend_module_entry), 20160303, 0, 0, NULL, NULL

STANDARD_MODULE_HEADER 之后是扩展名 “json”,然后是扩展 JOSN 提供的内部函数数组 json_functions

static const zend_function_entry json_functions[] = {
    PHP_FE(json_encode, arginfo_json_encode)
    PHP_FE(json_decode, arginfo_json_decode)
    PHP_FE(json_last_error, arginfo_json_last_error)
    PHP_FE(json_last_error_msg, arginfo_json_last_error_msg)
    PHP_FE_END
};

可以看出,JOSN 扩展提供了 4 个内部函数,其各自实现不在此书的讨论范围之内。

结构体的后面是扩展 JSON 提供的模块初始化阶段回调的钩子函数 PHP_MINFO_FUNCTION(json) 以及扩展版本等相关信息。