我是靠谱客的博主 专一花瓣,这篇文章主要介绍nginx 进程间通信,现在分享给大家,希望可以做个参考。

进程间通信(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提供了文件的名称。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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中主要用了第②种和第④种

代码展示只选了源代码的部分:


复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#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


共享锁准备好之后,就是锁的创建,加锁,解锁操作:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
 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

代码展示只选了源代码的部分:

 

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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); }


 

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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 结构。

复制代码
1
2
3
4
5
6
7
struct ngx_shm_zone_s { void *data; ngx_shm_t shm;//共享内存存储结构 ngx_shm_zone_init_pt init; void *tag; };

复制代码
1
2
3
4
5
6
7
8
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

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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是怎么管理所有的共享内存的呢?

复制代码
1
2
3
4
5
6
7
8
9
10
struct ngx_cycle_s { ...... ngx_list_t shared_memory;//这是一个list,存储了所有的共享内存。 ...... };

因为只是说一下共享内存的情况,所以摘取了部分代码,并且忽略了存在oldcycle中共享内存的情况。详解见代码

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
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

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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中


复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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,这就是整个共享内存区结构化开始


复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(53)

评论列表共有 0 条评论

立即
投稿
返回
顶部