上一节说过Linux内核使用伙伴系统算法来管理内存页, 但伙伴系统算法分配的单位是内存页, 就是至少要分配一个或以上的内存块. 但很多时候我们并不需要分配一个内存页, 例如我们要申请一个大小为200字节的结构体时, 如果使用伙伴系统分配算法至少申请一个内存页, 但只使用了200字节的内存, 那么剩余的3896字节就被浪费掉了.

为了解决小块内存申请的问题, Linux内核引入了 SLAB 分配算法. Linux 所使用的 SLAB 分配算法 的基础是 Jeff Bonwick 为SunOS 操作系统首次引入的一种算法。在内核中,会为有限的对象集(例如文件描述符和其他常见结构)分配大量内存。Jeff发现对内核中普通对象进行初始化所需的时间超过了对其进行分配和释放所需的时间。因此他的结论是不应该将内存释放回一个全局的内存池,而是将内存保持为针对特定目而初始化的状态。

为了更好的理解 SLAB分配算法, 我们先来介绍一下算法使用到的数据结构.

SLAB分配算法的初始化由 kmem_cache_init() 函数完成,如下:

这个函数主要用来初始化 cache_cache 这个变量,cache_cache 是一个类型为 kmem_cache_t 的结构体变量,定义如下:

为什么需要一个这样的对象呢?因为本身 kmem_cache_t 结构体也是小内存对象,所以也应该有slab分配器来分配的,但这样就出现“鸡蛋和鸡谁先出现”的问题。在系统初始化的时候,slab分配器还没有初始化,所以并不能使用slab分配器来分配一个 kmem_cache_t 对象,这时候只能通过定义一个 kmem_cache_t 类型的静态变量来来管理slab分配器了,所以 cache_cache 静态变量就是用来管理slab分配器的。

从上面的代码可知,cache_cache 的 objsize字段 被设置为 sizeof(kmem_cache_t) 的大小,所以这个对象主要是用来分配不同类型的 kmem_cache_t 对象的。

kmem_cache_init() 函数调用了 kmem_cache_estimate() 函数来计算一个slab能够保存多少个大小为 cache_cache.objsize 的对象,并保存到 cache_cache.num 字段中。一个slab中不可能全部都用来分配对象的,举个例子:一个4096字节大小的slab用来分配大小为22字节的对象,可以划分为186个,但还剩余4字节不能使用的,所以这部分内存用来作为着色区。着色区的作用是为了错开不同的slab,让CPU更有效的缓存slab。当然这属于优化部分,对slab分配算法没有多大的影响。就是说就算不对slab进行着色操作,slab分配算法还是可以工作起来的。


【文章福利】小编推荐自己的Linux内核技术交流群:【891587639】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!(含视频教程、电子书、实战项目及代码)    


kmem_cache_t 是用来管理和分配对象的,所以要使用slab分配器时,必须先申请一个 kmem_cache_t 对象,申请 kmem_cache_t 对象由 kmem_cache_create() 函数进行:

kmem_cache_create() 函数比较长,所以上面代码去掉了一些不那么重要的地方,使代码更清晰的体现其原理。

在 kmem_cache_create() 函数中,首先调用 kmem_cache_alloc() 函数申请一个 kmem_cache_t 对象,我们看到调用 kmem_cache_alloc() 时,传入的就是 cache_cache 变量。申请完 kmem_cache_t对象 后需要对其进行初始化操作,主要是对 kmem_cache_t对象 的所有字段进行初始化:

  • 计算需要多少个页面来作为slab的大小。

  • 计算一个slab能够分配多少个对象。

  • 计算着色区信息。

  • 初始化 slab_full / slab_partial / slab_free 链表。

  • 把申请的 kmem_cache_t对象 保存到 cache_chain 链表中。

申请完 kmem_cache_t对象 后,就使用通过调用 kmem_cache_alloc() 函数来申请指定的对象。kmem_cache_alloc() 函数代码如下:

kmem_cache_alloc() 函数被我展开之后如上代码,kmem_cache_alloc() 函数的主要步骤是:

  • 从kmem_cache_t对象 的 slab_partial 列表中查找是否有slab可用,如果有就直接从slab中分配一个对象。

  • 如果 slab_partial 列表中没有可用的slab,那么就从 slab_free 列表中查找可用的slab,如果有可用slab,就从slab分配一个对象,并且把此slab放置到 slab_partial 列表中。

  • 如果 slab_free 列表中没有可用的slab,那么就调用 kmem_cache_grow() 函数申请新的slab来进行对象的分配。kmem_cache_grow() 函数会调用 __get_free_pages() 函数来申请内存页并且初始化slab.

对象的释放比较简单,主要通过调用 kmem_cache_free() 函数完成,而 kmem_cache_free() 函数最终会调用 kmem_cache_free_one() 函数,代码如下:

对象释放的时候首先会把对象的索引添加到slab的空闲对象链表中,然后根据slab的使用情况移动slab到合适的列表中。

  • 如果slab所有对象都被释放完时,把slab放置到 slab_free 列表中。

  • 如果对象所在的slab原来在 slab_full 中,那么就把slab移动到 slab_partial 中。