PHP底层工作原理
PHP的相关进程是随着Web服务器如Apache、Nginx等的启动而运行的,这里以Apache为例简要梳理下流程:
- PHP通过
mod_php.so
扩展和Apache建立联系,具体来说是SAPI
服务器应用程序编程接口。
当Apache服务器启动后,PHP解释程序也随之启动,PHP启动的过程分为两步:
- 初始化环境变量,这些环境变量将在
SAPI
生命周期中发生作用。
PHP解释程序启动后,会调用各个扩展的MINIT
方法即“模块初始化”,从而使扩展切换到可用状态。MINIT
指的是,每个扩展模块都定义了一组函数、类库等用于处理其它请求。 - 生成只针对当前请求的变量设置
当客户端的请求发生时,SAPI
层将控制权交给PHP层。于是,PHP设置了用于回复本次请求所需的环境变量,用来存放执行过程中产生的变量名和值。PHP调用各个扩展模块的RINIT
方法即请求初始化。经典的案例使Session模块的RINIT方法,如果在php.ini
中启用了Session扩展模块,那么在调用该模块的RINIT
时就会初始化$_SESSION
全局变量,并将相关内容读取。RINIT
方法可看作是一个准备过程,在程序执行之间就会自动启动。
如同PHP启动一样,PHP的关闭也分为两个步骤
- 一旦脚本执行完毕,无论是执行到文件末尾还是使用
exit
或die
函数中止。PHP都会启动清理程序,清理程序会按顺序调用各个扩展模块的RSHUTDOWN
方法,RSHUTDOWN
方法用来清理程序运行时产生的符号表,也就是对每个变量调用unset
函数。 - 当所有的请求都处理完毕后,
SAPI
也准备关闭了。此时,PHP会调用每个扩展模块的MSHUTDOWN
方法,这是各个扩展模块最后一次释放内存的机会。
- PHP中共有三个模块:PHP内核、Zend引擎、扩展层
- PHP内核:用来处理请求、文件流、错误处理等相关操作
- Zend引擎:将PHP源文件转换成机器语言,并在Zend虚拟机上运行。
- 扩展层:是一组函数、类库和流
PHP使用这个三个模块来执行特定的操作,如使用MySQL扩展来连接MySQL数据。
- 当Zend引擎执行程序时可能会需要连接若干扩展,此时Zend引擎会将控制权交给扩展,等处理完特定任务后再返还。
- Zend引擎将运行结果返回给PHP内核,PHP内核将结果传递给SAPI,最终通过Web服务器输出到浏览器。
PHP脚本运行流程
PHP作为Swoole的宿主,下图是以CLI命令行下执行一个PHP脚本文件时的完整流程:
SAPI
是PHP给外部环境执行PHP内核提供的统一接口,常见有三种:CLI、PHP-FPM、MOD_PHP。
以PHP-FPM为例,将PHP运行周期的关键步骤提取:
初始化
PHP引擎初始化公用配置,读取.ini
配置文件,加载zend
引擎。MINIT
执行PHP各个扩展模块的MINIT
(模块初始化)方法后,常驻在PHP-FPM进程中,等待处理请求。RINIT
当请求过来后,调用PHP各个扩展模块的RINIT
(请求初始化)方法进行请求内数据的初始化,如超全局变量和模块数据的初始化等操作。-
执行PHP脚本
加载PHP脚本文件,进行词法分析、语法分析、生成Opcode中间代码,然后交给Zend虚拟机,暂存执行结果。
解释型语言PHP的执行流程 RSHUTDOWN
在结果返回给PHP-FPM之前,会调用PHP各个扩展模块的RSHUTDOWN
(请求关闭)方法进行数据的回收,Zend虚拟机会关闭打开的数据流,进行内存释放等操作,然后把暂存的执行结果flush
输出。MSHUTDOWN
当重启PHP-FPM时会调用PHP各个扩展模块的MSHUTDOWN
(模块关闭)方法,执行关闭Zend引擎等操作。
通过以上流程可以发现PHP-FPM中每个请求都是在执行第3~5步,Opcode缓存是将第4步的词法分析、语法分析、生成Opcode中间代码等几个操作给缓存起来,从而达到加速的目的。
既然每个请求都是独立的,那么能不能进行数据共享呢?由于在MINIT
模块初始化时数据是常驻在PHP-FPM进程中的,所有是可以实现的,例如比较典型的.ini
配置文件是放在这一步的。另外每个请求都能够独立释放内存,总体上是安全的,但也是有问题的,很有可能在扩展层就存在内存泄漏。所以PHP-FPM提供max_request
来重启PHP-FPM,达到完全释放内存的目的。
Swoole的生命周期
分析了PHP的基本执行流程后,Swoole是在哪一步执行的呢?首先,Swoole运行有个前提条件:必须在CLI命令行模式下执行。Swoole在PHP执行流程的第4步执行PHP脚本时就接管了PHP,进入了Swoole的生命周期。
Swoole的生命周期以多进程模式为例,具体流程如下:
- 初始化
创建Manager管理进程,创建Worker工作子进程,监听所有TCP/UDP端口,监听定时器Timer
。 - onStart
onStart
回调函数是在Master主进程中执行的,和Worker工作子进程的onWorkStart
是并行的,并没有先后之分。在此回调函数中强烈要求只做Log记录和进程名设置操作,不要做业务逻辑。因为业务逻辑代码的错误将直接导致Master主进程Crash崩溃,进而让整个Swoole服务器无法对外提供服务。 - onReceive
客户端请求的数据到达时会调用onReceive
函数,进行业务逻辑处理输出结果。客户端发送的多次请求,服务端是可以一次性接收的,所以会发现一个问题是onReceive
接收的数据会非常大。 - onWorkerStop
Worker工作子进程退出时回调onWorkerStop
函数。 - onShutDown
Swoole服务停止时回调onShutDown
函数,然后继续PHP-FPM的第5~6步,最后退出PHP的生命周期。