unix 的信號signal常用于進程管理.
比如管理員或者操作系統通過向master進程實現重啟和關閉服務.
master進程通過向worker進程發信號管理worker進程.
通常會在進程自定義信號處理函數,處理相關的邏輯.
自定義信號處理函數,從使用者的角度看,很簡單,有點像快捷鍵的定制.
FPM 信號處理有以下幾個特點:
- master進程,不是直接處理信號,而是通過socketpair創建一個管道,把信號轉換一個字符,寫到管道里,master進程事件處理無限循環,讀取到這個字符時,調用對應的函數.
socketpair,通常管道不同進程通信,而這里確是在同一個進程內部通信,左手交右手,感覺多此一舉.
這樣做的好處是: 避免信號處理函數與事件處理邏輯同時運行的情況.
注意worker 進程沒有用到這個socketpair管道. - worker 進程的信號處理常見的方式,直接綁定處理函數.
處理過程: sig_soft_quit -> fpm_php_soft_quit -> fcgi_set_in_shutdown
fcgi_set_in_shutdown 函數很簡單 就是設置in_shutdown這個全局的worker進程開關
worker進程無限循環時,每次都會檢查這個開關, in_shutdown=1 時,跳出循環,優雅退出.
源碼注釋說明:
//fpm_signals.c
#include "fpm_config.h"
...
//整數數組,存放socketpair創建的管道兩端文件句柄
static int sp[2];
...
//worker進程信號處理函數
static void sig_soft_quit(int signo) /* {{{ */
{
int saved_errno = errno;
/* closing fastcgi listening socket will force fcgi_accept() exit immediately */
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;
}
//master進程信號處理函數
static void sig_handler(int signo) /* {{{ */
{
//C99 的數組初始化語法
//信號整數和字符的對應關系.
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];
//信號對應的字符寫到管道
write(sp[1], &s, sizeof(s));
errno = saved_errno;
}
int fpm_signals_init_main() /* {{{ */
{
struct sigaction act;
//創建socketpair管道,管道兩端的文件句柄fd 放在數組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;
}
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;
}
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; //系統默認動作
//worker 進程不使用socketpair創建的管道
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)) {
zlog(ZLOG_SYSERROR, "failed to init child signals: sigaction()");
return -1;
}
return 0;
}
int fpm_signals_get_fd()
{
return sp[0];
}
master 進程的信號被寫到了管道,管道另一端的處理:
//fpm_events.c
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 'Q' : /* SIGQUIT */
zlog(ZLOG_DEBUG, "received SIGQUIT");
zlog(ZLOG_NOTICE, "Finishing ...");
fpm_pctl(FPM_PCTL_STATE_FINISHING, FPM_PCTL_ACTION_SET);
break;
case '1' : /* SIGUSR1 */
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");
}
/* else no access log are set */
break;
case '2' : /* SIGUSR2 */
zlog(ZLOG_DEBUG, "received SIGUSR2");
zlog(ZLOG_NOTICE, "Reloading in progress ...");
fpm_pctl(FPM_PCTL_STATE_RELOADING, FPM_PCTL_ACTION_SET);
break;
}
if (fpm_globals.is_child) {
break;
}
} while (1);
return;
}