教程地址:PHP扩展开发及内核应用
一、PHP的生命周期
PHP架构图
简单来说,就是Apache/Nginx启动后,PHP解释程序也随之启动(初始化一些环境变量用于整个SAPI生命周期,以及初始化只针对当前请求的一些变量设置),随着页面执行到页面执行完毕,PHP会自动unset掉各个变量,当请求结束后,关闭各个扩展,从而释放各个模块的内存。
接下来,就跟着教程,痛并快乐的学习下,PHP的生命周期到底是怎么样的。
1. SAPI
static sapi_module_struct cgi_sapi_module = {
#if PHP_FASTCGI
"cgi-fcgi", /* name */
"CGI/FastCGI", /* pretty name */
#else
"cgi", /* name */
"CGI", /* pretty name */
#endif
php_cgi_startup, /* startup,调用了PHP的初始化函数 */
php_module_shutdown_wrapper, /* shutdown,PHP关闭函数的简单封装 */
sapi_cgi_activate, /* activate,处理一些初始化,资源分配的事务 */
sapi_cgi_deactivate, /* deactivate,提供一个handler, 用来处理收尾工作 */
sapi_cgibin_ub_write, /* unbuffered write,这个hanlder告诉了Zend,如何输出数据 */
sapi_cgibin_flush, /* flush,提供给Zend的刷新缓存的函数句柄 */
NULL, /* get uid,让Zend可以验证一个要执行脚本文件的state,从而判断文件是否据有执行权限等等 */
sapi_cgibin_getenv, /* getenv,为Zend提供了一个根据name来查找环境变量的接口 */
php_error, /* error handler,错误处理函数 */
NULL, /* header handler,会在调用PHP的header()函数的时候被调用 */
sapi_cgi_send_headers, /* send headers handler,会在要真正发送header的时候被调用 */
NULL, /* send header handler,用来单独发送每一个header */
sapi_cgi_read_post, /* read POST data,这个句柄指明了如何获取post的数据 */
sapi_cgi_read_cookies, /* read Cookies,这个句柄指明了如何获取cookie的数据 */
sapi_cgi_register_variables, /* register server variables,用以给$_SERVER变量中添加变量 */
sapi_cgi_log_message, /* Log message,用来输出错误信息 */
NULL, /* Get request time */
NULL, /* Child terminate */
STANDARD_SAPI_MODULE_PROPERTIES
};
PS:该文件一般位置/.local/share/Trash/files/php-7.1.11/sapi/cgi/
2. PHP的启动与终止
下面直接引用原文中的代码,简单明了,清晰易懂。
//这些代码都在walu.c里面,不再.h里
int time_of_minit;//在MINIT中初始化,在每次页面请求中输出,看看是否变化
PHP_MINIT_FUNCTION(walu)
{
time_of_minit=time(NULL);//我们在MINIT启动中对他初始化
return SUCCESS;
}
int time_of_rinit;//在RINIT里初始化,看看每次页面请求的时候变不。
PHP_RINIT_FUNCTION(walu)
{
time_of_rinit=time(NULL);
return SUCCESS;
}
PHP_RSHUTDOWN_FUNCTION(walu)
{
FILE *fp=fopen("/cnan/www/erzha/time_rshutdown.txt","a+");//请确保文件可写,否则apache会莫名崩溃
fprintf(fp,"%d\n",time(NULL));//让我们看看是不是每次请求结束都会在这个文件里追加数据
fclose(fp);
return SUCCESS;
}
PHP_MSHUTDOWN_FUNCTION(walu)
{
FILE *fp=fopen("/cnan/www/erzha/time_mshutdown.txt","a+");//请确保文件可写,否则apache会莫名崩溃
fprintf(fp,"%d\n",time(NULL));
return SUCCESS;
}
//我们在页面里输出time_of_minit和time_of_rinit的值
PHP_FUNCTION(walu_test)
{
php_printf("%d<br />",time_of_minit);
php_printf("%d<br />",time_of_rinit);
return;
}
- time_of_minit的值每次请求都不变。
- time_of_rinit的值每次请求都改变。
- 每次页面请求都会往time_rshutdown.txt中写入数据。
- 只有在apache结束后time_mshutdown.txt才写入有数据。
3. PHP的生命周期
一个PHP实例,无论是从init脚本中调用的,还是从命令行启动的,都会向我们上一节说的那样, 依次进行Module init、Request init、Request Shutdown、Module shutdown四个过程, 当然之间还会执行脚本自己的逻辑。 那么两种init和两种shutdown各会执行多少次、各自的执行频率有多少呢? 这取决与PHP是用什么sapi与宿主通信的。最常见的四种方式如下所列:
- 直接以CLI/CGI模式调用
- 多进程模块
- 多线程模
- Embedded(嵌入式,在自己的C程序中调用Zend Engine)
3.1. 单进程SAPI生命周期
3.2. 多进程模式 & 多线程模式
简单解释下,多进程和多线程:
- 进程(process)是资源分配的最小单位,线程(thread)是处理机调度的最小单位。
- 一个进程可以有很多线程,每条线程并行执行不同的任务。
- PHP是单进程执行的,但是当数据量比较大(例如:队列处理、日志分析、文件处理等)或者运行守护进程的时候,就需要依赖服务器或PHP-FPM的多进程及它们进程的复用。
通常PHP是编译为apache的一个模块来处理PHP请求。Apache一般会采用多进程模式,Apache启动后会fork出多个子进程,每个进程的内存空间独立,每个子进程都会经过开始和结束环节,不过每个进程的开始阶段只在进程fork出来以来后进行,在整个进程的生命周期内可能会处理多个请求。
只有在Apache关闭或者进程被结束之后才会进行关闭阶段,在这两个阶段之间会随着每个请求重复请求开始-请求关闭的环节。
多线程模式和多进程中的某个进程类似,不同的是在整个进程的生命周期内会并行的重复着 请求开始-请求关闭的环节。
4. 线性安全
在多线程中,多个线程共享除函数调用栈之外的其他资源。 各种变量的作用域从定义来看如下:
- 全局变量,所有函数共享,因此所有的线程共享,不同线程中出现的不同变量都是这同一个变量。
- 静态全局变量,所有函数共享,也是所有线程共享。
- 局部变量,此函数的各次执行中涉及的这个变量没有联系,因此,也是各个线程间也是不共享的。
- 静态局部变量,本函数间共享,函数的每次执行涉及的这个变量都是同一个变量,因此,各个线程是共享的。