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 函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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 类型的成员变量。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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的内存页,并且初始化其中的对象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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 函数时详细说明。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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 信息:

1
2
3
4
5
6
7
8
9
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 的数量。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
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 = 3KMALLOC_SHIFT_HIGH = 13KMALLOC_MIN_SIZE = 8,许多修复 size_index 数组的条件语句不满足,此处省略不述:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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>/

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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 函数完成:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/*
 * 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 系统之后介绍伙伴系统。