13.slab分配器

首先可以通过该博客:http://www.cnblogs.com/liloke/archive/2011/11/20/2255737.html了解cache的工作原理

Linux内核中有许多内存动态分配的需求,而其中的对象大小也参差不齐,LINUX内核提供了slab层,扮演了通用数据结构缓存层的角色。slab根据对象的类型来分组不同的Cache,每个Cache存放不同类型的对象,例如,一个Cache被用来存储task_struck,而另一个存放Inode等,这些Cache包含几个slab,而slab由一个或多个物理上连续的page组成。对于一般的数据结构,每个slab只有一个slab来分配即可。每当要申请这样一个对象时,slab分配器就从一个slab列表中分配一个这样大小的单元出去,而当要释放时,将其重新保存在该列表中,而不是直接返回给伙伴系统,从而避免内部碎片。slab分配器并不丢弃已经分配的对象,而是释放并把它们保存在内存中。slab分配对象时,会使用最近释放的对象的内存块,因此其驻留在cpu高速缓存中的概率会大大提高。

内核中slab的主要数据结构

简要分析下这个图:kmem_cache是一个cache_chain的链表,描述了一个高速缓存,每个高速缓存包含了一个slabs的列表,这通常是一段连续的内存块。存在3种slab:slabs_full(完全分配的slab),slabs_partial(部分分配的slab),slabs_empty(空slab,或者没有对象被分配)。slab是slab分配器的最小单位,在实现上一个slab有一个货多个连续的物理页组成(通常只有一页)。单个slab可以在slab链表之间移动,例如如果一个半满slab被分配了对象后变满了,就要从slabs_partial中被删除,同时插入到slabs_full中去。

slab缓存

每个缓存结构都包括了两个重要的成员:

  • struct kmem_list3 **nodelists:kmem_list3结构中包含了三个链表头,分别对应于完全用尽的slab链表,部分用尽的slab链,空闲的slab链表,其中部分空闲的在最开始
  • struct array_cache *array[NR_CPUS + MAX_NUMNODES]:array是一个数组,系统中的每一个CPU,每一个内存节点都对应该数组中的一个元素。array_cache结构包含了一些特定于该CPU/节点的管理数据以及一个数组,每个数组元素都指向一个该CPU/节点刚释放的内存对象。该数组有助于提高高速缓存的利用率。
    • 当释放内存对象时,首先将内存对象释放到该数组中对应的元素中
    • 申请内存时,内核假定刚释放的内存对象仍然处于CPU高速缓存中,因而会先从该数组的对应数组元素中查找,看是否可以申请。
    • 当特定于CPU/节点的缓存数组是空时,会用slab缓存中的空闲对象填充它
因此,对象分配的次序为:
  1. 特定于CPU/节点的缓存列表中的对象
  2. 当前已经存在于slab缓存中中的未用对象
  3. 从伙伴系统获得内存,然后创建的对象

slab分配器的实现

.使用的数据结构

linux使用struct kmem_cache表示slab缓存,使用struct kmem_list3管理缓存所对应的slab链表的链表头,使用struct array_cache管理特定于CPU的slab对象的缓存(注意不是slab缓存是slab对象的缓存)。

2.内核采用的其它保护机制

为了检测错误,内核采用了一些机制来对内存进行保护,主要的方法有:
危险区:在每个对象的开始和结束处增加一个额外的内存区,其中会填充一些特殊的字段。如果这个区域被修改了,可能就是某些代码访问了不该访问的内存区域
对象毒化:在建立和释放slab时,将对象用预定义的模式填充。如果在对象分配时发现该模式已经改变,就可能是发生了内存越界。

3.初始化

slab分配器的初始化涉及到一个鸡与蛋的问题。为了初始化slab数据结构,内核需要很多远小于一页的内存区,很显然由kmalloc分配这种内存最合适,但是kmalloc只有在slab分配器初始化完才能使用。内核借助一些技巧来解决该问题。
kmem_cache_init函数被内核用来初始化slab分配器。它在伙伴系统启用后调用。在SMP系统中,启动CPU正在运行,其它CPU还未初始化,它要在smp_init之前调用。slab采用多步逐步初始化slab分配器,其工作过程:
创建第一个名为kmem_cache的slab缓存,此时该缓存的管理数据结构使用的是静态分配的内存。在slab分配器初始化完成后,会将这里使用的静态数据结构替换为动态分配的内存。
初始化其它的slab缓存,由于已经初始化了第一个slab缓存,因此这一步是可行。
将初始化过程由于“鸡与蛋”的问题而使用的静态数据结构替换为动态分配的。

4.API

1.创建缓存

slab分配器使用kmem_cache_create创建一个新的slab缓存。该函数的基本工作过程为:

  1. 参数检查
  2. 计算对齐
  3. 分配缓存的管理结构所需的内存
  4. 计算slab所需的物理内存大小以及每个slab中slab对象的个数
  5. 计算slab管理部分应该放在哪里,并存储在缓存的flags域中
  6. 计算slab的颜色,颜色数目存在color中,颜色偏移量存在color_off中
  7. 建立每CPU的缓存
  8. 将新创建的缓存添加到全局slab缓存链表slab_caches中

2.分配对象

kmem_cache_alloc用于从指定的slab缓存分配对象。与kmalloc相比,它多了一个缓存指针的参数,用于指向所要从其中分配内存的缓存。
其工作过程如图:

在NUMA系统中,如果在本节点分配失败,还会尝试其它节点。

cache_grow用于缓存的增长,它会从伙伴系统获取内存。其流程如图所示:

3.释放对象

kmem_cache_free用于将对象归还给指定的slab缓存,类似于kmem_cache_free,它比kfree多了一个指向所归还到的slab缓存指针参数。其流程如图:
free_block会将缓存中前batchcount个对象移动到slab链表中,并且将缓存中剩余的对象向数组的头部移动。根据slab对象所属的slab的状态(inuse域),slab对象可能被归给给部分空闲链表(如果该slab中有些slab对象正在被使用)或者空闲链表(该slab中没有其它对象正在被使用),同时如果加入到空闲slab链表中的slab对象数目超过了free_limit的限制(在kmem_list3结构中),则会调用slab_destroy销毁slab。

4.缓存收缩

可以使用kmem_cache_shrink来回收一个slab缓存所管理的内存。它会释放尽可能多的slab。它会尝试回收用于每CPU缓存的内存空间(调用free_block),以及用于空闲链表的slab内存空间,slab的释放最终都由slab_destroy完成。

5.通用缓存

如果不涉及到特定类型的内存,而只是普通类型的内存,可以使用kmalloc和kfree来申请和释放缓存。内核会找到并使用适用于所申请的大小的通用slab缓存来进行分配和释放。


全部评论

相关推荐

不愿透露姓名的神秘牛友
11-24 20:55
阿里国际 Java工程师 2.7k*16.0
程序员猪皮:没有超过3k的,不太好选。春招再看看
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务