02-01-01-cgi-lift-cycle
02-01-01-cgi-lift-cycle

还是这张图,本次要验证的是在不同的sapi客户端下,具体流程是怎样的。

测试环境

  • Debian 9
  • PHP 7.3.1
  • Nginx 1.10.3
  • Apache 2.4.25

测试方法

在扩展文件的这些方法MINIT、MSHUTDOWN、RINIT、RSHUTDOWN内部添加输出,通过不同的形式进行调用,查看输出规则。

我开始是在扩展内部封装了个函数,分别在上面列出的函数中调用该函数,向指定的文件内写日志:

1
2
3
4
5
6
7
void run_log(char *log)
{
FILE *fp = fopen("/tmp/c.log", "a+");
fputs(log, fp);
fclose(fp);
return;
}

这种写法在cli模式下运行正常,但是到fastcgi模式下就不太正常了。问题是这样:

启动php-fpm,日志记录正常。

通过nginx访问一个页面进行调用,nginx会出现如下日志:

1
2019/08/15 12:17:26 [error] 14922#14922: *77 recv() failed (104: Connection reset by peer) while reading response header from upstream, client: 192.168.199.200, server: _, request: "GET /a.php HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000", host: "192.168.199.235"

同时php-fpm也会有如下错误日志:

1
2
[15-Aug-2019 12:17:26] WARNING: [pool www] child 17308 exited on signal 11 (SIGSEGV) after 15.726199 seconds from start
[15-Aug-2019 12:17:26] NOTICE: [pool www] child 17315 started

看错误是worker进程莫名退出。

搜索错误中提到的信号SIGSEGV,其中有一种原因可能是文件权限问题,一看,还真是,因为这个文件的创建和写入涉及到两个用户,一个是php-fpm启动时候的用户,例如root,启动fpm的时候,会创建文件,权限是-rw-r--r--。另一个是通过nginx访问,最终操作文件是fpm进程的用户,例如www,而www用户对root用户创建的文件是没有写入权限的。所以给日志文件添加权限就可以了。

同理,如果先在cli下调用了,也可能会有这个问题。

除了上面这种方法,也可以直接用PHP的输出APIphp_printf,跟c的printf用法一致。

例如:

1
2
3
4
5
6
PHP_RSHUTDOWN_FUNCTION(wzh)
{
php_printf("r shutdown running\n");

return SUCCESS;
}

注:最好不要在扩展内部使用c语言原有的printf函数,否则可能会有意想不到的结果。

cli

以cli形式的调用没什么特别需要说明的,所有的流程都需要加载一遍,具体输出如下:

1
2
3
4
5
m init running
r init running
wzh function running
r shutdown running
m shutdown running

php-fpm

php-fpm是master/worker模型,从性能的角度考虑,一些公共的初始化可能在master进程完成,而请求相关的可能在worker进程来初始化。所以可以猜测MINIT、MSHUTDOWN是在php-fpm启动和关闭的时候运行的,而RINIT、RSHUTDOWN是在每个请求中运行的。

验证如下,启动php-fpm进程时候的日志:

1
2
m init running
m shutdown running

这里比较纳闷的是为什么会运行MSHUTDOWN

http请求之后的日志:

1
2
3
r init running
wzh function running (这个是调用扩展函数的日志 )
r shutdown running

kill掉php-fpm进程的日志:

1
m shutdown running

到这里可以验证我们的猜测是正确的。

apache2

apache2解析php可以用libapache2-mod-php模块,这种是方式基于cgi协议。目前apache也支持fastcgi,这种方式的加载同样依赖于php-fpm,所以就不做讨论了。

apache有几种进程模型,php官方推荐Prefork MPM,这个模型也是master/worker,所以可能也是master进程做模块初始化,worker进程处理请求,跟php-fpm类似。

通过构建扩展,网页访问输出如下:

1
2
3
r init running
wzh function running
r shutdown running

可以看到请求的执行流程与php-fpm一致。MINIT、MSHUTDOWN因为代码的问题,目前未能验证出来是在进程启动的时候执行的(详见问题2)。

遇到的问题

  1. apache与nginx的php编译参数差异
    我开始测试的web server是nginx,扩展什么的都能正常使用,但是换上apache2后,所有的扩展都不能使用,后来重新编译了php,添加了如下参数--with-apxs2=/usr/bin/apxs,如果没有apxs命令可以通过apt install apache2-dev来安装。

同样,添加了上述编译参数后编译好的扩展,nginx也不能用。

  1. 上述扩展中打日志用的run_log在apache2下不能正常使用,请求的时候有如下错误,具体原因暂未查到。暂时通过php_printf代替了。
1
child pid 57265 exit signal Segmentation fault (11)
  1. php-fpm下初始化会执行MSHUTDOWN的问题
    这个问题比较怪的是,如果换成php_printf代替,就不会有MSHUTDOWN了,原因暂时没找出来。

(完)