使用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 -abc for (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函数里会判断这个标记。
// 给主进程发送信号,则直接处理信号就行,不是启动nginx if (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。最后完成重启。具体的逻辑比较细,后续再分析。
本文分享自微信公众号 - 编程杂技(theanarkh),作者:theanarkh
原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。
原始发表时间:2020-03-15
本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。
我来说两句