PHP7 扩展开发(二) 执行流程

编辑于 2016-09-29

* 移动设备下, 可左滑手指以查看较宽代码

经常看到 php 作为 Apache 的模块运行? 或者有个叫 PHP CLI (命令行模式) 的东西? 其实不管是什么方式, 本质上都是对 SAPI 的一种实现.

更正为:先 MINIT 再 fork

了解 SAPI

SAPI (Server Application Programming Interface), 定义了一种规范, PHP 就可以使用此规范扩展服务器, 通常也就是我们常见的 Web 服务器. SAPI 提供了对 PHP 外部环境的抽象, 使得 PHP 无需关心太多的服务器细节, 从而保持一定的独立性.

PHP 的启动和停止过程

PHP 的启动和终止过程有四个, 分别是:

  • PHP 启动 - MINIT (Module Initialzation)

  • 请求到达 - RINIT (Request Initialzation)

  • 请求结束 - RSHUTDOWN (Request Shutdown)

  • PHP 停止 - MSHUTDOWN (Module Shutdown)

PHP 启动

比如 PHP 作为 Apache 的一个模块时, 如果启动 Apache, PHP 就会随着 Apache 的启动常驻内存, 它会把所有已加载扩展的 MINIT (Module Initialization)方法都执行一遍. 在这个时间里, 扩展可以完成自己的一些初始化工作. 这里定义的东西会常驻内存直到 Apache 卸载此模块.

内核代码中预置了 PHP_MINIT_FUNCTION 宏函数, 来帮助我们完成这个功能.

//ext 是扩展的名字
//在 MINIT 中初始化. 在每次页面请求中输出, 看是否变化
int minitTime;

PHP_MINIT_FUNCTION(ext)
{
    minitTime = time(NULL);

    //返回 SUCCESS 代表正常, 返回 FALIURE 则不会加载此扩展.
    return SUCCESS;
}

请求到达

当一个请求到达时(如访问页面), PHP 会扫描各个扩展, 遍历执行它们各自的RINIT方法.

内核代码中预置了 PHP_RINIT_FUNCTION 宏函数来帮助我们实现这个功能:

//在 RINIT 中初始化, 看看每次页面请求是否变化.
int rinitTime;

PHP_RINIT_FUNCTION(ext)
{
    rinitTime = time(NULL);

    return SUCCESS;
}

请求结束

等到这个请求处理完成, 或者用户执行了 exit, 这时 PHP 会启动回收机制. 它会执行所有已加载扩展的 RSHUTDOWN 方法, 然后释放掉这次请求中申请的变量等.

内核代码中预置了 PHP_RSHUTDOWN_FUNCTION 宏来帮助我们完成此功能.

PHP_RSHUTDOWN_FUNCTION(ext)
{
    //每次请求结束都追加到文件
    FILE *fp = fopen("rshutdown-time.txt", "a+");
    fprintf(fp, "%ld\n", time(NULL));
    fcolse(fp);

    return SUCCESS;
}

PHP 停止

比如 Apache 停止, 就会进入 MSHUTDOWN 阶段, 然后执行所有已加载扩展的 MSHUTDOWN 方法.

自然内核中预置了 PHP_MSHUTDOWN_FUNCTION 宏来实现:

PHP_RSHUTDOWN_FUNCTION(ext)
{
    //最后追加到文件
    FILE *fp = fopen("mshutdown-time.txt", "a+");
    fprintf(fp, "%ld\n", time(NULL));
    fclose(fp);

    return SUCCESS;
}

phpinfo

通过 phpinfo() 或者 php -i, 信息会组合成一个格式良好的、易于解析输出. 你的扩展可以轻松地通过 MINFO (模块信息)来添加这些块:

PHP_MINFO_FUNCTION(ext) {
    php_info_print_table_start();
    php_info_print_table_row(2, "Sample Module", "enabled");
    php_info_print_table_row(2, "version", PHP_EXT_EXTVER);
    php_info_print_table_end();

    /* Remove comments if you have entries in php.ini
    DISPLAY_INI_ENTRIES();
    */
}

结果

上述四个宏都是在 ext.c 中编写, 实际上它们在 main/php.h 中被定义.

我们定义一个函数来输出上面定义的两个时间值(后续章节会讲到, 这里知道意思即可):

PHP_FUNCTION(ext_test)
{
    php_printf("%d<br>", minitTime);
    php_printf("%d<br>", rinitTime);

    return ;
}

其结果是:

minitTime 的值每次请求都不变

rinitTime 的值每次请求都改变

rshutdown-time.txt 在每次请求结束后写出

mshutdown-time.txt 仅在 Apache 停止后写出

不同运行模式下的差异

上面是典型的流程, 实际情况取决于 PHP 的运行模式.


  • 以 CLI/CGI 模式运行

    CLI/CGI 也是对上述 SAPI 的一种实现, 但是很特殊: PHP 完全在一个单独的请求中完成. 也就是说上述的四个过程会依次走一遍, 然后结束. 显然这种模式效率很低, 通常用作运行脚本而不是处理大量的用户数据.

  • 以多进程模式运行

    当 Apache 处于 Pre-fork MPM工作模式下(或者其它类似的情况), 会先执行 MINIT,然后 fork 出好几个进程, 每一个进程都有独立的内存空间. 最后各自执行一次 MSHUTDOWN 操作. PHP-FPM也是先进行 MINIT 操作再 fork.

  • 以多线程模式运行

    此模式较为典型的情况是 IIS 的 isapiApache NPM Worker(支持混合的多进程多线程处理模块, Windows 平台基本上是这种). 与多进程模式不同的是, 多线程模式 MINIT 和 MSHUTDOWN 只需要执行一次, 多个线程之间可以共享一些数据, 从而减少一些性能开销.

  • 以 Embed 模式运行

    这玩意用得不多, 情况也比较复杂. 它允许 C/C++ 调用 PHP 的方法. 甚至可以嵌入到某个程序中去.