ngx学习——ngx_shm_zone_t
作者:互联网
1. 设计
woker进程常常需要对同一个对象示例进行操作,于是master进程需要准备共享内存,且使用slab算法管理这些内存块。
内存块的唯一标识为shm.name,且规定内存块只能绑定到一个模块(用tag表示)。
按照设计,内存块的用途必须确定且唯一,所以提供 init 和 data接口,让模块构造需要的容器
typedef ngx_int_t (*ngx_shm_zone_init_pt) (ngx_shm_zone_t *zone, void *data);
struct ngx_shm_zone_s {
void *data; // 为init方法留的接口
ngx_shm_t shm; // 共享内存
ngx_shm_zone_init_pt init; // 各个模块初始化本内存块的接口
void *tag; // 表示本内存块用于哪个模块使用
};
ngx_shm_zone_t *ngx_shared_memory_add(ngx_conf_t *cf, ngx_str_t *name,
size_t size, void *tag);
每个工作周期可以有很多共享内存,这些内存块使用 shared_memory链表管理
struct ngx_cycle_s {
ngx_pool_t *pool; // share_memory使用此缓存池分配
ngx_list_t shared_memory;
...
};
2. ngx_shm_t
ngx使用 共享内存或内存映射 封装的 共享内存
typedef struct {
u_char *addr; // 共享内存地址
size_t size; // 共享内存空间大小
ngx_str_t name; // 名称, 用于唯一区别共享内存
ngx_log_t *log;
ngx_uint_t exists; /* unsigned exists:1; */
} ngx_shm_t;
ngx_int_t ngx_shm_alloc(ngx_shm_t *shm);
void ngx_shm_free(ngx_shm_t *shm);
3. 实现
共享内存相关工作流程是:
- 配置文件解析阶段:各个模块 使用 ngx_shared_memory_add 申请共享内存
- ngx_cycle_init:ngx_cycle 遍历 shared_memory 链表,初始化共享内存
- fork()后 work进程使用 共享内存
3.1 申请共享内存
需要共享内存的模块定义相关command,在set接口调用ngx_shared_memory_add申请内存,
以 ngx_http_limit_zone_module为例
static char *
ngx_http_limit_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ssize_t n;
ngx_str_t *value;
ngx_shm_zone_t *shm_zone;
ngx_http_limit_zone_ctx_t *ctx;
value = cf->args->elts;
...;
// 申请新共享内存,或返回已有相关共享内存
shm_zone = ngx_shared_memory_add(cf, &value[1], n,
&ngx_http_limit_zone_module);
if (shm_zone == NULL) {
return NGX_CONF_ERROR;
}
if (shm_zone->data) {
ctx = shm_zone->data;
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"limit_zone \"%V\" is already bound to variable \"%V\"",
&value[1], &ctx->var);
return NGX_CONF_ERROR;
}
// shm_zone->data为 shm_zone->init的输入
shm_zone->init = ngx_http_limit_zone_init_zone;
shm_zone->data = ctx;
return NGX_CONF_OK;
}
ngx_shared_memory_add :返回已有的shm_zone,或构建新的shm_zone
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;
}
3.2 初始化共享内存
ngx_cycle会遍历 share_memory链表,后初始化共享内存
在解析完配置文件后,调用 ngx_cycle_init,会初始化共享内存
/* create shared memory */
part = &cycle->shared_memory.part;
shm_zone = part->elts;
// 遍历链表 shared_memory
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].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;
}
// 调用各个模块的 共享内存初始化接口
if (shm_zone[i].init(&shm_zone[i], NULL) != NGX_OK) {
goto failed;
}
shm_zone_found:
continue;
}
ngx_cycle_init关于共享内存的初始化,最后会调用shm_zone[i].init(&shm_zone[i], oshm_zone[n].data) 或者shm_zone[i].init(&shm_zone[i], NULL)
以limit_zone_module为例,在共享内存上建立容器
static ngx_int_t
ngx_http_limit_zone_init_zone(ngx_shm_zone_t *shm_zone, void *data)
{
ngx_http_limit_zone_ctx_t *octx = data;
size_t len;
ngx_slab_pool_t *shpool;
ngx_rbtree_node_t *sentinel;
ngx_http_limit_zone_ctx_t *ctx;
ctx = shm_zone->data;
if (octx) {
if (ngx_strcmp(ctx->var.data, octx->var.data) != 0) {
ngx_log_error(NGX_LOG_EMERG, shm_zone->shm.log, 0,
"limit_zone \"%V\" uses the \"%V\" variable "
"while previously it used the \"%V\" variable",
&shm_zone->shm.name, &ctx->var, &octx->var);
return NGX_ERROR;
}
// 沿用上个周期的树
ctx->rbtree = octx->rbtree;
return NGX_OK;
}
shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
if (shm_zone->shm.exists) {
ctx->rbtree = shpool->data;
return NGX_OK;
}
ctx->rbtree = ngx_slab_alloc(shpool, sizeof(ngx_rbtree_t));
if (ctx->rbtree == NULL) {
return NGX_ERROR;
}
shpool->data = ctx->rbtree;
sentinel = ngx_slab_alloc(shpool, sizeof(ngx_rbtree_node_t));
if (sentinel == NULL) {
return NGX_ERROR;
}
ngx_rbtree_init(ctx->rbtree, sentinel,
ngx_http_limit_zone_rbtree_insert_value);
len = sizeof(" in limit_zone \"\"") + shm_zone->shm.name.len;
shpool->log_ctx = ngx_slab_alloc(shpool, len);
if (shpool->log_ctx == NULL) {
return NGX_ERROR;
}
ngx_sprintf(shpool->log_ctx, " in limit_zone \"%V\"%Z",
&shm_zone->shm.name);
return NGX_OK;
}
3.3 模块使用共享内存工作
static ngx_int_t
ngx_http_limit_zone_handler(ngx_http_request_t *r)
{
size_t len, n;
uint32_t hash;
ngx_int_t rc;
ngx_slab_pool_t *shpool;
ngx_rbtree_node_t *node, *sentinel;
ngx_pool_cleanup_t *cln;
ngx_http_variable_value_t *vv;
ngx_http_limit_zone_ctx_t *ctx;
ngx_http_limit_zone_node_t *lz;
ngx_http_limit_zone_conf_t *lzcf;
ngx_http_limit_zone_cleanup_t *lzcln;
// 如果本请求已经被添加到了zone,则使用下一个模块
if (r->main->limit_zone_set) {
return NGX_DECLINED;
}
// 找到本模块的 conf
lzcf = ngx_http_get_module_loc_conf(r, ngx_http_limit_zone_module);
if (lzcf->shm_zone == NULL) {
return NGX_DECLINED;
}
// 找到ctx
ctx = lzcf->shm_zone->data;
// 找到 Key
vv = ngx_http_get_indexed_variable(r, ctx->index);
if (vv == NULL || vv->not_found) {
return NGX_DECLINED;
}
len = vv->len;
if (len == 0) {
return NGX_DECLINED;
}
if (len > 255) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"the value of the \"%V\" variable "
"is more than 255 bytes: \"%v\"",
&ctx->var, vv);
return NGX_DECLINED;
}
r->main->limit_zone_set = 1;
hash = ngx_crc32_short(vv->data, len);
// 注册本请求结束后的收尾工作
cln = ngx_pool_cleanup_add(r->pool, sizeof(ngx_http_limit_zone_cleanup_t));
if (cln == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
shpool = (ngx_slab_pool_t *) lzcf->shm_zone->shm.addr;
ngx_shmtx_lock(&shpool->mutex);
node = ctx->rbtree->root;
sentinel = ctx->rbtree->sentinel;
while (node != sentinel) {
if (hash < node->key) {
node = node->left;
continue;
}
if (hash > node->key) {
node = node->right;
continue;
}
/* hash == node->key */
lz = (ngx_http_limit_zone_node_t *) &node->color;
rc = ngx_memn2cmp(vv->data, lz->data, len, (size_t) lz->len);
if (rc == 0) {
if ((ngx_uint_t) lz->conn < lzcf->conn) {
lz->conn++;
goto done;
}
ngx_shmtx_unlock(&shpool->mutex);
ngx_log_error(lzcf->log_level, r->connection->log, 0,
"limiting connections by zone \"%V\"",
&lzcf->shm_zone->shm.name);
return NGX_HTTP_SERVICE_UNAVAILABLE;
}
node = (rc < 0) ? node->left : node->right;
}
n = offsetof(ngx_rbtree_node_t, color)
+ offsetof(ngx_http_limit_zone_node_t, data)
+ len;
node = ngx_slab_alloc_locked(shpool, n);
if (node == NULL) {
ngx_shmtx_unlock(&shpool->mutex);
return NGX_HTTP_SERVICE_UNAVAILABLE;
}
lz = (ngx_http_limit_zone_node_t *) &node->color;
node->key = hash;
lz->len = (u_char) len;
lz->conn = 1;
ngx_memcpy(lz->data, vv->data, len);
ngx_rbtree_insert(ctx->rbtree, node);
done:
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"limit zone: %08XD %d", node->key, lz->conn);
ngx_shmtx_unlock(&shpool->mutex);
cln->handler = ngx_http_limit_zone_cleanup;
lzcln = cln->data;
lzcln->shm_zone = lzcf->shm_zone;
lzcln->node = node;
return NGX_DECLINED;
}
标签:node,zone,ngx,ctx,NGX,shm 来源: https://www.cnblogs.com/yangxinrui/p/15155320.html