一文了解nginx中的signal處理機制
1. 引言
在計算機系統(tǒng)中,信號處理是一項重要的任務(wù),它允許操作系統(tǒng)和應(yīng)用程序之間進行通信和協(xié)調(diào)。在網(wǎng)絡(luò)服務(wù)器軟件中,如Nginx,信號處理機制起著關(guān)鍵作用,它能夠捕獲和處理各種類型的信號,從而實現(xiàn)服務(wù)器的靈活控制和運行時的動態(tài)行為。
nginx是一款高性能、輕量級的Web服務(wù)器和反向代理服務(wù)器,被廣泛應(yīng)用于構(gòu)建可靠、高效的Web應(yīng)用程序和服務(wù)。為了滿足各種需求和應(yīng)對不同的運行時情況,nginx提供了豐富的信號處理機制,使得管理員和開發(fā)人員能夠通過發(fā)送信號來實現(xiàn)對服務(wù)器的管理和控制。
信號是一種在操作系統(tǒng)中用于通知進程發(fā)生某種事件或請求某種操作的機制。它可以用于向進程發(fā)送中斷信號、終止信號、重啟信號等,以及自定義的應(yīng)用程序信號。nginx利用信號處理機制,可以捕獲和處理各種信號,例如重新加載配置文件、優(yōu)雅地停止或重啟服務(wù)器等。
深入理解nginx中的信號處理機制需要了解信號的基本概念和操作系統(tǒng)對信號的支持。當nginx接收到一個信號時,它會根據(jù)信號的類型和當前的運行狀態(tài)執(zhí)行相應(yīng)的操作。例如,當接收到重新加載配置文件的信號時,nginx會重新讀取配置文件并應(yīng)用新的配置,而不需要重啟整個服務(wù)器。
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ù)組進行遍歷,并將對應(yīng)的signal處理函數(shù)注冊到操作系統(tǒng)中。
在注冊一個signal信號的時候,需要分幾步:
初始化一個sigaction結(jié)構(gòu)體。
設(shè)置sigaction結(jié)構(gòu)體中sa_sigaction或者sa_handler(二選一)至信號處理函數(shù)。對于前者,需要設(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. 設(shè)置信號阻塞
為了nginx在處理信號的過程中確保能夠正確地處理并且避免被中斷,需要對信號設(shè)置block阻塞標記,從而能夠在處理指定信號的時候,避免新的信號進來打擾處理過程。
在ngx_master_process_cycle函數(shù)(當配置開啟了master_process模式時會作用master進程的主循環(huán))的開頭部分,進行了相關(guān)設(shè)置,源碼如下:
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);
這里對前面設(shè)置的10個信號(除了SIG_IGN)進行了設(shè)置。
4. signal信號的處理
在nginx中,signal信號是由ngx_signal_handler函數(shù)負責接收處理的。不過,ngx_signal_handler函數(shù)對信號的處理其實就是對應(yīng)接收到的信號設(shè)置相應(yīng)的標記,然后立即返回。譬如收到SIGTERM信號,則設(shè)置ngx_terminate = 1,收到SIGHUP信號,則設(shè)置ngx_reconfigure等等。其自己本身不進行實際的信號處理。
signal信號的處理邏輯是在主循環(huán)中進行的。如果是master/worker多進程運行模式下,在ngx_master_process_cycle函數(shù)中處理,如果是單進程運行模式下,則是在ngx_single_process_cycle函數(shù)中進行處理。在主循環(huán)函數(shù)中,它會檢查ngx_signal_handler中設(shè)置的標記位,然后根據(jù)各個標記位進行對應(yīng)的處理。
譬如在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被設(shè)置為1了,如果沒有設(shè)置,那么不執(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進程是如何通知正在提供服務(wù)的nginx進程執(zhí)行相應(yīng)的動作的呢?
這里就涉及到跨進程信號發(fā)送的操作了。
新啟動的進程根據(jù)命令行參數(shù),會讀取正在提供服務(wù)的nginx進程的pid文件,得到它的master進程的pid,然后調(diào)用系統(tǒng)函數(shù)kill來向master進程,這樣子master進程就會收到對應(yīng)的信號,然后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)設(shè)置,到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詳解Nginx 靜態(tài)文件服務(wù)配置及優(yōu)化
這篇文章主要介紹了Nginx 靜態(tài)文件服務(wù)配置及優(yōu)化,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-05-05結(jié)合 Nginx 將 DoNetCore 部署到 阿里云的安裝配置方法
這篇文章主要介紹了結(jié)合 Nginx 將 DoNetCore 部署到 阿里云的方法 ,需要的朋友可以參考下2018-10-10nginx+apache+mysql+php+memcached+squid搭建集群web環(huán)境
當前,LAMP開發(fā)模式是WEB開發(fā)的首選,如何搭建一個高效、可靠、穩(wěn)定的WEB服務(wù)器一直是個熱門主題,本文就是這個主題的一次嘗試。2011-03-03