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的一些结构体:
slab的函数调用:
关于slab的使用,我们在介绍nginx中共享内存的时候再去介绍吧,我们只需要知道在进程初始化时,ngx_init_cycle函数会调用ngx_init_zone_pool来初始化共享内存,然后在ngx_init_zone_pool函数中会调用ngx_slab_init来初始化slab内存池。随后,在进程中,我们就可以调用alloc与free来对共享内存进行操作了。
对于64位与32位系统,nginx里面默认的值是不一样的,我们看到数字可能会更好理解一点,所以我们就以32位来看,用实际的数字来说话!
查看图片附件
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; }
查看图片附件
相关文章推荐
- 使用Nginx的proxy_cache缓存功能取代Squid
- 使用Nginx的proxy_cache缓存功能取代Squid
- Nginx的proxy_cache缓存功能取代Squid
- 使用Nginx的proxy_cache缓存功能取代Squid
- nginx 反向代理,动静态请求分离,proxy_cache缓存及缓存清除
- 使用Nginx的proxy_cache缓存功能取代Squid
- Nginx配置proxy_cache后缓存不能生成缓存文件
- nginx缓存之proxy_cache遇到的坑
- 使用Nginx的proxy_cache缓存功能取代Squid
- nginx的proxy_cache缓存功能详解
- 使用Nginx的proxy_cache缓存功能取代Squid
- 使用Nginx的proxy_cache缓存功能取代Squid
- 使用Nginx的proxy_cache缓存功能取代Squid(转)
- 使用Nginx的proxy_cache缓存功能取代Squid
- 使用Nginx的proxy_cache缓存功能取代Squid
- Nginx缓存功能:Proxy_cache与fastcgi_cache区别
- 由cache"引起"的内存问题——记录Linux的Cache Memory(缓存内存)机制
- 使用Nginx的proxy_cache缓存功能取代Squid
- nginx中配置proxy_cache使用内存的方法
- 使用Nginx的proxy_cache缓存功能取代Squid