在生產環境中,nginx 一般採用乙個主程序和多個工作程序的模式,其中主程序不需要處理網路事件,不負責業務的執行,而只是對工作程序進行管理,以實現重啟服務、平滑公升級、替換日誌檔案等功能, 和配置檔案實時生效,同時使用工作程序提供服務,如靜態檔案服務、反向**等功能。
那麼nginx是如何實現master重啟服務的,平滑公升級,日誌檔案替換,配置檔案實時生效呢?此外,主程序如何實時通知工作程序重啟服務、平滑公升級、替換日誌檔案、可配置檔案生效?答案是訊號。 下面我們來看一下nginx是如何結合**做到上述的。
因為nginx使用訊號來實現平滑公升級、日誌檔案替換、配置檔案實時效果、重啟服務等功能,所以nginx啟動過程中使用的訊號會註冊到作業系統核心中,其**實現如下:
int ngx_cdeclngx init signals() 的實現方式如下:main(int argc, char *const *ar**)
初始化訊號*
if (ngx_init_signals(cycle->log) != ngx_ok)
ngx_int_t從 ngx init signals() 函式的實現中,我們可以看到 nginx 通過呼叫 sigaction() 將支援的訊號註冊到作業系統核心中,當核心捕獲到對應的訊號時,會呼叫 ** 函式進行訊號處理。 從**中可以看出,其實nginx支援的所有訊號對應的處理程式都是一樣的,即ngx訊號hanlder()。 在這個函式中,nginx 中的訊號會根據鎖的訊號設定乙個對應的全域性變數,然後在主程序的處理迴圈中根據這個全域性變數執行相應的動作,後面會描述。ngx_init_signals(ngx_log_t *log)
ngx_signal_t *sig;
struct sigaction sa;Linux 核心使用的訊號。
遍歷 signals 陣列,將所有 nginx 支援的訊號*註冊到核心
for (sig = signals; sig->signo != 0; sig++)
return ngx_ok;
一般來說,當 nginx 程序(包括 master 程序和 worker 程序)已經在環境中執行時,所謂的平滑公升級、日誌檔案或配置檔案的替換實時生效等管理功能。 那麼nginx是如何從命令列控制這些特性的呢?nginx 的做法是啟動乙個新的 nginx 程序,將相應的控制訊號傳送到環境中已經存在的主程序,以便現有的服務可以執行相應的動作。 那麼,新的 nginx 程序如何向環境中已有的主程序發出訊號呢?其**實現如下:
int ngx_cdecl從實現中可以看出,新的 nginx 程序在執行後返回退出,因為這個新啟動的程序是用來傳送訊號的。 那麼,新程序如何向現有主程序發出訊號呢?答案是通過終止系統呼叫。 一般來說,有兩個步驟:第乙個是獲取儲存在nginx中的正在執行的master程序pid 檔案中的 pid,即 ngx 訊號 process() 函式的作用,實現如下:main(int argc, char *const *ar**)
"nginx -s xxx"*/
if (ngx_signal)
ngx_int_t其次,在獲取到正在執行的 master 程序的 pid 後,呼叫 kill 命令將新的 nginx 程序攜帶的訊號傳送給正在執行的 master 程序,這也是 ngx os 訊號 process() 函式的功能,實現如下: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];
獲取核心模組中儲存的配置項的結構指標*
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;CCF->PID 是 nginxPID 檔案。
file.log = cycle->log;
以可讀的方式開啟 nginx。PID 檔案。
file.fd = ngx_open_file(file.name.data, ngx_file_rdonly,ngx_file_open, ngx_file_default_access);
讀檔案 * n = ngx 讀檔案(&file, buf, ngx int64 len + 2, 0);
if (ngx_close_file(file.fd) == ngx_file_error)
if (n == ngx_error)
刪除結束控制字元*
while (n-- buf[n] == cr ||buf[n] == lf))
將字串轉換為數字以獲取主程序 pid*
pid = ngx_atoi(buf, +n);
封裝終止系統呼叫的信令功能*
return ngx_os_signal_process(cycle, sig, pid);
ngx_int_t至此,新的nginx程序的任務完成,然後返回退出,那麼接下來執行中的master程序會發生什麼呢?這涉及到主程序的工作迴圈。 我們知道 master 程序並不向外界提供服務,而是專門用來管理 worker 程序的,那麼在 nginx 中是如何實現的呢?ngx_os_signal_process(ngx_cycle_t *cycle, char *name, ngx_pid_t pid)
ngx_signal_t *sig;
遍歷 nginx 核心支援的訊號,找到與名稱相同的訊號,並通過 kill 系統。
呼叫向主程序傳送訊號。
for (sig = signals; sig->signo != 0; sig++)
ngx_log_error(ngx_log_alert, cycle->log, ngx_errno,"kill(%p, %d) failed", pid, sig->signo);
return 1;
如前所述,nginx 通過啟動乙個新程序向主程序傳送訊號,因此主程序正在等待訊號到達工作迴圈。 訊號到達後,會觸發訊號處理功能,然後檢測訊號的相應標誌位置,然後在主程序中檢測相應的標誌進行相應的處理。 在討論主程序如何處理特定訊號之前,讓我們先看看主程序對哪些訊號感興趣,如下所示:
上面**中的chld訊號不是由新的nginx程序傳送的,但是作業系統核心在檢測到子程序正在退出時,會向父程序(即master)傳送chld訊號,然後master程序會對此進行進一步分析,這就是ngx reap children()的功能。 在 ngx master process cycle() 中,我們可以看到 master 首先將自己感興趣的訊號新增到阻塞自身的訊號集合中(通過 sig 塊呼叫 sigprocmask(),然後在這些操作後呼叫 sigsuspend() 暫停自己,等待訊號集中的訊號發生,喚醒自己, 然後在訊號發生後根據全域性變數(見上表)進行相應的處理,處理流程圖如下:
從主程序的工作迴圈中,我們可以看到,當主程序接收到相應的訊號並完成自己的處理過程時,會通過ngx signal worker processes()向工作程序傳送相應的訊號。 例如,在收到退出訊號後,主站會向所有 worker 子程序傳送退出訊號,通知 worker 優雅退出(所謂優雅,其實就是處理現有連線,不接受新連線),並為監聽器關閉套接字控制代碼。 那麼,工作程序會對哪些訊號感興趣,當它從主程序接收到相應的訊號時會發生什麼呢?這就是工人工作週期的意義所在。 在 worker 程序中,在接收到 master 傳送的訊號後,我們也會看到 worker 對哪些訊號感興趣,並在引入 worker 工作週期之前設定相應的全域性變數,具體如下:
除了上述三個訊號之外,在工作程序的工作迴圈中還可以看到另乙個全域性變數 NGX 激勵。 只有乙個地方會設定此標誌,即在收到退出訊號後。 NGX Quit 只會首次將 NGX Exciting 設定為 1。 為什麼?因為當 worker 收到退出訊號時,它知道它需要優雅地關閉程序,即完成對現有連線的處理並不再接受新連線,NGX Exciting 表示退出狀態,即仍有尚未處理的連線。 以下是工作程序的工作原理:
這裡只是對主程序和工作程序的訊號處理過程的簡要說明,對於詳細的處理過程,如平滑公升級、配置檔案的實時效果等,你還是需要閱讀**才能更好地梳理細節。