Nginx源碼學(xué)習(xí)——向master進(jìn)程發(fā)送信號(hào)

可以用命令行控制Nginx的啟動(dòng)與停止、重載配置文件、回滾日志文件、平滑升級(jí)等。而通過Nginx命令行發(fā)送信號(hào)有兩種方式:

  1. nginx [-s signal] 如快速地停止服務(wù) /usr/local/nginx/sbin/nginx -s stop;-s參數(shù)告訴Nginx程序向正在運(yùn)行的Nginx服務(wù)發(fā)送信號(hào), signal是要被發(fā)送的信號(hào)。
  2. 使用kill命令,如快速地停止服務(wù) kill -s SIGTERM <nginx master pid>

通過學(xué)習(xí)Nginx源碼,可以看到對(duì)這兩種方式進(jìn)行的不同處理。


第一種方式

當(dāng)在終端鍵入命令 nginx [-s signal]后,main()函數(shù)從頭開始執(zhí)行。執(zhí)行過程中調(diào)用ngx_get_option獲取命令行參數(shù)

 //選項(xiàng)參數(shù)的解析,該函數(shù)的設(shè)計(jì)使其能夠獨(dú)立于操作系統(tǒng)平臺(tái)
    if (ngx_get_options(argc, argv) != NGX_OK) {
        return 1;
    }

由參數(shù)s知——要求發(fā)送信號(hào)。具體何種信號(hào)由后一個(gè)參數(shù)決定,包括:stop quit reopen reload ,它們被保存在全局變量ngx_signal中

//ngx_get_option函數(shù)
    case 's':
          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)
           {
               //標(biāo)記本番執(zhí)行是為了傳遞信號(hào)
               ngx_process = NGX_PROCESS_SIGNALLER;
               goto next;
           }

           ngx_log_stderr(0, "invalid option: \"-s %s\"", ngx_signal);
           return NGX_ERROR;

再次回到main函數(shù),根據(jù)全局變量ngx_signal的值判斷有無信號(hào)發(fā)送。

  //向master進(jìn)程發(fā)送ngx_signal中保存的信號(hào),然后結(jié)束本次main函數(shù)的執(zhí)行。
    if (ngx_signal) {
        return ngx_signal_process(cycle, ngx_signal);
    }

進(jìn)入ngx_signal_process函數(shù),該函數(shù)主要有兩步行為:

  • 打開pid文件(nginx啟動(dòng)時(shí)生成了保存pid的文件),獲取master進(jìn)程的pid。
  • 調(diào)用ngx_os_signal_process函數(shù),將信號(hào)和master的pid傳遞之。
ngx_int_t
ngx_signal_process(ngx_cycle_t *cycle, char *sig)
{
    ......
    打開pid文件,獲取文件中記錄的master進(jìn)程pid
    ......
    return ngx_os_signal_process(cycle, sig, pid);
}

再看ngx_os_signal_process函數(shù)

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;
            }

            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "kill(%P, %d) failed", pid, sig->signo);
        }
    }
    return 1;
}

signals數(shù)組記錄了信號(hào)和相應(yīng)的信號(hào)處理程序。ngx_os_signal_process函數(shù)遍歷整個(gè)數(shù)組,根據(jù)Nginx自定義的信號(hào)名稱找到信號(hào)后,使用kill(pid, signalno)函數(shù)發(fā)送信號(hào)至master進(jìn)程。


typedef struct {
    int     signo;//信號(hào)編號(hào)
    char   *signame;//信號(hào)的系統(tǒng)名稱,如SIGTERM,前綴為SIG
    char   *name;    //nginx自定義的信號(hào)名稱,如quit,terminate
    //函數(shù)指針,指向信號(hào)處理函數(shù)
    void  (*handler)(int signo, siginfo_t *siginfo, void *ucontext);
} ngx_signal_t;


//信號(hào)數(shù)組
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 },
    ......
    ......
  }

總結(jié),通過以上代碼可以看到用第一種方式發(fā)送信號(hào)的具體過程。這種方式要以main函數(shù)為入口,一步步執(zhí)行,但會(huì)在某個(gè)位置判斷出本番執(zhí)行的目的是為了發(fā)送信號(hào),因此獲取master的pid,使用kill函數(shù)發(fā)送信號(hào),信號(hào)發(fā)送完成后,本番執(zhí)行也就返回退出main函數(shù)了,接下來就是信號(hào)處理函數(shù)和master進(jìn)程的工作了。


第二種方式

當(dāng)在終端鍵入kill [-s SIGNAL PID]后,會(huì)直接向nginx master進(jìn)程發(fā)送信號(hào)。用戶首先要知道m(xù)aster的進(jìn)程ID,可通過ps命令查看: ps -ef | grep nginx.
kill命令會(huì)直接觸發(fā)信號(hào)處理函數(shù)的執(zhí)行。


信號(hào)處理函數(shù)

無論是第一種方式還是第二種方式,都會(huì)觸發(fā)同一個(gè)信號(hào)處理函數(shù)。介紹信號(hào)處理函數(shù)之前,先要說明信號(hào)是怎么和信號(hào)處理函數(shù)綁定的。

前文中介紹了signals[]信號(hào)數(shù)組,它是一個(gè)全局?jǐn)?shù)組。main函數(shù)執(zhí)行過程中,會(huì)調(diào)用ngx_init_signals函數(shù),通過遍歷signals數(shù)組,獲得信號(hào)編號(hào)和信號(hào)處理函數(shù)指針,然后通過sigaction函數(shù)綁定之。代碼如下所示:

//將signals[]信號(hào)表中的所有信號(hào)分別和對(duì)應(yīng)的信號(hào)處理函數(shù)綁定
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) {
              ......
              ...do_something();
        }
    }

    return NGX_OK;
}

下面看信號(hào)處理函數(shù)ngx_signal_handler代碼片段。當(dāng)該函數(shù)被觸發(fā)后,根據(jù)信號(hào)編號(hào),改寫全局變量的值( 如ngx_quit, ngx_terminate等),當(dāng)master進(jìn)程被喚醒后(見下文),將根據(jù)該全局變量的值采取行動(dòng)。

 switch (signo) {

        //SIGQUIT信號(hào)
        case ngx_signal_value(NGX_SHUTDOWN_SIGNAL):
            ngx_quit = 1;
            action = ", shutting down";
            break;
        //SIGTERM信號(hào)或SIGINIT信號(hào)
        case ngx_signal_value(NGX_TERMINATE_SIGNAL):
        case SIGINT:
            ngx_terminate = 1;
            action = ", exiting";
            break;

        case ngx_signal_value(NGX_NOACCEPT_SIGNAL):
            if (ngx_daemonized) {
                ngx_noaccept = 1;
                action = ", stop accepting connections";
            }
            break;

        case ngx_signal_value(NGX_RECONFIGURE_SIGNAL):
            ngx_reconfigure = 1;
            action = ", reconfiguring";
            break;

        case ngx_signal_value(NGX_REOPEN_SIGNAL):
            ngx_reopen = 1;
            action = ", reopening logs";
            break;
            ......
            ......
         break;
 }
master的循環(huán)與喚醒

APUE中介紹了sigsuspend函數(shù)的其中一種使用方法。
書中288頁(中文):

sigsuspend另一種應(yīng)用是等待一個(gè)信號(hào)處理程序設(shè)置一個(gè)全局變量。

Nginx中就使用了這種方法。
master進(jìn)程循環(huán)在void ngx_master_process_cycle(ngx_cycle_t *cycle)函數(shù)中進(jìn)行,循環(huán)內(nèi)部調(diào)用了sigsuspend函數(shù),由此,在捕捉到一個(gè)信號(hào)或發(fā)生了一個(gè)會(huì)終止該進(jìn)程的信號(hào)之前,該進(jìn)程都是處于掛起狀態(tài)。如果捕捉到一個(gè)信號(hào)而且從該信號(hào)處理程序返回,則sigsuspend返回。
前文說過,信號(hào)處理函數(shù)將更改全局變量,那么master進(jìn)程中sigsuspend返回后,將逐個(gè)檢測全局變量。

for(;;){

  ......
  ......
  sigsuspend(&sig);
  ......

}

全局變量檢測示例:
當(dāng)變量ngx_quit置為1時(shí),向所有子進(jìn)程發(fā)送QUIT通知,并關(guān)閉監(jiān)聽socket。

if (ngx_quit) {
   ngx_signal_worker_processes(cycle,ngx_signal_value(NGX_SHUTDOWN_SIGNAL);
     ls = cycle->listening.elts;
     for (n = 0; n < cycle->listening.nelts; n++) {
         if (ngx_close_socket(ls[n].fd) == -1) {
            .......
         }
      }
      cycle->listening.nelts = 0;
      continue;
 }

總結(jié),Nginx執(zhí)行初期,會(huì)將指定信號(hào)與信號(hào)處理程序綁定。然后無論以nginx [-s signal]方式(其本質(zhì)還是調(diào)用kill函數(shù)),還是在終端上以kill命令發(fā)送信號(hào),都會(huì)觸發(fā)信號(hào)處理函數(shù)更改全局變量,且喚醒master進(jìn)程檢測全局變量的值,進(jìn)而采取一系列行動(dòng),如終止子進(jìn)程、關(guān)閉描述符等。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容