【推荐阅读】

背景

物理内存是按页来分配的,这样当实际只需要很小内存的时候,也会分配至少是 4K 大小的页面,而内核中有很多需要以字节为单位分配内存的场景(例如结构体),这样本来只想要几个字节而已却不得不分配一页内存,除去用掉的字节剩下的就形成了内部碎片。

slab分配器应运而生 ->

一般来说,内核对象的生命周期是这样的:分配内存-初始化-释放内存,内核中有大量的小对象,比如文件描述结构对象fd、任务描述结构对象task_struct,如果按照伙伴系统按页分配和释放内存,对小对象频繁的执行「分配内存-初始化-释放内存」会非常消耗性能。

伙伴系统分配出去的内存还是以页框为单位,而对于内核的很多场景都是分配小片内存,远用不到一页内存大小的空间。slab分配器,「通过将内存按使用对象不同再划分成不同大小的空间」,应用于内核对象的缓存。

伙伴系统和slab不是二选一的关系,slab 内存分配器是对伙伴分配算法的补充。

对于每个内核中的相同类型的对象,如:task_struct、file_struct 等需要重复使用的小型内核数据对象,都会有个 slab 缓存池 ,缓存住大量常用的「已经初始化」的对象,每当要申请这种类型的对象时,就从缓存池的slab 列表中分配一个出去;而当要释放时,将其重新保存在该列表中,而不是直接返回给伙伴系统,从而避免内部碎片,同时也大大提高了内存分配性能。

slab申请和分配的都是只针对内核空间,与用户空间申请分配内存无关。用户空间的malloc和free调用的是libc。

优点

  • slab 内存管理基于内核小对象,不用每次都分配一页内存,充分利用内存空间,避免内部碎片。
  • slab 对内核中频繁创建和释放的小对象做缓存,重复利用一些相同的对象,减少内存分配次数,提升性能。

尽管slab分配器对许多可能的工作负荷都工作良好,但也有一些情形,它无法提供最优性能。如微小的嵌入式系统,配备有大量物理内存的大规模并行系统。在第二种情况下,slab分配器所需的大量元数据可能成为一个问题:开发者称,在大型系统上仅slab的数据结构就需要很多吉字节内存。对嵌入式系统来说,slab分配器代码量和复杂性都太高。

为处理此类情形,在内核版本2.6开发期间,增加了slab分配器的两个替代品。

  • slob分配器进行了特别优化,以便减少代码量。它围绕一个简单的内存块链表展开(slob是simple linked list of block的缩写)。在分配内存时,使用了同样简单的最先适配算法。

slob分配器只有大约600行代码,总的代码量很小。事实上,从速度来说,它不是最高效的分配器,也肯定不是为大型系统设计的。

  • slub分配器通过将页帧打包为组,并通过 struct page 中未使用的字段来管理这些组,试图最小化所需的内存开销。读者此前已经看到,这样做不会简化该结构的定义,但在大型计算机上slub比slab提供了更好的性能,说明了这样做是正确的。

开始了!slab分配器

kmem_cache 是一个cache_chain 的链表组成节点,代表的是一个内核中的相同类型的「对象高速缓存」,每个kmem_cache 通常是一段连续的内存块,包含了三种类型的 slabs 链表:

  • slabs_full (完全分配的 slab 链表)
  • slabs_partial (部分分配的slab 链表)
  • slabs_empty ( 没有被分配对象的slab 链表)

slab 是slab 分配器的最小单位,在实现上一个 slab 由一个或多个连续的物理页组成(通常只有一页)。单个slab可以在 slab 链表之间移动,例如如果一个「半满slabs_partial链表」被分配了对象后变满了,就要从 slabs_partial 中删除,同时插入到「全满slabs_full链表」中去。内核slab对象的分配过程是这样的:

如果slabs_partial链表还有未分配的空间,分配对象,若分配之后变满,移动 slab 到slabs_full 链表

如果slabs_partial链表没有未分配的空间,进入下一步

如果slabs_empty 链表还有未分配的空间,分配对象,同时移动slab进入slabs_partial链表

如果slabs_empty为空,请求伙伴系统分页,创建一个新的空闲slab, 按步骤 3 分配对象

看下代码~kmem_cache的定义

slab高速缓存的分类

slab高速缓存分为两大类,「通用高速缓存」和「专用高速缓存」。

通用高速缓存

slab分配器中用 kmem_cache 来描述高速缓存的结构,它本身也需要 slab 分配器对其进行高速缓存。cache_cache 保存着对「高速缓存描述符的高速缓存」,是一种通用高速缓存,保存在cache_chain 链表中的第一个元素。

另外,slab 分配器所提供的小块连续内存的分配,也是通用高速缓存实现的。通用高速缓存所提供的对象具有几何分布的大小,范围为32到131072字节。内核中提供了 kmalloc() 和 kfree() 两个接口分别进行内存的申请和释放。

专用高速缓存

内核为专用高速缓存的申请和释放提供了一套完整的接口,根据所传入的参数为指定的对象分配slab缓存。

专用高速缓存的申请和释放

kmem_cache_create() 用于对一个指定的对象创建高速缓存。它从 cache_cache 普通高速缓存中为新的专有缓存分配一个高速缓存描述符,并把这个描述符插入到高速缓存描述符形成的 cache_chain 链表中。kmem_cache_destory() 用于撤消和从 cache_chain 链表上删除高速缓存。

另外还有slob->适合微小嵌入式,和slub->适合大型系统。

slab分配信息保存在 /proc/slabinfo中

<num_objs>:每个slab一共可以分出多少个obj,

<active_objs> :还可以分配多少个obj,

< pagesperslab>:每个slab对应多少个pages,

< objperslab>:每个slab可以分出多少个object,

< objsize>:每个obj多大,单位字节