nginx共享内存:共享内存的实现

       nginx中, 作者为我们提供了方便共享内存的使用的接口,关于共享内存的使用在我之前的文章中有介绍。这次我们来研究一下nginx是如何实现的。 我们知道,如果我们的模块中要使用一个共享内存,需要调用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_shm_zone_t这个结构体的实现源码:

struct ngx_cycle_s {
     ****
     ngx_list_t                shared_memory; //  共享内存 ngx_shm_zone_t
     ****
};

struct ngx_shm_zone_s {
    // 这里可以指向自定义的一个数据结构,主要是为了在数据初始化的时候使用到,或通过共享内存直接拿到与共享内存相关的数据,它不一定指向共享内存中的地址
    void                     *data;
    // 实际的共享内存
    ngx_shm_t                 shm;
    // 共享内存的初始化函数
    ngx_shm_zone_init_pt      init;
    // 标记
    void                     *tag;
};

我们再看看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;


这样,在进行初始化时,就会创建共享内存了,我们来看看它是如何做的,初始化工作是在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;
        }


        // 跳过未使用的共享内存
        if (shm_zone[i].init == NULL) {
            /* unused shared zone */
            continue;
        }


        shm_zone[i].shm.log = cycle->log;


        // 这里主要是考虑到在做reload等操作时,应该如何做
        opart = &old_cycle->shared_memory.part;
        oshm_zone = opart->elts;
        for (n = 0; /* void */ ; n++) {


            if (n >= opart->nelts) {
                if (opart->next == NULL) {
                    break;
                }
                opart = opart->next;
                oshm_zone = opart->elts;
                n = 0;
            }
            if (shm_zone[i].shm.name.len != oshm_zone[n].shm.name.len) {
                continue;
            }
            if (ngx_strncmp(shm_zone[i].shm.name.data,
                            oshm_zone[n].shm.name.data,
                            shm_zone[i].shm.name.len)
                != 0)
            {
                continue;
            }
            // 如果新的共享内存的大小与原有的共享内存大小相同,就不需要重新添加了
            if (shm_zone[i].shm.size == oshm_zone[n].shm.size) {
                // 新的共享内存直接指向已经存在的共享内存的地址
                shm_zone[i].shm.addr = oshm_zone[n].shm.addr;
                // 当然,还是需要重新初始化一下的,因为重新初始化的函数中,可能会有一些对本地内存的操作,比如在某个本地内存结构体中保存共享内存地址等,所以在我们的初始化函数中,要小心处理
                if (shm_zone[i].init(&shm_zone[i], oshm_zone[n].data)
                    != NGX_OK)
                {
                    goto failed;
                }


                goto shm_zone_found;
            }


            // 如果不存在,则释放掉老的共享内存
            // 注意,如果新配置的共享内存大小与老的共享内存大小不一样,那老的共享内存大小就被释放掉了,所以这点我们要特别注意
            ngx_shm_free(&oshm_zone[n].shm);


            break;
        }


        // 创建共享内存
        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;
        }


        // 这里调用我们自己的初始化函数,注意第二个参数是NULL的
        if (shm_zone[i].init(&shm_zone[i], NULL) != NGX_OK) {
            goto failed;
        }


    shm_zone_found:


        continue;
    }
 ........

我们看到,在对每一个共享内存,先调用ngx_shm_alloc创建共享内存,然后调用ngx_init_zone_pool对共享内存进行初始化,然后调用我们自己添加的共享内存init函数。ngx_init_zone_pool函数会对共享内存进行slab的初始化,之后,我们就可以通过slab进行共享内存的管理了。接下来,在我们的init函数里面,将共享内存强制转换成slab,以后,我们对共享内存的分配与释放,就可以通过这个slab来实现了(可以参考我前一篇文章中共享内存的使用相关的分析)。我们来看看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;


    // 使用slab来进行共享内存的管理
    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;
    }


    // 初始化slab分配器
    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, (void *) &sp->lock, file) != NGX_OK) {
        return NGX_ERROR;
    }


    // 初始化slab
    ngx_slab_init(sp);


    return NGX_OK;
}

在ngx_init_zone_pool之后,我们在共享内存里面做内存分配的工作,就需要slab来帮忙了。 
另外,共享内存的实际创建是通过ngx_shm_alloc来实现的,nginx里面包含了共享内存的实现的多种方式,linux中默认使用mmap来实现,实现代码比较简单,看看:

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);


    if (shm->addr == MAP_FAILED) {
        ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,
                      "mmap(MAP_ANON|MAP_SHARED, %uz) failed", shm->size);
        return NGX_ERROR;
    }


    return NGX_OK;
}

通过之前文章中的例子,再结合本文中的源码分析,相信对nginx中共享内存的使用应该会比较清楚了。

区块链、后端技术交流!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值