在生产环境中,nginx 一般采用一个主进程和多个工作进程的模式,其中主进程不需要处理网络事件,不负责业务的执行,而只是对工作进程进行管理,以实现重启服务、平滑升级、替换日志文件等功能, 和配置文件实时生效,同时使用工作进程提供服务,如静态文件服务、反向**等功能。
那么nginx是如何实现master重启服务的,平滑升级,日志文件替换,配置文件实时生效呢?此外,主进程如何实时通知工作进程重启服务、平滑升级、替换日志文件、可配置文件生效?答案是信号。 下面我们来看一下nginx是如何结合**做到上述的。
因为nginx使用信号来实现平滑升级、日志文件替换、配置文件实时效果、重启服务等功能,所以nginx启动过程中使用的信号会注册到操作系统内核中,其**实现如下:
int ngx_cdeclngx init signals() 的实现方式如下:main(int argc, char *const *ar**)
初始化信号*
if (ngx_init_signals(cycle->log) != ngx_ok)
ngx_int_t从 ngx init signals() 函数的实现中,我们可以看到 nginx 通过调用 sigaction() 将支持的信号注册到操作系统内核中,当内核捕获到对应的信号时,会调用 ** 函数进行信号处理。 从**中可以看出,其实nginx支持的所有信号对应的处理程序都是一样的,即ngx信号hanlder()。 在这个函数中,nginx 中的信号会根据锁的信号设置一个对应的全局变量,然后在主进程的处理循环中根据这个全局变量执行相应的动作,后面会描述。ngx_init_signals(ngx_log_t *log)
ngx_signal_t *sig;
struct sigaction sa;Linux 内核使用的信号。
遍历 signals 数组,将所有 nginx 支持的信号*注册到内核
for (sig = signals; sig->signo != 0; sig++)
return ngx_ok;
一般来说,当 nginx 进程(包括 master 进程和 worker 进程)已经在环境中运行时,所谓的平滑升级、日志文件或配置文件的替换实时生效等管理功能。 那么nginx是如何从命令行控制这些特性的呢?nginx 的做法是启动一个新的 nginx 进程,将相应的控制信号发送到环境中已经存在的主进程,以便现有的服务可以执行相应的动作。 那么,新的 nginx 进程如何向环境中已有的主进程发出信号呢?其**实现如下:
int ngx_cdecl从实现中可以看出,新的 nginx 进程在执行后返回退出,因为这个新启动的进程是用来发送信号的。 那么,新进程如何向现有主进程发出信号呢?答案是通过终止系统调用。 一般来说,有两个步骤:第一个是获取存储在nginx中的正在运行的master进程pid 文件中的 pid,即 ngx 信号 process() 函数的作用,实现如下:main(int argc, char *const *ar**)
"nginx -s xxx"*/
if (ngx_signal)
ngx_int_t其次,在获取到正在运行的 master 进程的 pid 后,调用 kill 命令将新的 nginx 进程携带的信号发送给正在运行的 master 进程,这也是 ngx os 信号 process() 函数的功能,实现如下: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];
获取核心模块中存储的配置项的结构指针*
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;CCF->PID 是 nginxPID 文件。
file.log = cycle->log;
以可读的方式打开 nginx。PID 文件。
file.fd = ngx_open_file(file.name.data, ngx_file_rdonly,ngx_file_open, ngx_file_default_access);
读文件 * n = ngx 读文件(&file, buf, ngx int64 len + 2, 0);
if (ngx_close_file(file.fd) == ngx_file_error)
if (n == ngx_error)
删除结束控制字符*
while (n-- buf[n] == cr ||buf[n] == lf))
将字符串转换为数字以获取主进程 pid*
pid = ngx_atoi(buf, +n);
封装终止系统调用的信令功能*
return ngx_os_signal_process(cycle, sig, pid);
ngx_int_t至此,新的nginx进程的任务完成,然后返回退出,那么接下来运行中的master进程会发生什么呢?这涉及到主进程的工作循环。 我们知道 master 进程并不向外界提供服务,而是专门用来管理 worker 进程的,那么在 nginx 中是如何实现的呢?ngx_os_signal_process(ngx_cycle_t *cycle, char *name, ngx_pid_t pid)
ngx_signal_t *sig;
遍历 nginx 内核支持的信号,找到与名称相同的信号,并通过 kill 系统。
调用向主进程发送信号。
for (sig = signals; sig->signo != 0; sig++)
ngx_log_error(ngx_log_alert, cycle->log, ngx_errno,"kill(%p, %d) failed", pid, sig->signo);
return 1;
如前所述,nginx 通过启动一个新进程向主进程发送信号,因此主进程正在等待信号到达工作循环。 信号到达后,会触发信号处理功能,然后检测信号的相应标志位置,然后在主进程中检测相应的标志进行相应的处理。 在讨论主进程如何处理特定信号之前,让我们先看看主进程对哪些信号感兴趣,如下所示:
上面**中的chld信号不是由新的nginx进程发送的,但是操作系统内核在检测到子进程正在退出时,会向父进程(即master)发送chld信号,然后master进程会对此进行进一步分析,这就是ngx reap children()的功能。 在 ngx master process cycle() 中,我们可以看到 master 首先将自己感兴趣的信号添加到阻塞自身的信号集合中(通过 sig 块调用 sigprocmask(),然后在这些操作后调用 sigsuspend() 暂停自己,等待信号集中的信号发生,唤醒自己, 然后在信号发生后根据全局变量(见上表)进行相应的处理,处理流程图如下:
从主进程的工作循环中,我们可以看到,当主进程接收到相应的信号并完成自己的处理过程时,会通过ngx signal worker processes()向工作进程发送相应的信号。 例如,在收到退出信号后,主站会向所有 worker 子进程发送退出信号,通知 worker 优雅退出(所谓优雅,其实就是处理现有连接,不接受新连接),并为监听器关闭套接字句柄。 那么,工作进程会对哪些信号感兴趣,当它从主进程接收到相应的信号时会发生什么呢?这就是工人工作周期的意义所在。 在 worker 进程中,在接收到 master 发送的信号后,我们也会看到 worker 对哪些信号感兴趣,并在引入 worker 工作周期之前设置相应的全局变量,具体如下:
除了上述三个信号之外,在工作进程的工作循环中还可以看到另一个全局变量 NGX 激励。 只有一个地方会设置此标志,即在收到退出信号后。 NGX Quit 只会首次将 NGX Exciting 设置为 1。 为什么?因为当 worker 收到退出信号时,它知道它需要优雅地关闭进程,即完成对现有连接的处理并不再接受新连接,NGX Exciting 表示退出状态,即仍有尚未处理的连接。 以下是工作进程的工作原理:
这里只是对主进程和工作进程的信号处理过程的简要说明,对于详细的处理过程,如平滑升级、配置文件的实时效果等,你还是需要阅读**才能更好地梳理细节。