Nginx 源码阅读笔记6 master 主循环

来源:互联网 发布:高性能php架构 编辑:程序博客网 时间:2024/06/06 19:54

只看了 master 模式的代码,没有考虑单进程的情况,master 主循环位于ngx_master_process_cycle函数,在main函数的最后被调用
首先是阻塞信号,这些信号的信号处理函数已经在main函数中设置了

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) { ... }sigemptyset(&set);

这里阻塞信号是为了之后调用sigsuspend函数等待信号,而参数就是最后被置为空的set
然后是设置进程标题,方法在之前说过,修改argv[0]的值,具体体现就是ngx_setproctitle这个函数

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_process是一个char数组,值为master process,这段代码整体逻辑比较简单
然后是根据配置文件中work_processes的值,启动指定数量的进程

ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);ngx_start_worker_processes(cycle, ccf->worker_processes, NGX_PROCESS_RESPAWN);ngx_start_cache_manager_processes(cycle, 0);

这里我略过了关于cache_manager的代码,以后有机会再看吧,主要看下ngx_start_worker_processes函数

for (i = 0; i < n; i++) {    ngx_spawn_process(cycle, ngx_worker_process_cycle,                      (void *) (intptr_t) i, "worker process", type);    ...}

首先看下生成子进程的部分,循环调用ngx_spawn_process函数,其中第二个参数ngx_worker_process_cycle即 worker 主循环,先来看下ngx_spawn_process函数的原型

ngx_pid_t ngx_spawn_process(ngx_cycle_t *cycle,    ngx_spawn_proc_pt proc, void *data, char *name, ngx_int_t respawn);

proc表示生成的子进程需要调用的函数,data作为传入proc的参数,而respawn比较特殊,如果这个参数是非负数,则代表ngx_processes数组的下标,这个数组是个存放了ngx_process_t的数组,代表所有子进程

typedef struct {    ngx_pid_t           pid;          // 进程 id    int                 status;       // 进程退出状态    ngx_socket_t        channel[2];   // unix domain socket 对 用于进程间通信    ngx_spawn_proc_pt   proc;         // 进程执行函数    void               *data;         // 自定义数据    char               *name;         // 进程名称    unsigned            respawn:1;    // 进程需要重新启动 例如子进程意外退出时    unsigned            just_spawn:1; // 进程刚刚启动 用于 reload 时区分新旧进程    unsigned            detached:1;   // 进程为分离状态 例如平滑升级启动新的二进制文件时    unsigned            exiting:1;    // 进程正在退出    unsigned            exited:1;     // 进程已退出} ngx_process_t;

如果respawn为负数则可以取下面几个值,这些值用于设置进程的一些属性,也就是respawn位,just_spawn位和detached

#define NGX_PROCESS_NORESPAWN     -1#define NGX_PROCESS_JUST_SPAWN    -2#define NGX_PROCESS_RESPAWN       -3#define NGX_PROCESS_JUST_RESPAWN  -4#define NGX_PROCESS_DETACHED      -5

接下来看ngx_spawn_process函数,首先是开头的部分

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) {        return NGX_INVALID_PID;    }}

决定了s的值,这个值表示ngx_processes数组的下标,如果respawn为非负数,由于含义相同所以直接赋值即可,否则寻找ngx_processes数组中第一个可用的下标,代码中ngx_last_process代表目前数组的最大下标之后的位置,默认为 0

if (respawn != NGX_PROCESS_DETACHED) {    if (socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) == -1) { ... }    if (ngx_nonblocking(ngx_processes[s].channel[0]) == -1) { ... }    if (ngx_nonblocking(ngx_processes[s].channel[1]) == -1) { ... }    on = 1;    if (ioctl(ngx_processes[s].channel[0], FIOASYNC, &on) == -1) { ... }    if (fcntl(ngx_processes[s].channel[0], F_SETOWN, ngx_pid) == -1) { ... }    if (fcntl(ngx_processes[s].channel[0], F_SETFD, FD_CLOEXEC) == -1) { ... }    if (fcntl(ngx_processes[s].channel[1], F_SETFD, FD_CLOEXEC) == -1) { ... }    ngx_channel = ngx_processes[s].channel[1];} else {    ngx_processes[s].channel[0] = -1;    ngx_processes[s].channel[1] = -1;}ngx_process_slot = s;

如果进程不被指定为分离状态则创建 socket 对用于进程间通信,然后设置为非阻塞的方式,其中第二个 socket 给即将生成的子进程使用,第一个 socket 给 master 和其他子进程使用,这里对于第一个 socket 还设置了信号驱动 IO,最后设置当前操作的进程下标ngx_process_slots
接下来调用fork生成子进程

pid = fork();switch (pid) {case -1:    ngx_close_channel(ngx_processes[s].channel, cycle->log);    return NGX_INVALID_PID;case 0:    ngx_pid = ngx_getpid();    proc(cycle, data);    break;default:    break;}

可以看到,子进程首先设置了ngx_pid,然后调用传入的proc函数,例如 worker 主循环,父进程则接下去执行

ngx_processes[s].pid = pid;ngx_processes[s].exited = 0;if (respawn >= 0) {    return pid;}

这里设置了进程的pid和退出状态,如果respawn参数为非负数则到这里就返回了,那么什么时候会以非负数调用呢?有一种情况是 worker 进程意外退出,需要 master 进程重新启动时,会以此 worker 进程的下标作为respawn参数,这时新的子进程除了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;}

其中设置了子进程的其余属性,并根据respawn参数设置了三个标志位,函数最后根据情况判断是否需要递增ngx_last_process,然后返回

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

回到之前的ngx_start_worker_processes函数,看下剩余的部分

ngx_channel_t  ch;ngx_memzero(&ch, sizeof(ngx_channel_t));ch.command = NGX_CMD_OPEN_CHANNEL;for (i = 0; i < n; i++) {    ... // ngx_spawn_process    ch.pid = ngx_processes[ngx_process_slot].pid;    ch.slot = ngx_process_slot;    ch.fd = ngx_processes[ngx_process_slot].channel[0];    ngx_pass_open_channel(cycle, &ch);}

这段代码首先根据新的子进程的各项信息设置ch,然后通过调用ngx_pass_open_channel传递这些信息给其他子进程,函数有点长且个人认为不是很重要就不贴出来了,主要就是利用sendmsg系统调用,通过辅助数据传递文件描述符
那么,worker 进程的启动到这里就结束了,在回到 master 主循环之前,先来看下main函数中设置的信号处理函数

ngx_signal_t  signals[] = {    { ngx_signal_value(NGX_RECONFIGURE_SIGNAL),      "SIG" ngx_value(NGX_RECONFIGURE_SIGNAL),  // SIGHUP      "reload", ngx_signal_handler },    { ngx_signal_value(NGX_REOPEN_SIGNAL),      "SIG" ngx_value(NGX_REOPEN_SIGNAL),       // SIGUSR1      "reopen", ngx_signal_handler },    { ngx_signal_value(NGX_NOACCEPT_SIGNAL),      "SIG" ngx_value(NGX_NOACCEPT_SIGNAL),     // SIGWINCH      "", ngx_signal_handler },    { ngx_signal_value(NGX_TERMINATE_SIGNAL),      "SIG" ngx_value(NGX_TERMINATE_SIGNAL),    // SIGTERM      "stop", ngx_signal_handler },    { ngx_signal_value(NGX_SHUTDOWN_SIGNAL),      "SIG" ngx_value(NGX_SHUTDOWN_SIGNAL),     // SIGQUIT      "quit", ngx_signal_handler },    { ngx_signal_value(NGX_CHANGEBIN_SIGNAL),      "SIG" ngx_value(NGX_CHANGEBIN_SIGNAL),    // SIGUSR2      "", 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", "", SIG_IGN },    { SIGPIPE, "SIGPIPE, SIG_IGN", "", SIG_IGN },    { 0, NULL, "", NULL }};

可以看到,这些信号的处理函数除了忽略SIG_IGN外,都是ngx_signal_handler,接下来看下信号处理函数中与 master 相关的部分

switch (signo) {case ngx_signal_value(NGX_SHUTDOWN_SIGNAL):    ngx_quit = 1;    break;case ngx_signal_value(NGX_TERMINATE_SIGNAL):case SIGINT:    ngx_terminate = 1;    break;case ngx_signal_value(NGX_NOACCEPT_SIGNAL):    if (ngx_daemonized) {        ngx_noaccept = 1;    }    break;case ngx_signal_value(NGX_RECONFIGURE_SIGNAL):    ngx_reconfigure = 1;    break;case ngx_signal_value(NGX_REOPEN_SIGNAL):    ngx_reopen = 1;    break;case ngx_signal_value(NGX_CHANGEBIN_SIGNAL):    if (getppid() > 1 || ngx_new_binary > 0) {        break;    }    ngx_change_binary = 1;    break;case SIGALRM:    ngx_sigalrm = 1;    break;case SIGIO:    ngx_sigio = 1;    break;case SIGCHLD:    ngx_reap = 1;    break;}if (signo == SIGCHLD) {    ngx_process_get_status();}

可以看到,收到不同信号的时候,会设置相应的标志位,这里比较特殊的是SIGUSR2,也就是需要平滑升级时发送的信号,这里有两种情况会忽略掉这个信号

  • 向成功启动新进程的旧进程再次发送这个信号,这时ngx_new_binary的值为新进程的pid
  • 向新进程发送这个信号,而此时旧进程还没退出,这时getppid()会得到旧进程(也就是父进程)的pid,这种情况应该只有不以守护进程形式启动时才会发生,还有就是在我的 ubuntu 环境下,如果是在图形界面下杀掉旧进程,新进程会被startup进程收养,此时startuppid也是大于 1 的

紧接着是ngx_process_get_status函数,调用的条件是收到SIGCHLD信号,在 worker 进程停止或者中止时会向 master 进程发送这个信号,简单看下重要的部分

for ( ;; ) {    pid = waitpid(-1, &status, WNOHANG);    ...    for (i = 0; i < ngx_last_process; i++) {        if (ngx_processes[i].pid == pid) {            ngx_processes[i].status = status;            ngx_processes[i].exited = 1;            break;        }    }    ...    if (WEXITSTATUS(status) == 2 && ngx_processes[i].respawn) {        ngx_processes[i].respawn = 0;    }    ngx_unlock_mutexes(pid);}

循环waitpid得到子进程的pid和状态,如果有则在ngx_processes数组中找到相应的项,修改其statusexited,如果退出状态码为 2 则代表致命错误,这时将respawn设为 0,表示不重新启动 worker 进程,最后是调用ngx_unlock_mutexes,函数里调用ngx_shmtx_force_unlock强制释放了该进程锁持有的锁,这个强制释放锁的函数之前也提到过
那么回到 master 的主循环,接下来设置了几个变量的值

ngx_new_binary = 0;   // 平滑升级时新进程的 piddelay = 0;            // 接收到中止信号时 延迟的时间sigio = 0;            // 接收到中止信号时 可以接收的最大信号数量live = 1;             // 标志位 表示至少有一个子进程存活

之后的部分都位于for循环中,这是一个无限的循环

if (delay) {    if (ngx_sigalrm) {        sigio = 0;        delay *= 2;        ngx_sigalrm = 0;    }    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");    }}

循环开头这段代码一开始看得我一脸懵逼,需要看后面才能理解,主要用于强制退出时,根据delay设置定时器,每次到期则将delay翻倍,直到delay大于 1000 则发送SIGKILL信号,也就是必杀的信号
接下来会调用sigsuspend等待信号到达,然后更新时间

sigsuspend(&set);ngx_time_update();

从信号处理函数中知道,子进程中止或停止时,会设置ngx_reap位,首先处理的是这种情况

if (ngx_reap) {    ngx_reap = 0;    live = ngx_reap_children(cycle);}

ngx_reap_children函数遍历ngx_processes数组,如果遇到设置了exited的进程,则关闭其channel文件描述符对并通知其他子进程,接着根据respawn位判断是否需要重新启动子进程,此外还有一种特殊情况,就是平滑升级时新进程意外退出了,这时旧进程若没有退出也会收到信号,如果此时旧进程为noaccepting状态则设置ngx_restart重新启动 worker 进程,最后函数返回是否有进程存活
接着根据live判断是否需要退出 master 进程

if (!live && (ngx_terminate || ngx_quit)) {    ngx_master_process_exit(cycle);}

可以看到,如果live为 0,且设置了退出或中止的标记,代表进程需要退出且子进程已经全部成功退出,此时退出 master 进程即可
接着是ngx_terminate的情况,这个代表强制中止但其实还是有一段延迟,也就是与循环内开头那段代码相关

if (ngx_terminate) {    if (delay == 0) {        delay = 50;    }    if (sigio) {        sigio--;        continue;    }    sigio = ccf->worker_processes + 2 /* cache processes */;    if (delay > 1000) {        ngx_signal_worker_processes(cycle, SIGKILL);    } else {        ngx_signal_worker_processes(cycle, ngx_signal_value(NGX_TERMINATE_SIGNAL));    }    continue;}

首次收到这个信号,会设置delaysigio的值,sigio的值是 worker 进程数量,然后向 worker 进程发送中止信号,这里sigio可以理解为定时器过期前可以接收的最大信号数量,避免了每次接收到信号都调用ngx_signal_worker_processes,这里应该含有一个假设,定时器过期前,每个 worker 进程都成功退出并发送 SIGCHLD,由于标准信号是不会排队的,所以接收到的 SIGCHLD 一定小于等于sigio,但是也可能收到其他信号
每次定时器到期或sigio递减为 0 时,都会再次调用ngx_signal_worker_processes,直至delay大于 1000 则发送SIGKILL信号
接下来是ngx_signal_worker_processes,看下主体部分

for (i = 0; i < ngx_last_process; i++) {    if (ngx_processes[i].detached || ngx_processes[i].pid == -1) {        continue;    }    if (ngx_processes[i].just_spawn) {        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;        }    }    if (kill(ngx_processes[i].pid, signo) == -1) {        err = ngx_errno;        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_processes数组,有以下几种情况

  • 子进程设置了detached,无视即可,这种情况对应于平滑升级
  • 子进程设置了 just_spawn,则简单地将其设置为 0,这种情况对应于 reload
  • 子进程设置了exiting且需要发送的信号为退出信号,这时也是无视即可,如果是中止信号,依旧需要发送

若不是上面几种情况,则首先尝试使用channel通知子进程,若失败则通过kill系统调用向子进程发送信号,每次成功通知子进程且目的为退出或中止时,都将exiting设置为 1,还有一个特例就是kill返回ESRCH,表示pid对应的子进程不存在,这时需要设置ngx_reap以检查子进程
接着是ngx_quit,这种情况下进程会优雅地退出,也就是会等到 worker 进程的当前任务完成后才退出

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        }    }    cycle->listening.nelts = 0;    continue;}

接着是ngx_reconfigure也就是 reload 重新读取配置文件的情况

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

可以看到,如果平滑升级后 reload 旧的 master 进程,旧的 master 进程会重新启动 worker 进程,如果不是这种情况,则调用ngx_init_cycle,然后再重新启动 worker 进程,这时的第三个参数与之前不同,是NGX_PROCESS_JUST_RESPAWN,也就是新进程会设置just_respawn位,那么这个位有什么用呢?看到最后一行,调用ngx_signal_worker_processes向所有进程发出了退出的信号,刚刚说过这个函数,在遍历ngx_processes数组遇到设置just_respawn的进程时,简单地将其置 0,而不会真正向其发送信号,所以接收到退出信号的只有旧的 worker 进程
然后是最后四种情况,ngx_restartngx_reopenngx_noaccept的情况比较直观,就不多说了,主要是看下ngx_change_binary的情况

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;}if (ngx_reopen) {    ngx_reopen = 0;    ngx_reopen_files(cycle, ccf->user);    ngx_signal_worker_processes(cycle, ngx_signal_value(NGX_REOPEN_SIGNAL));}if (ngx_change_binary) {    ngx_change_binary = 0;    ngx_new_binary = ngx_exec_new_binary(cycle, ngx_argv);}if (ngx_noaccept) {    ngx_noaccept = 0;    ngx_noaccepting = 1;    ngx_signal_worker_processes(cycle, ngx_signal_value(NGX_SHUTDOWN_SIGNAL));}

函数ngx_exec_new_binary首先将listening数组中 socket 对应的文件描述符的值,以某种格式放入环境变量NGINX_VAR中,然后调用ngx_execute启动新进程,ngx_execute函数比较简单

ngx_pid_tngx_execute(ngx_cycle_t *cycle, ngx_exec_ctx_t *ctx) {    return ngx_spawn_process(cycle, ngx_execute_proc, ctx, ctx->name,                             NGX_PROCESS_DETACHED);}

这里以NGX_PROCESS_DETACHED的形式启动新进程,然后子进程会调用ngx_execute_proc,这个函数简单地包装了execve,执行的路径为我们之前在main函数中保存过的argv[0]

0 0
原创粉丝点击