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

59 篇文章 1 订阅
54 篇文章 0 订阅

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);
#else
            ngx_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机制来实现对进程的各种控制功能,有不当之处敬请指正。

  • 14
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
nginx是一个高性能的Web服务器,同时也是一个反向代理服务器和电子邮件(IMAP/POP3)代理服务器。它最显著的特点是具有高并发处理能力和低内存占用。为了满足不同用户的需求,nginx提供了模块化的架构,可以通过开发模块来扩展其功能。 深入理解nginx模块开发,首先需要了解nginx的架构。nginx的主要部分包括master进程和worker进程。master进程负责管理worker进程,而worker进程负责处理实际的客户端请求。nginx的模块系统允许开发者向master进程或worker进程添加自定义的功能。 在nginx的模块开发,主要涉及到以下几个方面的内容: 1. 配置文件解析:nginx的配置文件是使用类似于C语言的语法进行解析的。模块开发者需要了解nginx的配置文件语法,并且能够解析和处理自定义的配置项。 2. HTTP请求处理:开发基于HTTP协议的模块时,需要能够处理和解析HTTP请求。模块可以拦截特定的URL,处理请求,并返回相应的响应。 3. 事件处理:nginx使用事件驱动的模型来处理并发请求。模块开发者需要了解事件驱动的机制,实现自己的事件处理逻辑,并与nginx的事件处理系统进行交互。 4. 内存管理:nginx以低内存占用著称,这是因为它使用了自己的内存管理机制。模块开发者需要了解nginx的内存管理方式,并遵循相应的规则。 5. 日志记录:nginx提供了灵活的日志记录功能。模块开发者可以通过定日志记录方式,将特定的信息记录到指定的日志文件。 总的来说,深入理解nginx模块开发与架构解析需要对nginx的整体架构有深入了解,并具备一定的系统编程和网络编程经验。通过开发和调试模块,可以进一步理解nginx的原理和内部实现,掌握更多高性能Web服务器开发的知识和技巧。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农心语

您的鼓励是我写作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值