Nginx学习——Nginx进程间的通信
nginx进程间的通信
进程间消息传递
共享内存
共享内存还是Linux下提供的最主要的进程间通信方式,它通过mmap和shmget系统调用在内存中创建了一块连续的线性地址空间,而通过munmap或者shmdt系统调用可以释放这块内存。使用共享内存的优点是当多个进程使用同一块共享内存时,在不论什么一个进程改动了共享内存中的内容后,其它进程通过訪问这段共享内存都可以得到改动后的内容。
Nginx定义了ngx_shm_t结构体。用于描写叙述一块共享内存,
typedef struct{
//指向共享内存的事实上地址
u_char* addr;
//共享内存的长度
size_t size;
//这块共享内存的名称
ngx_str_t name;
//记录日志的ngx_log_t对象
ngx_lot_t* log;
//表示共享内存是否已经分配过的标志位。为1时表示已经存在
ngx_uint_t exists;
} ngx_shm_t;
操作ngx_shm_t结构体的方法有两个:ngx_shm_alloc(基于mmap实现)用于分配新的共享内存。而ngx_shm_free(基于munmap实现)用于释放已经存在的共享内存。
Nginx各进程间共享数据的主要方式就是使用共享内存。通常是由master进程创建。在master进程fork出子进程后,全部的进程開始使用这块内存中的数据。
Nginx频道
ngx_channel_t频道是Nginx master进程与worker进程之间通信的经常使用工具。它是使用本机套接字实现的。socketpair方法,用于创建父子进程间使用的套接字。
int socketpair ( int d, int type, int protocol, int sv[2] );
通常在父子进程之间通信前。会先调用socketpair创建一组套接字,在调用fork方法创建出子进程后。将会在父进程中关闭sv[1]套接字,子进程关闭sv[0]套接字。
ngx_channel_t频道结构体是Nginx定义的master父进程和worker子进程间通信的消息格式。
例如以下所看到的:
typedef struct{
//传递的TCP消息中的命令
ngx_uint_t command;
//进程ID。通常是发送命令方的进程ID
ngx_pid_t pid;
//表示发送命令方在ngx_processes进程数组间的序号
ngx_int_t slot;
//通信的套接字句柄
ngx_fd_t fd;
} ngx_channel_t;
这个消息的格式之所以如此简单。是由于Nginx仅用这个频道同步master进程与work进程间的状态。这针对command成员已经定义的命令就能够快拿出来,例如以下所看到的:
//打开频道,使用频道这样的方式通信前必须发送的命令
#define NGX_CMD_OPEN_CHANNEL 1
//关闭已经打开的频道,实际上也就是关闭套接字
#define NGX_CMD_CLOSE_CHANNEL 2
//要求接收方正常地退出进程
#define NGX_CMD_QUIT 3
//要求接收方强制结束进程
#define NGX_CMD_TERMINATE 4
//要求接收方又一次打开进程已经打开过的文件
#define NGX_CMD_REOPEN 5
master进程正是通过socketpair产生的套接字发送命令的,即每次要派生一个进程之前都会调用socketpair方法。在Nginx派生子进程的ngx_spawn_proces方法中,会首先派生基于TCP的套接字。
Nginx封装了4个方法: ngx_write_channel,ngx_write_channel, ngx_write_channel和ngx_close_channel。
用于发送消息的ngx_write_channel方法。
ngx_int_t ngx_write_channel(ngx_socket_t s, ngx_channel_t* ch, size_t size, ngx_log_t*log);
这里的s參数是要使用的TCP套接字。ch參数是ngx_channel_t类型的消息,size參数是ngx_channel_t结构体的大小,log參数是日志对象。
读取消息的方法ngx_read_channel
ngx_int_t ngx_read_channel(ngx_socket_t s, ngx_channel_t* ch, size_t size, ngx_log_t* log);
worker进程使用ngx_add_channel_event方法把接受频道消息的套接字加入到epoll中,当接收到父进程消息时子进程会通过epoll的事件回调对应的handler方法来处理这个频道消息。
ngx_int_t ngx_add_channel_event(ngx_cycle_t* cycle, ngx_fd_t fd, ngx_int_t event,ngx_event_handler_pt handler);
cycle參数是每一个nginx进程必须具备的ngx_cycle_t核心结构体;fd參数是上面说过的须要接受消息的套接字。
event參数是须要检測的事件类型。这里必定是EPOLLIN;handler參数指向的方法就是用于读取消息的方法。
void ngx_close_channel(ngx_fd_t* fd, ngx_lot_t* log);
參数fd就是上面说过的套接字数组。
信号
Nginx定义了一个ngx_signal_t结构体用于描写叙述接收到信号的行为:
typedef struct{
//须要处理的信号
int signo;
//信号相应的字符串名称
char* siname;
//这个信号相应着的Nginx命令
char* name;
//收到signo信号后就会回调handler方法
void (*handler)(int signo);
} ngx_signal_t;
还定义了一个数组signals用来定义进程将会处理的全部信号,比如:
ngx_signal_t signals[] = {
{
ngx_signal_value(NGX_RECOFIGURE_SIGNAL),
“SIG” ngx_value(NGX_RECONFIGURE_SIGNAL),
“reload”,
ngx_signal_handler
},
…
}
在定义了signals数组后。ngx_init_signals方法会初始化signals数组中全部的信号,ngx_init_signals事实上是调用了sigaction方法注冊信号的回调方法。
ngx_int_t ngx_init_signals(nx_log_t* log)
{
ngx_signal_t* sig;
struct signaction sa;
//遍历signals数组。处理每个ngx_signal_t类型的结构体
for(sig = signals; sig->signo != 0; sig++){
ngx_memzero(&sa, sizeof(struct, sigaction));
//设置信号的处理方法为handler方法
sa.sa_handler = sig->handler;
//将sa中的为所有设置为0
sigemptyset(&sa.sa_mask);
//注冊信号的回调方法
if(sigaction(sig->signo, &sa, NULL) == -1){
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
“sigaction(%s) failed”, sig->signame);
return NGX_ERROR;
}
}
return NGX_OK;
}
这样进程就能够处理信号了。
对信号设置并生是在fork()函数调用之前进行的,所以工作金曾等都能受此作用。当然,普通情况下,我们不会向工作进程等子进程发送控制信息。而主要想监控进程父进程发送,父进程收到信号做对应处理后,在依据情况看是否把信号再通知到其它全部子进程。
进程同步
进程同步主要使用了原子操作,信号量和文件锁实现。当中基于原子操作能够实现自旋锁。基于原子操作、信号量以及文件锁,Nginx在更高层次上封装了一个相互排斥锁,,是用来方便。
原子操作
可以运行原子操作的原子变量仅仅有整型,包含无符号整型ngx_atomic_uint_t和有符号整型ngx_atomic_t,这两种类型都使用了volatilekeyword告诉C编译器不要做优化。
Nginx提供两个方法来使用原子操作来改动、获取整型变量:
ngx_atomic_cmp_set和ngx_atomic_fetch_add。
这两个方法都能够用来改动原子变量的值,而ngx_atomic_cmp_set方法同一时候还能够比較原子变量的值。
static ngx_inline ngx_atomic_uint ngx_atomic_cmp_set(ngx_atomic_t* lock, ngx_atomic_uint_t olc, ngx_atomic_uint_t set)
ngx_atomic_cmp_set方法会将old參数与原子变量lock的值做比較,假设他们相等,则将lock设为參数set,同一时候方法返回1;假设它们不相等,则不作不论什么改动,返回0。
static ngx_inline ngx_atomic_int_t ngx_atomic_fetch_add(ngx_atomic_t* value,ngx_atomic_int_t add)
ngx_atomic_fetch_add方法会把原子变量value的值加上參数add,同一时候翻译value的值。
自旋锁
基于原子操作,Nginx实现了一个自旋锁。自旋锁是一种非睡眠锁,也就是说,某进程假设试图获取自旋锁。当发现锁已经被其它进程获取时,那么不会使得当前进程进入睡眠状态,而是始终保持在可运行状态,每当内核调度到这个进程运行时就持续检查能否够获取锁。在拿不到锁时。这个进程的代码将会一直在自旋锁代码出运行。知道其它进程释放了锁且当前进程获取到了锁后,代码才会继续向下运行。
可见自旋锁主要是为了多处理器操作系统而设置的,它要解决的共享资源保护场景就是进程使用锁的时间很短。大部分Nginx的worker进程最好都不要进入睡眠状态,由于它很繁忙,在这个进程的epoll上可能会有十万甚至百万的TCP连接等等待着处理,进程一旦睡眠后必须等待其它时间的唤醒,这中间及其频繁的进程切换带来的负载消耗可能无法让用户接受。
以下介绍基于原子操作的自旋锁方法ngx_spinlock是怎样实现的。
它有3个參数。当中lock參数就是原子变量表达的锁。当lock值为0时,表示锁是被释放的,而lock值不为0时则表示锁已经被某个进程持有了;value參数表示希望当锁没有被不论什么进程持有时。把lock值设为value表示当前进程持有了锁;第三个參数spin表示在多处理器系统内,当ngx_spinlock方法没有拿到锁时。当前进程在内核的一次调度中,该方法等待其它处理器释放锁的时间。
以下看一下它的源代码:
/*
* Copyright (C) Igor Sysoev
* Copyright (C) Nginx, Inc.
*/ #include <ngx_config.h>
#include <ngx_core.h> //函数:基于原子操作的自旋锁方法ngx_spinlock的实现
//參数解释:
//lock:原子变量表达的锁
//value:标志位。锁是否被某一进程占用
//spin:在多处理器系统内,当ngx_spinlock方法没有拿到锁时,当前进程在内核的一次调度中该方法等待其它处理器释放锁的时间
void
ngx_spinlock(ngx_atomic_t *lock, ngx_atomic_int_t value, ngx_uint_t spin)
{ #if (NGX_HAVE_ATOMIC_OPS)//支持原子操作 ngx_uint_t i, n; //一直处于循环中,直到获取到锁
for ( ;; ) { //lock为0表示没有其它进程持有锁,这时将lock值设置为value參数表示当前进程持有了锁
if (*lock == 0 && ngx_atomic_cmp_set(lock, 0, value)) {
return;
} //假设是多处理器系统
if (ngx_ncpu > 1) {
/*
在多处理器下。当发现锁被其它进程占用时,当前进程并非立马让出正在使用的CPU处理器,而是等待一段时间,看看其它处理器上的进程是否会释放锁,这会降低进程间切换的次数。
*/
for (n = 1; n < spin; n <<= 1) {
//随着等待的次数越来越多,实际去检查锁的间隔时间越来越大
for (i = 0; i < n; i++) {
/*
ngx_cpu_pause是很多架构体系中专门为了自旋锁而提供的指令,它会告诉CPU如今处于自旋锁等待状态,通常一个CPU会将自己置于节能状态,降低功耗。可是当前进程并没有让出正在使用的处理器。
*/
ngx_cpu_pause();//
} /*
检查锁是否被释放了,假设lock值为0且释放了锁后。就把它的值设为value,当前进程持有锁成功并返回
*/
if (*lock == 0 && ngx_atomic_cmp_set(lock, 0, value)) {
return;
}
}
} /*
` 当前进程让出处理器,但仍然处于可运行状态,使得处理器优先调度其它可运行状态的进程,这样。在进程被内核再次调度时,在for循环代码中能够期望其它进程释放锁。
*/
ngx_sched_yield();
} #else #if (NGX_THREADS) #error ngx_spinlock() or ngx_atomic_cmp_set() are not defined ! #endif #endif }
释放锁时须要Nginx模块通过ngx_atomic_cmp_set方法将原子变量设为0。
信号量
Nginx仅把信号量作为简单的相互排斥锁来使用,使用信号量作为相互排斥锁有可能导致进程睡眠。不做具体解释。
文件锁
文件锁是一种文件读写机制,在不论什么特定的时间仅仅同意一个进程訪问一个文件。利用这样的机制可以使读写单个文件的过程变得更安全。
不做具体解释。
Nginx实现的相互排斥锁
基于原子操作、信号量以及文件锁,Nginx在更高层次封装了一个相互排斥锁。使用起来非常方便,很多Nginx模块也仅仅接受使用它。以下介绍的是操作这个相互排斥锁的5中方法:
ngx_shmtx_create 初始化相互排斥锁
ngx_shmtx_destory 销毁相互排斥锁
ngx_shmtx_trylock 无堵塞地试图获取相互排斥锁,返回1表示获取相互排斥锁成功。返回0表示获取相互排斥锁失败
ngx_shmtx_lock 以堵塞进程的方式获取相互排斥锁。在方法返回时就已经持有了相互排斥锁了
ngx_shmtx_unlock 释放相互排斥锁
获取相互排斥锁时既能够使用不会堵塞进程的ngx_shmtx_trylock方法,也能够使用ngx_shmtx_lock方法告诉Nginx必须持有相互排斥锁后才干继续向下运行代码。它们都通过操作ngx_shmtx_t类型的结构来实现相互排斥结构,以下来看一下ngx_shmtx_t有哪些成员。
typedef struct{
#if ( NGX_HAVE_ATOMIC_OPS)
//原子变量锁
ngx_atomic_t* lock;
#if (NGX_HAVE_POSIX_SEM)
//semaphore为1 时表示获取锁将可能使用到的信号量
ngx_uint_t semaphonre;
//sem就是信号量锁
sem_t sem;
#endif;
#else
//使用文件锁时fd表示使用的文件句柄
ngx_fd_t fd;
//name表示文件名称
u_char* name;
#endif
/*自旋次数。表示在自旋状态下等待其它处理器结果中释放的时间。 由文件锁实现,spin没有不论什么意义*/
ngx_uint_t spin;
} ngx_shmtx_t;
ngx_shmtx_t结构涉及两个宏:NGX_HAVE_ATOMIC_OPS、NGX_HVE_POIX_SEM。这两个宏相应着相互排斥锁的3种不同实现。
第1种实现:当不支持原子操作时,会使用文件锁来实现ngx_hmtx_t相互排斥锁,这时它仅有fd和name成员。
这两个成员使用上面介绍的文件锁来提供堵塞、非堵塞的相互排斥锁。
第2种实现,支持原子操作却又不支持信号量。
第3种实现,在支持原子操作的同一时候,操作系统也支持信号量。
后两种实现的唯一差别是ngx_shmtx_lock方法执行时的效果,也就是说,支持信号量仅仅会影响堵塞进程的ngx_shmtx_lock方法持有锁的方式。当不支持信号量时,ngx_shmtx_lock取锁与上面介绍的自旋锁是一致的,而支持信号量后,ngx_shmtx_lock将在spin指定的一段时间内自旋等待其它处理器释放锁,假设达到spin上限还没有获取到锁,那么将会使用sem_wait使得当前进程进入睡眠状态,等其它进程时回訪了锁内核后,才会唤醒这个进程。当然。在实际过程中。ngx_shmtx_lock方法执行一段时间后,假设其它进程始终不放弃锁,那么当前进程将有可能强制性地获取到这把锁。这也是出于Nginx不宜使用堵塞进程的睡眠锁方面的考虑。
Nginx学习——Nginx进程间的通信的更多相关文章
- Android进程间的通信之AIDL
Android服务被设计用来执行很多操作,比如说,可以执行运行时间长的耗时操作,比较耗时的网络操作,甚至是在一个单独进程中的永不会结束的操作.实现这些操作之一是通过Android接口定义语言(AIDL ...
- Android进程间的通信之Messenger
Android进程间的通信方式可以通过以下两种方式完成: Android接口定义语言(AIDL) 使用Messenger绑定服务 本文我们将学习使用Messenger绑定服务的方式进行进程间的通信. ...
- Nginx学习---Nginx的详解_【all】
1.1. Nginx简介 1.什么是nginx nginx:静态的,开源的www软件,可以解析静态的小文件(低于1M ),支持高并发占用较发少的资源(3W并发,10个进程,内存150M),跨平台 te ...
- c++ 网络编程(三) LINUX/windows 进程间的通信原理与实现代码 基于多进程的服务端实现
原文作者:aircraft 原文链接:https://www.cnblogs.com/DOMLX/p/9613027.html 锲子:进程与线程是什么,他们的区别在哪里: 1 进程概念 进程是程序的一 ...
- c 进程间的通信
在上篇讲解了如何创建和调用进程 c 进程和系统调用 这篇文章就专门讲讲进程通信的问题 先来看一段下边的代码,这段代码的作用是根据关键字调用一个Python程序来检索RSS源,然后打开那个URL #in ...
- Unix系统中,两个进程间的通信
进程之间通常需要进行数据的传输或者共享资源等,因此进程间需要通讯. 可以通过管道,信号,消息队列,共享内存,信号量和套接字等方式 FIFO表示命名管道,这种管道的操作是基于先进先出原理. PIPE 表 ...
- 探讨一个新的两个进程间的通信和编程模型 (Windows)
本文探讨一个新的Windows上的两个UI进程间的通信和编程模型. 开门见山,下面是这个通信模型的梗概图: 这个模型的设计目标描述如下: (1)发送数据接口:RpcSend, RpcPost RpcS ...
- 采用虚拟命名管道的字符设备和阻塞型I/O实现进程间的通信实现KWIC程序
采用虚拟命名管道的字符设备和阻塞型I/O实现进程间的通信实现KWIC程序专业程序代写c++程序代写
- python3,进程间的通信
本文来源于python 3.5版本的官方文档 multiprocessing模块为进程间通信提供了两种方法: 1.进程队列queue The Queue class is a near clone o ...
随机推荐
- maven学习(十一)——maven中的聚合与继承
一.聚合 如果我们想一次构建多个项目模块,那我们就需要对多个项目模块进行聚合 1.1.聚合配置代码 <modules> <module>模块一</module> & ...
- LAMP第三部分php配置和mysql配置
9. 配置防盗链http://www.lishiming.net/thread-71-1-1.html 防止别人的网站,放你网站图片的链接, 位置一般情况下在 /usr/local/apache/co ...
- 【bzoj4999】This Problem Is Too Simple! 树链剖分+动态开点线段树
题目描述 给您一颗树,每个节点有个初始值. 现在支持以下两种操作: 1. C i x(0<=x<2^31) 表示将i节点的值改为x. 2. Q i j x(0<=x<2^31) ...
- 【bzoj3930】[CQOI2015]选数 莫比乌斯反演+杜教筛
题目描述 我们知道,从区间[L,H](L和H为整数)中选取N个整数,总共有(H-L+1)^N种方案.小z很好奇这样选出的数的最大公约数的规律,他决定对每种方案选出的N个整数都求一次最大公约数,以便进一 ...
- libcmt.lib和msvcrt.lib冲突,原因和解决方法
libcmt.lib和msvcrt.lib冲突,原因和解决方法 https://blog.csdn.net/longlijun/article/details/7331093 libcmt.lib是w ...
- hihoCoder 1133 二分·二分查找之k小数(TOP K算法)
#1133 : 二分·二分查找之k小数 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 在上一回里我们知道Nettle在玩<艦これ>,Nettle的镇守府有很 ...
- aplusb 数论
题目描述 SillyHook 要给小朋友出题了,他想,对于初学者,第一题肯定是a+b啊, 但当他出完数据后神奇地发现.in 不见了,只留下了一些.out,他想还原.in,但 情况实在太多了,于是他想要 ...
- cocoapods的安装使用
本文非原创,只是看了别人的教程,自己做了下笔记 转载cocoapods其他详细教程 mac快速安装 由于天朝的那堵墙的阻挡,我们需要用taobao的镜像 以下操作在终端进行 gem sources - ...
- Python基础教程笔记 第二章
本章的名字虽然叫列表和元组,但是本章讲的最多的是列表,元祖指讲了很少的一部分.因为元组和列表很多方面都是一样的. 列表和元组的区别:列表可以被修改,元祖不可以被修改. python包含的6种内建序列: ...
- 【Linux】自主实现my_sleep【转】
转自:http://blog.csdn.net/scenlyf/article/details/52068522 版权声明:本文为博主原创文章,未经博主允许不得转载. 首先说一下信号相关的内容. ** ...