Linux内核中基于伙伴算法实现的分区页框分配器适合大块内存的请求,它所分配的内存区是以页框为基本单位的。对于内核中小块连续内存的请求,比 如说几个字节或者几百个字节,如果依然分配一个页框来来满足该请求,那么这很明显就是一种浪费,即产生内部碎片(internal fragmentation)
为了解决小块内存的分配,Linux内核基于Solaris 2.4中的slab分配算法实现了自己的slab分配器。除此之外,slab分配器另一个主要功能是作为一个高速缓存,它用来存储内核中那些经常分配并释放的对象。
Slab分配器的原理
slab分配器中用到了对象这个概念,所谓对象就是内核中的数据结构以及对该数据结构进行创建和撤销的操作。它的基本思想是将内核中经常使用的对象 放到高速缓存中,并且由系统保持为初始的可利用状态。比如进程描述符,内核中会频繁对此数据进行申请和释放。当一个新进程创建时,内核会直接从slab分 配器的高速缓存中获取一个已经初始化了的对象;当进程结束时,该结构所占的页框并不被释放,而是重新返回slab分配器中。如果没有基于对象的slab分 配器,内核将花费更多的时间去分配、初始化以及释放一个对象。
slab分配器有以下三个基本目标:
1.减少伙伴算法在分配小块连续内存时所产生的内部碎片;
2.将频繁使用的对象缓存起来,减少分配、初始化和释放对象的时间开销。
3.通过着色技术调整对象以更好的使用硬件高速缓存;
Slab相关数据结构
Kmem_cache_t是高速缓存描述符,每一类对象都有一个kmem_cache_t,当申请类的对象所需要的内存时,就向对应的kmem_cache_t中取申请,每个kmem_cache_t包含了slabs_full、slabs_prtial、slabs_empty三个链表,分别代表已使用完、部分空闲、完全空闲三个slab块,不过需要注意的是,当刚刚申请一类对象的高速缓存描述符kmem_cache_t时,kmem_cache_t是没有空闲的slab的,当第一次分配slab空间时才会向伙伴系统申请内存页空间。
Kmem_cache_t也是一类slab对象,这类slab是通过cache_chain来申请的,cache_chain的类型也是kmem_cache_t,只不过cache_chain是静态定义的,这就避免了鸡生蛋蛋生鸡的问题。
当该类kmem_cache_t中没有空闲的slab对象时,就会向伙伴系统申请一组连续空闲页空间,这段页空间包含slab偏移、slab描述符、对象描述符和对象空间,关系如下:
其中对象描述符标识了对应对象有没有被分配。
slab分配器初始化
Linux将高速缓存分为普通高速缓存和专用高速缓存
高速缓存又分成两种类型:普通的和专用的。普通高速缓存是slab用于自己目的的缓存,比方kmem_cache就是slab用来分配其余高速缓存描述符的,再比如上面讲到的kmalloc对应的缓存。而专用缓存是内核其它地方用到的缓存。普通缓存调用kmem_cache_init接口来初始化,其中cache_cache保存着第一个缓存描述符。专用缓存调用的是kmem_cache_create接口来创建。
kmem_cache_init
list_add(&cache_cache.next, &cache_chain);
cache_cache.colour_off = cache_line_size(); //256
cache_cache.array[smp_processor_id()] = &initarray_cache.cache; //首先是静态定义的array_cache
cache_cache.objsize = ALIGN(cache_cache.objsize, cache_line_size()); //往上对齐
//计算一页可以保存多少slab的对象,并且还剩多少空间
cache_estimate(0, cache_cache.objsize, cache_line_size(), 0, &left_over, &cache_cache.num);
size_t wastage = PAGE_SIZE<<gfporder;
if (!(flags & CFLGS_OFF_SLAB)) //slab管理结构在slab内部
base = sizeof(struct slab);
extra = sizeof(kmem_bufctl_t); //typedef unsigned short kmem_bufctl_t;
while (i*size + ALIGN(base+i*extra, align) <= wastage)
i++;
if(i > 0) i--;
*num = i;
wastage -= i*size;
wastage -= ALIGN(base+i*extra, align);
*left_over = wastage;
cache_cache.colour = left_over/cache_cache.colour_off;
cache_cache.colour_next = 0;
//一个slab元数据对齐后的大小
cache_cache.slab_size = ALIGN(cache_cache.num*sizeof(kmem_bufctl_t) + sizeof(struct slab), cache_line_size());
sizes = malloc_sizes;
names = cache_names;
//初始化mem_size,用于kmalloc
while(sizes->cs_size)
{
sizes->cs_cachep = kmem_cache_create(names->name, sizes->cs_size, ARCH_KMALLOC_MINALIGN, (ARCH_KMALLOC_FLAGS | SLAB_PANIC), NULL, NULL);
sizes->cs_dmacachep = kmem_cache_create(names->name_dma, sizes->cs_size, ARCH_KMALLOC_MINALIGN, (ARCH_KMALLOC_FLAGS | SLAB_CACHE_DMA | SLAB_PANIC), NULL, NULL);
sizes++;
names++;
}
ptr = kmalloc(sizeof(struct arraycache_init), GFP_KERNEL);
if (__builtin_constant_p(size))
return kmem_cache_alloc((flags & GFP_DMA) ? malloc_sizes[i].cs_dmacachep :malloc_sizes[i].cs_cachep, flags);
else
__kmalloc(size, flags);
memcpy(ptr, ac_data(&cache_cache), sizeof(struct arraycache_init)); //arraycache_init和araycache结构体大小相同
//ac_data为return cachep->array[smp_processor_id()];
cache_cache.array[smp_processor_id()] = ptr; //更换高速缓存指针的内容
ptr = kmalloc(sizeof(struct arraycache_init), GFP_KERNEL);
memcpy(ptr, ac_data(malloc_sizes[0].cs_cachep), sizeof(struct arraycache_init));
malloc_sizes[0].cs_cachep->array[smp_processor_id()] = ptr;
g_cpucache_up = FULL;
从上面可以看到kmem_cache_init的作用有以下几点
(1)初始化kmalloc里各大小的高速缓存描述符
(2)初始化cache_cache里的各项数据,比如对象大小、slab大小。
创建高速缓存
一个新创建的高速缓存没有包含任何slab,因此也没有空闲的对象。只有当以下两个条件都为真时,才给高速缓存分配slab:
(1)已发出一个分配新对象的请求
(2)高速缓存不包含任何空闲对象
实际上,cache_cache就是一个高速缓存,kmem_cache_create就是从cache_cache高速缓存中分配一个kmem_cache_t对象,最初cache_cache中是没有任何slab的,所以先需要像伙伴算法系统申请内存页面。
kmem_cache_create
cachep = (kmem_cache_t *) kmem_cache_alloc(&cache_cache, SLAB_KERNEL); //从cache_cache里申请的slab就是为了存放kmem_cache_t
return __cache_alloc(cachep, flags); //cachep就是 cache_cache
ac = ac_data(cachep); //返回struct array_cache *array[NR_CPUS];中的某项
return cachep->array[smp_processor_id()];
//静态初始化array_cache时,avail为0
if (likely(ac->avail)) //缓存中仍然有之前申请的内存
ac->touched = 1;
objp = ac_entry(ac)[--ac->avail];
else
objp = cache_alloc_refill(cachep, flags){ //第一次扩充的是cache_cache中的slab,返回的是最后插入到cache_array中的最后一个object的指针
ac = ac_data(cachep); //访问cache_cache的array,被初始化为&initarray_cache.cache;
retry:
batchcount = ac->batchcount; //一开始为1
/*
#define list3_data(cachep) (&(cachep)->lists)
*/
l3 = list3_data(cachep);
while (batchcount > 0) //batchcount 初始化为1
entry = l3->slabs_partial.next;
if (entry == &l3->slabs_partial)//先从部分链表找
l3->free_touched = 1;
entry = l3->slabs_free.next; //再从空闲链表找
if (entry == &l3->slabs_free) //两个链表都没有,需要扩充slab
goto must_grow;
slabp = list_entry(entry, struct slab, list);
while (slabp->inuse < cachep->num && batchcount--)
ac_entry(ac)[ac->avail++] = slabp->s_mem + slabp->free*cachep->objsize; //return (void**)(ac+1);
return (void**)(ac+1);
slabp->inuse++;
//更新slabp->free,该值指向下一个空闲的对象
next = slab_bufctl(slabp)[slabp->free]; //return (kmem_bufctl_t *)(slabp+1);
slabp->free = next;
list_del(&slabp->list);
if (slabp->free == BUFCTL_END)
list_add(&slabp->list, &l3->slabs_full);
else
list_add(&slabp->list, &l3->slabs_partial);
must_grow:
l3->free_objects -= ac->avail; //一开始avail为0
alloc_done:
if (unlikely(!ac->avail))
x = cache_grow(cachep, flags, -1){ //cachep最开始为cache_cache
offset = cachep->colour_next;
cachep->colour_next++;
if (cachep->colour_next >= cachep->colour)
cachep->colour_next = 0
offset *= cachep->colour_off;//横跨一个cache line ?
objp = kmem_getpages(cachep, flags, nodeid)//返回页对应的虚拟地址
if (likely(nodeid == -1))
page = alloc_pages(flags, cachep->gfporder);
alloc_pages_node(numa_node_id(), gfp_mask, order)
return __alloc_pages(gfp_mask, order, NODE_DATA(nid)->node_zonelists + (gfp_mask & GFP_ZONEMASK));
else
page = alloc_pages_node(nodeid, flags, cachep->gfporder);
addr = page_address(page); //virtual
while (i--)
SetPageSlab(page);
set_bit(PG_slab, &(page)->flags) //设置flags的第PG_slab位
page++;
return addr;
slabp = alloc_slabmgmt(cachep, objp, offset, local_flags)
slabp = objp+colour_off; //省略部分内存
colour_off += cachep->slab_size; //跨过元数据
slabp->inuse = 0;
slabp->colouroff = colour_off; //该slab内的偏移,也是真实对象的偏移
slabp->s_mem = objp+colour_off; //真实数据开始的位置
return slabp;
set_slab_attr(cachep, slabp, objp);
i = 1 << cachep->gfporder;
page = virt_to_page(objp);
pfn_to_page(__pa(kaddr) >> PAGE_SHIFT)
//#define pfn_to_page(pfn) (mem_map + (pfn))
//#define __pa(x) ((unsigned long)(x)-PAGE_OFFSET)
do {
//#define SET_PAGE_CACHE(pg,x) ((pg)->lru.next = (struct list_head *)(x))
SET_PAGE_CACHE(page, cachep);
((pg)->lru.next = (struct list_head *)(x))
//#define SET_PAGE_SLAB(pg,x) ((pg)->lru.prev = (struct list_head *)(x))
SET_PAGE_SLAB(page, slabp);
((pg)->lru.prev = (struct list_head *)(x))
page++;
}while(--i)
cache_init_objs(cachep, slabp, ctor_flags);
for (i = 0; i < cachep->num; i++)
void* objp = slabp->s_mem+cachep->objsize*i;//获取第i个对象
if (cachep->ctor)
cachep->ctor(objp, cachep, ctor_flags);
slab_bufctl(slabp)[i] = i+1; //return (kmem_bufctl_t *)(slabp+1);,取slabp下一个字节的位置
return (kmem_bufctl_t *)(slabp+1); //跨过slab元数据,直达描述符
slab_bufctl(slabp)[i-1] = BUFCTL_END;
slabp->free = 0;
list_add_tail(&slabp->list, &(list3_data(cachep)->slabs_free)); //刚开始加入到free链表中
list3_data(cachep)->free_objects += cachep->num;
ac = ac_data(cachep);
if(!ac->avail)
goto retry;
}
ac->touched = 1;
return ac_entry(ac)[--ac->avail];
}
objp = cache_alloc_debugcheck_after(cachep, flags, objp, __builtin_return_address(0));
return objp;
cache_estimate(cachep->gfporder, size, align, flags, &left_over, &cachep->num);
cachep->gfporder++;
slab_size = ALIGN(cachep->num*sizeof(kmem_bufctl_t) + sizeof(struct slab), align);
memset(cachep, 0, sizeof(kmem_cache_t));
slab_size = ALIGN(cachep->num*sizeof(kmem_bufctl_t)+ sizeof(struct slab), align);
cachep->colour_off = cache_line_size();
cachep->colour = left_over/cachep->colour_off;
//slab描述符和对象描述符的大小
cachep->slab_size = slab_size;
cachep->flags = flags;
cachep->gfpflags = 0;
cachep->objsize = size;
INIT_LIST_HEAD(&cachep->lists.slabs_full);
INIT_LIST_HEAD(&cachep->lists.slabs_partial);
INIT_LIST_HEAD(&cachep->lists.slabs_free);
cachep->ctor = ctor;
cachep->dtor = dtor;
cachep->name = name;
if (g_cpucache_up == FULL)
enable_cpucache(cachep);
else
if (g_cpucache_up == NONE)
cachep->array[smp_processor_id()] = &initarray_generic.cache;
g_cpucache_up = PARTIAL;
else
//等到第二次运行到这里的适合已经有对应size大小的malloc_sizes[i].cs_cachep了
cachep->array[smp_processor_id()] = kmalloc(sizeof(struct arraycache_init),GFP_KERNEL);//从已经建立的malloc_sizes中获取合适大小的slab对象
if (__builtin_constant_p(size))
return kmem_cache_alloc((flags & GFP_DMA) ? malloc_sizes[i].cs_dmacachep :malloc_sizes[i].cs_cachep, flags);
return __cache_alloc(cachep, flags);
ac = ac_data(cachep);
if (likely(ac->avail))
ac->touched = 1;
objp = ac_entry(ac)[--ac->avail];
else
objp = cache_alloc_refill(cachep, flags);
else
__kmalloc(size, flags);
ac_data(cachep)->avail = 0;
ac_data(cachep)->limit = BOOT_CPUCACHE_ENTRIES;
ac_data(cachep)->batchcount = 1;
ac_data(cachep)->touched = 0;
list_add(&cachep->next, &cache_chain);
return cachep;
kmem_cache_alloc就是从slab中分配指定的对象,但是最初kmem_cache_t中是没有任何slab空间的,所以需要先像伙伴算法系统中申请页空间,我们建立专用高速缓存时,就要先用kmem_cache_create建立高速缓存,然后再调用kmem_cahce_alloc从我们建立的高速缓存中分配对象空间。
释放slab对象
kmem_cache_free
//将对象空间回收到指定的高速缓存中
__cache_free(cachep, objp);
struct array_cache *ac = ac_data(cachep);
return cachep->array[smp_processor_id()];
if (likely(ac->avail < ac->limit))
ac_entry(ac)[ac->avail++] = objp;
else
cache_flusharray(cachep, ac);
batchcount = ac->batchcount;
//释放batchcount个对象空间
free_block(cachep, &ac_entry(ac)[0], batchcount);
cachep->lists.free_objects += nr_objects;
for (i = 0; i < nr_objects; i++)
void *objp = objpp[i];
//((struct slab *)(pg)->lru.prev)指向相应的slab描述符
//如果一个页是分配给slab,则页描述符的lru.prev指向相应的slab描述符
slabp = GET_PAGE_SLAB(virt_to_page(objp));
list_del(&slabp->list);
//计算slab内对象的下标
objnr = (objp - slabp->s_mem) / cachep->objsize;
//将该对象插入到slab空闲“链表”中,并将空闲对象游标指向该对象
slab_bufctl(slabp)[objnr] = slabp->free;
slabp->free = objnr;
slabp->inuse--;
//如果inuse等于0,即slab中所有对象空闲
if (slabp->inuse == 0)
//并且整个slab高速缓存中空闲对象的个数(cachep->lists.free_objects)大于cachep->free_limit
if (cachep->lists.free_objects > cachep->free_limit)
cachep->lists.free_objects -= cachep->num;
//将slab的页框释放到分区页框分配器
slab_destroy(cachep, slabp);
else
//否则函数将slab描述符插入到slabs_free链表中
list_add(&slabp->list, &list3_data_ptr(cachep, objp)->slabs_free);
else
//inuse > 0,slab被部分填充,则将slab描述符插入到slabs_partial中
list_add_tail(&slabp->list,&list3_data_ptr(cachep, objp)->slabs_partial);
ac->avail -= batchcount;
//移动本地高速缓存数组起始处的那个本地高速缓存中的所有指针。
//因为已经把第一个对象指针从本地高速缓存上删除,因此剩余的指针必须上移。
memmove(&ac_entry(ac)[0], &ac_entry(ac)[batchcount],sizeof(void*)*ac->avail);
//更新空闲对象的指针
ac_entry(ac)[ac->avail++] = objp;
通用对象
在kmem_cache_init中,我们还创建了固定大小的高速缓存存放在malloc_sizes中,范围是32~131072字节,当我们调用kmalloc来申请内存时,会从malloc_sizes中第一个大于要分配大小的高速缓存中分配对象空间
kmalloc(size_t size, int flags)
if (__builtin_constant_p(size))
#define CACHE(x) \
if (size <= x) \
goto found; \
else \
i++;
#include "kmalloc_sizes.h"
/**
* 运行到此,说明要分配的对象太大,不能分配这么大的对象。
*/
#undef CACHE
//请求分配的size对应的高速缓存描述符索引号为i,从malloc_sizes[i]中分配对象空间
return kmem_cache_alloc((flags & GFP_DMA) ? malloc_sizes[i].cs_dmacachep : malloc_sizes[i].cs_cachep, flags);
return __kmalloc(size, flags);
for (; csizep->cs_size; csizep++)
if (size > csizep->cs_size)
continue;
//从对应的高速缓存中申请对象空间
return __cache_alloc(flags & GFP_DMA ?csizep->cs_dmacachep : csizep->cs_cachep, flags);