0


[linux kernel]slub内存管理分析(4) 细节操作以及安全加固

文章目录

背景

前情回顾

关于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


本文转载自: https://blog.csdn.net/Breeze_CAT/article/details/130015721
版权归原作者 breezeO_o 所有, 如有侵权,请联系我们删除。

“[linux kernel]slub内存管理分析(4) 细节操作以及安全加固”的评论:

还没有评论