深入理解 PHP-FPM 多进程模型与源码执行流程
PHP-FPM 是 Nginx 环境下支撑 PHP 脚本执行的核心模块,负责通过 FastCGI 协议完成从 Socket 监听到请求分发的全流程。其采用主从(Master-Worker)多进程架构,由 Master 进程管理状态并监控子进程,多个 Worker 进程各自阻塞在 accept 上同步处理单一请求。本文将结合关键代码片段,深度解析 FPM 的事件循环、初始化逻辑及请求处理链路。
FPM 多进程运行框架
PHP-FPM 的整体生命周期可以通过 main 函数的执行流进行划分。其运行机制主要包含三个核心阶段:进程前的初始化配置、启动时的 fork 与事件循环、以及子进程的具体请求处理。
int main(int argc, char *argv[])
{
zend_signal_startup();
// 将全局变量 sapi_module 设置为 cgi_sapi_module
sapi_startup(&cgi_sapi_module);
fcgi_init();
// 获取命令行参数,其中 php-fpm -D、-i 等参数都是在这里被解析出来的
// ...
cgi_sapi_module.startup(&cgi_sapi_module);
// 【第1步】环境初始化与配置加载
fpm_init(argc, argv, fpm_config ? fpm_config : CGIG(fpm_config), fpm_prefix, fpm_pid, test_conf, php_allow_to_run_as_root, force_daemon, force_stderr);
/* master 进程会在这一步死循环,后面的流程都是子进程在执行。
【第2步】进入 Master 进程的永久事件循环 */
fcgi_fd = fpm_run(&max_requests);
request = fpm_init_request(fcgi_fd);
// 【第3步】子进程开始阻塞在 accept 等待请求
// ....
}FPM 中的事件监听机制
在 FPM 内部,无论是 IO 读写还是定时器触发,都被抽象为统一的事件结构。默认情况下,Linux/Kqueue 系统下优先使用 kqueue 作为后端 I/O 复用接口,而 Linux 通常对应 epoll。
事件结构与队列管理
每个事件由 struct fpm_event_s 定义,包含文件描述符(fd)、超时时间、回调函数指针以及标志位等。
// fpm 中的事件结构体
struct fpm_event_s {
int fd; // 事件的句柄
struct timeval timeout; // 下一次触发的事件
struct timeval frequency; // 频率:多久执行一次
void (*callback)(struct fpm_event_s *, short, void *); // 事件触发时调用的函数
void *arg; // 调用 callback 时的参数
int flags; // 标志位如 FPM_EV_READ 等
int index; // 在 fd 句柄数组中的索引
short which; // 事件类型
};
typedef struct fpm_event_queue_s {
struct fpm_event_queue_s *prev;
struct fpm_event_queue_s *next;
struct fpm_event_s *ev;
} fpm_event_queue;Kqueue 驱动的循环逻辑
Master 进程通过 fpm_event_loop 陷入死循环,不断监听注册好的事件。以管道信号读取为例,当管道的 sp[0] 端发生可读事件时,即触发注册的信号处理函数。
void fpm_event_loop(int err) {
static struct fpm_event_s signal_fd_event;
// 创建一个事件:管道 sp[0] 可读时触发
fpm_event_set(&signal_fd_event, fpm_signals_get_fd(), FPM_EV_READ, &fpm_got_signal, NULL);
fpm_event_add(&signal_fd_event, 0);
// 以阻塞的方式获取事件,module->wait() 根据平台不同分别调用 epoll_wait 或 kevent
ret = module->wait(fpm_event_queue_fd, timeout);
}
// kqueue 中添加事件的实现
static int fpm_event_kqueue_add(struct fpm_event_s *ev) {
struct kevent k;
EV_SET(&k, ev->fd, EVFILT_READ, EV_ADD, 0, 0, (void *)ev);
if (kevent(kfd, &k, 1, NULL, 0, NULL) < 0) {
zlog(ZLOG_ERROR, "kevent: unable to add event");
return -1;
}
ev->index = ev->fd;
return 0;
}信号通信与管道同步
Master 进程需要与子进程同步信息,除了共享内存(用于性能统计),还使用了双工管道(socketpair)来处理异步信号。所有注册的系统信号(如 SIGTERM、SIGUSR1)均指向同一个处理器 sig_handler,它只负责向管道的写端写入一个标识字符,随后在事件循环中统一读取并分发处理。
int fpm_signals_init_main() {
struct sigaction act;
// 创建全双工套接字作为进程间通讯管道
if (0 > socketpair(AF_UNIX, SOCK_STREAM, 0, sp)) {
return -1;
}
memset(&act, 0, sizeof(act));
act.sa_handler = sig_handler;
// 注册常用信号的处理函数
sigfillset(&act.sa_mask);
sigaction(SIGTERM, &act, 0);
sigaction(SIGINT, &act, 0);
// ... 其他信号注册
return 0;
}
static void sig_handler(int signo) {
const char sig_chars[NSIG + 1] = {
[SIGTERM] = 'T', [SIGINT] = 'I', [SIGUSR1] = '1' /*...*/
};
// 仅在主进程中写入管道通知事件循环
if (fpm_globals.parent_pid == getpid()) {
zend_quiet_write(sp[1], &sigs, 1);
}
}fpm_init 初始化核心流程
fpm_init() 阶段完成了服务器启动前的所有准备工作,包括配置加载、内存分配和端口绑定。
- fpm_conf_init_main:解析 php-fpm.conf,生成 worker pool 配置结构体,存入
fpm_worker_all_pools。每个 pool 对应一个独立监听 Socket。 - fpm_scoreboard_init_main:为每个 pool 分配内存记录运行状态(如当前活跃进程数、空闲数)。通过原子操作保证 Master 与 Worker 间的计数安全。
- fpm_event_init_main:初始化 Kqueue/Epoll 环境,预留足够的
kevents空间以备后续事件添加。
fpm_run 进程管理与 Fork
初始化完成后,进入 fpm_run。此阶段 Master 进程遍历所有的 worker pool,调用 fpm_children_create_initial 批量 fork 出初始数量的子进程(Children)。
int fpm_run(int *max_requests) {
struct fpm_worker_pool_s *wp;
for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
is_parent = fpm_children_create_initial(wp);
if (!is_parent) {
goto run_child; // 只有新创建的子进程才会跳到这里继续执行
}
}
/* 进入事件处理死循环,持续监控子进程状态、定时器及信号 */
fpm_event_loop(0);
run_child:
// 子进程清理并返回监听 socket,准备进入接收请求流程
return fpm_globals.listening_socket;
}子进程请求处理闭环
一旦 Master 进程进入 fpm_event_loop,fork 出去的子进程便脱离 Master 控制,转而阻塞在 fcgi_accept_request(request) 函数上。该函数本质上是对标准的 TCP Socket accept 进行的封装。
request = fpm_init_request(fcgi_fd);
zend_first_try {
while (EXPECTED(fcgi_accept_request(request) >= 0)) {
// 解析 Request Header
fpm_request_info();
// 开启 SAPI 模块,重置全局变量
php_request_startup();
// 打开指定 DOCUMENT_ROOT 下的 PHP 脚本
php_fopen_primary_script(&file_handle);
// 【核心动作】执行脚本内容,产生输出
php_execute_script(&file_handle);
// 结束本次 FastCGI 事务,清空请求上下文
fastcgi_request_done:;
}
} zend_end_try();总结来说,PHP-FPM 是一个典型的多进程网络应用框架。其高效性建立在 Master 对资源的集中管理和 Worker 无阻塞地处理单请求之上;而其灵活性则得益于可插拔的事件循环后端。以上就是 PHP 网络处理模块 FPM 源码分析的详细内容,更多关于 PHP 高性能架构的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









