NGINX 进程通信机制

来源:互联网 发布:黄乙玲心事谁人知 编辑:程序博客网 时间:2024/06/04 17:57

本文地址:http://blog.csdn.net/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 发送信号,会有相应的动作。 


  1. ngx_get_options   
  2. case 's':  
  3. if (*p) {  
  4.     ngx_signal = (char *) p;  
  5.   
  6. }   
  7.   
  8. if (ngx_strcmp(ngx_signal, "stop") == 0  
  9.     || ngx_strcmp(ngx_signal, "quit") == 0  
  10.     || ngx_strcmp(ngx_signal, "reopen") == 0  
  11.     || ngx_strcmp(ngx_signal, "reload") == 0)  
  12. {  
  13.     ngx_process = NGX_PROCESS_SIGNALLER;  
  14.     goto next;  
  15. }  

3. 获取pid

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

[cpp] view plain copy
 print?
  1. ngx_int_t  
  2. ngx_signal_process(ngx_cycle_t *cycle, char *sig)  
  3. {  
  4.     ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);  
  5.   
  6.     file.name = ccf->pid;  
  7.     file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY,  
  8.                             NGX_FILE_OPEN, NGX_FILE_DEFAULT_ACCESS);  
  9.   
  10.     n = ngx_read_file(&file, buf, NGX_INT64_LEN + 2, 0);  
  11.     pid = ngx_atoi(buf, ++n);  
  12.   
  13.     return ngx_os_signal_process(cycle, sig, pid);  
  14. }  

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


4. 发送信号  
[cpp] view plain copy
 print?
  1.  ngx_int_t  
  2. ngx_os_signal_process(ngx_cycle_t *cycle, char *name, ngx_int_t pid)  
  3. {  
  4.       ngx_signal_t  *sig;  
  5.   
  6.       for (sig = signals; sig->signo != 0; sig++) {  
  7.           if (ngx_strcmp(name, sig->name) == 0) {  
  8.               if (kill(pid, sig->signo) != -1) {  
  9.                   return 0;  
  10.               }  
  11.           }  
  12.       }  
  13.   
  14.       return 1;  
  15. }  

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

信号注册流程

1. 信号定义

[cpp] view plain copy
 print?
  1. typedef struct {  
  2. int     signo;  
  3.         char   *signame;  
  4.         char   *name;  
  5.         void  (*handler)(int signo);  
  6. } ngx_signal_t;  

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

[cpp] view plain copy
 print?
  1. { ngx_signal_value(NGX_SHUTDOWN_SIGNAL),  
  2.   "SIG" ngx_value(NGX_SHUTDOWN_SIGNAL),  
  3.   "quit",  
  4.    ngx_signal_handler }  

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

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

 

2. 信号注册

[cpp] view plain copy
 print?
  1. ngx_int_t  
  2. ngx_init_signals(ngx_log_t *log)  
  3. {  
  4.     ngx_signal_t      *sig;  
  5.     struct sigaction   sa;  
  6.   
  7.     for (sig = signals; sig->signo != 0; sig++) {  
  8.         ngx_memzero(&sa, sizeof(struct sigaction));  
  9.         sa.sa_handler = sig->handler;  
  10.         sigemptyset(&sa.sa_mask);  
  11.         if (sigaction(sig->signo, &sa, NULL) == -1) {  
  12.             return NGX_ERROR;  
  13.         }  
  14.     }  
  15.     return NGX_OK;  
  16. }  

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

 

3. 信号处理

[cpp] view plain copy
 print?
  1. void  
  2. ngx_signal_handler(int signo)  
  3. {  
  4.     switch (ngx_process)   
  5.     case NGX_PROCESS_MASTER:  
  6.         switch (signo)   
  7.         case ngx_signal_value(NGX_SHUTDOWN_SIGNAL):  
  8.             ngx_quit = 1;  
  9. }  

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


4. 响应信号

[cpp] view plain copy
 print?
  1. void  
  2. ngx_master_process_cycle(ngx_cycle_t *cycle)  
  3. {  
  4.     sigemptyset(&set);  
  5.     sigaddset(&set, ngx_signal_value(NGX_SHUTDOWN_SIGNAL));  
  6.   
  7.     sigemptyset(&set);    
  8.   
  9.     for ( ;; ) {  
  10.         sigsuspend(&set);  
  11.      
  12.         if (ngx_quit) {  
  13.             ngx_signal_worker_processes(cycle,  
  14.                                         ngx_signal_value(NGX_SHUTDOWN_SIGNAL));  
  15.             continue;  
  16.         }   
  17.     }  
  18. }  
sigsuspend将阻塞进程,直到set中的信号到达为止。当有信号达到,调用handler函数,然后ngx_quit被设置为1,

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

Socket通信

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

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

1. 频道定义

[cpp] view plain copy
 print?
  1. typedef struct {  
  2.      ngx_uint_t  command;  
  3.      ngx_pid_t   pid;  
  4.      ngx_int_t   slot;  
  5.      ngx_fd_t    fd;  
  6. } ngx_channel_t;  

2. 注册频道

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

[cpp] view plain copy
 print?
  1. ngx_pid_t  
  2. ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data,  
  3.     char *name, ngx_int_t respawn)  
  4. {  
  5.     socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) == -1);  
  6.     ngx_nonblocking(ngx_processes[s].channel[0]) == -1);  
  7.     ngx_nonblocking(ngx_processes[s].channel[1]) == -1);  
  8.              
  9.     ngx_channel = ngx_processes[s].channel[1];  
  10. }  
设置非阻塞,同时,将ngx_channel赋值。


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

[cpp] view plain copy
 print?
  1. static void  
  2. ngx_worker_process_init(ngx_cycle_t *cycle, ngx_uint_t priority)  
  3. {  
  4.     if (ngx_add_channel_event(cycle, ngx_channel, NGX_READ_EVENT,  
  5.                               ngx_channel_handler)  
  6.         == NGX_ERROR)  
  7.     {  
  8.         exit(2);  
  9.     }  
  10. }  

ngx_channel_handler为回调函数。

 

3. 发送消息

[cpp] view plain copy
 print?
  1. static void  
  2. ngx_signal_worker_processes(ngx_cycle_t *cycle, int signo)  
  3. {  
  4.     ngx_channel_t  ch;  
  5.   
  6.     switch (signo) {  
  7.     case ngx_signal_value(NGX_SHUTDOWN_SIGNAL):  
  8.         ch.command = NGX_CMD_QUIT;  
  9.         break;  
  10.     }  
  11.   
  12.     for (i = 0; i < ngx_last_process; i++)  
  13.             ngx_write_channel(ngx_processes[i].channel[0],  
  14.                                   &ch, sizeof(ngx_channel_t), cycle->log)  
  15.        
  16. }  
master进程通过ngx_signal_woker_processes向worker进程发送消息。

4. 响应消息

[cpp] view plain copy
 print?
  1. static void  
  2. ngx_channel_handler(ngx_event_t *ev)  
  3. {  
  4.     c = ev->data;  
  5.     for ( ;; ) {  
  6.   
  7.         n = ngx_read_channel(c->fd, &ch, sizeof(ngx_channel_t), ev->log);  
  8.         switch (ch.command) {  
  9.         case NGX_CMD_QUIT:  
  10.             ngx_quit = 1;  
  11.             break;  
  12.         }  
  13.     }  
  14. }  

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


5. woker进程受理

[cpp] view plain copy
 print?
  1. static void  
  2. ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)  
  3. {  
  4.     ngx_uint_t         i;  
  5.     ngx_connection_t  *c;  
  6.   
  7.     for ( ;; ) {  
  8.         ngx_process_events_and_timers(cycle);  
  9.         if (ngx_quit) {  
  10.             ngx_quit = 0;  
  11.   
  12.             if (!ngx_exiting) {  
  13.                 ngx_close_listening_sockets(cycle);  
  14.                 ngx_exiting = 1;  
  15.             }  
  16.         }  

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统计连接数,这个全局变量也放于共享内存中,多个工作进程可以去改写这个变量,

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


共享内存一个小例子:

[cpp] view plain copy
 print?
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <sys/mman.h>  
  4. #include <unistd.h>  
  5. #include <fcntl.h>  
  6.   
  7. int  
  8. main(int argc, char *argv[])  
  9. {  
  10.     int *num;  
  11.     pid_t pid;  
  12.   
  13.       
  14.     num = (int*) mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE,  
  15.                       MAP_SHARED | MAP_ANONYMOUS, 0, 0);  
  16.       
  17.     if ((pid = fork()) < 0) {  
  18.         perror("fork error");  
  19.         exit(0);  
  20.     } else if (pid == 0) {  
  21.         *num += 5;  
  22.         exit(0);  
  23.     }  
  24.       
  25.     waitpid(pid, NULL, 0);  
  26.     printf("father get data: %d\n", *num);  
  27.        
  28.     return 0;  
  29. }  

附录

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)。

0 0