深入理解nginx中的signal处理机制

ops/2024/9/25 10:32:39/

1. 引言

  在计算机系统中,信号处理是一项重要的任务,它允许操作系统和应用程序之间进行通信和协调。在网络服务器软件中,如Nginx,信号处理机制起着关键作用,它能够捕获和处理各种类型的信号,从而实现服务器的灵活控制和运行时的动态行为。

  nginx是一款高性能、轻量级的Web服务器和反向代理服务器,被广泛应用于构建可靠、高效的Web应用程序和服务。为了满足各种需求和应对不同的运行时情况,nginx提供了丰富的信号处理机制,使得管理员和开发人员能够通过发送信号来实现对服务器的管理和控制。

  信号是一种在操作系统中用于通知进程发生某种事件或请求某种操作的机制。它可以用于向进程发送中断信号、终止信号、重启信号等,以及自定义的应用程序信号。nginx利用信号处理机制,可以捕获和处理各种信号,例如重新加载配置文件、优雅地停止或重启服务器等。

  深入理解nginx中的信号处理机制需要了解信号的基本概念和操作系统对信号的支持。当nginx接收到一个信号时,它会根据信号的类型和当前的运行状态执行相应的操作。例如,当接收到重新加载配置文件的信号时,nginx会重新读取配置文件并应用新的配置,而不需要重启整个服务器

2. signal信号处理函数的注册

  在nginx的main函数中有一个函数调用,如下:

    if (ngx_init_signals(cycle->log) != NGX_OK) {return 1;}

  这个调用的作用就是向操作系统注册当前进程的signal处理函数。

  下面是ngx_init_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) {
#if (NGX_VALGRIND)ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,"sigaction(%s) failed, ignored", sig->signame);
#elsengx_log_error(NGX_LOG_EMERG, log, ngx_errno,"sigaction(%s) failed", sig->signame);return NGX_ERROR;
#endif}}return NGX_OK;
}

  在ngx_init_signals函数中,对定义的signals数组进行遍历,并将对应的signal处理函数注册到操作系统中。

  在注册一个signal信号的时候,需要分几步:

    1. 初始化一个sigaction结构体。
    1. 设置sigaction结构体中sa_sigaction或者sa_handler(二选一)至信号处理函数。对于前者,需要设置sa_flags = SA_SIGINFO。
    1. 如果不希望在处理当前signal的时候block其他信号,那么用sigemptyset清空sa_mask。
    1. 最后,通过sigaction向操作系统注册消息处理函数。

  通过上面的循环遍历,nginx注册了SIGHUP(reload)、SIGUSR1(reopen)、SIGWINCH(noaccept)、SIGTERM(stop)、SIGQUIT(quit)、SIGUSR2(change bin)、SIGARLRM(timer)、SIGINT(stop)、SIGIO()、SIGCHLD(child reap)、SIGSYS(ignore)、SIGPIPE(ignore)共12个信号。

  signals的定义如下:


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

3. 设置信号阻塞

  为了nginx在处理信号的过程中确保能够正确地处理并且避免被中断,需要对信号设置block阻塞标记,从而能够在处理指定信号的时候,避免新的信号进来打扰处理过程。

  在ngx_master_process_cycle函数(当配置开启了master_process模式时会作用master进程的主循环)的开头部分,进行了相关设置,源码如下:

    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));if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) {ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,"sigprocmask() failed");}sigemptyset(&set);

  这里对前面设置的10个信号(除了SIG_IGN)进行了设置。

4. signal信号的处理

  在nginx中,signal信号是由ngx_signal_handler函数负责接收处理的。不过,ngx_signal_handler函数对信号的处理其实就是对应接收到的信号设置相应的标记,然后立即返回。譬如收到SIGTERM信号,则设置ngx_terminate = 1,收到SIGHUP信号,则设置ngx_reconfigure等等。其自己本身不进行实际的信号处理。
  signal信号的处理逻辑是在主循环中进行的。如果是master/worker多进程运行模式下,在ngx_master_process_cycle函数中处理,如果是单进程运行模式下,则是在ngx_single_process_cycle函数中进行处理。在主循环函数中,它会检查ngx_signal_handler中设置的标记位,然后根据各个标记位进行对应的处理。

  譬如在ngx_master_process_cycle函数中对配置重加载信号的处理逻辑如下:

	if (ngx_reconfigure) {ngx_reconfigure = 0;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;}ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reconfiguring");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);ngx_start_worker_processes(cycle, ccf->worker_processes,NGX_PROCESS_JUST_RESPAWN);ngx_start_cache_manager_processes(cycle, 1);/* allow new processes to start */ngx_msleep(100);live = 1;ngx_signal_worker_processes(cycle,ngx_signal_value(NGX_SHUTDOWN_SIGNAL));}

 &esmp;首先它判断是否ngx_reconfigure被设置为1了,如果没有设置,那么不执行配置重加载的操作。
  接着,如果是正在更新二进制文件操作,即ngx_new_binary=1,那么需要在这里启动新的worker进程和cache manager进程。
  再下来是调用ngx_init_cycle重新加载配置文件。
  加载新的worker进程,最后通知老的worker进程进行优雅退出。

5. 跨进程发送signal

  在nginx运行的过程中,如果我们需要让当前的nginx能够重新加载配置文件,我们可以在命令行输入以下命令:

nginx -s reload

  又或者,如果我们希望停止nginx运行,我们可以在命令行输入一下命令:

nginx -s stop

  因为我们在命令行输入以上命令的时候,其实shell又重新启动了一个新的nginx进程,那新的nginx进程是如何通知正在提供服务的nginx进程执行相应的动作的呢?

  这里就涉及到跨进程信号发送的操作了。
&esmp; 新启动的进程根据命令行参数,会读取正在提供服务的nginx进程的pid文件,得到它的master进程的pid,然后调用系统函数kill来向master进程,这样子master进程就会收到对应的信号,然后master主循环函数就会进行信号的处理。

  在main函数中,我们可以看到下面的代码:

    if (ngx_signal) {return ngx_signal_process(cycle, ngx_signal);}

  意思就是向nginx进程发送指定的信号。再看ngx_signal_process函数的实现:

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];ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "signal process started");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;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;}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);}if (n == NGX_ERROR) {return 1;}while (n-- && (buf[n] == CR || buf[n] == LF)) { /* void */ }pid = ngx_atoi(buf, ++n);if (pid == (ngx_pid_t) NGX_ERROR) {ngx_log_error(NGX_LOG_ERR, cycle->log, 0,"invalid PID number \"%*s\" in \"%s\"",n, buf, file.name.data);return 1;}return ngx_os_signal_process(cycle, sig, pid);}

  非常好理解,就是读取pid文件,然后调用ngx_os_signal_process函数对pid发送signal。由于linux/unix和windows的signal机制是不一样的,所以ngx_os_signal_process函数针对两类操作系统nginx进行了单独实现,这里不再赘述。

6. 总结

  以上通过对nginx的源码分析,从signal信号的注册和阻塞状态设置,到signal信号的处理,最后到跨进程singla信号的发送进行了详细的介绍,我们可以从中一窥nginx如何利用操作系统的signal机制来实现对进程的各种控制功能,有不当之处敬请指正。


http://www.ppmy.cn/ops/39054.html

相关文章

eclipse创建web项目

前言:我是第一次写web项目,探索了很多天,今天就把我知道的分享给大家,希望大家能够少走弯路,早点写出属于自己的web项目。完成课程设计或毕业设计。 一.准备工作 首先,在这里推荐一个网站--菜鸟教程。这个…

(AI Web、ChatGPT Native、Ai Loading、AI Tools、知豆AI)

目录 1、AI Web 2、ChatGPT Native 3、Ai Loading 4、AI Tools 5、知豆AI 1、AI Web

SinoDB数据库导入导出工具External table

External table又叫SinoDB外部表,外部表采用多线程机制,支持多线程读取、写入数据文件以及多线程数据转换、插入操作。多线程机制只需要消耗相对较少的系统资源,但是能提供高速数据导入、导出,可以应用在数据采集、表重建、数据库…

智慧互联,统信UOS V20桌面专业版(1070)解锁办公新模式丨年度更新

从小屏到大屏 突破,就在方寸之间 从人机到智脑 融合,旨在新质生产力 统信UOS一直致力于将先进科技与用户场景相结合,不断提升用户的工作效率和生产力。在最新发布的统信UOS V20桌面专业版(1070)版本中,我们…

【ARMv8/v9 系统寄存器 4 -- ARMv8 通用寄存器详细介绍】

文章目录 ARMv8 通用寄存器通用寄存器X30 寄存器和链接寄存器(LR)程序计数器(PC)ARMv8 X30和PC之间的关系小结 ARMv8 通用寄存器 在ARMv9架构中(这也适用于ARMv8,因为ARMv9是其进化版本)&#…

【HMWeb】HTML使用Leaflet实现本地离线地图Gis应用

下载Leaflet 官网下载&#xff1a;https://leafletjs.com/reference.html CSDN&#xff1a;https://download.csdn.net/download/hmxm6/89291989 选择版本号 添加html文件 加入代码 <!DOCTYPE html> <html lang"en"> <head><meta charset&qu…

vue3 JSX的使用与警告【JSX 元素隐式具有类型 “any“,因为不存在接口 “JSX.IntrinsicElements“】解决办法

一、安装 pnpm i vitejs/plugin-vue-jsx -D 二、配置 1、tsconfig.json "compilerOptions":{"jsx":"preserve" } 2、vite.config.ts import VueJsx from "vitejs/plugin-vue-jsx"...plugin:[vue(),VueJsx() ] 三、简单使用案例…

HarmonyOS开发案例:【卡片二级联动】

1 卡片介绍 使用ArkTS语言&#xff0c;实现一个导航与内容二级联动的效果。 2 标题 二级联动&#xff08;ArkTS&#xff09; 3 介绍 介绍了如何基于List组件实现一个导航和内容的二级联动效果。样例主要包含以下功能&#xff1a; 切换左侧导航&#xff0c;右侧滚动到对应…