作为一名 phper, 对 php 扩展是如何实现的一直都有点好奇,今天就来看看 php 扩展的架构大概是什么样子的。随意看了几个扩展源码,发现 json 扩展的源码实现还是蛮简单的。不过再看它之前,咱们先自己生成一个扩展来看看。
[root@iz2ze9ryeo103b85y5iy2wz ext]# pwd
/usr/local/src/php/ext
[root@iz2ze9ryeo103b85y5iy2wz ext]# ./ext_skel --extname=lucas
执行以上命令后,便在当前目录下生成一个 lucas 文件夹。进去后会发现一个与扩展同名的 c 文件。它就是一个扩展最原始的架构,省略部分后大概如下:
// lucas_functions[] Every user visible function must have an entry in lucas_functions[].
const zend_function_entry lucas_functions[] = {
PHP_FE(confirm_lucas_compiled, NULL) /* For testing, remove later. */
PHP_FE_END /* Must be the last line in lucas_functions[] */
};
// Every user-visible function in PHP should document itself in the source */
// proto string confirm_lucas_compiled(string arg) Return a string to confirm that the module is compiled in
PHP_FUNCTION(confirm_lucas_compiled)
{
...
}
PHP_MINIT_FUNCTION(lucas)
{
return SUCCESS;
}
PHP_MSHUTDOWN_FUNCTION(lucas)
{
return SUCCESS;
}
// Remove if there's nothing to do at request start
PHP_RINIT_FUNCTION(lucas)
{
return SUCCESS;
}
// Remove if there's nothing to do at request end
PHP_RSHUTDOWN_FUNCTION(lucas)
{
return SUCCESS;
}
// lucas_module_entry
zend_module_entry lucas_module_entry = {
...
};
#ifdef COMPILE_DL_LUCAS
ZEND_GET_MODULE(lucas)
#endif
按顺序大致包含以下几个部分:
- 声明 zend 函数块
- 实现导出函数
- 模块初始化
- 关闭模块
- 请求初始化
- 关闭请求
- 声明 zend 模块
- 实现 get_module() 函数
咱们现在就借助 json 扩展的对应源码来看下每部分都干了啥。
一、声明 zend 函数块
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
};
先考虑一个宏观问题,函数在扩展中怎么引入到 zend 引擎,然后在 php 代码中调用呢?譬如常用的 json_encode() 和 json_decode() 函数。这里就要用到 zend_function_entry 结构体:
typedef struct _zend_function_entry {
char *fname; //指定在 php 脚本里调用的函数名
void (*handler) (INTERNAL_FUNCTION_PARAMETERS); //指向对应c函数的句柄,就是声明的导出函数句柄
unsigned char *func_arg_types; //标识一些参数是否要强制性地按引用方式进行传递
} zend_function_entry;
zend 引擎在载入扩展时,会自动把这个结构体数组中的所有函数引入到函数表中,这样 php 脚本就可以直接调用这些函数了。
二、实现导出函数
在确定函数能在php脚本中被调用以后,就考虑根据需要实现函数功能了。譬如 json_encode() 函数实现如下:
static PHP_FUNCTION(json_encode)
{
zval *parameter;
php_json_encoder encoder;
smart_str buf = {0};
zend_long options = 0;
zend_long depth = PHP_JSON_PARSER_DEFAULT_DEPTH;
ZEND_PARSE_PARAMETERS_START(1, 3)
Z_PARAM_ZVAL(parameter)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(options)
Z_PARAM_LONG(depth)
ZEND_PARSE_PARAMETERS_END();
php_json_encode_init(&encoder);
encoder.max_depth = (int)depth;
php_json_encode_zval(&buf, parameter, (int)options, &encoder);
JSON_G(error_code) = encoder.error_code;
if (encoder.error_code != PHP_JSON_ERROR_NONE && !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
smart_str_free(&buf);
RETURN_FALSE;
}
smart_str_0(&buf); /* copy? */
if (buf.s) {
RETURN_NEW_STR(buf.s);
}
RETURN_EMPTY_STRING();
}
实现导出函数是通过 PHP_FUNCTION 宏来定义的,然后在函数体中实现想要的功能。并且在 zend 函数块中声明的所有函数都必须实现,否则会出现严重错误。
三、模块初始化
static PHP_MINIT_FUNCTION(json)
{
zend_class_entry ce;
INIT_CLASS_ENTRY(ce, "JsonSerializable", json_serializable_interface);
php_json_serializable_ce = zend_register_internal_interface(&ce);
/* options for json_encode */
PHP_JSON_REGISTER_CONSTANT("JSON_HEX_TAG", PHP_JSON_HEX_TAG);
PHP_JSON_REGISTER_CONSTANT("JSON_HEX_AMP", PHP_JSON_HEX_AMP);
PHP_JSON_REGISTER_CONSTANT("JSON_HEX_APOS", PHP_JSON_HEX_APOS);
PHP_JSON_REGISTER_CONSTANT("JSON_HEX_QUOT", PHP_JSON_HEX_QUOT);
PHP_JSON_REGISTER_CONSTANT("JSON_FORCE_OBJECT", PHP_JSON_FORCE_OBJECT);
PHP_JSON_REGISTER_CONSTANT("JSON_NUMERIC_CHECK", PHP_JSON_NUMERIC_CHECK);
PHP_JSON_REGISTER_CONSTANT("JSON_UNESCAPED_SLASHES", PHP_JSON_UNESCAPED_SLASHES);
PHP_JSON_REGISTER_CONSTANT("JSON_PRETTY_PRINT", PHP_JSON_PRETTY_PRINT);
PHP_JSON_REGISTER_CONSTANT("JSON_UNESCAPED_UNICODE", PHP_JSON_UNESCAPED_UNICODE);
PHP_JSON_REGISTER_CONSTANT("JSON_PARTIAL_OUTPUT_ON_ERROR", PHP_JSON_PARTIAL_OUTPUT_ON_ERROR);
PHP_JSON_REGISTER_CONSTANT("JSON_PRESERVE_ZERO_FRACTION", PHP_JSON_PRESERVE_ZERO_FRACTION);
PHP_JSON_REGISTER_CONSTANT("JSON_UNESCAPED_LINE_TERMINATORS", PHP_JSON_UNESCAPED_LINE_TERMINATORS);
...
return SUCCESS;
}
从这串代码中可以看到,json_encode() 函数中的第二个参数引用的常量都是在这定义的。可以看出在该函数中可以做些初始化的工作,如定义常量等。
四、关闭模块
这块没啥好说的,做一些收尾工作。
五、请求初始化
这块 json 扩展并没有相应的实现部分,但根据其它扩展的实现部分,这块主要是当请求到达后,php 会初始化执行脚本的基本环境。还可以记录调用该部分的开始时间,随后在请求结束时候记录结束时间,这样就能够记录下处理请求所花费的时间了。
六、关闭请求
略。
七、声明 zend 模块
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
};
php 扩展的信息是保存在 zend_module_entry 结构体中的,它包含了所有需要向 zend 引擎提供的模块信息。定义如下:
struct _zend_module_entry {
...
unsigned char zend_debug; //标识是否为调试版本
const char *name; //模块名称
const struct _zend_function_entry *functions; //zend 函数块的指针
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); //模块信息函数,当脚本调用 phpinfo() 函数时,就会调用这个函数
const char *version; //模块版本号
...
};
八、实现 get_module() 函数
#ifdef COMPILE_DL_JSON
#ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE()
#endif
ZEND_GET_MODULE(json)
#endif
这块基本没啥变化。 get_module() 函数是通过 ZEND_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()
可以看出 ZEND_GET_MODULE 宏展开后就是 get_module() 函数,并返回一个 zend_module_entry 指针,指向的也就是上面提到的保存扩展所有信息的 zend_module_entry 结构体。所以 php 内核就是通过 get_module() 函数与扩展进行通信的。另外,当扩展被编译成一个内建的模块时, get_module() 函数将不会被实现。
以上就是 php 扩展的大概架构。欢迎交流。