nginx之共享内存

常用函数

void *ngx_slab_alloc(ngx_slab_pool_t *pool, size_t size);

分配内容



使用的几个步骤

1. 一般我们需要几个变量来管理共享内存,一般放到全局的配置结构全中去,或直接一个全局变量。ngx_shm_zone_t指针来引用 我们创建的共享内存、ngx_slab_pool_t指针来管理我们共享内存的分配与释放。
2. 在读取或初始化配置文件时,调用ngx_shared_memory_add向全局共享内存链表中添加一个共享内存,然后保存到之前创建的gx_shm_zone_t指针。接着,注册我们自己的共享内存初始化函数。

3 ngx_init_cycle在执行时,会遍历链表ngx_cycle_s->shared_memory,创建共享内存(通过ngx_shm_alloc)。

4. 在我们自己的初始化函数中, 我们将共享内存初始地址转换为ngx_slab_pool_t指针保存到在步骤一中创建的那个ngx_slab_pool_t指针中。那么在后面,我们就通过这个ngx_slab_pool_t来进行共享内存的分配与释放。


一些理论

nginx是多进程模型,在许多场景我们可能需要跨进程共享数据,考虑到这个可能性,nginx本身也提供了共享内存这方面的接口ngx_int_t ngx_shm_alloc(ngx_shm_t *shm);
这个是nginx中关于分配共享内存最底层的接口了,它实际上就是直接调用mmap进行共享内存的分配的。
对于需要多次分配和释放共享内存的场景来说,直接用这个接口实际是无法忍受效率上的问题,或者有人想到可以直接分配一块大内存自己来进行分配释放的管理,但其实你不用自己做,nginx已经做好这部分的工作了,相关的接口是ngx_shm_zone_t *ngx_shared_memory_add(ngx_conf_t *cf, ngx_str_t *name, size_t size, void *tag),通过这个接口我们可以分配一大块内存,再借助ngx_slab_pool_t这个结构体来管理共享内存。那nginx又是如何来组织共享内存的呢?

对于每一个共享内存,都会先调用ngx_shm_alloc进行共享内存的分配,然后使用ngx_init_zone_pool对共享内存进行初始化,最后就调用我们设置的init函数了。

如果我们的模块中要使用一个共享内存,需要调用ngx_shared_memory_add来创建共享内存。而ngx_shared_memory_add不会马上创建一个共享内存,它是先登记一下共享内存的使用信息,比如名称、大小等,然后在进程初始化的时候再进行共享内存的创建与初始化。


POSIX是标准化组织推行的,适合系统之间移植,更简单一些。而System V 历史较为悠久,接口比较复杂。另一方面,可能有些系统尚未对POSIX做实现,而system V的实现则更全。应该说POSIX和system V是各有所长,但POSIX似乎是大势所趋吧。相对于具体应用,在IPC,进程间的消息传递和同步上,似乎POSIX用得较普遍;而在共享内存方面,POSIX实现尚未完善,system V仍为主流。

共享内存的实际创建是通过ngx_shm_alloc来实现的,nginx里面包含了共享内存的实现的多种方式,linux中默认使用mmap来实现,实现代码比较简单。

共享内存的主要操作有以下几种:

共享内存的分配ngx_shm_alloc
共享内存的释放ngx_shm_free

锁常用函数

ngx_int_t

ngx_shmtx_create(ngx_shmtx_t *mtx, ngx_shmtx_sh_t *addr, u_char *name);

创建锁


ngx_shmtx_destory()

销毁锁

ngx_shmtx_trylock()

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

ngx_shmtx_lock()

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

ngx_shmtx_unlock()

解锁

ngx_shmtx_force_unlock()

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

ngx_shmtx_wakeup()

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


slab

// 初始化slab池  
void ngx_slab_init(ngx_slab_pool_t *pool);  
// 未加锁的  
void *ngx_slab_alloc(ngx_slab_pool_t *pool, size_t size);  
// 在调用前已加锁,分配指定大小空间  
void *ngx_slab_alloc_locked(ngx_slab_pool_t *pool, size_t size);  
void ngx_slab_free(ngx_slab_pool_t *pool, void *p);  
// 释放空间  
void ngx_slab_free_locked(ngx_slab_pool_t *pool, void *p);


自旋锁简介
Nginx框架使用了三种消息传递方式:共享内存、套接字、信号。
Nginx主要使用了三种同步方式:原子操作、信号量、文件锁。
基于原子操作,nginx实现了一个自旋锁。自旋锁是一种非睡眠锁。如果某进程视图获得自旋锁,当发现锁已经被其他进程获得时,那么不会使得当前进程进入睡眠状态,而是始终保持进程在可执行状态,每当内核调度到这个进程执行时就持续检查是否可以获取到所锁。
自旋锁的应用场景自旋锁主要是为多处理器操作系统而设置的,他要解决的共享资源保护场景就是进程使用锁的时间非常短(如果锁的使用时间很久,自旋锁不合适,因为会占用大量的CPU资源)。
大部分情况下Nginx的worker进程最好都不要进入睡眠状态,因为它非常繁忙,在这个进程的epoll上可能有十万甚至百万的TCP连接等待着处理,进程一旦睡眠后必须等待其他事件的唤醒,这中间及其频繁的进程间切换带来的负载消耗可能无法让用户接受。
自旋锁源码分析
下面通过源代码看自旋锁的具体实现(Nginx_spinlock.c,Nginx1.4.1版本):
在多处理器下,当发现锁被其他进程占用时,当前进程并不是立刻让出正在使用的CPU处理器,而是等待一段时间,看看其他处理器上的进程是否会释放锁,这会减少进程间切换的次数。
函数ngx_cpu_pause():是虚度哦架构体系中专门为了自旋锁而提供的指令,它会告诉CPU现在处于自旋锁等待状态,通常一个CPU会将自己置于节能状态,降低功耗。但是当前进程并没有让出正在使用的处理器。
函数 ngx_sched_yield():当前进程仍仍然处于可执行状态,但暂时让出处理器,使得处理器优先调度其他可执行状态的进程,这样,在进程被内核再次调度时,在for循环代码中可以期望其他进程释放锁。


一些共享内存的结构体:
ypedef struct {
    u_char      *addr;     // 分配的共享内存的实际地址(指向共享内存的起始地址)
    size_t       size;     // 共享内存的大小
    ngx_str_t    name;     // 该字段用作共享内存的唯一标识,能让Nginx知道想使用哪个共享内存
    ngx_log_t   *log;
    ngx_uint_t   exists;   /* unsigned  exists:1;  */
} ngx_shm_t;

typedef struct ngx_shm_zone_s  ngx_shm_zone_t;

typedef ngx_int_t (*ngx_shm_zone_init_pt) (ngx_shm_zone_t *zone, void *data);

struct ngx_shm_zone_s {
    void                     *data; // 指向自定义数据结构,一般用来初始化时使用,可能指向本地地址
    ngx_shm_t                 shm;  // 真正的共享内存所在
    ngx_shm_zone_init_pt      init; // 这里有一个钩子函数,用于实际共享内存进行分配后的初始化
    void                     *tag;  // 区别于shm.name,shm.name没法让Nginx区分到底是想新创建一个共享内存,还是使用已存在的旧的共享内存,因此这里引入tag字段来解决该问题,tag一般指向当前模块的ngx_module_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不同)。

如果我们的模块中要使用一个共享内存,需要调用ngx_shared_memory_add来创建共享内存。而ngx_shared_memory_add不会马上创建一个共享内存,它是先登记一下共享内存的使用信息,比如名称、大小等,然后在进程初始化的时候再进行共享内存的创建与初始化。
那么,ngx_shared_memory_add这个函数是将共享内存的分配登记在哪的呢?在ngx_cycle_s这个结构体中有一个成员,即ngx_cycle_s->shared_memory,它是一个list,用来保存所有登记的共享内存,这个list中保存的是ngx_shm_zone_t类型的数据。 我们再看看ngx_shared_memory_add这个函数的实现,该函数先检查要添加的共享内存是否已存在,如果已存在,则直接返回,否则,创建一个新的。
1. 两个相同名字的共享内存大小要一样。
2. 两个相同名字的共享内存tag要一样。
3. 如果当前共享内存已经存在,则不需要再次添加。会返回同一个共享内存
4. 如果此共享内存不存在,则添加一个新的ngx_shm_zone_t
添加完后,会返回ngx_shm_zone_t,然后再设置init函数与data数据。
// tag一般为某一模块
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_shared_memory_add所创建的ngx_shm_zone_t,然后设置我们自己的初始化函数,ngx_shm_zone_t->init,这样,在进程初始化的时候,会创建这个共享内存,然后会调用我们的init函数。这个init函数的原型是:

typedef ngx_int_t (*ngx_shm_zone_init_pt) (ngx_shm_zone_t *zone, void *data);

添加共享内存代码示例:

shm_zone = ngx_shared_memory_add(cf, &name, size,
                 &ngx_http_limit_req_module);
if (shm_zone == NULL) {
  return NGX_CONF_ERROR;
}

shm_zone->init = ngx_http_limit_req_init_zone;
shm_zone->data = ctx;






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值