slab分配器形象地说就是先由伙伴机制申请空闲内存空间,然后slab分配器再把这个内存空间进行分割,按同样大小来进行分割,最后再用一些数据结构来进行管理。上面只是形象地说法。我们平时把由slab进行分割后的内存我们称之为高速缓存内存。这样看来就知道所谓的高速就是说不是每次分配和释放内存都要找伙伴机制,可以先访问这些高速内存的,和我们前面文章中提到的冷热区是一个作用的。我们会发现我们原来说的冷热区和伙伴机制,都是以页为单位进行分配的。这就有点限制了,为什么我不可以再按小点的内存去分配呢?所以就产生了slab分配器了。
当我们从高速缓存内存中分配对象时(这里我们不再说内存页了,我们该为对象,即内存对象),首先检查对应处理器的高速缓存内存阵列中是否有未使用的内存对象。什么是处理器的高速缓存内存阵列?--kmem_cache_t结构里面也为每个处理器预留一个高速缓冲内存阵列(struct array_cache),我们都是用其成员*array[NR_CPUS]这个指针数组里面的每个成员都是指向struct array_cache结构变量,紧跟在struct array_cache结构后面的是连续存放的该struct array_cache结构指定个数的对象指针,这些指针指向的对象就是将来要被分配的空间。kmem_cache_t结构就是算是我们说的高速缓存内存的描述结构。
如果我们发现对应处理器的高速缓存内存阵列中有未使用的内存对象,直接把我们刚才说的struct arrary_cache结构指定个数的对象指针传给调用者。如果没有,那么首先查找共享高速缓存内存阵列中是否有可用的对象,在高速缓存内存描述结构(struct kmem_cache_t)中有一个成员可以访问到它,就是struct kmem_list3 lists中的share成员。其实这个是真正多个cpu的时候才会采取的方案。如果发现这里有可用内存对象的话,我们先把内存对象的地址复制到对应处理器的高速缓存内存阵列中,再从处理器的高速缓存内存阵列中分配对象。
一般情况如果共享高速缓存内存阵列中也没有可用对象时,我们就去查找部分空闲和全部空闲slab列表。struct kmem_list3 lists是高速缓存内存描述结构struct kmem_cache_t的成员,这个链表结构就是用来描述上述的slab列表。链表结构描述了三种高速缓存内存单位,由一个struct slab结构管理的一组特定大小的内存对象叫做一个高速缓存内存单位,一个内存单位包括一些大小一致的内存对象、一些色块(不会被调用的,只是起到对齐的用途)、一个struct slab结构、与每个内存对象一一对应的kmem_bufctl_t变量。每个高速缓存内存单元都有或者没有空闲对象。所以链表结构(list)就根据高速缓存内存单元中空闲对象的情况分成3类,list.slabs_partial(部分空闲)
list.slabs_full(没有空闲)、list.slabs_free(全部空闲)。我们会直接看看list.slabs_free和list.slabs_partial链表是否为空,如果不为空,我们就通过第一个struct slab找到对应的高速缓存内存单元,在其中复制一定数量的对象地址到对应处理器的高速缓存内存阵列中,然后我们在到这个阵列里去分配对象。如果上述的链表都为空的,那么就从伙伴系统中申请一个新的高速缓存内存单元,这样就利用这个单元做上述一样的动作。
上面说的是分配的过程,对于释放,其实过程也是类似的,由于篇幅的原因,我这里就不再细讲了,读者可以通过代码自行进行总结。
下面我们先来介绍下高速缓存内存的初始化函数,kmem_cache_init()。以下是它的具体代码。
void __init kmem_cache_init(void)
{
size_t left_over;
struct cache_sizes *sizes;
struct cache_names *names;
if (num_physpages > (32 << 20) >> PAGE_SHIFT)//num_physpages是记录系统实际存在物理内存的总页数,如果大于32M
slab_break_gfp_order = BREAK_GFP_ORDER_HI;//才可以创建高阶指数内存页数的高速缓存内存对象。
init_MUTEX(&cache_chain_sem);//初始化高速缓存内存链表互斥信号量。
list_add(&cache_cache.next, &cache_chain);//把cache_cache的next挂在cache_chain链表上面,cache_chain是一个全局
//链表,cache_cache是kmem_cache_t结构类型变量。这个变量也是一个全局的,
//这个高速缓存内存结构描述的高速缓存对象正是专门为kmem_cache_t结构本身创建的
cache_cache.colour_off = cache_line_size();//为这个描述结构体的色块进行大小定义,必须为cache line大小。
cache_cache.array[smp_processor_id()] = &initarray_cache.cache;//高速缓存内存阵列指针数组array[],我们找到当前处理
//器对应的成员array[0],对这个成员初始化。
cache_cache.objsize = ALIGN(cache_cache.objsize, cache_line_size());//要求高速缓存对象的大小按cache line对齐。
cache_estimate(0, cache_cache.objsize, cache_line_size(), 0,
&left_over, &cache_cache.num);//计算一页内存可以容纳多少个内存对象,同时这个内存页还包括一个struct
//slab和与对象相对应个数的struct kmem_bufctl_t结构体。可容纳的个数赋给
//cache_cache.num,把剩余的内存空间的大小保存在*eft_over中。
if (!cache_cache.num)
BUG();//如果一页内存空间不可以保存任何一个内存对象,则系统崩溃。
cache_cache.colour = left_over/cache_cache.colour_off;//统计这个kmem_cache_t结构体色块的数量。
cache_cache.colour_next = 0;//设置下一个色块的编号为0,这三个变量只是起到调节的作用,不会被系统内存所调用。目的是使
//高速缓存对象的起始地址和辅助变量的起始位置是按cache line对齐的。
cache_cache.slab_size = ALIGN(cache_cache.num*sizeof(kmem_bufctl_t) +sizeof(struct slab), cache_line_size());
//先说说什么是slab管理数据空间,起始就是一个struct slab结构和与缓存对象一一对应的辅助变量结构体
//(kmem_bufctl_t)。slab_size就是描述这个空间的大小。当flags没配置CFLGS_OFF_SLAB标志时,这个
//空间和缓存对象存放在一起,这时要求这个空间必须是按cache line对齐的。反之配置了,两个东西不是存储在
//一块的,同时不要对齐。有上面的cache_estimate()函数知道flags没有配置CFLGS_OFF_SLAB
sizes = malloc_sizes;//struct cache_sizes malloc_sizes[],可以看出现在sizes指向这个指针数组的首地址。malloc_sizes这
//数组的每个成员都描述着linux预先为特定大小对象建立普通高速缓存和dma高速缓存内存。
names = cache_names;//指向上面预先创建的高速缓存内存对应的名字结构。
while (sizes->cs_size) {//遍历整个malloc_sizes[]指针数组,如果cache_sizes结构体的cs_size成员不为空时,执行以下
sizes->cs_cachep = kmem_cache_create(names->name,sizes->cs_size, ARCH_KMALLOC_MINALIGN,
(ARCH_KMALLOC_FLAGS | SLAB_PANIC), NULL, NULL);
//先创建普通内存高速缓存内存,调用kmem_cache_create()函数按ARCH_KMALLOC_MINALIGN方式对齐,内存对象
//大小sizes->cs_size,名叫names->name和一定标志字创建高速缓存内存,函数返回kmem_cache_t结构体指针赋给
//cs_cachep。这个函数我们会在后面分析的。
if (!(OFF_SLAB(sizes->cs_cachep))) {//如果没设置CFLGS_OFF_SLAB,slab管理数据空间要和缓存对象一起存放。
offslab_limit = sizes->cs_size-sizeof(struct slab);
offslab_limit /= sizeof(kmem_bufctl_t);//这两行是计算slab中最多可以包含多少个缓存对象(以cs_size为大小的)
}
sizes->cs_dmacachep=kmem_cache_create(names->name_dma,sizes->cs_size,ARCH_KMALLOC_MINALLGN
,(ARCH_KMALLOC_FLAGS|SLAB_CACHE_DMA|SLAB_PANIC),NULL,NULL);
//创建dma高速缓存内存的描述结构kmem_cache_t的指针赋给cs_dmacachep。
sizes++;//这里是指向malloc_sizes[]下一个指针。
names++;//这里是指向malloc_names[]下一个指针。
}
{
void * ptr;
ptr = kmalloc(sizeof(struct arraycache_init), GFP_KERNEL);//为arraycache_init结构体在内核空间中申请一段空间。
local_irq_disable();//禁止irq中断。
BUG_ON(ac_data(&cache_cache) != &initarray_cache.cache);
memcpy(ptr, ac_data(&cache_cache), sizeof(struct arraycache_init));//ptr中存放了initarray_cache变量的内容,
//同时ptr的改变不会影响到这个全局变量。
cache_cache.array[smp_processor_id()] = ptr;//array[smp_processor_id()]成员指向了上面申请的空间。
local_irq_enable();//使能irq中断。
ptr = kmalloc(sizeof(struct arraycache_init), GFP_KERNEL);//在此申请arraycache_init结构体空间
local_irq_disable();
BUG_ON(ac_data(malloc_sizes[0].cs_cachep) != &initarray_generic.cache);
memcpy(ptr, ac_data(malloc_sizes[0].cs_cachep),
sizeof(struct arraycache_init));//申请的空间用来存放上面说的initarray_generic变量里的内容。
malloc_sizes[0].cs_cachep->array[smp_processor_id()] = ptr;
local_irq_enable();
}
{
kmem_cache_t *cachep;
down(&cache_chain_sem);//获得高速缓存内存链表互斥信号量。
list_for_each_entry(cachep, &cache_chain, next)//遍历整个cache_chain链表,同时通过kmem_cache_t成员next
//找到对应的kmem_cache_t结构体,然后执行下一条语句,它会一直循环到遍历完
//整个cache_chain链表为止。
enable_cpucache(cachep);//根据缓存对象的大小先设置imit值(这个是一个高速缓存内存对象个数的上限)
//然后调用do_tune_cpucache ()函数,先是为每个cpu申请一个arraycache_init结构体大小
//并初始化这个结构体的new[i]成员,然后执行do_ccupdate_local后,把新的赋给cachep的
//当前cpu的缓冲内存阵列。然后又初始化cachep的limit,bacthcount和free_limit成员。剩下就
//是对cache->lists.shared的成员进行初始化,还有为其struct array_cache和limit个对象的
//指针申请空间,用来存放他们的。
up(&cache_chain_sem);//释放高速缓存内存链表互斥信号量。
}
g_cpucache_up = FULL;//将所有cpu的cache使能。
register_cpu_notifier(&cpucache_notifier);//对于不是多处理器时,该函数为空。
}