NGINX 进程通信机制
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];
- }
在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进程初始化的时候加入
- 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)。
NGINX 进程通信机制相关推荐
- linux 进程通信机制,LINUX内核进程高效通信机制研究
摘要:进程间的通信是多任务.多用户操作系统必须考虑的重大问题,Linux继承了Unix的基本设计思想,其安全性和稳定性得到了人们的认可,但随着其应用领域的不断拓展,其通信机制已经不能满足用户的需求.本 ...
- PHP-FPM 与 Nginx 的通信机制总结
PHP-FPM 介绍 CGI 协议与 FastCGI 协议 每种动态语言( PHP,Python 等)的代码文件需要通过对应的解析器才能被服务器识别,而 CGI 协议就是用来使解释器与服务器可以互相通 ...
- linux 进程通信机制,linux的进程通信机制小结
linux向应用软件提供下列进程间通信手段: ####第一类通信方式:只能用于父进程与子进程之间,或者两个兄递进程之间. >管道Pipe >信号Signal >跟踪Trace 管道: ...
- Android跨进程通信Binder机制与AIDL实例
文章目录 进程通信 1.1 进程空间划分 1.2 跨进程通信IPC 1.3 Linux跨进程通信 1.4 Android进程通信 Binder跨进程通信 2.1 Binder简介 2.2 Binder ...
- linux 进程通信比较,浅析Linux进程通信的几种方式及其比较
摘要:本文通过对Linux下几种主要通信方式进行概述,并结合Linux系统的系统调用对OS中的实现进行简要概述,并对其优缺点进行分析,阐述了在不同通信情况下应该选择何种通信方式进行选择. 关键词:Li ...
- Linux 内核、进程调度、进程通信、多线程、协程
Linux内核 操作系统是什么 内核是什么 从功能层面上来说,内核就是一个中间层,软件和硬件之间交互的中间层,链接层 从其他方面理解内核 系统调用,开放了很多接口:资源管理 内核实现的策略 宏内核 微 ...
- 【朝花夕拾】Android跨进程通信总结篇
前言 原文:https://www.cnblogs.com/andy-songwei/p/10256379.html 只要是面试高级工程师岗位,Android跨进程通信就是最受面试官青睐的知识点之一. ...
- 【朝花夕拾】Android性能篇之(七)Android跨进程通信篇
前言 转载请声明,转自[https://www.cnblogs.com/andy-songwei/p/10256379.html],谢谢! 只要是面试高级工程师岗位,Android跨进程通信就是最受面 ...
- 【朝花夕拾】Android性能篇之(七)Android跨进程通信篇...
前言 原文:https://www.cnblogs.com/andy-songwei/p/10256379.html 只要是面试高级工程师岗位,Android跨进程通信就是最受面试官青睐的知识点之一. ...
最新文章
- 安装Hadoop系列 — 导入Hadoop源码项目
- jsp程序带mysql数据库_【web开发】:JSP连接Mysql数据库(非常详细,代码一看就懂)...
- C#获取控制台句柄的方法
- 更改c语言程序保存地址吗,C语言文本文件实现局部修改
- wifi android系统耗电,魅族mx4 pro耗电严重是什么原因?异常费电元凶居然是WiFi
- 前端开发中通过js设置cookie的一组方法
- 恢复html的初始选定状态,Adobe Photoshop
- vuex commit 传参数,传递多个参数
- Linux网络配置的基本方法
- html table 转置,jQuery+CSS实现的table表格行列转置功能示例
- Python 基础 —— docstring
- 人工神经网络评价法案例_人工神经网络评价法.
- 阙值,阈值,阀值,傻傻分不清
- 怎么把png转换jpeg?png如何转其他格式?
- 利用python绘制自定义棋盘格
- 共模电感(扼流圈) 差模电感(扼流圈)
- 计【思考】如不解决这些问题,山东招远这类事情以后仍然会不断发生!!
- 友推SDK微信分享问题
- 如何在AWS上购买云主机
- 如何下载MySQL各个版本
热门文章
- 理解 LINUX 的处理器负载均值(翻译)
- 牛客 - 丁姐姐喜欢Fibonacci(找规律+思维)
- HDU - 4757 Tree(LCA+可持久化trie树)
- 蓝桥杯 - 完美的代价(贪心+模拟)
- mfc如何将一个数组中的字节数据用串口发送出去_[翻译] 串口通信的帧(frame)...
- Python基础语法-三种函数特殊形参定义以及使用方式
- Delphi常用关键字用法详解
- STL 之reverse,reverse_copy,rotate,rotate_copy
- Safari 14.0 的功臣 Webp?
- 聊聊风口上的 eBPF