Darwin Kernel Version 18.0.0: root:xnu-4903.201.2~1/RELEASE_X86_64 x86_64

4、本文提到的扩展开拓是PHP扩展,而不是Zend扩展

5、文章原文来自于博客(https://github.com/WGrape/Blog),关注获取最新文章

二、PHP扩展与Zend扩展1、什么是PHP扩展

在/php-src/Zend/zend_modules.h头文件中定义了_zend_module_entry构造体,这个构造体便是PHP扩展的构造体,称为module,除了一些基本信息外,紧张供应了以下个钩子函数

php扩展下载PHP源码系列之扩大的道理与开辟 AJAX

MINT :模块初始化时被调用MSHUTDOWN :模块结束化时被调用RINT :每一次要求时被调用RSHUTDOWN :每一次要求结束后被调用

struct _zend_module_entry { // 基本信息一样平常通过STANDARD_MODULE_HEADER常量添补即可 unsigned short size;unsigned int zend_api;const struct _zend_ini_entry ini_entry; int module_started;int module_number; // 扩展的名称 const char name; // 扩展的函数指针, 用于获取扩展的函数列表const struct _zend_function_entry functions; // MINT钩子函数 int (module_startup_func)(INIT_FUNC_ARGS); // MSHUTDOWN钩子函数int (module_shutdown_func)(SHUTDOWN_FUNC_ARGS); // RINT钩子函数int (request_startup_func)(INIT_FUNC_ARGS); // RSHUTDOWN钩子函数int (request_shutdown_func)(SHUTDOWN_FUNC_ARGS); // 调用phpinfo()时打印扩展信息 void (info_func)(ZEND_MODULE_INFO_FUNC_ARGS);};2、什么是Zend扩展

在/php-src/Zend/zend_extensions.h头文件中定义了_zend_extension构造体,这个构造体便是Zend扩展的构造体,称为extension。
比较PHP扩展,紧张供应了更底层的钩子函数,如下所示

struct _zend_extension { // 一些基本信息char name;char version;char author;char URL;char copyright; /Zend生命周期内的一些钩子函数/startup_func_t startup;shutdown_func_t shutdown;activate_func_t activate;deactivate_func_t deactivate;message_handler_func_t message_handler;op_array_handler_func_t op_array_handler;statement_handler_func_t statement_handler;fcall_begin_handler_func_t fcall_begin_handler;fcall_end_handler_func_t fcall_end_handler;op_array_ctor_func_t op_array_ctor;op_array_dtor_func_t op_array_dtor; /Zend生命周期内的一些钩子函数/};3、举例(1) Json扩展

Json扩展定义构造体为zend_module_entry,可知它是PHP扩展

(2) Opcache扩展

Opcache扩展定义构造体为zend_extension,可知它是Zend扩展

(3) Xdebug扩展

Xdebug扩展必须在Zend生命周期内Hook才能实现对代码的调试,以是Xdebug是Zend扩展

4、总结

扩展是区分php扩展和zend扩展的,在PHP源码中也严格区分module和extension这两个定义

module表示php extension,即PHP的扩展,通过extension=加载extension表示zend extension,即Zend的扩展,通过zend_extension=加载三、扩展的组成构造1、目录构造

在源码中的php-src/ext目录便是扩展目录,如json、mysqli、pdo等常用的扩展,个中每个扩展都紧张由以下文件组成

tests :单元测试目录config.m4 :扩展的编译配置文件(Unix系统)config.w32 :扩展的编译配置文件(Windows系统)php_{$module}.h :扩展的头文件{$module}.c :扩展源文件{$module}.php :扩展的测试文件2、代码构造(1) 单元测试

在编译扩展成功后,会在扩展目录下天生一个run-test.php脚本文件,这个脚本会自动实行tests目录下的所有单元测试。

此外在扩展目录下还会自动天生一个{$module}.php扩展的测试文件,可以方便的测试扩展是否可以正常加载和利用

(2) 编译配置文件

扩展下载后只有源码,须要进行编译天生.so扩展文件后才可以被PHP利用,config.m4和config.w32这两个文件便是在后续实行phpize阶段会利用到的编译配置文件。

m4是一种宏处理文件,紧张由PHP_ARG_WITH和PHP_ARG_ENABLE两部分构成,一样平常利用第二部分即可,用于开启指定的扩展。
这样在编译阶段,就会判断$PHP_PHP_HELLO变量不是no,从而实行此扩展的编译。

个中dnl宏会删除本行多余的字符,可以大略的理解为注释,如下所示,如果须要编译php_hello这个扩展,把PHP_ARG_ENABLE部分最前面的dnl宏都去掉即可

dnl If your extension references something external, use with:dnl PHP_ARG_WITH(php_hello, for php_hello support,dnl Make sure that the comment is aligned:dnl [ --with-php_hello Include php_hello support])dnl Otherwise use enable:dnl PHP_ARG_ENABLE(php_hello, whether to enable php_hello support,dnl Make sure that the comment is aligned:dnl [ --enable-php_hello Enable php_hello support])if test "$PHP_PHP_HELLO" != "no"; then PHP_NEW_EXTENSION(php_hello, php_hello.c, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)fi(3) 扩展头文件

一样平常名为php_{$module}.h的是扩展头文件,一样平常用于定义须要的常量和函数等

(4) 扩展源文件

一样平常名称为{$module}.c的是扩展源文件,紧张由以下部分组成

zend_module_entry :定义扩展的构造体PHP_FUNCTION :定义扩展的函数Hook_FUNCTION :如 PHP_MINIT_FUNCTION 等四、扩展的加载过程(1) 源码注释

在php-src/main/main.c文件中,php_module_startup()函数会实行扩展的加载与初始化。

int php_module_startup(sapi_module_struct sf, zend_module_entry additional_modules, uint num_additional_modules){ // Zend引擎初始化 zend_startup(&zuf, NULL); // 注册常量 php_output_register_constants();php_rfc1867_register_constants(); // 注册ini配置if (php_init_config() == FAILURE) {return FAILURE;}REGISTER_INI_ENTRIES();zend_register_standard_ini_entries(); // 注册$_GET/$_POST/$_SERVER/$_REQUEST等全局变量 php_startup_auto_globals() // 加载静态编译的扩展这些扩展包含在main/internal_functions.c中 if (php_register_internal_extensions_func(TSRMLS_C) == FAILURE) { php_printf("Unable to start builtin modules\n"); return FAILURE; } // 注册SAPI的扩展模块,即additional_modules中的扩展 php_register_extensions_bc(additional_modules, num_additional_modules TSRMLS_CC); // 根据ini配置,先加载Zend扩展, 再加载PHP扩展 php_ini_register_extensions(TSRMLS_C); // 扩展初始化, 触发MINT()钩子 zend_startup_modules(TSRMLS_C); zend_startup_extensions();}(2) php_ini_register_extensions函数

在此函数中,extension_lists是一个保存着解析ini配置后得到的所有扩展(包括PHP扩展和Zend扩展)的链表,通过利用&extension_lists.engine和&extension_lists.functions获取PHP扩展列表和Zend扩展链表,然后通过php_load_zend_extension_cb()或php_load_php_extension_cb()分别完身分歧类型扩展的加载

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);}(3) 扩展的生命周期

如在PHP扩展与Zend扩展一节中看到的,这两种扩展分别供应了不同的钩子函数,这些函数在PHP生命周期内的调用顺序如下图所示

五、扩展的开拓教程1、获取PHP源码

获取PHP源码后,切换至7.1.19版本,按如下命令操作

git clone https://github.com/php/php-srcgit checkout remotes/origin/PHP-7.1.192、天生扩展的根本文件

切换到ext扩展目录下,在此目录下,有一个ext_skel脚本,可以用来自动天生扩展的根本文件。
比如创建一个print_hello的扩展,按如下命令操作

cd php-src/ext/./ext_skel --extname=print_hello

实行成功后,会得到如下提示

回到在ext目录下,创造已经成功天生print_hello目录,紧张包含如下文件

tests :单元测试目录config.m4 :扩展的编译配置文件(Unix系统)config.w32 :扩展的编译配置文件(Windows系统)php_print_hello.h :扩展的头文件print_hello.c :扩展源文件print_hello.php :扩展的测试文件

3、修正编译配置文件

打开config.m4配置文件,如下所示

找到PHP_ARG_ENABLE这段代码,删掉前面的dnl宏,如下所示

# 修正前dnl PHP_ARG_ENABLE(print_hello, whether to enable print_hello support,dnl Make sure that the comment is aligned:dnl [ --enable-print_hello Enable print_hello support])# 修正后PHP_ARG_ENABLE(print_hello, whether to enable print_hello support,Make sure that the comment is aligned:[ --enable-print_hello Enable print_hello support])4、修正print_hello.c文件

找到PHP_FUNCTION(表示定义的扩展函数),在如下confirm_print_hello_compiled函数中添加一句输出hello world的代码

PHP_FUNCTION(confirm_print_hello_compiled){char arg = NULL;size_t arg_len, len;zend_string strg;if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &arg, &arg_len) == FAILURE) {return;}strg = strpprintf(0, "Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.", "print_hello", arg); // 新增下面输出hello world的代码 php_printf("hello world!\n"); RETURN_STR(strg);}5、编译扩展

通过实行以下命令实行对扩展的编译处理

cd print_hellophpize./configure --with-php-config=/usr/bin/php-configmakesudo make

实行make命令成功后如下所示

实行sudo make install命令成功后如下所示

6、实行扩展测试脚本

测试脚本会先动态加载print_hello扩展,并输出扩展中所有供应的函数,末了实行在print_hello.c源文件中定义的confirm_print_hello_compiled 函数,如果正常实行则解释扩展加载且实行成功

$br = (php_sapi_name() == "cli")? "":"<br>";// 判断扩展是否已加载if(!extension_loaded('print_hello')) { // 在运行时动态加载扩展库 // 如果加载失落败,须要修正php.ini配置文件,直接开启动态加载扩展的选项enable_dl = On即可,在命令行下实行不须要重启PHP dl('print_hello.' . PHP_SHLIB_SUFFIX);}$module = 'print_hello';// 依次输出扩展供应的所有函数$functions = get_extension_funcs($module);echo "Functions available in the test extension:$br\n";foreach($functions as $func) { echo $func."$br\n";}echo "$br\n";// 如果扩展加载成功, 则实行 confirm_print_hello_compiled 函数$function = 'confirm_' . $module . '_compiled';if (extension_loaded($module)) {$str = $function($module);} else {$str = "Module $module is not compiled into PHP";}echo "$str\n";

脚本实行成功后如下所示

7、结束

到目前为止,大略的print_hello扩展就已经开拓完成,当然还可以在print_hello.c源文件中定义更多的扩展函数,做更多有趣的事情!
不过篇幅有限就不再讲解,关于扩展的高阶利用请关注博客(https://github.com/WGrape/Blog),获取最新文章!