PHP的生命周期

本文最后更新于:2021年8月27日 中午

PHP的生命周期

1.0 先从PHP开始说起

在介绍PHP的生命周期之前,我们先来看一张图,如图1所示:


图1 PHP的生命周期
首先我们先引入一个概念SAPI,SAPI是PHP的应用接入层,是整个PHP框架最外层的部分,像我们平常用的比较多的Fpm和Cli就是SAPI的具体实现,而main函数也定义在对应的SAPI中。通常来说PHP的生命周期被划分为5个阶段:模块初始化阶段、请求初始化阶段、脚本执行阶段、请求关闭阶段和模块关闭阶段。在不同的SAPI中,各阶段的调用过程也略有不同,像Cli模式基本会按照顺序完整的执行一遍,而在FastCgi也就是Fpm模式中,只会在启动时执行一次模块初始化,后面的每个请求都只会执行请求初始化阶段、脚本执行阶段和请求关闭阶段,最后在SAPI关闭时执行模块关闭阶段。
1.1 模块初始化阶段
这个阶段大概流程如图2-1-1所示:
图1-1 PHP的模块初始化阶段
首先是激活SAPI,对SG宏进行初始化。接着初始化代表PHP输出的OG宏、调用gc_globals_ctor初始化垃圾回收相关的变量。再之后就是正式对Zend引擎进行初始化操作,这块主要进行了如下操作:

  • 启动内存管理器(start_memory_manager)
    
  • 设置Zend编译器、执行器函数句柄
    
    zend_compile_file = compile_file;
    zend_execute_ex = execute_ex;
  • 初始化opcode执行句柄(zend_init_opcodes_handlers),以供执行阶段执行opcode使用
    
  • 为(函数符号表)GLOBAL_FUNCTION_TABLE,(类符号表)GLOBAL_CLASS_TABLE,(超全局符号表)GLOBAL_AUTO_GLOBALS_TABLE和(常量符号表)GLOBAL_CONSTANTS_TABLE分配内存空间并初始化到CG宏中
    
    GLOBAL_FUNCTION_TABLE = (HashTable *) malloc(sizeof(HashTable));
    GLOBAL_CLASS_TABLE = (HashTable *) malloc(sizeof(HashTable));
    GLOBAL_AUTO_GLOBALS_TABLE = (HashTable *) malloc(sizeof(HashTable));
    GLOBAL_CONSTANTS_TABLE = (HashTable *) malloc(sizeof(HashTable));
  • 初始化INI和PHP全局扫描器,这两个扫描器主要是为了解析php.ini文件和PHP脚本
    
    ini_scanner_globals_ctor(&ini_scanner_globals);
    php_scanner_globals_ctor(&language_scanner_globals);
  • 初始化$GLOBALS超全局变量
    
  • 给registered_zend_ini_directives分配内存并初始化到EG(ini_directives)中
    
    在Zend引擎初始化完毕之后,紧接着又定义了一些PHP的的全局常量,像我们平常使用的PHP_EOL就是在这里定义的。
    再往下调用php_init_config函数来读取php.ini文件,设置配置参数。接下来的操作可以分为下面几步:
  • 注册PHP核心模块的ini配置(REGISTER_INI_ENTRIES)
  • 注册Zend模块的ini配置(zend_register_standard_ini_entries)
  • 启动静态编译的扩展(php_register_internal_extensions_func)
  • 启动附加的扩展,注意在Cli模式中是没有附加的扩展的
  • 根据php.ini的配置加载扩展(php_ini_register_extensions)
  • 调用各PHP扩展的PHP_MINIT_FUNCTION启动扩展(zend_startup_modules)
  • 启动Zend扩展(zend_startup_extensions)
  • 采集各个扩展的其他阶段的方法句柄,包括RINT,RSHUTDOWN,MSHUTDOWN等(zend_collect_module_handlers)
  • 禁用php.ini配置的类和方法
    最后关闭SAPI,结束模块初始化阶段,局部关闭内存管理器。在FastCgi模式中该阶段执行完毕后,主进程在生成完子进程之后直接挂起,由子进程Accpect请求之后直接进入循环,执行后面的阶段。

21.2 请求初始化阶段
这个阶段一般是在请求处理之前经历的一个阶段,即PHP脚本真正执行之前的最后一个阶段。该阶段的执行过程如图2-1-2所示:
1-2 PHP的请求初始化阶段
首先激活PHP输出、重置全局输出、设置输出栈,接下来激活Zend引擎,相关的操作如下:

  • 重置垃圾回收(gc_reset)
    
  • 初始化编译器(init_compiler)
    
  • 初始化执行器(init_executor)
    
  • 启动扫描器为解析脚本做准备(startup_scanner)
    
    紧接着激活SAPI,这块操作跟模块初始化相同。然后激活Zend引擎的信号处理、设置超时时间、激活超全局变量、构建Request参数
    php_build_argv(SG(request_info).query_string, &PG(http_globals)[TRACK_VARS_SERVER]);
    最后调用各PHP扩展的RINT方法,结束该阶段。

1.3 脚本执行阶段
这个阶段的主要流程如图2-1-3 :
图1-3 PHP的脚本执行阶段
在这个阶段我们会调用Zend引擎去直接执行脚本文件,在这里我们首先会把脚本文件编译到op_array中
,相关的方法我们在模块初始化阶段已经进行过设置,具体编译流程如下:

  • 使用扫描器解析脚本(open_file_for_scanning)
  • 编译脚本(zend_compile)
    • 清空op_array
    • 清空AST语法树
    • 重新创建一个语法树的栈大小32K,栈上偏移24字节用来存放zend_arena结构体
    • 调用zendparse进行词法解析
    • 给op_array重新分配内存
    • 将CG(active_op_array)存到original_active_op_array中
    • 初始化op_array(init_op_array)并存入CG(active_op_array)
    • 解析语法树,根据ast生成op_array(zend_compile_top_stmt)
    • 设置op_array的头和尾
      op_array->line_start = 1;
      op_array->line_end = last_lineno;
    • 生成handler

      (pass_two)
    • 将original_active_op_array存到CG(active_op_array)中
    • 销毁语法树
    • 销毁语法树空间
      最后通过调用zend_execute方法执行编译得到的op_array,完成PHP脚本的执行。由此我们可以看到不管是Cli还是FastCgi都是在脚本执行阶段进行PHP代码的解析、编译、执行。但是通常在生产环境中我们的PHP代码是不常变动的,无疑是浪费了不少性能。
  • 1.4 请求关闭阶段*
    这个阶段具体流程如图2-1-4所示:
    图 1-4
    首先是清空EG(current_execute_data),因为在zend_executor回调函数中可能会用到。接下来的操作如下:
  • 调用在register_shutdown_function中注册的所有方法
    
  • 调用所有的__destruct方法
    
  • 清空输出缓冲区
    
  • 重置最大执行时间
    
  • 调用所有PHP扩展的RSHUTDOWN方法
    
  • 关闭输出层(发送设置的HTTP头,清除输出句柄)
    
  • 释放shutdown方法
    
  • 销毁超全局变量
    
  • 释放请求相关的全局变量
    
  • 挂起Zend引擎,关闭扫描器、执行器、编译器
    
  • 调用所有扩展的post-RSHUTDOWN方法
    
  • 挂起SAPI
    
  • 释放虚拟CWD内存
    
  • 销毁哈希流
    
  • 关闭内存管理器
    
  • 重置最大超时时间
    
    在Cli模式中到这个阶段我们的PHP程序已经基本走进尾声了,但在FastCgi模式中,子进程请求关闭阶段执行之后会重新Accepct请求,然后在进入请求初始化,执行脚本…。
  • 1.5 模块关闭阶段*
    不管在哪种模式这个阶段都是PHP生命周期的最后一个阶段,其具体流程如图2-1-5所示:
  • 清空SAPI
    
  • 关闭Zend引擎
    
  • 销毁过滤器和传输器
    
  • 关闭ini配置
    
  • 关闭输出
    
  • 关闭垃圾回收
    
  • 1.6 小结*
    一般来说我们使用PHP更多的是提供Web服务。从FastCgi相信我们不难理解PHP还有个Cgi模式,在这个模式下每次请求过来都会fork出一个子进程处理请求,每次都会执行上面5个阶段,性能十分低下。而FastCgi模式只进行一次模块初始化,通过预先启动一定数量的子进程(父子进程之间共享内存空间,且写时复制),循环执行请求初始化、执行脚本、请求关闭阶段。避免了模块初始化阶段的重复执行和大量进程反复加载,极大的提升了性能。

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!