[PHP-FPM] 一次请求过程的解析

一、概要

PHP-FPM启动后,master进程会陷入event_loop(0)中来管理维持worker进程,而fork出的worker进程会回到主函数开始循环接收、处理请求。一次请求可以总结为 请求接收、请求处理、请求结束 三个阶段,下面就详细来讲一下。

运行环境: Mac 10.14.2 + PHP 7.3.7

二、请求接收阶段

  1. 对listen_socket加锁: 因为accept()会有惊群问题,在调用accept()之前会对listen_socket加锁。惊群问题在Linux2.6版本中得到解决,内核在收到一个客户端连接时只会唤醒等待队列上的第一个进程。
  2. 获取client_socket: worker进程会调用 accept(listen_socket, (struct sockaddr *)&sa, &len) 从全连接队列中接受一个连接,如果队列中暂时没有则会一直阻塞着,这里的listen_socket是在fcgi_listen()中创建监听的。
  3. 判断client_socket是否被允许: 满足如下请求之一即可
    1. client_socket为unix_socket,表明客户端为本机
    2. 客户端地址在allowed_clients列表里,allowed_clients是通过listen.allowed_clients参数配置
  4. 等待client_socket上的可读事件发生: 在do-while循环中调用poll()来监听client_socket上的可读事件,这里的while条件是while (ret < 0 && errno == EINTR); EINTR错误是当阻塞中的poll()被捕获到的信号中断所产生的错误,所以可以重新执行poll系统调用。
  5. 读取client_socket中的数据: 这里是对FastCGI协议的一个实现,Nginx会按照FastCGI协议的消息格式发送数据,worker进程再按照协议多次read()数据并解析,消息传递大致如下。关于PHP如何实现FastCGI协议可以看下这篇文章

FastCGI消息传递

三、请求处理阶段

初始化

在上一阶段读取到请求数据后,worker进程接着会初始化输出相关的堆栈、初始化编译阶段用到的compiler_globals(CG宏)、执行阶段用到的executor_globals(EG宏)、执行每个扩展的PHP_RINIT_FUNCTION函数 等等。

ZendVM

讲到请求处理阶段就不得不提ZendVM,大家都知道PHP是解释型语言,ZendVM就是PHP的解释器,负责PHP的解析、执行。计算机理解不了PHP代码,但是ZendVM可以,对PHP而言,ZendVM就像是真正的“计算机“,这台“计算机“可以识别的指令就是自己事先定义好的opcode。在运行时,PHP会被编译为一系列opcode指令,ZendVM会逐个调用opcode对应的机器指令,最终完成PHP代码的运行。

ZendVM运行过程

  1. 词法语法分析,生成AST: 这一步的目的是生成抽象语法树AST,AST是PHP7引入的概念,PHP7之前是在语法分析后就直接生成opcode了。在这过程中语法分析器yacc不断调用词法分析器re2c将PHP代码切割为token,然后yacc根据token组合匹配语法规则,最终生成AST。
  2. 解析AST,生成zend_op_array:这一步的目的是生成zend_op_arrayzend_op_array是编译后所有opline指令的集合,也包括编译期间生成的关键数据。对于ZendVM而言,zend_op_array就是可执行数据。
  3. ZendVM执行zend_op_arrayzend_op_array作为ZendVM编译器的输出,也是ZendVM执行器的输入。执行时,ZendVM执行器会调用opcode相应的handler完成指令的处理,其中handler是每条opcode对应的C语言编写的处理逻辑。

四、请求结束阶段

  1. 执行用户通过register_shutdown_function()注册的关闭函数
  2. 释放资源,清理符号表,销毁超全局变量,重置max_execution_time 等等
  3. 冲刷掉所有缓冲区
  4. 执行每个扩展的PHP_RSHUTDOWN_FUNCTION函数
  5. …...

经过以上的清理操作,worker进程就准备好接收处理下一个请求了。

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!