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

可以用命令行控制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进程检测全局变量的值,进而采取一系列行动,如终止子进程、关闭描述符等。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 224,896评论 6 522
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,283评论 3 402
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 172,085评论 0 367
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 61,010评论 1 300
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 70,015评论 6 400
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,492评论 1 314
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,858评论 3 428
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,829评论 0 279
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,374评论 1 324
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,409评论 3 346
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,527评论 1 355
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,131评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,858评论 3 339
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,296评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,434评论 1 276
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,087评论 3 381
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,597评论 2 366

推荐阅读更多精彩内容