Linux最近几年的发展迅速,很多中小企业网站都安装了Linux操作系统。本文将着重介绍Linux内核的内存管理,尤其是slab分配提供的机制。将探索slab分配器背后所采用的思想,并介绍这种方法提供的接口和用法。
slab 缓存
Linux所使用的slab分配器的基础是Jeff Bonwick为SunOS操作系统首次引入的一种算法。Jeff 的分配器是围绕对象缓存进行的。在内核中,会为有限的对象集(例如文件描述符和其他常见结构)分配大量内存。Jeff发现对内核中普通对象进行初始化所需的时间超过了对其进行分配和释放所需的时间。
因此他的结论是不应该将内存释放回一个全局的内存池,而是将内存保持为针对特定目而初始化的状态。例如,如果内存被分配给了一个互斥锁,那么只需在为互斥锁首次分配内存时执行一次互斥锁初始化函数(mutex_init)即可。后续的内存分配不需要执行这个初始化函数,因为从上次释放和调用析构之后,它已经处于所需的状态中了。
Linux slab分配器使用了这种思想和其他一些思想来构建一个在空间和时间上都具有高效性的内存分配器。
图 1 给出了slab结构的高层组织结构。在{zg}层是cache_chain,这是一个slab缓存的链接列表。这对于best-fit算法非常有用,可以用来查找最适合所需要的分配大小的缓存(遍历列表)。cache_chain的每个元素都是一个 kmem_cache 结构的引用(称为一个 cache)。它定义了一个要管理的给定大小的对象池。
图 1. slab 分配器的主要结构
每个缓存都包含了一个 slabs 列表,这是一段连续的内存块(通常都是页面)。存在 3 种 slab:
slabs_full
xx分配的 slab
slabs_partial
部分分配的 slab
slabs_empty
空slab,或者没有对象被分配
注意 slabs_empty 列表中的 slab是进行回收(reaping)的主要备选对象。正是通过此过程,slab 所使用的内存被返回给操作系统供其他用户使用。
slab列表中的每个slab都是一个连续的内存块(一个或多个连续页),它们被划分成一个个对象。这些对象是从特定缓存中进行分配和释放的基本元素。注意 slab 是 slab分配器进行操作的最小分配单位,因此如果需要对slab进行扩展,这也就是所扩展的最小值。通常来说,每个slab被分配为多个对象。
由于对象是从slab中进行分配和释放的,因此单个slab可以在 slab 列表之间进行移动。例如,当一个slab中的所有对象都被使用完时,就从slabs_partial 列表中移动到 slabs_full 列表中。当一个 slab xx被分配并且有对象被释放后,就从 slabs_full 列表中移动到 slabs_partial 列表中。当所有对象都被释放之后,就从 slabs_partial 列表移动到 slabs_empty 列表中。
slab背后的动机
与传统的内存管理模式相比, slab缓存分配器提供了很多优点。首先,内核通常依赖于对小对象的分配,它们会在系统生命周期内进行无数次分配。slab 缓存分配器通过对类似大小的对象进行缓存而提供这种功能,从而避免了常见的碎片问题。slab分配器还支持通用对象的初始化,从而避免了为同一目而对一个对象重复进行初始化。{zh1},slab分配器还可以支持硬件缓存对齐和着色,这允许不同缓存中的对象占用相同的缓存行,从而提高缓存的利用率并获得更好的性能。