【好文推荐】
SLAB分配器的目的
在内核中,经常会发生对有限的几种数据结构频繁的分配内存和回收内存,比如进程描述符struct task_struct,索引节点对象struct inode等等。相比4KB或者8KB的物理页而言,这些对象往往较小,例如进程描述符一般只有1.7KB左右,索引节点对象则更小。那么对于这种高频发生的小数据结构的内存分配和回收,是否有可能进行优化呢?
计算机科学家Jeff Bonwick早已关注到这一点。同时他还发现,内核中普通对象进行初始化所需的时间超过了对其进行分配和释放所需的时间。于是他设想这样改进:不将内存释放回一个全局的内存池,而是将内存保持为针对特定目而初始化的状态。后续的内存分配不需要重复初始化,因为从上次释放和调用析构之后,它已经处于所需的状态中了。基于这些思路,slab分配器(slab allocator)应运而生。
slab分配器主要的功能就是对频繁分配和释放的小对象提供高效的内存管理。它的核心思想是实现一个缓存池,分配对象的时候从缓存池中取,释放对象的时候再放入缓存池。slab分配器是基于对象类型进行内存管理的,每一种对象被划分为一类,例如索引节点对象是一类,进程描述符又是一类,等等。每当需要申请一个特定的对象时,就从相应的类中分配一个空白的对象出去;当这个对象被使用完毕时,就重新“插入”到相应的类中(其实并不存在插入的动作,仅仅是将该对象重新标记为空闲而已)。
SLAB分配器的原理
slab分配器其实就是一个叫做Cache_chain的链表,链表的每个节点是一个struct kmem_cache类型的结构,而每个kmem_cache又由多个被称为slab的结构组成,每个slab包含若干个对象成员(这里的对象就是被缓存起来的数据结构,已经被初始化好,无需重复初始化)。一共有三种slab:满、半满、空。一个满的slab所有的对象都已经被分配,一个空的slab所有的对象都是空闲可用的,而一个半满的slab则是一部分对象空闲可用,另一部分已经被分配。如图所示,这三种slab被分别组织到了三个链表中:slabs_full,slabs_partial,slabs_empty。
如果有一个名叫inode_cache的struct kmem_cache节点,它存放了一些inode对象。当内核请求分配一个新的inode对象时,slab分配器就开始工作了:
- 首先要查看inode_cache的slabs_partial链表,如果slabs_partial非空,就从中选中一个slab,返回一个指向已分配但未使用的inode结构的指针。完事之后,如果这个slab满了,就把它从slabs_partial中删除,插入到slabs_full中去,结束;
- 如果slabs_partial为空,也就是没有半满的slab,就会到slabs_empty中寻找。如果slabs_empty非空,就选中一个slab,返回一个指向已分配但未使用的inode结构的指针,然后将这个slab从slabs_empty中删除,插入到slabs_partial(或者slab_full)中去,结束;
- 如果slabs_empty也为空,那么没办法,cache内存已经不足,只能新创建一个slab了。
SLAB分配器的优点
与传统的内存管理模式相比, slab 缓存分配器有很多优点。
1)可以大大减少内部碎片。
2)slab 分配器还支持通用对象的初始化,从而避免了为同一目而对一个对象重复进行初始化。
关于着色
看名字很难理解,其实又很好理解,我们知道内存需要处理时要先放入CPU硬件高速缓存中,而CPU硬件高速缓存与内存的映射方式有多种。在同一个kmem_cache中所有SLAB都是相同大小,都是相同连续长度的页框组成,这样的话在不同SLAB中相同对象号对于页框的首地址的偏移量也相同,这样有很可能导致不同SLAB中相同对象号的对象放入CPU硬件高速缓存时会处于同一行,当我们交替操作这两个对象时,CPU的cache就会交替换入换出,效率就非常差。SLAB着色就是在同一个kmem_cache中对不同的SLAB添加一个偏移量,就让相同对象号的对象不会对齐,也就不会放入硬件高速缓存的同一行中,提高了效率,如下图:
SLAB分配器的使用
创建SLAB描述符
分配SLAB对象
释放SLAB缓冲对象
查看SLABINFO
在SLAB分配器中将SLAB分为两大类:专用SLAB和普通SLAB。专用SLAB用于特定的场合(比如TCP有自己专用的SLAB,当TCP模块需要小内存时,会从自己的SLAB中分配),而普通SLAB就是用于常规分配的时候。我们可以使用命令查看SLAB的状态.
结果如下