深入理解 PHP-FPM 多进程模型与源码执行流程

Nora
Nora
发布时间:2026-10-13 | 4人浏览过
原创

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 高性能架构的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表