1.2 PHP 的启动与终止
PHP 程序的启动可以看做有2个概念上的启动,终止也有2个。其中一个是 PHP 作为 Apache(举例)的一个模块的启动和终止,
这次启动PHP会初始化一些必要的数据,比如与宿主apache有关的,并且这些数据是常驻内存的。终止与之相对。还有一个概念上
的启动就是当 apache 分配一个页面请求过来的时候,php 会有一次启动与终止。
在最初的初始化的时候,就是 php 随着 apache 的启动而诞生在内存的时候,它会把自己所有已加载的扩展的 MINIT方法(
全场 module initialization, 是由每个模块自己定义的函数)都执行一遍。在这个时间里,扩展可以定义一些自己的常量,类,
资源等所有会被用户的 php 脚本用到的东西。但要记住,这里定义的东西会在 apache 常驻内存,可以被所有请求使用,直到 apache
卸载掉 php 模块。
内核中预置了 PHP_MINIT_FUNCTION 宏函数,来帮助我们实现这个功能:
int time_of_minit; // 在 MINIT() 中初始化,在每次页面请求中输出,看是否有变化
PHP_MINIT_FUNCTION(扩展名)
{
time_of_minit = time(null);
return SUCCESS; // 返回 SUCCESS 代表正常,返回 FALIURE 就不会加载这个扩展了
}
当一个页面请求到来的时候,php 会迅速的开辟一个新的环境,并重新扫描自己的各个扩展,遍历执行它们各自的 RINIT方法(俗称 request initialization),
这个时候扩展可能会初始化本次请求中会用到的变量等,还会初始化用户端(即 php 脚本)中的变量之类的,内核预置了 PHP_RINIT_FUNCTION()这个宏函数来帮我们
实现这个功能:
int time_of_minit;
PHP_RINIT_FUNCTION(扩展名)
{
time_of_minit=time(null);
return SUCCESS;
}
好了,现在这个页面请求执行的差不多了,可能是顺利的走到了自己文件的最后。也可以被用户的 die 或者 exit 了,这个时候 php 就会启动回收程序。它这次会执行
所有已经加载的扩展的 RSHUTDOWN(request shutdown)方法,这个时候扩展可以抓紧利用内核的变量表之类的做一些事情,因为一旦 php 把所有扩展的 RSHUTDOWN 方法
执行完,便会释放这次请求使用过的所有东西,包括变量表的所有变量,所有在这次请求中申请的内存等。
内核预置了 PHP_RSHUTDOWN_FUNCTION() 宏函数来帮助我们实现。
PHP_RSHUTDOWN_FUNCTION(扩展名)
{
FILE *fp=fopen('time_rshutdown.txt', 'a+');
fprintf(fp, "%ld\n", time(null)); // 每次请求结束都在这里追加数据
fclose(fp);
return SUCCESS;
}
当 apache 终止时,会通知 php 自己要 stop 了。 php 便进入 MSHUTDOWN(module shutdown)阶段。这个时候 php 便会给所有的扩展下最后通牒,如果哪个扩展还有
未了心事,就放在 MSHUTDOWN 方法里,一旦 php 把扩展的 MSHUTDOWN 执行完,便进入自毁程序,这里一定要把自己申请的内存给释放掉。
PHP_MSHUTDOWN_FUNCTION(扩展名)
{
FILE *fp=fopen('time_mshutdown.txt', 'a+');
fprintf(fp, "%ld\n", time(NULL));
return SUCCESS;
}
这里的4个宏是在 自己的扩展里面实现的,其他的都是在 main/php.h 里别定义的
1.4 线程安全
在 PHP 初期,是作为单进程的 cgi 来运行的,所以并没有考虑线程安全的问题。我们可以随意的在全局作用域中设置变量并在程序中对
它进行修改,访问,内核申请的资源如果没有被正确释放,也会在 cgi 进程结束后自动被清理干净。
随着事业在多线程模式的软件越来越多,php 内核中需要一种新的资源管理方式,
并最终在 php 内核中形成了一个新的抽象层:TSRM(thread safe resource management)。
线程安全与非线程安全:
在一个没有线程的程序中,我们往往倾向于把全局变量放在源文件的顶部,编辑器会自动的为它分配资源供我们在声明语句之下的程序逻辑中使用。
但是在一个多线程的程序中,如果我们需要每个线程都拥有自己独立的资源的话,便需要为每个线程独立开辟出一个区域来存放它们各自的资源。
thread-safe data pools(线程安全的资源池):
在扩展的 module init 里,扩展可以调用 ts_allocate_id() 来告诉 trsm 自己需要多少资源。trsm 接收后更新系统使用的资源,并得到一个
指向刚刚分配的那份资源的 id。
当一个请求需要访问数据段的时候,扩展从 tsrm 层请求当前线程的资源池,以 ts_allocate_id() 返回的资源 id 来获取偏移量。
因为在 php 的线程安全中构建访问全局变量资源设计到线程数据池查找对应的偏移量,这是一些额外的负载,结果就是它比对应的非线程方式(直接从编译计算好的真实
全局变量地址取出数据)慢一些。
非线程构建还有进程隔离的优势,这样给定的请求碰到完全出乎意料的情况时,它也不会影响到其他进程,即使是产生错误也不会导致整个 web-server 瘫痪。实际上,
apache 的 MaxRequestPerChild 指令就是设计用来提升整个特性的,它经常性的有目的的 kill 掉子进程并产生新的子进程,来避免某些可能由于进程长时间运行 "积累"
而来的问题(比如内存泄漏).
访问全局变量:
在创建一个扩展时,你并不知道它最终的运行环境是否是线程安全的。幸运的是,你要使用的标准包含文件集合中已经包含了条件定义的 ZTS 预处理标记。当 php 因为 sapi
需要通过 enable-maintainer-zts 选项安装等原因以线程安全方式构建时,这个值会被自动的标记,并可以用一组 #ifdef ZTS 这样的指令集去测试它的值。就像你前面看到的,
只有在 php 以线程安全方式编译时,才会存到线程安全池,只有线程安全池存在时,才会真的在线程安全池中分配空间。
即使你不需要线程也需要考虑线程:
正常的 php 构建默认是关闭线程安全的,只有在构建的 sapi 明确需要线程安全或线程安全在 ./configure 阶段显示打开,才会以线程安全方式构建。给出了全局查找的速度问题
和进程隔离的缺点后,你看你会疑惑为什么明明不需要还有人故意打开它?这是因为,多数情况下,扩展和 sapi 的开发者认为你是线程安全开关的操作者,这样可以很大程度上保证新代码
可以在所有的环境中正常运行。当线程安全启用的时候,一个名为 tsrm_ls 的特殊指针被增加到了很多的内部函数原型中。这个指针允许 php 区分不同的线程的数据。
1.2 PHP 的启动与终止