其他分享
首页 > 其他分享> > ngx学习——ngx_shm_zone_t

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. 实现

共享内存相关工作流程是:

  1. 配置文件解析阶段:各个模块 使用 ngx_shared_memory_add 申请共享内存
  2. ngx_cycle_init:ngx_cycle 遍历 shared_memory 链表,初始化共享内存
  3. 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