linux中的伙伴系统内存分配器是以页为最小粒度来进行内存分配。在实际的应用中,内核存储的大多数obj(如strcut task_strcuct,struct inode等数据结构)往往只需要几字节到几百字节不等。如果这些内核object的内存申请和释放都通过伙伴系统进行管理,会有以下缺点:
  1. 内存资源浪费,页内部碎片化严重
  2. 内存分配效率低

比如内核用伙伴系统为一个128字节的obj分配内存,内核不得不分配一个4k的page来存储该object,该页剩余的3972字节将会被闲置,内存资源被浪费。而且伙伴系统在释放一个内存块后,会立即将该内存块放在对应的freelist上,且该内存块还有可能与freelist上的相邻内存块合并成高一阶的内存块,若此时内核马上又要使用该内存块,则还需要再次从伙伴系统的freelist上去申请对应内存块,这种操作效率是很低的(未合理使用cpu高速缓存)。

为解决上面小块内存分配时遇到的问题,linux内核提出了slab分配机制,该内存分配器不是按页进行内存分配,而是按字节来分配的。

1 slab分配器原理

slab分配器中用到了对象这个概念,所谓对象就是内核中的数据结构以及对该数据结构进行创建和撤销的操作。它的基本思想是将内核中经常使用的对象放到slab缓存中,并且由系统保持为初始的可利用状态。比如进程描述符,内核中会频繁对此数据进行申请和释放。当一个新进程创建时,内核会直接从slab分配器的slab缓存中获取一个已经初始化了的对象;当进程结束时,该结构所占的页框并不被释放,而是重新返回slab分配器中。如果没有基于对象的slab分配器,内核将花费更多的时间去分配、初始化以及释放一个对象。

slab分配器把对象分组放进slab缓存。每个slab缓存都是同种类型对象的一种“储备”。一个slab cache管理一组大小固定的内存块(也称为对象实体),每个内存块都可用作一种数据结构。slab cache中的内存块来自一到多个slab。一个slab来自物理内存管理器的一到多个物理页,该slab被分成一组固定大小的块,被称为slab对象(object),一个slab属于一个slab cache,其中的对象就是该slab cache所管理的固定大小的内存块。所以一个slab cache可以有一到多个slab。

在基于slab的内核内存管理器中,基本的概念是保存管理型数据的缓存(即slab cache)和保存被管理对象的各个slab。每个缓存都负责一种对象类型,比如kmalloc-128缓存负责管理65-128字节内存空间的的分配。系统中的所有缓存类型都保存在一个链表slab_caches中。下图给出了slab分配器的各个组成部分间的相互关系:

slab分配器有以下三个基本目标:

  1. 减少伙伴算法在分配小块连续内存时所产生的内部碎片;
  2. 将频繁使用的对象缓存起来,减少分配、初始化和释放对象的时间开销。
  3. 通过着色技术调整对象以更好的使用硬件高速缓存;
2 slab分配器重要数据结构以及组织关系

2.1 slab cache描述符struct kmem_cache

在slab分配器中slab cache的描述符为struct kmem_cache,它是该机制的核心数据结构。

在slab分配器中,最顶层结构为slab cache,其描述符为struct kmem_cache.linux中每个slab obj对应的slab cache都有自己的名字(如:ext4_inode_cache,kmalloc-8和kmalloc-16等).

struct kmem_cache用于描述一种slab cache,并管理着一个或多个该slab中的的所有obj。linux中所有的kmem_cache会保持在一个以slab_caches作为头节点的链表中。kmem_cache代码定义如下所示。

// mm_slab_def.h
/*
 * Definitions unique to the original Linux SLAB allocator.
 */

struct kmem_cache {
    //本地高速缓存,每CPU结构,对象释放时,优先放入这个本地CPU高速缓存中
	struct array_cache __percpu *cpu_cache;
/* 1) Cache tunables. Protected by slab_mutex */
    /*batchcount指定了在per-CPU变量中entry列表为空的情况下,从缓存的slab中获取对象的数目。它还表示在
     *缓存增长时,若entry数组中obj数量超过limit限制后,需要从entry数组中迁移走的obj数量(到本地节点cpu共享空闲链
     *表中)。
     */
	unsigned int batchcount;
    //本地高速缓存中entry数组中空闲obj的最大数目
	unsigned int limit;
    //CPU共享高速缓存标志,实际地址保存在kmem_cache_node结构中
	unsigned int shared;
	//对象长度 + 填充字节
	unsigned int size;
	struct reciprocal_value reciprocal_buffer_size;
/* 2) touched by every alloc & free from the backend */
	//属性的标识,如果SLAB管理结构放在外部,则CFLAGS_OFF_SLAB置1
	unsigned int flags;		/* constant flags */
    //每个slab中obj数量
	unsigned int num;		/* # of objs per slab */

/* 3) cache_grow/shrink */
	/* order of pgs per slab (2^n) */
    //每个slab页块的阶(一个slab由2^gfporder个页构成)
	unsigned int gfporder;

	/* force GFP flags, e.g. GFP_DMA */
    //从伙伴系统分配页,补足slab时,页分配的gfp码
	gfp_t allocflags;

	//一个slab中有多少不同的cache line
    size_t colour;			/* cache colouring range */
    //一个cache colour的长度,和一个cache line的大小相同
	unsigned int colour_off;	/* colour offset */
    /*
     *空闲对象链表放在slab外部时使用,管理用于slab对象管理结构中freelist成员的缓存,也就是又一个新缓存
     */
	struct kmem_cache *freelist_cache;
    //空闲对象链表的大小
	unsigned int freelist_size;

	/* constructor func */
    // 创建高速缓存时的构造函数指针,一半为null
	void (*ctor)(void *obj);

/* 4) cache creation/removal */
     //slab缓存名字
	const char *name;
    //slab缓存描述符双向链表指针
	struct list_head list;
	int refcount;
    //slab中每个obj的大小
	int object_size;
    //obj对齐字节
	int align;
...
...
...
	//slab节点链表组,对于NUMA系统中每个节点都会有一个struct kmem_cache_node数据结构
	struct kmem_cache_node *node[MAX_NUMNODES];
};

通过struct kmem_cache的代码实现可以看出,slab cache中所有slab的大小一致,由一个或多个连续页组成(通常为一个page,伙伴系统提供),每个slab中的obj大小和数量也是相同的。

slab cache描述符struct kmem_cache中除了相关的管理数据外,有两个很重要的成员:

// mm_slab_def.h
/*
 * struct array_cache
 *
 * Purpose:
 * - LIFO ordering, to hand out cache-warm objects from _alloc
 * - reduce the number of linked list operations
 * - reduce spinlock operations
 *
 * The limit is stored in the per-cpu structure to reduce the data cache
 * footprint.
 *
 */
struct array_cache {
    //当前CPU上该slab可用对象数量
	unsigned int avail;
    //最大对象数目,和kmem_cache中一样
	unsigned int limit;
    /*同kmem_cache,batchcount指定了在per-CPU列表为空的情况下,从缓存的slab中获取对象的数目。它还表示在
     *缓存增长时,若entry数组中obj数量超过limit限制后,需要迁移entry中obj的数量(到本地节点cpu共享空闲链
     *表中)。
     */
	unsigned int batchcount;
	/*在从缓存移除一个对象时,将touched设置为1,而缓存收缩时, 则将touched设置为0。这使得内核能够确认在缓
	 * 存上一次收缩之后是否被访问过,也是缓存重要性的一个标志。
	 */
    unsigned int touched;
    /*最后一个成员entry是一个伪数组, 其中并没有数组项, 只是为了便于访问内存中array_cache实例之后缓存中的
     *各个对象而已
     */
	void *entry[];	/*
			 * Must have this definition in here for the proper
			 * alignment of array_cache. Also simplifies accessing
			 * the entries.
			 */
};
//mm/slab.h
struct kmem_cache_node {
    //该链表中存储的所有slab中只有部分obj是空闲的
	struct list_head slabs_partial;	/* partial list first, better asm code */
    //该链表中存储的所有slab中不存在空闲的obj
	struct list_head slabs_full;
    //该链表中存储的所有slab中每个obj都是空闲的
	struct list_head slabs_free;
    //该节点中此kmem_cache的slab总数
	unsigned long num_slabs;
    //该节点中此kmem_cache空闲obj总数
	unsigned long free_objects;
    //该节点中此kmem_cache中空闲obj数量的上限,多了就会回收到伙伴系统的空闲链表中
	unsigned int free_limit;
	unsigned int colour_next;	/* Per-node cache coloring */
    //该节点上所有cpu共享的本地高速缓存
	struct array_cache *shared;	/* shared per node */
    //其他节点上所有cpu共享的本地高速缓存
	struct alien_cache **alien;	/* on other nodes */
	unsigned long next_reap;	/* updated without locking */
	int free_touched;		/* updated without locking */
};

2.2 slab描述符struct page

为了提高linux内存利用率,slab描述符结合在页描述符struct page中,当页描述符成员flage标志设置了PG_SLAB后,struct page中的部分成员可以用来描述slab对象.如下代码所示(由于struct page成员很多,下面只将描述slab对象有关的成员列出):

//为了节省内存,struct page中许多结构体会通过union联合体进行封装,此处做了省略
struct page {
	/* First double word block */
    //当页描述符成员flage标志设置了PG_SLAB后,struct page中对应的slab成员才有效
	unsigned long flags;		/* Atomic flags, some possibly updated asynchronously */
	void *s_mem;			/* slab first object */
    //freelist实际上是一个数组,数组中存放slab中未使用的obj的index值
	void *freelist;		/* sl[aou]b first free object */
		/* page_deferred_list().prev	-- second tail page */
	//当前slab已经使用的obj数量
	unsigned int active;		/* SLAB */
    
    /* 通过该成员将链到slab cache对应节点kmem_cache_node所管理的3个链表上*/
	struct list_head lru;	/* Pageout list, eg. active_list
					 * protected by zone_lru_lock !
					 * Can be used as a generic list
					 * by the page owner.
					 */
    
    //指向slab的slab cache描述符kmem_cache
	struct kmem_cache *slab_cache;	/* SL[AU]B: Pointer to slab */
    
    //slab首地址,待验证
    void *virtual;
	
}

由上面代码可以看出在slab描述符中最重要两个数据结构是s_mem和freelist这两个成员。

  1. s_mem用于指向指定slab区域中第一个obj(slab由一个或多个连续的页构成,由伙伴系统分配的内存块,固一个slab包含页的个数为2^gfp_order)
  2. freelist指向的是slab空闲obj的索引数组,该数组要和slab描述符中的active成员搭配使用。freelist指向的区域是一个连续地址的数组,它保存的地方有两种情况:一种情况是放在slab自身所在的内存区域,另外一种情况是保存在slab所在内存区域的外部(若保存在slab外部,则保存在slab对应slab cache的kmem_cache结构体中freelist_cache成员指针指向的链表).

下面介绍slab的空闲索引数组保存在slab自身内部的情况slab描述符是如何管理slab和其内部obj,如下图所示:

通过示意图来描述slab如何根据freelist数组和active成员来分配和释放其内部的空闲obj的(假设该slab中有6个obj)。

在slab机制中slab管理的内存区域的连续页块数,和slab cache结构描述符struct kmem_cache中的gfporder有关。该gfp是在slab cache初始化时通过每个slab中obj的数量和大小,着色区大小,freelist大小,slab对象描述符大小等一系列数据计算而来。需要注意的是这些对象的大小并不是创建的时候对象本身的大小,系统为了优化内存访问还会做一些字节对齐的填充,或者是在对象交界处填充一些magicnum进行防越界处理(如red_zone).

3.slab分配器中各个重要结构体间的关系总结

通过上图我们对slab分配器中各个重要结构体间的关系做一个总结:

/ # cat /proc/slabinfo
slabinfo - version: 2.1
# name            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> >
zswap_entry            0      0    360   22    2 : tunables    0    0    0 : slabdata      0      0      0
sw_flow_stats          0      0    448   18    2 : tunables    0    0    0 : slabdata      0      0      0
		......
		......
		......

dma-kmalloc-64         0      0    368   22    2 : tunables    0    0    0 : slabdata      0      0      0
dma-kmalloc-32         0      0    336   24    2 : tunables    0    0    0 : slabdata      0      0      0
dma-kmalloc-16         0      0    320   25    2 : tunables    0    0    0 : slabdata      0      0      0
dma-kmalloc-8          0      0    312   26    2 : tunables    0    0    0 : slabdata      0      0      0
dma-kmalloc-192        0      0    496   16    2 : tunables    0    0    0 : slabdata      0      0      0
dma-kmalloc-96         0      0    400   20    2 : tunables    0    0    0 : slabdata      0      0      0
kmem_cache_node      213    216    448   18    2 : tunables    0    0    0 : slabdata     12     12      0
kmem_cache           216    224    576   28    4 : tunables    0    0    0 : slabdata      8      8      0

(1).通过slab着色(slab coloring), slab分配器能够均匀地在缓存中分布对象, 以实现均匀的缓存利用
(2).经常使用的内核对象保存在CPU高速缓存中,这是我们想要的效果。从`slab`分配器的角度进行衡量, 伙伴系统的高速缓存和TLB占用较大,
      这是一个负面效应,因为这会导致不重要的数据驻留在`CPU`高速缓存中, 而重要的数据则被置换到内存, 显然应该防止这种情况出现.
(3).着色这个术语是隐喻性的. 它与颜色无关, 只是表示slab中的对象需要移动的特定偏移量, 以便使对象放置到不同的缓存行.