概述
进程间通信(IPC)
一:父进程与子进程间通信
nginx是master-worker服务器模型,master负责接收外部信号,并给子进程发送信号,比如:重启,二进制文件替换等。
在这里采用的是高级进程间通信方式:unix域套接字,socketpair
nginx细节:
子进程只保留自己的channel[1],并把其他子进程的channel[1]给关闭了,避免本进程误接收到发给其他进程的信号。
父进程独占所有进程的channel[0]: fcntl(ngx_processes[s].channel[0], F_SETOWN, ngx_pid)。
channel只用于父进程给子进程发送信号,并没有子进程给父进程发送信号的需求。
注:
unix域套接字是套接字和管道的结合物。
相比较网络ipc:因为unix域套接字效率要高,它只是复制数据,并不进行协议处理,网络报头,数据检验等操作,只是数据的复制。
相比较管道:因为管道pipe两次才能实现全双工,使得代码复杂。两者区别见:点击打开链接
Socketpair与其他IPC相比最大的不同是它可以在进程间传递描述符
二:子进程间通信
子进程通讯主要有两个地方:
一个是子进程间的accpet锁,解决epoll_wait造成的惊群。
一个是cache模块和limit模块,这些模块需要子进程访问共同的存储区,并对共同存储器区的访问也需要加锁控制。
锁在nginx里有两种实现方式,一个是共享内存地址作为信号量lock,一个是文件锁,如果操作系统支持原子操作,首先考虑使用共享内存信号量,因为信号量比文件锁耗时少,但是不得不说文件锁使用还是相当方便的。
子进程要访问的共同的存储区则使用了共享内存实现。
1.子进程accept 解决惊群
子进程用同步来实现,同步一般是为了同步多个进程(线程)的活动,来允许进程(线程)间共享数据。
锁其实就是多个进程间共享一个变量,来控制进程的行为,nginx实现同步锁的时候有两种方式:共享内存原子锁;文件锁。
先看一下创建锁的函数:
ngx_int_t ngx_shmtx_create(ngx_shmtx_t *mtx, ngx_shmtx_sh_t *addr, u_char *name)
这个函数有三个参数,mtx是锁的数据结构,addr提供了共享内存的地址,name提供了文件的名称。
typedef struct {
//如果操作系统支持原子操作(NGX_HAVE_ATOMIC_OPS),那么使用共享内存锁,
//如果操作系统不支持院原子操作,那么使用文件锁
//支持原子操作方式的时候,因为使用的共享内存锁,这时候锁就是一个信号量
#if (NGX_HAVE_ATOMIC_OPS)
ngx_atomic_t *lock;
#if (NGX_HAVE_POSIX_SEM)
ngx_atomic_t *wait;
ngx_uint_t semaphore;
sem_t sem;
#endif
#else
ngx_fd_t fd;
u_char *name;
#endif
ngx_uint_t spin;
} ngx_shmtx_t;
下面分别看一下共享内存锁和文件锁:
1)在操作系统支持原子操作的情况下,采用了共享内存,锁就是共享内存的地址
共享内存主要有四种:
①内存映射文件:实名文件
②匿名内存映射
③posix共享内存区
④system V共享内存区
nginx中主要用了第②种和第④种
代码展示只选了源代码的部分:
#include <ngx_config.h>
#include <ngx_core.h>
#if (NGX_HAVE_MAP_ANON)
ngx_int_t
ngx_shm_alloc(ngx_shm_t *shm)
{
shm->addr = (u_char *) mmap(NULL, shm->size,
PROT_READ|PROT_WRITE,
MAP_ANON|MAP_SHARED, -1, 0);
}
#elif (NGX_HAVE_MAP_DEVZERO)
ngx_int_t
ngx_shm_alloc(ngx_shm_t *shm)
{
ngx_fd_t fd;
fd = open("/dev/zero", O_RDWR);
shm->addr = (u_char *) mmap(NULL, shm->size, PROT_READ|PROT_WRITE,
MAP_SHARED, fd, 0);
}
#elif (NGX_HAVE_SYSVSHM)
#include <sys/ipc.h>
#include <sys/shm.h>
ngx_int_t
ngx_shm_alloc(ngx_shm_t *shm)
{
int id;
id = shmget(IPC_PRIVATE, shm->size, (SHM_R|SHM_W|IPC_CREAT));
shm->addr = shmat(id, NULL, 0);
}
#endif
共享锁准备好之后,就是锁的创建,加锁,解锁操作:
ngx_int_t
ngx_shmtx_create(ngx_shmtx_t *mtx, ngx_shmtx_sh_t *addr, u_char *name)
{
mtx->lock = &addr->lock;
if (mtx->spin == (ngx_uint_t) -1) {
return NGX_OK;
}
mtx->spin = 2048;
#if (NGX_HAVE_POSIX_SEM)
mtx->wait = &addr->wait;
if (sem_init(&mtx->sem, 1, 0) == -1) {
ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_errno,
"sem_init() failed");
} else {
mtx->semaphore = 1;
}
#endif
return NGX_OK;
}
ngx_uint_t
ngx_shmtx_trylock(ngx_shmtx_t *mtx)
{
return (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid));
}
void
ngx_shmtx_unlock(ngx_shmtx_t *mtx)
{
if (mtx->spin != (ngx_uint_t) -1) {
ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, "shmtx unlock");
}
if (ngx_atomic_cmp_set(mtx->lock, ngx_pid, 0)) {
ngx_shmtx_wakeup(mtx);
}
}
2)不支持原子操作的情况下,采用了文件上锁:posix fcntl,锁就是此文件的权限锁。
对于文件上锁,只要准备好文件名字name即可,nginx默认的文件路径为:logs/nginx.lock.accpt
代码展示只选了源代码的部分:
ngx_int_t
ngx_shmtx_create(ngx_shmtx_t *mtx, ngx_shmtx_sh_t *addr, u_char *name)
{
if (mtx->name) {
if (ngx_strcmp(name, mtx->name) == 0) {
mtx->name = name;
return NGX_OK;
}
ngx_shmtx_destroy(mtx);
}
mtx->fd = ngx_open_file(name, NGX_FILE_RDWR, NGX_FILE_CREATE_OR_OPEN,
NGX_FILE_DEFAULT_ACCESS);
}
ngx_uint_t
ngx_shmtx_trylock(ngx_shmtx_t *mtx)
{
ngx_err_t err;
err = ngx_trylock_fd(mtx->fd);
}
void
ngx_shmtx_unlock(ngx_shmtx_t *mtx)
{
ngx_err_t err;
err = ngx_unlock_fd(mtx->fd);
}
ngx_err_t
ngx_trylock_fd(ngx_fd_t fd)
{
struct flock fl;
ngx_memzero(&fl, sizeof(struct flock));
fl.l_type = F_WRLCK;
fl.l_whence = SEEK_SET;
if (fcntl(fd, F_SETLK, &fl) == -1) {
return ngx_errno;
}
return 0;
}
ngx_err_t
ngx_unlock_fd(ngx_fd_t fd)
{
struct flock fl;
ngx_memzero(&fl, sizeof(struct flock));
fl.l_type = F_UNLCK;
fl.l_whence = SEEK_SET;
if (fcntl(fd, F_SETLK, &fl) == -1) {
return ngx_errno;
}
return 0;
}
三:子进程之间的共享资源
比如 访问控制 limit模块
比如proxy模块中的cache
对于进程的共享资源,nginx采用的是共享内存的方式。
其中共享内存 采用了ngx_shm_zone_s 结构。
struct ngx_shm_zone_s {
void *data;
ngx_shm_t shm;//共享内存存储结构
ngx_shm_zone_init_pt init;
void *tag;
};
typedef struct {
u_char *addr;//指向共享内存地址
size_t size;
ngx_str_t name;
ngx_log_t *log;
ngx_uint_t exists; /* unsigned exists:1; */
} ngx_shm_t;
ngx_shm_t 结构中的addr是共享内存的地址,共享内存的起始位置被结构化为:ngx_slab_pool_t,后续的空间都用来存储数据,ngx_slab_pool_t是用来组织共享内存的。
通过ngx_slab_pool_t名字可以看出,共享内存的使用采用的是slab算法 ,详见nginx slab算法。
ngx_shared_memory_add
typedef struct {
ngx_shmtx_sh_t lock;
size_t min_size;
size_t min_shift;
ngx_slab_page_t *pages;
ngx_slab_page_t free;
u_char *start;
u_char *end;
ngx_shmtx_t mutex;
u_char *log_ctx;
u_char zero;
void *data;
void *addr;
} ngx_slab_pool_t;
nginx共享内存用在了若干个地方,那个nginx是怎么管理所有的共享内存的呢?
struct ngx_cycle_s {
......
ngx_list_t shared_memory;//这是一个list,存储了所有的共享内存。
......
};
因为只是说一下共享内存的情况,所以摘取了部分代码,并且忽略了存在oldcycle中共享内存的情况。详解见代码
ngx_cycle_t *
ngx_init_cycle(ngx_cycle_t *old_cycle)
{
......
//初始化共享内存的list链表
if (ngx_list_init(&cycle->shared_memory, pool, n, sizeof(ngx_shm_zone_t))
!= NGX_OK)
{
ngx_destroy_pool(pool);
return NULL;
}
//ngx_conf_parse 会依次解析配置文件。那么调用模块指令的handler,在使用共享内存模块的handler中会将其共享内存 ngx_shm_zone_s加入到链表中,后面会有代码展示
if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) {
environ = senv;
ngx_destroy_cycle_pools(&conf);
return NULL;
}
//未完,待续,先看一下 ngx_conf_parse 发生了什么。此函数中后面的代码解析见后
}
解析配置文件时候,根据不同的指令,去调用对应模块的hanlder,可以发现新建共享内存的包括以下几个模块:
fastcgi module 的cache;
proxy module 的cache;
scgi module 的cache;
uwsgi module 的cache;
ngx_http_file_cache: 这个是以上四个模块公用的一个共享内存;
limit_conn_zone模块两个共享内存:limit_conn_zone 和limit_zone
limit_req模块两个共享内存:limit_req_zone 和 limit_req
在以上几个模块相应指令的的handler都调用了:
shm_zone = ngx_shared_memory_add(cf, &name, size,&ngx_http_limit_req_module);
ngx_shared_memory_add函数做的主要就是初始化共享内存区,并加入cycle->shared_memory的链表里
举例:limit_conn_zone:
配置指令:limit_conn_zone $binary_remote_addr zone=ip_zone:10m;
这个指令的hanler是ngx_http_limit_conn_zone函数,其中也调用了ngx_shared_memory_add。
static char *
ngx_http_limit_conn_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
u_char *p;
ssize_t size;
ngx_str_t *value, name, s;
ngx_uint_t i;
ngx_shm_zone_t *shm_zone;
ngx_http_limit_conn_ctx_t *ctx;
value = cf->args->elts;
ctx = NULL;
size = 0;
name.len = 0;
for (i = 1; i < cf->args->nelts; i++) {
if (ngx_strncmp(value[i].data, "zone=", 5) == 0) {
name.data = value[i].data + 5;
p = (u_char *) ngx_strchr(name.data, ':');
if (p == NULL) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid zone size "%V"", &value[i]);
return NGX_CONF_ERROR;
}
name.len = p - name.data;
s.data = p + 1;
s.len = value[i].data + value[i].len - s.data;
size = ngx_parse_size(&s);
if (size == NGX_ERROR) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid zone size "%V"", &value[i]);
return NGX_CONF_ERROR;
}
if (size < (ssize_t) (8 * ngx_pagesize)) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"zone "%V" is too small", &value[i]);
return NGX_CONF_ERROR;
}
continue;
}
if (value[i].data[0] == '$') {
value[i].len--;
value[i].data++;
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_conn_ctx_t));
if (ctx == NULL) {
return NGX_CONF_ERROR;
}
ctx->index = ngx_http_get_variable_index(cf, &value[i]);
if (ctx->index == NGX_ERROR) {
return NGX_CONF_ERROR;
}
ctx->var = value[i];
continue;
}
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid parameter "%V"", &value[i]);
return NGX_CONF_ERROR;
}
if (name.len == 0) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
""%V" must have "zone" parameter",
&cmd->name);
return NGX_CONF_ERROR;
}
if (ctx == NULL) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"no variable is defined for %V "%V"",
&cmd->name, &name);
return NGX_CONF_ERROR;
}
//name和size都是根据配置指令来获取的,按照上面的配置:则name就是ip_zone,size就是10m
shm_zone = ngx_shared_memory_add(cf, &name, size,
&ngx_http_limit_conn_module);
if (shm_zone == NULL) {
return NGX_CONF_ERROR;
}
if (shm_zone->data) {
ctx = shm_zone->data;
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"%V "%V" is already bound to variable "%V"",
&cmd->name, &name, &ctx->var);
return NGX_CONF_ERROR;
}
//设置init函数和data
shm_zone->init = ngx_http_limit_conn_init_zone;
shm_zone->data = ctx;
return NGX_CONF_OK;
}
接下来看看函数ngx_shared_memory_add
ngx_shm_zone_t *
ngx_shared_memory_add(ngx_conf_t *cf, ngx_str_t *name, size_t size, void *tag)
{
ngx_uint_t i;
ngx_shm_zone_t *shm_zone;
ngx_list_part_t *part;
part = &cf->cycle->shared_memory.part;
shm_zone = part->elts;
//先查找同名的共享内存区存在吗?如果存在,就返回存在的。
for (i = 0; /* void */ ; i++) {
if (i >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
shm_zone = part->elts;
i = 0;
}
if (name->len != shm_zone[i].shm.name.len) {
continue;
}
if (ngx_strncmp(name->data, shm_zone[i].shm.name.data, name->len)
!= 0)
{
continue;
}
if (size && size != shm_zone[i].shm.size) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"the size %uz of shared memory zone "%V" "
"conflicts with already declared size %uz",
size, &shm_zone[i].shm.name, shm_zone[i].shm.size);
return NULL;
}
if (tag != shm_zone[i].tag) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"the shared memory zone "%V" is "
"already declared for a different use",
&shm_zone[i].shm.name);
return NULL;
}
return &shm_zone[i];
}
//如果不存在,创建,加入队列。
shm_zone = ngx_list_push(&cf->cycle->shared_memory);
if (shm_zone == NULL) {
return NULL;
}
shm_zone->data = NULL;
shm_zone->shm.log = cf->cycle->log;
shm_zone->shm.size = size;
shm_zone->shm.name = *name;
shm_zone->shm.exists = 0;
shm_zone->init = NULL;
shm_zone->tag = tag;
return shm_zone;
}
解析完所有配置,自然就完成了上面的步骤,接下来回到ngx_init_cycle中
ngx_cycle_t *
ngx_init_cycle(ngx_cycle_t *old_cycle)
{
......
//紧接前面解析配置文件代码
//依次初始化链表中的共享内存
/* create shared memory */
part = &cycle->shared_memory.part;
shm_zone = part->elts;
for (i = 0; /* void */ ; i++) {
if (i >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
shm_zone = part->elts;
i = 0;
}
if (shm_zone[i].shm.size == 0) {
ngx_log_error(NGX_LOG_EMERG, log, 0,
"zero size shared memory zone "%V"",
&shm_zone[i].shm.name);
goto failed;
}
shm_zone[i].shm.log = cycle->log;
......
//进行内存映射,获取共享内存地址
if (ngx_shm_alloc(&shm_zone[i].shm) != NGX_OK) {
goto failed;
}
//初始化共享内存,进行slab算法结构分配
if (ngx_init_zone_pool(cycle, &shm_zone[i]) != NGX_OK) {
goto failed;
}
//调用各模块特定的init函数
if (shm_zone[i].init(&shm_zone[i], NULL) != NGX_OK) {
goto failed;
}
continue;
}
}
看一下函数ngx_init_zone_pool,这就是整个共享内存区结构化开始
static ngx_int_t
ngx_init_zone_pool(ngx_cycle_t *cycle, ngx_shm_zone_t *zn)
{
u_char *file;
ngx_slab_pool_t *sp;
sp = (ngx_slab_pool_t *) zn->shm.addr;
if (zn->shm.exists) {
if (sp == sp->addr) {
return NGX_OK;
}
ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
"shared zone "%V" has no equal addresses: %p vs %p",
&zn->shm.name, sp->addr, sp);
return NGX_ERROR;
}
sp->end = zn->shm.addr + zn->shm.size;
sp->min_shift = 3;
sp->addr = zn->shm.addr;
#if (NGX_HAVE_ATOMIC_OPS)
file = NULL;
#else
file = ngx_pnalloc(cycle->pool, cycle->lock_file.len + zn->shm.name.len);
if (file == NULL) {
return NGX_ERROR;
}
(void) ngx_sprintf(file, "%V%V%Z", &cycle->lock_file, &zn->shm.name);
#endif
//创建个同步锁
if (ngx_shmtx_create(&sp->mutex, &sp->lock, file) != NGX_OK) {
return NGX_ERROR;
}
sp->size
//进行slab ,这样以后内存分配就按照slab算法来进行了
ngx_slab_init(sp);
return NGX_OK;
}
好吧,nginx涉及到的进程间通信就这些了,写的有点乱,啊哈哈哈
最后
以上就是专一花瓣为你收集整理的nginx 进程间通信的全部内容,希望文章能够帮你解决nginx 进程间通信所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复