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)
以及扩展版本等相关信息。