void* ngx_slab_alloc(ngx_slab_pool_t* pool, size_t size)
{
void* p;
// 进程间加锁保护
ngx_shmtx_lock(&pool->mutex);
// 申请内存块
p = ngx_slab_alloc_locked(pool, size);
// 进程间解锁
ngx_shmtx_unlock(&pool->mutex);
return p;
}
void* ngx_slab_alloc_locked(ngx_slab_pool_t* pool, size_t size)
{
size_t s;
uintptr_t p, m, mask, *bitmap;
ngx_uint_t i, n, slot, shift, map;
ngx_slab_page_t* page, * prev, * slots;
if (size > ngx_slab_max_size)
{
// 如果申请的共享内存大于2048字节
/*
设计说明:
因为申请的字节大于2048字节,因此直接分配一页(4096字节)
*/
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, ngx_cycle->log, 0,
"slab alloc: %uz", size);
// 获取空闲页
page = ngx_slab_alloc_pages(pool, (size >> ngx_pagesize_shift)
+ ((size % ngx_pagesize) ? 1 : 0));
if (page)
{
p = ngx_slab_page_addr(pool, page);
} else
{
p = 0;
}
goto done;
}
if (size > pool->min_size)
{
// 申请的字节大于pool->min_size(8字节)
shift = 1;
// 计算出偏移量
for (s = size - 1; s >>= 1; shift++) { /* void */ }
// 获取slot下标
slot = shift - pool->min_shift;
}
else
{
// 如果申请的字节数小于8字节,按8字节算
shift = pool->min_shift;
// slots下标为0
slot = 0;
}
pool->stats[slot].reqs++;
ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, ngx_cycle->log, 0,
"slab alloc: %uz slot: %ui", size, slot);
//
slots = ngx_slab_slots(pool);
page = slots[slot].next;
if (page->next != page)
{
// 已经初始化过slots
if (shift < ngx_slab_exact_shift)
{
// size 小于 128字节
// 提取当前页的 bitmap
bitmap = (uintptr_t *) ngx_slab_page_addr(pool, page);
// 计算bitmap数组的大小
/*
设计说明:
假设ngx_pagesize=4096字节,shift = 3
那么一页可以拆分512个8字节的slot块,即需要512bit来标记每个slot的使用情况(已使用为1,未使用为0)
512bit即64字节,每个bitmap单元需要4个字节,因此需要16个bitmap单元才能表示512个slot
map = 16
*/
map = (ngx_pagesize >> shift) / (8 * sizeof(uintptr_t));
// 遍历bitmap数组
for (n = 0; n < map; n++)
{
if (bitmap[n] != NGX_SLAB_BUSY)
{
// 当前bitmap[n]还有可用的slot
for (m = 1, i = 0; m; m <<= 1, i++)
{
if (bitmap[n] & m)
{
// 当前bit对应的slot块已经被使用,跳过
continue;
}
// 找到一个未使用的bit
// 设置当前bit为1,表示被占用
bitmap[n] |= m;
// 计算偏移量
/*
设计说明:
n表示bitmap的下标,每个bitmap可以表示 8 * sizeof(uintptr_t) 个slot
i 为当前 bitmap[n] 的偏移量
(n * 8 * sizeof(uintptr_t) + i) 表示使用到当前页的第几个slot
(n * 8 * sizeof(uintptr_t) + i) << shift 可以计算出相对于 bitmap 的偏移量
*/
i = (n * 8 * sizeof(uintptr_t) + i) << shift;
// 定位到当前slot
/*
设计说明:
i 是偏移的字节数
bitmap 是当前页起始位置
(uintptr_t) bitmap 将指针类型转成整数类型,方便进行偏移量计算
或者 p = (char*)bitmap + i 也是可以的
*/
p = (uintptr_t) bitmap + i;
// 记录使用
pool->stats[slot].used++;
if (bitmap[n] == NGX_SLAB_BUSY)
{
// 当前bitmap[n]管理的slot已经完全被使用完了
for (n = n + 1; n < map; n++)
{
// 当前页还有可以分配的slot
if (bitmap[n] != NGX_SLAB_BUSY)
{
goto done;
}
}
// 当前页上所有的slot已经被使用光了
// 从链表上将该页摘除
prev = ngx_slab_page_prev(page);
prev->next = page->next;
page->next->prev = page->prev;
// 标记该页属性
page->next = NULL;
page->prev = NGX_SLAB_SMALL;
}
goto done;
}
}
}
}
else if (shift == ngx_slab_exact_shift)
{
// 申请slot为128字节
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_prev(page);
prev->next = page->next;
page->next->prev = page->prev;
page->next = NULL;
page->prev = NGX_SLAB_EXACT;
}
p = ngx_slab_page_addr(pool, page) + (i << shift);
pool->stats[slot].used++;
goto done;
}
}
else
{
// 申请内存块大于128字节
/*
设计说明:
shift = 8(256字节),mask=0xFFFF0000
shift = 9(512字节),mask=0xFF0000
shift = 10(1024字节),mask=0xF0000
*/
mask = ((uintptr_t) 1 << (ngx_pagesize >> shift)) - 1;
mask <<= NGX_SLAB_MAP_SHIFT;
for (m = (uintptr_t) 1 << NGX_SLAB_MAP_SHIFT, i = 0;
m & mask;
m <<= 1, i++)
{
// 查找可用的slot
if (page->slab & m)
{
continue;
}
page->slab |= m;
if ((page->slab & NGX_SLAB_MAP_MASK) == mask)
{
// 表示当前页上slot已经全部用完
prev = ngx_slab_page_prev(page);
prev->next = page->next;
page->next->prev = page->prev;
page->next = NULL;
page->prev = NGX_SLAB_BIG;
}
p = ngx_slab_page_addr(pool, page) + (i << shift);
pool->stats[slot].used++;
goto done;
}
}
ngx_slab_error(pool, NGX_LOG_ALERT, "ngx_slab_alloc(): page is busy");
ngx_debug_point();
}
// 申请一页
page = ngx_slab_alloc_pages(pool, 1);
if (page)
{
if (shift < ngx_slab_exact_shift)
{
// 申请内存块小于128字节
// 页的开头内存作为bitmap
/*
设计说明:
每页的前面的n个slot不能用来分配给用户使用,他们是用来记录当前页上slot的使用情况的
每页上slot单元越多,需要记录slot的bitmap单元也就越多
目前最小的slot是8字节,一页可以分配512个slot,但是为了记录512个slot,那么需要512bit来记录,对应的字节是64字节
那么需要消耗8个slot单元来记录当前页上slot信息
每个bitmap单元是4个字节,即需要16个bitmap单元来记录slots信息
*/
bitmap = (uintptr_t *) ngx_slab_page_addr(pool, page);
// 计算一页中可以划分多少slot
/*
设计说明:
ngx_pagesize >> shift 计算每页可以划分多少个slot
每个slot的大小是 1 << shift
8bit 等于 1字节
该页上slot信息需要n个slot单元才能记录
*/
n = (ngx_pagesize >> shift) / ((1 << shift) * 8);
if (n == 0)
{
// 至少使用1个slot
n = 1;
}
/* "n" elements for bitmap, plus one requested */
/*
设计说明:
n + 1 表示需要使用n+1个slot单元来记录slot信息
为啥是n+1而不是n呢?
因为本次分配就要占据一个slot,所以是n+1
(8 * sizeof(uintptr_t) 表示每个 bitmap 可以记录32个slot信息
(n + 1) / (8 * sizeof(uintptr_t)) 表示记录slot需要占据bitmap单元的个数
即使是slot=8,那么当前页以记录信息为目的消耗的slot数目是8,那么一个bitmap也足够了
*/
for (i = 0; i < (n + 1) / (8 * sizeof(uintptr_t)); i++)
{
bitmap[i] = NGX_SLAB_BUSY;
}
/*
设计说明:
这里m的值就是为了将bitmap[i]前面 (n+1) bit全部设置为1
这是一种常用的位移操作,当物理公式记住就行,不需要理解
*/
m = ((uintptr_t) 1 << ((n + 1) % (8 * sizeof(uintptr_t)))) - 1;
// 更新bitmap单元
bitmap[i] = m;
// 计算bitmap单元的个数
map = (ngx_pagesize >> shift) / (8 * sizeof(uintptr_t));
for (i = i + 1; i < map; i++)
{
// 初始化为0
bitmap[i] = 0;
}
// 设置当前页的偏移量
page->slab = shift;
page->next = &slots[slot];
// 标记当前页是用于分配小于128字节的slot
page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_SMALL;
slots[slot].next = page;
// 新页可以为pool->stats[slot].total增加多少可用slot单元
pool->stats[slot].total += (ngx_pagesize >> shift) - n;
// 获取可用的内存节点
p = ngx_slab_page_addr(pool, page) + (n << shift);
// 更新userd
pool->stats[slot].used++;
goto done;
}
else if (shift == ngx_slab_exact_shift)
{
// 申请内存块等于128字节
/*
设计说明:
当申请内存块大小等于128字节的时候,使用page->slab来代替page
因为一页4096字节,至多可以划分出32个128字节大小的slot块
那么可以使用page->slab来记录slot的使用情况
对内存的使用很精细
*/
// 标记第一块slot已经被占用
page->slab = 1;
page->next = &slots[slot];
page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_EXACT;
slots[slot].next = page;
// 设置总数量增加32个slot
pool->stats[slot].total += 8 * sizeof(uintptr_t);
p = ngx_slab_page_addr(pool, page);
// 已使用数量自增
pool->stats[slot].used++;
goto done;
}
else
{
// 申请内存块大于128字节
/*
设计说明:
当申请内存块大小大于128字节的时候,使用page->slab来代替page
因为一页4096字节,至多可以划分出16个大于128字节大小的slot块
那么可以使用page->slab的[高16位]来记录slot的使用情况
((uintptr_t) 1 << NGX_SLAB_MAP_SHIFT) 高16位记录使用情况
低16位用来存储shift信息
备注:
32位机器上,NGX_SLAB_MAP_SHIFT=16,uintptr_t即unsigned int
*/
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;
pool->stats[slot].total += ngx_pagesize >> shift;
p = ngx_slab_page_addr(pool, page);
pool->stats[slot].used++;
goto done;
}
}
p = 0;
pool->stats[slot].fails++;
done:
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, ngx_cycle->log, 0,
"slab alloc: %p", (void *) p);
return (void *) p;
}
void* ngx_slab_calloc(ngx_slab_pool_t* pool, size_t size)
{
void *p;
ngx_shmtx_lock(&pool->mutex);
p = ngx_slab_calloc_locked(pool, size);
ngx_shmtx_unlock(&pool->mutex);
return p;
}
void* ngx_slab_calloc_locked(ngx_slab_pool_t* pool, size_t size)
{
void *p;
p = ngx_slab_alloc_locked(pool, size);
if (p) {
ngx_memzero(p, size);
}
return p;
}