欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  后端开发

PHP扩展开发-内核执行流程与扩展结构

程序员文章站 2024-04-05 21:30:43
...

在开发扩展之前,最好了解下PHP内核的执行流程,PHP大概包括三个方面:

SAPI
Zend VM
内部扩展

  • Zend VM是PHP的虚拟机,与JVM类似,都是各自语言的编译/执行的核心。它们都会把各自的代码先编译为一种中间代码,PHP的通常叫opcode,Java通常叫bytecode,不同的是PHP的opcode直接被Zend VM的执行单元调用对应的C函数执行,不会显示保留下来(可以cache保留),而Java通常是生成class文件保留下来。而这一点可能也是PHP interpreter的名称的由来吧。其实相对严格的C/C++等编译型语言,PHP和Java更多的是结合了编译型和解释性的风格。
  • SAPI可以看作是Zend VM向外界提供编译/执行PHP代码服务的方式和规范。无论是作为cli/cgi/fastcgi/apache_mod与其他程序交互,还是嵌入其他语言中如C/C++等,都可以通过SAPI的规范实现。它的一个重要数据结构就是sapi_module_struct(main/SAPI.h line 217)
  • 内部扩展部分可以看作是搭建在Zend VM和SAPI之上的库,为PHP开发人员提供性能和易用性上的保证。Java的各种包/Python的各种模块功能类似,不同的是PHP中为了性能是用C扩展来实现的,类似的在Java中可以通过JNI来实现,Python中如_socket和_select(多路复用)都不是原生Python实现。
  • 生命周期

    关于各种SAPI或者PHP本身的生命周期,可能会和其他组件如apache耦合,后续再细谈。关于PHP扩展的生命周期,这里借用一张图。流程应该是很容易明白的,关于这个过程,网上也有很多资料,不再赘述。我们开发扩展需要注意的几个地方也可以对应到图中的某些节点:

    全局变量的定义,通常是zend_modulename_globals
    模块的初始化,包括资源/类/常量/ini配置等模块级的初始化
    请求的初始化,包括与单次请求相关的一些初始化
    请求的结束,清理单次请求相关的数据/内存
    模块的卸载,清理模块相关的数据/内存

    基本上我们要做的就是按照上面的流程,实现相关的内置函数,定义自己的资源/全局变量/类/函数等。值得注意的地方是在在嵌入其他语言如Python或者被嵌入其他组件如apache时,要小心多进程多线程相关的问题。

    PHP扩展结构

    使用php-src/ext/ext_skel可以生成PHP扩展的框架

    ./ext_skel --extname=myext[tan@tan ~/software/needbak/php-5.5.20/ext 12:24]$==> ls myext/config.m4  config.w32  CREDITS  EXPERIMENTAL  myext.c  myext.php  php_myext.h  tests

    比较重要的文件是config.m4(当然还有源码),config.m4文件可以使用phpize命令生成configure文件,其中说明了我们是否开启模块,以及外部依赖的库。

    //config.m4//如果你的扩展依赖其他外部库dnl PHP_ARG_WITH(myext, for myext support,dnl Make sure that the comment is aligned:dnl [  --with-myext             Include myext support])//扩展不依赖外部库dnl PHP_ARG_ENABLE(myext, whether to enable myext support,dnl Make sure that the comment is aligned:dnl [  --enable-myext           Enable myext support])//寻找并包含头文件if test "$PHP_MYEXT" != "no"; then  dnl Write more examples of tests here...  dnl # --with-myext -> check with-path  dnl SEARCH_PATH="/usr/local /usr"     # you might want to change this  dnl SEARCH_FOR="/include/myext.h"  # you most likely want to change this  dnl if test -r $PHP_MYEXT/$SEARCH_FOR; then # path given as parameter  dnl   MYEXT_DIR=$PHP_MYEXT  dnl else # search default path list  dnl   AC_MSG_CHECKING([for myext files in default path])  dnl   for i in $SEARCH_PATH ; do  dnl     if test -r $i/$SEARCH_FOR; then  dnl       MYEXT_DIR=$i  dnl       AC_MSG_RESULT(found in $i)  dnl     fi  dnl   done  dnl fi  dnl  dnl if test -z "$MYEXT_DIR"; then  dnl   AC_MSG_RESULT([not found])  dnl   AC_MSG_ERROR([Please reinstall the myext distribution])  dnl fi  dnl # --with-myext -> add include path  dnl PHP_ADD_INCLUDE($MYEXT_DIR/include)  //加载的lib位置  dnl # --with-myext -> check for lib and symbol presence  dnl LIBNAME=myext # you may want to change this  dnl LIBSYMBOL=myext # you most likely want to change this   dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL,  dnl [  dnl   PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $MYEXT_DIR/$PHP_LIBDIR, MYEXT_SHARED_LIBADD)  dnl   AC_DEFINE(HAVE_MYEXTLIB,1,[ ])  dnl ],[  dnl   AC_MSG_ERROR([wrong myext lib version or lib not found])  dnl ],[  dnl   -L$MYEXT_DIR/$PHP_LIBDIR -lm  dnl ])  dnl  dnl PHP_SUBST(MYEXT_SHARED_LIBADD)  PHP_NEW_EXTENSION(myext, myext.c, $ext_shared)fi
    //php_myext.h#ifndef PHP_MYEXT_H#define PHP_MYEXT_Hextern zend_module_entry myext_module_entry;#define phpext_myext_ptr &myext_module_entry//导出符号,在链接的时候有用#ifdef PHP_WIN32# define PHP_MYEXT_API __declspec(dllexport)#elif defined(__GNUC__) && __GNUC__ >= 4# define PHP_MYEXT_API __attribute__ ((visibility("default")))#else# define PHP_MYEXT_API#endif#ifdef ZTS#include "TSRM.h"#endif//几个核心函数的声明PHP_MINIT_FUNCTION(myext);PHP_MSHUTDOWN_FUNCTION(myext);PHP_RINIT_FUNCTION(myext);PHP_RSHUTDOWN_FUNCTION(myext);PHP_MINFO_FUNCTION(myext);//自动生成的测试函数声明,我们自己定义的模块函数需要在此声明PHP_FUNCTION(confirm_myext_compiled);   //全局变量在这定义,展开后是zend_myext_globals结构体ZEND_BEGIN_MODULE_GLOBALS(myext)    long  global_value;    char *global_string;ZEND_END_MODULE_GLOBALS(myext)//线程安全与非线程安全下获取全局变量的方式#ifdef ZTS#define MYEXT_G(v) TSRMG(myext_globals_id, zend_myext_globals *, v)#else#define MYEXT_G(v) (myext_globals.v)#endif#endif /* PHP_MYEXT_H */
    //myext.c#ifdef HAVE_CONFIG_H#include "config.h"#endif#include "php.h"#include "php_ini.h"#include "ext/standard/info.h"#include "php_myext.h"//全局变量声明ZEND_DECLARE_MODULE_GLOBALS(myext)/* True global resources - no need for thread safety here */static int le_myext;//模块函数的导出const zend_function_entry myext_functions[] = {    PHP_FE(confirm_myext_compiled,  NULL)       /* For testing, remove later. */    PHP_FE_END  /* Must be the last line in myext_functions[] */};//模块结构zend_module_entry myext_module_entry = {#if ZEND_MODULE_API_NO >= 20010901    STANDARD_MODULE_HEADER,#endif    "myext",    myext_functions,    PHP_MINIT(myext),    PHP_MSHUTDOWN(myext),    PHP_RINIT(myext),       /* Replace with NULL if there's nothing to do at request start */    PHP_RSHUTDOWN(myext),   /* Replace with NULL if there's nothing to do at request end */    PHP_MINFO(myext),#if ZEND_MODULE_API_NO >= 20010901    PHP_MYEXT_VERSION,#endif    STANDARD_MODULE_PROPERTIES};#ifdef COMPILE_DL_MYEXTZEND_GET_MODULE(myext)#endif//ini配置文件的设置PHP_INI_BEGIN()    STD_PHP_INI_ENTRY("myext.global_value",      "42", PHP_INI_ALL, OnUpdateLong, global_value, zend_myext_globals, myext_globals)    STD_PHP_INI_ENTRY("myext.global_string", "foobar", PHP_INI_ALL, OnUpdateString, global_string, zend_myext_globals, myext_globals)PHP_INI_END()//初始化全局变量static void php_myext_init_globals(zend_myext_globals *myext_globals){    myext_globals->global_value = 0;    myext_globals->global_string = NULL;}//模块加载时的函数PHP_MINIT_FUNCTION(myext){    /* If you have INI entries, uncomment these lines REGISTER_INI_ENTRIES(); */    return SUCCESS;}//模块卸载时函数PHP_MSHUTDOWN_FUNCTION(myext){    /* uncomment this line if you have INI entries UNREGISTER_INI_ENTRIES(); */    return SUCCESS;}//请求初始化函数PHP_RINIT_FUNCTION(myext){    return SUCCESS;}//请求关闭函数PHP_RSHUTDOWN_FUNCTION(myext){    return SUCCESS;}//模块信息,phpinfoPHP_MINFO_FUNCTION(myext){    php_info_print_table_start();    php_info_print_table_header(2, "myext support", "enabled");    php_info_print_table_end();    /* Remove comments if you have entries in php.ini DISPLAY_INI_ENTRIES(); */}//测试函数PHP_FUNCTION(confirm_myext_compiled){    char *arg = NULL;    int arg_len, len;    char *strg;    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) {        return;    }    len = spprintf(&strg, 0, "Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.", "myext", arg);    RETURN_STRINGL(strg, len, 0);}