从nginx1.17.9源码理解nginx -s reload

使用nginx的时候,我们经常会使用nginx -s reload命令重启。下面我们就分析一下,执行这个命令的时候,nginx里发生了什么?我们从nginx的main函数开始。在main函数里,执行ngx_get_options函数命令行的初始化工作。
我们只看reload相关的逻辑。

// 解析命令行参数
static ngx_int_t
ngx_get_options(int argc, char *const *argv)
{
    u_char     *p;
    ngx_int_t   i;
    // 遍历每个参数./nginx -abc
    for (i = 1; i < argc; i++) {

        p = (u_char *) argv[i];
        // 命令行参数要以-开头
        if (*p++ != '-') {
            ngx_log_stderr(0, "invalid option: \"%s\"", argv[i]);
            return NGX_ERROR;
        }

        while (*p) {

            switch (*p++) {
             // ...
            // 给主进程发送一个信号
            case 's':
                // 紧跟随后(./nginx -sxxx),否则相隔了一个空格(./nginx -s xxx)
                if (*p) {
                    ngx_signal = (char *) p;

                } else if (argv[++i]) {
                    ngx_signal = argv[i];

                } else {
                    ngx_log_stderr(0, "option \"-s\" requires parameter");
                    return NGX_ERROR;
                }
                // 判断需要发送的信号
                if (ngx_strcmp(ngx_signal, "stop") == 0
                    || ngx_strcmp(ngx_signal, "quit") == 0
                    || ngx_strcmp(ngx_signal, "reopen") == 0
                    || ngx_strcmp(ngx_signal, "reload") == 0)
                {
                    ngx_process = NGX_PROCESS_SIGNALLER;
                    goto next;
                }

                ngx_log_stderr(0, "invalid option: \"-s %s\"", ngx_signal);
                return NGX_ERROR;
            }
        }

    next:

        continue;
    }

    return NGX_OK;
}

从上面的代码中我们知道,执行nginx -s reload的时候,nginx会设置ngx_signal 变量的值为reload。然后nginx在main函数里会判断这个标记。

     // 给主进程发送信号,则直接处理信号就行,不是启动nginx
    if (ngx_signal) {
        return ngx_signal_process(cycle, ngx_signal);
    }

ngx_int_t
ngx_signal_process(ngx_cycle_t *cycle, char *sig)
{
    ssize_t           n;
    ngx_pid_t         pid;
    ngx_file_t        file;
    ngx_core_conf_t  *ccf;
    u_char            buf[NGX_INT64_LEN + 2];

    // 从配置文件中拿到保存了主进程进程id的文件路径
    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);

    ngx_memzero(&file, sizeof(ngx_file_t));

    file.name = ccf->pid;
    file.log = cycle->log;
    // 打开保存了主进程进程id的文件
    file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY,
                            NGX_FILE_OPEN, NGX_FILE_DEFAULT_ACCESS);

    if (file.fd == NGX_INVALID_FILE) {
        ngx_log_error(NGX_LOG_ERR, cycle->log, ngx_errno,
                      ngx_open_file_n " \"%s\" failed", file.name.data);
        return 1;
    }
    // 把进程id读进来
    n = ngx_read_file(&file, buf, NGX_INT64_LEN + 2, 0);

    if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                      ngx_close_file_n " \"%s\" failed", file.name.data);
    }

    while (n-- && (buf[n] == CR || buf[n] == LF)) { /* void */ }
    // 解析字符串为int型
    pid = ngx_atoi(buf, ++n);

    // 处理
    return ngx_os_signal_process(cycle, sig, pid);

}

这时候nginx拿到了主进程进程id。

ngx_int_t
ngx_os_signal_process(ngx_cycle_t *cycle, char *name, ngx_pid_t pid)
{
    ngx_signal_t  *sig;

    for (sig = signals; sig->signo != 0; sig++) {
        // 找到给主进程发的信号
        if (ngx_strcmp(name, sig->name) == 0) {
            // 给主进程进程发信号
            if (kill(pid, sig->signo) != -1) {
                return 0;
            }
        }
    }

    return 1;
}

nginx会从signals变量中找到reload信号的配置。

 { 
         ngx_signal_value(NGX_RECONFIGURE_SIGNAL),
      "SIG" ngx_value(NGX_RECONFIGURE_SIGNAL),
      "reload",
      ngx_signal_handler 
 },

然后给主进程发送SIGNGX_RECONFIGURE_SIGNAL(linux的HUP)信号。就完成了使命。我们来看看主进程关于信号处理这块都做了些什么。nginx在启动的时候会注册信号处理函数。

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

// 根据signals变量定义的信号信息,注册到系统
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) {

        }
    }

    return NGX_OK;
}

所以我们知道reload的时候,主进程收到信号后,会执行ngx_signal_handler函数。该函数有这么一段代码。

 case ngx_signal_value(NGX_RECONFIGURE_SIGNAL):
            ngx_reconfigure = 1;
            action = ", reconfiguring";
            break;

主要是设置了ngx_reconfigure = 1;我们知道,nginx启动后,主进程是在死循环里监控worker进程的工作和处理信号的。所以我们看一下死循环里面的代码。

  if (ngx_reconfigure) {
            ngx_reconfigure = 0;

            cycle = ngx_init_cycle(cycle);

            ngx_cycle = cycle;
            ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx,
                                                   ngx_core_module);
            // 重新fork work进程和cache进程
            ngx_start_worker_processes(cycle, ccf->worker_processes,
                                       NGX_PROCESS_JUST_RESPAWN);
            ngx_start_cache_manager_processes(cycle, 1);

            ngx_msleep(100);

            live = 1;
            // 杀死旧的worker进程
            ngx_signal_worker_processes(cycle,
                                        ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
        }

主进程首先启动新的一批worker然后杀死旧的worker。最后完成重启。具体的逻辑比较细,后续再分析。

发布于 03-15

文章被以下专栏收录