Linux内核中基于伙伴算法实现的分区页框分配器适合大块内存的请求,它所分配的内存区是以页框为基本单位的。对于内核中小块连续内存的请求,比如说几个字节或者几百个字节,如果依然分配一个页框来来满足该请求,那么这很明显就是一种浪费,即产生内部碎片(internal fragmentation)
为了解决小块内存的分配,Linux内核基于Solaris 2.4中的slab分配算法实现了自己的slab分配器。除此之外,slab分配器另一个主要功能是作为一个高速缓存,它用来存储内核中那些经常分配并释放的对象。
1.slab分配器的基本原理
slab分配器中用到了对象这个概念,所谓对象就是内核中的数据结构以及对该数据结构进行创建和撤销的操作。它的基本思想是将内核中经常使用的对象放到高速缓存中,并且由系统保持为初始的可利用状态。比如进程描述符,内核中会频繁对此数据进行申请和释放。当一个新进程创建时,内核会直接从slab分配器的高速缓存中获取一个已经初始化了的对象;当进程结束时,该结构所占的页框并不被释放,而是重新返回slab分配器中。如果没有基于对象的slab分配器,内核将花费更多的时间去分配、初始化以及释放一个对象。
slab分配器有以下三个基本目标:
1.减少伙伴算法在分配小块连续内存时所产生的内部碎片;
2.将频繁使用的对象缓存起来,减少分配、初始化和释放对象的时间开销。
3.通过着色技术调整对象以更好的使用硬件高速缓存;
2.slab分配器的结构
slab分配器为每种对象分配一个高速缓存,这个缓存可以看做是同类型对象的一种储备。每个高速缓存所占的内存区又被划分多个slab,每个slab是由一个或多个连续的页框组成。每个页框中包含若干个对象,既有已经分配的对象,也包含空闲的对象。slab分配器的大致组成图如下:
每个高速缓存通过kmem_cache结构来描述,这个结构中包含了对当前高速缓存各种属性信息的描述。所有的高速缓存通过双链表组织在一起,形成高速缓存链表cache_chain。每个kmem_cache结构中并不包含对具体slab的描述,而是通过kmem_list3结构组织各个slab。该结构的定义如下:
struct kmem_list3 {
struct list_head slabs_partial;
struct list_head slabs_full;
struct list_head slabs_free;
unsigned long free_objects;
unsigned int free_limit;
unsigned int colour_next;
spinlock_t list_lock;
struct array_cache *shared;
struct array_cache **alien;
unsigned long next_reap;
int free_touched;
};
可以看到,该结构将当前缓存中的所有slab分为三个集合:空闲对象的slab链表slabs_free,非空闲对象的slab链表slabs_full以及部分空闲对象的slab链表slabs_partial。每个slab有相应的slab描述符,即slab结构,它的定义如下:
struct slab {
struct list_head list;
unsigned long colouroff;
void *s_mem;
unsigned int inuse;
kmem_bufctl_t free;
unsigned short nodeid;
};
slab描述符中的list字段标明了当前slab处于三个slab链表的其中一个。我们将上述的slab分配器进行细化,可以得到下面的结构图:
3.高速缓存的分类
slab高速缓存分为两大类,普通高速缓存和专用高速缓存。普通高速缓存并不针对内核中特定的对象,它首先会为kmem_cache结构本身提供高速缓存,这类缓存保存在cache_cache变量中,该变量即代表的是cache_chain链表中的第一个元素;另一方面,它为内核提供了一种通用高速缓存。专用高速缓存是根据内核所需,通过指定具体的对象而创建。
3.1 普通高速缓存
slab分配器中kmem_cache是用来描述高速缓存的结构,因此它本身也需要slab分配器对其进行高速缓存。cache_cache变量保存着对高速缓存描述符的高速缓存。
static struct kmem_cache cache_cache = {
.batchcount = 1,
.limit = BOOT_CPUCACHE_ENTRIES,
.shared = 1,
.buffer_size = sizeof(struct kmem_cache),
.name = "kmem_cache",
};
slab分配器所提供的小块连续内存的分配是通过通用高速缓存实现的。通用高速缓存所提供的对象具有几何分布的大小,范围为32到131072字节。内核中提供了kmalloc()和kfree()两个接口分别进行内存的申请和释放。
3.2 专用高速缓存
内核为专用高速缓存的申请和释放提供了一套完整的接口,根据所传入的参数为具体的对象分配slab缓存。
高速缓存的申请和释放
kmem_cache_create()用于对一个指定的对象创建高速缓存。它从cache_cache普通高速缓存中为新的专有缓存分配一个高速缓存描述符,并把这个描述符插入到高速缓存描述符形成的cache_chain链表中。kmem_cache_destory()用于撤销一个高速缓存,并将它从cache_chain链表上删除。
slab的申请和释放
kmem_cache_alloc()在其参数所指定的高速缓存中分配一个slab。相反,kmem_cache_free()在其参数所指定的高速缓存中释放一个slab。