PHP 的 SAPI 是个什么东西(转)
这是 PHP 内核提供给外部调用其服务的接口,即外部系统可以通过 SAPI 来调用 PHP 提供的编译脚本、执行脚本的服务。
PHP中常用的SAPI有cli、php-fpm,cli是命令行下执行PHP脚本的实现:bin/php script.php
,它是单进程的,处理模型比较简单,而php-fpm相对比较复杂,它实现了网络处理模块,用于与web服务器交互。
从下图可以较为清晰的理解外部系统是如何通过 SAPI 调用 PHP 服务的
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 startup
、request startup
、execute script
、request shutdown
、module 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 脚本、关闭请求。
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)。