看过PHP源码或者扩展开发相关资料的都知道PHP扩展的几个关键函数,或者叫生命周期
PHP_MINIT
PHP_RINIT
PHP_RSHUTDOWN
PHP_MSHUTDOWN
其中PHP_MINIT 是php启动的时候加载扩展的时候会调用的函数 , 这个宏展开后其实真的就是定义了一个这样的C函数
zm_startup_##module(...){...}
那么这个在扩展的代码里定义的C函数是如何被执行的呢? 接下来,我们还会发现,每个扩展都有一个zend_module_entry 的结构体定义
例如swoole的扩展 zend_module_entry 定义如下
zend_module_entry swoole_module_entry =
{#if ZEND_MODULE_API_NO >= 20050922
STANDARD_MODULE_HEADER_EX, NULL, NULL,#else
STANDARD_MODULE_HEADER,#endif
"swoole",
swoole_functions,
PHP_MINIT(swoole),
PHP_MSHUTDOWN(swoole),
PHP_RINIT(swoole), //RINIT
PHP_RSHUTDOWN(swoole), //RSHUTDOWN
PHP_MINFO(swoole),
PHP_SWOOLE_VERSION,
STANDARD_MODULE_PROPERTIES
};定义这么一个结构体就加载了吗? 显然是不能的,php源码在编写的时候又不知道你要定义什么扩展。 关键点就在下面的代码,每个扩展还会有这么一句代码
ZEND_GET_MODULE(swoole)//上面这个宏展开后其实是个函数 , 以swoole为例,这个函数就是返回上面定义的 swoole_module_entry 这个结构体zend_module_entry *get_module(void) { return &name##_module_entry; }大概总结下流程
1.扩展会提供一个 get_module(void)的方法拿到扩展的 zend_module_entry 结构体的定义
扩展被编译成so文件后,在php.ini文件中配置 xxx.so, 表示加载扩展
php 启动的时候会读php.ini 文件,并做解析
4.在linux下 通过 dlopen()打开扩展的xxx.so库文件通过系统的 dlsym()获取动态库中get_module()函数的地址,执行每个扩展的get_module方法拿到 zend_module_entry 结构体
把zend_module_entry 结构体注册到php的 extension_lists 扩展列表中
7.在php的生生命周期中执行各个扩展定义的PHP_MINIT
上面是个执行流程的总结,下面进行源码求证
看源码很容易在php启动的时候找到这个函数执行 php_module_startup()
int php_module_startup(sapi_module_struct *sf, zend_module_entry *additional_modules, uint num_additional_modules){
... //根据php.ini注册扩展
php_ini_register_extensions();
...
}动态库就是在php_ini_register_extensions()这个函数中完成的注册:
//main/php_ini.cvoid 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是一个链表,保存着根据php.ini中定义的extension=xxx.so取到的全部扩展名称,其中engine是zend扩展,functions为php扩展,依次遍历这两个数组然后调用php_load_php_extension_cb()或php_load_zend_extension_cb()进行各个扩展的加载:
static void php_load_php_extension_cb(void *arg)
{#ifdef HAVE_LIBDL
php_load_extension(*((char **) arg), MODULE_PERSISTENT, 0);#endif}
HAVE_LIBDL这个宏根据dlopen()函数是否存在设置的:#Zend/Zend.m4AC_DEFUN([LIBZEND_LIBDL_CHECKS],[
AC_CHECK_LIB(dl, dlopen, [LIBS="-ldl $LIBS"])
AC_CHECK_FUNC(dlopen,[AC_DEFINE(HAVE_LIBDL, 1,[ ])])
])接着就是最关键的操作了,php_load_extension():
//ext/standard/dl.cPHPAPI int php_load_extension(char *filename, int type, int start_now){ void *handle; char *libpath;
zend_module_entry *module_entry;
zend_module_entry *(*get_module)(void);
... //调用dlopen打开指定的动态连接库文件:xx.so
handle = DL_LOAD(libpath);
... //调用dlsym获取get_module的函数指针
get_module = (zend_module_entry *(*)(void)) DL_FETCH_SYMBOL(handle, "get_module");
... //调用扩展的get_module()函数
module_entry = get_module();
... //检查扩展使用的zend api是否与当前php版本一致
if (module_entry->zend_api != ZEND_MODULE_API_NO) {
DL_UNLOAD(handle); return FAILURE;
}
...
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)) == NULL) {
DL_UNLOAD(handle); return FAILURE;
}
...
}DL_LOAD()、DL_FETCH_SYMBOL()这两个宏在linux下展开后就是:dlopen()、dlsym(),所以上面过程的实现就比较直观了:
作者:cc180912
链接:https://www.jianshu.com/p/8beeb1d482d9



