您的位置:首页 > 运维架构 > Nginx

nginx 进程通信--共享内存

2012-11-26 16:15 357 查看
共享内存是Linux下进程之间进行数据通信的最有效方式之一,而nginx就为我们提供了统一的操作接口来使用共享内存。

在nginx里,一块完整的内存以结构体ngx_shm_zone_s封装.其中包括是共享内存的名字(shm_zone[i].shm.name),大小(shm_zone[i].shm.size),标签(shm_zone[i].tag), ngx_shm_zone_init_pt init; (初始化共享内存时的回调函数)

一些共享内存的结构体:

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;


这些字段大都容易理解,只有tag字段需要解释一下,因为看上去它和name字段有点重复,而事实上,name字段主要用作共享内存的唯一标识,它能让nginx知道我想使用哪个共享内存,但它没法让nginx区分我到底是想新创建一个共享内存,还是使用那个已存在的旧的共享内存。举个例子,模块A创建了共享内存sa,模块A或另外一个模块B再以同样的名称sa去获取共享内存,那么此时nginx是返回模块A已创建的那个共享内存sa给模块A/模块B,还是直接以共享内存名重复提示模块A/模块B出错呢?不管nginx采用哪种做法都有另外一种情况出错,所以新增一个tag字段做冲突标识,该字段一般也就指向当前模块的ngx_module_t变量即可。这样在上面的例子中,通过tag字段的帮助,如果模块A/模块B再以同样的名称sa去获取模块A已创建的共享内存sa,模块A将获得它之前创建的共享内存的引用(因为模块A前后两次请求的tag相同),而模块B则将获得共享内存已做它用的错误提示(因为模块B请求的tag与之前模块A请求时的tag不同)。

当我们要使用一个共享内存时,总会在配置文件里加上该共享内存的相关配置信息,而nginx在进行配置解析的过程中,根据这些配置信息就会创建相应的共享内存,不过此时的创建只是代表共享内存的结构体变量的ngx_shm_zone_t的创建,具体实现在share_memory_add()中.nginx中所有共享内存都是以list链表的形式组织在全局变量cf->cycle->shared_memory下,在创建新的共享内存之前会先对该链表进行遍历查找以及冲突检测,对于已经存在且不存在冲突的共享内存可直接返回引用。

以nginx中ngx_http_limit_req_module模块为例://nginx限制链接

limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;

rate=1r/s 的意思是每个地址每秒只能请求一次,原理 burst=120 一共有120块令牌,并且每秒钟只新增1块令牌,120块令牌发完后 多出来的那些请求就会返回503

nginx在进行配置解析的时,遇到limit_req_zone配置项时,调用ngx_http_limit_req_zone(),而在该函数中继续调用share_memory_add()创建ngx_shm_zone_t结构体变量并加入到全局链表中:ngx_http_limit_req_zone() -> ngx_shared_memory_add() -> ngx_list_push()。

共享内存的真正创建是在配置文件全部解析完后,所有代表共享内存的结构体ngx_shm_zone_t变量以链表的形式挂接在全局变量cf->cycle->shared_memory下,nginx此时遍历该链表并逐个进行实际创建,即分配内存、管理机制(比如锁、slab)初始化等:

ngx_cycle_t *
ngx_init_cycle(ngx_cycle_t *old_cycle)
{
part = &cycle->shared_memory.part;
shm_zone = part->elts;
...............
for (i = 0; /* void */ ; i++) {
if (ngx_shm_alloc(&shm_zone[i].shm) != NGX_OK) {
goto failed;
}

if (ngx_init_zone_pool(cycle, &shm_zone[i]) != NGX_OK) {
goto failed;
}

if (shm_zone[i].init(&shm_zone[i], NULL) != NGX_OK) {
goto failed;
}
}


其中函数ngx_shm_alloc()时共享内存的实际分配,针对当前系统可提供的接口,可以时mmap,shmget等,而ngx_init_zone_pool()函数是共享内存管理机制的初始化,共享内存涉及2个主题:(1)多进程共同使用共享内存块,必须考虑互斥问题 (2)nginx以性能著称,那么对于共享内存自然也有其独特的使用方式,虽然我们可以不用,但在这里也默认都会以这种slab的高效访问机制进行初始化.

回调函数shm_zone[i].init()是各个共享内存所特定的,根据使用方的自身需求不同而不同,这也是我们在使用共享内存时需特别注意的函数.

函数ngx_http_limit_req_init_zone()的第二个参数data表示‘旧’数据,在进行重新加载配置时(即nginx收到SIGHUP信号)该值将不为空,如果旧数据可继续使用,那么可直接返回NGX_OK;否则,需根据自身模块逻辑对共享内存的使用做相关初始化,比如ngx_http_limit_req_module模块,在第634、642行直接使用默认已初始化好的slab机制,进行内存的分配等。当函数ngx_http_limit_req_init_zone()正确执行结束,一个完整的共享内存就已创建并初始完成,接着要做的就是共享内存的使用,这即回到前面提到的两个主题:互斥与slab。

函数

含义

ngx_shmtx_create()

创建

ngx_shmtx_destory()

销毁

ngx_shmtx_trylock()

尝试加锁(加锁失败则直接返回,不等待)

ngx_shmtx_lock()

加锁(持续等待,直到加锁成功)

ngx_shmtx_unlock()

解锁

ngx_shmtx_force_unlock()

强制解锁(可对其它进程进行解锁)

ngx_shmtx_wakeup()

唤醒等待加锁进程(系统支持信号量的情况下才可用)

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: