Nginx之共享内存与slab机制

1. 共享内存

在 Nginx 里,一块完整的共享内存以结构体 ngx_shm_zone_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);

typedef struct {
    /* 执行共享内存的起始地址 */
    u_char      *addr;
    /* 共享内存的长度 */
    size_t       size;
    /* 这块共享内存的名称 */
    ngx_str_t    name;
    /* 记录日志的 ngx_log_t 对象 */
    ngx_log_t   *log;
    /* 表示共享内存是否已经分配过的标志位,为 1 时表示已经存在 */
    ngx_uint_t   exists; /* unsigned exists:1 */
}ngx_shm_t;

struct ngx_shm_zone_s {
    // 通常指向创建该共享内存模块的上下文结构体,
    // 如对于 ngx_http_limit_req_module 模块,则指向
    // ngx_http_limit_req_ctx_t 结构体
    void                     *data;
    // 描述了一块共享内存
    ngx_shm_t                 shm;
    // 初始回调函数
    ngx_shm_zone_init_pt      init;
    void                     *tag;
    ngx_uint_t                noreuse;  /* unsigned  noreuse:1; */
};
  • tag 与 shm.name:name 字段主要用作共享内存的唯一标识,它能让 Nginx 知道调用者想使用哪个共享内存,但它没法让 Nginx 区分user到底想创建一个共享内存,还是使用那个已经存在的旧的共享内存。如,模块 A 创建了共享内存 sa,模块 A 或另外一个模块 B 再以同样的名称 sa 去获取共享内存,那么此时 Nginx 是返回模块 A 已创建的那个共享内存 sa 给模块 A /模块 B,还是直接以共享内存名重复提示模块 A /模块 B 出错呢?因此新增一个 tag 字段做冲突标识,该字段一般指向当前模块的 ngx_module_t 变量即可。通过 tag 字段,如果模块A/模块B再以同样的名称 sa 去获取模块A已创建的共享内存sa,模块A将获得它之前创建的共享内存的引用(因为模块A前后两次请求的tag相同),而模块B则将获得共享内存已做他用的错误提示(因为模块B请求的tag与之前模块A请求的tag不同)。

使用共享内存,需要在配置文件里加上该共享内存的相关配置信息,而 Nginx 在进行配置解析的过程中,根据这些配置信息就会创建对应的共享内存,不过此时的创建仅仅只是代表共享内存的结构体 ngx_shm_zone_t 变量的创建。具体实现在函数 ngx_shared_memory_add 内。

下面以 ngx_http_limit_req_module 模块为例,讲述共享内存的创建,配置如下:

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

当检测到该配置项 limit_req_zone 时,ngx_http_limit_req_module 模块就会调用 ngx_http_limit_req_zone 函数进行解析,如下:

typedef struct {
    ngx_http_limit_req_shctx_t  *sh;
    ngx_slab_pool_t             *shpool;
    /* integer value, 1 corresponds to 0.001 r/s */
    ngx_uint_t                   rate;
    ngx_http_complex_value_t     key;
    ngx_http_limit_req_node_t   *node;
} ngx_http_limit_req_ctx_t;

static char *
ngx_http_limit_req_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    u_char                            *p;
    size_t                             len;
    ssize_t                            size;
    ngx_str_t                         *value, name, s;
    ngx_int_t                          rate, scale;
    ngx_uint_t                         i;
    ngx_shm_zone_t                    *shm_zone;
    ngx_http_limit_req_ctx_t          *ctx;
    ngx_http_compile_complex_value_t   ccv;

    // 获取第一个参数,这里即为 "limit_req_zone"
    value = cf->args->elts;

    // 为 ngx_http_limit_req_ctx_t 结构体分配内存
    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_req_ctx_t));
    if (ctx == NULL) {
        return NGX_CONF_ERROR;
    }

    ngx_memzero(&ccf, sizeof(ngx_http_compile_complex_value_t));

    ccv.cf = cf;
    // 获取第二个参数,即为 "$binary_remote_addr"
    ccv.value = &value[1];
    ccv.complex_value = &ctx->key;

    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
        return NGX_CONF_ERROR;
    }

    size = 0;
    rate = 1;
    scale = 1;
    name.len = 0;

    for (i = 2; i < cf->args->nelts; i++) {

        // value[2].data = zone=one:10m
        if (ngx_strncmp(value[i].data, "zone=", 5) == 0) {

            // name.data = one:10m
            name.data = value[i].data + 5;

            // p -> ":10m"
            p = (u_char *) ngx_strchr(name.data, ‘:‘);

            if (p == NULL) {
                return NGX_CONF_ERROR;
            }

            // name.len = 3
            name.len = p - name.data;

            // s.data = "10m"
            s.data = p + 1;
            // s.len = 3
            s.len = value[i].data + value[i].len - s.data;

            // size = 10 * 1024 * 1024 = 10485760
            size = ngx_parse_size(&s);

            if (size == NGX_ERROR) {
                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;
        }

        // value[3].data = "rate=1r/s"
        if (ngx_strncmp(value[i].data, "rate=", 5) == 0) {

            // len = 9
            len = value[i].len;
            // p = "r/s"
            p = value[i].data + len - 3;

            if (ngx_strncmp(p, "r/s", 3) == 0) {
                scale = 1;
                // len = 6
                len -= 3;
            } else if (ngx_strncmp(p, "r/m", 3) == 0) {
                scale = 60;
                len -= 3;
            }

            // rate = 1
            rate = ngx_atoi(value[i].data + 5, len - 5);
            if (rate <= 0) {
                return NGX_CONF_ERROR;
            }

            continue;
        }

        return NGX_CONF_ERROR;
    }

    if (name.len == 0) {
        return NGX_CONF_ERROR;
    }

    // ctx->rate = 1000
    ctx->rate = rate * 1000 / scale;

    // 创建一个共享内存 ngx_shm_zone_t,并将该共享内存以list链表的形式
    // 添加到 cf->cycle->shared_memory 下
    shm_zone = ngx_shared_memory_add(cf, &name, size,
                                     &ngx_http_limit_req_module);
    if (shm_zone == NULL) {
        return NGX_CONF_ERROR;
    }

    if (shm_zone->data) {
        ctx = shm_zone->data;

        return NGX_CONF_ERROR;
    }

    // 设置该共享内存的初始化函数
    shm_zone->init = ngx_http_limit_req_init_zone;
    shm_zone->data = ctx;

    return NGX_CONF_OK;
}

该函数中会调用 ngx_shared_memory_add 为该 ngx_http_limit_req_module 模块创建一个共享内存,并将其以list链表的形式组织到全局变量 cf->cycle->shared_memory 下,具体实现如下:

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;

    // 先遍历 shared_memory 链表,检测是否有与 name 相冲突的
    // 共享内存,若name和size一样的,则直接返回该共享内存
    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 (tag != shm_zone[i].tag) {
            return NULL;
        }

        if (shm_zone[i].shm.size == 0) {
            shm_zone[i].shm.size = size;
        }

        if (size && size != shm_zone[i].shm.size) {
            return NULL;
        }

        return &shm_zone[i];
    }

    // 从 shared_memory 链表中取出一个空闲项
    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;
    // 由上面知 size 为 10m
    shm_zone->shm.size = size;
    // name = "one:10m"
    shm_zone->shm.name = *name;
    shm_zone->shm.exists = 0;
    shm_zone->init = NULL;
    // ngx_shm_zone_t 的 tag 字段指向 ngx_http_limit_req_module 变量
    shm_zone->tag = tag;
    shm_zone->noreuse = 0;

    // 返回该表示共享内存的结构体
    return shm_zone;
}

上面的执行仅是为 ngx_http_limit_req_module 模块创建 ngx_shm_zone_t 结构体变量并将其加入到全局链表 cf->cycle->shared_memory 中。共享内存的真正创建是在配置文件全部解析完后,所有代表共享内存的结构体 ngx_shm_zone_t 变量以链表的形式挂接在全局变量 cf->cycle->shared_memory 下,Nginx 此时遍历该链表并逐个进行实际创建,即分配内存、管理机制(如锁、slab)初始化等。具体代码如下所示:

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;

        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].tag == oshm_zone[n].tag
                && shm_zone[i].shm.size == oshm_zone[n].shm.size
                && !shm_zone[i].noreuse)
            {
                shm_zone[i].shm.addr = oshm_zone[n].shm.addr;
#if (NGX_WIN32)
                shm_zone[i].shm.handle = oshm_zone[n].shm.handle;
#endif

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

        // 分配新的共享内存,有前面分析知大小为 10m
        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;
        }

        // 该 shm_zone[i].init 是各个共享内存所特定的,根据使用方的自身需求不同而不同
        if (shm_zone[i].init(&shm_zone[i], NULL) != NGX_OK) {
            goto failed;
        }

    shm_zone_found:

        continue;
    }

    ...
}

2. slab 机制

Nginx 的 slab 机制主要是和共享内存一起使用,Nginx 在解析完配置文件,把即将使用的共享内存全部以 list 链表的形式组织在全局变量 cf->cycle->shared_memory 下之后,就会统一进行实际的内存分配,而 Nginx 的 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;

    // 指向该共享内存的起始地址处
    sp = (ngx_slab_pool_t *) zn->shm.addr;

    // 判断该共享内存是否已经存在了
    if (zn->shm.exists) {

        if (sp == sp->addr) {
            return NGX_OK;
        }
#if (NGX_WIN32)
        ...
#endif
        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是没有意义的
    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

    // 初始化一个信号量:对于Nginx,若支持原子操作,则优先使用
    // 原子变量实现的信号量
    if (ngx_shmtx_create(&sp->mutex, &sp->lock, file) != NGX_OK) {
        return NGX_ERROR;
    }

    ngx_slab_init(sp);

    return NGX_OK;
}

ngx_init_zone_pool() 函数是在共享内存分配好后进行的初始化调用,而该函数先调用 ngx_shmtx_create 函数为该共享内存初始化好一个信号量,接着调用 slab 的初始化函数 ngx_slab_init()。此时,该新分配的共享内存初始布局图如下:

原文地址:https://www.cnblogs.com/jimodetiantang/p/9193858.html

时间: 2024-08-01 12:20:00

Nginx之共享内存与slab机制的相关文章

内存管理 ---slab机制 分配对象

从一个缓存中分配对象总是遵循下面的原则: 1.本地高速缓存中是否有空闲对象,如果有的话则从其中获取对象, 2.如果本地高速缓存中没有对象,则从kmem_list3中的slab链表中寻找空闲对象并填充到本地高速缓存再分配: 3.如果所有的slab中都没有空闲对象了,那么就要创建新的slab,再分配 . 来自:http://blog.csdn.net/vanbreaker/article/details/7671211 Linux内核从slab中分配内存空间由kmalloc()或kmem_cache

内存管理---slab机制 销毁对象

Linux内核中将对象释放到slab中上层所用函数为kfree()或kmem_cache_free().两个函数都会调用__cache_free()函数. 缓存回收对象基于以下原则 1.本地高速缓存的空间还可以容纳空闲对象,则直接将对象放回本地高速缓存 2.本地高速缓存的空间已满,则按batchcount的值将对象从本地高速缓存转移到slab中,转移是基于先进先出的原则的,也就是转移entry数组最前面的batchcount个空闲对象,因为这些对象在数组中存在的时间相对较长,不太可能仍然驻留在C

Nginx + Lua + 共享内存

转自:http://blog.csdn.net/lxb_champagne/article/details/17099383 lua_package_path "/usr/local/share/luajit-2.0.2/jit?.lua;;"; lua_shared_dict devicedb 45m; location /query { default_type 'text/plain'; content_by_lua ' local args = ngx.req.get_uri_

共享内存通讯

1.核心理论 共享内存是IPC机制中的一种,它允许两个不相关的进程访问同一段内存,这是传递数据的一种非常有效的方式. 2.函数学习 创建/获取共享内存 函数名:shmget 函数原型:int shmget(key_t key,size_t size,int shmflg) 函数功能:创建或者获取共享内存,并返回其描述符 头文件:<sys/ipc.h> <sys/shm.h> 返回值:成功:返回创建或获取到的共享内存的描述符 失败:-1 参数说明:key:共享内存的键值 size:共

一张图深度解析Linux共享内存的内核实现

一张图深度解析Linux共享内存的内核实现 Sailor_forever  sailing_9806#163.com http://blog.csdn.net/sailor_8318/article/details/39484747 (本原创文章发表于Sailor_forever 的个人blog,未经本人许可,不得用于商业用途.任何个人.媒体.其他网站不得私自抄袭:网络媒体转载请注明出处,增加原文链接,否则属于侵权行为.如有任何问题,请留言或者发邮件给sailing_9806#163.com)

Linux IPC之共享内存C 事例

Linux IPC之共享内存 标签: linuxrandomnull工作 2011-08-25 11:52 4123人阅读 评论(0) 收藏 举报  分类: Linux(3)  读书札记(3)  版权声明:本文为博主原创文章,未经博主允许不得转载. 简介 共享内存(shared memory)是最简单的Linux进程间通信方式之一.使用共享内存,不同进程可以对同一块内存进行读写.由于所有进程对共享内存的访问就和访问自己的内存空间一样,而不需要进行额外系统调用或内核操作,同时还避免了多余的内存拷贝

Shared Memory共享内存----kithara RTS

翻译注:共享内存是程序之间进行数据交互的最基本的方式,而由于windows和kithara rts本身为两个独立的系统,为了能够使两个不同系统之上的程序进行通信,那么就必须开辟一块内存区域用于数据共享.本文是对kithara rts官方原文进行的翻译,加入了本人的一些使用经验. Shared Memory(共享内存) What you need to know about Shared Memory(共享内存基本知识) In 32-bit and64-bit Windows operating

[国嵌攻略][085][共享内存通讯]

共享内存 共享内存是IPC机制中的一种,它允许两个相关的进程访问同一段内存,这是传递数据的一种有效的方式. A.c #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <stdio.h> #include <string.h> #define SIZE 2048 typedef struct sharemem{ int flag; //共享标志 cha

共享内存通讯编程

共享内存是IPC机制中的一种.它允许两个不相关的进程访问同一段内存,这是传递数据的一种非常有效的方式. shmget函数 原型:int shmget(key_t key,size_t size,int shmflg) 头文件:<sys/ipc.h><sys/shm.h> 功能:创建/获取一个共享内存,成功则返回共享内存的描述符id,失败返回-1 参数:key:键值 size:创建的共享内存大小 shmflg:打开标志.如值为IPC_CREAT,则会创建一个新的共享内存 shmget