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 the total
	 * object size including these internal fields, the following two
	 * variables contain the offset to the user object and its size.
	 */
	int obj_offset;
	int obj_size;
#endif
	/*
	 * We put nodelists[] at the end of kmem_cache, because we want to size
	 * this array to nr_node_ids slots instead of MAX_NUMNODES
	 * (see kmem_cache_init())
	 * We still use [MAX_NUMNODES] and not [1] or [0] because cache_cache
	 * is statically defined, so we reserve the max number of nodes.
	 */
	struct kmem_list3 *nodelists[MAX_NUMNODES];//nodelists是一个数组,每个数组对应于系统中一个可能的内存结点。每个数组项都包含kmem_list3的一个实例
	/*
	 * Do not add fields after nodelists[]
	 */
}

array_cache数据结构描述:

struct array_cache {
	unsigned int avail;//本地高速缓存中可用的空闲对象数
	unsigned int limit;//空闲对象的上限
	unsigned int batchcount;//一次转入和转出的对象数量
	unsigned int touched;//标识本地CPU最近是否被使用
	spinlock_t lock;
	void *entry[];//这是一个伪数组,便于对后面用于跟踪空闲对象的指针数组的访问
};


kmem_list3数据结构描述:

struct kmem_list3 {
	struct list_head slabs_partial;//部分空闲的slab链表
	struct list_head slabs_full;//非空闲的slab链表
	struct list_head slabs_free;//完全空闲的slab链表
	unsigned long free_objects;//部分空闲的slab链表和完全空闲的slab链表中空闲对象的总数
	unsigned int free_limit;//指定了所有slab上容许未使用对象的最大数目
	unsigned int colour_next;//内核建立的下一个slab的颜色
	spinlock_t list_lock;
	struct array_cache *shared;//结点内共享
	struct array_cache **alien;//在其他结点上
	unsigned long next_reap;//定义了内核在两次尝试收缩缓存之间,必须经过的时间间隔
	int free_touched;//表示缓存是否是活动的
};


slab数据结构描述:

struct slab {
	struct list_head list;
	unsigned long colouroff;//该Slab上着色区的大小
	void *s_mem;//指向对象区的起点
	unsigned int inuse;//Slab中所分配对象的个数
	kmem_bufctl_t free;//指明了空闲对象链中的第一个对象,kmem_bufctl_t其实是一个整数
	unsigned short nodeid;//结点标识号
};

其实slab机制的简介表示如下图所示:


slab内的结构如下图所示:


每个Slab的首部都有一个小小的区域是不用的,称为“着色区(coloring area)”。着色区的大小使Slab中的每个对象的起始地址都按高速缓存中的”缓存行(cache line)”大小进行对齐(80386的一级高速缓存行大小为16字节,Pentium为32字节)。因为Slab是由1个页面或多个页面(最多为32)组成,因此,每个Slab都是从一个页面边界开始的,它自然按高速缓存的缓冲行对齐。但是,Slab中的对象大小不确定,设置着色区的目的就是将Slab中第一个对象的起始地址往后推到与缓冲行对齐的位置。因为一个缓冲区中有多个Slab,因此,应该把每个缓冲区中的各个Slab着色区的大小尽量安排成不同的大小,这样可以使得在不同的Slab中,处于同一相对位置的对象,让它们在高速缓存中的起始地址相互错开,这样就可以改善高速缓存的存取效率。

每个Slab上最后一个对象以后也有个小小的废料区是不用的,这是对着色区大小的补偿,其大小取决于着色区的大小,以及Slab与其每个对象的相对大小。但该区域与着色区的总和对于同一种对象的各个Slab是个常数。

每个对象的大小基本上是所需数据结构的大小。只有当数据结构的大小不与高速缓存中的缓冲行对齐时,才增加若干字节使其对齐。所以,一个Slab上的所有对象的起始地址都必然是按高速缓存中的缓冲行对齐的。


与传统的内存管理模式相比, slab 缓存分配器提供了很多优点。首先,内核通常依赖于对小对象的分配,它们会在系统生命周期内进行无数次分配。slab 缓存分配器通过对类似大小的对象进行缓存而提供这种功能,从而避免了常见的碎片问题。slab 分配器还支持通用对象的初始化,从而避免了为同一目而对一个对象重复进行初始化。最后,slab 分配器还可以支持硬件缓存对齐和着色,这允许不同缓存中的对象占用相同的缓存行,从而提高缓存的利用率并获得更好的性能。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值