nginx使用ngx_shared_memory_add和ngx_slab_alloc等在共享内存里创建一棵红黑树

nginx是多进程的运行的一个程序,对需要跨进程访问的资源,nginx提供了共享内存上的slab来管理。这里我们实现一个在slab上的红黑树,这里把代码贴出来,对流程的理解都在代码的注释里!!

首先,我们在配置阶段来创建一块共享内存,然后再该共享内存上使用nginx的slab机制,把这块共享内存管理起来,方便我们分配红黑树相关的内容。

static void 
ngx_jtxy_create_shared_memory(ngx_conf_t *cf, ngx_jtxy_conf_t* jtxy_cf)
{
	ngx_shm_zone_t *shm_zone;
	void* tag = (void*)&ngx_jtxy_module;
	//在配置阶段调用ngx_shared_memory_add,
    //说明要创建一块名为sean_share_data_name的共享内存,
	//此时,并没有真正创建出来,要等配置阶段完成,
    //nginx统计出所有模块要创建的所有共享内存后,
	//会在ngx_init_cycle里真正的分配每一块有名的共享内存
	//这里再提一下,再nginx里,几乎所有的add,都是返回出一个指针的做法,
    //add函数内会分配出一块内存并返回这块内存的
	//指针给调用者使用。
	//比如他的list,array都是这样,拿到返回的指针后再初始化这个元素,
    //而不像是我们常规的做法,先初始化好一个元素,
	//再把这个元素添加到list,array里,这是因为nginx里list,
    //array都是在pool里分配的,它的add实际是向pool申请一块内存出来使用。
	shm_zone = ngx_shared_memory_add(cf, 
        &jtxy_share_uri_data_name, ngx_pagesize * 16, tag);
    if (shm_zone->data) {
		//多个location都配置了的,之后的就会到这里
        ngx_log_error(NGX_LOG_EMERG, cf->cycle->log, 0,
                           "ngx_jtxy_create_shared_memory %V is already add",
                           &jtxy_share_uri_data_name);
		ngx_jtxy_conf_t* last_jtxy_cf = (ngx_jtxy_conf_t*)shm_zone->data;
		jtxy_cf->uri_shctx = last_jtxy_cf->uri_shctx;
        return;
    }

	ngx_log_error(NGX_LOG_EMERG, shm_zone->shm.log, 0,
		"ngx_jtxy_create_shared_memory  %V, shm_zone %p, sean_cf %p, "
		"shm_zone->data %p, noreuse %d, ngx_pagesize * 2 %d",
		&jtxy_share_uri_data_name, shm_zone, jtxy_cf, 
		shm_zone->data, shm_zone->noreuse, ngx_pagesize * 2);

    ngx_http_jtxy_shctx_t* shctx = (ngx_http_jtxy_shctx_t*)ngx_pcalloc(
            cf->pool, sizeof(ngx_http_jtxy_shctx_t));
    if (shctx == NULL) {
        return;
    }
	jtxy_cf->uri_shctx = shctx;

	//这里为什么要设置一下init函数和data值呢,因为上面讲到
	//ngx_shared_memory_add时并没有真正的去分配出一块共享内存出来,
    //他得等到统计完所有的模块后才会去真正分配一块共享内存出来
	//到那时,共享内存分配出来了,就会给用户一个时机,
    //调用这里设置的回调ngx_sean_shared_memory_init来做一些初始化的事情。
	shm_zone->init = ngx_jtxy_shared_memory_init;
	shm_zone->data = (void*)jtxy_cf;

	shm_zone->noreuse = 1;  //每次重启都新建共享内存
	ngx_log_error(NGX_LOG_EMERG, shm_zone->shm.log, 0,
		"ngx_sean_create_shared_memory 2 %V, shm_zone %p, "
		"sean_cf %p, shm_zone->data %p, noreuse %d",
		&jtxy_share_uri_data_name, shm_zone, jtxy_cf
		, shm_zone->data, shm_zone->noreuse);
}

上面的函数就是我们在配置阶段开始分配共享内存 ,比如如下,我们就是在ngx_jtxy_block函数里调用ngx_jtxy_create_shared_memory。

typedef struct {
	ngx_rbtree_t                  uri_rbtree;           //统计共享内存中的红黑树
	ngx_rbtree_node_t             uri_rbtree_sentinel;  //统计共享内存中的红黑树
} ngx_http_jtxy_shctx_rbtree_t;    //共享内存下的红黑树

typedef struct {
	ngx_slab_pool_t*              uri_shpool;       //统计的slab共享内存
	ngx_http_jtxy_shctx_rbtree_t* rbtree;           //统计共享内存中的红黑树
} ngx_http_jtxy_shctx_t;    //共享内存下的红黑树

typedef struct
{
	ngx_http_jtxy_shctx_t*       uri_shctx; //保存共享内存的相关信息
}ngx_jtxy_conf_t;

static ngx_command_t  ngx_jtxy_commands[] = {

    { ngx_string("jtxy"),         //               name
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS,   //type
      ngx_jtxy_block,     //  char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
      NGX_HTTP_LOC_CONF_OFFSET,                       //   conf
      0,                       //   offset 
      NULL },              //   post

     ngx_null_command
};

...
...
...

static char *ngx_jtxy_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ... 
    ...
    ...
	ngx_jtxy_conf_t* jtxy_cf = (ngx_jtxy_conf_t*)conf;
	
	//uri的slab上的红黑树
	ngx_jtxy_create_shared_memory(cf, jtxy_cf);
    ...
    ...
    ...
	return NGX_CONF_OK;
}

        在配置阶段调用ngx_shared_memory_add,说明要创建一块名为sean_share_data_name的共享内存,此时,并没有真正创建出来,要等配置阶段完成,nginx统计出所有模块要创建的所有共享内存后,会在ngx_init_cycle里真正的分配每一块有名的共享内存。因为可能在不同的location里会使用相同名称的共享内存,先做统一的名称统计,然后再分配。

        这里再提一下,在nginx里,几乎所有的add函数,都是返回出一个指针的做法。add函数内会分配出一块内存并返回这块内存的指针给调用者使用。比如他的list,array都是这样,拿到返回的指针后再初始化这个元素,而不像是我们常规的做法,先初始化好一个元素,再把这个元素添加到list,array里,这是因为nginx里list,array都是在pool里分配的,它的add实际是向pool申请一块内存出来使用。

我们看看对shm_zone->data的判断和赋值

    if (shm_zone->data) {
		//多个location都配置了的,之后的就会到这里
        ngx_log_error(NGX_LOG_EMERG, cf->cycle->log, 0,
                           "ngx_jtxy_create_shared_memory %V is already add",
                           &jtxy_share_uri_data_name);
		ngx_jtxy_conf_t* last_jtxy_cf = (ngx_jtxy_conf_t*)shm_zone->data;
		jtxy_cf->uri_shctx = last_jtxy_cf->uri_shctx;
        return;
    }
 
    ngx_http_jtxy_shctx_t* shctx = (ngx_http_jtxy_shctx_t*)ngx_pcalloc(
            cf->pool, sizeof(ngx_http_jtxy_shctx_t));
    if (shctx == NULL) {
        return;
    }
	jtxy_cf->uri_shctx = shctx;
 
	shm_zone->init = ngx_jtxy_shared_memory_init;
	shm_zone->data = (void*)jtxy_cf;

这里的逻辑是怎么回事呢?这个共享内存确实是在一个module里创建的,但是,如果我们在多个location里都配置了该模块,那么该共享内存的配置就应该保存到每个location的配置里,ngx_jtxy_create_shared_memory传进来的参数cf和jtxy_cf就是ngx_jtxy_block传入的每个location的上下文信息。注意不同的location的cf和jtxy_cf是不同的,指针指向不同的内存块。

这里呢,我们的共享内存块只有一块,他的shm_zone->data只能保存一个值,一个location的上下文保存在了shm_zone->data里,其他的location就保存不到shm_zone->data里了,那么其他location的上下文如何拿到共享内存的值呢,当然就是上面的if (shm_zone->data)判断里了,让其他location能拿到保存着共享内存信息的那片内存的指针。

然后看着两行代码

	shm_zone->init = ngx_jtxy_shared_memory_init;
	shm_zone->data = (void*)jtxy_cf;

        这里为什么要设置一下init函数和data值呢,因为上面讲到ngx_shared_memory_add时并没有真正的去分配出一块共享内存出来,他得等到统计完所有的模块后才会去真正分配一块共享内存出来,到那时,共享内存分配出来了,就会给用户一个时机,调用这里设置的回调ngx_sean_shared_memory_init来做一些初始化的事情。接下来再看看ngx_sean_shared_memory_init函数。

static ngx_int_t
ngx_jtxy_shared_memory_init(ngx_shm_zone_t *shm_zone, void *data)
{
	//void *data 共享内存重用时有用,就是上次(reload前)分配的共享内存
	ngx_jtxy_conf_t* jtxy_cf = (ngx_jtxy_conf_t*)shm_zone->data; //ngx_sean_conf_t

	//把这片共享内存设置为内存池,
	//可以看到,这里并没有初始化shpool的ngx_slab_pool_t结构体,
    //只是简单的把shm_zone->shm.addr赋值给了shpool,
	//难道不用初始化shpool吗,其实初始化ngx_slab_pool_t的过程nginx已经做了。
    //在ngx_init_zone_pool函数里
	//ngx_init_cycle会调用ngx_init_zone_pool来完成。
    //这里我们就只是保存一下,拿到他的指针而已
	jtxy_cf->uri_shctx->uri_shpool = (ngx_slab_pool_t *)shm_zone->shm.addr;

    if (shm_zone->shm.exists) {
		//如果这片共享内存池之前已经存在了,拿一下之前的共享内存上的shctx
		//为什么会有这段代码呢,因为我们创建的是有名的共享内存,
        //即使nginx停掉了,这块内存还是存在的,
		//等nginx重新启动后,拿到这片共享内存后就会在这里得到之前的上下文。
		//为什么会重用这个共享内存呢,因为shm_zone->noreuse被设置为了0,
        //这也nginx是默认设置(ngx_shared_memory_add里),它表明是否复用之前的共享内存
       
	   // jtxy_cf->uri_shctx = (ngx_http_jtxy_shctx_t*)jtxy_cf->uri_shctx->uri_shpool->data; /to do //

        return NGX_OK;
    }

	//到这里呢我们就可以开始使用者片共享内存了,
    //要注意的是所有的内存分配都要是在这片共享内存上

	//这里只看到在shpool上分配内存了,实际shpool还要调用ngx_slab_init初始化,
    //这个过程应该是系统里帮我们做了
	//比如下面的代码,就是分配一个红黑树的东西并做初始化,
    jtxy_cf->uri_shctx->rbtree = (ngx_http_jtxy_shctx_rbtree_t*)ngx_slab_alloc(
			jtxy_cf->uri_shctx->uri_shpool, sizeof(ngx_http_jtxy_shctx_rbtree_t));
    if (jtxy_cf->uri_shctx->rbtree == NULL) {
		ngx_log_error(NGX_LOG_EMERG, shm_zone->shm.log, 0,
                           "ngx_slab_alloc %V, ngx_http_sean_shctx_t failed");
        return NGX_ERROR;
    }


	//把分配出来的值存到uri_shpool得data上下文中,因为如果shm_zone->shm.exists存在,还要用的
	jtxy_cf->uri_shctx->uri_shpool->data = jtxy_cf->uri_shctx->rbtree;

	ngx_rbtree_init(&jtxy_cf->uri_shctx->rbtree->uri_rbtree
		, &jtxy_cf->uri_shctx->rbtree->uri_rbtree_sentinel, ngx_str_rbtree_insert_value);

	return NGX_OK;
}

我们看看这句代码

jtxy_cf->uri_shctx->uri_shpool = (ngx_slab_pool_t *)shm_zone->shm.addr;

        可以看到,这里并没有初始化shpool的ngx_slab_pool_t结构体,只是简单的把shm_zone->shm.addr赋值给了shpool,难道不用初始化shpool吗,其实初始化ngx_slab_pool_t的过程nginx已经做了。在ngx_init_zone_pool函数里ngx_init_cycle会调用ngx_init_zone_pool来完成。这里我们就只是保存一下,拿到他的指针而已。

if (shm_zone->shm.exists)这句判断是什么意思呢? 

如果这片共享内存池之前已经存在了,拿一下之前的共享内存上的shctx,为什么会有这段代码呢,因为我们创建的是有名的共享内存,即使nginx停掉了,这块内存还是存在的,等nginx重新启动后,拿到这片共享内存后就会在这里得到之前的上下文。

为什么会重用这个共享内存呢?

因为shm_zone->noreuse被设置为了0,这也nginx是默认设置(ngx_shared_memory_add里),它表明是否复用之前的共享内存。我们这里设置的是

shm_zone->noreuse = 1;  //每次重启都新建共享内存

可以不管if内的处理。

接下来就是调用ngx_slab_alloc和ngx_rbtree_init来创建一个在共享内存上的红黑树了。这个看看上面的代码就明白了。

因为是多进程共享这一棵树,那么使用时就需要加锁。这里没有slab上的锁操作,是因为目前还在初始化阶段,不涉及资源竞争的问题。

当需要使用这棵树时是这样的,这时就需要加锁了

ngx_shmtx_lock(&jtxy_cf->uri_shctx->uri_shpool->mutex);

uriinfo_node = ngx_str_rbtree_lookup(&jtxy_cf->uri_shctx->rbtree->uri_rbtree, token, hash);
...
...
...
ngx_shmtx_unlock(&jtxy_cf->uri_shctx->uri_shpool->mutex);
ngx_shmtx_lock(&jtxy_cf->uri_shctx->uri_shpool->mutex);

ngx_rbtree_insert(&jtxy_cf->uri_shctx->rbtree->uri_rbtree, &jtxyreqinfo->key_node.node);
...
...
...
ngx_shmtx_unlock(&jtxy_cf->uri_shctx->uri_shpool->mutex);

最后,我们可以通过ngx_slab_stat模块开查看slab内存的使用情况,如下,ngx_slab_stat是Tengine提供的一个模块,我们可以把他编译进nginx里使用,方便了我们查看slab的内容。

Hello World!root@DESKTOP-EMC868D:~# curl -v --noproxy "*" http://127.0.0.1:8881/slab_stat
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8881 (#0)
> GET /slab_stat HTTP/1.1
> Host: 127.0.0.1:8881
> User-Agent: curl/7.58.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< Server: nginx/1.20.0
< Date: Thu, 13 Jan 2022 08:02:09 GMT
< Content-Length: 2120
< Connection: keep-alive
< 
* shared memory: sean_share_data
total:          16(KB) free:           8(KB) size:           4(KB)
pages:           8(KB) start:00007FFFF7FF4000 end:00007FFFF7FF7000
slot:           8(Bytes) total:           0 used:           0 reqs:           0 fails:           0
slot:          16(Bytes) total:           0 used:           0 reqs:           0 fails:           0
slot:          32(Bytes) total:           0 used:           0 reqs:           0 fails:           0
slot:          64(Bytes) total:          64 used:           1 reqs:           1 fails:           0
slot:         128(Bytes) total:           0 used:           0 reqs:           0 fails:           0
slot:         256(Bytes) total:           0 used:           0 reqs:           0 fails:           0
slot:         512(Bytes) total:           0 used:           0 reqs:           0 fails:           0
slot:        1024(Bytes) total:           0 used:           0 reqs:           0 fails:           0
slot:        2048(Bytes) total:           0 used:           0 reqs:           0 fails:           0
* shared memory: jtxy_share_uri_data
total:          64(KB) free:          56(KB) size:           4(KB)
pages:          56(KB) start:00007FFFF7FE4000 end:00007FFFF7FF3000
slot:           8(Bytes) total:           0 used:           0 reqs:           0 fails:           0
slot:          16(Bytes) total:           0 used:           0 reqs:           0 fails:           0
slot:          32(Bytes) total:           0 used:           0 reqs:           0 fails:           0
slot:          64(Bytes) total:          64 used:           1 reqs:           1 fails:           0
slot:         128(Bytes) total:           0 used:           0 reqs:           0 fails:           0
slot:         256(Bytes) total:           0 used:           0 reqs:           0 fails:           0
slot:         512(Bytes) total:           0 used:           0 reqs:           0 fails:           0
slot:        1024(Bytes) total:           0 used:           0 reqs:           0 fails:           0
slot:        2048(Bytes) total:           0 used:           0 reqs:           0 fails:           0

我们看

slot:          64(Bytes) total:          64 used:           1 reqs:           1 fails:           0

 表示slot为64bytes的块有64个,使用了1个,请求了1次,失败0次

如果64个都使用完了呢,就会在分配一页出来给64bytes的slot使用。

total:          64(KB) free:          56(KB) size:           4(KB)
pages:          56(KB) start:00007FFFF7FE9000 end:00007FFFF7FF8000
slot:           8(Bytes) total:           0 used:           0 reqs:           0 fails:           0
slot:          16(Bytes) total:           0 used:           0 reqs:           0 fails:           0
slot:          32(Bytes) total:           0 used:           0 reqs:           0 fails:           0
slot:          64(Bytes) total:           0 used:           0 reqs:           0 fails:           0
slot:         128(Bytes) total:          32 used:          32 reqs:          32 fails:           0
slot:         256(Bytes) total:           0 used:           0 reqs:           0 fails:           0
slot:         512(Bytes) total:           0 used:           0 reqs:           0 fails:           0
slot:        1024(Bytes) total:           0 used:           0 reqs:           0 fails:           0
slot:        2048(Bytes) total:           0 used:           0 reqs:           0 fails:           0

 比如上面,128bytes的32个slot都使用完了,我们再请求分配会如何呢,如下

slot:           8(Bytes) total:           0 used:           0 reqs:           0 fails:           0
slot:          16(Bytes) total:           0 used:           0 reqs:           0 fails:           0
slot:          32(Bytes) total:           0 used:           0 reqs:           0 fails:           0
slot:          64(Bytes) total:           0 used:           0 reqs:           0 fails:           0
slot:         128(Bytes) total:          64 used:          33 reqs:          33 fails:           0
slot:         256(Bytes) total:           0 used:           0 reqs:           0 fails:           0
slot:         512(Bytes) total:           0 used:           0 reqs:           0 fails:           0
slot:        1024(Bytes) total:           0 used:           0 reqs:           0 fails:           0
slot:        2048(Bytes) total:           0 used:           0 reqs:           0 fails:           0

128bytes的slot变为了64个,使用了33个。可见系统又拿出一页来给128bytes的slot使用

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值