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()。此时,该新分配的共享内存初始布局图如下:

猜你喜欢

转载自www.cnblogs.com/jimodetiantang/p/9193858.html