fpm源码之初探【原创】

本文在php-7.2.0版本下进行分析。主要介绍fpm目录文件,并介绍fpm入口文件。

1、fpm目录

fpm位于php_src/sapi目录下

1
2
3
4
5
6
7
8
9
10
11
12
13
.
├── CREDITS //fpm名称和贡献者列表
├── LICENSE //声明
├── Makefile.frag //make时使用的文件
├── config.m4 //构建fpm扩展的配置文件
├── fpm //fpm源码目录
├── init.d.php-fpm.in //fpm提供给init.d程序使用的配置
├── php-fpm.8.in
├── php-fpm.conf.in //fpm运行时的配置文件
├── php-fpm.service.in //fpm提供给systemd管理时使用的配置
├── status.html.in // fpm运行时状态查看页面
├── tests //测试fpm的脚本
└── www.conf.in //fpm运行时配置,这个主要是pool池配置管理

linux管理进程有两种常见方式,centOS7以下,基本采用init.d来管理操作系统的启动,而centOS7开始使用systemd来管理系统的启动,关于这两种启动方式,超出本文所讲述的范围,这里只给出相关链接。systemd,阮一峰老师的一篇文章,讲解的非常浅显易懂,Systemd入门。init这里没有找到比较好的,给出维基百科的解释。https://zh.wikipedia.org/wiki/Init

之所以要提及init和systemd,主要是在我们的fpm目录下,为这两个程序提供了相应的管理配置。具体配置这里不展开详述了,感兴趣的可以了解相关资料。

fpm目录

存放fpm源码的目录,fpm采用c语言编写,每个文件都是成对出现的,.c.h文件,其详细结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
php-7.2.0/sapi/fpm
.
├── events //fpm事件处理相关源码
│   ├── devpoll.c
│   ├── devpoll.h
│   ├── epoll.c
│   ├── epoll.h
│   ├── kqueue.c
│   ├── kqueue.h
│   ├── poll.c
│   ├── poll.h
│   ├── port.c
│   ├── port.h
│   ├── select.c
│   └── select.h
├── fpm.c //fpm主程序实现
├── fpm.h //fpm主程序头文件
├── fpm_arrays.h //fpm数组相关
├── fpm_atomic.h //fpm原子操作相关
├── fpm_children.c //fpm子进程相关
├── fpm_children.h
├── fpm_cleanup.c
├── fpm_cleanup.h //fpm内存清理
├── fpm_clock.c
├── fpm_clock.h //fpm时钟管理
├── fpm_conf.c
├── fpm_conf.h //fpm运行时配置解析,包括运行方式、进程池等的配置
├── fpm_config.h //fpm计数器配置
├── fpm_env.c
├── fpm_env.h //fpm环境变量
├── fpm_events.c
├── fpm_events.h //fpm事件处理相关
├── fpm_log.c
├── fpm_log.h //fpm日志相关
├── fpm_main.c //fpm入口文件
├── fpm_php.c
├── fpm_php.h //fpm中php与zend的中间交互
├── fpm_php_trace.c
├── fpm_php_trace.h //fpm执行栈
├── fpm_process_ctl.c
├── fpm_process_ctl.h //fpm进程控制
├── fpm_request.c
├── fpm_request.h //fpm请求处理
├── fpm_scoreboard.c
├── fpm_scoreboard.h //fpm的计数器,主要用于管理work
├── fpm_shm.c
├── fpm_shm.h //fpm共享内存分配处理
├── fpm_signals.c
├── fpm_signals.h //fpm信号处理
├── fpm_sockets.c
├── fpm_sockets.h //fpm套接字处理
├── fpm_status.c
├── fpm_status.h //fpm状态管理
├── fpm_stdio.c
├── fpm_stdio.h //fpm输入输出管理
├── fpm_str.h //fpm字符串复制
├── fpm_systemd.c
├── fpm_systemd.h //fpm与systemd通信管理
├── fpm_trace.c
├── fpm_trace.h //fpm调用栈相关
├── fpm_trace_mach.c //fpm master进程检测相关
├── fpm_trace_pread.c //fpm进程状态检测相关
├── fpm_trace_ptrace.c
├── fpm_unix.c
├── fpm_unix.h //处理unix文件系统相关
├── fpm_worker_pool.c
├── fpm_worker_pool.h //fpm 工作进程池相关
├── zlog.c
└── zlog.h //fpm日志记录

fpm入口程序

FPM(FastCGI Process Manager)是PHP FastCGI运行模式的一个进程管理器,从它的定义可以看出,FPM的核心功能是进程管理,那么它用来管理什么进程呢?接下来我们将从入口程序开始,一点一点揭开其神秘面纱。分析一个程序,找准入口是关键,找到入口后根据主脉络往下捋即可。按照这样的指导思想,那我们就开始吧。

php_src/sapi/fpm/fpm/fpm_main.c:fpm入口程序,找到main入口函数,我们先来看下fpm启动执行的主要流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
int main(int argc, char *argv[])
{
...
//注册SAPI:将全局变量sapi_module设置为cgi_sapi_module
sapi_startup(&cgi_sapi_module);
...
//执行php_module_starup()
if (cgi_sapi_module.startup(&cgi_sapi_module) == FAILURE) {
return FPM_EXIT_SOFTWARE;
}
...
//初始化
if(0 > fpm_init(...)){
...
}
...
fpm_is_running = 1;

fcgi_fd = fpm_run(&max_requests);//后面都是worker进程的操作,master进程不会走到下面,因为在fpm_run中已经根据work_pool进行子进程fork了
parent = 0;
...
request = fpm_init_request(fcgi_fd);//初始化请求,开辟内存空间、设置请求相关的环境变量、绑定socket等
zend_first_try {//开始一个try
while (EXPECTED(fcgi_accept_request(request) >= 0)) {//阻塞等待请求到来,关于fpm如何接受请求,后面会有文章专门讲解
...
init_request_info();//初始化请求结构,包括PATH_INFO、SCRIPT_NAME、REQUEST_URI、SCRIPT_FILENAME等
fpm_request_info();//将cgi中的请求信息复制到fpm请求中
php_request_startup()//php请求处理开始
fpm_status_handle_request()//处理fpm启动后状态检测,如果状态不太对,就跳过后面的处理,结束请求
...
php_fopen_primary_script(&file_handle)//

...
fpm_request_executing();//请求执行
php_execute_script(&file_handle);//执行脚本
...
fpm_request_end();//请求执行结束
php_request_shutdown((void *) 0);//请求关闭
...
requests++;
if (UNEXPECTED(max_requests && (requests == max_requests))) {//判断一个子进程处理的请求数,如果达到处理上线就光荣退出
fcgi_request_set_keep(request, 0);
fcgi_finish_request(request, 0);
break;
}
...
}
fcgi_destroy_request(request);//销毁fpm请求
fcgi_shutdown();//fcgi关闭
} zend_catch {
exit_status = FPM_EXIT_SOFTWARE;
} zend_end_try();

php_module_shutdown();//关闭模块
...
return exit_status;//进程退出
}

总结一些入口文件干的事情:

  1. 初始化数据结构
  2. 解析命令行参数
  3. 初始化fpm_init
  4. 执行fpm_run
  5. 启动监听端口并处理请求(此处主进程是不会走到的)

下面一篇文章将会讲解fpm的进程是如何管理的

  • 本文作者: 风月
  • 本文链接: /php-fpm-01/
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!