一文了解nginx中的signal處理機制
1. 引言
在計算機系統(tǒng)中,信號處理是一項重要的任務,它允許操作系統(tǒng)和應用程序之間進行通信和協(xié)調(diào)。在網(wǎng)絡服務器軟件中,如Nginx,信號處理機制起著關(guān)鍵作用,它能夠捕獲和處理各種類型的信號,從而實現(xiàn)服務器的靈活控制和運行時的動態(tài)行為。
nginx是一款高性能、輕量級的Web服務器和反向代理服務器,被廣泛應用于構(gòu)建可靠、高效的Web應用程序和服務。為了滿足各種需求和應對不同的運行時情況,nginx提供了豐富的信號處理機制,使得管理員和開發(fā)人員能夠通過發(fā)送信號來實現(xiàn)對服務器的管理和控制。
信號是一種在操作系統(tǒng)中用于通知進程發(fā)生某種事件或請求某種操作的機制。它可以用于向進程發(fā)送中斷信號、終止信號、重啟信號等,以及自定義的應用程序信號。nginx利用信號處理機制,可以捕獲和處理各種信號,例如重新加載配置文件、優(yōu)雅地停止或重啟服務器等。
深入理解nginx中的信號處理機制需要了解信號的基本概念和操作系統(tǒng)對信號的支持。當nginx接收到一個信號時,它會根據(jù)信號的類型和當前的運行狀態(tài)執(zhí)行相應的操作。例如,當接收到重新加載配置文件的信號時,nginx會重新讀取配置文件并應用新的配置,而不需要重啟整個服務器。
2. signal信號處理函數(shù)的注冊
在nginx的main函數(shù)中有一個函數(shù)調(diào)用,如下:
if (ngx_init_signals(cycle->log) != NGX_OK) {
return 1;
}
這個調(diào)用的作用就是向操作系統(tǒng)注冊當前進程的signal處理函數(shù)。
下面是ngx_init_signals函數(shù)的實現(xiàn)源碼:
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_init_signals函數(shù)中,對定義的signals數(shù)組進行遍歷,并將對應的signal處理函數(shù)注冊到操作系統(tǒng)中。
在注冊一個signal信號的時候,需要分幾步:
初始化一個sigaction結(jié)構(gòu)體。
設置sigaction結(jié)構(gòu)體中sa_sigaction或者sa_handler(二選一)至信號處理函數(shù)。對于前者,需要設置sa_flags = SA_SIGINFO。
如果不希望在處理當前signal的時候block其他信號,那么用sigemptyset清空sa_mask。
最后,通過sigaction向操作系統(tǒng)注冊消息處理函數(shù)。
通過上面的循環(huán)遍歷,nginx注冊了SIGHUP(reload)、SIGUSR1(reopen)、SIGWINCH(noaccept)、SIGTERM(stop)、SIGQUIT(quit)、SIGUSR2(change bin)、SIGARLRM(timer)、SIGINT(stop)、SIGIO()、SIGCHLD(child reap)、SIGSYS(ignore)、SIGPIPE(ignore)共12個信號。
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 }
};
3. 設置信號阻塞
為了nginx在處理信號的過程中確保能夠正確地處理并且避免被中斷,需要對信號設置block阻塞標記,從而能夠在處理指定信號的時候,避免新的信號進來打擾處理過程。
在ngx_master_process_cycle函數(shù)(當配置開啟了master_process模式時會作用master進程的主循環(huán))的開頭部分,進行了相關(guān)設置,源碼如下:
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);
這里對前面設置的10個信號(除了SIG_IGN)進行了設置。
4. signal信號的處理
在nginx中,signal信號是由ngx_signal_handler函數(shù)負責接收處理的。不過,ngx_signal_handler函數(shù)對信號的處理其實就是對應接收到的信號設置相應的標記,然后立即返回。譬如收到SIGTERM信號,則設置ngx_terminate = 1,收到SIGHUP信號,則設置ngx_reconfigure等等。其自己本身不進行實際的信號處理。
signal信號的處理邏輯是在主循環(huán)中進行的。如果是master/worker多進程運行模式下,在ngx_master_process_cycle函數(shù)中處理,如果是單進程運行模式下,則是在ngx_single_process_cycle函數(shù)中進行處理。在主循環(huán)函數(shù)中,它會檢查ngx_signal_handler中設置的標記位,然后根據(jù)各個標記位進行對應的處理。
譬如在ngx_master_process_cycle函數(shù)中對配置重加載信號的處理邏輯如下:
if (ngx_reconfigure) {
ngx_reconfigure = 0;
if (ngx_new_binary) {
ngx_start_worker_processes(cycle, ccf->worker_processes,
NGX_PROCESS_RESPAWN);
ngx_start_cache_manager_processes(cycle, 0);
ngx_noaccepting = 0;
continue;
}
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reconfiguring");
cycle = ngx_init_cycle(cycle);
if (cycle == NULL) {
cycle = (ngx_cycle_t *) ngx_cycle;
continue;
}
ngx_cycle = cycle;
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx,
ngx_core_module);
ngx_start_worker_processes(cycle, ccf->worker_processes,
NGX_PROCESS_JUST_RESPAWN);
ngx_start_cache_manager_processes(cycle, 1);
/* allow new processes to start */
ngx_msleep(100);
live = 1;
ngx_signal_worker_processes(cycle,
ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
}
首先它判斷是否ngx_reconfigure被設置為1了,如果沒有設置,那么不執(zhí)行配置重加載的操作。
接著,如果是正在更新二進制文件操作,即ngx_new_binary=1,那么需要在這里啟動新的worker進程和cache manager進程。
再下來是調(diào)用ngx_init_cycle重新加載配置文件。
加載新的worker進程,最后通知老的worker進程進行優(yōu)雅退出。
5. 跨進程發(fā)送signal
在nginx運行的過程中,如果我們需要讓當前的nginx能夠重新加載配置文件,我們可以在命令行輸入以下命令:
nginx -s reload
又或者,如果我們希望停止nginx運行,我們可以在命令行輸入一下命令:
nginx -s stop
因為我們在命令行輸入以上命令的時候,其實shell又重新啟動了一個新的nginx進程,那新的nginx進程是如何通知正在提供服務的nginx進程執(zhí)行相應的動作的呢?
這里就涉及到跨進程信號發(fā)送的操作了。
新啟動的進程根據(jù)命令行參數(shù),會讀取正在提供服務的nginx進程的pid文件,得到它的master進程的pid,然后調(diào)用系統(tǒng)函數(shù)kill來向master進程,這樣子master進程就會收到對應的信號,然后master主循環(huán)函數(shù)就會進行信號的處理。
在main函數(shù)中,我們可以看到下面的代碼:
if (ngx_signal) {
return ngx_signal_process(cycle, ngx_signal);
}
意思就是向nginx進程發(fā)送指定的信號。再看ngx_signal_process函數(shù)的實現(xiàn):
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];
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "signal process started");
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;
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;
}
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);
}
if (n == NGX_ERROR) {
return 1;
}
while (n-- && (buf[n] == CR || buf[n] == LF)) { /* void */ }
pid = ngx_atoi(buf, ++n);
if (pid == (ngx_pid_t) NGX_ERROR) {
ngx_log_error(NGX_LOG_ERR, cycle->log, 0,
"invalid PID number \"%*s\" in \"%s\"",
n, buf, file.name.data);
return 1;
}
return ngx_os_signal_process(cycle, sig, pid);
}
非常好理解,就是讀取pid文件,然后調(diào)用ngx_os_signal_process函數(shù)對pid發(fā)送signal。由于linux/unix和windows的signal機制是不一樣的,所以ngx_os_signal_process函數(shù)針對兩類操作系統(tǒng)nginx進行了單獨實現(xiàn),這里不再贅述。
6. 總結(jié)
以上通過對nginx的源碼分析,從signal信號的注冊和阻塞狀態(tài)設置,到signal信號的處理,最后到跨進程singla信號的發(fā)送進行了詳細的介紹,我們可以從中一窺nginx如何利用操作系統(tǒng)的signal機制來實現(xiàn)對進程的各種控制功能。
到此這篇關(guān)于一文了解nginx中的signal處理機制的文章就介紹到這了,更多相關(guān)nginx signal 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Nginx+Keepalived實現(xiàn)雙機主備的方法
這篇文章主要介紹了Nginx+Keepalived實現(xiàn)雙機主備的方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-03-03
結(jié)合 Nginx 將 DoNetCore 部署到 阿里云的安裝配置方法
這篇文章主要介紹了結(jié)合 Nginx 將 DoNetCore 部署到 阿里云的方法 ,需要的朋友可以參考下2018-10-10
nginx+apache+mysql+php+memcached+squid搭建集群web環(huán)境
當前,LAMP開發(fā)模式是WEB開發(fā)的首選,如何搭建一個高效、可靠、穩(wěn)定的WEB服務器一直是個熱門主題,本文就是這個主題的一次嘗試。2011-03-03

