一、Slab定义
- 在操作系统的运行过程中,经常涉及到大量对象的重复生成、使用和释放问题,对象生成算法的诞生,可以在很大程度上提高整个提供的性能。在LINUX中所用到的对象,比较典型的例子如inode、task_struct等,都有这些特点。一般来说,这类对象的种类相对稳定,每类对象的数据却是大量的,并且在初始化与析构时要做大量的工作,所占时间比例大大超过内存分配所占用的时间。因此,如果我们能够用合适的方法使得在对象前后两次被使用时,在同一块内存或者同一类内存空间,且保留基本数据结构,就可以大大提高效率。
- slab算法同时还具有一个优点,那就是可以大大提高内存的利用率以及硬件缓存区级系统总线的利用率。Linux的2.4版本内核的内核态内存分配采用了这种面向对象的slab分配原则。
- slab算法思路中最基本的一点被称为object-caching,即对象缓存。其核心做法就是保留对象初始化状态的不变部分,这样,对象就用不着在每次使用时重新初始化(构造)及破坏(析构)。
- 比如说,如果一个对象拥有一个互斥锁成员(semphore),则这个成员真的初始化只需要做一次,这个对象的哪些成员可以被认为是不变的呢,它主要包含内嵌锁、引用计数、状态、只读数据等等。即使成员在使用过程中被修改,但是在释放对象的时候,又恢复到了初始化状态,比如引用计数,初始化为0,释放时,也为0.
二、Slab思路介绍
-
算法实现基于对象缓存的分配算法的基本思路如下.
-
分配对象:
if(在对象对应缓存区中还有空闲的对象位置)
{
获取此对象,不必再初始化;
}
else
{
分配内存;
初始化对象;
} -
释放对象:
只需要在缓存中将对象所在位置标为空闲,而不需要作析构。 -
slab特殊点说明。
当系统资源不足时,slab算法也允许将一部分未使用的缓存空间释放,以缓解系统压力。从缓存中取得一些未使用的对象空间,对这些对象作析构,将对象所占用的空间释放给系统。
三、Slab技术关键字
- 缓存区(cache):
一种对象的所有实例都存在同一个缓存区中。不同的对象,即使大小相同,也放在不同的缓存区中。每个缓存区有若干个slab,按照满、半满、空的顺序排列。在slab块分配思想中,整个内核态内存块可以看作是按照这种缓存区来组织的,对每种对象类型使用一种缓存区,例如:vm_area_struct(存放vma结构的缓存区)、slab_cache(用于slab缓存区)等等,这些缓存区的信息可以从/proc/slabinfo文件中看到。缓存区按照slab块组建。缓存区的管理者记录了这个缓存区中对象的大小,性质(如是否可用于dma操作),每个slab块中对象的个数以及每个slab块大小(物理页帧数)。 - slab块:
slab块是内核内存分配与页面级分配的接口。每个slab块大小都是页面大小的整数倍,由若干对象组成。slab块分三类;
1)完全块:没有空闲对象
2)部分块:只分配了部分对象空间,仍有空闲对象
3)空闲块:没有分配对象,即整个块内对象空间均可分配
在申请新的对象空间时,如果缓存中存在部分块,那么首先检查部分块寻找空闲对象空间,若未成功再看空闲块,如果还未成功则为这个对象分配一块新的slab块,然后得到一块空闲对象空间。 - 对象:
将被申请的空间视为对象,使用构造函数初始化对象,然后由用户使用对象(即所需的屋里空间)。当需要回收空间时,使用析构函数析构对象,再释放给系统。 - 着色机制:
所谓着色机制是指,按照对象要求的对其字节数,分配合适的着色区(即偏移量),以使该类对象具有良好的地址分布,便于硬件操作,从而带来较高的硬件缓存区和系统总线使用率。
四、Slab物理结构
- 数据结构 kmem_bufctl_t:
1)kmem_bufctl_t 是 一个slab块中每个对象都有一个这样的管理结构。
通过使用对象所在物理页帧结构struct page中list成员来得到此对象所在cache和slab块(list.next指向此页所在缓存区描述结构,list.prev指向它所在的slab块描述结构)。这样简化在老版本内核中复杂数据结构,只使用了一个简单的整形变量。
2) kmem_bufctl_t变量 存储了它所对应对象的下一个空闲对象在对象数组中的下标。
创建新的slab块后的初始化过程中,第一个对象的kmem_bufctl_t赋值1,后面按步长1顺序递加,最大可以达到0xffffFFFE。如下是on slab 方式的slab块内存分布,要求是对象的大小小于1物理页面大小。
- [slab_s][1.2.3.4.5…0xffffFFFE][对象1][对象2]…[对象n]
- slab数据结构+kmem_bufctl_t数组+对象列表
3)这个变量在分配对象的过程中被使用。
例如kmalloc()函数在分配出去的一个对象之后查看它所对应的kmem_bufctl_t结构就可以得到下一个slab块中空闲对象的下标;slabp->free = slab_bufctl(slabp)[slabp->free];
slab_bufctl()是一个宏,它将指向slab块结构的指针slabp转换指向块内包含的kmem_bufctl_t类型数组的数组头指针。所以这个操作将slab块中下一个空闲对象的对象下标free更新为保存在当前空闲对象的kmem_bufctl_t中的那个值,也就是下一个空闲对象的对象下标。
-
数据结构 slab_s:
管理slab块的数据结构,构成组成 list、colouroff、 *s_mem 、inuse 、free,一个cashe中的每个slab块都包含了这样的一个结构体。对于on slab块,这个结构体位移每个slab块起始位置,对于off slab块,它位于一块公共的cashe(cashe_slabp)中。
1)list ;在同一个cache中的各个slab块进行链表连接的数就结构
2)colouroff;slab块中的染色体偏移量,即当前slab块中第一个对象距离slab块所在页面的页面边界位置偏移量。对应on slab方式,这个偏移量的内存区域内依次存放了slab块的染色区(也就是这slab块内起始地址的页内偏移量),一个slab_t结构的管理变量,以及与此slab块中对象个数相当的kmem_buf_t类型变量。
3)*s_mem;指向slab块中第一个对象的指针。
4)inuse;在此slab块中活动对象的个数。
5)free;在此slab块中第一个空闲对象距离s_mem所指向对象的偏移量。也就是把所有slab块中的对象看作存放在一个以s_mem指向开头的对象数组(重点)中时,free就是当前第一个空闲对象在此数组中的下标。 -
数据结构 kmem_cache_s:
1)slabs;每个缓存区的所有slab通过双向链表进行连接,所有slab块排列在此链表的最前面,部分slab紧接着后面,完全空闲块位于链表最后的位置。在进行空闲块分配与释放的同时对slab块位置进行动态调整。
2)* firstnotfull;这是一个所有非完全slab块构成的链表的表头,也就是指向第一个非完全活动slab块的指针。在分配对象空间的时候将这个指针所指向slab块开始查找空闲对象。
3)objsize;此slab块中对象的大侠。
4)flags;属性标志;
SLAB_POISON:对此缓存区未初始化区域用特殊字节模式填充如A5A5A5A5;
SLAB_RED_ZONE:如果此标志被设置,则使用“红色区域”包围所分配的内存,也就是在对象在起始位置前的一个字节和结束位置后的一个字节都放置一个magic number,用以辨别这个对象当前的活动状态;
SLAB_NO_REEAP;在内存紧缺时也不自动收缩这个缓冲区;
SLAB_HWCACHE_ALIGN;将此cache中的对象使用硬件CACHELIE对齐;
CFLAGS_OFF_SLAB;为off slab方式。
5)num;此缓存区中每个slab中对象的个数。
6)spinlock;互斥锁,所有cache都位于一条链表上,在访问cache时使用这个互斥信号量来避免同事访问这条链表。
7)gfporder;该缓存区每个slab块中包含页面个数对于2的幂次方。
8)gfpflags;申请页面时用到的申请优先级。
9)colour/colour off /colour next;缓存区的染色机制。
10)* slabp_cache;对于off slab方式此指针指向一块公共的cache_slabp缓存区,此缓存区用以存放与每个slab块相对应的slab_t结构。
- growing;正在对此缓存区增加的新的slab块的时候设置此标志,用于防止同时对此slab块的收缩操作。
12)void (* ctor)(void ,kmem_cache_t * ,unsigned long) ;对象的构造函数,在创建新slab块之后给每个块对象调用这个构造函数。
13)void ( dtor)(void *,kmem_cache_t * ,unsigned long) ;对象的析构函数。
14)name[CACHE_NAMELEN];此缓存区的名字,它将在/proc/sabinfo文件中。
15)next;链接下一个缓存区结构的指针。
五、缓存区的创建与销毁操作
-
函数名称:kmem_cache_create()创建
例子:uid_cachep = kmem_cache_create(“uid_cache”,sizeof(struct user_struct),0,SLAB_HWCACHE_ALIGN,NULL,NULL)
1)入口参数:name是标识名称、size是缓存区对象的大小、offset四页内偏移量、flags是缓存区slab的标志位、ctor和dtor分别是构造函数和析构函数指针;创建成功后返回缓存区指针,否则返回NULL
2)进行错误检查,如果有错误打印内核错误信息并发出蜂鸣生;
3)调用kmem_cache_alloc()函数从cache_cache中分配一个对象。由于cache_cache是专门存放kmem_cache_t类型对象的缓存区;
4)如果需要硬件对齐,将align赋值为CPU的L1级缓存区所使用的字长,否则align为CPU字长;
5)根据对象大小,设置为off slab 或者 on slab模式;
6)更具对象大小选择每个slab的大小,以最少能容纳一个对象为依据;并保正2^x的屋里片内存大小;
7)计算缓存区每个slab的对象个数和剩余空间;
8)…
9)将新建成的缓存区挂在cache_chain链表中,cache_chain链表是起始于全局变量cache_cache的next区域,由所有缓存区作为成员的链表。每次新建的缓存区将挂在这个链表的紧接着cache_cache.next后面位置。
10)返回缓存区的指针; -
函数名称:kmem_cache_destrory()销毁
1)将这个缓存区从cache_chain 中删除;
2)函数调用释放cachep中完全空闲的slab块,如果此时缓存区中所有的slab都被释放,此函数返回0,否则返回1;
3)如果返回1,说明缓存区的slab块中还有对象仍在使用,打印内核信息;
4)销毁不成功返回1; -
函数名称:kmem_cache_shrink()收缩
1)释放完全空闲的slab块; -
函数名称:kmem_cache_reap()收缩
1)释放空闲的slab块;最多释放80% -
函数名称:kmem_cache_grow()增长
1)新增1个slab块;
六、对象的分配与释放操作
- 函数名称:kmem_cache_alloc()分配