vislee / leevis.com

Blog
87 stars 13 forks source link

nginx中的信号处理 #128

Open vislee opened 7 years ago

vislee commented 7 years ago

概述

通过向nginx的master进程发送信号来管理nginx。例如:向nginx进程发送sighup信号重新加载配置。操作:kill -1 pid 或者nginx -s reload。

知识回顾

linux信号是进程间通信的一种方式,信号是一种软中断,实际上也属于进程控制的一部分。 软中断信号signal用来通知进程发生了异步事件,进程之间通过kill系统调用发送软中断,内核也会因内部事件而给进程发送信号,通知发生了某个事件。信号仅通知发生了什么事件,并不给进程传递数据。

收到信号的进程可以有3种方式来处理,1: 进程通过signal等函数指定对应的处理函数。2: 忽略信号。3: 系统默认的处理方式。

实际执行信号的处理动作称为信号递达(delivery),信号从产生到递达之间的状态称为信号未决(pending)。进程可以选择阻塞信号,被阻塞的信号产生时保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。sigsuspend函数可以临时解除信号阻塞。

下面是信号相关的函数:

注册信号回调函数

// sig 指定信号编号
// func 处理回调函数,SIG_IGN表示忽略信号。SIG_DFL表示采用系统默认处理方式。
void (*signal(int sig, void (*func)(int)))(int);

struct sigaction
{
    // 同signal函数的回调
    void (*sa_handler) (int);
    // 新的信号处理函数
    void  (*sa_sigaction)(int, siginfo_t *, void *);
    // 信号屏蔽
    sigset_t sa_mask;
    // 标识位,SA_SIGINFO表示使用sa_sigaction作为信号回调函数。
    // SA_RESTART表示被信号中断的系统调用会自动重启(不重启的需要处理EINTR错误)
    // SA_NOCLDSTOP使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号
    // SA_NOCLDWAIT使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程。
    // SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。
    // SA_RESETHAND:信号处理之后重新设置为默认的处理方式。
    int sa_flags;
    void (*sa_restorer) (void);
}
// signum 指定信号编号,act新的设置信号回调的结构体,oldact 用来获取已经设置的信号的信息
int sigaction(int signum,const struct sigaction *act ,struct sigaction *oldact);
typedef struct {
    unsigned long sig[_NSIG_WORDS];
} sigset_t

sigemptyset(sigset_t set):信号集全部清0; sigfillset(sigset_t set): 信号集全部置1,则信号集包含linux支持的64种信号; sigaddset(sigset_t set, int signum):向信号集中加入signum信号; sigdelset(sigset_t set, int signum):向信号集中删除signum信号; sigismember(const sigset_t set, int signum):判定信号signum是否存在信号集中。 // SIG_BLOCK:将set指向信号集中的信号,添加到进程阻塞信号集; // SIG_UNBLOCK:将set指向信号集中的信号,从进程阻塞信号集删除 // SIG_SETMASK:将set指向信号集中的信号,设置成进程阻塞信号集 sigprocmask(int how, const sigset_t set, sigset_t oldset)); 不同how参数,实现不同功能 sigpending(sigset_t set)):获取已发送到进程,却被阻塞的所有信号 sigsuspend(const sigset_t *mask)):用mask代替进程的原有掩码,并暂停进程执行,直到收到信号再恢复原有掩码并继续执行进程。

参考:

  1. Linux信号系统
  2. Linux 信号signal处理机制
  3. Linux信号机制

nginx中信号的处理

在main函数中会调用ngx_init_signals注册信号回调函数。

ngx_signal_t  signals[] = {
    { ngx_signal_value(NGX_RECONFIGURE_SIGNAL),
      "SIG" ngx_value(NGX_RECONFIGURE_SIGNAL),
      "reload",
      ngx_signal_handler },

    { ngx_signal_value(NGX_REOPEN_SIGNAL),
      "SIG" ngx_value(NGX_REOPEN_SIGNAL),
      "reopen",
      ngx_signal_handler },

    { ngx_signal_value(NGX_NOACCEPT_SIGNAL),
      "SIG" ngx_value(NGX_NOACCEPT_SIGNAL),
      "",
      ngx_signal_handler },

    { ngx_signal_value(NGX_TERMINATE_SIGNAL),
      "SIG" ngx_value(NGX_TERMINATE_SIGNAL),
      "stop",
      ngx_signal_handler },

    { ngx_signal_value(NGX_SHUTDOWN_SIGNAL),
      "SIG" ngx_value(NGX_SHUTDOWN_SIGNAL),
      "quit",
      ngx_signal_handler },

    { ngx_signal_value(NGX_CHANGEBIN_SIGNAL),
      "SIG" ngx_value(NGX_CHANGEBIN_SIGNAL),
      "",
      ngx_signal_handler },

    { SIGALRM, "SIGALRM", "", ngx_signal_handler },

    { SIGINT, "SIGINT", "", ngx_signal_handler },

    { SIGIO, "SIGIO", "", ngx_signal_handler },

    { SIGCHLD, "SIGCHLD", "", ngx_signal_handler },

    { SIGSYS, "SIGSYS, SIG_IGN", "", NULL },

    { SIGPIPE, "SIGPIPE, SIG_IGN", "", NULL },

    { 0, NULL, "", NULL }
};

ngx_int_t
ngx_init_signals(ngx_log_t *log)
{
    ngx_signal_t      *sig;
    struct sigaction   sa;

    for (sig = signals; sig->signo != 0; sig++) {
        ngx_memzero(&sa, sizeof(struct sigaction));

        if (sig->handler) {
            sa.sa_sigaction = sig->handler;
            sa.sa_flags = SA_SIGINFO;

        } else {
            sa.sa_handler = SIG_IGN;
        }

        sigemptyset(&sa.sa_mask);
        if (sigaction(sig->signo, &sa, NULL) == -1) {
#if (NGX_VALGRIND)
            ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
                          "sigaction(%s) failed, ignored", sig->signame);
#else
            ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
                          "sigaction(%s) failed", sig->signame);
            return NGX_ERROR;
#endif
        }
    }

    return NGX_OK;
}

在调用ngx_master_process_cycle函数启动worker进程前,先阻塞设置的信号。

    // 阻塞信号
    sigemptyset(&set);
    sigaddset(&set, SIGCHLD);
    sigaddset(&set, SIGALRM);
    sigaddset(&set, SIGIO);
    sigaddset(&set, SIGINT);
    sigaddset(&set, ngx_signal_value(NGX_RECONFIGURE_SIGNAL));
    sigaddset(&set, ngx_signal_value(NGX_REOPEN_SIGNAL));
    sigaddset(&set, ngx_signal_value(NGX_NOACCEPT_SIGNAL));
    sigaddset(&set, ngx_signal_value(NGX_TERMINATE_SIGNAL));
    sigaddset(&set, ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
    sigaddset(&set, ngx_signal_value(NGX_CHANGEBIN_SIGNAL));

    if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) {
        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                      "sigprocmask() failed");
    }

    // 清空信号集
    sigemptyset(&set);

    ......

    // fork 工作进程
    ngx_start_worker_processes(cycle, ccf->worker_processes,
                               NGX_PROCESS_RESPAWN);

    ......

    // master进程最后会阻塞到该函数,等待信号的唤醒。
    // 信号阻塞一直没有解除,信号只会再此处被注销执行。
    sigsuspend(&set);

在fork出的子进程会调用ngx_worker_process_init函数初始化worker进程。在该函数会清楚从父进程继承的阻塞信号位。

    sigemptyset(&set);

    if (sigprocmask(SIG_SETMASK, &set, NULL) == -1) {
        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                      "sigprocmask() failed");
    }