Slub分配器的来龙去脉

slab分配器设计的需求在linux内核的内存子系统中,伙伴系统无疑处于内存管理的核心地带,但是如果将内存管理从逻辑上分层,它的位置则处于最底层 。Buddy是所有物理内存的管家,不论使用何种接口申请内存都要经由伙伴系统进行分配 。但是,伙伴系统管理的物理内存是以页为单位,以4K页为例,它也包含了4096个字节 。但是无论是内核自己还是用户程序,在日常的使用中都很少会需要使用四千多字节大小的内存 。试想如果我们仅需要为10个字符的字符串分配内存,但是伙伴系统却给了我们一页,那这一页剩余没有使用的内存就浪费了,而且这个浪费近乎奢侈 。除了浪费的问题,还有一个更需要关心的问题是,在这样的分配情况下,如果分配非常频繁,系统可能很快就会面临严重的碎片化问题 。因为频繁使用的数据结构也会频繁的分配和释放,加速生产内存碎片 。另外,直接调用伙伴系统的操作对系统的数据和指令高速缓存也有很大的影响 。所以,基于以上的原因,也源于现实需求,内核需要一种轻量的、快速的、灵活的新型内存分配器,最主要的是,它可以提供小块内存的分配 。为了实现这样的小内存分配器,Sun公司的J.Bonwick首先在Solaris 2.4中设计并实现了slab分配器,并对其开源 。在Linux中也实现了具有相同的基本设计思想的同名分配器slab 。
slab、slob和slub关系slab、slob和slub都是小内存分配器,slab是slob和slub实现的基础,而slob和slub是针对slab在不同场景下的优化版本 。在slab引入Linux的很多年内,其都是Linux内核管理对象缓冲区的主流算法 。并且由于slab的实现非常复杂,很长一段时间内都少有对它的改动 。随着多处理器的发展和NUMA架构的广泛应用,slab的不足也逐渐显现 。slab的缓存队列管理复杂,其用于管理的数据结构存储开销大,对NUMA支持复杂,slab着色机制效果不明显 。这些不足让slab很难在两种场景下提供最优的性能:小型嵌入式系统和配备有大量物理内存的大规模并行系统 。对于小型嵌入式系统来说,slab分配器的代码量和复杂性都太高;对于大规模并行系统,slab用于自身管理的数据结构就需要占用很多G字节内存 。针对slab的不足,内核开发人员Christoph Lameter在在内核版本2.6开发期间,引入了新的Slub分配器 。Slub简化了slab一些复杂的设计,但保持slab的基本设计思想 。同时,一种新的针对小型嵌入式系统的分配器slob也被引入,为了适应嵌入式系统的特点,slob进行了特别的优化,以大幅减少代码量(slob只有大约600行代码) 。
slab层在内存管理子系统的层次slab层可以理解为一个通用层,其包含了slab、slob和slub,至于底层具体使用哪种分配器可以通过配置内核选项进行选择 。对于内核的其他模块,则不需要关注底层使用了哪个分配器 。因为为了保证内核的其他模块都可以无缝迁移到Slub/slob,所有分配器的接口都是相同的,它们都实现了一组特定的接口用于内存分配 。下图为Slab层在内存管理中的层次图:

Slub分配器的来龙去脉

文章插图
 
逻辑上看,slab层位于伙伴系统之上 。因为Buddy是最底层的分配器,Slub需要先向Buddy申请内存,而不能越过Buddy获取page 。从Buddy申请到内存后,Slub才可以对其进行自己的操作 。
 
、slab分配器设计的需求在Linux内核的内存子系统中,伙伴系统无疑处于内存管理的核心地带,但是如果将内存管理从逻辑上分层,它的位置则处于最底层 。Buddy是所有物理内存的管家,不论使用何种接口申请内存都要经由伙伴系统进行分配 。但是,伙伴系统管理的物理内存是以页为单位,以4K页为例,它也包含了4096个字节 。但是无论是内核自己还是用户程序,在日常的使用中都很少会需要使用四千多字节大小的内存 。试想如果我们仅需要为10个字符的字符串分配内存,但是伙伴系统却给了我们一页,那这一页剩余没有使用的内存就浪费了,而且这个浪费近乎奢侈 。除了浪费的问题,还有一个更需要关心的问题是,在这样的分配情况下,如果分配非常频繁,系统可能很快就会面临严重的碎片化问题 。因为频繁使用的数据结构也会频繁的分配和释放,加速生产内存碎片 。另外,直接调用伙伴系统的操作对系统的数据和指令高速缓存也有很大的影响 。所以,基于以上的原因,也源于现实需求,内核需要一种轻量的、快速的、灵活的新型内存分配器,最主要的是,它可以提供小块内存的分配 。为了实现这样的小内存分配器,Sun公司的J.Bonwick首先在Solaris 2.4中设计并实现了slab分配器,并对其开源 。在Linux中也实现了具有相同的基本设计思想的同名分配器slab 。


推荐阅读