从kmalloc切入到分析slab分配

ps: 笔记以linux4.9版本源代码为基础,为了省略一些代码,使用 ‘–>’ 来表示包含的函数。

先查看下proc下面的slabinfo
这里写图片描述
这里写图片描述
从上面这个图,可以看到slab有很多的包含很多的对象,根据ULK里提到,高速缓存可以分规为两类:

  1. 普通高速缓存
  2. 专用高速缓存
    有什么区别呢?专用高速缓存是由kmem_cache_creat()函数创建的,专门使用与特殊类型的对象,例如:dentry buffer_head task_struct 等等,我们熟悉的结构体。普通高速缓存就是一些不是专用的,使用kmalloc进行分配的,例如:kmalloc-8 kmalloc-16 dma-kmalloc-8之类的对象。

从kmalloc 看slab分配过程:

static __always_inline void *__do_kmalloc(size_t size, gfp_t flags,
                      unsigned long caller)
{
    struct kmem_cache *cachep;
    void *ret;
    //kmalloc_slab 根据需要分配的大小来选择哪一个kmem_cache
    cachep = kmalloc_slab(size, flags);
    if (unlikely(ZERO_OR_NULL_PTR(cachep)))
        return cachep;
    //下面分析
    ret = slab_alloc(cachep, flags, caller);
    ....
}
static inline void *____cache_alloc(struct kmem_cache *cachep, gfp_t flags)
{

    ....
    //获取cpu本地高速缓存
    ac = cpu_cache_get(cachep); 
    if (likely(ac->avail)) { //有空闲obj
        ac->touched = 1;
        objp = ac->entry[--ac->avail];

        STATS_INC_ALLOCHIT(cachep);
        goto out;
    }

    STATS_INC_ALLOCMISS(cachep);
    //cpu本地高速缓存没有空闲obj,就进入下一步分配
    objp = cache_alloc_refill(cachep, flags); 
    ac = cpu_cache_get(cachep);

out:
    if (objp)
        kmemleak_erase(&ac->entry[ac->avail]);
    return objp;
}

先说明下struct array_cache 结构体:

struct array_cache { 
    unsigned int avail;/*当前cpu上有多少个可用的对象*/ 
    unsigned int limit;/*per_cpu里面最大的对象的个数,当超过这个值时,将对象返回给伙伴系统*/ 
    unsigned int batchcount;/*一次转入和转出的对象数量*/ 
    /*啥叫一次转入和转出的对象数量呢?
        转入即 当array_cache里面没有数据的时间,即avail为0的时候。array_cache需要从cpu共享高速缓存内读取多少个对象内容到cpu_cache补充,再如果cpu共享高速缓存里面没有的话就去。
        转出指本地高速缓存的空间已满,则按batchcount的值将batchcount个对象从本地高速缓存转移出去.
        (ps : 下面再具体的说内容)
    */
    unsigned int touched;/*标示本地cpu最近是否被使用*/ 
    spinlock_t lock;/*自旋锁*/ 
    void *entry[]; /* * Must have this definition in here for the proper * alignment of array_cache. Also simplifies accessing * the entries. */ 
    //entry指向对象的起始地址    
};

cachep->cpu_cache就是kmem_cache里包含的struct array_cache __percpu *cpu_cache。

ac = cpu_cache_get(cachep); 
--> return  this_cpu_ptr(cachep->cpu_cache);

分析下要使用cpu高速缓存进行读取操作:
因为每个CPU都有它们自己的硬件高速缓存(L1,L2,L3缓存),当此CPU上释放对象时,可能这个对象很可能还在这个CPU的硬件高速缓存中。
所以内核为每个CPU维护cpu_cache 一个这样的链表,当需要新的对象时,会优先尝试从当前CPU的本地CPU空闲对象链表(cpu_cache)获取相应大小的对象。如果命中的话(数据不会一直存在硬件缓存内的,会被其他数据换出),硬件缓存就不会有进行换进换出的动作了,大大减少了数据操作时间。这个本地CPU空闲对象链表在系统初始化完成后是一个空的链表,只有释放对象时才会将对象加入这个链表。

即操作会优先在cpu本地的高速缓存上进行读取,有空闲位置的话就直接进行使用,没有的话就进行第2步操作。那什么时候会进行第2步操作呢?

即 ac->avail == 0 , avail是表示当前cpu上有多少个可用的对象 , ==0即没有可以使用的对象了
如有有空闲对象的话就直接分配

objp = ac->entry[--ac->avail];

由于ac是记录着这次struct arrary_cache结构体存放地址,通过ac->entry数组,我们就得到下一紧接地址,这个地址可以看做是为本高速缓存内存的内存对象指针存放首地址,这里可以看出,我们是从最后一个对象开始分配的即avail对应的空闲对象是最热的,即最近释放出来的,更有可能驻留在CPU高速缓存中。

static void *cache_alloc_refill(struct kmem_cache *cachep, gfp_t flags)
{
    int batchcount;
    struct kmem_cache_node *n;
    struct array_cache *ac, *shared;
    int node;
    void *list = NULL;
    struct page *page;

    check_irq_off();
    node = numa_mem_id();// 获取node节点
    (ps : 不知道node节点的建议先看下
        http://blog.csdn.net/gatieme/article/details/52384058 )

    ac = cpu_cache_get(cachep);// 获取cpu本地高速缓存
    batchcount = ac->batchcount; //获取cpu本地高速缓存一次性需要读入的对象数
    if (!ac->touched && batchcount > BATCHREFILL_LIMIT) {// BATCHREFILL_LIMIT 16
    //如果cpu本地高速缓存近期没有被访问过,且batchcount大于16
    //那么为啥要有这个条件,下面这段有解释
        /*
         * If there was little recent activity on this cache, then
         * perform only a partial refill.  Otherwise we could generate
         * refill bouncing.
         */
        batchcount = BATCHREFILL_LIMIT;  
    }
    n = get_node(cachep, node); //获取kmem_cache_node
    // 那么为啥要根据node来进行kmem_cache_node获取呢
    //先看struct kmem_cache 结构里的定义
    //struct kmem_cache_node *node[MAX_NUMNODES];
    // MAX_NUMNODES为node的个数,即每个node节点维护一个kmem_cache_node
    //node节点有自己都对应一个本地物理内存,所以要区分具体看上面这个链接

    BUG_ON(ac->avail > 0 || !n);
    shared = READ_ONCE(n->shared);
    if (!n->free_objects && (!shared || !shared->avail)) 
    //空闲链表里没有空闲的obj 而且(没有cpu本地高速缓存或者没有可用的obj)
        goto direct_grow; 

    spin_lock(&n->list_lock);
    shared = READ_ONCE(n->shared);

    /* See if we can refill from the shared array */
    /*如果有则进行obj传输,下面具体分析*/
    if (shared && transfer_objects(ac, shared, batchcount)) {
        shared->touched = 1;
        goto alloc_done;
    }

    while (batchcount > 0) {
        /* Get slab alloc is to come from. */
        page = get_first_slab(n, false); //下面分析
        if (!page) //如果还是没有分配到页就跳到 must_grow
            goto must_grow;

        check_spinlock_acquired(cachep);

        batchcount = alloc_block(cachep, ac, page, batchcount);
        fixup_slab_list(cachep, n, page, &list);
    }
//下面是重新分配一个新的slab
must_grow:
    n->free_objects -= ac->avail;
alloc_done:
    spin_unlock(&n->list_lock);
    fixup_objfreelist_debug(cachep, &list);

direct_grow:
    if (unlikely(!ac->avail)) {
        /* Check if we can use obj in pfmemalloc slab */
        if (sk_memalloc_socks()) {
            void *obj = cache_alloc_pfmemalloc(cachep, n, flags);
            if (obj)
                return obj;
        }
        //这里是主要分析的地方,下面进行进一步分析
        page = cache_grow_begin(cachep, gfp_exact_node(flags), node);

        /*
         * cache_grow_begin() can reenable interrupts,
         * then ac could change.
         */
        ac = cpu_cache_get(cachep);
        if (!ac->avail && page)
            alloc_block(cachep, ac, page, batchcount);
        cache_grow_end(cachep, page);

        if (!ac->avail)
            return NULL;
    }
    ac->touched = 1;

    return ac->entry[--ac->avail];
}
static int transfer_objects(struct array_cache *to,
        struct array_cache *from, unsigned int max)
{
    /* Figure out how many entries to transfer */
    int nr = min3(from->avail, max, to->limit - to->avail);
    //从 from->avail :cpu共享高速缓存
    //  max: batchcount 最大读取数量
    //  to->limit - to->avail : cpu本地高速缓存可以接受的大小
    if (!nr)
        return 0;

    memcpy(to->entry + to->avail, from->entry + from->avail -nr,
            sizeof(void *) *nr);
    //将共享高速缓存的多个obj拷贝到本地高速缓存内
    from->avail -= nr;
    to->avail += nr;
    return nr;
}
static struct page *get_first_slab(struct kmem_cache_node *n, bool pfmemalloc)
{
    struct page *page;

    page = list_first_entry_or_null(&n->slabs_partial,
            struct page, lru); //从slabs_partial里读出一页
    if (!page) {
        n->free_touched = 1;
        page = list_first_entry_or_null(&n->slabs_free,
                struct page, lru); ///从slabs_free里读出一页
    }
    // 这里 pfmemalloc == false 所以没啥用,这条不分析了
    if (sk_memalloc_socks())
        return get_valid_first_slab(n, page, pfmemalloc);

    return page;
}
static __always_inline int alloc_block(struct kmem_cache *cachep,
        struct array_cache *ac, struct page *page, int batchcount)
{
    /*
     * There must be at least one object available for
     * allocation.
     */
    BUG_ON(page->active >= cachep->num);
    while (page->active < cachep->num && batchcount--) {
        STATS_INC_ALLOCED(cachep);
        STATS_INC_ACTIVE(cachep);
        STATS_SET_HIGH(cachep);
        //将slab里的对象放到cpu本地高速缓存里
        ac->entry[ac->avail++] = slab_get_obj(cachep, page);
    }

    return batchcount

//下面是具体的分配过程,不理解的话,下面有介绍到:
static void *slab_get_obj(struct kmem_cache *cachep, struct page *page)
{
    void *objp;

    objp = index_to_obj(cachep, page, get_free_obj(page, page->active));
    page->active++;

#if DEBUG
    if (cachep->flags & SLAB_STORE_USER)
        set_store_user_dirty(cachep);
#endif

    return objp;
}

static inline freelist_idx_t get_free_obj(struct page *page, unsigned int idx)
{
    return ((freelist_idx_t *)page->freelist)[idx];
}

static inline void *index_to_obj(struct kmem_cache *cache, struct page *page,
                 unsigned int idx)
{
    //page->s_mem指向首个obj的地址,cache->size为obj大小,idx为已经使用的obj的个数。
    return page->s_mem + cache->size * idx;
}

到这里我们已经知道怎么取空闲的obj了,不过整个slab什么结构,我们还不知道,先分析下slab的结构组成,如图所示:
这里写图片描述
4.9内核较之前面的版本有些许区别,他把freelist放在了对象的后面。
然后我们就得分析这玩意是怎么来的。
分析两段代码:

1.cache_alloc_refill 的direct_grow标签后面部分
2.kmem_cache_create创建kmem_cache

下面部分就不具体的分析了,代码多了看着蛋疼,这里只说怎么做出上面这图。
先说第一段代码direct_grow 下面的部分:

page = cache_grow_begin(cachep, gfp_exact_node(flags), node);
{
    ....
    //分配物理页
    page = kmem_getpages(cachep, local_flags, nodeid);
    ....
    //下面分析
    freelist = alloc_slabmgmt(cachep, page, offset,
            local_flags & ~GFP_CONSTRAINT_MASK, page_node);
    ....
    //下面分析
    cache_init_objs(cachep, page);
}
static void *alloc_slabmgmt(struct kmem_cache *cachep,
                   struct page *page, int colour_off,
                   gfp_t local_flags, int nodeid)
{
    void *freelist;
    void *addr = page_address(page);

    page->s_mem = addr + colour_off;  //这里即把page->s_mem指向页开始colour_off偏移处
    page->active = 0; //以分配的obj数量,这里初始化为0

    if (OBJFREELIST_SLAB(cachep)) //这里应该是根据cachep的flag是否设立freelist
        freelist = NULL;
    else if (OFF_SLAB(cachep)) { //这里是freelist与obj分离,需重新分配freelist
        /* Slab management obj is off-slab. */
        freelist = kmem_cache_alloc_node(cachep->freelist_cache,
                          local_flags, nodeid);
        if (!freelist)
            return NULL;
    } else {//这里是freelist与obj在一起,地址相连
        /* We will use last bytes at the slab for freelist */
        freelist = addr + (PAGE_SIZE << cachep->gfporder) -
                cachep->freelist_size;
        //这里我们不看分离情况
        //我们看到freelist指向了slab最后向前freelist_size的地址处
        //那么freelist_size大小是多少呢?freelist指向的地址在哪呢?
        //不知道的话,我们下面会分析
    }

    return freelist;
}
cache_init_objs(cachep, page);
    -->shuffled = shuffle_freelist(cachep, page);
        -->{        
                count = cachep->num;
                for (i = 0; i < count; i++)
                    set_free_obj(page, i, i);
           }    
            --> set_free_obj(page, i, i);

static inline void set_free_obj(struct page *page,
                    unsigned int idx, freelist_idx_t val)
{
    //这里从0开始排,排到count-1大小       
    //那cachep->num是多少呢?下面分析
    ((freelist_idx_t *)(page->freelist))[idx] = val;
}

//这此为止我们已经知道slab的具体结构了,但是排列间隔及大小我们还不清楚,然后我们再进行下一步分析。

第二步分析kmem_cache_creat:

struct kmem_cache * kmem_cache_create(const char *name, size_t size, size_t align,
          unsigned long flags, void (*ctor)(void *))
    --> create_cache
        --> __kmem_cache_create //这里有几个重要的东西,先分析下
int   __kmem_cache_create (struct kmem_cache *cachep, unsigned long flags)
{
    size_t size = cachep->size;
    ....
    //关于size的代码有点分散,简要说明下,size是指的是obj的大小加上一个偏移,作    用是为了进行对齐操作
    ....
    f (ralign < cachep->align) {
        ralign = cachep->align;
    }
    ....
    cachep->align = ralign; // cachep->align是kmem_cache_create指定大小,ralign这里是字   大小
    cachep->colour_off = cache_line_size(); //读取缓存行,缓存行一般64字节(注:这里说到着色,下面可以具体分析一下)
    /* Offset must be a multiple of the alignment. */
    if (cachep->colour_off < cachep->align)
        cachep->colour_off = cachep->align;
    ....
    if (set_objfreelist_slab_cache(cachep, size, flags)) { //下面分析
        flags |= CFLGS_OBJFREELIST_SLAB;
        goto done;
    }   
    ....
 
done:
    cachep->freelist_size = cachep->num * sizeof(freelist_idx_t); // freelist_size大小就是freelist  数量* freelist大小
    cachep->flags = flags;
    cachep->allocflags = __GFP_COMP;
    if (flags & SLAB_CACHE_DMA)
        cachep->allocflags |= GFP_DMA;
    cachep->size = size;// size是obj大小+偏移
    cachep->reciprocal_buffer_size = reciprocal_value(size);
    ....
 }
static bool set_objfreelist_slab_cache(struct kmem_cache *cachep,
            size_t size, unsigned long flags)
{
    size_t left;
    cachep->num = 0;
    if (cachep->ctor || flags & SLAB_DESTROY_BY_RCU)
        return false;
    left = calculate_slab_order(cachep, size,
            flags | CFLGS_OBJFREELIST_SLAB); //下面分析
    if (!cachep->num)
        return false;
    if (cachep->num * sizeof(freelist_idx_t) > cachep->object_size) // freelist_size不能大于    //object_size
        return false;
    cachep->colour = left / cachep->colour_off;  // slab 的 颜色
    return true;
}
calculate_slab_order
    --> num = cache_estimate(gfporder, size, flags, &remainder);

static unsigned int cache_estimate(unsigned long gfporder, size_t buffer_size,
        unsigned long flags, size_t *left_over)
{
    unsigned int num;
    size_t slab_size = PAGE_SIZE << gfporder;
    if (flags & (CFLGS_OBJFREELIST_SLAB | CFLGS_OFF_SLAB)) {
        num = slab_size / buffer_size;
        *left_over = slab_size % buffer_size;
    } else { // 我们看下面
        num = slab_size / (buffer_size + sizeof(freelist_idx_t)); //可以看出num 大小就等于
        //slab的大小除以(buffer_size + sizeof(freelist_idx_t) 注:buffer_size就等于       //cachep->size
        *left_over = slab_size %
            (buffer_size + sizeof(freelist_idx_t)); //剩下的空间大小,与slab颜色相关
    }

    return num;
}

好了,以上就是分析了slab的具体结构来源,现在分析一下遗留的问题,slab的着色。

slab的着色:

先找源头,即上图slab结构的colour_off,哪里来的呢?回到分配新的slab那里,
cache_grow_begin函数中

    n->colour_next++;//上一个slab的颜色偏移 ++
    if (n->colour_next >= cachep->colour) // 在上面提到过得 cachep->colour 
        n->colour_next = 0;

    offset = n->colour_next; 
    if (offset >= cachep->colour)
        offset = 0;

    offset *= cachep->colour_off; //这里是就offset  == colour_off

提一下 cachep->colour 怎么来的:
上面cache_estimate函数中剩下的空间大小left_over,往回走到调用它的函数
set_objfreelist_slab_cache里面,这里写到:
cachep->colour = left / cachep->colour_off;
left就是上网的left_over
好了,现在都知道cachep->colour的来源了。着色即偏移。

然后是分析一下怎么进行slab里面obj的取和释放:
取:
即slab_get_obj 函数,上面已经挂上代码了,

释放:
怎么找释放地方呢?我们分析的是kmalloc,对应的是什么,当然是kfree。好了找到slab_put_obj 释放函数

static void slab_put_obj(struct kmem_cache *cachep,
            struct page *page, void *objp)
{
    unsigned int objnr = obj_to_index(cachep, page, objp);//获取释放的freelist组号
#if DEBUG //DEBUG 不用管它
    unsigned int i;

    /* Verify double free bug */
    for (i = page->active; i < cachep->num; i++) {
        if (get_free_obj(page, i) == objnr) {
            pr_err("slab: double free detected in cache '%s', objp %p\n",
                   cachep->name, objp);
            BUG();
        }
    }
#endif
    page->active--;
    if (!page->freelist)
        page->freelist = objp + obj_offset(cachep); 

    set_free_obj(page, page->active, objnr);  //在freelist组号为active里写下objnr号(即为刚释放的obj组号)
}

介绍这么多,具体是怎么样的还是配合图比较好容易理解
//建议看下面这个博客,写的真是好,我是也参考它进行分析的
http://www.cnblogs.com/tolimit/p/4566189.html?utm_source=tuicool&utm_medium=referral

好了回归正题,总结下slab的具体分配过程:

先到kmem_cache里面找cpu本地高速缓存里面找没有空闲的obj,没有再到cpu的共享高速缓存,再没有就重新建立一个空的slab。
最后将分配的obj移动到cpu本地高速缓存后再进行分配操作,即移动到ac->entry。
从cpu的共享高速缓存 到 cpu本地高速缓存:
transfer_objects(ac, shared, batchcount)
从slab 到 cpu本地高速缓存:
alloc_block(cachep, ac, page, batchcount);
有兴趣的可以去看一下。
观察仔细的朋友一定发现了,这样操作在一定形式上模仿了硬件的缓存机制。
cpu本地高速缓存 : L1高速缓存
cpu共享高速缓存 : L2高速缓存
slab : 内存

好了,先分析到这里,写的有些乱,大家见谅。

  • 6
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值