文章目录
背景
前情回顾
关于slab几个结构体的关系和初始化和内存分配的逻辑请见:
[linux kernel]slub内存管理分析(0) 导读
[linux kernel]slub内存管理分析(1) 结构体
[linux kernel]slub内存管理分析(2) 初始化
[linux kernel]slub内存管理分析(2.5) slab重用
[linux kernel]slub内存管理分析(3) kmalloc
描述方法约定
PS:为了方便描述,这里我们将一个用来切割分配内存的page 称为一个slab page,而
struct kmem_cache
我们这里称为slab管理结构,它管理的真个slab 体系成为slab cache,
struct kmem_cache_node
这里就叫node。单个堆块称为object或者堆块或内存对象。
简介
本篇主要就
kmalloc
中的一些层数比较深并且很多逻辑中都要调用的操作和两个安全加固
CONFIG_SLAB_FREELIST_HARDENED
与
CONFIG_SLAB_FREELIST_RANDOM
进行分析,比如(安全加固下)对
freelist
的操作、
cpu_slab->page
的强制下架等。
本章介绍的内容不影响分析slub算法的整体逻辑,对细节没有兴趣可以跳过。
freelist操作与CONFIG_SLAB_FREELIST_HARDENED
CONFIG_SLAB_FREELIST_HARDENED简介
CONFIG_SLAB_FREELIST_HARDENED
安全加固宏是给
freelist
链表指针进行混淆:
混淆后的指针=原指针 ^ 随机数random ^ 指针地址
直接在内存中/通过内存泄露获得
freelist
和
freelist
成员的
next
指针是混淆后的,避免直接泄露地址或直接修改地址。在每次存取
freelist
指针之前都会进行一次上面的异或操作得到真实指针或从真实指针转换成混淆指针。
CONFIG_SLAB_FREELIST_HARDENED初始化
linux\include\linux\slub_def.h : struct kmem_cache
structkmem_cache{
··· ···
#ifdefCONFIG_SLAB_FREELIST_HARDENEDunsignedlong random;//开启CONFIG_SLAB_FREELIST_HARDENED宏会多一个random 成员#endif
··· ···
};
开启
CONFIG_SLAB_FREELIST_HARDENED
宏会多一个
random
成员,在初始化流程中的
kmem_cache_open
函数中会对
random
进行初始化(完整初始化流程在前文已经分析过了):
linux\mm\slub.c : kmem_cache_open
staticintkmem_cache_open(structkmem_cache*s,slab_flags_t flags){
s->flags =kmem_cache_flags(s->size, flags, s->name);#ifdefCONFIG_SLAB_FREELIST_HARDENED
s->random =get_random_long();//初始化random 成员#endif
··· ···
··· ···
}
这里调用
get_random_long
函数获取一个随机的
long
类型数据,所以
random
就是一个随机数而已。
CONFIG_SLAB_FREELIST_HARDENED实现与freelist相关操作
freelist_ptr 混淆/去混淆指针
freelist_ptr
函数用于将
freelist
中一个
object
的指向下一个
object
的
next
指针从真实值计算出混淆值或从混淆值还原出真实值:
linux\mm\slub.c : freelist_ptr
staticinlinevoid*freelist_ptr(conststructkmem_cache*s,void*ptr,unsignedlong ptr_addr)//[1] 传入参数{#ifdefCONFIG_SLAB_FREELIST_HARDENED/*
* When CONFIG_KASAN_SW/HW_TAGS is enabled, ptr_addr might be tagged.
* Normally, this doesn't cause any issues, as both set_freepointer()
* and get_freepointer() are called with a pointer with the same tag.
* However, there are some issues with CONFIG_SLUB_DEBUG code. For
* example, when __free_slub() iterates over objects in a cache, it
* passes untagged pointers to check_object(). check_object() in turns
* calls get_freepointer() with an untagged pointer, which causes the
* freepointer to be restored incorrectly.
*/return(void*)((unsignedlong)ptr ^ s->random ^//[2] 混淆/去混淆操作swab((unsignedlong)kasan_reset_tag((void*)ptr_addr)));#elsereturn ptr;// [3] 没开启混淆则直接返回#endif}
[1] 传入的
ptr
参数就是要去混淆的指针,
ptr_addr
是该指针的地址。
[2] 混淆公式为:
混淆后的指针=原指针 ^ 随机数random ^ 指针地址
,所以去混淆就是
混淆后的指针 ^ 随机数random ^ 指针地址
。因为是异或,所以操作相同。
[3] 如果没开启
CONFIG_SLAB_FREELIST_HARDENED
则直接返回ptr即可。
get_freepointer 获取next指针
linux\mm\slub.c : get_freepointer
staticinlinevoid*get_freepointer(structkmem_cache*s,void*object){
object =kasan_reset_tag(object);//默认直接返回地址returnfreelist_dereference(s, object + s->offset);//[1]next指针为object+s->offset}staticinlinevoid*freelist_dereference(conststructkmem_cache*s,void*ptr_addr)//[2]直接调用freelist_ptr{returnfreelist_ptr(s,(void*)*(unsignedlong*)(ptr_addr),(unsignedlong)ptr_addr);}
[1] 这里调用
freelist_dereference
,根据参数,传入的
ptr_addr
也就是指向下一个
object
的
next
指针地址是
object + s->offset
,
s->offset
一般是该slab 大小的一半。
[2]
freelist_dereference
函数中直接调用上面分析的
freelist_ptr
获取真实指针
set_freepointer 设置next指针
linux\mm\slub.c : set_freepointer
staticinlinevoidset_freepointer(structkmem_cache*s,void*object,void*fp){unsignedlong freeptr_addr =(unsignedlong)object + s->offset;//获得next指针地址#ifdefCONFIG_SLAB_FREELIST_HARDENEDBUG_ON(object == fp);/* naive detection of double free or corruption */#endif//[1] 这里检测double free
freeptr_addr =(unsignedlong)kasan_reset_tag((void*)freeptr_addr);*(void**)freeptr_addr =freelist_ptr(s, fp, freeptr_addr);//[2] 调用freelist_ptr 混淆指针值}
[1] 首先会在开启
CONFIG_SLAB_FREELIST_HARDENED
的时候检测double free,如果要释放的
object
和
list
指针一样,则是double free,类似用户层
malloc
。
[2] 在给指针复制之前调用了
freelist_ptr
进行混淆操作,如果没有开启混淆,则
freelist_ptr
会直接返回指针原本的值。
CONFIG_SLAB_FREELIST_RANDOM
CONFIG_SLAB_FREELIST_RANDOM简介
CONFIG_SLAB_FREELIST_RANDOM
安全加固是将
freelist
列表顺序随机化,正常
freelist
列表就是从开始往后按顺序排列,但如果开启了
CONFIG_SLAB_FREELIST_RANDOM
则会打乱
freelist
中
object
的顺序,即便是刚申请好的新slab,其中的
freelist
列表顺序也是随机的。
CONFIG_SLAB_FREELIST_RANDOM初始化
linux\include\linux\slub_def.h : struct kmem_cache
structkmem_cache{
··· ···
#ifdefCONFIG_SLAB_FREELIST_RANDOMunsignedint*random_seq;#endif
··· ···
};
开启
CONFIG_SLAB_FREELIST_RANDOM
之后会多一个叫做
random_seq
的数组,以指针成员的形式出现在
kmem_cache
也就是slab 管理结构体中。在
kmem_cache_init
中会进行初始化:
linux\mm\slub.c : kmem_cache_init
void __init kmem_cache_init(void){
··· ···
create_kmalloc_caches(0);//正式初始化各个slab/* Setup random freelists for each cache */init_freelist_randomization();//开启了CONFIG_SLAB_FREELIST_RANDOM需要操作一下
··· ···
}
在
create_kmalloc_caches
函数已经完成了各个slab 的初始化之后,调用
init_freelist_randomization
对所有slab 进行
freelist random
初始化:
linux\mm\slub.c : init_freelist_randomization
staticvoid __init init_freelist_randomization(void){structkmem_cache*s;mutex_lock(&slab_mutex);list_for_each_entry(s,&slab_caches, list)init_cache_random_seq(s);//对slab_caches 列表中所有slab调用init_cache_random_seqmutex_unlock(&slab_mutex);}
每个slab 都会加入
slab_caches
列表,这里对列表中每个成员都调用
init_cache_random_seq
进行初始化:
linux\mm\slub.c : init_cache_random_seq
staticintinit_cache_random_seq(structkmem_cache*s){unsignedint count =oo_objects(s->oo);//[1] 获取该slab中有多少个object对象int err;/* Bailout if already initialised */if(s->random_seq)//已经初始化好就不管了return0;
err =cache_random_seq_create(s, count, GFP_KERNEL);//[2] 调用cache_random_seq_create 对s->random_seq初始化if(err){pr_err("SLUB: Unable to initialize free list for %s\n",
s->name);return err;}/* Transform to an offset on the set of pages */if(s->random_seq){//如果初始化好unsignedint i;for(i =0; i < count; i++)
s->random_seq[i]*= s->size;//[3] s->random_seq此时是打乱的序号,size 是一块内存的大小,相乘得到的就是随机的某一块内存的偏移}return0;}
[1] 调用
oo_objects
获得该slab 中每个slab 中分出的内存块的数量。
[2] 调用
cache_random_seq_create
对没初始化
s->random_seq
的slab 进行初始化,其实就是
s->random_seq
的值赋值成内存块的编号,然后打乱顺序。
[3]
s->random_seq
此时是打乱的序号,
size
是一块内存的大小,相乘得到的就是随机的某一块内存的偏移。后面用于
shuffle_freelist
。
cache_random_seq_create
然后是
cache_random_seq_create
函数,用于制造一个混乱顺序的列表:
linux\mm\slab_common.c : cache_random_seq_create
intcache_random_seq_create(structkmem_cache*cachep,unsignedint count,gfp_t gfp){structrnd_state state;if(count <2|| cachep->random_seq)//小于2 的不需要随机return0;
cachep->random_seq =kcalloc(count,sizeof(unsignedint), gfp);//[1] 申请数组内存count * sizeof(int)if(!cachep->random_seq)return-ENOMEM;/* Get best entropy at this stage of boot */prandom_seed_state(&state,get_random_long());//获取随机种子freelist_randomize(&state, cachep->random_seq, count);//[2] 打乱顺序return0;}
[1] 首先根据该slab 中的内存块数量 申请
random_seq
数组的内存空间
count * sizeof(int)
[2] 然后调用
freelist_randomize
函数制造一个乱序列表,
freelist_randomize
:
linux\mm\slab_common.c : freelist_randomize
staticvoidfreelist_randomize(structrnd_state*state,unsignedint*list,unsignedint count){unsignedint rand;unsignedint i;for(i =0; i < count; i++)
list[i]= i;//[1]先按照下标排序/* Fisher-Yates shuffle */for(i = count -1; i >0; i--){//[2]然后随机打乱顺序
rand =prandom_u32_state(state);
rand %=(i +1);swap(list[i], list[rand]);}}
[1] 先将这个数组按照下标排序,每个值就是下标
[2] 然后跟随机下标的另一个数互换位置。这样这个数组就被打乱顺序了。
CONFIG_SLAB_FREELIST_RANDOM使用
CONFIG_SLAB_FREELIST_RANDOM
生效主要是在新slab 申请的时候调用的
shuffle_freelist
函数:
linux\mm\slub.c : allocate_slab
staticstructpage*allocate_slab(structkmem_cache*s,gfp_t flags,int node){
··· ···
shuffle =shuffle_freelist(s, page);//打乱freelist 顺序if(!shuffle){//freelist 正常顺序
start =fixup_red_left(s, start);
start =setup_object(s, page, start);
page->freelist = start;for(idx =0, p = start; idx < page->objects -1; idx++){
next = p + s->size;
next =setup_object(s, page, next);set_freepointer(s, p, next);
p = next;}set_freepointer(s, p,NULL);}
··· ···
}
可以看出,正常顺序就是从前往后依次链接,如果开启
CONFIG_SLAB_FREELIST_RANDOM
,则会调用
shuffle_freelist
打乱
freelist
顺序。
shuffle_freelist
linux\mm\slub.c : shuffle_freelist
static bool shuffle_freelist(structkmem_cache*s,structpage*page){void*start;void*cur;void*next;unsignedlong idx, pos, page_limit, freelist_count;if(page->objects <2||!s->random_seq)//内存块数量小于2 则无需乱序return false;
freelist_count =oo_objects(s->oo);//获取slab page中内存块数量
pos =get_random_int()% freelist_count;//随机一个起始下标
page_limit = page->objects * s->size;//page大小为 内存块数量*每块大小
start =fixup_red_left(s,page_address(page));/* First entry is used as the base of the freelist */
cur =next_freelist_entry(s, page,&pos, start, page_limit,
freelist_count);//随机获取第一个内存块地址
cur =setup_object(s, page, cur);
page->freelist = cur;//将第一个内存块加入freelistfor(idx =1; idx < page->objects; idx++){//依次从next_freelist_entry获取随机内存块加入freelist
next =next_freelist_entry(s, page,&pos, start, page_limit,
freelist_count);//获取一个随机内存块地址
next =setup_object(s, page, next);set_freepointer(s, cur, next);//加入freelist后面
cur = next;}set_freepointer(s, cur,NULL);return true;}
根据代码中的注释,
shuffle_freelist
起始就是结合下面的
next_freelist_entry
函数,根据
random_seq
记录的随机的内存块顺序,依次加入到
freelist
中。
linux\mm\slub.c : next_freelist_entry
staticvoid*next_freelist_entry(structkmem_cache*s,structpage*page,unsignedlong*pos,void*start,unsignedlong page_limit,unsignedlong freelist_count){unsignedint idx;/*
* If the target page allocation failed, the number of objects on the
* page might be smaller than the usual size defined by the cache.
*/do{
idx = s->random_seq[*pos];//[1]s->random_seq 里是随机一个内存块的偏移*pos +=1;if(*pos >= freelist_count)*pos =0;}while(unlikely(idx >= page_limit));return(char*)start + idx;//[2]start + 偏移 即返回该内存块的地址}
[1]
s->random_seq
是之前打乱顺序的各个内存块的地址偏移,这里的意思是获取
pos
下标的内存块偏移
[2] 然后返回
start+idx
起始地址+偏移,即内存块实际地址,所以
next_freelist_entry
函数就是根据
random_seq
记录的顺序获取下一个随机内存块的地址
deactivate_slab强制下架
deactivate_slab
函数比较长,强制下架当前
cpu_slab->page
,也就是强制切换
cpu_slab
控制的slab page,大部分使用场景都是
unlinkely
分支,也就是大部分都在异常情况下出现,除了在
flush_slab
函数中:
mm\slub.c : flush_slab
staticinlinevoidflush_slab(structkmem_cache*s,structkmem_cache_cpu*c){stat(s, CPUSLAB_FLUSH);deactivate_slab(s, c->page, c->freelist, c);//强制下架
c->tid =next_tid(c->tid);}
flush_slab
flush_slab
函数应用场景主要在调用
new_slab_objects
申请新slab page时和销毁slab 时会调用
flush_all
对所有cpu调用
flush_slab
。其中在
new_slab_objects
中:
mm\slub.c : new_slab_objects
staticinlinevoid*new_slab_objects(structkmem_cache*s,gfp_t flags,int node,structkmem_cache_cpu**pc){void*freelist;structkmem_cache_cpu*c =*pc;structpage*page;WARN_ON_ONCE(s->ctor &&(flags & __GFP_ZERO));
freelist =get_partial(s, flags, node, c);//[1]获取node 的partical slabif(freelist)return freelist;
page =new_slab(s, flags, node);//[2]调用new_slab分配新pageif(page){
c =raw_cpu_ptr(s->cpu_slab);//[2.1]获取当前cpuif(c->page)flush_slab(s, c);//[2.2]如果当前cpu有page则flush_slab刷新吗,这里会将page强制下架
freelist = page->freelist;//[2.3]更新freelist、page
page->freelist =NULL;//被cpu_slab控制的page 的freelist 都要设置为NULLstat(s, ALLOC_SLAB);
c->page = page;//该page被cpu_slab控制*pc = c;}return freelist;//[3]返回freelist}
如果申请好了新slab page,当前
cpu_slab->page
不为空,则会调用
flush_slab
然后调用
deactivate_slab
强制下架
cpu_slab->page
。也就是我现在有新的了,必须得把老的下架放回node中,否则新的slab page不知道放哪(总不能刚申请完就放到node中)。
deactivate_slab
deactivate_slab
函数完成强制下架,把
cpu_slab
当前控制的slab page状态更新,并根据现在的slab 分配情况(半空、满、空)放入对应的node的list中或者销毁掉。
linux\mm\slub.c : deactivate_slab
staticvoiddeactivate_slab(structkmem_cache*s,structpage*page,void*freelist,structkmem_cache_cpu*c)//[1]page为要下架的page{enumslab_modes{ M_NONE, M_PARTIAL, M_FULL, M_FREE };structkmem_cache_node*n =get_node(s,page_to_nid(page));int lock =0, free_delta =0;enumslab_modes l = M_NONE, m = M_NONE;void*nextfree,*freelist_iter,*freelist_tail;int tail = DEACTIVATE_TO_HEAD;structpage new;structpage old;if(page->freelist){stat(s, DEACTIVATE_REMOTE_FREES);//设置为1
tail = DEACTIVATE_TO_TAIL;}/*
* Stage one: Count the objects on cpu's freelist as free_delta and
* remember the last object in freelist_tail for later splicing.
*/
freelist_tail =NULL;
freelist_iter = freelist;while(freelist_iter){//[2]循环遍历找到cpu_slab->freelist里最后一个free iter,并计数//cpu_slab 的freelist 和page freelist 是不同步的//cpu分配的时候page这边不动,在下架的时候才同步
nextfree =get_freepointer(s, freelist_iter);/*
* If 'nextfree' is invalid, it is possible that the object at
* 'freelist_iter' is already corrupted. So isolate all objects
* starting at 'freelist_iter' by skipping them.
//检测是否corrupted
*/if(freelist_corrupted(s, page,&freelist_iter, nextfree))break;
freelist_tail = freelist_iter;
free_delta++;//[2]用于统计freelist 里有多少个object
freelist_iter = nextfree;}/*
* Stage two: Unfreeze the page while splicing the per-cpu
* freelist to the head of page's freelist.
*
* Ensure that the page is unfrozen while the list presence
* reflects the actual number of objects during unfreeze.
*
* We setup the list membership and then perform a cmpxchg
* with the count. If there is a mismatch then the page
* is not unfrozen but the page is on the wrong list.
*
* Then we restart the process which may have to remove
* the page from the list that we just put it on again
* because the number of objects in the slab may have
* changed.
*/
redo://[3]
old.freelist =READ_ONCE(page->freelist);//[3.1]page在cpu_slab 控制下,所以old.freelist 为NULL
old.counters =READ_ONCE(page->counters);VM_BUG_ON(!old.frozen);//slab必须在CPU_slab中/* Determine target state of the slab */
new.counters = old.counters;if(freelist_tail){//[3.2]freelist 不为空,找到了freelist 最后一个对象
new.inuse -= free_delta;//更新page inuse数set_freepointer(s, freelist_tail, old.freelist);//freelist_tail(next)=old.freelist
new.freelist = freelist;//更新freelist}else//freelist为空,则更新new.freelist = NULL
new.freelist = old.freelist;
new.frozen =0;//[3.3]解冻,准备从cpu_slab下架if(!new.inuse && n->nr_partial >= s->min_partial)//[4]判断slab 状态
m = M_FREE;//[4.1]page 为空,且node中partail数量满足最小要求,则设置free状态准备释放elseif(new.freelist){
m = M_PARTIAL;//[4.2]说明node中partial 不满足最小要求,inuse 不为0,page不为空,则设置partial状态if(!lock){
lock =1;/*
* Taking the spinlock removes the possibility
* that acquire_slab() will see a slab page that
* is frozen
*/spin_lock(&n->list_lock);}}else{//[4.3]说明没有freelist了,所有object 都分配出去,page 是full状态
m = M_FULL;if(kmem_cache_debug_flags(s, SLAB_STORE_USER)&&!lock){
lock =1;/*
* This also ensures that the scanning of full
* slabs from diagnostic functions will not see
* any frozen slabs.
*/spin_lock(&n->list_lock);}}if(l != m){//根据状态进行操作if(l == M_PARTIAL)remove_partial(n, page);elseif(l == M_FULL)remove_full(s, n, page);if(m == M_PARTIAL)//[4.4]根据状态放入对应的node表中add_partial(n, page, tail);//放入partial 列表elseif(m == M_FULL)add_full(s, n, page);//放入full列表}
l = m;if(!__cmpxchg_double_slab(s, page,
old.freelist, old.counters,//[3.4]page->freelist = new.freelist
new.freelist, new.counters,//page->counters = new.counters"unfreezing slab"))goto redo;if(lock)spin_unlock(&n->list_lock);if(m == M_PARTIAL)stat(s, tail);elseif(m == M_FULL)stat(s, DEACTIVATE_FULL);elseif(m == M_FREE){stat(s, DEACTIVATE_EMPTY);discard_slab(s, page);//[4.1]如果为空,则销毁slabstat(s, FREE_SLAB);}
c->page =NULL;//[5]清空cpu_slab 的page 和freelist,完成下架
c->freelist =NULL;}
[1] 传入参数:
page
和
freelist
为要被下架slab page和它的
freelist
,即
cpu_slab
的
page
和
freelist
成员,
c
为要被下架的slab page所在的
cpu_slab
。
[2] 循环遍历找到
freelist
最后一个
object
,顺便用
free_delta
统计
freelist
中现存
object
数量。
[3] 之前在
kmalloc
章节中分析过,当slab page被
cpu_slab
控制之后,
page
结构体中的
freelist
和
inuse
就不会随着该slab 中的内存被分配而更新了。即
freelist
已经给了
cpu_slab
控制,
page
结构体中的
freelist
被设置成了
NULL
。现在要从
cpu_slab
中下架,所以要把最新的
freelist
状态和
inuse
数量更新回
page
结构体。
[3.1]
old.freelist
为
page
结构体中的
freelist
,由于
page
被
cpu_slab
控制,所以是
NULL
[3.2]
freelist_tail
不为空,则说明该slab 没有分配光,还有现存的free object,这里要根据刚刚统计的现存free object 数量更新
inuse
信息,然后将现在的
freelist
更新回
page
结构体。
[3.3]
frozen
设置为0,准备解冻,从
cpu_slab
中下架。该标志位代表是否被
cpu_slab
控制。
[4] 判断下架的slab 状态,分为空、部分空和满三种
[4.1] 如果
inuse = 0
,则说明没有任何
object
分配出去(或全部释放了),如果node中的
partial
list中的slab 数量满足slab 要求的最小数量,则该(当前被下架的)slab可以标记为
FREE
,在后面会调用
discard_slab
销毁掉。
[4.2] 否则,如果
inuse
不为0 或不满足最小条件,说明不能销毁该slab,则根据是否有
freelist
,有
freelist
说明还有可以分配的,可以标记为
PARTIAL
半空,后面会放入
node->partial
列表中。
[4.3] 否则说明没有
freelist
,
object
全部分配出去了,slab 状态为满,标记为
FULL
,后续放入
node->full
列表中。
[4.4] 根据上面标记的状态进行操作
[5] 把
cpu_slab
的
page
和
freelist
都置空,完成下架。
get_partial 从node->partial向cpu_slab补充slab
这一部分在上一章中已经进行过简要分析;
get_partial
是在申请新slab 的
new_slab_objects
中调用的,在申请一个全新的slab page 之前,会先看看node 的
partial
列表中有没有可用的半满slab page,如果有的话则会将其上架给
cpu_slab
:
linux\mm\slub.c : get_partial
staticvoid*get_partial(structkmem_cache*s,gfp_t flags,int node,structkmem_cache_cpu*c){void*object;int searchnode = node;if(node == NUMA_NO_NODE)//[1]NUMA_NO_NODE获取当前node
searchnode =numa_mem_id();
object =get_partial_node(s,get_node(s, searchnode), c, flags);//[2]从这个node获取partial 链表if(object || node != NUMA_NO_NODE)return object;returnget_any_partial(s, flags, c);//[3]随便来一个}
该函数虽然在之前
kmalloc
章节分析过了,但这里[2] 处调用了
get_partial_node
函数,其中有一些细节操作需要在这里分析一下:
get_partial_node
get_partial_node
不止会从
node->partial
找到一个可用slab page并返回一个可用
object
,还会将
node->partial
中大部分可用slab page填充进
cpu_slab->partial
中:
linux\mm\slub.c : get_partial_node
staticvoid*get_partial_node(structkmem_cache*s,structkmem_cache_node*n,structkmem_cache_cpu*c,gfp_t flags){structpage*page,*page2;void*object =NULL;unsignedint available =0;int objects;/*
* Racy check. If we mistakenly see no partial slabs then we
* just allocate an empty slab. If we mistakenly try to get a
* partial slab and there is none available then get_partial()
* will return NULL.
*/if(!n ||!n->nr_partial)//node还没正式使用或者里面没partial就直接返回returnNULL;spin_lock(&n->list_lock);list_for_each_entry_safe(page, page2,&n->partial, slab_list){//[1] 遍历node->partial中的pagevoid*t;if(!pfmemalloc_match(page, flags))//flag不匹配就过continue;//[2] 第一次还没找到object,接下来找到了影响第四个参数//如果是第一次,将page从partial中拿下来,然后设置page的一些counters参数,获取堆块数量,返回page的freelist//否则统计数量,然后把page从node->partial拿出来,但不改变page状态
t =acquire_slab(s, n, page, object ==NULL,&objects);if(!t)break;
available += objects;//统计可用堆块数量if(!object){//[3] 刚找到合适的page的时候
c->page = page;//设置cpu_slab->pagestat(s, ALLOC_FROM_PARTIAL);
object = t;//设置object用于返回}else{//[4] 已经找到合适的page了put_cpu_partial(s, page,0);//将page放到cpu_slab->partial中stat(s, CPU_PARTIAL_NODE);}if(!kmem_cache_has_cpu_partial(s)//[4] 如果可用堆块数量达到了cpu_partial 的二分之一就停止|| available >slub_cpu_partial(s)/2)break;}spin_unlock(&n->list_lock);return object;}
[1] 遍历
node->partial
中的
page
,找到的第一个合适的
page
用于给
cpu_slab->page
用于分配的
page
,并且其他合适的添加到
cpu_slab->partial
中用于补充。
[2] 对每一个
page
,调用
acquire_slab
函数处理,第四个参数代表是否将该页设置为
cpu_slab
控制的页(修改
freelist
、
frozen
等)。这个函数的代码和简要逻辑在下面
[3] 如果还没找到适合返回的
freelist
的话,则将刚刚通过
acquire_slab
函数获得的
freelist
设置为返回的
freelist
,将
cpu_slab->page
设置为其所属
page
。
[4] 否则说明刚刚已经找到用于返回的
freelist
了,将新找到的
page
补充到
cpu_slab->partial
中。直到满足
kmem_cache->cpu_partial
要求的数量的一半为止。
put_cpu_partial
函数会在后面分析。
acquire_slab
acquire_slab 函数找到一个可用slab,并根据第四个参数
mode
,对
page
设置状态, 如果
mode
为
true
,则改变
page
的
frozen
、
freelist
,让
page
达到"被
cpu_slab->page
控制"的状态。然后将
page
从
node->partial
链表中取出。返回
page
的
freelist
列表。
linux\mm\slub.c : acquire_slab
staticinlinevoid*acquire_slab(structkmem_cache*s,structkmem_cache_node*n,structpage*page,int mode,int*objects){void*freelist;unsignedlong counters;structpage new;lockdep_assert_held(&n->list_lock);/*
* Zap the freelist and set the frozen bit.
* The old freelist is the list of objects for the
* per cpu allocation list.
*/
freelist = page->freelist;//获取page的freelist counters等
counters = page->counters;
new.counters = counters;*objects = new.objects - new.inuse;//获取剩余堆块数量if(mode){//如果还没有获取到object
new.inuse = page->objects;//将page inuse 设置满,因为要放入cpu_slab->page里了
new.freelist =NULL;//cpu_slab->page中的page的freelist要置空}else{
new.freelist = freelist;//否则不变}VM_BUG_ON(new.frozen);
new.frozen =1;//准备放入cpu_slab->pageif(!__cmpxchg_double_slab(s, page,//更新page的一些信息
freelist, counters,
new.freelist, new.counters,"acquire_slab"))returnNULL;remove_partial(n, page);//从partial列表中删除WARN_ON(!freelist);return freelist;//返回freelist}
总结
pass
版权归原作者 breezeO_o 所有, 如有侵权,请联系我们删除。