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.engine 和 extension_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 中完成,现在仔细研究下这个函数:
-
获取配置文件定义的扩展目录
extension_dir。 -
调用
dlopen打开对应的动态链接库.so文件。 -
调用
dlsym找到get_module函数的地址,通过get_module方法获得此扩展的struct_zend_module_entry结构。 -
zend api 版本校验。
-
检查扩展依赖,没有错误的时候将扩展添加到全局变量
module_registry中;如果存在内部函数,则将这些函数注册到全局变量EG(function_table)中。至此完成了 PHP 扩展的加载。
JSON扩展
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。JSON 采用完全独立于语言的文本格式,各类语言都有对其实现,包括 C、C++、C#、Java、JavaScript、Perl、Python 等,因此 JSON 格式是比较理想的数据交换语言。对象是一个无序的键/值对集合。一个对象以 "{"(左括号)开始,从 "}"(右括号)结束。每个 “名称” 后跟一个 ":"(冒号),名称/值对之间使用 ","(逗号)分隔。数组是值(value)的有序集合。一个数组以 "["(左中括号)开始,以 "]"(右中括号)结束。值之间使用 ","(逗号)分隔,值可以是双引号括起来的字符串(string)、数值(number)、true、false、null、对象(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) 以及扩展版本等相关信息。