美文网首页
Nginx源码学习——向master进程发送信号

Nginx源码学习——向master进程发送信号

作者: 丹丘生___ | 来源:发表于2018-09-13 13:00 被阅读0次

    可以用命令行控制Nginx的启动与停止、重载配置文件、回滚日志文件、平滑升级等。而通过Nginx命令行发送信号有两种方式:

    1. nginx [-s signal] 如快速地停止服务 /usr/local/nginx/sbin/nginx -s stop;-s参数告诉Nginx程序向正在运行的Nginx服务发送信号, signal是要被发送的信号。
    2. 使用kill命令,如快速地停止服务 kill -s SIGTERM <nginx master pid>

    通过学习Nginx源码,可以看到对这两种方式进行的不同处理。


    第一种方式

    当在终端键入命令 nginx [-s signal]后,main()函数从头开始执行。执行过程中调用ngx_get_option获取命令行参数

     //选项参数的解析,该函数的设计使其能够独立于操作系统平台
        if (ngx_get_options(argc, argv) != NGX_OK) {
            return 1;
        }
    

    由参数s知——要求发送信号。具体何种信号由后一个参数决定,包括:stop quit reopen reload ,它们被保存在全局变量ngx_signal中

    //ngx_get_option函数
        case 's':
              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;
    

    再次回到main函数,根据全局变量ngx_signal的值判断有无信号发送。

      //向master进程发送ngx_signal中保存的信号,然后结束本次main函数的执行。
        if (ngx_signal) {
            return ngx_signal_process(cycle, ngx_signal);
        }
    

    进入ngx_signal_process函数,该函数主要有两步行为:

    • 打开pid文件(nginx启动时生成了保存pid的文件),获取master进程的pid。
    • 调用ngx_os_signal_process函数,将信号和master的pid传递之。
    ngx_int_t
    ngx_signal_process(ngx_cycle_t *cycle, char *sig)
    {
        ......
        打开pid文件,获取文件中记录的master进程pid
        ......
        return ngx_os_signal_process(cycle, sig, pid);
    }
    

    再看ngx_os_signal_process函数

    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;
                }
    
                ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                              "kill(%P, %d) failed", pid, sig->signo);
            }
        }
        return 1;
    }
    

    signals数组记录了信号和相应的信号处理程序。ngx_os_signal_process函数遍历整个数组,根据Nginx自定义的信号名称找到信号后,使用kill(pid, signalno)函数发送信号至master进程。

    
    typedef struct {
        int     signo;//信号编号
        char   *signame;//信号的系统名称,如SIGTERM,前缀为SIG
        char   *name;    //nginx自定义的信号名称,如quit,terminate
        //函数指针,指向信号处理函数
        void  (*handler)(int signo, siginfo_t *siginfo, void *ucontext);
    } ngx_signal_t;
    
    
    //信号数组
    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 },
        ......
        ......
      }
    

    总结,通过以上代码可以看到用第一种方式发送信号的具体过程。这种方式要以main函数为入口,一步步执行,但会在某个位置判断出本番执行的目的是为了发送信号,因此获取master的pid,使用kill函数发送信号,信号发送完成后,本番执行也就返回退出main函数了,接下来就是信号处理函数和master进程的工作了。


    第二种方式

    当在终端键入kill [-s SIGNAL PID]后,会直接向nginx master进程发送信号。用户首先要知道master的进程ID,可通过ps命令查看: ps -ef | grep nginx.
    kill命令会直接触发信号处理函数的执行。


    信号处理函数

    无论是第一种方式还是第二种方式,都会触发同一个信号处理函数。介绍信号处理函数之前,先要说明信号是怎么和信号处理函数绑定的。

    前文中介绍了signals[]信号数组,它是一个全局数组。main函数执行过程中,会调用ngx_init_signals函数,通过遍历signals数组,获得信号编号和信号处理函数指针,然后通过sigaction函数绑定之。代码如下所示:

    //将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) {
                  ......
                  ...do_something();
            }
        }
    
        return NGX_OK;
    }
    

    下面看信号处理函数ngx_signal_handler代码片段。当该函数被触发后,根据信号编号,改写全局变量的值( 如ngx_quit, ngx_terminate等),当master进程被唤醒后(见下文),将根据该全局变量的值采取行动。

     switch (signo) {
    
            //SIGQUIT信号
            case ngx_signal_value(NGX_SHUTDOWN_SIGNAL):
                ngx_quit = 1;
                action = ", shutting down";
                break;
            //SIGTERM信号或SIGINIT信号
            case ngx_signal_value(NGX_TERMINATE_SIGNAL):
            case SIGINT:
                ngx_terminate = 1;
                action = ", exiting";
                break;
    
            case ngx_signal_value(NGX_NOACCEPT_SIGNAL):
                if (ngx_daemonized) {
                    ngx_noaccept = 1;
                    action = ", stop accepting connections";
                }
                break;
    
            case ngx_signal_value(NGX_RECONFIGURE_SIGNAL):
                ngx_reconfigure = 1;
                action = ", reconfiguring";
                break;
    
            case ngx_signal_value(NGX_REOPEN_SIGNAL):
                ngx_reopen = 1;
                action = ", reopening logs";
                break;
                ......
                ......
             break;
     }
    
    master的循环与唤醒

    APUE中介绍了sigsuspend函数的其中一种使用方法。
    书中288页(中文):

    sigsuspend另一种应用是等待一个信号处理程序设置一个全局变量。

    Nginx中就使用了这种方法。
    master进程循环在void ngx_master_process_cycle(ngx_cycle_t *cycle)函数中进行,循环内部调用了sigsuspend函数,由此,在捕捉到一个信号或发生了一个会终止该进程的信号之前,该进程都是处于挂起状态。如果捕捉到一个信号而且从该信号处理程序返回,则sigsuspend返回。
    前文说过,信号处理函数将更改全局变量,那么master进程中sigsuspend返回后,将逐个检测全局变量。

    for(;;){
    
      ......
      ......
      sigsuspend(&sig);
      ......
    
    }
    
    

    全局变量检测示例:
    当变量ngx_quit置为1时,向所有子进程发送QUIT通知,并关闭监听socket。

    if (ngx_quit) {
       ngx_signal_worker_processes(cycle,ngx_signal_value(NGX_SHUTDOWN_SIGNAL);
         ls = cycle->listening.elts;
         for (n = 0; n < cycle->listening.nelts; n++) {
             if (ngx_close_socket(ls[n].fd) == -1) {
                .......
             }
          }
          cycle->listening.nelts = 0;
          continue;
     }
    

    总结,Nginx执行初期,会将指定信号与信号处理程序绑定。然后无论以nginx [-s signal]方式(其本质还是调用kill函数),还是在终端上以kill命令发送信号,都会触发信号处理函数更改全局变量,且唤醒master进程检测全局变量的值,进而采取一系列行动,如终止子进程、关闭描述符等。

    相关文章

      网友评论

          本文标题:Nginx源码学习——向master进程发送信号

          本文链接:https://www.haomeiwen.com/subject/sihigftx.html