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