分类: LINUX
2008-12-15 15:44:13
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层用已知的值a
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.