slab源码分析--缓存器的创建
这个函数通常是在内核初始化时进行的,或者在首次加载模块时进行的。
structkmem_cachekmem_cache_create(constcharname,size_tsize,size_talign,unsignedlongflags,
void(ctor)(void,structkmem_cache,unsignedlong),void(dtor)(void,structkmem_cache,unsignedlong));
name参数定义了缓存器的名称,proc文件系统(可以cat/proc/slabinfo查看)使用它标识这个缓存。size参数指定了这个缓存器负责分配的对象大小。align参数定义了每个对象必须的对齐。flags参数指定了为缓存启用的选项。
flags的标志如图:
选项 说明
RED_ZONE 在对象头、尾插入标志,用来支持对缓冲区溢出的检查
SLAB_POISON 使用一种已知模式(如0xa5a5a5a5)填充slab,允许对缓存中的对象进行监视,不过可以在外部进行修改)
SLAB_HWCACHE_ALIGN 指定缓存对象必须与硬件缓存行对齐
?
ctor和dtor是构造函数和析构函数,这不用说。
在创建缓存器之后,kmem_cache_create()函数会返回对它的引用。注意这个函数并没有向缓存器提供任何用来分配对象内存。相反,在试图从缓存器(最初为空)分配对象时,会通过cache_alloc_refill()函数向伙伴系统申请内存。当所有对象都被分配出去后,可以再次这样做。
首先给出该函数的调用机制流程图:
kmem_cache_create()函数:创建缓存器
kmem_cache_zalloc()函数:申请缓存器缓存
__cache_alloc()函数:kmem_cache_alloc和kmalloc申请内存的总接口
__do_cache_alloc()函数:转调函数
____cache_alloc()函数:申请缓存的核心函数
ac=cpu_cache_get()函数,获得本地缓存
if(av->avail),本地缓存足够
取ac末尾最热数据,objp=ac->entry[--ac->avail]
缓存器缓存申请成功
cache_alloc_refill(),重新填充
if(ls->shared),本地共享缓存足够
transfer_objects()函数,本地共享缓存转给本地缓存
成功向ac中转移了至少一个可用对象
retry:在三链中搜寻可分配对象
三链中有空闲对象
cache_grow()函数,从伙伴系统获取
kmem_getpages()和alloc_slabmgmt()函数,并执行retry
yes
no
yes
no
yes
no
下面来看kmem_cache_create()函数的实现:
/
kmem_cache_create-Createacache.
@name:Astringwhichisusedin/proc/slabinfotoidentifythiscache.
@size:Thesizeofobjectstobecreatedinthiscache.
@align:Therequired(必须的的)alignmentfortheobjects.//
@flags:SLABflags
@ctor:Aconstructorfortheobjects.
@dtor:Adestructorfortheobjects(notimplementedanymore).
Returnsaptrtothecacheonsuccess,NULLonfailure.//成功返回cache指针,失败返回空
Cannotbecalledwithinaint,butcanbeinterrupted.//不能在中断中调用,但是可以被打断
The@ctorisrunwhennewpagesareallocatedbythecache
andthe@dtorisrunbeforethepagesarehandedback.
@namemustbevaliduntilthecacheisdestroyed.Thisimpliesthat
themodulecallingthishastodestroythecachebeforegettingunloaded.
Theflagsare//填充标记
%SLAB_POISON-Poison(使污染)theslabwithaknowntestpattern(a5a5a5a5)//使用a5a5a5a5填充这片未初始化区域
tocatchreferencestouninitialisedmemory.
%SLAB_RED_ZONE-Insert`Red''zonesaroundtheallocatedmemorytocheck//添加红色警戒区,检测越界
forbufferoverruns.
%SLAB_HWCACHE_ALIGN-Aligntheobjectsinthiscachetoahardware//物理缓存行对齐,
cacheline.Thiscanbebeneficialifyou''recountingcyclesasclosely
asdavem.
/
//创建缓存器
/gfporder:取值0~11遍历直到计算出cache的对象数量跳出循环,slab由2^gfporder个页面组成
buffer_size:为当前cache中对象经过cache_line_size对齐后的大小
align:是cache_line_size,按照该大小对齐
flags:此处为0,用于标识内置slab还是外置slab
left_over:输出值,记录slab中浪费空间的大小
num:输出值,用于记录当前cache中允许存在的对象数目
/
structkmem_cache
kmem_cache_create(constcharname,size_tsize,size_talign,
unsignedlongflags,
void(ctor)(void,structkmem_cache,unsignedlong),
void(dtor)(void,structkmem_cache,unsignedlong))
{
size_tleft_over,slab_size,ralign;
structkmem_cachecachep=NULL,pc;
/
Sanitychecks...theseareallserioususagebugs.
/
//参数检查,名字不能为NULL,不能在中断中调用本函数(本函数可能会睡眠)
//获取长度不得小于4字节,即CPU字长,获取长度不得大于最大值(我剖析的这个版本是2^25,有的可能是2^22)
if(!name||in_interrupt()||(size size>KMALLOC_MAX_SIZE||dtor){
printk(KERN_ERR"%s:Earlyerrorinslab%s\n",__FUNCTION__,
name);
BUG();
}
/
Weusecache_chain_mutextoensureaconsistentviewof
cpu_online_mapaswell.Pleaseseecpuup_callback
/
mutex_lock(&cache_chain_mutex);
#if0//DEBUG部分被我注释掉了,免得挡点//一些检查机制,无需关注
...
#endif
/
Checkthatsizeisintermsof(依据)words.Thisisneededtoavoid
unalignedaccessesforsomearchs(拱)whenredzoningisused,andmakes//避免当红色警戒区被使用时,避免未对齐的访问接触红区
sureanyon-slabbufctl''sarealsocorrectlyaligned.//同时确保任何on-slab的bfclt正确对齐
/
////
//为什么kmem_cache_init函数已经计算过size,align了,这里还要计算?
//因为这里是用来创建缓存器的,只是借用了cache_cache,而kmem_cache_init函数中初始化的是cache_cahce的
//size,align等成员,所以无关系。
////
//先检查对象!!!!是不是32位对齐,如果不是则进行调整
if(size&(BYTES_PER_WORD-1)){
size+=(BYTES_PER_WORD-1);
size&=~(BYTES_PER_WORD-1);
}
/calculatethefinalbufferalignment:/
/1)archrecommendation:canbeoverriddenfordebug/
//再检查对象!!!要不要求按照缓冲行对齐
if(flags&SLAB_HWCACHE_ALIGN){
/
Defaultalignment:asspecifiedbythearchcode.Exceptif
anobjectisreallysmall,thensqueezemultipleobjectsinto
onecacheline.
/
ralign=cache_line_size();
while(size<=ralign/2)//进行对齐大小的调整,我们要保证对象的大小大于针对硬件缓冲行对齐所需的大小
ralign/=2;
}else{//不需要按硬件缓冲行对齐,那就默认4字节,即32位
ralign=BYTES_PER_WORD;
}
/
Redzoninganduserstorerequirewordalignmentorpossiblylarger.
Notethiswillbeoverriddenbyarchitectureorcallermandated
alignmentifeitherisgreaterthanBYTES_PER_WORD.
/
//如果开启了DEBUG,则按需要进行相应的对齐
if(flags&SLAB_STORE_USER)
ralign=BYTES_PER_WORD;
if(flags&SLAB_RED_ZONE){
ralign=REDZONE_ALIGN;
/Ifredzoning,ensurethatthesecondredzoneissuitably
aligned,byadjustingtheobjectsizeaccordingly./
size+=REDZONE_ALIGN-1;
size&=~(REDZONE_ALIGN-1);
}
/2)archmandatedalignment/
if(ralign ralign=ARCH_SLAB_MINALIGN;
}
/3)callermandatedalignment/
if(ralign ralign=align;
}
/disabledebugifnecessary/
if(ralign>__alignof__(unsignedlonglong))
flags&=~(SLAB_RED_ZONE|SLAB_STORE_USER);
/
4)Storeit.
/
align=ralign;//通过上面一大堆计算,算出了align值
/Getcache''sdescriptionobj./
//按照cache_cache的大小分配一个kmem_cache新实例,实际上cache_cache在内核初始化完成后就是kmem_cache了,为了内核初始化时可使用kmalloc,所以这里要用cache_cache
cachep=kmem_cache_zalloc(&cache_cache,GFP_KERNEL);//哈哈,这就是使用cache_cache
//这里会分配一块干净的清零过的内存
if(!cachep)
gotooops;
#ifDEBUG
...
#endif
/
Determineiftheslabmanagementis''on''or''off''slab.
(bootstrappingcannotcopewithoffslabcachessodon''tdo
ittooearlyon.)
/
//第一个条件通过PAGE_SIZE确定slab管理对象的存储方式,内置还是外置。
//初始化阶段采用内置式(kmem_cache_init()中创建两个普通高速缓存后就把slab_early_init置0了
if((size>=(PAGE_SIZE>>3))&&!slab_early_init)
/
Sizeislarge,assumebesttoplacetheslabmanagementobj
off-slab(shouldallowbetterpackingofobjs).
/
flags|=CFLGS_OFF_SLAB;
size=ALIGN(size,align);//从这一步可知,slab机制先把对象针对及其字长进行对齐,然后再在此基础上又针对硬件缓冲行进行对齐。
//以后所有的对齐都要照这个总的对齐值对齐
//计算碎片大小,计算slab由几个页面(order)组成,同时计算每个slab中有多少个对象
left_over=calculate_slab_order(cachep,size,align,flags);//这次计算的不是cache_cache了
if(!cachep->num){
printk(KERN_ERR
"kmem_cache_create:couldn''tcreatecache%s.\n",name);
kmem_cache_free(&cache_cache,cachep);
cachep=NULL;
gotooops;
}
//计算slab管理对象的大小,包括structslab对象和kmem_bufctl_t数组
slab_size=ALIGN(cachep->numsizeof(kmem_bufctl_t)
+sizeof(structslab),align);
/
Iftheslabhasbeenplacedoff-slab,andwehaveenoughspacethen
moveiton-slab.Thisisattheexpenseofanyextracolouring.
/
//如果是一个外置slab,并且碎片大小大于slab管理对象的大小,则可将slab管理对象移到slab中,改造成一个内置slab!!!!!
if(flags&CFLGS_OFF_SLAB&&left_over>=slab_size){
flags&=~CFLGS_OFF_SLAB;
left_over-=slab_size;//slab_size就是slab管理对象大小
}
if(flags&CFLGS_OFF_SLAB){
//align是针对slab对象的,如果slab管理者是外置存储,自然也不会像内置那样影响到后面slab对象的存储位置
//slab管理者也就不需要对齐了
/reallyoffslab.Noneedformanualalignment/
slab_size=
cachep->numsizeof(kmem_bufctl_t)+sizeof(structslab);
}
//着色块单位,为L1_CACHE_BYTES,即32字节
cachep->colour_off=cache_line_size();
/Offsetmustbeamultipleofthealignment./
//着色单位必须是对齐单位的整数倍
if(cachep->colour_off cachep->colour_off=align;
//计算碎片区域需要多少个着色块
cachep->colour=left_over/cachep->colour_off;
//管理对象的大小
cachep->slab_size=slab_size;
cachep->flags=flags;
cachep->gfpflags=0;
if(CONFIG_ZONE_DMA_FLAG&&(flags&SLAB_CACHE_DMA))//与伙伴系统交互的DMA标志
cachep->gfpflags|=GFP_DMA;
//slab对象的大小
cachep->buffer_size=size;
//倒数
cachep->reciprocal_buffer_size=reciprocal_value(size);
//如果是外置slab,这里要分配一个管理对象,保存在slabp_cache中,如果是内置式的slab,此指针为空
//array_cachine,cache_cache,3list这几个肯定是内置式,不会进入这个
if(flags&CFLGS_OFF_SLAB){
cachep->slabp_cache=kmem_find_general_cachep(slab_size,0u);
/
Thisisapossibilityforoneofthemalloc_sizescaches.
Butsincewegooffslabonlyforobjectsizegreaterthan
PAGE_SIZE/8,andmalloc_sizesgetscreatedinascendingorder,
thisshouldnothappenatall.
ButleaveaBUG_ONforsomeluckydude.
/
BUG_ON(!cachep->slabp_cache);
}
//kmem_cach的名字和它管理的对象的构造函数
cachep->ctor=ctor;
cachep->name=name;
//设置每个CPU上的localcache,配置localcache和slab三链
if(setup_cpu_cache(cachep)){
__kmem_cache_destroy(cachep);
cachep=NULL;
gotooops;
}
//将kmem_cache加入到cache_chain为头的kmem_cache链表中
/cachesetupcompleted,linkitintothelist/
list_add(&cachep->next,&cache_chain);//还是用了cache_chain
oops:
if(!cachep&&(flags&SLAB_PANIC))
panic("kmem_cache_create():failedtocreateslab`%s''\n",
name);
mutex_unlock(&cache_chain_mutex);//mutex
returncachep;//返回该kmem_cache
}
在这个函数中,首先要计算一些对齐的值。一是内存对齐,我们需要数据按照CPU字长进行对齐(比如结构体中间一个char类型数据32位下所占字节可能是4字节),这样才能提高CPU访问数据的效率。其次如果设置了SLAB_HWCACHE_ALIGN,那么还要和缓存行(cachineline)进行对齐。和缓存行除此之外,除了需要DEBUG,可能还需要一些对齐,不过这都不是我们关注的重点。
总之,前面一大串就是计算出了slab的对齐值。
然后调用kmem_cache_zalloc()函数为要创建的缓存器申请内存,我们知道,先前我们定义了cache_cache这个缓存器的缓存器,此时就派上用场了,这里就用它来做参数。
/
kmem_cache_zalloc-Allocateanobject.Thememoryissettozero.
@cache:Thecachetoallocatefrom.
@flags:Seekmalloc().
Allocateanobjectfromthiscacheandsettheallocatedmemorytozero.
Theflagsareonlyrelevantifthecachehasnoavailableobjects.
/
voidkmem_cache_zalloc(structkmem_cachecache,gfp_tflags)
{
//底层调用__cache_alloc函数
voidret=__cache_alloc(cache,flags,__builtin_return_address(0));
if(ret)
memset(ret,0,obj_size(cache));
returnret;
}
它只是转调了一下,负责将申请的内存清零,继续看:
////不管是kmalloc还是kmem_cache_alloc,kmem_cache_zalloc,最终都是调用__cache_alloc函数,这是给调用者分配slab的总接口
static__always_inlinevoid
__cache_alloc(structkmem_cachecachep,gfp_tflags,voidcaller)
{
unsignedlongsave_flags;
voidobjp;
if(should_failslab(cachep,flags))
returnNULL;
//和上面一样都是参数检测
cache_alloc_debugcheck_before(cachep,flags);
local_irq_save(save_flags);
//底层调用__do_cache_alloc函数
objp=__do_cache_alloc(cachep,flags);
local_irq_restore(save_flags);
objp=cache_alloc_debugcheck_after(cachep,flags,objp,caller);
prefetchw(objp);
returno
而这个函数,不管是kmalloc()还是kmem_cache_create()都会汇总到这个底层接口上来。kmalloc()主要是靠提供一个size参数去malloc_sizes[]表中查到大小然后到达这里的。而kmem_cache_create()是通过cache_cache缓存器到达这里的。它们两个只是通过不同途径获知了要申请内存的大小,然后都交由__cache_alloc()函数处理,该函数又调用__do_cache_alloc()函数,如下:
static__always_inlinevoid
__do_cache_alloc(structkmem_cachecachep,gfp_tflags)
{
//转调用___cache_alloc
return____cache_alloc(cachep,flags);
}
转调用下面的:
//分配的重点
staticinlinevoid____cache_alloc(structkmem_cachecachep,gfp_tflags)
{
voidobjp;
structarray_cacheac;
check_irq_off();
//获得缓存的本地高速缓存的描述符array_cache
ac=cpu_cache_get(cachep);//如果是第一个cache_cache,在这里就返回0
//三部曲,第一步:先看localcache中有没有分配的对象,如果有就直接用,没有就只能执行cache_alloc_refill,里面有三部曲的另外两步
if(likely(ac->avail)){
STATS_INC_ALLOCHIT(cachep);//FIXME:空函数,定义是#defineSTATS_INC_ALLOCHIT(x)do{}while(0),这有什么用?
//将avail的值减1,这样avail对应的空闲对象是最热的,即最近释放出来的,更有可能驻留在CPU高速缓存中
ac->touched=1;//slab分配对象是按块分配,这里肯定只要一块,拿走就行了。
//由于ac是记录这至此structarray_cache结构体存放地址,通过ac->entry[]后,我们就得到一个地址,
//这个地址可以看做是为localcache可用对象的首地址,从这里可以看出,是从最后一个对象开始分配的,即LIFO结构。
objp=ac->entry[--ac->avail];
}else{
STATS_INC_ALLOCMISS(cachep);//空函数
objp=cache_alloc_refill(cachep,flags);//为高速缓存内存空间增加新的内存对象,将会执行三部曲的二三步
}
returnobjp;
}
这个函数首先尝试从本地缓存分配一个对象出来,如果没有那就调用cache_alloc_refill()函数尝试从本地共享缓存或者三链中分配对象,调用的cache_alloc_refill()如下:
staticvoidcache_alloc_refill(structkmem_cachecachep,gfp_tflags)
{
intbatchcount;
structkmem_list3l3;
structarray_cacheac;
intnode;
node=numa_node_id();
check_irq_off();
//本低高速缓存变量
ac=cpu_cache_get(cachep);
retry:
//准备填充本地高速缓存,这里先记录填充对象个数,即batchcount成员(批量转入转出的个数)
batchcount=ac->batchcount;
if(!ac->touched&&batchcount>BATCHREFILL_LIMIT){
/
Iftherewaslittlerecentactivityonthiscache,then
performonlyapartialrefill.Otherwisewecouldgenerate
refillbouncing.
/
batchcount=BATCHREFILL_LIMIT;
}
//获得本内存节点的kmem_cache的三链
l3=cachep->nodelists[node];
BUG_ON(ac->avail>0||!l3);
spin_lock(&l3->list_lock);
/Seeifwecanrefillfromthesharedarray/
//三步曲之第二步:
//如果有本地共享高速缓存,则从共享本地高速缓存填充,仅用于多核,多个CPU共享的高速缓存
if(l3->shared&&transfer_objects(ac,l3->shared,batchcount))
gotoalloc_done;//一次性只会申请一个块,分配成功就跳去alloc_down,ac->avail已经被修改
//三步曲第三步:从本地的高速缓存的三链中分配
while(batchcount>0){
structlist_headentry;
structslabslabp;
/Getslaballocistocomefrom./
//首先访问部分满的slab链表,是entry指向第一个节点
entry=l3->slabs_partial.next;
//如果部分满链表的都没了,就找全空闲的
if(entry==&l3->slabs_partial){
//在访问全空闲slab链表前先做一个标记,表明全空闲slab链表被使用过了
l3->free_touched=1;
//使entry指向全空闲链表第一个节点
entry=l3->slabs_free.next;
//全空闲的也没了,也就是我们的三步曲都失败了,则必须扩充了:)
if(entry==&l3->slabs_free)
gotomust_grow;
}
//上面出来的只有两种情况就是部分满的链表或者全空闲的链表有可分配对象,我们就去该链,准备调整
slabp=list_entry(entry,structslab,list);
//底下是两个空函数
check_slabp(cachep,slabp);
check_spinlock_acquired(cachep);
/
Theslabwaseitheronpartialorfreelistso
theremustbeatleastoneobjectavailablefor
allocation.
/
BUG_ON(slabp->inuse<0||slabp->inuse>=cachep->num);
//如果三链中还存在对象未分配,就把它填充到ac中,最多batchcount个
while(slabp->inusenum&&batchcount--){
//一般情况下下面是三个空函数
STATS_INC_ALLOCED(cachep);
STATS_INC_ACTIVE(cachep);
STATS_SET_HIGH(cachep);
//从slab中提取空闲对象,插入到ac中
ac->entry[ac->avail++]=slab_get_obj(cachep,slabp,
node);
}
check_slabp(cachep,slabp);//空函数
/moveslabptocorrectslabplist:/
//由于三链在上面会分配出部分对象,所以在此处需要调整。如果slab中没有空闲对象,添加到三链的full链表;
//还有空闲对象,添加到三链的partialslab链表中
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:
//前面从三链中添加了avail个空闲对象到ac中,此时需要更新三链的空闲对象数
l3->free_objects-=ac->avail;
alloc_done:
spin_unlock(&l3->list_lock);
//此处有可能之前sharedarray通过transfer给了ac一部分,也不会进入这里了
//三链转移成功也不会进入了
if(unlikely(!ac->avail)){
intx;
//使用cache_grow为高速缓存分配一个新的slab
//参数分别是:cache指针、标志、内存节点、页虚拟地址(为空表示还未申请内存页,不为空,说明已申请内存页,可直接用来创建slab)
//返回值:1为成功,0为失败
x=cache_grow(cachep,flags|GFP_THISNODE,node,NULL);
/cache_growcanreenableinterrupts,thenaccouldchange./
//上面的操作使能了中断,此期间localcache指针可能发生了变化,需要重新获得
ac=cpu_cache_get(cachep);
//如果cache_grow失败,localcache中也没有空闲对象,说明我们无法分配内存了,那就挂了:)
if(!x&&ac->avail==0)/noobjectsinsight?abort/
returnNULL;
//如果ac中的可用对象为0,我们就把上面通过伙伴系统给三链的slab分给ac一部分,毕竟per-cpu优先嘛
//不过,只要ac_>avail不为0,说明其他进程填充了ac,因为本进程只填充给链表,所以不许要再分配对象给ac了,不用执行retry
if(!ac->avail)/objectsrefilledbyinterrupt?/
gotoretry;
}
//重填了localcache,即ac,设置近期访问标志touch
ac->touched=1;
//我们需要返回ac中最后一个对象的地址,因为从localcache中分配对象总是最优的
returnac->entry[--ac->avail];
}
该函数先视察本地共享缓存,如果没有,再看三链。三链也没有,那就只能通过伙伴系统分配内存了,并创建新的可用的的slab。分配好后,转到ac中,毕竟ac是本地缓存,ac的效率最高。然后返回ac的末尾就行了,内存就分配好了。
不过,我们还是看一下cache_grow()函数如何实现的:
/
Grow(by1)thenumberofslabswithinacache.Thisiscalledby
kmem_cache_alloc()whentherearenoactiveobjsleftinacache.
/
staticintcache_grow(structkmem_cachecachep,
gfp_tflags,intnodeid,voidobjp)
{
structslabslabp;
size_toffset;
gfp_tlocal_flags;
structkmem_list3l3;
/
Belazyandonlycheckforvalidflagshere,keepingitoutofthe
criticalpathinkmem_cache_alloc().
/
BUG_ON(flags&~(GFP_DMA|GFP_LEVEL_MASK));
local_flags=(flags&GFP_LEVEL_MASK);
/Takethel3listlocktochangethecolour_nextonthisnode/
//确定关了中断
check_irq_off();
//获取本节点cache分配器的slab三链
l3=cachep->nodelists[nodeid];
spin_lock(&l3->list_lock);
/Getcolourfortheslab,andcalthenextvalue./
//获取待创建slab的着色数目
offset=l3->colour_next;
//获取完了要++,更新下一次要创建的slab的着色数目
l3->colour_next++;
if(l3->colour_next>=cachep->colour)
//颜色编号必须小于颜色个数,如果超过了,重置为0,这就是着色循环问题。事实上,如果slab中浪费的空间很少,那么很快就会循环一次
l3->colour_next=0;
spin_unlock(&l3->list_lock);
//该cache块的着色偏移,注意=
offset=cachep->colour_off;//colour_off是单位
//使用了_GFP_WAIT就会开启中断
if(local_flags&__GFP_WAIT)
local_irq_enable();
/
Thetestformissingatomicflagisperformedhere,ratherthan
themoreobviousplace,simplytoreducethecriticalpathlength
inkmem_cache_alloc().Ifacallerisseriouslymis-behavingthey
willeventuallybecaughthere(whereitmatters).
/
//检查与伙伴系统交互的标记
kmem_flagcheck(cachep,flags);
/
Getmemfortheobjs.Attempttoallocateaphysicalpagefrom
''nodeid''.
/
//从buddy获取物理页,返回的是虚拟地址objp
if(!objp)
objp=kmem_getpages(cachep,flags,nodeid);
if(!objp)//失败就failed
gotofailed;
/Getslabmanagement./
//获得一个新的slab描述符
slabp=alloc_slabmgmt(cachep,objp,offset,
local_flags&~GFP_THISNODE,nodeid);
if(!slabp)
gotoopps1;
//设置节点id
slabp->nodeid=nodeid;
//把slab描述符slabp赋给物理的prev字段,把高速缓存描述符cachep赋给物理页的LRU字段
//本质是建立slab和cache到物理页的映射,用于快速根据物理页定位slab描述符和cache描述符
slab_map_pages(cachep,slabp,objp);
//初始化cache描述符合slab对象描述符
cache_init_objs(cachep,slabp);
if(local_flags&__GFP_WAIT)
local_irq_disable();
check_irq_off();
spin_lock(&l3->list_lock);
/Makeslabactive./
//把上面初始化好的slab尾插法加入到三链的全空链表
list_add_tail(&slabp->list,&(l3->slabs_free));
STATS_INC_GROWwww.tt951.comN(cachep);
//更新高速缓存中空闲对象计数器
l3->free_objects+=cachep->num;
spin_unlock(&l3->list_lock);
return1;
opps1:
kmem_freepages(cachep,objp);
failed:
if(local_flags&__GFP_WAIT)
local_irq_disable();
return0;
}
该函数与伙伴系统交互的函数有:
/
Interfacetosystem''spageallocator.Noneedtoholdthecache-lock.
Ifwerequesteddmaablememory,wewillgetit.Evenifwe
didnotrequestdmaablememory,wemightgetit,butthat
wouldberelativelyrareandignorable.
/
staticvoidkmem_getpages(structkmem_cachecachep,gfp_tflags,intnodeid)
{
structpagepage;
intnr_pages;
inti;
#ifndefCONFIG_MMU
/
Nommuusesslab''sforprocessanonymousmemoryallocations,andthus
requires__GFP_COMPtoproperlyrefcounthigherorderallocations
/
flags|=__GFP_COMP;
#endif
flags|=cachep->gfpflags;
//从buddy获取物理页,大小由cachep->gfporder决定(2^cachep->gfporder)
page=alloc_pages_node(nodeid,flags,cachep->gfporder);
if(!page)
returnNULL;
//计算出要获取的物理页个数(2^cachep->gfporder)
nr_pages=(1<gfporder);
//设置页的状态(是否可回收),在vmstat中设置
if(cachep->flags&SLAB_RECLAIM_ACCOUNT)
add_zone_page_state(page_zone(page),
NR_SLAB_RECLAIMABLE,nr_pages);
else
add_zone_page_state(page_zone(page),
NR_SLAB_UNRECLAIMABLE,nr_pages);
//把这些物理页设置属性为slab
for(i=0;i __SetPageSlab(page+i);
returnpage_address(page);
}
上述这个是为slab分配页面的,下面是处理slab管理者的函数:
/
Getthememoryforaslabmanagementobj.
Foraslabcachewhentheslabdescriptorisoff-slab,slabdescriptors
alwayscomefrommalloc_sizescaches.Theslabdescriptorcannot
comefromthesamecachewhichisgettingcreatedbecause,
whenwearesearchingforanapprowww.baiyuewang.netpriatecacheforthese
descriptorsinkmem_cache_create,wesearchthroughthemalloc_sizesarray.//通过malloc_sizes表
Ifwearecreatingamalloc_sizescachehereitwouldnotbevisible(明显的)to
kmem_find_general_cacheptilltheinitializationiscomplete.
Hence(因此,今后)wecannothaveslabp_cachesameastheoriginalcache.
/
staticstructslaballoc_slabmgmt(structkmem_cachecachep,voidobjp,
intcolour_off,gfp_tlocal_flags,
intnodeid)
{
structslabslabp;
//如果是外置slab
if(OFF_SLAB(cachep)){
/Slabmanagementobjisoff-slab./
//外置slab我们分配到slabp指针上,在外面创建一个slab:)
slabp=kmem_cache_alloc_node(cachep->slabp_cache,
local_flags&~GFP_THISNODE,nodeid);
if(!slabp)
returnNULL;
}else{
//对于内置slab,slab描述符就在该slab所占空间的起始,即所占虚拟起始地址加上它的着色偏移
//然后应该更新该着色偏移即加上管理对象的空间
slabp=objp+colour_off;
colour_off+=cachep->slab_size;
}
//注意上面有两种情况,如果slab管理者是外置的,在上面slabp的地址可能改变。不过下面不考虑这些,直接在slabp地址上设置slab管理者就行了
slabp->inuse=0;
//对于第一个对象页内偏移,由上面可知如果是内置式slab,colouroff成员不仅包括着色区,还包括管理对象占用的空间
//对于外置式slab,colouroff成员只包括着色区
slabp->colouroff=colour_off;
//第一个对象的虚拟地址,这时着色偏移对于内置slab已加上了管理对象的空间
slabp->s_mem=objp+colour_off;
//更新节点id
slabp->nodeid=nodeid;
returnslabp;
}
没错,对于外置式slab,slab管理者或为自己通过缓存器单独分配一块内存,只不过他的s_mem指针依旧指向了被管理的对象而已。
关于着色
由于slab都是整数页大小,在硬件高速缓存中,同一种slab的相同对象会被映射在相同的缓存行。在交错访问多个同一种类型的slab时,就会造成缓存频繁冲突不命中。同一缓存行会被频繁的换入换出,产生缓存颠簸(cachethrashing),会极大影响系统性能。所以我们通过在每个slab前方着色,也就是采取一段偏移,一般情况下,大小为iL1_CACHE_BYTES(L1缓存行大小),i从零开始逐步增加,增加到一定程度回到零。这样就可以将每个slab对象错开一个。这样即使频繁访问,也不会造成缓存颠簸,有效利用了硬件高速缓存。
不过由于i从零到一定程度再回到零,这就是着色循环,可见多多少少还是会有缓存冲突的,只不过概率比不着色小了一些。
关于着色偏移计算的代码在cache_grow()中,因为该函数从伙伴系统申请并创建了新的slab,如下:
/Getcolourfortheslab,andcalthenextvalue./
//获取待创建slab的着色数目
offset=l3->colour_next;
//获取完了要++,更新下一次要创建的slab的着色数目
l3->colour_next++;
if(l3->colour_next>=cachep->colour)
//颜色编号必须小于颜色个数,如果超过了,重置为0,这就是着色循环问题。事实上,如果slab中浪费的空间很少,那么很快就会循环一次
l3->colour_next=0;
spin_unlock(&l3->list_lock);
//该cache块的着色偏移,注意=
offset=cachep->colour_off;//colour_off是单位
colour_next是三链的成员,用来标志下一个slab要偏移几个单位的coulour。而colour是缓存器的成员,它是一个缓存器所管理的所有slab着色单位大小。这是在申请缓存器时,在kmem_cache_create()函数中已经决定过的值,不过特殊地方就在这里,下面截取了kmem_cache_create()函数中求colour_off的代码:
if(flags&SLAB_HWCACHE_ALIGN){
...
//如果开启了DEBUG,则按需要进行相应的对齐
if(flags&SLAB_STORE_USER)
ralign=BYTES_PER_WORD;
if(flags&SLAB_RED_ZONE){
...
}
//着色块单位,为L1_CACHE_BYTES,即32字节
cachep->colour_off=cache_line_size();
/Offsetmustbeamultipleofthealignment./
//着色单位必须是对齐单位的整数倍
if(cachep->colour_off cachep->colour_off=align;
//计算碎片区域需要多少个着色块
cachep->colour=left_over/cachep->colour_off;
分析一下,首先coulour_off赋值为L1缓存行大小。但是,我们的对齐单位不一定就是L1缓存行大小,还要考虑内存对齐,DEBUG对齐(如RED_ZONE)。kmem_cache_create()函数中一开始就在检测各种机制,计算对齐值align,所以在此处,如果L1缓存行大小小于计算出来的align,我们选择更大的也能解决缓存行冲突,所以换更大的并不矛盾。不过,更重要的是下面一句,left_over/cachep->colour_off,这是用剩余碎片长度除以对齐单位,这样求得着色块的数目甚至可能为0!也就是不会进行着色。
结论
一般情况下,slab着色对齐值是L1缓存行大小,由于内存对齐等其他因素可能更大,不过在slab中剩余碎片大小不足时,那么不会进行着色。
其他辅助函数:
calculate_slab_order()函数:
/
calculate_slab_order-calculatesize(pageorder)ofslabs
@cachep:pointertothecachethatisbeingcreated
@size:sizeofobjectstobecreatedinthiscache.
@align:requiredalignmentfortheobjects.
@flags:slaballocationflags
Alsocalculatesthenumberofobjectsperslab.
Thiscouldbemademuchmoreintelligent.Fornow,trytoavoidusing
highorderpagesforslabs.Whenthegfp()functionsaremorefriendly
towardshigh-orderrequests,thisshouldbechanged.
/
//计算该slab的order,即该slab是由几个页面构成
staticsize_tcalculate_slab_order(structkmem_cachecachep,
size_tsize,size_talign,unsignedlongflags)
{
unsignedlongoffslab_limit;
size_tleft_over=0;
intgfporder;
//首先从order为0开始,知道最大order(KMALLOC_MAX_ORDER)为止
for(gfporder=0;gfporder<=KMALLOC_MAX_ORDER;gfporder++){
unsignedintnum;
size_tremainder;
//通过cache计算函数,如果该order放不下一个size大小的对象,即num为0,表示order太小,需要调整。
//参数:gfproder:slab大小为2^gfporder个页面
//buffer_size:对象大小
//align:对象的对齐方式
//flags:是外置slab还是内置slab
//remainder:slab中浪费的空间碎片是多少
//num:slab中对象的个数
cache_estimate(gfporder,size,align,flags,&remainder,&num);
if(!num)
continue;
//如果slab管理者没有和对象放在一起,并且该order存放对象数量大于每个slab允许存放最大的对象数量,则返回
if(flags&CFLGS_OFF_SLAB){
/
Maxnumberofobjs-per-slabforcacheswhich
useoff-slabslabs.Neededtoavoidapossible
loopingconditionincache_grow().
/
offslab_limit=size-sizeof(structslab);
offslab_limit/=sizeof(kmem_bufctl_t);
if(num>offslab_limit)
break;
}
/Foundsomethingacceptable-saveitaway/
//找到了合适的order,将相关参数保存
cachep->num=num;//slab中的对象数量
cachep->gfporder=gfporder;//order值
left_over=remainder;//slab中的碎片大小
/
AVFS-reclaimableslabtendstohavemostallocations
asGFP_NOFSandwereallydon''twanttohavetobeallocating
higher-orderpageswhenweareunabletoshrinkdcache.
/
//SLAB_RECLAIM_ACCOUNT表示此slab所占页面为可回收的,当内核检测是否有足够的页面满足用户态的需求时,
//此类页面将被计算在内,通过调用kmem_freepages()函数可以将释放分配给slab的页框。
//由于是可回收的,所以不用做后面的碎片检测了。
if(flags&SLAB_RECLAIM_ACCOUNT)
break;
/
Largenumberofobjectsisgood,butverylargeslabsare
currentlybadforthegfp()s.
/
//slab_break_gfp_order初始化为1,即slab最多是2^1=2个页
if(gfporder>=slab_break_gfp_order)
break;
/
Acceptableinternalfragmentation?
/
//slab所占页面的大小是碎片大小的8倍以上,页面利用率较高,可以接收这样的order
if(left_over8<=(PAGE_SIZE< break;
}
//返回碎片大小
returnleft_over;
}
cache_estimate()函数:
/
Calculatethenumberofobjectsandleft-overbytesforagivenbuffersize.
/
//计算对象的数目以及碎片的大小,estimate是估计的意思,第三个参数align=cache_line_size()
staticvoidcache_estimate(unsignedlonggfporder,size_tbuffer_size,
size_talign,intflags,size_tleft_over,
unsignedintnum)
{
intnr_objs;
size_tmgmt_size;
//PAGE_SIZE代表一个页面,slab_size记录需要多少个页面,下面这个式子等效于(1(页面)(2^gfporder))
size_tslab_size=PAGE_SIZE<
/
Theslabmanagementstructurecanbeeitherofftheslabor//有off-slab和on-slab两种方式
onit.Forthelattercase,thememoryallocatedfora
slabisusedfor://这段内存被用来存储:
-Thestructslab//slab结构体
-Onekmem_bufctl_tforeachobject//每个对象的kmem_bufctl_t
-Paddingtorespectalignmentof@align//对齐的大小
-@buffer_sizebytesforeachobject//每个对象的大小
Iftheslabmanagementstructureisofftheslab,thenthe
alignmentwillalreadybecalculatedintothesize.Because//如果是off-slab,align早已被计算出来
theslabsareallpagesaligned,theobjectswillbeatthe//因为所有的页面对齐过了,对象申请时会处在正确的位置
correctalignmentwhenallocated.
/
//对于外置slab,没有slab管理对象问题,直接用申请空间除以对象大小就是对象个数
if(flags&CFLGS_OFF_SLAB){
//外置slab不存在管理对象,全部用于存储slab对象
mgmt_size=0;
//所以对象个数=slab大小/对象大小
nr_objs=slab_size/buffer_size;//注意buffer_size已经和cacheline对齐过了
//对象个数不许超限
if(nr_objs>SLAB_LIMIT)
nr_objs=SLAB_LIMIT;
}else{
/
Ignorepaddingfortheinitialguess.Thepadding
isatmost@align-1bytes,and@buffer_sizeisat
least@align.Intheworstcase,thisresultwill
beonegreaterthanthenumberofobjectsthatfit
intothememoryallocationwhentakingthepadding
intoaccount.
/
//内置式slab,slab管理对象与slab对象都在一片内存中,此时slab页面包含:
//一个structslab对象,一个kmem_bufctl_t类型数组(kmem_bufctl_t数组的项数和slab对象数目相同)
//slab大小需要减去管理对象大小,所以对象个数为剩余大小/(每个对象大小+sizeof(kmem_bufctl_t)),它们是一一匹配的关系
nr_objs=(slab_size-sizeof(structslab))/
(buffer_size+sizeof(kmem_bufctl_t));
/
Thiscalculatednumberwillbeeithertheright
amount,oronegreaterthanwhatwewant.
/
//如果对齐后超过slab总大小,需要减去一个对象
if(slab_mgmt_size(nr_objs,align)+nr_objsbuffer_size
>slab_size)
nr_objs--;
//对象个数不许超限
if(nr_objs>SLAB_LIMIT)
nr_objs=SLAB_LIMIT;
//计算管理对象以缓存行对齐后的总大小
mgmt_size=slab_mgmt_size(nr_objs,align);
}
//得出slab最终对象个数
num=nr_objs;
//前面已经得到了slab管理对象大小(外置为0,内置也已计算),这样就可以最终的出slab最终浪费空间大小
left_over=slab_size-nr_objsbuffer_size-mgmt_size;
}
用于找某个大小对应的缓存器的函数:
staticstructkmem_cachekmem_find_general_cachep(size_tsize,gfp_tgfpflags)
{
return__find_general_cachep(size,gfpflags);
}
调用:
staticinlinestructkmem_cache__find_general_cachep(size_tsize,
gfp_tgfpflags)
{
structcache_sizescsizep=malloc_sizes;
#if0
#ifDEBUG
/Thishappensifsomeonetriestocall
kmem_cache_create(),or__kmalloc(),before
thegenericcachesareinitialized.
/
BUG_ON(malloc_sizes[INDEX_AC].cs_cachep==NULL);
#endif
#endif
//这是本函数唯一有用的地方,计算出cache的最适大小
//csizep所指的就是malloc_size数组,该数组内部就是一大堆cache大小的值,最小为LI_CACHE_BYTES为32,最大为2^25=40968192(为什么是25次方?)
while(size>csizep->cs_size)//反正就是各种对象的大小由小到大任你选,不过是选择最合适的
csizep++;
/
Reallysubtle:Thelastentrywithcs->cs_size==ULONG_MAX
hascs_{dma,}cachep==NULL.Thusnospecialcase
forlargekmalloccallsrequired.
/
#ifdefCONFIG_ZONE_DMA
if(unlikely(gfpflags&GFP_DMA))
returncsizep->cs_dmacachep;
#endif
returncsizep->cs_cachep;//返回对应cache_sizes的cs_cachep,也就是一个kmem_cache的描述符指针
}
|
|