kmalloc kfree学习笔记

slub中的kmalloc和kfree学习笔记
 
 
2.6.26中的内存管理大概分为3个层次 SLUB,伙伴系统和ZONE,其中SLUB在最高层,这里通过分析kmalloc和kfree来分析SLUB的模型,在内存管理中还有NUMA系统,但是NUMA不是必须得,所以以下笔记建立在无SMP和不使用NUMA的环境下,并且不运行DEBUG设置

SLUB主要对1页以下的内存进行管理,将1页内存分成相同大小的块,SLUB将这些块称为object,内核进行内存申请时则分配1个块,也就是1个object


在x86下的32位处理器中,SLUB由13个缓冲结构组成,每个缓冲结构管理大小不同的object,其中0号归NUMA使用,其它12个按顺序分别为96,192,8,16,32,64,128,256,512,1024,2048,4096,如下图所示:

 
举例来说,第5个缓冲结构管理object大小为16的页面,对于该缓冲来说,将1页内存按16的大小分成了256项,当内核申请1个大小为9-16大小的内存时,SLUB就根据第5个缓冲结构中的空闲object指针freelist取出1个object交给内核
这里可以发现,申请大小为9时,返回16的大小,申请大小为15时,也返回16的大小,大家会认为如果申请的内存都在9到10大小左右徘徊的时候就会浪费大概50%的内存空间,对的,所以学习SLUB就更有必要了嘛,如果都在9-10大小的话就自己更改SLUB缓冲的结构,设置1个9-10大小的缓冲区,专门负责这些内存申请.避免浪费
对每页内存进行分块,虽然在一定程度上浪费了内存,但是方便了内存的申请与回收,提高了效率,下面就对SLUB的这种管理进行分析
 首先是SLUB的缓冲结构kmalloc_caches[]数组的初始化,该初始化在kmem_cache_init中进行
 kmem_cache_init在/mm/slub.c中,代码如下:

void __init kmem_cache_init(void)
{
    int i;
    int caches = 0;

    init_alloc_cpu();
#ifdef CONFIG_NUMA
    create_kmalloc_cache(&kmalloc_caches[0], "kmem_cache_node",
        sizeof(struct kmem_cache_node), GFP_KERNEL);
    kmalloc_caches[0].refcount = -1;
    caches++;
    hotplug_memory_notifier(slab_memory_callback, SLAB_CALLBACK_PRI);
#endif
    slab_state = PARTIAL;
    //如果kmalloc的最小object小于64
    //则初始化1号和2号kmalloc_caches的大小为96和192
    if (KMALLOC_MIN_SIZE <= 64)
    {
        create_kmalloc_cache(&kmalloc_caches[1],
                "kmalloc-96", 96, GFP_KERNEL);
        caches++;
        
        create_kmalloc_cache(&kmalloc_caches[2],
                "kmalloc-192", 192, GFP_KERNEL);
        caches++;
    }
    //按照kmalloc的最小object初始化kmalloc_caches
    for (i = KMALLOC_SHIFT_LOW; i <= PAGE_SHIFT; i++)
    {
        create_kmalloc_cache(&kmalloc_caches[i],
            "kmalloc", 1 << i, GFP_KERNEL);
        caches++;
    }
    BUILD_BUG_ON(KMALLOC_MIN_SIZE > 256 ||
        (KMALLOC_MIN_SIZE & (KMALLOC_MIN_SIZE - 1)));
    //按照kmalloc的最小object重新设置size_index
    for (i = 8; i < KMALLOC_MIN_SIZE; i += 8)
        size_index[(i - 1) / 8] = KMALLOC_SHIFT_LOW;

    if (KMALLOC_MIN_SIZE == 128)
    {
        for (i = 128 + 8; i <= 192; i += 8)
            size_index[(i - 1) / 8] = 8;
    }
    slab_state = UP;
    //按照kmalloc的最小object重新设置kmalloc_caches的名字
    for (i = KMALLOC_SHIFT_LOW; i <= PAGE_SHIFT; i++)
        kmalloc_caches[i]. name =
            kasprintf(GFP_KERNEL, "kmalloc-%d", 1 << i);
#ifdef CONFIG_SMP
    register_cpu_notifier(&slab_notifier);
    kmem_size = offsetof(struct kmem_cache, cpu_slab) +
                nr_cpu_ids * sizeof(struct kmem_cache_cpu *);
#else
    kmem_size = sizeof(struct kmem_cache);
#endif
    printk(KERN_INFO
        "SLUB: Genslabs=%d, HWalign=%d, Order=%d-%d, MinObjects=%d,"
        " CPUs=%d, Nodes=%d/n",
        caches, cache_line_size(),
        slub_min_order, slub_max_order, slub_min_objects,
        nr_cpu_ids, nr_node_ids);
}

由于不使用NUMA系统,所以这里不会执行#ifdef CONFIG_NUMA中的代码,也就不会初始化0号缓冲

所有缓冲结构的初始化都是由create_kmalloc_cache负责
create_kmalloc_cache在/mm/slub.c中,代码如下:

static struct kmem_cache *create_kmalloc_cache(struct kmem_cache *s,
        const char *name, int size, gfp_t gfp_flags)
{
    unsigned int flags = 0;


    //检测是否为DMA缓冲结构
    if (gfp_flags & SLUB_DMA)
        //是则加上DMA标志
        flags = SLAB_CACHE_DMA;
    down_write(&slub_lock);
    //分配一个缓冲
    if (!kmem_cache_open(s, gfp_flags, name, size, ARCH_KMALLOC_MINALIGN,flags, NULL))
        goto panic;
    //将该缓冲挂载到slab_caches链表中
    list_add(&s->list, &slab_caches);
    up_write(&slub_lock);
    if (sysfs_slab_add(s))
        goto panic;
    return s;
panic:
    panic("Creation of kmalloc slab %s size=%d failed./n", name, size);
}

主要的初始化在kmem_cache_open中进行
kmem_cache_open在/mm/slub.c中,代码如下:

static int kmem_cache_open(struct kmem_cache *s, gfp_t gfpflags,
        const char *name, size_t size,
        size_t align, unsigned long flags,
        void (*ctor)(struct kmem_cache *, void *))
{
    //初始化kmem缓冲,将内容全部清零
    memset(s, 0, kmem_size);
    //设置缓冲的名字
    s->name = name;
    //设置缓冲的object初始化函数
    s->ctor = ctor;
    //设置缓冲的object大小
    s->objsize = size;
    //设置缓冲的对齐
    s->align = align;
    //设置缓冲的标志
    s->flags = kmem_cache_flags(size, flags, name, ctor);
    //根据object的大小计算对应的object数目
    if (!calculate_sizes(s, -1))
        goto error;
    s->refcount = 1;
#ifdef CONFIG_NUMA
    s->remote_node_defrag_ratio = 100;
#endif
    //初始化邻居页面链表
    if (!init_kmem_cache_nodes(s, gfpflags & ~SLUB_DMA))
        goto error;
    //初始化CPU的私有kmem缓冲
    if (alloc_kmem_cache_cpus(s, gfpflags & ~SLUB_DMA))
        return 1;
    free_kmem_cache_nodes(s);
error:
    if (flags & SLAB_PANIC)
        panic("Cannot create slab %s size=%lu realsize=%u "
            "order=%u offset=%u flags=%lx/n",
            s->name, (unsigned long)size, s->size, oo_order(s->oo),
            s->offset, flags);
    return 0;
}

calculate_sizes负责计算object的大小,就是对kmem_cache结构中oo,max,min成员的赋值,以及对object大小进行边界和字对齐,但是对边界和字对齐还不熟悉,所以我就不分析了 = 3=

接下来是init_kmem_cache_nodes, init_kmem_cache_nodes负责对邻居页面链表进行初始化
init_kmem_cache_nodes在/mm/slub.c中,由于这里不使用NUMA系统,所以代码如下:

static int init_kmem_cache_nodes(struct kmem_cache *s, gfp_t gfpflags)
{
    init_kmem_cache_node(&s->local_node);
    return 1;
}

init_kmem_cache_node在mm/slub.c中,代码如下:

static void init_kmem_cache_node(struct kmem_cache_node *n)
{
    n->nr_partial = 0;
    spin_lock_init(&n->list_lock);
    INIT_LIST_HEAD(&n->partial);
#ifdef CONFIG_SLUB_DEBUG
    atomic_long_set(&n->nr_slabs, 0);
    INIT_LIST_HEAD(&n->full);
#endif
}

主要进行了一下初始化工作

回到kmem_cache_open中,现在到alloc_kmem_cache_cpus, alloc_kmem_cache_cpus负责CPU私有缓冲的初始化工作
 alloc_kmem_cache_cpus在mm/slub.c中,由于不使用SMP,所以代码如下:

static inline int alloc_kmem_cache_cpus(struct kmem_cache *s, gfp_t flags)
{
    init_kmem_cache_cpu(s, &s->cpu_slab);
    return 1;
}

init_kmem_cache_cpu在mm/slub.c中,代码如下

static void init_kmem_cache_cpu(struct kmem_cache *s,
            struct kmem_cache_cpu *c)
{
    c->page = NULL;
    c->freelist = NULL;
    c->node = 0;
    c->offset = s->offset / sizeof(void *);
    c->objsize = s->objsize;
#ifdef CONFIG_SLUB_STATS
    memset(c->stat, 0, NR_SLUB_STAT_ITEMS * sizeof(unsigned));
#endif
}

初始化完成后, kmem_cache_open和create_kmalloc_cache也执行完了,返回到kmem_cache_init中,接下来kmem_cache_init主要执行缓冲名字的设置工作

下图是第6个缓冲结构,也就是object大小为32的缓冲初始化后的结构图

 

kmem_cache_init执行完成后,SLUB的初始化就完成了,就接下来我们就能使用kmalloc进行内存的分配了
假设kmalloc申请的大小为32,标志为GFP_KKERNEL,GFP_KERNEL是标志_GFP_WAIT , _GFP_IO 和 _GFP_FS的集合,也就是kmalloc(32,GFP_KERNEL)
下面就进入到kmalloc的分析中
kmalloc在/mm/slub.c中,代码如下:

static __always_inline void *kmalloc(size_t size, gfp_t flags)
{
    //也就是检测size是变量还是常量
    //为常量则执行if
    if (__builtin_constant_p(size))
    {
        //检测申请的大小是否超过1页内存的大小
        if (size > PAGE_SIZE)
            //调用大块内存分配
            return kmalloc_large(size, flags);
        //检测申请的内存是否用于DMA
        if (!(flags & SLUB_DMA))
        {
            //根据申请的大小选取对应的缓冲结构
            struct kmem_cache *s = kmalloc_slab(size);
            //检测kmem缓冲取得是否成功
            if (!s)
                return ZERO_SIZE_PTR;
            //使用缓冲结构取得内存
            return kmem_cache_alloc(s, flags);
        }
    }
    //变量及DMA使用__kmalloc分配内存
    return __kmalloc(size, flags);
}

__builtin_constant_p检测参数是变量还是常量,举个例子说kmalloc(i,GFP_KERNEL)就是变量,kmalloc(32,GFP_KERNEL)就是常量
这里先看常量,进入if中,这里先说一下kmalloc_large, kmalloc_large负责超过1页内存的申请,超过1页的内存分配由伙伴系统进行,不由SLUB进行.
接下来到if (!(flags & SLUB_DMA)),这里我们申请的内存标志为GFP_KERNEL,没有DMA标志,所以进入到if中
首先根据申请的大小选取对应的缓冲序号,进入到kmalloc_slab中
kmalloc_slab在include /linux/slub_def.h中,代码如下:

static __always_inline struct kmem_cache *kmalloc_slab(size_t size)
{
    //根据申请的大小取得对应kmem缓冲的序号
    int index = kmalloc_index(size);
    if (index == 0)
        return NULL;
    //根据序号取得对应的kmem缓冲
    return &kmalloc_caches[index];
}

继续进入到kmalloc_index
kmalloc_index在include /linux/slub_def.h中,代码如下:

static __always_inline int kmalloc_index(size_t size)
{
    //检测大小是否为0
    if (!size)
        //为0则返回0
        return 0;
    //检测大小是否小于kmalloc的最小object
    if (size <= KMALLOC_MIN_SIZE)
        //小于则返回最小object的对数
        return KMALLOC_SHIFT_LOW;
//检测kmalloc的最小object是否小于64
#if KMALLOC_MIN_SIZE <= 64
    //大于64而小于96则使用1号kmem
    if (size > 64 && size <= 96)
        return 1;
    //大于128而小于192则使用2号kmem
    if (size > 128 && size <= 192)
        return 2;
#endif
    //以下根据大小的不同,返回对应的kmem缓冲号
    if (size <= 8) return 3;
    if (size <= 16) return 4;
    if (size <= 32) return 5;
    if (size <= 64) return 6;
    if (size <= 128) return 7;
    if (size <= 256) return 8;
    if (size <= 512) return 9;
    if (size <= 1024) return 10;
    if (size <= 2 * 1024) return 11;
    if (size <= 4 * 1024) return 12;
     

    //以下是对于分页大于4K所使用的检测
    if (size <= 8 * 1024) return 13;
    if (size <= 16 * 1024) return 14;
    if (size <= 32 * 1024) return 15;
    if (size <= 64 * 1024) return 16;
    if (size <= 128 * 1024) return 17;
    if (size <= 256 * 1024) return 18;
    if (size <= 512 * 1024) return 19;
    if (size <= 1024 * 1024) return 20;
    if (size <= 2 * 1024 * 1024) return 21;
    return -1;
}

得到缓冲结构后,就来到了kmem_cache_alloc中
kmem_cache_alloc在mm/slub.c中

void *kmem_cache_alloc(struct kmem_cache *s, gfp_t gfpflags)
{
    return slab_alloc(s, gfpflags, -1, __builtin_return_address(0));
}

简单的调用, __builtin_return_address产生的值用于DEBUG,这里我们并不会使用到,继续来到slab_alloc中
slab_alloc在mm/slub.c中,代码如下:

static __always_inline void *slab_alloc(struct kmem_cache *s,
        gfp_t gfpflags, int node, void *addr)
{
    void **object;
    struct kmem_cache_cpu *c;
    unsigned long flags;
    unsigned int objsize;

    //保存并关闭中断
    local_irq_save(flags);
    //取得kmem缓冲中对应当前CPU序号的私有kmem缓冲
    c = get_cpu_slab(s, smp_processor_id());
    //取得缓冲中object的大小
    objsize = c->objsize;
    //检测CPU的私有kmem缓冲的空闲object指针是否为空
    if (unlikely(!c->freelist || !node_match(c, node)))
        //为空则新申请1块页面
        object = __slab_alloc(s, gfpflags, node, addr, c);
    //不为空则使用object指针所指的object
    else
    {
        //取得空闲的object
        object = c->freelist;
        //object指针指向下1个空闲的object
        c->freelist = object[c->offset];
        //设置ALLOC_FASTPATH状态计数器加1
        stat(c, ALLOC_FASTPATH);
    }
    //恢复中断
    local_irq_restore(flags);
    //检测是否需要清零object
    //需要的话检测当先object是否为空
    if (unlikely((gfpflags & __GFP_ZERO) && object))
        memset(object, 0, objsize);
    return object;
}

get_cpu_slab负责取得CPU的私有kmem缓冲
get_cpu_slab在mm/slub.c中,代码如下

static inline struct kmem_cache_cpu *get_cpu_slab(struct kmem_cache *s, int cpu)
{
#ifdef CONFIG_SMP
    return s->cpu_slab[cpu];
#else
    return &s->cpu_slab;
#endif
}

由于不使用SMP,所以这里很简单,返回缓冲结构中的CPU私有缓冲结构
接下来到node_match , node_match在mm/slub.c中,代码如下

static inline int node_match(struct kmem_cache_cpu *c, int node)
{
#ifdef CONFIG_NUMA
    if (node != -1 && c->node != node)
        return 0;
#endif
    return 1;
}

由于我们不使用NUMA,所以node_match永远返回1
因为这时候是第1次调用kmalloc,所以CPU的私有kmem缓冲中的freelist指针为空
所以我们进入到__slab_alloc中
__slab_alloc在mm/slub.c中,代码如下:

static void *__slab_alloc(struct kmem_cache *s,
        gfp_t gfpflags, int node, void *addr, struct kmem_cache_cpu *c)
{
    void **object;
    struct page *new;

    //这里不处理清零,取消清零标记
    gfpflags &= ~__GFP_ZERO;
    //检测CPU的私有kmem缓冲的页面是否为空
    if (!c->page)
        //页面为空则申请1块页面
        goto new_slab;
    //为将要操作的页面上锁
    slab_lock(c->page);
    if (unlikely(!node_match(c, node)))
        goto another_slab;
    stat(c, ALLOC_REFILL);
load_freelist:
    //取得页面的空闲object指针
    object = c->page->freelist;
    //检测空闲object是否为空
    if (unlikely(!object))
        //为空则使用别的
        goto another_slab;
    if (unlikely(SlabDebug(c->page)))
        goto debug;
    //CPU的私有缓冲的object指针指向下1个空闲的object
    c->freelist = object[c->offset];
    //设置页面的使用计数器为页面的object数目
    c->page->inuse = c->page->objects;
    //设置页面的空闲object指针为空
    c->page->freelist = NULL;
    c->node = page_to_nid(c->page);
unlock_out:
    //解除页面的锁
    slab_unlock(c->page);
    stat(c, ALLOC_SLOWPATH);
    return object;
another_slab:
    deactivate_slab(s, c);
new_slab:
    //检测是否有邻居页面
    new = get_partial(s, gfpflags, node);
    //检测邻居页面取得是否成功
    if (new)
    {
        //连接邻居页面到CPU得私有kmem缓冲上
        c->page = new;
        //ALLOC_FROM_PARTIAL状态计数器加1
        stat(c, ALLOC_FROM_PARTIAL);
        //跳转到load_freelist
        goto load_freelist;
    }
    //检测是否可以中断,可以则打开IRQ
    if (gfpflags & __GFP_WAIT)
        local_irq_enable();
    //取得1块新的页面
    new = new_slab(s, gfpflags, node);
    //关闭IRQ
    if (gfpflags & __GFP_WAIT)
        local_irq_disable();
    //检测取得页面是否成功
    if (new)
    {
        //取得当前CPU的私有kmem缓冲
        c = get_cpu_slab(s, smp_processor_id());
        //ALLOC_SLAB状态计数器加1
        stat(c, ALLOC_SLAB);
        //检测当前CPU的私有kmem缓冲的页面是否为空
        if (c->page)
            //释放该页面
            flush_slab(s, c);
        //为将要操作的页面上锁
        slab_lock(new);
        //设置页面属性,设置PG_active属性
        SetSlabFrozen(new);
        //连接该页面到CPU的私有kmem缓冲
        c->page = new;
        goto load_freelist;
    }
    return NULL;
debug:
    if (!alloc_debug_processing(s, c->page, object, addr))
        goto another_slab;
    c->page->inuse++;
    c->page->freelist = object[c->offset];
    c->node = -1;
    goto unlock_out;
}

这时候CPU的私有kmem缓冲中的页面还为空,所以我们来到了new_slab标号处
这里首先检测是否有邻居页面,刚初始化完出来,还没邻居呢,所以会返回空,以后我们再来分析有邻居的时候是如何分配的

呢么就来到new_slab
new_slab在/mm/slub.c中,代码如下:

static struct page *new_slab(struct kmem_cache *s, gfp_t flags, int node)
{
    struct page *page;
    void *start;
    void *last;
    void *p;

    BUG_ON(flags & GFP_SLAB_BUG_MASK);
    //取得1块新的页面
    page = allocate_slab(s,
        flags & (GFP_RECLAIM_MASK | GFP_CONSTRAINT_MASK), node);
    //检测取得页面是否成功
    if (!page)
        goto out;
    inc_slabs_node(s, page_to_nid(page), page->objects);
    //连接缓冲结构到该页面
    page->slab = s;
    //设置页面的slab属性
    page->flags |= 1 << PG_slab;
    if (s->flags & (SLAB_DEBUG_FREE | SLAB_RED_ZONE | SLAB_POISON |
            SLAB_STORE_USER | SLAB_TRACE))
        SetSlabDebug(page);
    //取得页面的起始地址
    start = page_address(page);
    if (unlikely(s->flags & SLAB_POISON))
        memset(start, POISON_INUSE, PAGE_SIZE << compound_order(page));
    //下面进行页面的object设置
    //首先取得页面的起始地址
    last = start;
    //按照object的大小将页面分成对应的块数
    //历遍块数大小
    //p指向页面的起始地址,每次自增object的大小
    for_each_object(p, s, start, page->objects)
    {
        //使用缓冲的ctor函数对object进行初始化
        setup_object(s, page, last);
        //设置last所指的内容为p所指向的地址
        //也就是将所有object连接成1个单向链表
        set_freepointer(s, last, p);
        //设置last为p
        last = p;
    }
    //使用缓冲的ctor函数对object进行初始化
    setup_object(s, page, last);
    //设置last所指的内容为NULL
    //也就是到达了页尾,最后一个object
    set_freepointer(s, last, NULL);
    //设置页面的空闲object指针为页面起始地址
    //也就是第1个object
    page->freelist = start;
    //设置使用计数器为0
    page->inuse = 0;
out:
    //返回该页面
    return page;
}

allocate_slab取得一个空的页面,并进行一下初始化,主要是将page->objects设成了缓冲成员oo中的x,这里也就是128,因为涉及到kmem_cache结构中oo成员,这个成员和边界对齐有些关系,我对边界对齐还不熟悉,就暂时不分析这个函数,不过并不会影响到下面的分析

下面主要分析SLUB对页面object的初始化,也就是如何将页面分成1个个的object
for_each_object是一个宏,在mm/slub.c中,代码如下:

#define for_each_object(__p, __s, __addr, __objects) /
    for (__p = (__addr); __p < (__addr) + (__objects) * (__s)->size;/
            __p += (__s)->size)

setup_objec在mm/slub.c中,代码如下:

static void setup_object(struct kmem_cache *s, struct page *page,
                void *object)
{
    setup_object_debug(s, page, object);
    if (unlikely(s->ctor))
        s->ctor(s, object);
}

因为ctor为NULL,所以不会执行s->ctor(s, object);
什么都没执行,所以这相等于一个空函数

set_freepointer在mm/slub.c中,代码如下:

static inline void set_freepointer(struct kmem_cache *s, void *object, void *fp)
{
    *(void **)(object + s->offset) = fp;
}

呢么展开这几个函数,就是下列代码

for( p = start ; p < start + page->objects * s->size ; p += s->size)
{
    *(void **)(last + s->offset) = p;
    last = p;
}
*(void **)(last + s->offset) = p;

再将缓冲结构中的数值代入,得出

for( p = start ; p < start + 128 * 32 ; p += 32)
{
    *(void **)(last + 0) = p;
    last = p;
}
*(void **)(last + 0) = p;

这里也是SLUB最主要的部分,我们用图来说明,首先先看一下逻辑上的视图

 
这其实连接成了1个单向链表,用链表的视角来看的话如下
 
 
 

然后我们假设页面的起始地址为0x0000,呢么该页面的内存视图如下

初始化完成后将页面的空闲object指针指向第1个object,如下

 

然后返回到__slab_alloc中,现在跳转到标号load_freelist处
首先将当前page->freelist的值赋给了将要返回的指针
然后将CPU的私有kmem缓冲中的freelist,也就是空闲object指针指向了下一项,如下

 

将空闲object指针指向下一项的代码为c->freelist = object[c->offset] 由于c->offset为0,所以这里为c->freelist = object[0],这句代码困惑了我很久,多亏了chinaunix的dreamice和fera的提示,才顺利解决

这句代码主要就是提取object指针所指的内容,object[0]也就是提取偏移为0的内容,这里object指向了第一项object,呢么object[0]就是第一项object中的下一项object地址,也就是相当于读取了一个链表节点的next节点
然后将页面的freelist指针设为NULL
我认为这里设为NULL的意思也就是该页面所有的项都由SLUB来管理,所以页面没有空余的项
然后返回到slab_alloc中,根据__GFP_ZERO标志来判断是否需要将得到的object进行清零,然后再返回object的地址
到这里kmalloc的操作就完成了

现在回头看看是变量或者DMA的情况下如何进行的
这种情况下会进入__kmalloc中
__kmalloc在mm/slub.c中,代码如下

void *__kmalloc(size_t size, gfp_t flags)
{
    struct kmem_cache *s;
    //检测大小是否超过1页内存
    if (unlikely(size > PAGE_SIZE))
        return kmalloc_large(size, flags);
    //取得对应大小的kmem缓冲
    s = get_slab(size, flags);
    //检测取得是否成功
    if (unlikely(ZERO_OR_NULL_PTR(s)))
        return s;
    //从缓冲结构中分配object
    return slab_alloc(s, flags, -1, __builtin_return_address(0));
}

首先进到get_slab中
get_slab在mm/slub.c中,代码如下:

static struct kmem_cache *get_slab(size_t size, gfp_t flags)
{
    int index;

    //检测大小是否小于192
    if (size <= 192)
    {
        //大小为0或者NULL则返回(void *)16
        if (!size)
            return ZERO_SIZE_PTR;
        //取得对应的kmem序号
        index = size_index[(size - 1) / 8];
    }
    else
        //将大小转化为2进制,取得最后1位的位置
        index = fls(size - 1);
#ifdef CONFIG_ZONE_DMA
    if (unlikely((flags & SLUB_DMA)))
        return dma_kmalloc_cache(index, flags);
#endif
    //返回对应序号的kmem缓冲
    return &kmalloc_caches[index];
}

然后执行slab_alloc, slab_alloc在之前已经分析过了,这里就不再复述了

以上只说明了第1种分配方法,根据程序的走向,kmalloc一共有4种分配方法:
1. 页面为空
2. 页面未满
3. 页面已满
4. 邻居页面未满

下面分析先分析第2和第3种分配方法

首先是第2种,假设还是使用kmalloc(32,GFP_KERNEL),呢么第6个缓冲结构进行第二次分配工作
一路来到slab_alloc中,这次不会进入__slab_alloc了,因为这时c->freelist并不为空,它指向了页面的第二个object,然后执行下列代码:
//取得空闲的object
object = c->freelist;
//object指针指向下1个空闲的object
c->freelist = object[c->offset];

执行完成后的视图如下:

 
 

c->freelist指向了第3个object,这里需要注意的是第1项,也就是最早分配的object,他的下一项空闲object指针也许被冲掉了,主要有2个原因,1是标志ZERO,将这个object初始化为0,2是程序的读写将指针改写了,所以如果使用kmalloc申请小于1页的内存时不使用GFP_ZERO标记,然后马上对得到的内存进行读取,呢么就能得到下一项空闲object的地址

第2种分配方法一直会持续到c->freelist指向最后一项,也就是NULL,这个时候第3种分配方法就要执行了

还是来到slab_alloc中,这个时候c->freelist为NULL,进入到__slab_alloc中
由于这个时候CPU的私有kmem缓冲中的页面并不为空,但是页面的freelist指针为空,所以会执行
if (unlikely(!object))
//为空则使用别的
goto another_slab;
跳转到标号another_slab处执行
标号another_slab处只有一个deactivate_slab函数
deactivate_slab在mm/slub.c中,代码如下:

static void deactivate_slab(struct kmem_cache *s, struct kmem_cache_cpu *c)
{
    struct page *page = c->page;
    int tail = 1;


    if (page->freelist)
        stat(c, DEACTIVATE_REMOTE_FREES);
     //检测object是否已经使用完了
    while (unlikely(c->freelist))
    {
        void **object;
        tail = 0;    /* Hot objects. Put the slab first */
        /* Retrieve object from cpu_freelist */
        object = c->freelist;
        c->freelist = c->freelist[c->offset];
        /* And put onto the regular freelist */
        object[c->offset] = page->freelist;
        page->freelist = object;
        page->inuse--;
    }
    //设置CPU私有缓冲使用的页面为空
    c->page = NULL;
    unfreeze_slab(s, page, tail);
}

由于这时c->freelist为NULL,所以不会执行while中的内容,来到unfreeze_slab中
unfreeze_slab在mm/slub.c中,代码如下:

static void unfreeze_slab(struct kmem_cache *s, struct page *page, int tail)
{
    struct kmem_cache_node *n = get_node(s, page_to_nid(page));
    struct kmem_cache_cpu *c = get_cpu_slab(s, smp_processor_id());

    //清除页面的PG_active属性
    ClearSlabFrozen(page);
    //检测页面的使用计数器
    if (page->inuse)
    {
        //检测页面的空闲object指针是否为空
        if (page->freelist)
        {
            //不为空则添加到邻居页面链表中
            add_partial(n, page, tail);
            stat(c, tail ? DEACTIVATE_TO_TAIL : DEACTIVATE_TO_HEAD);
        }
        else
        {
            stat(c, DEACTIVATE_FULL);
            if (SlabDebug(page) && (s->flags & SLAB_STORE_USER))
                add_full(n, page);
        }
        slab_unlock(page);
    }
    else
    {
        stat(c, DEACTIVATE_EMPTY);
        //检测缓冲中的邻居页面计数器是否达到了最小值
        if (n->nr_partial < MIN_PARTIAL)
        {
             //加入到邻居页面链表中
            add_partial(n, page, 1);
            slab_unlock(page);
        }
        else
        {
            slab_unlock(page);
            stat(get_cpu_slab(s, raw_smp_processor_id()), FREE_SLAB);
            //释放该页面
            discard_slab(s, page);
        }
    }
}

这时页面的使用计数器不为0,所以进入到if中,然后因为页面的freelist为NULL,所以进入到else中
else中主要执行了状态计数器的增加和DEBUG测试,并没有什么实质性的工作,然后就退出了

回到__slab_alloc中,又来到了new_slab,这个时候邻居页面链表还是为空,所以又执行了1次第1种分配方法,分配了1个新的页面

接下来先分析kfree,然后再看第4种分配方法

kfree负责回收使用的内存,
kfree在mm/slub.c中,代码如下:

void kfree(const void *x)
{
    struct page *page;
    void *object = (void *)x;

    //检测所要释放的地址是否为空
    if (unlikely(ZERO_OR_NULL_PTR(x)))
        return;
    //按照地址取得对应的页面结构
    page = virt_to_head_page(x);
    //检测页面是否有PageSlab属性
    if (unlikely(!PageSlab(page)))
    {
        //无则释放该页面
        put_page(page);
        return;
    }
    slab_free(page->slab, page, object, __builtin_return_address(0));
}

这里假设使用kfree回收的地址的页面结构属于SLUB管理的页面,也就是具有pageslab属性,不会进入到if中
__builtin_return_address产生的值用于DEBUG,这里我们并不会使用到
然后进入到slab_free中
slab_free在mm/slub.c中,代码如下

static __always_inline void slab_free(struct kmem_cache *s,
            struct page *page, void *x, void *addr)
{
    void **object = (void *)x;
    struct kmem_cache_cpu *c;
    unsigned long flags;

    local_irq_save(flags);
    //取得当前CPU的私有kmem缓冲
    c = get_cpu_slab(s, smp_processor_id());
    debug_check_no_locks_freed(object, c->objsize);
    if (!(s->flags & SLAB_DEBUG_OBJECTS))
        debug_check_no_obj_freed(object, s->objsize);
    //检测缓冲中的页面是否为当前页面
    //还有缓冲中的node属性是否大于或者等于0
    if (likely(page == c->page && c->node >= 0))
    {
        //将object中的下一空闲object指针设置为缓冲的下一空闲object
        object[c->offset] = c->freelist;
        //将缓冲的下一空闲object指针指向object
        c->freelist = object;
        stat(c, FREE_FASTPATH);
    }
    else
        //不为缓冲中的页面主体调用__slab_free
        __slab_free(s, page, x, addr, c->offset);
    local_irq_restore(flags);
}

如果为当前页面,假设c->freelist指向第3项object,我们所要回收的是第1项object,呢么视图如下:

 

这里主要将设置object所指的object中的空闲object指针指向c->freelist,然后再将c->freelist指向object,指向完毕后的视图如下:

当不为当前页面的时候,则进入到__slab_free中
__slab_free在mm/slub.c中,代码如下:

static void __slab_free(struct kmem_cache *s, struct page *page,
                void *x, void *addr, unsigned int offset)
{
    void *prior;
    void **object = (void *)x;
    struct kmem_cache_cpu *c;

    //取得当前CPU的kmem缓冲
    c = get_cpu_slab(s, raw_smp_processor_id());
    stat(c, FREE_SLOWPATH);
    //将页面上锁
    slab_lock(page);
    if (unlikely(SlabDebug(page)))
        goto debug;
checks_ok:
    //将object的下一空闲object指针指向页面的下一空闲object
    //并且将页面的下一空闲object保存在prior
    prior = object[offset] = page->freelist;
    //将页面的下一空闲object指针指向当前object
    page->freelist = object;
    //减少页面的使用计数器
    page->inuse--;
    //检测页面是否有PG_active
    if (unlikely(SlabFrozen(page)))
    {
        stat(c, FREE_FROZEN);
        goto out_unlock;
    }
    //检测页面的使用计数器是否为0
    if (unlikely(!page->inuse))
        goto slab_empty;
     //检测页面原来的下一空闲object指针是否为NULL
    if (unlikely(!prior))
    {
        //将该页面添加到缓冲的邻居页面中
        add_partial(get_node(s, page_to_nid(page)), page, 1);
        stat(c, FREE_ADD_PARTIAL);
    }
out_unlock:
    slab_unlock(page);
    return;
slab_empty:
    //检测页面原来的下一空闲object指针是否为NULL
    if (prior)
    {
        //从邻居页面链表中移除该页面
        remove_partial(s, page);
        stat(c, FREE_REMOVE_PARTIAL);
    }
    slab_unlock(page);
    stat(c, FREE_SLAB);
    //释放该页面
    discard_slab(s, page);
    return;
debug:
    if (!free_debug_processing(s, page, x, addr))
        goto out_unlock;
    goto checks_ok;
}

__slab_free首先进行再连接过程,就如上面为当前页面差不多,然后有两种选择,1种是加入到邻居页面链表中,另1种是释放该页面
先看加入到邻居页面链表中
当检测使用计数器不为0,并且原下一空闲object指针为NULL的时候(不为NULL说明已经加入到邻居页面链表中了,不需要再加1次),就会执行add_partial
不过首先先看get_node
get_node在/mm/slub.c中,代码如下:

static inline struct kmem_cache_node *get_node(struct kmem_cache *s, int node)
{
#ifdef CONFIG_NUMA
    return s->node[node];
#else
    return &s->local_node;
#endif
}

由于我们不使用NUMA,所以不论node的结果如何,get_node都返回&s->local_node

然后到add_partial,
add_partial在mm/slub.c中,代码如下

static void add_partial(struct kmem_cache_node *n,
                struct page *page, int tail)
{
    spin_lock(&n->list_lock);
    //增加邻居页面计数器
    n->nr_partial++;
    //检测是否添加到尾部
    if (tail)
        //将该页面添加到邻居页面链表中
        list_add_tail(&page->lru, &n->partial);
    else
        list_add(&page->lru, &n->partial);
    spin_unlock(&n->list_lock);
}

连接完成后的结构图如下:

 

然后看释放该页面
如果使用计数器为0则跳转到标号slab_empty处
首先检测是否在邻居页面链表中,如果在则执行remove_partial函数
remove_partial在mm/slub.c中,代码如下:

static void remove_partial(struct kmem_cache *s, struct page *page)
{
    struct kmem_cache_node *n = get_node(s, page_to_nid(page));

    spin_lock(&n->list_lock);
    //从邻居页面链表中脱离
    list_del(&page->lru);
    //减少邻居页面计数器
    n->nr_partial--;
    spin_unlock(&n->list_lock);
}

简单的将页面移除并减少邻居页面计数器

最后执行discard_slab释放页面,由于discard_slab和伙伴系统牵涉较深,等之后的伙伴系统学习笔记再分析吧 = 3=

好,现在邻居页面链表中有存货了~ 我们看看第4种分配方法
当第2种分配方法一直持续到c->freelist指向最后一项,也就是NULL,并且邻居页面链表中有存货的时候,就会进入到第4种分配方法

第4种分配方法其实属于第3种方法的分支,当第3种方法进入到标号new_slab处时,进入get_partial从邻居页面链表中取得页面成功就为第4种分配方法

现在进入get_partial函数中
get_partial在mm/slub.c中,代码如下:

static struct page *get_partial(struct kmem_cache *s, gfp_t flags, int node)
{
    struct page *page;
    
    int searchnode = (node == -1) ? numa_node_id() : node;
    //取得邻居页面
    page = get_partial_node(get_node(s, searchnode));
    //检测取得是否成功
    if (page || (flags & __GFP_THISNODE))
        return page;
    return get_any_partial(s, flags);
}

get_node在之前已经分析过了,他会返回&s->local_node;
然后到get_partial_node中
get_partial_node在mm/slub.c中,代码如下

static struct page *get_partial_node(struct kmem_cache_node *n)
{
    struct page *page;


    //检测缓冲节点或者节点中的局部计数器是否为0
    if (!n || !n->nr_partial)
        return NULL;
    //给当前的kmem_cache_node结构上锁
    spin_lock(&n->list_lock);
    //历遍LRU最少使用链表
    list_for_each_entry(page, &n->partial, lru)
        //检测取得邻居页面是否成功
        if (lock_and_freeze_slab(n, page))
            goto out;
    //没有可用的邻居页面
    page = NULL;
out:
    //解锁
    spin_unlock(&n->list_lock);
    //返回页面
    return page;
}

主要就是历遍partial链表寻找对应的页面,视图如下:

 

红线是prev,蓝线是next, partial以next为顺序进行历遍,因为最后1个page是最后加入的

然后执行lock_and_freeze_slab
lock_and_freeze_slab在mm/slub.c中,代码如下

static inline int lock_and_freeze_slab(struct kmem_cache_node *n,
                            struct page *page)
{
    //设置页面的PG_locked属性,并检测是否设置成功
    if (slab_trylock(page))
    {
        //从邻居页面链表中脱离
        list_del(&page->lru);
        //邻居页面数减1
        n->nr_partial--;
        //设置页面的PG_active属性
        SetSlabFrozen(page);
        return 1;
    }
    return 0;
}

lock_and_freeze_slab首先尝试锁定页面
如果页面锁定属性为0则锁定并返回操作成功
如果页面锁定属性已经为1,则不能操作该页面,返回0表示失败

get_partial返回页面后跳转到load_freelist处,接下来的执行就和之前的几种方法一样了

= 3= 笔记就到此结束了

在这次分析中心中还是有不少疑问的,像page中的inuse属性,这个属性目前分析是只在初始化中复制,只有每次kfree的时候减一,而从不自增,如果一个页面一直减一,但其内容一直为满的话,如果最后inuse属性为0的时候,不就把页面所有的内容都释放掉了,呢正在使用中的object也释放掉了,呢么不就导致内存同步错误了

还有分析过程中的一些函数不能理解是什么时候才调用的

看来光有理论还不行啊~  还需要多实践才能搞明白 T 3T

希望大家喜欢~ = 3=)/

转载请注明转自小生的BLOG zwolf.cublog.cn

你可能感兴趣的:(linux内核函数)