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