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 发送信号,会有相应的动作。

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

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. file.name = ccf->pid;
  6. file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY,
  7. NGX_FILE_OPEN, NGX_FILE_DEFAULT_ACCESS);
  8. n = ngx_read_file(&file, buf, NGX_INT64_LEN + 2, 0);
  9. pid = ngx_atoi(buf, ++n);
  10. return ngx_os_signal_process(cycle, sig, pid);
  11. }

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. for (sig = signals; sig->signo != 0; sig++) {
  6. if (ngx_strcmp(name, sig->name) == 0) {
  7. if (kill(pid, sig->signo) != -1) {
  8. return 0;
  9. }
  10. }
  11. }
  12. return 1;
  13. }

根据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. for (sig = signals; sig->signo != 0; sig++) {
  7. ngx_memzero(&sa, sizeof(struct sigaction));
  8. sa.sa_handler = sig->handler;
  9. sigemptyset(&sa.sa_mask);
  10. if (sigaction(sig->signo, &sa, NULL) == -1) {
  11. return NGX_ERROR;
  12. }
  13. }
  14. return NGX_OK;
  15. }

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. sigemptyset(&set);
  7. for ( ;; ) {
  8. sigsuspend(&set);
  9. if (ngx_quit) {
  10. ngx_signal_worker_processes(cycle,
  11. ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
  12. continue;
  13. }
  14. }
  15. }

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. ngx_channel = ngx_processes[s].channel[1];
  9. }

在linux下,使用socketpair函数能够创建一对套节字进行进程间通信(IPC)。

函数原形:
#include <sys/types.h>
#include <sys/socket.h>

int socketpair(int domain, int type, int protocol, int sv[2]);

参数1(domain):表示协议族,在Linux下只能为AF_LOCAL或者AF_UNIX。(自从Linux 2.6.27后也支持SOCK_NONBLOCK和SOCK_CLOEXEC)
参数2(type):表示协议,可以是SOCK_STREAM或者SOCK_DGRAM。SOCK_STREAM是基于TCP的,而SOCK_DGRAM是基于UDP的
参数3(protocol):表示类型,只能为0
参数4(sv[2]):套节字柄对,该两个句柄作用相同,均能进行读写双向操作
返回结果: 0为创建成功,-1为创建失败,并且errno来表明特定的错误号,具体错误号如下所述:

EAFNOSUPPORT:本机上不支持指定的address。

EFAULT: 地址sv无法指向有效的进程地址空间内。

EMFILE: 已经达到了系统限制文件描述符,或者该进程使用过量的描述符。

EOPNOTSUPP:指定的协议不支持创建套接字对。

EPROTONOSUPPORT:本机不支持指定的协议。

注意:

1、该函数只能用于UNIX域(LINUX)下。
2、只能用于有亲缘关系的进程(或线程)间通信。
3、所创建的套节字对作用是一样的,均能够可读可写(而管道PIPE只能进行单向读或写)。
4、在读的时候,管道内必须有内容,否则将会阻塞;简而言之,该函数是阻塞的。

设置非阻塞,同时,将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. switch (signo) {
  6. case ngx_signal_value(NGX_SHUTDOWN_SIGNAL):
  7. ch.command = NGX_CMD_QUIT;
  8. break;
  9. }
  10. for (i = 0; i < ngx_last_process; i++)
  11. ngx_write_channel(ngx_processes[i].channel[0],
  12. &ch, sizeof(ngx_channel_t), cycle->log)
  13. }

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. n = ngx_read_channel(c->fd, &ch, sizeof(ngx_channel_t), ev->log);
  7. switch (ch.command) {
  8. case NGX_CMD_QUIT:
  9. ngx_quit = 1;
  10. break;
  11. }
  12. }
  13. }

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. for ( ;; ) {
  7. ngx_process_events_and_timers(cycle);
  8. if (ngx_quit) {
  9. ngx_quit = 0;
  10. if (!ngx_exiting) {
  11. ngx_close_listening_sockets(cycle);
  12. ngx_exiting = 1;
  13. }
  14. }

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. int
  7. main(int argc, char *argv[])
  8. {
  9. int *num;
  10. pid_t pid;
  11. num = (int*) mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE,
  12. MAP_SHARED | MAP_ANONYMOUS, 0, 0);
  13. if ((pid = fork()) < 0) {
  14. perror("fork error");
  15. exit(0);
  16. else if (pid == 0) {
  17. *num += 5;
  18. exit(0);
  19. }
  20. waitpid(pid, NULL, 0);
  21. printf("father get data: %d\n", *num);
  22. return 0;
  23. }

附录

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

NGINX 进程通信机制相关推荐

  1. linux 进程通信机制,LINUX内核进程高效通信机制研究

    摘要:进程间的通信是多任务.多用户操作系统必须考虑的重大问题,Linux继承了Unix的基本设计思想,其安全性和稳定性得到了人们的认可,但随着其应用领域的不断拓展,其通信机制已经不能满足用户的需求.本 ...

  2. PHP-FPM 与 Nginx 的通信机制总结

    PHP-FPM 介绍 CGI 协议与 FastCGI 协议 每种动态语言( PHP,Python 等)的代码文件需要通过对应的解析器才能被服务器识别,而 CGI 协议就是用来使解释器与服务器可以互相通 ...

  3. linux 进程通信机制,linux的进程通信机制小结

    linux向应用软件提供下列进程间通信手段: ####第一类通信方式:只能用于父进程与子进程之间,或者两个兄递进程之间. >管道Pipe >信号Signal >跟踪Trace 管道: ...

  4. Android跨进程通信Binder机制与AIDL实例

    文章目录 进程通信 1.1 进程空间划分 1.2 跨进程通信IPC 1.3 Linux跨进程通信 1.4 Android进程通信 Binder跨进程通信 2.1 Binder简介 2.2 Binder ...

  5. linux 进程通信比较,浅析Linux进程通信的几种方式及其比较

    摘要:本文通过对Linux下几种主要通信方式进行概述,并结合Linux系统的系统调用对OS中的实现进行简要概述,并对其优缺点进行分析,阐述了在不同通信情况下应该选择何种通信方式进行选择. 关键词:Li ...

  6. Linux 内核、进程调度、进程通信、多线程、协程

    Linux内核 操作系统是什么 内核是什么 从功能层面上来说,内核就是一个中间层,软件和硬件之间交互的中间层,链接层 从其他方面理解内核 系统调用,开放了很多接口:资源管理 内核实现的策略 宏内核 微 ...

  7. 【朝花夕拾】Android跨进程通信总结篇

    前言 原文:https://www.cnblogs.com/andy-songwei/p/10256379.html 只要是面试高级工程师岗位,Android跨进程通信就是最受面试官青睐的知识点之一. ...

  8. 【朝花夕拾】Android性能篇之(七)Android跨进程通信篇

    前言 转载请声明,转自[https://www.cnblogs.com/andy-songwei/p/10256379.html],谢谢! 只要是面试高级工程师岗位,Android跨进程通信就是最受面 ...

  9. 【朝花夕拾】Android性能篇之(七)Android跨进程通信篇...

    前言 原文:https://www.cnblogs.com/andy-songwei/p/10256379.html 只要是面试高级工程师岗位,Android跨进程通信就是最受面试官青睐的知识点之一. ...

最新文章

  1. 安装Hadoop系列 — 导入Hadoop源码项目
  2. jsp程序带mysql数据库_【web开发】:JSP连接Mysql数据库(非常详细,代码一看就懂)...
  3. C#获取控制台句柄的方法
  4. 更改c语言程序保存地址吗,C语言文本文件实现局部修改
  5. wifi android系统耗电,魅族mx4 pro耗电严重是什么原因?异常费电元凶居然是WiFi
  6. 前端开发中通过js设置cookie的一组方法
  7. 恢复html的初始选定状态,Adobe Photoshop
  8. vuex commit 传参数,传递多个参数
  9. Linux网络配置的基本方法
  10. html table 转置,jQuery+CSS实现的table表格行列转置功能示例
  11. Python 基础 —— docstring
  12. 人工神经网络评价法案例_人工神经网络评价法.
  13. 阙值,阈值,阀值,傻傻分不清
  14. 怎么把png转换jpeg?png如何转其他格式?
  15. 利用python绘制自定义棋盘格
  16. 共模电感(扼流圈) 差模电感(扼流圈)
  17. 计【思考】如不解决这些问题,山东招远这类事情以后仍然会不断发生!!
  18. 友推SDK微信分享问题
  19. 如何在AWS上购买云主机
  20. 如何下载MySQL各个版本

热门文章

  1. 理解 LINUX 的处理器负载均值(翻译)
  2. 牛客 - 丁姐姐喜欢Fibonacci(找规律+思维)
  3. HDU - 4757 Tree(LCA+可持久化trie树)
  4. 蓝桥杯 - 完美的代价(贪心+模拟)
  5. mfc如何将一个数组中的字节数据用串口发送出去_[翻译] 串口通信的帧(frame)...
  6. Python基础语法-三种函数特殊形参定义以及使用方式
  7. Delphi常用关键字用法详解
  8. STL 之reverse,reverse_copy,rotate,rotate_copy
  9. Safari 14.0 的功臣 Webp?
  10. 聊聊风口上的 eBPF