從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。最後完成重啓。具體的邏輯比較細,後續再分析。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章