PHP 的 SAPI 是个什么东西(转)

这是 PHP 内核提供给外部调用其服务的接口,即外部系统可以通过 SAPI 来调用 PHP 提供的编译脚本、执行脚本的服务。

PHP中常用的SAPI有cli、php-fpm,cli是命令行下执行PHP脚本的实现:bin/php script.php,它是单进程的,处理模型比较简单,而php-fpm相对比较复杂,它实现了网络处理模块,用于与web服务器交互。

从下图可以较为清晰的理解外部系统是如何通过 SAPI 调用 PHP 服务的

PHP 的 SAPI 是个什么东西(转)

Zend引擎

Zend是PHP语言实现的最为重要的部分,是PHP最基础、最核心的部分,它的源码在/Zend目录下,PHP代码从编译到执行都是由Zend完成的,后面章节绝大部分的源码分析都是针对Zend的。Zend整体由两个部分组成:

  • 编译器: 负责将PHP代码编译为抽象语法树,然后进一步编译为可执行的opcodes,这个过程相当于GCC的工作,编译器是一个语言实现的基础
  • 执行器: 负责执行编译器输出的opcodes,也就是执行PHP脚本中编写的代码逻辑

 

接下来主要是讨论下我们常见的 Cli、和 Fpm 是如何工作的。

Cli

Cli(Command Line Interface),即命令行接口,用于在命令行下执行 PHP 脚本,就像 Shell 那样,它是执行 PHP 脚本最简便的一种方式。

Cli 是单进程模式,处理完请求后就直接关闭了,生命周期先后经历 module startuprequest startupexecute scriptrequest shutdownmodule shutdown,其执行流程比较简单,关键的处理过程如下:

main()-> php_cli_startup()-> do_cli()-> php_module_shutdown()
Fpm

Fpm(FastCGI Process Manager)是 PHP FastCGI 运行模式的一个进程管理器,从它的定义可以看出,Fpm的核心功能是进程管理。

FastCGI 是 Web 服务器(如Nginx、Apache)和处理程序之间的一种通信协议,它是与HTTP类似的一种应用层通信协议。
注意:它只是一种协议!

Fpm 是一种多进程模型,它由一个 master 进程和多个 worker 进程组成。master 进程启动时会创建一个 socket,但是不会接收、处理请求,而是由 fork 出的 worker 子进程完成请求的接收及处理。即 master 进程管理 worker 进程,而 worker 进程才是真正的处理请求。

Fpm 在启动后首先会进行 SAPI 的注册操作;接着会进入 PHP 生命周期的 module startup 阶段,在这个阶段会调用各个扩展定义的 MINT 钩子函数。然后会进行一系列的初始化操作,最后 master、worker 进程进入不同的处理环节。

worder 进程的生命周期如下图:

PHP 的 SAPI 是个什么东西(转)

其生命周期主要经历这几个阶段:等待请求、解析请求、请求初始化、执行 PHP 脚本、关闭请求。

master 进程主要通过三种不同的方式来管理 worder 进程,分别是静态模式(static)、动态模式(dynamic)、按需模式(ondemand)。具体要使用哪种模式可以在conf配置中通过pm指定。

  • static: 这种方式比较简单,在启动时master按照pm.max_children配置fork出相应数量的worker进程,即worker进程数是固定不变的
  • dynamic: 动态进程管理,首先在fpm启动时按照pm.start_servers初始化一定数量的worker,运行期间如果master发现空闲worker数低于pm.min_spare_servers配置数(表示请求比较多,worker处理不过来了)则会fork worker进程,但总的worker数不能超过pm.max_children,如果master发现空闲worker数超过了pm.max_spare_servers(表示闲着的worker太多了)则会杀掉一些worker,避免占用过多资源,master通过这4个值来控制worker数
  • ondemand: 这种方式一般很少用,在启动时不分配worker进程,等到有请求了后再通知master进程fork worker进程,总的worker数不超过pm.max_children,处理完成后worker进程不会立即退出,当空闲时间超过pm.process_idle_timeout后再退出

线程安全

在C语言中声明在任何函数之外的变量为全局变量,全局变量为各线程共享,不同的线程引用同一地址空间,如果一个线程修改了全局变量就会影响所有的线程。所以线程安全是指多线程环境下如何安全的获取公共资源。

PHP的SAPI多数是单线程环境,比如cli、fpm、cgi,每个进程只启动一个主线程,这种模式下是不存在线程安全问题的,但是也有多线程的环境,比如Apache,或用户自己嵌入PHP实现的环境,这种情况下就需要考虑线程安全的问题了,因为PHP中有很多全局变量,比如最常见的:EG、CG,如果多个线程共享同一个变量将会冲突,所以PHP为多线程的应用模型提供了一个安全机制:Zend线程安全(Zend Thread Safe, ZTS)。