1. 程式人生 > >菜鳥學習Nginx之啟動流程(2)

菜鳥學習Nginx之啟動流程(2)

上一篇介紹了啟動流程中關於初始化ngx_cycle_t。由於ngx_cycle_t是Nginx核心結構,Nginx整個架構均是圍繞它構建起來的。雖然用了一整篇文章介紹ngx_cycle_t,但是感覺還是有些內容沒有介紹清楚。初始化ngx_cycle_t有一部分程式碼沒有介紹,ngx_conf_parse,該函式解析配置檔案nginx.conf函式。此函式就是單純解析配置檔案,裡面程式碼比較枯燥乏味,理解上比較困難,所以沒有深入閱讀,也就在博文中沒有體現出來。

今天繼續介紹啟動流程後半部分內容--服務程序啟動。

一、master/worker模式

眾所周知,Nginx是單執行緒服務程序,它為了充分利用CPU多核特性(提升吞吐量、高併發),它採用使用多程序方式並且為了保證高可用性,又採用了一個管理程序(master程序)和多個服務程序(worker程序)模式。

1.1、職責

  主要職責 備註
master

1、負責管理worker程序,例如:當worker程序異常退出,能夠及時排程起新的worker程序

2、接收外部訊號事件,例如:通過命令列發起平滑升級

外部程序只能通過訊號方式與master程序通訊,例如:命令列通過kill傳送訊息給master
worker

1、負責接收客戶端請求,例如:處理http請求

2、接收master程序指定,例如:master程序傳送Quit訊息,通知worker程序優雅退出

3、處理部分訊號

master與worker程序只能通過unix domain方式通訊。

二、啟動後臺程序

我們一般啟動程式,是通過登入終端,然後執行/usr/local/nginx/sbin/nginx。然後這樣啟動程序屬於前端程序,也就是說如果不加特殊處理,當終端關閉時nginx程序也會退出。那麼如何將前端程序變成後臺守護程序(精靈程序)?在main函式中有如下程式碼:

    if (ngx_init_signals(cycle->log) != NGX_OK) {//初始化訊號
        return 1;
    }
    /* fork出一個子程序,子程序為master 父程序(前端程序)退出*/
    if (!ngx_inherited && ccf->daemon) {
        if (ngx_daemon(cycle->log) != NGX_OK) {
            return 1;
        }
        ngx_daemonized = 1;
    }

    if (ngx_inherited) {
        ngx_daemonized = 1;
    }

 

/**
 * 生成守護程序
 */
ngx_int_t
ngx_daemon(ngx_log_t *log)
{
    int  fd;

    switch (fork()) {//建立一個子程序
    case -1:
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "fork() failed");
        return NGX_ERROR;

    case 0:
        break;

    default: //前端程序 直接退出
        exit(0);
    }
    //設定各種資料
    ngx_pid = ngx_getpid();

    if (setsid() == -1) {//很關鍵 設定新會話id 這樣就與終端會話 脫離
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "setsid() failed");
        return NGX_ERROR;
    }

    umask(0);

    fd = open("/dev/null", O_RDWR);
    if (fd == -1) {
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
                      "open(\"/dev/null\") failed");
        return NGX_ERROR;
    }

    if (dup2(fd, STDIN_FILENO) == -1) {
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "dup2(STDIN) failed");
        return NGX_ERROR;
    }

    if (dup2(fd, STDOUT_FILENO) == -1) {
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "dup2(STDOUT) failed");
        return NGX_ERROR;
    }

#if 0
    if (dup2(fd, STDERR_FILENO) == -1) {
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "dup2(STDERR) failed");
        return NGX_ERROR;
    }
#endif

    if (fd > STDERR_FILENO) {
        if (close(fd) == -1) {
            ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "close() failed");
            return NGX_ERROR;
        }
    }

    return NGX_OK;
}

通過生成守護程序,這樣就能夠保證Nginx始終在後臺執行提供服務。 

三、master程序

master程序,入口函式是ngx_master_process_cycle,該函式是在main函式呼叫,接下來重點分析一下該函式:

/**
 * master程序主迴圈函式
 */
void ngx_master_process_cycle(ngx_cycle_t *cycle)
{
    char *title;
    u_char *p;
    size_t size;
    ngx_int_t i;
    ngx_uint_t n, sigio;
    sigset_t set;/* 訊號集 */
    struct itimerval itv;
    ngx_uint_t live;
    ngx_msec_t delay;
    ngx_listening_t *ls;
    ngx_core_conf_t *ccf;

    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));
    /**
     * 設定訊號遮蔽字
     * 引數1: 操作型別
     *        SIG_BLOCK    將set訊號集與當前程序原有的訊號遮蔽字,進行或操作
     *        SIG_UNBLOCK  解除set指定的訊號
     *        SIG_SETMASK  將當前程序訊號遮蔽字設定為set訊號集。相當於重新賦值
     * 引數2: 
     * 引數3: 該引數是輸出引數 返回當前程序設定的訊號遮蔽字
     * 我的個人理解:
     *     訊號的發生是百分之百的非同步,而且可能併發產生多個訊號。那麼如果應用
     * 程序希望以阻塞方式對訊號進行處理,那麼就需要設定訊號遮蔽字。
     *
     * sigprocmask函式適用於單執行緒的程序
     * pthread_sigmask函式適用於多執行緒的程序
     */
    if (sigprocmask(SIG_BLOCK, &set, NULL) == -1)
    {
        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                      "sigprocmask() failed");
    }

    sigemptyset(&set);
    //計算命令列引數,用於重新設定程序名稱
    size = sizeof(master_process);

    for (i = 0; i < ngx_argc; i++)
    {
        size += ngx_strlen(ngx_argv[i]) + 1;
    }

    title = ngx_pnalloc(cycle->pool, size);
    if (title == NULL)
    {
        /* fatal */
        exit(2);
    }

    p = ngx_cpymem(title, master_process, sizeof(master_process) - 1);
    for (i = 0; i < ngx_argc; i++)
    {
        *p++ = ' ';
        p = ngx_cpystrn(p, (u_char *)ngx_argv[i], size);
    }

    ngx_setproctitle(title);//設定master程序名稱

    ccf = (ngx_core_conf_t *)ngx_get_conf(cycle->conf_ctx, ngx_core_module);
    //啟動worker程序,啟動成功之後就返回
    ngx_start_worker_processes(cycle, ccf->worker_processes,
                               NGX_PROCESS_RESPAWN);
    ngx_start_cache_manager_processes(cycle, 0);//啟動監控程序 預設不啟動

    ngx_new_binary = 0;
    delay = 0;
    sigio = 0;
    live = 1; //表示是否活躍

這段程式碼邏輯並不是很複雜,對於訊號這部分處理,我並不是很熟悉,我深入瞭解了一下,並將其寫到註釋中。如果還有不清楚的,建議看一下《Unix環境程式設計》。 上面程式碼中ngx_start_worker_processes函式用於建立worker程序,具體內容在下一面一小節中會詳細介紹。接下來,master程序進入主迴圈(無限迴圈),程式碼如下:

    for (;;)
    {
        if (delay)
        {//延遲 
            if (ngx_sigalrm)
            {
                sigio = 0;
                delay *= 2;
                ngx_sigalrm = 0;
            }

            ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                           "termination cycle: %M", delay);

            itv.it_interval.tv_sec = 0;
            itv.it_interval.tv_usec = 0;
            itv.it_value.tv_sec = delay / 1000;
            itv.it_value.tv_usec = (delay % 1000) * 1000;

            if (setitimer(ITIMER_REAL, &itv, NULL) == -1)
            {
                ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                              "setitimer() failed");
            }
        }

        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "sigsuspend");
        /**
         * 程序阻塞  非常重要一點
         * 等待訊號發生 當訊號產生會先呼叫訊號處理函式 當訊號處理函式結束後
         * sigsuspend才返回,執行後續程式碼
         */
        sigsuspend(&set);

        ngx_time_update();

        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                       "wake up, sigio %i", sigio);

        if (ngx_reap)
        {//當子程序異常退出時,會接收到SIGCHLD訊號,因此會在呼叫起來一個子程序
            ngx_reap = 0;
            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "reap children");

            live = ngx_reap_children(cycle);
        }

        if (!live && (ngx_terminate || ngx_quit))
        {//立即退出
            ngx_master_process_exit(cycle);
        }

        if (ngx_terminate)
        {//接收到TERM訊號 理應立即關閉 但是Nginx採用延遲關閉方式
            if (delay == 0)
            {
                delay = 50; //50ms
            }

            if (sigio)
            {
                sigio--;
                continue;
            }

            sigio = ccf->worker_processes + 2 /* cache processes */;

            if (delay > 1000)
            {//如果延遲大於1000ms 則暴力關閉程序
                ngx_signal_worker_processes(cycle, SIGKILL);
            }
            else
            {
                ngx_signal_worker_processes(cycle,
                                            ngx_signal_value(NGX_TERMINATE_SIGNAL));
            }

            continue;
        }

        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)
                {
                    ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
                                  ngx_close_socket_n " %V failed",
                                  &ls[n].addr_text);
                }
            }
            cycle->listening.nelts = 0;

            continue;
        }
        /**
         * 當master程序接收到HUP訊號,用於重新載入配置。例如:配置檔案變化,需要
         * 更新配置
         */
        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));
        }
        /**
         * 表示重啟worker程序,進入此分支的前提是master程序接收到SIGCHLD訊號,即
         * worker程序異常退出
         */
        if (ngx_restart)
        {
            ngx_restart = 0;
            ngx_start_worker_processes(cycle, ccf->worker_processes,
                                       NGX_PROCESS_RESPAWN);
            ngx_start_cache_manager_processes(cycle, 0);
            live = 1;
        }
        /**
         * 當master程序接收到USR1訊號,表明需要重新開啟日誌檔案
         */
        if (ngx_reopen)
        {
            ngx_reopen = 0;
            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
            ngx_reopen_files(cycle, ccf->user);
            ngx_signal_worker_processes(cycle,
                                        ngx_signal_value(NGX_REOPEN_SIGNAL));
        }
        /**
         * 當master程序接收到USR2訊號,表明進行平滑升級
         */
        if (ngx_change_binary)
        {
            ngx_change_binary = 0;
            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "changing binary");
            ngx_new_binary = ngx_exec_new_binary(cycle, ngx_argv);
        }
        /**
         * master程序收到WINCH訊號(通過kill傳送)後,會通過channel傳送QUIT訊息
         * 給worker程序,當worker程序接收到QUIT訊息就會優雅退出
         */
        if (ngx_noaccept)
        {
            ngx_noaccept = 0;
            ngx_noaccepting = 1;
            ngx_signal_worker_processes(cycle,
                                        ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
        }

master程序處理邏輯並不複雜,畢竟master程序是比較清閒的。master程序主要處理各種訊號事件,master也沒有事件驅動(epoll),並且worker程序也不會主動發訊息給master。 

四、worker程序

接下來看一下,worker程序。worker程序處理邏輯相比master程序就複雜了,但是這裡並不想介紹特別深入,只做到點睛之筆就行。因為後面還會專題進行詳細介紹。

/**
 * 建立worker程序
 * @param cycle  核心結構體 
 * @param n      worker程序數量
 * @param type   建立worker程序方式
 */
static void
ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t type)
{
    ngx_int_t i;
    ngx_channel_t ch;

    ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "start worker processes");

    ngx_memzero(&ch, sizeof(ngx_channel_t));

    ch.command = NGX_CMD_OPEN_CHANNEL; //傳送第一個訊息

    for (i = 0; i < n; i++)
    {
        //啟動多個worker程序 回撥函式為ngx_worker_process_cycle
        ngx_spawn_process(cycle, ngx_worker_process_cycle,
                          (void *)(intptr_t)i, "worker process", type);

        ch.pid = ngx_processes[ngx_process_slot].pid; /* 子程序id */
        ch.slot = ngx_process_slot; /* 子程序在ngx_processes陣列中索引 */
        ch.fd = ngx_processes[ngx_process_slot].channel[0]; /* 父程序socketpair fd */

        ngx_pass_open_channel(cycle, &ch);//通過unix domain傳送第一個訊息給worker程序
    }
}

 程式碼比較簡單,在看ngx_spawn_process函式之前,先來看一下流程圖,如下:

通過流程圖可知,該函式處理邏輯比較簡單,下面是具體內容: 

/**
 * 生成子程序
 * @param cycle 核心結構體
 * @param proc  子程序程序函式
 * @param data  子程序程序函式,入參
 * @param name  子程序程序名稱
 * @param respawn 生產子程序方式
 */
ngx_pid_t
ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data,
    char *name, ngx_int_t respawn)
{
    u_long     on;
    ngx_pid_t  pid; /* 子程序id */
    ngx_int_t  s; /* 子程序在陣列ngx_processes中索引 */

    /* 確定子程序在陣列ngx_processes中索引 */     
    if (respawn >= 0) {
        s = respawn;
    } else {
        for (s = 0; s < ngx_last_process; s++) {
            if (ngx_processes[s].pid == -1) {
                break;
            }
        }

        if (s == NGX_MAX_PROCESSES) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
                          "no more than %d processes can be spawned",
                          NGX_MAX_PROCESSES);
            return NGX_INVALID_PID;
        }
    }


    if (respawn != NGX_PROCESS_DETACHED) {

        /* Solaris 9 still has no AF_LOCAL */
        /* 建立socketpair 並且設定socket 選項 */
        if (socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) == -1)
        {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "socketpair() failed while spawning \"%s\"", name);
            return NGX_INVALID_PID;
        }

        ngx_log_debug2(NGX_LOG_DEBUG_CORE, cycle->log, 0,
                       "channel %d:%d",
                       ngx_processes[s].channel[0],
                       ngx_processes[s].channel[1]);

        if (ngx_nonblocking(ngx_processes[s].channel[0]) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          ngx_nonblocking_n " failed while spawning \"%s\"",
                          name);
            ngx_close_channel(ngx_processes[s].channel, cycle->log);
            return NGX_INVALID_PID;
        }

        if (ngx_nonblocking(ngx_processes[s].channel[1]) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          ngx_nonblocking_n " failed while spawning \"%s\"",
                          name);
            ngx_close_channel(ngx_processes[s].channel, cycle->log);
            return NGX_INVALID_PID;
        }

        on = 1;
        if (ioctl(ngx_processes[s].channel[0], FIOASYNC, &on) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "ioctl(FIOASYNC) failed while spawning \"%s\"", name);
            ngx_close_channel(ngx_processes[s].channel, cycle->log);
            return NGX_INVALID_PID;
        }

        if (fcntl(ngx_processes[s].channel[0], F_SETOWN, ngx_pid) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "fcntl(F_SETOWN) failed while spawning \"%s\"", name);
            ngx_close_channel(ngx_processes[s].channel, cycle->log);
            return NGX_INVALID_PID;
        }
        /* FD_CLOEXEC標誌 表示當執行exec家族函式時自動關閉當前檔案控制代碼 */
        if (fcntl(ngx_processes[s].channel[0], F_SETFD, FD_CLOEXEC) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "fcntl(FD_CLOEXEC) failed while spawning \"%s\"",
                           name);
            ngx_close_channel(ngx_processes[s].channel, cycle->log);
            return NGX_INVALID_PID;
        }

        if (fcntl(ngx_processes[s].channel[1], F_SETFD, FD_CLOEXEC) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "fcntl(FD_CLOEXEC) failed while spawning \"%s\"",
                           name);
            ngx_close_channel(ngx_processes[s].channel, cycle->log);
            return NGX_INVALID_PID;
        }

        ngx_channel = ngx_processes[s].channel[1];

    } else {
        ngx_processes[s].channel[0] = -1;
        ngx_processes[s].channel[1] = -1;
    }

    ngx_process_slot = s;


    pid = fork(); //建立子程序

    switch (pid) {

    case -1:
        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                      "fork() failed while spawning \"%s\"", name);
        ngx_close_channel(ngx_processes[s].channel, cycle->log);
        return NGX_INVALID_PID;

    case 0:
        ngx_pid = ngx_getpid();//子程序 fork返回值0
        proc(cycle, data); //子程序一直迴圈,不會結束。當結束時子程序也就是exit
        break;

    default://父程序 fork返回值為非0 是子程序 程序id
        break;
    }
    /**
     * 以下程式碼是父程序執行 子程序永遠不會執行到這裡. 因此子程序再退出時直接
     * 呼叫exit 沒有機會執行下列程式碼
     */
    ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "start %s %P", name, pid);
    /* 設定子程序資訊 */
    ngx_processes[s].pid = pid;
    ngx_processes[s].exited = 0;

    if (respawn >= 0) {
        return pid;
    }

    ngx_processes[s].proc = proc;
    ngx_processes[s].data = data;
    ngx_processes[s].name = name;
    ngx_processes[s].exiting = 0;

    switch (respawn) {

    case NGX_PROCESS_NORESPAWN:
        ngx_processes[s].respawn = 0;
        ngx_processes[s].just_spawn = 0;
        ngx_processes[s].detached = 0;
        break;

    case NGX_PROCESS_JUST_SPAWN:
        ngx_processes[s].respawn = 0;
        ngx_processes[s].just_spawn = 1;
        ngx_processes[s].detached = 0;
        break;

    case NGX_PROCESS_RESPAWN:
        ngx_processes[s].respawn = 1;
        ngx_processes[s].just_spawn = 0;
        ngx_processes[s].detached = 0;
        break;

    case NGX_PROCESS_JUST_RESPAWN:
        ngx_processes[s].respawn = 1;
        ngx_processes[s].just_spawn = 1;
        ngx_processes[s].detached = 0;
        break;

    case NGX_PROCESS_DETACHED:
        ngx_processes[s].respawn = 0;
        ngx_processes[s].just_spawn = 0;
        ngx_processes[s].detached = 1;
        break;
    }

    if (s == ngx_last_process) {
        ngx_last_process++;
    }

    return pid;
}

四、訊號

訊號註冊是在main函式中呼叫,具體註冊內容比較簡單,如下:

/**
 * 訊號註冊
 */
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));
        sa.sa_handler = sig->handler;/* 訊號處理函式 */
        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;
}

當有訊號事件發生,程序會發生中斷,然後呼叫訊號處理函式,如下為訊號中斷處理函式:

static void
ngx_signal_handler(int signo)
{
    char            *action;
    ngx_int_t        ignore;
    ngx_err_t        err;
    ngx_signal_t    *sig;

    ignore = 0;

    err = ngx_errno;

    for (sig = signals; sig->signo != 0; sig++) {
        if (sig->signo == signo) {
            break;
        }
    }

    ngx_time_sigsafe_update();

    action = "";

    switch (ngx_process) {
    /* master程序處理訊號 */
    case NGX_PROCESS_MASTER:
    case NGX_PROCESS_SINGLE:
        switch (signo) {

        case ngx_signal_value(NGX_SHUTDOWN_SIGNAL):
            ngx_quit = 1;
            action = ", shutting down";
            break;

        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;
        /* 用於平滑升級 USR2 */
        case ngx_signal_value(NGX_CHANGEBIN_SIGNAL):
            if (getppid() > 1 || ngx_new_binary > 0) {

                /*
                 * Ignore the signal in the new binary if its parent is
                 * not the init process, i.e. the old binary's process
                 * is still running.  Or ignore the signal in the old binary's
                 * process if the new binary's process is already running.
                 */

                action = ", ignoring";
                ignore = 1;
                break;
            }

            ngx_change_binary = 1;
            action = ", changing binary";
            break;

        case SIGALRM:
            ngx_sigalrm = 1;
            break;

        case SIGIO:
            ngx_sigio = 1;
            break;

        case SIGCHLD:
            ngx_reap = 1;
            break;
        }

        break;
    /* worker程序處理相關訊號 */
    case NGX_PROCESS_WORKER:
    case NGX_PROCESS_HELPER:
        switch (signo) {

        case ngx_signal_value(NGX_NOACCEPT_SIGNAL):
            if (!ngx_daemonized) {
                break;
            }
            ngx_debug_quit = 1;
            /* fall through */
        case ngx_signal_value(NGX_SHUTDOWN_SIGNAL):
            ngx_quit = 1;
            action = ", shutting down";
            break;

        case ngx_signal_value(NGX_TERMINATE_SIGNAL):
        case SIGINT:
            ngx_terminate = 1;
            action = ", exiting";
            break;

        case ngx_signal_value(NGX_REOPEN_SIGNAL):
            ngx_reopen = 1;
            action = ", reopening logs";
            break;
        /* worker程序忽略HUP USR2訊號 */
        case ngx_signal_value(NGX_RECONFIGURE_SIGNAL):
        case ngx_signal_value(NGX_CHANGEBIN_SIGNAL):
        case SIGIO:
            action = ", ignoring";
            break;
        }

        break;
    }

    ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0,
                  "signal %d (%s) received%s", signo, sig->signame, action);

    if (ignore) {
        ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, 0,
                      "the changing binary signal is ignored: "
                      "you should shutdown or terminate "
                      "before either old or new binary's process");
    }

    if (signo == SIGCHLD) {/* 表示worker程序有退出 */
        ngx_process_get_status();
    }

    ngx_set_errno(err);
}

通過中斷處理函式可知,有兩點不同:

1、master程序處理訊號種類要比worker程序多,worker程序會忽略訊號HUP,USR2。

2、給master傳送訊號一般都是通過kill或者nginx -s方式,然而給worker程序一般是通過master程序直接呼叫kill函式傳送訊號。 

五、總結

本篇介紹Nginx啟動流程中master/worker模式,從程式碼中可知,master程序處理邏輯並不是很複雜。複雜的地方是在於worker程序。再下一篇將會詳細介紹worker程序主函式ngx_worker_process_cycle。