原文:http://blog.chinaunix.net/u1/44250/showart.php?id=1721937
Linux 所使用的 slab 分配器的基础是 Jeff Bonwick 为 SunOS 操作系统首次引入的一种算法。 Jeff 的分配器是围绕对象缓存进行的。在内核中,会为有限的对象集(例如文件描述符和其他常见结构)分配大量内存。 Jeff 发 现对内核中普通对象进行初始化所需的时间超过了对其进行分配和释放所需的时间。因此他的结论是不应该将内存释放回一个全局的内存池,而是将内存保持为针对 特定目而初始化的状态。例如,如果内存被分配给了一个互斥锁,那么只需在为互斥锁首次分配内存时执行一次互斥锁初始化函数( mutex_init )即可。后续的内存分配不需要执行这个初始化函数,因为从上次释放和调用析构之后,它已经处于所需的状态中了。
Linux slab 分配器使用了这种思想和其他一些思想来构建一个在空间和时间上都具有高效性的内存分配器。
Slab 实现了一个 kmem_cache_t 类型的缓存。可以通过以下 4 个函数来操作
kmem_cache_create() 创建一个新缓存
kmem_cache_alloc() 分配对象
kmem_cache_free() 释放对象
kmem_cache_destroy() 缓存使用完后,卸载该模块
kmem_cache_create() 创建一个专用的高速缓存 , 包含了由内核使用的其余高速缓存的高速缓存描述符 ,cache_cache 变量包含第一个高速缓存的描述符
kmem_cache_destroy() 撤销一个高速缓存,并将它从 cache_chain 链表上删除,必须要在撤销高速缓存本身之前就撤销其所有的 slab 。 kmem_cache_shrink() 通过反复调用 slab_destroy() 撤销高速缓存中所有的 slab.
kmem_cache_create()
有以下标志:
SLAB_NO_REAP
通知 slab 不要在内存短缺时自动回收对象
SLAB_HWCACHE_ALIGN
通知 slab 层把一个 slab 内的所有对象按高速缓存行对齐
SLAB_MUST_HWCACHE_ALIGN
强制 slab 层缓存对齐对象
SLAB_POISON
使 slab 层用已知的值 a5a5a5a5 填充 slab ,有利于对未初始化内存的访问。
SLAB_RED_ZONE
在已分配的内存周万恶插入“红色警戒区”探测缓冲越界
SLAB_CACHE_DMA
使 slab 层使用可以执行的 DMA 的内存给每个 slab 分配空间
kmem_cache_create 执行过程如下:
1、 检查 name 字段是否为空,是否存在中断, size 是否 BYTES_PER_WORD 和 KMALLOC_MAX_SIZE 之间,并报告所得到的错误
2、 得到 cache 所在的 cache_chain
3、 用互斥锁锁住 cache_chain_mutex
4、 遍历每个 cache_chain 的入口,探测没有没加载的并且也没有被销毁的 slab 缓存,并且也没有再使用 vmalloc 申请空间的模块,输出警告信息。
5、 采用 BYTES_PER_WORD 格式化 size 实际分配的大小
6、 当标志位有 SLAB_HWCACHE_ALIGN 标志时,设置最小 ralign 值,否则,将 ralign 设置成一个无类型指针的大小
7、 当标志位中有 SLAB_STORE_USER 时,设置 ralign 为 BYTES_PER_WORD
8、 当标志位中有 SLAB_RED_ZONE 时,设置 ralign 为 REDZONE_ALIGN, 并将 size 的值按照 REDZONE_ALIGN 重新设置
9、 当 ralign < ARCH_SLAB_MINALIGN 时,设置 ralign 的值为 ARCH_SLAB_MINALIGN
10、 当 ralign < align 时,设置 ralign 的值为 align
11、 当 ralign 大于 __alignof__(unsigned long long) 时,标志位中去掉 SLAB_RED_ZONE 和 SLAB_STORE_USER
12、 存储 ralign 的值到 align
13、 使用 kmem_cache_zalloc ,实际是调用 kmem_cache_alloc 分配空间,将指针交给 cachep ,分配失败时,释放自旋锁和 cache 所在的 cache_chain 仍然返回 cachep 指针 ( 应该是个 null)
14、 当 size 大于 (PAGE_SIZE /8) 并且 slab 已经被初始化时设置 CFLGS_OFF_SLAB 标志
15、 设置 size 的大小为 ALIGN(size, align)
16、 将 left_over 设置为单个 slab 的的对象数量
17、 如果 cachep->num 为 0 时,从 cache_cache 中释放掉 cachep 并设置 cachep 为空,释放自旋锁和 cache 所在的 cache_chain 返回空指针
18、 设置 slab_size 为 ALIGN(cachep->num * sizeof(kmem_bufctl_t)+ sizeof(struct slab), align)
19、 如果 CFLGS_OFF_SLAB 在 flags 中,并且 left_over 大于 slab_size ,清除 CFLGS_OFF_SLAB ,设置 left_over 的值为大于 slab_size 的那部分
20、 当 flags 中仍然有 CFLGS_OFF_SLAB 时,重新设置 slab_size 的值为 cachep->num * sizeof(kmem_bufctl_t) + sizeof(struct slab);
21、 初始化 cachep, 并将其加入到 cache_chain 链表中,释放 cache 所在的 cache_chain 和互斥锁,返回 cachep
个人观点:第 1 、 3 步在最终剪裁的时需要进一步精简内核时或许可以去掉。
kmem_cache_create() 对应的释放函数为 kmem_cache_destroy() 释放过程如下 :
1、 得到 cache 所在的 cache_chain
2、 用互斥锁锁定当前 cache_chain
3、 删除 cache->next 指针
4、 当 __cache_shrink() 无法删除 cachap 上的所有节点时,报错,回滚上述操作
5、 当 cachep->flags 中没有 SLAB_DESTROY_BY_RCU 标志时,进行 RCU 的同步操作 , 调用 __kmem_cache_destroy(cachep) 销毁 cachep ,释放互斥锁和 cache 所在的 cache_chain
kmem_cache_init()
1、 当节点不为 0 时,设置 use_alien_caches , numa_platform 的值为 0
2、 初始化 initkmem_list3 数组,对下标不大于 MAX_NUMNODES 的 cache_cache 的节点链表设置为空
3、 设置所有的 kmem_list3 的链表大小相同
4、 定义在超过 32MB 的机器上只能使用更大的内存
5、 初始化 cache_cache
a) 创建一个 cache_cache
b) 创建一个用于 kmalloc 的缓存
c) 替换掉引导 head 数组
d) 替换掉引导队列的所有 kmem_list3
e) 重新设置 head 数组的大小为他们的最终大小
kmem_cache_init 和 kmem_cache_create 的区别在于 kmem_cache_create 只是初始化一个 cache ,而 kmem_cache_init 则是初始化整个 cache_chain.