使用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。最后完成重启。具体的逻辑比较细,后续再分析。