NGINX 进程通信机制

本文详细探讨了NGINX进程通信的三种主要方式:Linux信号、Socket通信和共享内存。通过信号来实现系统与NGINX的交互,如重启、关闭等操作;master与worker进程间的通信借助于Socket通信,特别是使用频道机制;worker进程间则通过共享内存进行高效沟通,包括处理连接数统计和互斥锁机制。文章还提到了惊群问题的解决策略以及信号处理的详细流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文地址:http://blog.youkuaiyun.com/spch2008/article/details/38945033


nginx的进程通信分为三种类别:linux 系统与nginx 通信, master 进程与worker进程通信, worker进程间通信。

master进程管理worker进程,本文将追溯nginx 的退出过程。


Linux信号

linux 系统与nginx是通过信号才进行通信的,通过信号控制nginx重启、关闭以及加载配置文件等。


信号发送流程

1. 发送信号

    ./nginx –s quit  向master进程发送信号

   这里有一点是:执行 ./nginx –s quit 实际上是新开了一个master进程,只不过它半路夭折了,即向原master发送信号后,

   它就死掉啦。它存在的意义就是向原master发送信号。

2. 获取参数

    nginx 通过 -s 知道用户要给nginx 发送信号,会有相应的动作。 

     ngx_get_options 
     case 's':
     if (*p) {
         ngx_signal = (char *) p;

     } 

     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;
     }

3. 获取pid

    要发送信号,需要知道master进程的pid,那如何获得呢?nginx 启动的时候将其写入了本地文件中。

ngx_int_t
ngx_signal_process(ngx_cycle_t *cycle, char *sig)
{
    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);

    file.name = ccf->pid;
    file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY,
                            NGX_FILE_OPEN, NGX_FILE_DEFAULT_ACCESS);

    n = ngx_read_file(&file, buf, NGX_INT64_LEN + 2, 0);
    pid = ngx_atoi(buf, ++n);

    return ngx_os_signal_process(cycle, sig, pid);
}

file.name即为配置文件中指定pid所在的文件,该文件存放master的pid。通过配置文件中的pid字段,指明存放进程id文件的地址。


4. 发送信号  
 ngx_int_t
ngx_os_signal_process(ngx_cycle_t *cycle, char *name, ngx_int_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;
              }
          }
      }

      return 1;
}

根据name到ngx_signal_t的名字中去匹配,找到信号,然后通过kill向master发送消息。

信号注册流程

1. 信号定义

typedef struct {
int     signo;
        char   *signame;
        char   *name;
        void  (*handler)(int signo);
} ngx_signal_t;

简单解释一下,signo为信号的数字表示,而signame为信号的名字,name为nginx定义的信号名,handler为回调函数。比如:

{ ngx_signal_value(NGX_SHUTDOWN_SIGNAL),
  "SIG" ngx_value(NGX_SHUTDOWN_SIGNAL),
  "quit",
   ngx_signal_handler }

转换后为:{ SIGQUIT,  "SIGQUIT", "quit",  ngx_signal_handler }, 具体语法规则见附录。然后SIGQUIT会借助于预处理器进行数字的对应,

SIGQUIT为在signal.h中的宏定义。

 

2. 信号注册

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));
        sa.sa_handler = sig->handler;
        sigemptyset(&sa.sa_mask);
        if (sigaction(sig->signo, &sa, NULL) == -1) {
            return NGX_ERROR;
        }
    }
    return NGX_OK;
}

signals 为ngx_signal_t 数组,内部存放所有nginx会处理的信号。上述中,通过sigaction注册信号。

 

3. 信号处理

void
ngx_signal_handler(int signo)
{
    switch (ngx_process) 
    case NGX_PROCESS_MASTER:
        switch (signo) 
        case ngx_signal_value(NGX_SHUTDOWN_SIGNAL):
            ngx_quit = 1;
}

设置全局变量ngx_quit 标志为1.


4. 响应信号

void
ngx_master_process_cycle(ngx_cycle_t *cycle)
{
    sigemptyset(&set);
    sigaddset(&set, ngx_signal_value(NGX_SHUTDOWN_SIGNAL));

    sigemptyset(&set);  

    for ( ;; ) {
        sigsuspend(&set);
   
        if (ngx_quit) {
            ngx_signal_worker_processes(cycle,
                                        ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
            continue;
        } 
    }
}
sigsuspend将阻塞进程,直到set中的信号到达为止。当有信号达到,调用handler函数,然后ngx_quit被设置为1,

此时,通过ngx_signal_worker_processes 向工作进程传递信号。

Socket通信

master进程与worker进程通过sockpair进行通信。在nginx中,将这种通信定义为频道,channel。

master就是通过频道与worker进程通信滴。

1. 频道定义

typedef struct {
     ngx_uint_t  command;
     ngx_pid_t   pid;
     ngx_int_t   slot;
     ngx_fd_t    fd;
} ngx_channel_t;

2. 注册频道

其一:创建频道,其实就是socketpair,在启动worker进程的时候创建频道

ngx_pid_t
ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data,
    char *name, ngx_int_t respawn)
{
    socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) == -1);
    ngx_nonblocking(ngx_processes[s].channel[0]) == -1);
    ngx_nonblocking(ngx_processes[s].channel[1]) == -1);
           
    ngx_channel = ngx_processes[s].channel[1];
}
设置非阻塞,同时,将ngx_channel赋值。


其二:加入epoll等待数据到来,在worker进程初始化的时候加入

static void
ngx_worker_process_init(ngx_cycle_t *cycle, ngx_uint_t priority)
{
    if (ngx_add_channel_event(cycle, ngx_channel, NGX_READ_EVENT,
                              ngx_channel_handler)
        == NGX_ERROR)
    {
        exit(2);
    }
}

ngx_channel_handler为回调函数。

 

3. 发送消息

static void
ngx_signal_worker_processes(ngx_cycle_t *cycle, int signo)
{
    ngx_channel_t  ch;

    switch (signo) {
    case ngx_signal_value(NGX_SHUTDOWN_SIGNAL):
        ch.command = NGX_CMD_QUIT;
        break;
    }

    for (i = 0; i < ngx_last_process; i++)
            ngx_write_channel(ngx_processes[i].channel[0],
                                  &ch, sizeof(ngx_channel_t), cycle->log)
     
}
master进程通过ngx_signal_woker_processes向worker进程发送消息。

4. 响应消息

static void
ngx_channel_handler(ngx_event_t *ev)
{
    c = ev->data;
    for ( ;; ) {

        n = ngx_read_channel(c->fd, &ch, sizeof(ngx_channel_t), ev->log);
        switch (ch.command) {
        case NGX_CMD_QUIT:
            ngx_quit = 1;
            break;
        }
    }
}

worker进程通过ngx_read_channel读取消息,然后根据command判断是什么消息,同时设置worker进程的ngx_quit变量。


5. woker进程受理

static void
ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
{
    ngx_uint_t         i;
    ngx_connection_t  *c;

    for ( ;; ) {
        ngx_process_events_and_timers(cycle);
        if (ngx_quit) {
            ngx_quit = 0;

            if (!ngx_exiting) {
                ngx_close_listening_sockets(cycle);
                ngx_exiting = 1;
            }
        }

worker内部ngx_worker_process_cycle为一个循环,处理事件,当检测到退出标志后,做相应处理


共享内存

worker进程间则是通过比较快速的共享内存进行通信。

1. mmap 匿名

  即不与文件关联,不需要创建文件删除文件,简单高效。但有可能有些系统不支持,所以若不支持,采用第二种方案。

 

2.mmap /dev/zero

  与字符设备/dev/zero关联,相当于打开一个文件,但mac os不支持,所以采用第三种方式。

  fd = open(“/dev/zero”, O_RDWR);

  mmap(NULL, sizeof(int), PROT_READ |PROT_WRITE, MAP_SHARED, fd, 0).

 

3.shmget 古老的system v获取

  繁琐,但所有系统都支持


由master进程创建共享变量,worker进程共享。nginx 解决惊群问题,是通过设置互斥锁,

只有拥有互斥锁的工作进程才能担负与客户建立连接的任务,这个互斥锁就放于共享内存中。

另外,ngin统计连接数,这个全局变量也放于共享内存中,多个工作进程可以去改写这个变量,

当然,需要一些互斥机制。


共享内存一个小例子:

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>

int
main(int argc, char *argv[])
{
    int *num;
    pid_t pid;

    
    num = (int*) mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE,
                      MAP_SHARED | MAP_ANONYMOUS, 0, 0);
    
    if ((pid = fork()) < 0) {
        perror("fork error");
        exit(0);
    } else if (pid == 0) {
        *num += 5;
        exit(0);
    }
    
    waitpid(pid, NULL, 0);
    printf("father get data: %d\n", *num);
     
    return 0;
}

附录

1. #转换为字符

  在define宏定义中,#代表将参数转换成字符串, 即“ ”.

  #definevalue(n)  #n

 char *str = value(12345);  è str = “12345”

 

2. ##连接成一个token

  在define宏定义中,两个##表示将参数连接起来构成一个标示符

 #define value(n)   val##n

  intval1 = 3;

 printf(“%d\n”, value(1));   è val1

 

3. 字符串连接

   //C语言中,“ ”表示空串   不准确

  char *str = “123”   “456”;

   等效

  char *str = “123456”;

   解析: 相当123”   “456 红色是一对,空串省略


4. 为什么worker 与master采用socketpair进行通信

     worker进程有一个循环,在不停的处理请求,怎样接收master发送的数据呢。而socketpair为一种套接字,将它加入epoll中,通过事件模块获取该套接字,进行处理。ngx_write_channel将数据写入channel。而在ngx_process_cycle.c->ngx_worker_process_init初始化的时候,已经调用ngx_add_channel_event将套接字写入epoll中,后续向套接字写数据,epoll能检测到,并调用注册的回调函数ngx_channel_handler,将相应标志位设1(ngx_quit)。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值