PHP - FPM信号机制
来源:互联网 发布:阿里云栖大会2016 编辑:程序博客网 时间:2024/06/15 11:16
熟悉PHP-FPM的童鞋应该都知道它采用master/worker多进程架构设计,我们可以通过执行”xxx/sbin/php-fpm stop”或”xxx/sbin/php-fpm reload”停止或重新加载fpm。那么它究竟是怎样运作呢?
简单来说,其运用了信号机制来实现相应的功能。当我们执行”stop”命令时,系统向fpm进程发送停止信号,当我们执行”reload”命令时,系统向fpm进程发送SIGUSR2信号。fpm的信号分为两块:主进程(master)信号和子进程(worker)信号。下面将逐一介绍。
主进程信号
主进程信号的初始化流程:fpm_init
->fpm_signals_init_main
。
static int sp[2];int fpm_signals_init_main(){ struct sigaction act; //创建管道sp if (0 > socketpair(AF_UNIX, SOCK_STREAM, 0, sp)) { zlog(ZLOG_SYSERROR, "failed to init signals: socketpair()"); return -1; } //设置非阻塞模式 if (0 > fd_set_blocked(sp[0], 0) || 0 > fd_set_blocked(sp[1], 0)) { zlog(ZLOG_SYSERROR, "failed to init signals: fd_set_blocked()"); return -1; } if (0 > fcntl(sp[0], F_SETFD, FD_CLOEXEC) || 0 > fcntl(sp[1], F_SETFD, FD_CLOEXEC)) { zlog(ZLOG_SYSERROR, "falied to init signals: fcntl(F_SETFD, FD_CLOEXEC)"); return -1; } //重置act memset(&act, 0, sizeof(act)); //设置信号回调函数 act.sa_handler = sig_handler; sigfillset(&act.sa_mask); //注册信号 if (0 > sigaction(SIGTERM, &act, 0) || 0 > sigaction(SIGINT, &act, 0) || 0 > sigaction(SIGUSR1, &act, 0) || 0 > sigaction(SIGUSR2, &act, 0) || 0 > sigaction(SIGCHLD, &act, 0) || 0 > sigaction(SIGQUIT, &act, 0)) { zlog(ZLOG_SYSERROR, "failed to init signals: sigaction()"); return -1; } return 0;}
上面的代码分为两部分:
- 定义一个双向通信管道
sp
,并将管道设置为非阻塞模式; - 为主进程设置
SIGTERM
、SIGINT
、SIGUSR1
、SIGUSR2
、SIGCHLD
、SIGQUIT
信号回调函数sig_handler
;
接着分析sig_handler
:
static void sig_handler(int signo){ static const char sig_chars[NSIG + 1] = { [SIGTERM] = 'T', [SIGINT] = 'I', [SIGUSR1] = '1', [SIGUSR2] = '2', [SIGQUIT] = 'Q', [SIGCHLD] = 'C' }; char s; int saved_errno; //确保主进程执行 if (fpm_globals.parent_pid != getpid()) { return; } saved_errno = errno; s = sig_chars[signo]; //主进程向sp[1]管道写入信号数据 write(sp[1], &s, sizeof(s)); errno = saved_errno;}
从上面的代码可以看出,当触发信号时,系统向sp[1]发送对应的信号数据,即sig_chars[signo]。那么fpm如何从sp管道提取信号数据呢?
fpm定义了一个sp[0]的IO读取事件:
int fpm_signals_get_fd(){ return sp[0];}void fpm_event_loop(int err){ static struct fpm_event_s signal_fd_event; /* sanity check */ //非主进程 则跳过 if (fpm_globals.parent_pid != getpid()) { return; } //注册信号IO时间 fpm_event_set(&signal_fd_event, fpm_signals_get_fd(), FPM_EV_READ, &fpm_got_signal, NULL); fpm_event_add(&signal_fd_event, 0); //...省略部分代码...}
上面注册sp[0]读操作事件,回调函数为fpm_got_signal
。
static void fpm_got_signal(struct fpm_event_s *ev, short which, void *arg){ char c; int res, ret; int fd = ev->fd; do { do { res = read(fd, &c, 1); } while (res == -1 && errno == EINTR); if (res <= 0) { if (res < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { zlog(ZLOG_SYSERROR, "unable to read from the signal pipe"); } return; } switch (c) { case 'C' : zlog(ZLOG_DEBUG, "received SIGCHLD"); //监听子进程退出信号 fpm/fpm_children.c //该信号由子进程发出 fpm_children_bury(); break; case 'I' : zlog(ZLOG_DEBUG, "received SIGINT"); zlog(ZLOG_NOTICE, "Terminating ..."); //发送SIGINT信号,主进程和子进程退出 fpm_pctl(FPM_PCTL_STATE_TERMINATING, FPM_PCTL_ACTION_SET); break; case 'T' : zlog(ZLOG_DEBUG, "received SIGTERM"); zlog(ZLOG_NOTICE, "Terminating ..."); //发送SIGTERM信号,主进程和子进程退出 fpm_pctl(FPM_PCTL_STATE_TERMINATING, FPM_PCTL_ACTION_SET); break; case 'Q' : zlog(ZLOG_DEBUG, "received SIGQUIT"); zlog(ZLOG_NOTICE, "Finishing ..."); //发送SIGQUIT信号,主进程和子进程退出 fpm_pctl(FPM_PCTL_STATE_FINISHING, FPM_PCTL_ACTION_SET); break; case '1' : zlog(ZLOG_DEBUG, "received SIGUSR1"); if (0 == fpm_stdio_open_error_log(1)) { zlog(ZLOG_NOTICE, "error log file re-opened"); } else { zlog(ZLOG_ERROR, "unable to re-opened error log file"); } //重新打开日志,重启子进程 ret = fpm_log_open(1); if (ret == 0) { zlog(ZLOG_NOTICE, "access log file re-opened"); } else if (ret == -1) { zlog(ZLOG_ERROR, "unable to re-opened access log file"); } break; case '2' : zlog(ZLOG_DEBUG, "received SIGUSR2"); zlog(ZLOG_NOTICE, "Reloading in progress ..."); //重启fpm fpm_pctl(FPM_PCTL_STATE_RELOADING, FPM_PCTL_ACTION_SET); break; } if (fpm_globals.is_child) { break; } } while (1); return;}
fpm_got_signal
执行read(fd, &c, 1)
读取sp[0]管道内容,根据具体的内容执行与之对应的操作。
fpm_children_bury:子进程退出信号处理。通过waitpid
收集子进程退出信号,删除该子进程相关配置信息并释放该子进程使用的资源等操作。
fpm_log_open:重新打开日志文件,重启子进程。
fpm_pctl:该函数有两个参数,第一个参数表示状态值,第二个参数表示操作类型。当第二个参数值为FPM_PCTL_ACTION_SET
时,设置fpm当前状态;然后执行fpm_pctl_action_next
函数处理信号对应的操作。
static void fpm_pctl_action_next(){ int sig, timeout; //判断是否有子进程在运行 if (!fpm_globals.running_children) { //主进程退出 fpm_pctl_action_last(); } if (fpm_signal_sent == 0) { if (fpm_state == FPM_PCTL_STATE_TERMINATING) { sig = SIGTERM; } else { sig = SIGQUIT; } //首次调用使用fpm.conf配置`process_control_timeout`参数值作为超时时间 timeout = fpm_global_config.process_control_timeout; } else { //FPM_PCTL_ACTION_TIMEOUT if (fpm_signal_sent == SIGQUIT) { sig = SIGTERM; } else { sig = SIGKILL; } timeout = 1; } //向所有子进程发送sig信号 fpm_pctl_kill_all(sig); fpm_signal_sent = sig; //注册定时事件监控子进程是否全部退出 fpm_pctl_timeout_set(timeout);}
fpm_pctl_action_next
向子进程发出kill信号后,通过fpm_pctl_timeout_set(timeout)
注册定时事件检查子进程kill情况,当所有子进程都已退出时,则执行fpm_pctl_action_last()
。
static void fpm_pctl_action_last(){ switch (fpm_state) { //SIGUSR2 case FPM_PCTL_STATE_RELOADING: fpm_pctl_exec(); break; //SIGQUIT case FPM_PCTL_STATE_FINISHING: case FPM_PCTL_STATE_TERMINATING: fpm_pctl_exit(); break; }}
- 对于SIGUSR2信号,执行
fpm_pctl_exec
函数。该函数内部调用C语言execvp
函数启动fpm。 - 对应SIGQUIT、SIGINT、SIGTREM信号,执行
fpm_pctl_exit
函数实现主进程退出。
以上就是主进程信号的处理流程,下面介绍子进程信号功能。
子进程信号
子进程信号的初始化流程:fpm_child_init
->fpm_signals_init_child
。
int fpm_signals_init_child(){ struct sigaction act, act_dfl; memset(&act, 0, sizeof(act)); memset(&act_dfl, 0, sizeof(act_dfl)); act.sa_handler = &sig_soft_quit; act.sa_flags |= SA_RESTART; act_dfl.sa_handler = SIG_DFL; close(sp[0]); close(sp[1]); if (0 > sigaction(SIGTERM, &act_dfl, 0) || 0 > sigaction(SIGINT, &act_dfl, 0) || 0 > sigaction(SIGUSR1, &act_dfl, 0) || 0 > sigaction(SIGUSR2, &act_dfl, 0) || 0 > sigaction(SIGCHLD, &act_dfl, 0) || 0 > sigaction(SIGQUIT, &act, 0)) { printf("failed to init child signals: sigaction()\n"); return -1; } return 0;}
子进程设置SIGTERM
、SIGINT
、SIGUSR1
、SIGUSR2
、SIGCHLD
、SIGQUIT
6个信号处理方式。
其中SIGTERM
、SIGINT
、SIGUSR1
、SIGUSR2
、SIGCHLD
的信号回调函数为SIG_DFL
,即默认处理。而SIGQUIT
的信号回调函数为sig_soft_quit
。
static void sig_soft_quit(int signo){ int saved_errno = errno; close(0); if (0 > socket(AF_UNIX, SOCK_STREAM, 0)) { zlog(ZLOG_WARNING, "failed to create a new socket"); } fpm_php_soft_quit(); errno = saved_errno;}
此处有一个较难理解的地方:close(0)
;它的作用是关闭子进程accept客户端请求的fd描述符。当子进程初始化时,系统将wp->listening_socket
通过dup2
函数绑定到子进程的STDIN_FILENO
设备,STDIN_FILENO
对应的FD值为0。 fpm_php_soft_quit
函数实现子进程的软退出。代码如下:
void fpm_php_soft_quit(){ fcgi_set_in_shutdown(1);}void fcgi_set_in_shutdown(int new_value){ in_shutdown = new_value;}
fpm_php_soft_quit()
,将in_shutdown值设为1,in_shutdown控制子进程accept客户端请求操作,当in_shutdown==1时,表明不再accept请求,则子进程会exit
,关闭cgi,释放资源等操作。
总结
总体来说,FPM的信号机制简洁、清晰。主进程通过管道来传递信号,注册异步I0事件获取信号内容,通过事件回调来实现信号对应的具体操作。子进程除了SIGQUIT信号实现了软退出,其他5个信号都是使用系统默认函数(SIG_DFL)处理,子信号的信号处理流程位于主进程中。
- PHP - FPM信号机制
- Nginx 与 PHP-FPM 的协作机制
- php-fpm
- php-fpm
- PHP-FPM
- php-fpm
- php-fpm
- Php-fpm
- php-fpm
- PHP-FPM
- php-fpm
- php-fpm
- 深入理解PHP之:Nginx 与 FPM 的工作机制
- 深入理解 PHP 之:Nginx 与 FPM 的工作机制
- 深入理解PHP之:Nginx 与 FPM 的工作机制
- php 5.3.3 下的php-fpm需要使用信号控制
- php-cgi php-fpm
- PHP FPM php-fpm.conf设置详解
- BZOJ1500: [NOI2005]维修数列
- iOS 书写高质量代码 怎么处理耦合关系
- (OK) usbip-utils - usbip attach - usbip detach
- php数组
- 14My2.0版本聊天系统(服务器selector移植)
- PHP - FPM信号机制
- 欢迎使用CSDN-markdown编辑器
- 快速发布局域网WEB地图的方法
- 机器学习系列(3)_逻辑回归应用之Kaggle泰坦尼克之灾
- Word Search
- 堆与栈的区别
- 【BZOJ 2321】[BeiJing2011集训]星器 脑洞
- python数据类型(python cookbook读书笔记一)
- 机器学习中的特征——特征选择的方法以及注意点