文章目录

  • 配置热更新流程
  • main
  • ngx_get_options
  • main
  • ngx_signal_process
  • ngx_os_signal_process
  • master 进程处理 SIGHUP 信号
  • ngx_signal_worker_processes
  • ngx_channel_handler
  • ngx_worker_process_cycle
  • ngx_worker_process_exit
  • master 进程处理 worker 进程退出后产生的 SIGCHLD 信号


配置热更新流程

当修改了 nginx 的配置文件时,执行 nginx -s reload 命令就可以使新的配置生效(业务不会中断),而不需要关闭当前的进程再重新启动。这就是所谓的配置热更新。这一节让我们看看 nginx 是如何实现配置热更新的。
先看看执行 nginx -s reload 命令命令后发生了什么。看这节内容时,需要先看前一节内容 nginx 启动流程。

main

main 函数中,会先调用 ngx_get_options 函数处理命令行参数:

if (ngx_get_options(argc, argv) != NGX_OK) {
        return 1;
    }

ngx_get_options

ngx_get_options 函数中,对应的处理 -s 选项的代码如下:

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)
                {
                    ngx_process = NGX_PROCESS_SIGNALLER;
                    goto next;
                }

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

ngx_signal 全局变量指向了 -s 选项后面的字符串,也就是 “reload”,然后下面的 if 语句判断 ngx_signal 是否是 -s 支持的4个参数——“stop”,“quit”,“reopen”,“reload是的”之一, 如果是的话将 ngx_process 全局变量赋值为 NGX_PROCESS_SIGNALLER

main

之后,main 函数流程在调用 ngx_init_cycle 函数处理完所有的配置项后,会经过如下的 if 语句:

if (ngx_signal) {
        return ngx_signal_process(cycle, ngx_signal);
    }

此时 ngx_signal 指向字符串 “reload”,满足 if 条件,main 函数直接调用 ngx_signal_process 函数,然后进程就退出了。

ngx_signal_process

ngx_signal_process 函数从配置文件中获取 nginx 进程 pid 文件路径,然后读取文件里的 pid ,最后调用 ngx_os_signal_process 函数:

ngx_int_t
ngx_signal_process(ngx_cycle_t *cycle, char *sig)
{
	...

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

}

ngx_os_signal_process

ngx_os_signal_process 函数是与操作系统相关的函数,我们一般都是使用 linux 系统,这个版本的函数代码如下:

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

它将 ngx_signal 的值与全局数组 signals 中各项比较,如果名字相同,就调用 kill 向正在运行的 nginx master 进程发送特定的信号。
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 }
};

我们找到 “reload” 对应的一项如下:

{ ngx_signal_value(NGX_RECONFIGURE_SIGNAL),
      "SIG" ngx_value(NGX_RECONFIGURE_SIGNAL),
      "reload",
      ngx_signal_handler },

这个数组每一项是 ngx_signal_t 结构:

typedef struct {
    int     signo;
    char   *signame;
    char   *name;
    void  (*handler)(int signo, siginfo_t *siginfo, void *ucontext); 
} ngx_signal_t;

signo 是信号的值,signame 是信号的名称,name 是信号对应于 -s 选项的名称,ngx_signal_handler 是进程对这个信号的处理函数。根据以下几个宏的定义:

#define NGX_RECONFIGURE_SIGNAL   HUP

#define ngx_signal_helper(n)     SIG##n
#define ngx_signal_value(n)      ngx_signal_helper(n)

我们就知道了,原来 nginx -s reload 命令做的工作基本上就是对正在运行的 nginx master 进程发送 SIGHUP 信号

master 进程处理 SIGHUP 信号

master 进程在收到 SIGHUP 信号后,会由信号处理函数 ngx_signal_handler 函数处理。对于这个信号的处理很简单:

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

就是将 ngx_reconfigure 全局变量置 1 。
在处理完信号后,master 进程从 ngx_master_process_cycle 函数中调用 sigsuspend 函数的地方恢复执行,它先调用 ngx_time_update 更新系统时间,然后判断各个全局变量是否置 1,如果 ngx_reconfigure 全局变量的值是 1 ,那么它的处理流程如下:

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_start_privileged_agent_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);
            ngx_start_privileged_agent_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_init_cyclengx_init_cycle 函数中会按新的配置文件处理配置项,若成功则返回一个新的 ngx_cycle_t 结构,我们将它保存在局部变量 cycle 和全局变量 ngx_cycle 中。

接着再调用 ngx_start_worker_processes 函数创建新的 worker 进程,传入的参数是新的 cycle,这样新的 wroker 进程就会以新的配置工作

最后调用 ngx_signal_worker_processes 通知当前所有的 worker 进程要求它们优雅地退出

ngx_signal_worker_processes

让我们来看看 master 进程如何通知 worker 进程退出:

static void
ngx_signal_worker_processes(ngx_cycle_t *cycle, int signo)
{
    ngx_int_t      i;
    ngx_err_t      err;
    ngx_channel_t  ch;

    ngx_memzero(&ch, sizeof(ngx_channel_t));
        switch (signo) {

    case ngx_signal_value(NGX_SHUTDOWN_SIGNAL):
        ch.command = NGX_CMD_QUIT;
        break;

    case ngx_signal_value(NGX_TERMINATE_SIGNAL):
        ch.command = NGX_CMD_TERMINATE;
        break;

    case ngx_signal_value(NGX_REOPEN_SIGNAL):
        ch.command = NGX_CMD_REOPEN;
        break;

    default:
        ch.command = 0;
    }
    ch.fd = -1;
    for (i = 0; i < ngx_last_process; i++) {

        ngx_log_debug7(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                       "child: %i pid:%P exiting:%d exited:%d detached:%d respawn:%d just_spawn:%d",
                       i,
                       ngx_processes[i].pid,
                       ngx_processes[i].exiting,
                       ngx_processes[i].exited,
                       ngx_processes[i].detached,
                       ngx_processes[i].respawn,
                       ngx_processes[i].just_spawn);

        if (ngx_processes[i].detached || ngx_processes[i].pid == -1) { /* 忽略不是 woker 进程的进程或已退出的 wroker 进程 */
            continue;
        }

        if (ngx_processes[i].just_spawn) { /* 忽略刚启动的 worker 进程 */
            ngx_processes[i].just_spawn = 0;
            continue;
        }

        if (ngx_processes[i].exiting /* 如果当前进程正在优雅地退出,则忽略 */
            && signo == ngx_signal_value(NGX_SHUTDOWN_SIGNAL))
        {
            continue;
        }

        if (ch.command) {
            /* 通过管道给进程发送消息 */
            if (ngx_write_channel(ngx_processes[i].channel[0],
                                  &ch, sizeof(ngx_channel_t), cycle->log)
                == NGX_OK)
            {
                if (signo != ngx_signal_value(NGX_REOPEN_SIGNAL)) {
                    ngx_processes[i].exiting = 1; /* 置上进程正在退出的标志 */
                }

                continue;
            }
        }

        ngx_log_debug2(NGX_LOG_DEBUG_CORE, cycle->log, 0,
                       "kill (%P, %d)", ngx_processes[i].pid, signo);

        if (kill(ngx_processes[i].pid, signo) == -1) {
            err = ngx_errno;
            ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
                          "kill(%P, %d) failed", ngx_processes[i].pid, signo);

            if (err == NGX_ESRCH) {
                ngx_processes[i].exited = 1;
                ngx_processes[i].exiting = 0;
                ngx_reap = 1;
            }

            continue;
        }

        if (signo != ngx_signal_value(NGX_REOPEN_SIGNAL)) {
            ngx_processes[i].exiting = 1; 
        }
    }
}

上面 ngx_signal_worker_processes 代码很明显地展示了,master 进程是通过管道发送消息给 worker 进程要求它退出的。消息由 ngx_channel_t 结构体表示:

typedef struct {
    ngx_uint_t  command;
    ngx_pid_t   pid;
    ngx_int_t   slot;
    ngx_fd_t    fd;
} ngx_channel_t;

对于发送要进程退出的消息,则 command 成员的值为 NGX_CMD_QUIT
worker 进程收到这个消息后,调用 ngx_channel_handler 函数处理。

ngx_channel_handler

这个函数循环调用 ngx_read_channel 函数读取消息,如果消息中的 command 成员的值为 NGX_CMD_QUIT 则会设置全局变量 ngx_quit 的值为 1 :

case NGX_CMD_QUIT:
            ngx_quit = 1;
            break;

ngx_worker_process_cycle

上一节内容我们说明 worker 进程在 ngx_worker_process_cycle 函数中循环处理事件:

for ( ;; ) {
        if (ngx_exiting) { /* 进程正在退出 */
            if (ngx_event_no_timers_left() == NGX_OK) {
                ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
                ngx_worker_process_exit(cycle);
            }
        }

        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "worker cycle");

        ngx_process_events_and_timers(cycle);

        if (ngx_terminate) { /* 立即退出进程 */
            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
            ngx_worker_process_exit(cycle);
        }

        if (ngx_quit) {  /* 等待请求处理结束后再退出,优雅地退出进程 */
            ngx_quit = 0;
            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
                          "gracefully shutting down");
            /* 修改进程名称为 worker process is shutting down */
            ngx_setproctitle("worker process is shutting down");

            if (!ngx_exiting) {
                ngx_exiting = 1; /* 设置进程正在退出标志 */
                ngx_set_shutdown_timer(cycle);
                ngx_close_listening_sockets(cycle);
                ngx_close_idle_connections(cycle);
            }
        }

        if (ngx_reopen) {
            ngx_reopen = 0;
            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
            ngx_reopen_files(cycle, -1);
        }
    }

当它发现全局变量 ngx_quit 的值为 1 时,就会修改进程名为 “worker process is shutting down”,然后设置 ngx_exiting 的值为 1,并调用 ngx_set_shutdown_timer 函数设置一个定时器。这个函数的代码如下:

void
ngx_set_shutdown_timer(ngx_cycle_t *cycle)
{
    ngx_core_conf_t  *ccf;

    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);

    if (ccf->shutdown_timeout) {
        ngx_shutdown_event.handler = ngx_shutdown_timer_handler;
        ngx_shutdown_event.data = cycle;
        ngx_shutdown_event.log = cycle->log;
        ngx_shutdown_event.cancelable = 1;

        ngx_add_timer(&ngx_shutdown_event, ccf->shutdown_timeout);
    }
}

这个定时器的值由配置项 worker_shutdown_timeout 决定,默认无这个配置项,也就不会启用这个定时器。如果使用了这个配置了项,则当定时器超时时,它的处理函数 ngx_shutdown_timer_handler 会强制断开所有的连接。

然后调用 ngx_close_listening_sockets 将所有的 listening socket 描述符从 epoll 中移除,并关闭 socket 。这样,woker 进程就不会处理所有的新建连接请求。它的代码如下:

/* 被 ngx_worker_process_cycle 等函数调用*/
void
ngx_close_listening_sockets(ngx_cycle_t *cycle)
{
    ngx_uint_t         i;
    ngx_listening_t   *ls;
    ngx_connection_t  *c;

    if (ngx_event_flags & NGX_USE_IOCP_EVENT) {
        return;
    }

    ngx_accept_mutex_held = 0;
    ngx_use_accept_mutex = 0;

    ls = cycle->listening.elts;
    for (i = 0; i < cycle->listening.nelts; i++) {

        c = ls[i].connection;

        if (c) {
            if (c->read->active) {
                if (ngx_event_flags & NGX_USE_EPOLL_EVENT) {

                    /*
                     * it seems that Linux-2.6.x OpenVZ sends events
                     * for closed shared listening sockets unless
                     * the events was explicitly deleted
                     */

                    ngx_del_event(c->read, NGX_READ_EVENT, 0);

                } else {
                    ngx_del_event(c->read, NGX_READ_EVENT, NGX_CLOSE_EVENT);
                }
            }

            ngx_free_connection(c);

            c->fd = (ngx_socket_t) -1;
        }

        ngx_log_debug2(NGX_LOG_DEBUG_CORE, cycle->log, 0,
                       "close listening %V #%d ", &ls[i].addr_text, ls[i].fd);

        if (ngx_close_socket(ls[i].fd) == -1) {
            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
                          ngx_close_socket_n " %V failed", &ls[i].addr_text);
        }

#if (NGX_HAVE_UNIX_DOMAIN)

        if (ls[i].sockaddr->sa_family == AF_UNIX
            && ngx_process <= NGX_PROCESS_MASTER
            && ngx_new_binary == 0)
        {
            u_char *name = ls[i].addr_text.data + sizeof("unix:") - 1;

            if (ngx_delete_file(name) == NGX_FILE_ERROR) {
                ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
                              ngx_delete_file_n " %s failed", name);
            }
        }

#endif

        ls[i].fd = (ngx_socket_t) -1;
    }

    cycle->listening.nelts = 0;
}

然后调用 ngx_close_idle_connections 关闭空闲的连接。空闲的连接是指这个连接上没有正在处理的 http 请求。它的代码如下:

void
ngx_close_idle_connections(ngx_cycle_t *cycle)
{
    ngx_uint_t         i;
    ngx_connection_t  *c;

    c = cycle->connections;

    for (i = 0; i < cycle->connection_n; i++) {

        /* THREAD: lock */

        if (c[i].fd != (ngx_socket_t) -1 && c[i].idle) {
            c[i].close = 1; /* 置上 close 标志,read event handler 里会关闭连接 */
            c[i].read->handler(c[i].read);
        }
    }
}

接着在 for 循环开始处,由于 ngx_exiting 已被置 1 ,将执行 ngx_event_no_timers_left 函数。这个函数只有当前所有的定时器是可取消的才返回 NGX_OK 。这意味,如果有活跃的连接,那么它不会返回 NGX_OK ,这样 worker 进程就不会退出,直到所有的连接正常关闭。如果活跃的连接数很多,那么旧的 worker 进程就会占用大量的内存。因此才有 worker_shutdown_timeout 配置项,使得 worker 进程超时后能够强制退出。当它返回 NGX_OK 时,会调用 ngx_worker_process_exit 函数。

ngx_worker_process_exit

ngx_worker_process_exit 函数先调用各个模块的 exit_process 回调函数,然后调用 exit 系统调用退出进程:

static void
ngx_worker_process_exit(ngx_cycle_t *cycle)
{
    ngx_uint_t         i;
    ngx_connection_t  *c;

    for (i = 0; cycle->modules[i]; i++) {
        if (cycle->modules[i]->exit_process) {
            cycle->modules[i]->exit_process(cycle);
        }
    }

    if (ngx_exiting) {
        c = cycle->connections;
        for (i = 0; i < cycle->connection_n; i++) {
            if (c[i].fd != -1
                && c[i].read
                && !c[i].read->accept
                && !c[i].read->channel
                && !c[i].read->resolver)
            {
                ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
                              "*%uA open socket #%d left in connection %ui",
                              c[i].number, c[i].fd, i);
                ngx_debug_quit = 1;
            }
        }

        if (ngx_debug_quit) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, "aborting");
            ngx_debug_point();
        }
    }

    /*
     * Copy ngx_cycle->log related data to the special static exit cycle,
     * log, and log file structures enough to allow a signal handler to log.
     * The handler may be called when standard ngx_cycle->log allocated from
     * ngx_cycle->pool is already destroyed.
     */

    ngx_exit_log = *ngx_log_get_file_log(ngx_cycle->log);

    ngx_exit_log_file.fd = ngx_exit_log.file->fd;
    ngx_exit_log.file = &ngx_exit_log_file;
    ngx_exit_log.next = NULL;
    ngx_exit_log.writer = NULL;

    ngx_exit_cycle.log = &ngx_exit_log;
    ngx_exit_cycle.files = ngx_cycle->files;
    ngx_exit_cycle.files_n = ngx_cycle->files_n;
    ngx_cycle = &ngx_exit_cycle;

    ngx_destroy_pool(cycle->pool);

    ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0, "exit");

    exit(0);
}

master 进程处理 worker 进程退出后产生的 SIGCHLD 信号

当 worker 进程退出后,master 进程会收到 SIGCHLD 信号,信号处理函数 ngx_signal_handler 中会将 ngx_reap 全局变量置 1 ,然后调用 ngx_process_get_status 函数:

static void
ngx_process_get_status(void)
{
    int              status;
    char            *process;
    ngx_pid_t        pid;
    ngx_err_t        err;
    ngx_int_t        i;
    ngx_uint_t       one;

    one = 0;

    for ( ;; ) {

        pid = waitpid(-1, &status, WNOHANG);

        if (pid == 0) { /* 有可能 worker 进程被暂停 */
            return;
        }

        if (pid == -1) {
            err = ngx_errno;

            if (err == NGX_EINTR) {
                continue;
            }

            if (err == NGX_ECHILD && one) {
                return;
            }

            /*
             * Solaris always calls the signal handler for each exited process
             * despite waitpid() may be already called for this process.
             *
             * When several processes exit at the same time FreeBSD may
             * erroneously call the signal handler for exited process
             * despite waitpid() may be already called for this process.
             */

            if (err == NGX_ECHILD) {
                ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, err,
                              "waitpid() failed");
                return;
            }

            ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, err,
                          "waitpid() failed");
            return;
        }


        one = 1;
        process = "unknown process";

        /* 更新已退出的进程状态 */
        for (i = 0; i < ngx_last_process; i++) {
            if (ngx_processes[i].pid == pid) {
                ngx_processes[i].status = status;
                ngx_processes[i].exited = 1; /* 进程已经退出 */
                process = ngx_processes[i].name;
                break;
            }
        }

        if (WTERMSIG(status)) {
#ifdef WCOREDUMP
            ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
                          "%s %P exited on signal %d%s",
                          process, pid, WTERMSIG(status),
                          WCOREDUMP(status) ? " (core dumped)" : "");
#else
            ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
                          "%s %P exited on signal %d",
                          process, pid, WTERMSIG(status));
#endif

        } else {
            ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0,
                          "%s %P exited with code %d",
                          process, pid, WEXITSTATUS(status));
        }

        if (WEXITSTATUS(status) == 2 && ngx_processes[i].respawn) {
            ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
                          "%s %P exited with fatal code %d "
                          "and cannot be respawned",
                          process, pid, WEXITSTATUS(status));
            ngx_processes[i].respawn = 0;
        }

        ngx_unlock_mutexes(pid);
    }
}

这个函数获取 worker 进程退出时的状态,将保存该进程状态的 ngx_process_t 结构中的 exited 标志置 1 ,表示该槽位所在的进程已经退出。
处理完信号后,又回到 ngx_master_process_cycle 函数中。

if (ngx_reap) { /* 处理 worker 进程退出 */
            ngx_reap = 0;
            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "reap children");

            live = ngx_reap_children(cycle);
        }

在知道 ngx_reap 置 1 时,它调用 ngx_reap_children 函数。

这个函数主要就是通知其他的 worker 进程关闭已退出的 worker 进程的管道描述符。并将保存该进程状态的 ngx_process_t 结构中的 pid 赋值为 -1 ,表明这个结构现在没有 worker 进程占用。下次创建新的 worker 进程就会重用这个结构。

至此,整个配置热更新流程结束。