mm-slab初始化

SLAB用来响应较小的内存分配请求,事实上,现在的Linux内核使用的是SLUB——unqueued SLAB分配器。

Linux内核支持三种分配器,分别为SLAB,SLOB,SLUB。x86架构下,默认采用SLUB分配器。
因此,本文解析内核代码时,默认采用SLUB下的代码定义;同时,虽然三种分配器名称不同,但是都起源于SLAB,因此本文依然用slab来指代小内存分配器,而不是用SLUB。

由于slab涉及到的内容较多,初步分为三部分进行介绍:slab初始化,slab对象的分配,slab对象的回收。

建议先看一下资料关于SLUB的介绍,对整个系统即各个组件之间的关系有个大体认识。

slab cache的初始化通过函数 kmem_cache_init 函数完成,主要完成三个工作:创建 kmem_cache_nodekmem_cachekmalloc_caches 三个slab cache。

kmem_cache_node 分配 struct kmem_cache_node 对象,因为 struct kmem_cache 对象包含 struct kmem_cache_node 的成员,因此先创建 kmem_cache_node

kmem_cache 分配 struct kmem_cache 对象,供创建其他的slab cache使用,例如 kmalloc_caches slab cache。

kmalloc_caches 是一个slab cache数组,包含分配各种大小的对象的slab cache,供 kmalloc 函数使用。

1. struct kmem_cache

struct kmem_cache 是描述slab cache的结构体,定义在 include/linux/slub_def.h ,以 kmem_cache_node 分配器为例, 执行这些函数时,设置其成员变量的函数为:

  • create_boot_cachemm/slab_common.c

    • const char *name = "kmem_cache_node"
      slab cache的名称,显示在 /sys/kernel/slab/ 目录下
    • int size = sizeof(struct kmem_cache_node) = 64
      slab cache保存的对象的大小,包含元数据
    • int object_size = 64
      slab cache保存的对象的大小,不包含包含元数据
    • int align = 64
    • int refcount = -1
      重用计数器,请求创建新的SLUB时,SLUB分配器重用已经创建的相似大小的SLUB,以减少SLUB种类的个数
  • kmem_cache_openmm/slub.c

    • unsigned long flags
      标志位,指明slab cache的特点
    • int reserved = 0
    • unsigned long min_partial = 5
      每个node的部分空slab缓冲区数量不能低于这个值
    • unsigned long cpu_partial = 30
      cpu的可用对象数量的最大值
    • unsigned long remote_node_defrag_ratio = 1000
      用于NUMA系统,值越小,越倾向于在本节点分配对象
  • calculate_sizesmm/slub.c

    • int inuse = 64
      元数据的偏移量
    • int size = 64
    • gfp_t allocflags = 0
      每一次分配时使用的标志
    • struct kmem_cache_order_object oo = { 64 }
      保存slab需要的页框数量的order值和object的数量,可以计算出需要多少页框,这个是默认值,初始化时根据经验设置
    • struct kmem_cache_order_object min = { 64 }
      保存slab需要的页框数量的order值和object的数量,这个是最小值,如果尝试用oo分配失败,使用最小值进行分配
    • struct kmem_cache_order_object max = { 64 }
      保存slab需要的页框数量的order值和object的数量,这个是最大值

2. create_boot_cache

kmem_cache_init 主要调用 create_boot_cache 创建slab分配器,设置参数。涉及到的函数及调用关系如下:

kmem_cache_init

  • create_boot_cache(kmem_cache_node)
    • __kmem_cache_create
      • kmem_cache_open
        • init_kmem_cache_nodes
          • early_kmem_cache_node_alloc / kmem_cache_alloc_node
            • init_kmem_cache_node
        • alloc_kmem_cache_cpus
  • create_boot_cache(kmem_cache)
  • create_kmalloc_caches -- kmalloc_caches[]

开启 CONFIG_SLUB 时, create_boot_cache 只有创建 kmem_cache_nodekmem_cachekmalloc_caches 时会调用。

create_boot_cache 会设置传入cache的一些成员变量,然后调用 __kmem_cache_create 函数。

void __init create_boot_cache(struct kmem_cache *s, const char *name, size_t size,
        unsigned long flags)
{
    int err;
    s->name = name;
    s->size = s->object_size = size;
    s->align = calculate_alignment(flags, ARCH_KMALLOC_MINALIGN, size);
    err = __kmem_cache_create(s, flags);

    if (err)
        panic("Creation of kmalloc slab %s size=%zu failed. Reason %d\n",
                    name, size, err);

    s->refcount = -1;   /* Exempt from merging for now */
}

__kmem_cache_crete 主要通过 kmem_cache_open 实现,这个函数除了设置cache的一些参数以外,还会调用 init_kmem_cache_nodesalloc_kmem_cache_cpus ;前者用于初始化kmem_cache中的 struct kmem_cache_node *node[MAX_NUMNODES] 成员,
后者用于分配 struct kmem_cache 中的per-cpu成员变量 struct kmem_cache_cpu __percpu *cpu_slab

2.1. init_kmem_cache_nodes

init_kmem_cache_nodes 函数根据当前slab系统的状态,为传入的 struct kmem_cache 对象分配 struct kmem_cache_node 类型的成员变量。

static int init_kmem_cache_nodes(struct kmem_cache *s)
{
    int node;
    /* 遍历每个具有normal内存的内存节点 */
    for_each_node_state(node, N_NORMAL_MEMORY) {
        struct kmem_cache_node *n;
        /*
         此时kmem_cache_node和kmem_cache两个slab
         cache还没有建立,不能使用 */
        if (slab_state == DOWN) {
            early_kmem_cache_node_alloc(node);
            continue;
        }
        /*
         kmem_cache_node已经建立,直接从中分配一个
         struct kmem_cache_node对象 */
        n = kmem_cache_alloc_node(kmem_cache_node,
                        GFP_KERNEL, node);

        if (!n) {
            free_kmem_cache_nodes(s);
            return 0;
        }
        /* 设置kmem_cache的per-cpu变量指向正确的节点 */
        s->node[node] = n;
        /* 初始化kmem_cache_node的成员变量 */
        init_kmem_cache_node(n);
    }
    return 1;
}

2.1.1. early_kmem_cache_node_alloc

如果系统的slab系统还没有启动,即 slab_state = DOWN ,这发生在 kmalloc_caches 数组初始化之前——还没有调用 create_kmalloc_caches ,就通过 early_kmem_cache_node_alloc 分配并初始化 kmem_cache_node

根据内核的代码,当且仅当创建 kmem_cache_nodekmem_cache_node 时,会执行 early_kmem_cache_node_alloc 函数。

这个函数首先通过 new_slab 函数从指定的内存节点通过buddy allocator分配一个新的page给 kmem_cache_node 分配器,然后初始化struct page中和SLUB相关的信息,并且将page保存到 kmem_cache_nodenode[] 域中。

static void early_kmem_cache_node_alloc(int node)
{
    struct page *page;
    struct kmem_cache_node *n;

    BUG_ON(kmem_cache_node->size < sizeof(struct kmem_cache_node));

    /*
     调用伙伴系统的__alloc_pages_nodemask函数分配
     新的page给kmem_cache_node,并设置page的成员变量 */
    page = new_slab(kmem_cache_node, GFP_NOWAIT, node);

    BUG_ON(!page);
    if (page_to_nid(page) != node) {
        pr_err("SLUB: Unable to allocate memory from node %d\n", node);
        pr_err("SLUB: Allocating a useless per node structure in order to be able to continue\n");
    }
    /* 对于新分配的page,page->freelist为页面的起始地址 */
    n = page->freelist;
    BUG_ON(!n);
    /*
     page->freelist指向page的起始地址,
     即第一个可用的free object */
    page->freelist = get_freepointer(kmem_cache_node, n);
    page->inuse = 1;
    page->frozen = 0;
    /* 保存到kmem_cache_node中对应的节点中 */
    kmem_cache_node->node[node] = n;
#ifdef CONFIG_SLUB_DEBUG
    /* 根据kmem_cache_node的标志设置对象 */
    init_object(kmem_cache_node, n, SLUB_RED_ACTIVE);
    init_tracking(kmem_cache_node, n);
#endif
    /*
     初始化cache node中partial list,nr_slabs,
     total_objects,full list */
    init_kmem_cache_node(n);
    /* 增加cache node的统计信息,包括nr_slabs,total_objects */
    inc_slabs_node(kmem_cache_node, node, page->objects);

    /*
     * No locks need to be taken here as it has just been
     * initialized and there is no concurrent access.
     */
     /* 将page添加到node的partial list中 */
    __add_partial(n, page, DEACTIVATE_TO_HEAD);
}

2.1.1.1. new_slab

early_kmem_cache_node_alloc 首先调用 new_slab 为指定的内存节点分配用作slab的内存页,并且初始化其中的对象。

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

    BUG_ON(flags & GFP_SLAB_BUG_MASK);

    /* allocate_slab最终调用alloc_pages函数分配新的page,
       并且设置page->objects = s->oo.x。
       函数根据传入的kmem_cache->oo的大小分配指定数量的page。*/
    page = allocate_slab(s,
        flags & (GFP_RECLAIM_MASK | GFP_CONSTRAINT_MASK), node);
    if (!page)
        goto out;
    /* 如果是复合页面,返回复合页面包含的page数;
       否则返回0 */
    order = compound_order(page);
    /* 将新的page包含的对象数添加到slab cache中对应的节点中,
       并增加slab的计数(每个page计为一个slab) */
    inc_slabs_node(s, page_to_nid(page), page->objects);
    /* 设置page所属的slab cache */
    page->slab_cache = s;
    /* 设置page的PG_slab标志 */
    __SetPageSlab(page);
    /* pfmemalloc标志 */
    if (page->pfmemalloc)
        SetPageSlabPfmemalloc(page);
    /* start设置为page的内核虚拟地址 */
    start = page_address(page);
    /* 设置slab cache为特定值 */
    if (unlikely(s->flags & SLAB_POISON))
        memset(start, POISON_INUSE, PAGE_SIZE << order);

    last = start;
    /*
     设置slab cache中的每个对象指向下一个对象,如果
     slab cache定义了构造函数,用构造函数初始化对象 */
    for_each_object(p, s, start, page->objects) {
        setup_object(s, page, last);
        set_freepointer(s, last, p);
        last = p;
    }
    setup_object(s, page, last);
    /* 最后一个object指向NULL */
    set_freepointer(s, last, NULL);
    /* 设置page->freelist为内存页的起始虚拟地址 */
    page->freelist = start;
    /* page->inuse等于页面中包含的对象的数量 */
    page->inuse = page->objects;
    page->frozen = 1;
out:
    return page;
}

2.1.2. kmem_cache_alloc_node

kmem_cache_alloc_node 函数和 slab_alloc 一样,通过 slab_alloc_node 实现,之后在介绍 slab_alloc 函数时详细说明。

void *kmem_cache_alloc_node(struct kmem_cache *s, gfp_t gfpflags, int node)
{
    void *ret = slab_alloc_node(s, gfpflags, node, _RET_IP_);

    trace_kmem_cache_alloc_node(_RET_IP_, ret,
                    s->object_size, s->size, gfpflags, node);

    return ret;
}
EXPORT_SYMBOL(kmem_cache_alloc_node);

2.2. alloc_kmem_cache_cpus

kmem_cache_open 函数调用 init_kmem_cache_nodes 后,接着调用 alloc_kmem_cache_cpus ,初始化 struct kmem_cache 结构体中的per-cpu成员 cpu_slab

cpu_slab 类型为 struct kmem_cache_cpu ,包含每个CPU的slab信息:

struct kmem_cache_cpu {
    void **freelist;    /* Pointer to next available object */
    unsigned long tid;  /* Globally unique transaction id */
    struct page *page;  /* The slab from which we are allocating */
    struct page *partial;   /* Partially allocated frozen slabs */
#ifdef CONFIG_SLUB_STATS
    unsigned stat[NR_SLUB_STAT_ITEMS];
#endif
};

对于多CPU系统而言,每一个slab cache对象,都包含系统中所有CPU的同种类型的slab信息——对于某种对象的slab cache,系统中的每个CPU都有一个slab用来响应对应CPU的对象分配请求。

这些信息保存在 cpu_slab ,而 struct kmem_cache 包括所有CPU的slab信息。其中和 cpu_slab 相关的成员变量包括 cpu_partial ,即每个CPU需要保留的partial objects的数量。

static inline int alloc_kmem_cache_cpus(struct kmem_cache *s)
{
    BUILD_BUG_ON(PERCPU_DYNAMIC_EARLY_SIZE <
            KMALLOC_SHIFT_HIGH * sizeof(struct kmem_cache_cpu));

    /*
     * Must align to double word boundary for the double cmpxchg
     * instructions to work; see __pcpu_double_call_return_bool().
     */
    s->cpu_slab = __alloc_percpu(sizeof(struct kmem_cache_cpu),
                     2 * sizeof(void *));

    if (!s->cpu_slab)
        return 0;
    /* 初始化cpu_slab的tid为CPU ID */
    init_kmem_cache_cpus(s);

    return 1;
}

至此,slab系统的两个cache—— kmem_cache_nodekmem_cache 初始化完成,可以用来响应其他 struct kmem_cache_nodestruct kmem_cache 对象的内存分配请求。

3. kmalloc_caches

分配kmalloc slab cache的关键变量是 kmalloc_caches ,定义在mm/slab_common.c中, *struct kmem_cache kmalloc_caches[KMALLOC_SHIFT_HIGH + 1] ,在 create_kmalloc_caches 函数中初始化。

这个函数中,x86架构下 KMALLOC_SHIFT_LOW =3, KMALLOC_SHIFT_HIGH =13, KMALLOC_MIN_SIZE =8,许多修复 size_index 数组的条件语句不满足,此处省略不述:

void __init create_kmalloc_caches(unsigned long flags)
{
    int i;
    ...
    for (i = KMALLOC_SHIFT_LOW; i <= KMALLOC_SHIFT_HIGH; i++) {
        if (!kmalloc_caches[i]) {
            /* create_alloc_cache函数完成实际工作 */
            kmalloc_caches[i] = create_kmalloc_cache(NULL,
                            1 << i, flags);
        }

        /*
         * Caches that are not of the two-to-the-power-of size.
         * These have to be created immediately after the
         * earlier power of two caches
         */
        if (KMALLOC_MIN_SIZE <= 32 && !kmalloc_caches[1] && i == 6)
            kmalloc_caches[1] = create_kmalloc_cache(NULL, 96, flags);

        if (KMALLOC_MIN_SIZE <= 64 && !kmalloc_caches[2] && i == 7)
            kmalloc_caches[2] = create_kmalloc_cache(NULL, 192, flags);
    }
    /*
     现在,kmalloc_caches[]的大小为
     -,96,192,8,16 ... 2^13 */
    /* Kmalloc array is now usable */
    slab_state = UP;
    /* 设置slab的名称 */
    for (i = 0; i <= KMALLOC_SHIFT_HIGH; i++) {
        struct kmem_cache *s = kmalloc_caches[i];
        char *n;

        if (s) {
            n = kasprintf(GFP_NOWAIT, "kmalloc-%d", kmalloc_size(i));

            BUG_ON(!n);
            s->name = n;
        }
    }
    #ifdef CONFIG_ZONE_DMA
    for (i = 0; i <= KMALLOC_SHIFT_HIGH; i++) {
        struct kmem_cache *s = kmalloc_caches[i];

        if (s) {
            int size = kmalloc_size(i);
            char *n = kasprintf(GFP_NOWAIT,
                 "dma-kmalloc-%d", size);

            BUG_ON(!n);
            kmalloc_dma_caches[i] = create_kmalloc_cache(n,
                size, SLAB_CACHE_DMA | flags);
        }
    }
#endif
}

结合此函数中创建 kmalloc_caches 的过程,可以理解mm/slab_common.cstatic s8 size_index[24] 数组的内容。

3.1. create_kmalloc_caches

create_kmalloc_caches 函数的主要工作通过 create_kmalloc_cache 实现,这个函数首先从 kmem_cache 中分配一个 struct kmem_cache 对象,然后调用 create_boot_cache 函数,设置创建的 kmem_cache 对象的参数,包括per-cpu成员变量;并且创建对应的sysfs目录,位于 /sys/kernel/slab/<kmem_cache name>/

struct kmem_cache *__init create_kmalloc_cache(const char *name, size_t size,
                unsigned long flags)
{
    struct kmem_cache *s = kmem_cache_zalloc(kmem_cache, GFP_NOWAIT);

    if (!s)
        panic("Out of memory when creating slab %s\n", name);

    create_boot_cache(s, name, size, flags);
    /* slab_cache是保存系统中所有slab cache的链表 */
    list_add(&s->list, &slab_caches);
    s->refcount = 1;
    return s;
}

可以看到,创建 kmalloc_caches 时,直接通过 kmem_cache_zallockmem_cache slab cache中分配一个 struct kmem_cache 对象使用。

4. 其他slab cache的初始化

除了 kmem_cache_nodekmem_cachekmalloc_caches 三个slab cache,内核中其他的slab cache的创建通过 kmem_cache_create 函数完成:

/*
 * kmem_cache_create - Create a cache.
 * @name: A string which is used in /proc/slabinfo to identify this cache.
 * @size: The size of objects to be created in this cache.
 * @align: The required alignment for the objects.
 * @flags: SLAB flags
 * @ctor: A constructor for the objects.
 *
 * Returns a ptr to the cache on success, NULL on failure.
 * Cannot be called within a interrupt, but can be interrupted.
 * The @ctor is run when new pages are allocated by the cache.
 *
 * The flags are
 *
 * %SLAB_POISON - Poison the slab with a known test pattern (a5a5a5a5)
 * to catch references to uninitialised memory.
 *
 * %SLAB_RED_ZONE - Insert `Red' zones around the allocated memory to check
 * for buffer overruns.
 *
 * %SLAB_HWCACHE_ALIGN - Align the objects in this cache to a hardware
 * cacheline.  This can be beneficial if you're counting cycles as closely
 * as davem.
 */
struct kmem_cache *
kmem_cache_create(const char *name, size_t size, size_t align,
          unsigned long flags, void (*ctor)(void *))
{
    struct kmem_cache *s;
    char *cache_name;
    int err;

    get_online_cpus();
    get_online_mems();

    mutex_lock(&slab_mutex);

    err = kmem_cache_sanity_check(name, size);
    if (err)
        goto out_unlock;
     /*
     * Some allocators will constraint the set of valid flags to a subset
     * of all flags. We expect them to define CACHE_CREATE_MASK in this
     * case, and we'll just provide them with a sanitized version of the
     * passed flags.
     */
    flags &= CACHE_CREATE_MASK;
    /*
     寻找系统中是否有可以复用的slab cache。如果有,增加
     匹配的slab的引用计数,设置slab的参数;如果没有,则
     调用do_kmem_cache_create创建一个slab cache */
    s = __kmem_cache_alias(name, size, align, flags, ctor);
    if (s)
        goto out_unlock;

    cache_name = kstrdup(name, GFP_KERNEL);
    if (!cache_name) {
        err = -ENOMEM;
        goto out_unlock;
    }
    /*
     和create_kmalloc_cache类似,先调用kmem_cache_zalloc
     分配一个kmem_cache对象,然后调用__kmem_cache_create
     执行kmem_cache的初始化 */
    s = do_kmem_cache_create(cache_name, size, size,
                 calculate_alignment(flags, align, size),
                 flags, ctor, NULL, NULL);
    if (IS_ERR(s)) {
        err = PTR_ERR(s);
        kfree(cache_name);
    }

out_unlock:
    mutex_unlock(&slab_mutex);

    put_online_mems();
    put_online_cpus();

    if (err) {
        if (flags & SLAB_PANIC)
            panic("kmem_cache_create: Failed to create slab '%s'. Error %d\n",
                name, err);
        else {
            printk(KERN_WARNING "kmem_cache_create(%s) failed with error %d",
                name, err);
            dump_stack();
        }
        return NULL;
    }
    return s;
}
EXPORT_SYMBOL(kmem_cache_create);

5. 总结

从代码看,每一个slab cache通过一个 struct kmem_cache 对象描述;每个slab cache包含多个slab,每个slab通常为一个page;每个slab cache可以为一种对象分配内存;每个slab cache包含系统中 所有CPU所有内存节点 的属于该对象的slab信息,保存在 struct kmem_cachenode 变量和 cpu_slab 变量;前者以节点为单位统计slab的信息,后者以CPU为单位统计slab的信息。

整个slab系统的建立过程从 kmem_cache_nodekmem_cachekmalloc_caches 三个slab cache的初始化开始。这三个分配器通过 create_boot_cache 函数建立;其他的分配器通过函数 kmem_cache_create 函数创建。

两个函数最终都会调用 __kmem_cache_create 来初始化新创建 struct kmem_cache 对象,区别在于获取 struct kmem_cache 对象的方式。

slab最终通过伙伴系统获取内存页,介绍slab系统之后介绍伙伴系统。

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