前言

这里主要是描述 nginx 中的 slab 内存分配相关 

slab 在很多的地方都有使用, 比如 linux, nginx, netty 等等 

主要的作用是 内存管理, 复用 

简略 nginx 中的 slab 的流程

# slab related
	void* poolPtr = malloc(2048);
	ngx_slab_pool_t *pool = (ngx_slab_pool_t *)poolPtr;
	ngx_slab_init(pool);

	void* allocatedPtr = ngx_slab_alloc(pool, 36);
	int x = 0;
	x += 1;
	poolPtr = allocatedPtr;


# 如果 sz 的 shift 为 ngx_slab_exactly_shift
    则使用 page->slab 来存储当前 page 的 n 个 entry 是否被使用
    bitmap 的数量为 1, 使用 page->slab 来表示
    前面 m 个 entry 为 bitmap 的位置, m 的计算方式为 (ngx_page_size / (1 << shift)) / ((1 << shift) * 8)
        (ngx_page_size / (1 << shift)) 为 entry 的数量
        每一个 entry 中存在 (1 << shift) 个 byte, 合计 ((1 << shift) * 8) bit
        因此需要的 m 为 (ngx_page_size / (1 << shift)) / ((1 << shift) * 8)
    在计算 bitmap[i] 的时候, n + 1 是因为当前申请了一块空间, 将这块额外的空间 置为 used
    page->slab 中存放的是 shift
# 如果 sz 的 shift < ngx_slab_exactly_shift
    则使用 page->bitmap 来存储当前 page 的 n 个 entry 是否被使用
    bitmap 的数量计算来自于 (ngx_page_size / (1 << shift)) / (8 * sizeof(uint_ptr))
# 如果 sz 的 shift > ngx_slab_exactly_shift
    如果是 shift > ngx_slab_exactly_shift, 则能够存放的 entry 数量必定小于等于 32
    page->slab 高32bit 存放的是 bitmap
    page->slab 低32bit 存放的是 shift


ngx_slab_exactly_shift = 6
ngx_page_size = 4096
page->slab 类型为 uint_ptr 长度为 64
ngx_slab_exactly_shift 为 6, 一个空间占用 64byte, uint_ptr 有 64 bit, 总共可以表示 4096 byte, 因此 ngx_slab_exactly_shift 为 6

nginx 中的 slab 空间的分配

1. 如果 size 大于 ngx_slab_max_size[为 1/2 * pageSize] 

直接以页为单位 来分配空间 

10 nginx 中的 slab_memory

2. 计算 shift 以及 slot 

shift 指代的是存储 size 的最小的 2的n次幂空间 的对数[即为n] 

slot 指代的是当前 slab 中存储当前 shift 对应的  slot 

假设需求空间为 27b, page->min_shift 为 3, 则存储 27b 的最小的 2的n次幂空间 为 32 

shift 为 5 

page->min_shift 为 3 表示最小以 8b 为单位申请空间, 那么这里的 slot 为 2

slot[0] 存放的是申请的 8b 的 page 列表, slot[1] 存放的是申请的 16b 的 page 列表, slot[2] 存放的是申请的 32b 的 page 列表

10 nginx 中的 slab_内存布局_02

3. 假设我们需要新申请一个 page 

3.1 假设 shift < ngx_slab_exact_shift[为6], 即 size < 64b 

那么此时维护当前 page 的 bitmap 需要使用前 n 个 entry[每一个 entry 占用 (2 << shift) byte]

当前 page 总共会有 (ngx_pagesize / (2 << shift)) 个 entry 

那么 n 为 ((ngx_pagesize / (2 << shift)) / ((2 << shift) * 8)) 

然后下面初始化 bitmap, 下面的 bitmap[0] ~ btimap[i] 的处理, 指代的是 bitmap 中前 (n + 1) 个 entry 已经被使用[因为 bitmap 本身需要占用这一部分的空间, 分配空间的时候不能分配这部分空间, 标记为已经使用] 

bitmap[i+1] - bitmap[map-1] 指代的是 n+1 以后的 entry 可用 

然后 下面 page-> slab 记录的是 shift, 更新 slot 的相关引用, 以及对应的统计 等等 

返回 bitmap 占用空间之后的第一个 entry 给调用方 

10 nginx 中的 slab_memory_03

画个图来配合理解一下 

10 nginx 中的 slab_slab_04

3.2 假设 shift = ngx_slab_exact_shift[为6], 即 size = 64b 

使用 page->slab 来维护当前 page 的 bitmap 

返回当前 page 的起始空间给调用方 

10 nginx 中的 slab_内存管理_05

3.3 假设 shift > ngx_slab_exact_shift[为6], 即 size > 64b

那么此时 能够存储的 entry 的数量一定是 <= 32, 因此使用 page->slab 的高 32bit 存放 bitmap, 低 32 bit 存放 shift 

返回当前 page 的起始空间给调用方 

10 nginx 中的 slab_memory_06

 4. 假设是基于已有的 page 进行空间分配  

4.1 假设 shift < ngx_slab_exact_shift[为6], 即 size < 64b 

轮询 bitmap 找到可以分配的空间的索引, 更新给定的bitmap[idx]为已经使用, 然后将给定的空间的地址返回回去 

如果当前 page 空间已经被全部使用, 从 slots[slot] 中 unlink 

10 nginx 中的 slab_memory_07

4.2 假设 shift = ngx_slab_exact_shift[为6], 即 size = 64b 

轮询 bitmap 找到可以分配的空间的索引, 更新给定的bitmap[idx]为已经使用, 然后将给定的空间的地址返回回去 

如果当前 page 空间已经被全部使用, 从 slots[slot] 中 unlink 

10 nginx 中的 slab_slab_08

4.3 假设 shift > ngx_slab_exact_shift[为6], 即 size > 64b

轮询 bitmap 找到可以分配的空间的索引, 更新给定的bitmap[idx]为已经使用, 然后将给定的空间的地址返回回去 

如果当前 page 空间已经被全部使用, 从 slots[slot] 中 unlink 

10 nginx 中的 slab_slab_09

nginx 中的 slab 空间的释放

1. 根据地址确定属于哪一个 page, 并且确定 page 的类型 

根据 p 的偏移进行计算 所在 page 的索引, 然后根据 page 中存储的类型信息确定类型 

10 nginx 中的 slab_nginx_10

2. 假设 类型为 SMALL, 即 size < 64b 

根据 p 计算 页内 偏移, 计算当前元素的 entry 的索引 

查询 bitmap 当前 entry 是否已经被释放 

如果还未被释放, 释放当前 entry, link 当前 page 到 slots[slot] 

如果当前 page 空间已经全部被释放, 归还 page 到 free, 更新 stats 

10 nginx 中的 slab_slab_11

3. 假设 类型为 EXACTLY, 即 size = 64b 

根据 p 计算 页内 偏移, 计算当前元素的 entry 的索引 

查询 bitmap 当前 entry 是否已经被释放 

如果还未被释放, 释放当前 entry, link 当前 page 到 slots[slot] 

如果当前 page 空间已经全部被释放, 归还 page 到 free, 更新 stats 

10 nginx 中的 slab_slab_12

4. 假设 类型为 BIG, 即 size > 64b 

根据 p 计算 页内 偏移, 计算当前元素的 entry 的索引 

查询 bitmap 当前 entry 是否已经被释放 

如果还未被释放, 释放当前 entry, link 当前 page 到 slots[slot] 

如果当前 page 空间已经全部被释放, 归还 page 到 free, 更新 stats 

10 nginx 中的 slab_nginx_13

4. 假设 类型为 PAGE, 即 size > pageSize/2 

10 nginx 中的 slab_内存布局_14

nginx 中 slab 的内存布局

pool 中内存边界为 [&pool, pool->end] 

n 为 ngx_pagesize_shift - pool->min_shift, 总共的 slot 的数量 

pages 为 ((pool->end - &pool) - (n * sizeof(ngx_slab_page_t)) - (n * sizeof(ngx_slab_stat_t))) / (ngx_page_size + sizeof(ngx_slab_page_t)) 

ngx_slab_pool_t 的元数据
n 个 slots
n 个 slot_page
pages 个 slot_stats
pages 个 page 存储真实的数据

10 nginx 中的 slab_内存布局_15