日期:2014-05-16  浏览次数:20738 次

Linux内存管理 Slab分配器(一:概述)

采用伙伴算法分配内存时,每次至少分配一个页面。但当请求分配的内存大小为几十个字节或几百个字节时应该如何处理?如何在一个页面中分配小的内存区,小内存区的分配所产生的内碎片又如何解决?Linux采用Slab。

Linux 所使用的 slab 分配器的基础是 Jeff Bonwick 为 SunOS 操作系统首次引入的一种算法。Jeff 的分配器是围绕对象缓存进行的。在内核中,会为有限的对象集(例如文件描述符和其他常见结构)分配大量内存。Jeff 发现对内核中普通对象进行初始化所需的时间超过了对其进行分配和释放所需的时间。因此他的结论是不应该将内存释放回一个全局的内存池,而是将内存保持为针对特定目而初始化的状态。例如,如果内存被分配给了一个互斥锁,那么只需在为互斥锁首次分配内存时执行一次互斥锁初始化函数(mutex_init)即可。后续的内存分配不需要执行这个初始化函数,因为从上次释放和调用析构之后,它已经处于所需的状态中了。
Linux slab 分配器使用了这种思想和其他一些思想来构建一个在空间和时间上都具有高效性的内存分配器。
图 1 给出了 slab 结构的高层组织结构。在最高层是 cache_chain,这是一个 slab 缓存的链接列表。这对于 best-fit 算法非常有用,可以用来查找最适合所需要的分配大小的缓存(遍历列表)。cache_chain 的每个元素都是一个 kmem_cache 结构的引用(称为一个 cache)。它定义了一个要管理的给定大小的对象池。


每个缓存都包含了一个 slabs 列表,这是一段连续的内存块(通常都是页面)。存在 3 种 slab:
slabs_full:完全分配的 slab
slabs_partial:部分分配的 slab
slabs_free:空 slab,或者没有对象被分配
注意 slabs_free 列表中的 slab 是进行回收(reaping)的主要备选对象。正是通过此过程,slab 所使用的内存被返回给操作系统供其他用户使用。
slab 列表中的每个 slab 都是一个连续的内存块(一个或多个连续页),它们被划分成一个个对象。这些对象是从特定缓存中进行分配和释放的基本元素。注意 slab 是 slab 分配器进行操作的最小分配单位,因此如果需要对 slab 进行扩展,这也就是所扩展的最小值。通常来说,每个 slab 被分配为多个对象。
由于对象是从 slab 中进行分配和释放的,因此单个 slab 可以在 slab 列表之间进行移动。例如,当一个 slab 中的所有对象都被使用完时,就从 slabs_partial 列表中移动到 slabs_full 列表中。当一个 slab 完全被分配并且有对象被释放后,就从 slabs_full 列表中移动到 slabs_partial 列表中。当所有对象都被释放之后,就从 slabs_partial 列表移动到 slabs_free 列表中。

为了便于理解,在网上找了一个图,如下:


用于描述缓存的数据结构kmem_cache如下:

struct kmem_cache{
/* 1) per-cpu data, touched during every alloc/free */
	struct array_cache *array[NR_CPUS];//array是一个指向数组的指针,每个数组项都对应于系统中的一个CPU。每个数组项都包含了另一个指针,指向下文讨论的array_cache结构的实例
/* 2) Cache tunables. Protected by cache_chain_mutex */
	unsigned int batchcount;//指定了在每CPU列表为空的情况下,从缓存的slab中获取对象的数目。它还表示在缓存增长时分配的对象数目
	unsigned int limit;//limit指定了每CPU列表中保存的对象的最大数目,如果超出该值,内核会将batchcount个对象返回到slab
	unsigned int shared;

	unsigned int buffer_size;//指定了缓存中管理的对象的长度
	u32 reciprocal_buffer_size;//buffer_size的倒数值,为了克服出发运算对性能的影响
/* 3) touched by every alloc & free from the backend */

	unsigned int flags;//是一个标志寄存器,定义缓存的全局性质,当前只有一个标志位,用于标记slab头得管理数据是在slab内还是外
	unsigned int num;//保存了可以放入slab的对象的最大数目

/* 4) cache_grow/shrink */
	/* order of pgs per slab (2^n) */
	unsigned int gfporder;//指定了slab包含的页数目以2为底得对数

	/* force GFP flags, e.g. GFP_DMA */
	gfp_t gfpflags;//与伙伴系统交互时所提供的分配标识

	size_t colour;//指定了颜色的最大数目
	unsigned int colour_off;//是基本偏移量乘以颜色值获得的绝对偏移量
	struct kmem_cache *slabp_cache;//如果slab头部的管理数据存储在slab外部,则slabp_cache指向分配所需内存的一般性缓存;如果slab头部在slab上,则其为NULL
	unsigned int slab_size;//slab管理区的大小
	unsigned int dflags;//另一个标志集合,描述slab的“动态性质”,但目前还没有定义标志
	/* constructor func */
	void (*ctor)(struct kmem_cache *, void *);//创建高速缓存时的构造函数指针
/* 5) cache creation/removal */
	const char *name;//缓存的名称
	struct list_head next;//用于将kmem_cache的所有实例保存在全局链表cache_chain上

/* 6) statistics */
#if STATS//统计数据字段
	unsigned long num_active;
	unsigned long num_allocations;
	unsigned long high_mark;
	unsigned long grown;
	unsigned long reaped;
	unsigned long errors;
	unsigned long max_freeable;
	unsigned long node_allocs;
	unsigned long node_frees;
	unsigned long node_overflow;
	atomic_t allochit;
	atomic_t allocmiss;
	atomic_t freehit;
	atomic_t freemiss;
#endif
#if DEBUG
	/*
	 * If debugging is enabled, then the allocator can add additional
	 * fields and/or padding to every object. buffer_size contains