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缓存中的空闲对象填充它
- 特定于CPU/节点的缓存列表中的对象
- 当前已经存在于slab缓存中中的未用对象
- 从伙伴系统获得内存,然后创建的对象
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缓存。该函数的基本工作过程为:
- 参数检查
- 计算对齐
- 分配缓存的管理结构所需的内存
- 计算slab所需的物理内存大小以及每个slab中slab对象的个数
- 计算slab管理部分应该放在哪里,并存储在缓存的flags域中
- 计算slab的颜色,颜色数目存在color中,颜色偏移量存在color_off中
- 建立每CPU的缓存
- 将新创建的缓存添加到全局slab缓存链表slab_caches中
2.分配对象
kmem_cache_alloc用于从指定的slab缓存分配对象。与kmalloc相比,它多了一个缓存指针的参数,用于指向所要从其中分配内存的缓存。其工作过程如图:
在NUMA系统中,如果在本节点分配失败,还会尝试其它节点。
cache_grow用于缓存的增长,它会从伙伴系统获取内存。其流程如图所示: