您的位置:首页 > 运维架构 > Nginx

Nginx Proxy Cache的slab page内存缓存机制

2014-06-20 13:50 537 查看
参考:http://bollaxu.iteye.com/blog/875163

Nginx的内存缓存是通过slab pool来实现的,但是目前Nginx代码没有对http响应进行内存缓存。比如作为反向代理服务器时向后端获取的文件也只是缓存在磁盘里,而内存只是用来做索引。不过Nginx已经提供了内存缓存功能的函数,所以如果在其他地方有需要使用内存缓存的话,也可以通过修改代码来实现(当然,也可以用memory disk来实现内存缓存)。在Nginx的内存缓存机制中,最重要的结构就是ngx_slab_pool_t,里面存放了包括内存缓存的空间使用情况、位置映射以及缓存空间本身的几乎所有信息。先来看一下ngx_slab_pool_t吧。

C代码



typedef struct {

ngx_atomic_t lock; //mutex的锁

size_t min_size; //内存缓存obj最小的大小,一般是1个byte

size_t min_shift; //slab pool以shift来比较和计算所需分配的obj大小、

//每个缓存页能够容纳obj个数以及所分配的页在缓存空间的位置

ngx_slab_page_t *pages; //slab page空间的开头

ngx_slab_page_t free; //如果要分配新的页,就从free.next开始

u_char *start; //实际缓存obj的空间的开头

u_char *end; //整个缓存空间的结尾

ngx_shmtx_t mutex; //互斥锁

u_char *log_ctx;

u_char zero;

void *data;

void *addr; //指向ngx_slab_pool_t的开头

} ngx_slab_pool_t;

C代码



struct ngx_slab_page_s {

uintptr_t slab; //多种情况,多个用途

//当需要分配新的页的时候,slab表示剩余页的数量

//当分配某些大小的obj的时候(一个缓存页存放多个obj),slab表

//示被分配的缓存的占用情况(是否空闲),以bit位来表示

ngx_slab_page_t *next; //在分配较小obj的时候,next指向slab page在pool->pages的位置

uintptr_t prev;

};

注意,在ngx_slab_pool_t里面有两种类型的slab page,虽然都是ngx_slab_page_t定义的结构,但是功能不尽相同。一种是slots,用来表示存放较小obj的内存块(如果页大小是4096B,则是<2048B的obj,即小于1/2页),另一种来表示所要分配的空间在缓存区的位置。Nginx把缓存obj分成大的(>=2048B)和小的(<2048B)。每次给大的obj分配一整个页,而把多个小obj存放在一个页中间,用bitmap等方法来表示其占用情况。而小的obj又分为3种:小于128B,等于128B,大于128B且小于2048B。其中小于128B的obj需要在实际缓冲区额外分配bitmap空间来表示内存使用情况(因为slab成员只有4个byte即32bit,一个缓存页4KB可以存放的obj超过32个,所以不能用slab来表示),这样会造成一定的空间损失。等于或大于128B的obj因为可以用一个32bit的整形来表示其状态,所以就可以直接用slab成员。每次分配的空间是2^n,最小是8byte,8,16,32,64,128,256,512,1024,2048。小于2^i且大于2^(i-1)的obj会被分配一个2^i的空间,比如56byte的obj就会分配一个64byte的空间。

先看一下初始化slab pool的函数,在ngx_init_cycle()中调用的ngx_init_zone_pool()中被调用

C代码



void ngx_slab_init(ngx_slab_pool_t *pool)

{

//假设每个page是4KB

//设置ngx_slab_max_size = 2048B。如果一个页要存放多个obj,则obj size要小于这个数值

//设置ngx_slab_exact_size = 128B。分界是否要在缓存区分配额外空间给bitmap

//ngx_slab_exact_shift = 7,即128的位表示

//...

//pool->min_shift = 3

//最小分配的空间是8byte

pool->min_size = 1 << pool->min_shift;

//这些slab page是给大小为8,16,32,64,128,256,512,1024,2048byte的内存块

//这些slab page的位置是在pool->pages的前面

//初始化

p = (u_char *) pool + sizeof(ngx_slab_pool_t);

slots = (ngx_slab_page_t *) p;

n = ngx_pagesize_shift - pool->min_shift;

for (i = 0; i < n; i++) {

slots[i].slab = 0;

slots[i].next = &slots[i];

slots[i].prev = 0;

}

//跳过上面那些slab page

p += n * sizeof(ngx_slab_page_t);

//**计算这个空间总共可以分配的缓存页(4KB)的数量,每个页的overhead是一个slab page的大小

//**这儿的overhead还不包括之后给<128B物体分配的bitmap的损耗

pages = (ngx_uint_t) (size / (ngx_pagesize + sizeof(ngx_slab_page_t)));

//把每个缓存页对应的slab page归0

ngx_memzero(p, pages * sizeof(ngx_slab_page_t));

//pool->pages指向slab page的头

pool->pages = (ngx_slab_page_t *) p;

//初始化free,free.next是下次分配页时候的入口

pool->free.prev = 0;

pool->free.next = (ngx_slab_page_t *) p;

//更新第一个slab page的状态,这儿slab成员记录了整个缓存区的页数目

pool->pages->slab = pages;

pool->pages->next = &pool->free;

pool->pages->prev = (uintptr_t) &pool->free;

//实际缓存区(页)的开头,对齐

pool->start = (u_char *)ngx_align_ptr((uintptr_t) p + pages * sizeof(ngx_slab_page_t), ngx_pagesize);

//根据实际缓存区的开始和结尾再次更新内存页的数目

m = pages - (pool->end - pool->start) / ngx_pagesize;

if (m > 0) {

pages -= m;

pool->pages->slab = pages;

}

//...

}

下面来看一下需要分配缓存空间时调用的函数,由于是共享内存,所以在进程间需要用锁来保持同步

C代码



void * ngx_slab_alloc(ngx_slab_pool_t *pool, size_t size)

{

//spinlock获取锁

ngx_shmtx_lock(&pool->mutex);

p = ngx_slab_alloc_locked(pool, size);

//解锁

ngx_shmtx_unlock(&pool->mutex);

return p;

}

C代码



//返回的值是所要分配的空间在内存缓存区的位置

void * ngx_slab_alloc_locked(ngx_slab_pool_t *pool, size_t size)

{

//这儿假设page_size是4KB

//如果是large obj, size >= 2048B

if(...){

//分配1个或多个内存页

page = ngx_slab_alloc_pages(pool, (size + ngx_pagesize - 1) >> ngx_pagesize_shift);

//返回指向内存缓存页的位置,这儿slab page的位置与所要返回的缓存页的位置是对应的

p = (page - pool->pages) << ngx_pagesize_shift;

p += (uintptr_t) pool->start;

//done, return p

//...

}

//较小的obj, size < 2048B

//根据需要分配的size来确定在slots的位置,每个slot存放一种大小的obj的集合,如slots[0]表示8byte的空间,slots[3]表示64byte的空间

//如果obj过小(<1B),slot的位置是1B空间的位置,即最小分配1B

//...

//如果之前已经有此类大小obj且那个已经分配的内存缓存页还未满

if(...){

//小obj,size < 128B,更新内存缓存页中的bitmap,并返回待分配的空间在缓存的位置

//...

//size == 128B,因为一个页可以放32个,用slab page的slab成员来标注每块内存的占用情况,不需要另外在内存缓存区分配bitmap,并返回待分配的空间在缓存的位置

//...

//size > 128B,也是更新slab page的slab成员,但是需要预先设置slab的部分bit,因为一个页的obj数量小于32个,并返回待分配的空间在缓存的位置

//...

}

//此前没有此类大小的obj或者之前的页已经满了,分配一个新的页,page是新的页相应的slab page

page = ngx_slab_alloc_pages(pool, 1);

//小obj,size < 128B,更新内存缓存页中的bitmap,并返回待分配的空间在缓存的位置(跳过bitmap的位置)

//...

//size == 128B,更新slab page的slab成员(即页中的每个相同大小空间的占用情况),并返回待分配的空间在缓存的位置

//...

//size > 128B,更新slab page的slab成员(即页中的每个相同大小空间的占用情况),并返回待分配的空间在缓存的位置

//...

}

C代码



//返回一个slab page,这个slab page之后会被用来确定所需分配的空间在内存缓存的位置

static ngx_slab_page_t * ngx_slab_alloc_pages(...)

{

//从pool->free.next开始,每次取(slab page) page = page->next

for(;;){

//本个slab page剩下的缓存页数目>=需要分配的缓存页数目N

if(...){

//更新从本个slab page开始往下第N个slab page的缓存页数目为本个slab page数目减去N

//N为需要分配的缓存页数目

//更新pool->free.next,下次从第N个slab page开始

//...

//更新被分配的page slab中的第一个的slab成员,即页的个数和占用情况

page->slab = pages | NGX_SLAB_PAGE_START;

//...

//如果分配的页数N>1,更新后面page slab的slab成员为NGX_SLAB_PAGE_BUSY

//...

return page;

}

}

//没有找到空余的页

return NULL;

}

附图

1. ngx_slab_alloc_pages图例:



2. 小物体bitmap图例:



3. slab page和缓存页的映射:





二:
http://www.cnblogs.com/fll369/archive/2012/11/26/2789704.html
slab的一些结构体:




typedef struct {
ngx_atomic_t      lock; // 锁,因为slab在nginx中一般配合共享内存使用

size_t            min_size; // 分配空间的最小值
size_t            min_shift;    // 该最小值对应的移位数

ngx_slab_page_t  *pages; // 页数组
ngx_slab_page_t   free; // 空闲的页

u_char           *start;    // 分配地址开始地址
u_char           *end;

ngx_shmtx_t       mutex;

u_char           *log_ctx;
u_char            zero;

void             *data;
void             *addr;
} ngx_slab_pool_t;

// 页结构体
struct ngx_slab_page_s {
uintptr_t         slab;     // 保存当前页的一些信息
ngx_slab_page_t  *next;// 下一个
uintptr_t         prev;// 上一个
};





slab的函数调用:




// 初始化slab池
void ngx_slab_init(ngx_slab_pool_t *pool);
// 未加锁的
void *ngx_slab_alloc(ngx_slab_pool_t *pool, size_t size);
// 在调用前已加锁,分配指定大小空间
void *ngx_slab_alloc_locked(ngx_slab_pool_t *pool, size_t size);
void ngx_slab_free(ngx_slab_pool_t *pool, void *p);
// 释放空间
void ngx_slab_free_locked(ngx_slab_pool_t *pool, void *p);





关于slab的使用,我们在介绍nginx中共享内存的时候再去介绍吧,我们只需要知道在进程初始化时,ngx_init_cycle函数会调用ngx_init_zone_pool来初始化共享内存,然后在ngx_init_zone_pool函数中会调用ngx_slab_init来初始化slab内存池。随后,在进程中,我们就可以调用alloc与free来对共享内存进行操作了。

对于64位与32位系统,nginx里面默认的值是不一样的,我们看到数字可能会更好理解一点,所以我们就以32位来看,用实际的数字来说话!

// 页大小
ngx_pagesize: 4096
// 页大小对应的移位数
ngx_pagesize_shift: 12
// slab的一次最大分配空间,默认为pagesize/2
ngx_slab_max_size: 2048
// slab精确分配大小,这个是一个分界点,通常是4096/32,为什么会这样,我们后面会有介绍
ngx_slab_exact_size: 128
// slab精确分配大小对应的移位数
ngx_slab_exact_shift: 7

ngx_slab_exact_size这个时依赖slab的分配算法.它的值是这样来的.4096/32,2048是slab页大小,而32是一个int的位数,最后的值是128。
why? 我们在分配时,在一页中,我们可以将这一页分成多个块,而某个块需要标记是否被分配,而一页空间正好被分成32个128字节大小的块,于是我们可以用一个int的32位表示这块的使用情况,而此时,我们是使用ngx_slab_page_s结构体中的slab成员来表示块的使用情况的。另外,在分配大于128与小于128时,表示块的占用情况有所有同





// 初始化
void
ngx_slab_init(ngx_slab_pool_t *pool)
{
u_char           *p;
size_t            size;
ngx_int_t         m;
ngx_uint_t        i, n, pages;
ngx_slab_page_t  *slots;

/* STUB */
if (ngx_slab_max_size == 0) {
// 最大分配空间为页大小的一半
ngx_slab_max_size = ngx_pagesize / 2;
// 精确分配大小,8为一个字节的位数,sizeof(uintptr_t)为一个uintptr_t的字节,我们后面会根据这个size来判断使用不同的分配算法
ngx_slab_exact_size = ngx_pagesize / (8 * sizeof(uintptr_t));
// 计算出此精确分配的移位数
for (n = ngx_slab_exact_size; n >>= 1; ngx_slab_exact_shift++) {
/* void */
}
}
/**/

pool->min_size = 1 << pool->min_shift;

// p 指向slot数组
p = (u_char *) pool + sizeof(ngx_slab_pool_t);
size = pool->end - p;

// 将开始的size个字节设置为0
ngx_slab_junk(p, size);

// 某一个大小范围内的页,放到一起,具有相同的移位数
slots = (ngx_slab_page_t *) p;
// 最大移位数,减去最小移位数,得到需要的slot数量
// 默认为8
n = ngx_pagesize_shift - pool->min_shift;

// 初始化各个slot
for (i = 0; i < n; i++) {
slots[i].slab = 0;
slots[i].next = &slots[i];
slots[i].prev = 0;
}

// 指向页数组
p += n * sizeof(ngx_slab_page_t);

// 计算出当前内存空间可以放下多少个页,此时的计算没有进行对齐,在后面会进行调整
pages = (ngx_uint_t) (size / (ngx_pagesize + sizeof(ngx_slab_page_t)));

ngx_memzero(p, pages * sizeof(ngx_slab_page_t));

pool->pages = (ngx_slab_page_t *) p;

pool->free.prev = 0;
pool->free.next = (ngx_slab_page_t *) p;

pool->pages->slab = pages;
pool->pages->next = &pool->free;
pool->pages->prev = (uintptr_t) &pool->free;

// 计算出对齐后的返回内存的地址
pool->start = (u_char *)
ngx_align_ptr((uintptr_t) p + pages * sizeof(ngx_slab_page_t),
ngx_pagesize);

// 用于判断我们对齐后的空间,是否需要进行调整
m = pages - (pool->end - pool->start) / ngx_pagesize;
// 说明之前是没有对齐过的,由于对齐之后,最后那一页,有可能不够一页,所以要去掉那一块
if (m > 0) {
pages -= m;
pool->pages->slab = pages;
}

pool->log_ctx = &pool->zero;
pool->zero = '\0';
}








void *
ngx_slab_alloc_locked(ngx_slab_pool_t *pool, size_t size)
{
size_t            s;
uintptr_t         p, n, m, mask, *bitmap;
ngx_uint_t        i, slot, shift, map;
ngx_slab_page_t  *page, *prev, *slots;

// 如果超出slab最大可分配大小,即大于2048,则我们需要计算出需要的page数,
// 然后从空闲页中分配出连续的几个可用页
if (size >= ngx_slab_max_size) {

// 计算需要的页数,然后分配指针页数
page = ngx_slab_alloc_pages(pool, (size + ngx_pagesize - 1)
>> ngx_pagesize_shift);
if (page) {
// 由返回page在页数组中的偏移量,计算出实际数组地址的偏移量
p = (page - pool->pages) << ngx_pagesize_shift;
// 计算出实际的数据地址
p += (uintptr_t) pool->start;

} else {
p = 0;
}

goto done;
}

// 如果小于2048,则启用slab分配算法进行分配

// 计算出此size的移位数以及此size对应的slot以及移位数
if (size > pool->min_size) {
shift = 1;
// 计算移位数
for (s = size - 1; s >>= 1; shift++) { /* void */ }
// 由移位数得到slot
slot = shift - pool->min_shift;

} else {
// 小于最小可分配大小的都放到一个slot里面
size = pool->min_size;
shift = pool->min_shift;
// 因为小于最小分配的,所以就放在第一个slot里面
slot = 0;
}

slots = (ngx_slab_page_t *) ((u_char *) pool + sizeof(ngx_slab_pool_t));
// 得到当前slot所占用的页
page = slots[slot].next;

// 找到一个可用空间
if (page->next != page) {

// 分配大小小于128字节时的算法,看不懂的童鞋可以先看等于128字节的情况
// 当分配空间小于128字节时,我们不可能用一个int来表示这些块的占用情况
// 此时,我们就需要几个int了,即一个bitmap数组
// 我们此时没有使用page->slab,而是使用页数据空间的开始几个int空间来表示了
// 看代码

if (shift < ngx_slab_exact_shift) {

do {
// 得到页数据部分
p = (page - pool->pages) << ngx_pagesize_shift;
// 页的开始几个int大小的空间来存放位图数据
bitmap = (uintptr_t *) (pool->start + p);

// 当前页,在当前size下可分成map*32个块
// 我们需要map个int来表示这些块空间
map = (1 << (ngx_pagesize_shift - shift))
/ (sizeof(uintptr_t) * 8);

for (n = 0; n < map; n++) {

if (bitmap
!= NGX_SLAB_BUSY) {

for (m = 1, i = 0; m; m <<= 1, i++) {
if ((bitmap
& m)) {
// 当前位表示的块已被使用了
continue;
}

// 设置已占用
bitmap
|= m;

i = ((n * sizeof(uintptr_t) * 8 ) << shift)
+ (i << shift);

// 如果当前bitmap所表示的空间已都被占用,就查找下一个bitmap
if (bitmap
== NGX_SLAB_BUSY) {
for (n = n + 1; n < map; n++) {
// 找到下一个还剩下空间的bitmap
if (bitmap
!= NGX_SLAB_BUSY) {
p = (uintptr_t) bitmap + i;

goto done;
}
}

// 剩下所有的bitmap都被占用了,表明当前的页已完全被使用了,把当前页从链表中删除
prev = (ngx_slab_page_t *)
(page->prev & ~NGX_SLAB_PAGE_MASK);
prev->next = page->next;
page->next->prev = page->prev;

page->next = NULL;
// 小内存分配
page->prev = NGX_SLAB_SMALL;
}

p = (uintptr_t) bitmap + i;

goto done;
}
}
}

page = page->next;

} while (page);

} else if (shift == ngx_slab_exact_shift) {
// 如果分配大小正好是128字节,则一页可以分成32个块,我们可以用一个int来表示这些个块的使用情况
// 这里我们使用page->slab来表示这些块的使用情况,当所有块被占用后,该值就变成了0xffffffff,即NGX_SLAB_BUSY
// 表示该块都被占用了

do {
// 当前页可用
if (page->slab != NGX_SLAB_BUSY) {

for (m = 1, i = 0; m; m <<= 1, i++) {
// 如果当前位被使用了,就继续查找下一块
if ((page->slab & m)) {
continue;
}

// 设置当前为已被使用
page->slab |= m;

// 最后一块也被使用了,就表示此页已使用完
if (page->slab == NGX_SLAB_BUSY) {
// 将当前页从链表中移除
prev = (ngx_slab_page_t *)
(page->prev & ~NGX_SLAB_PAGE_MASK);
prev->next = page->next;
page->next->prev = page->prev;

page->next = NULL;
// 标识使用类型,精确
page->prev = NGX_SLAB_EXACT;
}

p = (page - pool->pages) << ngx_pagesize_shift;
p += i << shift;
p += (uintptr_t) pool->start;

goto done;
}
}

// 查找下一页
page = page->next;

} while (page);

} else { /* shift > ngx_slab_exact_shift */
// 当需要分配的空间大于128时,我们可以用一个int的位来表示这些空间
//所以我们依然采用跟等于128时类似的情况,用page->slab来表示
// 但由于 大于128的情况比较多,移位数分别为8、9、10、11这些情况
// 对于一个页,我们如何来知道这个页的分配大小呢?
// 而我们知道,最小我们只需要使用16位即可表示这些空间了,即分配大小为256~512时
// 那么我们采用高16位来表示这些空间的占用情况
// 而最低位,我们也利用起来,表示此页的分配大小,即保存移位数
// 比如我们分配256,当分配第一个空间时,此时的page->slab位图情况是:0x0001008
// 那分配下一空间就是0x0003008了,当为0xffff008时,就分配完了
// 看代码

// page->slab & NGX_SLAB_SHIFT_MASK 即得到最低一位的值,其实就是当前页的分配大小的移位数
// ngx_pagesize_shift减掉后,就是在一页中标记这些块所需要的移位数,也就是块数对应的移位数
n = ngx_pagesize_shift - (page->slab & NGX_SLAB_SHIFT_MASK);
// 得到一个页面所能放下的块数
n = 1 << n;
// 得到表示这些块数都用完的bitmap,用现在是低16位的
n = ((uintptr_t) 1 << n) - 1;
// 将低16位转换成高16位,因为我们是用高16位来表示空间地址的占用情况的
mask = n << NGX_SLAB_MAP_SHIFT;

do {
// 判断高16位是否全被占用了
if ((page->slab & NGX_SLAB_MAP_MASK) != mask) {

// NGX_SLAB_MAP_SHIFT 为移位偏移, 得到0x10000
for (m = (uintptr_t) 1 << NGX_SLAB_MAP_SHIFT, i = 0;
m & mask;
m <<= 1, i++)
{
// 当前块是否被占用
if ((page->slab & m)) {
continue;
}

// 将当前位设置成1
page->slab |= m;

// 当前页是否完全被占用完
if ((page->slab & NGX_SLAB_MAP_MASK) == mask) {
prev = (ngx_slab_page_t *)
(page->prev & ~NGX_SLAB_PAGE_MASK);
prev->next = page->next;
page->next->prev = page->prev;

page->next = NULL;
page->prev = NGX_SLAB_BIG;
}

p = (page - pool->pages) << ngx_pagesize_shift;
p += i << shift;
p += (uintptr_t) pool->start;

goto done;
}
}

page = page->next;

} while (page);
}
}

// 如果当前slab对应的page中没有空间可分配了,则重新从空闲page中分配一个页
page = ngx_slab_alloc_pages(pool, 1);

if (page) {
if (shift < ngx_slab_exact_shift) {
// 小于128时
p = (page - pool->pages) << ngx_pagesize_shift;
bitmap = (uintptr_t *) (pool->start + p);

// 需要的空间大小
s = 1 << shift;
n = (1 << (ngx_pagesize_shift - shift)) / 8 / s;

if (n == 0) {
n = 1;
}

bitmap[0] = (2 << n) - 1;

// 需要使用的
map = (1 << (ngx_pagesize_shift - shift)) / (sizeof(uintptr_t) * 8);

for (i = 1; i < map; i++) {
bitmap[i] = 0;
}

page->slab = shift;
page->next = &slots[slot];
page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_SMALL;

slots[slot].next = page;

p = ((page - pool->pages) << ngx_pagesize_shift) + s * n;
p += (uintptr_t) pool->start;

goto done;

} else if (shift == ngx_slab_exact_shift) {

// 第一块空间被占用
page->slab = 1;
page->next = &slots[slot];
page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_EXACT;

slots[slot].next = page;

p = (page - pool->pages) << ngx_pagesize_shift;
p += (uintptr_t) pool->start;

goto done;

} else { /* shift > ngx_slab_exact_shift */

// 低位表示存放数据的大小
page->slab = ((uintptr_t) 1 << NGX_SLAB_MAP_SHIFT) | shift;
page->next = &slots[slot];
page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_BIG;

slots[slot].next = page;

p = (page - pool->pages) << ngx_pagesize_shift;
p += (uintptr_t) pool->start;

goto done;
}
}

p = 0;

done:

return (void *) p;
}








/ 分配指针个数的页
static ngx_slab_page_t *
ngx_slab_alloc_pages(ngx_slab_pool_t *pool, ngx_uint_t pages)
{
ngx_slab_page_t  *page, *p;

for (page = pool->free.next; page != &pool->free; page = page->next) {

if (page->slab >= pages) {

// 当前可以分配
if (page->slab > pages) {
// 空闲的pages大于需要分配的pages

// 减少
page[pages].slab = page->slab - pages;
// 这几个页就分配出去了哦!
page[pages].next = page->next;
page[pages].prev = page->prev;

p = (ngx_slab_page_t *) page->prev;
p->next = &page[pages];
page->next->prev = (uintptr_t) &page[pages];

} else {
// slab == pages
// 正好分配完

// 设置free链表
p = (ngx_slab_page_t *) page->prev;
p->next = page->next;
page->next->prev = page->prev;
}

page->slab = pages | NGX_SLAB_PAGE_START;
page->next = NULL;
page->prev = NGX_SLAB_PAGE;

if (--pages == 0) {
return page;
}

for (p = page + 1; pages; pages--) {
p->slab = NGX_SLAB_PAGE_BUSY;
p->next = NULL;
p->prev = NGX_SLAB_PAGE;
p++;
}

return page;
}
}

return NULL;
}





查看图片附件
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: