mm-slab对象的分配

slab cache建立后,就可以从中分配对象。以 kmalloc_caches 为例,执行 kmalloc 函数时会从中分配对象。

1. kmalloc

搞内核的肯定对 kmalloc 不会陌生,和 malloc 函数类似,这个函数用来分配内存,定义在 include/linux/slab.h 中:

/* kmalloc is the normal method of allocating memory
 * for objects smaller than page size in the kernel.
 * @size: how many bytes of memory are required.
 * @flags: the type of memory to allocate.
 */
static __always_inline void *kmalloc(size_t size, gfp_t flags)
{
    if (__builtin_constant_p(size)) {
        /*# slub下KMALLOC_MAX_CACHE_SIZE = 8kb */
        if (size > KMALLOC_MAX_CACHE_SIZE)
            return kmalloc_large(size, flags);
#ifndef CONFIG_SLOB /* x86下默认关闭 */
        if (!(flags & GFP_DMA)) {
            int index = kmalloc_index(size);

            if (!index)
                return ZERO_SIZE_PTR;

            return kmem_cache_alloc_trace(kmalloc_caches[index],
                    flags, size);
        }
#endif
    }
    return __kmalloc(size, flags);
}

虽然代码的注释说明 kmalloc 函数通常用于向内核的对象分配小于页面大小的内存,但是函数一开始会判断请求的内存的大小,如果大于8KB,就会调用 kmalloc_large 函数;否则调用 __kmalloc 函数。

kmalloc 函数主要是两个分支:一个是分配小于等于8kb的内存 __kmalloc ,一个是分配大于8kb的内存 kmalloc_large ,对应的函数调用关系如下:

kmalloc

  1. kmalloc_large
    • get_order
    • kmalloc_order
      • alloc_kmem_pages
        • alloc_pages
          • __alloc_pages_nodemask
  2. __kmalloc
    • kmalloc_large -- unlikely
    • kmalloc_slab -- kmalloc_caches[]
    • slab_alloc
      • slab_alloc_node
        • __slab_alloc -- unlikely
        • prefetch

调用 kmalloc 函数需要提供 flags 参数,指明要申请的内存的类型,或者说,可以用于响应该申请操作的内存类型。

根据内核中 kmalloc 函数的注释, flags 参数可以是下列值:

  • %GFP_USER - Allocate memory on behalf of user. May sleep.
  • %GFP_KERNEL - Allocate normal kernel ram. May sleep.
  • %GFP_ATOMIC - Allocation will not sleep.
    May use emergency pools. For example, use this inside interrupt handlers.
  • %GFP_HIGHUSER - Allocate pages from high memory.
  • %GFP_NOIO - Do not do any I/O at all while trying to get memory.
  • %GFP_NOFS - Do not make any fs calls while trying to get memory.
  • %GFP_NOWAIT - Allocation will not sleep.
  • %__GFP_THISNODE - Allocate node-local memory only.
  • %GFP_DMA - Allocation suitable for DMA.
    Should only be used for kmalloc() caches. Otherwise, use a slab created with SLAB_DMA.

Also it is possible to set different flags by OR'ing in one or more of the following additional @flags:

  • %__GFP_COLD - Request cache-cold pages instead of trying to return cache-warm pages.
  • %__GFP_HIGH - This allocation has high priority and may use emergency pools.
  • %__GFP_NOFAIL - Indicate that this allocation is in no way allowed to fail(think twice before using).
  • %__GFP_NORETRY - If memory is not immediately available, then give up at once.
  • %__GFP_NOWARN - If allocation fails, don't issue any warnings.
  • %__GFP_REPEAT - If allocation fails initially, try once more before failing.

更多的标志符定义在 include/linux/gfp.h 中,上面列出的都是常用的标志符。

这些标志符可以分为三类:行为修饰符、区域修饰符、类型标志。

2. __kmalloc

如果请求的内存小于8KB,就会调用 __kmalloc 函数, CONFIG_SLUB 下,这个函数定义在 mm/slub.c 中:

void *__kmalloc(size_t size, gfp_t flags)
{
    struct kmem_cache *s;
    void *ret;
    /* KMALLOC_MAX_CACHE_SIZE = 8MB */
    if (unlikely(size > KMALLOC_MAX_CACHE_SIZE))
        /* 和kmalloc函数size大于8kb时采用相同的分配函数 */
        return kmalloc_large(size, flags);
    /* 获取slab cache */
    s = kmalloc_slab(size, flags);

    if (unlikely(ZERO_OR_NULL_PTR(s)))
        return s;

    ret = slab_alloc(s, flags, _RET_IP_);

    trace_kmalloc(_RET_IP_, ret, size, s->size, flags);

    return ret;
}
EXPORT_SYMBOL(__kmalloc);

2.1. kmalloc_slab

其中 kmalloc_slab 函数根据传入的 size 返回slab高速缓存,如果 size 大于SLUB下 KMALLOC_MAX_SIZE = 8MB ,直接返回NULL:

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

    /*# KMALLOC_MAX_SIZE = 8MB */
    if (unlikely(size > KMALLOC_MAX_SIZE)) {
        WARN_ON_ONCE(!(flags & __GFP_NOWARN));
        return NULL;
    }

    if (size <= 192) {
        if (!size)
            return ZERO_SIZE_PTR;
        /*
         size_index数组根据所需的slab cache的
         大小转化为对应的index,只涉及到小于等
         于192的情况 */
        index = size_index[size_index_elem(size)];
    } else
        /*
         根据代码中的注释,fls函数返回参数中
         最高有效位的index */
        index = fls(size - 1);

#ifdef CONFIG_ZONE_DMA
    if (unlikely((flags & GFP_DMA)))
        return kmalloc_dma_caches[index];

#endif
    return kmalloc_caches[index];
}

2.2. slab_alloc

如果 __kmalloc 函数调用 kmalloc_slab 之后返回值不为空,说明系统中有符合请求的内存大小的kmalloc slab分配器,就会调用 slab_alloc 函数,对slab分配器进行修改,并从中分配一个请求的对象作为返回值。

CONFIG_SLUBslab_alloc 函数通过 slab_alloc_node 函数实现,后者定义在 mm/slub.c 中。

这个函数的主体逻辑是两个分支语句:一种情况是当前CPU的freelist为空,或者分配slab的节点和当前CPU的内存节点不匹配,执行较慢的分配路径 __slab_alloc ;否则直接从freelist中获取新分配的对象,通过预取指令将对象放到cache中:

static __always_inline void *slab_alloc_node(struct kmem_cache *s,
        gfp_t gfpflags, int node, unsigned long addr)
{
    ...
    object = c->freelist;
    /* page属于per-cpu变量c */
    page = c->page;
    /* 没有可用的对象,或者page不属于当前node */
    if (unlikely(!object || !node_match(page, node))) {
        /* 执行慢分配路径 */
        object = __slab_alloc(s, gfpflags, node, addr, c);
        /*# increase the statistic in s->cpu_slab */
        stat(s, ALLOC_SLOWPATH);
    } else {
        /* 获取新的对象 */
        void *next_object = get_freepointer_safe(s, object);
        /* 更新freelist和tid的值 */
        if (unlikely(!this_cpu_cmpxchg_double(
                s->cpu_slab->freelist, s->cpu_slab->tid,
                object, tid,
                next_object, next_tid(tid)))) {
            /* 记录更新失败的信息 */
            note_cmpxchg_failure("slab_alloc", s, tid);
            goto redo;
        }
        /* 通过指令预取分配的对象 */
        prefetch_freepointer(s, next_object);
        /* 增加s->cpu_slab的统计信息 */
        stat(s, ALLOC_FASTPATH);
    }
    ...
}

2.2.1. __slab_alloc

正如代码注释所说:

Slow path. The lockless freelist is empty or we need to perform debugging duties.

__slab_alloc 执行速度较慢,无锁freelist为空或者需要执行调试操作时会走这条路径。

Processing is still very fast if new objects have been freed to the regular freelist. In that case we simply take over the regular freelist as the lockless freelist and zap the regular freelist.

如果常规freelist中有新的对象被释放,仍然能够快速处理。这种情况下,只需要将常规freelist当做无锁freelist使用,然后移除常规freelist。

If that is not working then we fall back to the partial lists. We take the first element of the freelist as the object to allocate now and move the rest of the freelist to the lockless freelist.

否则,转向partial list,将freelist中的第一个元素视为要分配的对象,并将剩余的freelist放到无锁freelist中。

And if we were unable to get a new slab from the partial slab lists then we need to allocate a new slab. This is the slowest path since it involves a call to the page allocator and the setup of a new slab.

如果无法从partial list中获取一个新的slab(内存页),就分配一个新的slab,这是最慢的路径——需要调用页分配器(buddy allocator),并且初始化slab。

static void *__slab_alloc(struct kmem_cache *s, gfp_t gfpflags, int node,
              unsigned long addr, struct kmem_cache_cpu *c)
{
    void *freelist;
    struct page *page;
    unsigned long flags;

    local_irq_save(flags);
#ifdef CONFIG_PREEMPT
    /*
     * We may have been preempted and rescheduled on a different
     * cpu before disabling interrupts. Need to reload cpu area
     * pointer.
     */
    c = this_cpu_ptr(s->cpu_slab);
#endif
    /* page指向当前CPU分配slab的内存页 */
    page = c->page;
    /* 内存页为空,跳转到分配操作 */
    if (!page)
        goto new_slab;
redo:
    /* 内存page和node节点不匹配 */
    if (unlikely(!node_match(page, node))) {
        stat(s, ALLOC_NODE_MISMATCH);
        /* 将page从slab cache中删除 */
        deactivate_slab(s, page, c->freelist);
        c->page = NULL;
        c->freelist = NULL;
        /* 跳转到分配操作 */
        goto new_slab;
    }

    /*
     * By rights, we should be searching for a slab page that was
     * PFMEMALLOC but right now, we are losing the pfmemalloc
     * information when the page leaves the per-cpu allocator
     */
    if (unlikely(!pfmemalloc_match(page, gfpflags))) {
        deactivate_slab(s, page, c->freelist);
        c->page = NULL;
        c->freelist = NULL;
        goto new_slab;
    }

    /* must check again c->freelist in case of cpu migration or IRQ */
    freelist = c->freelist;
    /*
     freelist可用,跳转到加载操作;这种情况即函数注释的第一种情况:
     slab中有新的对象释放,直接获取然后返回即可 */
    if (freelist)
        goto load_freelist;

    freelist = get_freelist(s, page);

    if (!freelist) {
        c->page = NULL;
        stat(s, DEACTIVATE_BYPASS);
        /* 没有空闲对象可用,从partial list中获取可用对象 */
        goto new_slab;
    }
    /*
     ALLOC_REFILL对应的应该是第一次请求空闲对象失败,但是
     执行慢路径时当前slab有对象被释放,并且分配成功 */
    stat(s, ALLOC_REFILL);

load_freelist:
    /*
     * freelist is pointing to the list of objects to be used.
     * page is pointing to the page from which the objects are obtained.
     * That page must be frozen for per cpu allocations to work.
     */
    VM_BUG_ON(!c->page->frozen);
    /* 从slab cache中获取下一个可用的对象 */
    c->freelist = get_freepointer(s, freelist);
    /* tid + 1 */
    c->tid = next_tid(c->tid);
    local_irq_restore(flags);
    return freelist;

new_slab:
    /* partial list可用,从partial list分配新的对象 */
    if (c->partial) {
        page = c->page = c->partial;
        c->partial = page->next;
        stat(s, CPU_PARTIAL_ALLOC);
        c->freelist = NULL;
        /*
         将partial list的第一个slab作为当前正在使用的slab,
         再次执行各种判断逻辑 */
        goto redo;
    }
    /* partial list不可用,创建新的slab */
    freelist = new_slab_objects(s, gfpflags, node, &c);
    /* 创建新的slab失败 */
    if (unlikely(!freelist)) {
        slab_out_of_memory(s, gfpflags, node);
        local_irq_restore(flags);
        return NULL;
    }

    page = c->page;
    if (likely(!kmem_cache_debug(s) && pfmemalloc_match(page, gfpflags)))
        goto load_freelist;

    /* Only entered in the debug case */
    if (kmem_cache_debug(s) &&
            !alloc_debug_processing(s, page, freelist, addr))
        goto new_slab;  /* Slab failed checks. Next slab needed */

    deactivate_slab(s, page, get_freepointer(s, freelist));
    c->page = NULL;
    c->freelist = NULL;
    local_irq_restore(flags);
    return freelist;
}

2.2.1.1. new_slab_objects

执行慢路径 __slab_alloc 时,如果当前CPU的slab cache的partial list也不可用,就通过 new_slab_objects 函数,先从属于当前节点的slab中通过 get_partial 尝试从当前的节点获取一个partial slab,如果失败再通过 get_any_partial 从其他的节点获取一个partial slab,如果都失败则通过伙伴系统分配新的内存页作为slab使用。

2.2.1.1.1. get_partial_node

get_partial 函数的主要功能通过 get_partial_node 从指定的节点获取partial slab,然后保存到当前CPU的slab cache的partial list中。

static void *get_partial_node(struct kmem_cache *s, struct kmem_cache_node *n,
                struct kmem_cache_cpu *c, gfp_t flags)
{
    struct page *page, *page2;
    void *object = NULL;
    int 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_partials()
     * will return NULL.
     */
    if (!n || !n->nr_partial)
        return NULL;

    spin_lock(&n->list_lock);
    list_for_each_entry_safe(page, page2, &n->partial, lru) {
        void *t;
        /* 如果没有设置pfmemalloc,直接跳过 */
        if (!pfmemalloc_match(page, flags))
            continue;
        /*
         将page从slab cache的partial表移除,并且将其中
         包含的可用对象的数量保存在objects中,设置page
         的冻结标志 */
        t = acquire_slab(s, n, page, object == NULL, &objects);
        /*
         如果acquire_slab函数执行cmpxchg操作失败,!t为真。
         暂时没有想到什么时候会出现这种情况 */
        if (!t)
            break;
        /* 将可用对象增加到available */
        available += objects;
        if (!object) {
            /* 设置当前页面为正在使用的slab */
            c->page = page;
            stat(s, ALLOC_FROM_PARTIAL);
            object = t;
        } else {
            /* 添加到当前CPU的partial list中 */
            put_cpu_partial(s, page, 0);
            stat(s, CPU_PARTIAL_NODE);
        }
        /*
         如果可用的对象已经大于s->cpu_partial的一半,退出
         循环;否则继续便利当前节点的partial list*/
        if (!kmem_cache_has_cpu_partial(s)
            || available > s->cpu_partial / 2)
            break;

    }
    spin_unlock(&n->list_lock);
    return object;
}
2.2.1.1.2. get_any_partial

get_any_partial 按照距离当前节点由近到远的顺序在其他的节点上寻找可用的partial slab,主要功能同样通过 get_partial_node 实现。

2.2.1.2. deactivate_slab

deactivate_slab 函数将一个slab从slab cache中移除,涉及到slab释放的问题,放在下一篇“slab对象的回收”说明。

3. kmem_cache_alloc

“slab初始化”中说过,更一般的,通过 kmem_cache_create 函数创建一个slab cache。

创建完成后可以通过函数 kmem_cache_alloc 函数从slab cache中分配指定大小的对象。

4. 总结

从代码可以看出SLUB分配对象时的操作流程(简化流程):

  1. 首先从当前CPU的freelist中获取可用对象,获取成功直接返回对象,更新freelist,通过预取指令放到cache,即快路径。
  2. 如果freelist没有对象可用,执行慢路径,从CPU的partial slab中取出第一个元素,作为当前正在使用的slab,并尝试从中分配对象。
  3. 如果CPU的partial list也为空,就从当前节点中获取一个partial slab,分配给当前CPU使用。
  4. 如果当前节点中也没有partial slab可用,就按照距离当前节点的从近到远的顺序,分配partial slab使用。

所以, __slab_alloc 函数的注释中所说的“lockless freelist”和“regular freelist”,指的应该是 struct kmem_cache_cpufreelist 成员;“the rest of the freelist”的freelist指的应该是第一个partial slab分配完请求的对象后剩余的freelist。

posted @ 2019-10-22 11:25  glob  阅读(908)  评论(0编辑  收藏  举报