佛山 移动宽带 限制网站深圳seo推广外包
使用nginx的时候,我们经常会使用nginx -s reload命令重启。下面我们就分析一下,执行这个命令的时候,nginx里发生了什么?我们从nginx的main函数开始。在main函数里,执行ngx_get_options函数命令行的初始化工作。
我们只看reload相关的逻辑。
// 解析命令行参数
static ngx_int_t
ngx_get_options(int argc, char *const *argv)
{u_char *p;ngx_int_t i;// 遍历每个参数./nginx -abcfor (i = 1; i < argc; i++) {p = (u_char *) argv[i];// 命令行参数要以-开头if (*p++ != '-') {ngx_log_stderr(0, "invalid option: \"%s\"", argv[i]);return NGX_ERROR;}while (*p) {switch (*p++) {// ...// 给主进程发送一个信号case 's':// 紧跟随后(./nginx -sxxx),否则相隔了一个空格(./nginx -s xxx)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;}}next:continue;}return NGX_OK;
}
从上面的代码中我们知道,执行nginx -s reload的时候,nginx会设置ngx_signal 变量的值为reload。然后nginx在main函数里会判断这个标记。
// 给主进程发送信号,则直接处理信号就行,不是启动nginxif (ngx_signal) {return ngx_signal_process(cycle, ngx_signal);}
ngx_int_t
ngx_signal_process(ngx_cycle_t *cycle, char *sig)
{ssize_t n;ngx_pid_t pid;ngx_file_t file;ngx_core_conf_t *ccf;u_char buf[NGX_INT64_LEN + 2];// 从配置文件中拿到保存了主进程进程id的文件路径ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);ngx_memzero(&file, sizeof(ngx_file_t));file.name = ccf->pid;file.log = cycle->log;// 打开保存了主进程进程id的文件file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY,NGX_FILE_OPEN, NGX_FILE_DEFAULT_ACCESS);if (file.fd == NGX_INVALID_FILE) {ngx_log_error(NGX_LOG_ERR, cycle->log, ngx_errno,ngx_open_file_n " \"%s\" failed", file.name.data);return 1;}// 把进程id读进来n = ngx_read_file(&file, buf, NGX_INT64_LEN + 2, 0);if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,ngx_close_file_n " \"%s\" failed", file.name.data);}while (n-- && (buf[n] == CR || buf[n] == LF)) { /* void */ }// 解析字符串为int型pid = ngx_atoi(buf, ++n);// 处理return ngx_os_signal_process(cycle, sig, pid);}
这时候nginx拿到了主进程进程id。
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;}}}return 1;
}
nginx会从signals变量中找到reload信号的配置。
{ ngx_signal_value(NGX_RECONFIGURE_SIGNAL),"SIG" ngx_value(NGX_RECONFIGURE_SIGNAL),"reload",ngx_signal_handler },
然后给主进程发送SIGNGX_RECONFIGURE_SIGNAL(linux的HUP)信号。就完成了使命。我们来看看主进程关于信号处理这块都做了些什么。nginx在启动的时候会注册信号处理函数。
ngx_signal_t signals[] = {{ ngx_signal_value(NGX_RECONFIGURE_SIGNAL),"SIG" ngx_value(NGX_RECONFIGURE_SIGNAL),"reload",ngx_signal_handler },...
}
// 根据signals变量定义的信号信息,注册到系统
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) {}}return NGX_OK;
}
所以我们知道reload的时候,主进程收到信号后,会执行ngx_signal_handler函数。该函数有这么一段代码。
case ngx_signal_value(NGX_RECONFIGURE_SIGNAL):ngx_reconfigure = 1;action = ", reconfiguring";break;
主要是设置了ngx_reconfigure = 1;我们知道,nginx启动后,主进程是在死循环里监控worker进程的工作和处理信号的。所以我们看一下死循环里面的代码。
if (ngx_reconfigure) {ngx_reconfigure = 0;cycle = ngx_init_cycle(cycle);ngx_cycle = cycle;ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx,ngx_core_module);// 重新fork work进程和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;// 杀死旧的worker进程ngx_signal_worker_processes(cycle,ngx_signal_value(NGX_SHUTDOWN_SIGNAL));}
主进程首先启动新的一批worker然后杀死旧的worker。最后完成重启。具体的逻辑比较细,后续再分析。