nginx master工作循环

微笑、不失礼 提交于 2019-12-24 06:49:11

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

        默认情况下,nginx使用的是master-worker工作模式接收命令行指令和处理客户端请求。在nginx启动master进程后,其会进行整个nginx工作环境的初始化,然后会依次启动worker进程、cache manager和cache loader进程,接着会进入一个工作循环中,以等待命令行发送来的指令,从而实现对nginx的管理工作。本文主要讲解nginx是如何进行信号的初始化和处理的。

1. 信号处理方法

        在介绍nginx是如何组织信号之前,我们首先需要理解的是对信号进行管理的几个主要的方法:

方法 参数 作用
sigaction(int signo, conststruct *act, struct sigaction * oldact); signo指定了当前需要监听的信号类型;act指定了处理当前信号的方法;oldact将接收原先设置的处理信号的方法; 对于signo指定的信号,为其设置接收到该信号时的处理方法
sigemptyset(sigset_t *set) set中存储了当前进程正在监听的信号集 将指定信号集中的信号清空
sigaddset(sigset_t *set, int signo) set为目标信号集;signo为将要添加的信号 将指定信号添加到目标信号集中
sigprocmask(int how, const sigset_t *set, sigset_t *oldset) how可以传入三种值:SIG_BLOCK、SIG_SETMASK、SIG_UNBLOCK。这三个值的作用分别为:这三个参数的作用主要如下:SIG_BLOCK:会将信号屏蔽集中的信号合并到当前进程已经屏蔽的信号集中;SIG_SETMASK:会将信号屏蔽集中的信号替代当前进程已经屏蔽的信号;SIG_UNBLOCK:会将当前进程屏蔽的信号集中删除传入的信号集中的信号。set指定了将要添加到当前进程的信号屏蔽集。oldset会存储当前进程原始的信号屏蔽集。 这个方法是非常关键的一个方法,该方法的主要作用在于将set指定的信号集按照how所指定的方式添加到目标信号屏蔽字集合中,oldset将会返回当前进程原先的信号屏蔽字集合。需要注意的是,通过该方法添加的信号集是会被屏蔽的,也即当前进程收到这些信号也不会进行处理
sigsuspend(const sigset_t *set) set指定了将要进行屏蔽的信号集 当前进程有一个信号屏蔽集,而该方法的作用是将set所指定的信号屏蔽集替换当前进程的信号屏蔽集,也即即使收到set中所指定的信号也还是继续挂起当前进程,只有收到其他的信号时,才会唤醒当前进程处理信号

        通过上面几个方法的介绍,我们其实就可以通过组织这几个方法来处理用户发出的信号,然后进行相应的流程的处理。比如首先通过sigaction()方法为需要监听的信号添加回调方法,然后通过sigsuspend()方法监听相应的信号,而sigemptyset()sigaddset()则主要用于对信号集进行管理,sigprocmask()方法则用于控制何时需要对目标信号进行屏蔽。本质上,nginx就是通过组合使用这些方法来不断监听用户发送来的信号指令,然后进行处理的。

2. 信号处理流程

        本质上,nginx就是通过在一个无限循环中不断监听用户命令行发送来的指令进行处理,然后进行下一次等待的。下面的流程图展示了nginx是如何处理当前的信号集的:

  • 设置信号回调方法:nginx定义了一个全局的ngx_signal_t数组,该数组中定义了各个信号的signo以及对应的回调方法。在nginx启动过程中,其会调用ngx_init_signals()方法将这些信号及其回调方法设置到当前进程中;
  • 屏蔽信号:这里屏蔽信号的目的主要是当前nginx还未启动完成,因而需要对目标信号进行屏蔽;
  • 启动worker、cache进程:启动各个worker进程、cache manager和cache loader进程,这一部分的原理前面的文章已经进行了讲解;
  • 监听信号:通过sigsuspend()方法监听目标信号;
  • 设置状态标志位:这一部分主要是在各个信号的回调方法中进行的,这些回调方法只是将对应的状态标志位进行了设置,而未进行其他的处理;
  • 根据状态标志位处理信号:在收到命令行信号之后,信号回调方法会设置对应的状态标志位,而这一步主要是在master循环中,通过检查这些标志位是否为1,然后进行相应的处理。

3. 源码讲解

3.1 初始化信号回调方法

        nginx定义了一个全局的信号集,以及各个信号对应的回调方法,其定义如下:

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",
      "",
      SIG_IGN
      },
    {
      SIGPIPE,
      "SIGPIPE, SIG_IGN",
      "",
      SIG_IGN
      },
    {
      0, 
      NULL,
      "",
      NULL
    }
};

        上面的信号的定义中,第一个参数定义了信号的编号,第二个参数定义了信号的名称,第三个参数则定义了当前信号结构体的标识,第四个参数定义了信号的回调方法。这里需要特别强调的是,上面各个信号的定义中,大部分信号的回调方法都是通过ngx_signal_handler()方法进行处理的。这里我们首先看一下nginx是如何初始化信号的:

ngx_int_t ngx_init_signals(ngx_log_t *log) {
  ngx_signal_t *sig;
  struct sigaction sa;

  for (sig = signals; sig->signo != 0; sig++) {
    // 为sa结构体申请内存空间,并且将其值都置为0
    ngx_memzero(&sa, sizeof(struct sigaction));
    // 设置当前信号的回调方法为sig->handler指定的方法
    sa.sa_handler = sig->handler;
    sigemptyset(&sa.sa_mask);
    // 这里的sigaction()函数的作用主要是设置信号的处理函数,这里的信号是由signals变量定义的
    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;
}

        这里的ngx_init_signals()方法是在nginx启动的main()方法中进行调用的,也就是说,在nginx刚开始启动的时候就已经为各个信号的回调方法。而且,这里的初始化过程主要是遍历signals数组(也就是前面的数组),然后通过sigaction()方法设置各个信号的回调方法。

3.2 信号回调

        下面我们来看一下处理各个信号的回调方法:ngx_signal_handler()

/**
 * 这里主要是根据传入的信号编号设置相应的标志位,在对应的进程循环中将会检查各个标志位,从而进行相应的处理
 *
 * @param signo 信号编号
 */
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) {
    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;

        case ngx_signal_value(NGX_CHANGEBIN_SIGNAL):
          if (getppid() > 1 || ngx_new_binary > 0) {
            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;

          // 这里的SIGCHILD信号表示当前进程的子进程意外退出了
        case SIGCHLD:
          ngx_reap = 1;
          break;
      }

      break;

    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;

        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;

        case ngx_signal_value(NGX_RECONFIGURE_SIGNAL):
        case ngx_signal_value(NGX_CHANGEBIN_SIGNAL):
        case SIGIO:
          action = ", ignoring";
          break;
      }

      break;
  }

  // 这里如果当前的信号是子进程退出了,那么就会调用ngx_process_get_status()方法,在该方法中,
  // 会获取意外退出的子进程的pid和状态,然后更新当前进程中保存的ngx_processes数组中对应的进程的结构体数据
  if (signo == SIGCHLD) {
    ngx_process_get_status();
  }

  ngx_set_errno(err);
}

        在ngx_signal_handler()方法中,其主要是根据信号的编号,将对应的标志位置为1。这里有一点需要说明的是,对于SIGCHLD信号,其表示某个子进程意外退出了,在收到该信号之后,其会调用ngx_process_get_status()方法来获取到这个意外退出的子进程的pid,并且会更新ngx_processes数组中该退出的子进程的结构体的状态。如下是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 (;;) {
    // 这里waitpid()方法主要是获取指定子进程的状态,第一个参数可以传子进程id,如果传-1,则表示当前进程的
    // 所有子进程。status将会存储指定进程的状态,WNOHANG表示进行状态检查之后,不作任何等待,直接返回。
    // 由于第一个参数传了-1,那么这里的调用的作用就是检查所有的子进程,判断是否有意外退出的子进程,如果有,
    // 则获取其状态和进程id
    pid = waitpid(-1, &status, WNOHANG);

    // 如果返回的pid为0,则表明没有任何子进程退出
    if (pid == 0) {
      return;
    }

    // 如果pid为-1,则表示当前进程获取子进程状态发生了异常
    if (pid == -1) {
      err = ngx_errno;

      if (err == NGX_EINTR) {
        continue;
      }

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

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

    // 由于是子进程退出,因而当前master进程中记录的该进程的状态还处于正常状态,
    // 这里主要是遍历ngx_processes数组,找到这个已经退出的子进程,然后将其标志位进行处理
    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;
      }
    }

    // 这里判断status是否为2其实是检查进程退出是是否执行的是类似于exit(2)这样的语句,当以这样的方式
    // 退出时,表示当前进程是异常退出的,比如无法申请到内存、无法设置进程状态等等不可恢复的异常,
    // 此时就会将进程的respawn属性标记为0,因为再次创建新进程也无法成功
    if (WEXITSTATUS(status) == 2 && ngx_processes[i].respawn) {
      ngx_processes[i].respawn = 0;
    }

    ngx_unlock_mutexes(pid);
  }
}

3.3 master循环

        通过ngx_signal_handler()回调方法,在接收到相应的信号之后,就会设置对应的状态位,而这些状态位的处理主要是在master进程的循环中进行的,本质其实就是通过不断的检查这些状态位来进行相应逻辑的处理。master循环主要是在ngx_master_process_cycle()方法中进行的,如下是该方法的源码:

/**
 * 进入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));

  // 这里主要是对上面添加的信号进行忽略,也即即使收到这些信号,nginx也不会处理这些信号。
  // 因为当前正在启动nginx,因而需要对上述的信号进行屏蔽。
  // 这里需要注意的是,sigprocmask()方法的第一个参数可以传如三种类型的值:SIG_BLOCK、
  // SIG_SETMASK、SIG_UNBLOCK。
  // 这三个参数的作用主要如下:
  // SIG_BLOCK:会将信号集中的信号合并到当前进程已经监听的信号集中进行监听;
  // SIG_SETMASK:会将信号集中的信号替代当前进程已经监听的信号,以进行监听;
  // SIG_UNBLOCK:会将当前进程监听的信号集中删除传入的信号集中的信号。
  // 因而这里的主要作用是对上面添加的信号集进行监听
  if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) {
    ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                  "sigprocmask() failed");
  }

  // 重置信号集,这里重置之后,后面就可以通过set集合重新监听上面忽略的信号集了
  sigemptyset(&set);

  // 这里的master_process是一个字符串,实际上就是给master进程起的一个名字
  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);
  }

  // 将master名称和各个参数的值都复制到title中
  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);
  }

  // 为进程设置title
  ngx_setproctitle(title);

  // 获取核心模块的配置
  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);
  // 启动cache进程
  ngx_start_cache_manager_processes(cycle, 0);

  ngx_new_binary = 0;
  delay = 0;
  sigio = 0;
  live = 1;

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

    // 由于前面已经对set进行了清空,因而这里重新监听这些信号,以对这些信号进行处理,这里需要注意的是,
    // 如果没有收到任何信号,主进程就会被挂起在这个位置。
    // 关于master进程处理信号的流程,这里需要说明的是,在nginx.c的main()方法中,会调用
    // ngx_init_signals()方法将全局变量signals中定义的信号及其会调用方法设置到当前进程的信号集中。
    // 而signals中定义的信号的回调方法都是ngx_signal_handler()方法,
    // 该方法在接收到对应的信号之后,会设置对应的标志位,也即下面多个if判断中的参数,
    // 通过这种方式来触发相应的逻辑的执行。
    sigsuspend(&set);

    // 更新当前进程或缓存的时间数据
    ngx_time_update();

    // ngx_reap表示当前有子进程意外结束,此时需要通过ngx_reap_children()监控所有的子进程
    if (ngx_reap) {
      ngx_reap = 0;
      ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "reap children");

      // 这里的操作主要分为三种情况:
      // 1. 关闭创建的每一个进程;
      // 2. 如果某个进程标记为了respawn,也即重新生成,那么就会重新创建一个进程以替代正在关闭的进程;
      // 3. 如果当前是平滑升级,则将旧的pid文件替换为新的pid文件;
      // 需要注意的是,这里的live表示当前是否还有进程是处于存活状态的
      live = ngx_reap_children(cycle);
    }

    // 如果当前nginx已经退出了,则退出master进程,这里的live标志了只要有任意一个子进程没退出时就会为1
    if (!live && (ngx_terminate || ngx_quit)) {
      ngx_master_process_exit(cycle);
    }

    // 这里会向所有的子进程发送SIGKILL或者NGX_TERMINATE_SIGNAL信号,以关闭所有子进程,
    // 只有在所有子进程都关闭了,才会进入上面的ngx_master_process_exit()方法退出master进程。
    // ngx_terminate表示当前需要强制关闭nginx,无论是否有正在处理的连接
    if (ngx_terminate) {
      if (delay == 0) {
        delay = 50;
      }

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

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

      if (delay > 1000) {
        // 这里主要是向各个进程发送SIGKILL命令
        ngx_signal_worker_processes(cycle, SIGKILL);
      } else {
        // 这里主要是向各个进程发送NGX_TERMINATE_SIGNAL命令
        ngx_signal_worker_processes(cycle, ngx_signal_value(NGX_TERMINATE_SIGNAL));
      }

      continue;
    }

    // ngx_quit表示当前需要优雅关闭nginx,此时就会向各个子进程发送NGX_SHUTDOWN_SIGNAL信号,
    // 并且关闭当前进程所监听的各个句柄。
    // 需要注意的是,父进程和子进程是可以同时监听同一个端口的,其实现原理就在于,子进程会自动继承
    // 父进程所监听的句柄。因而在退出时,父进程和子进程都需要关闭其所监听的句柄
    if (ngx_quit) {
      // 这里主要是向各个进程发送NGX_SHUTDOWN_SIGNAL命令
      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;
    }

    // ngx_reconfigure表示需要重新读取配置文件,并且需要服务对新配置文件生效
    if (ngx_reconfigure) {
      ngx_reconfigure = 0;

      // 如果当前是平滑升级,则重新创建worker进程和cache相关进程
      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对象
      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);
      // 然后重新生成worker和cache进程
      ngx_start_worker_processes(cycle, ccf->worker_processes, NGX_PROCESS_JUST_RESPAWN);
      ngx_start_cache_manager_processes(cycle, 1);

      ngx_msleep(100);

      live = 1;
      // 这里调用ngx_signal_worker_processes()方法的目的是关闭旧的worker进程。
      // 在上面调用ngx_start_worker_processes()方法的时候,传入的第三个参数是
      // NGX_PROCESS_JUST_RESPAWN,此时,会将重新生成的子进程的just_spawn标志位置为1,
      // 而旧的进程的just_spawn则是0,在当前方法中,会检查当前遍历到的进程的just_spawn
      // 标志位如果为1,则不进行处理,否则就会发送shutdown命令。通过这种方式就关闭了旧的进程
      ngx_signal_worker_processes(cycle, ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
    }

    // 重启worker进程和cache manager进程
    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_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));
    }

    // ngx_change_binary标记了当前正在进行nginx的平滑升级
    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);
    }

    // ngx_noaccept标志位的作用是,通知所有的子进程不要再接收任何新的连接
    if (ngx_noaccept) {
      ngx_noaccept = 0;
      ngx_noaccepting = 1;
      ngx_signal_worker_processes(cycle, ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
    }
  }
}

        上面的流程中,nginx首先初始化了一个信号集,接着通过sigprocmask()方法的调用对该信号集中的信号进行屏蔽,也即不处理这些信号。然后通过sigemptyset()方法清空了该set属性中的信号。接下来主要是通过ngx_start_worker_processes()方法和ngx_start_cache_manager_processes()方法启动worker进程和cache相关进程。最后就是进入了一个无限for循环,而在这个循环中,首先通过sigsuspend(&set)监听各个信号,需要注意的是,这里的set属性已经在前面的sigemptyset()调用中对其进行了清空,因而这里sigsuspend()调用就是重新开始监听这里的各个信号。如果没有任何信号发生,那么当前进程就会被阻塞在这里,如果发生了目标信号,那么就会回调前面讲解的ngx_signal_handler()方法,从而设置各个标志位的值,而这里的for循环的下一步就是检查这些标志位的值是否不为0,如果不为0,则进行相应的逻辑的处理。

4. 小结

        本文首先对信号处理的各个方法工作方式进行了讲解。然后使用图例的方式讲解了nginx处理各个信号的流程。接着结合源码对信号的初始化, 信号的回调,以及信号处理循环的逻辑进行了详细讲解。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!