PHP FPM源代碼反芻品味之五:信號signal處理

unix 的信號signal經常使用於進程管理.
好比管理員或者操做系統經過向master進程實現重啓和關閉服務.
master進程經過向worker進程發信號管理worker進程.php

一般會在進程自定義信號處理函數,處理相關的邏輯.
自定義信號處理函數,從使用者的角度看,很簡單,有點像快捷鍵的定製.數組

FPM 信號處理有如下幾個特色:

  1. master進程,不是直接處理信號,而是經過socketpair建立一個管道,把信號轉換一個字符,寫到管道里,master進程事件處理無限循環,讀取到這個字符時,調用對應的函數.
    socketpair,一般管道不一樣進程通訊,而這裏確是在同一個進程內部通訊,左手交右手,感受畫蛇添足.
    這樣作的好處是: 避免信號處理函數與事件處理邏輯同時運行的狀況.
    注意worker 進程沒有用到這個socketpair管道.
  2. 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 進程的信號被寫到了管道,管道另外一端的處理:socket

//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;
}
相關文章
相關標籤/搜索