首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

Linux SLUB 分配器详解(3)SLUB 分配器的实现

Linux SLUB 分配器详解(3)SLUB 分配器的实现

为了保证内核其它模块能够无缝迁移到 SLUB 分配器,SLUB保留了原有 SLAB 分配器所有的接口 API 函数。表 4 列出主要的 API 函数:
表 4. SLUB API 函数函数描述kmem_cache_create创建新的缓冲区。kmem_cache_destroy销毁缓冲区。因为存在重用缓冲区的情况,只有当 kmem_cache 结构的 refcount 字段为 0时才真正销毁。kmem_cache_alloc从处理器本地的活动 slab 中分配对象。kmem_cache_alloc_node如果指定的 NUMA 节点与本处理器所在节点不一致,则先从指定节点上获取 slab,替换处理器活动 slab,然后分配对象。kmem_cache_free释放对象。如果对象属于某 Partial slab 且释放操作使这个 slab转变成Empty 状态,则释放该 slab。kmem_ptr_validate检查给定对象的指针是否合法。kmem_cache_size返回对象实际大小。kmem_cache_shrink检查各个节点的 Partial 队列,回收实际处于 Empty 状态的 slab,并将剩余的 slab 按已分配对象的数目排序。kmalloc从通用缓冲区中分配一个对象。kmalloc_node从通用缓冲区中分配一个属于指定 NUMA 节点的对象。kfree释放一个通用对象。ksize返回分配给对象的内存大小(可能大于对象的实际大小)
下面介绍 kmem_cache_alloc,kmem_cache_alloc_node 和 kmem_cache_free 三个函数的实现细节。kmem_cache_alloc 和 kmem_cache_alloc_node 函数都是直接调用 slab_alloc 函数,只是 kmem_cache_alloc 传入的 node 参数为 -1;kmem_cache_free 则调用 slab_free 函数。
清单 1. slab_alloc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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;

    local_irq_save(flags);
    c = get_cpu_slab(s, smp_processor_id());                         (a)
    if (unlikely(!c->freelist || !node_match(c, node)))
        object = __slab_alloc(s, gfpflags, node, addr, c);         (b)
    else {
        object = c->freelist;                                            (c)
        c->freelist = object[c->offset];
        stat(c, ALLOC_FASTPATH);
    }
    local_irq_restore(flags);

    if (unlikely((gfpflags & __GFP_ZERO) && object))
        memset(object, 0, c->objsize);

    return object;                                                         (d)
}




  • 获取本处理器的 kmem_cache_cpu 数据结构。
  • 假如当前活动 slab 没有空闲对象,或本处理器所在节点与指定节点不一致,则调用 __slab_alloc 函数。
  • 获得第一个空闲对象的指针,然后更新指针使其指向下一个空闲对象。
  • 返回对象地址。
清单 2. __slab_alloc
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
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;

    if (!c->page)                                                          (a)
        goto new_slab;

    slab_lock(c->page);
    if (unlikely(!node_match(c, node)))                                (b)
        goto another_slab;

    stat(c, ALLOC_REFILL);

load_freelist:
    object = c->page->freelist;
    if (unlikely(!object))                                                (c)
        goto another_slab;
    if (unlikely(SlabDebug(c->page)))
        goto debug;

    c->freelist = object[c->offset];                                    (d)
    c->page->inuse = s->objects;
    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);                                                (e)

new_slab:
    new = get_partial(s, gfpflags, node);                              (f)
    if (new) {
        c->page = new;
        stat(c, ALLOC_FROM_PARTIAL);
        goto load_freelist;
    }

    if (gfpflags & __GFP_WAIT)                                           (g)
        local_irq_enable();

    new = new_slab(s, gfpflags, node);                                 (h)

    if (gfpflags & __GFP_WAIT)
        local_irq_disable();

    if (new) {
        c = get_cpu_slab(s, smp_processor_id());
        stat(c, ALLOC_SLAB);
        if (c->page)
            flush_slab(s, c);
        slab_lock(new);
        SetSlabFrozen(new);
        c->page = new;
        goto load_freelist;
    }
    if (!(gfpflags & __GFP_NORETRY) &&
                (s->flags & __PAGE_ALLOC_FALLBACK)) {
        if (gfpflags & __GFP_WAIT)
            local_irq_enable();
        object = kmalloc_large(s->objsize, gfpflags);                (i)
        if (gfpflags & __GFP_WAIT)
            local_irq_disable();
        return object;
    }
    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;
}




  • 如果没有本地活动 slab,转到 (f) 步骤获取 slab 。
  • 如果本处理器所在节点与指定节点不一致,转到 (e) 步骤。
  • 检查处理器活动 slab 没有空闲对象,转到 (e) 步骤。
  • 此时活动 slab 尚有空闲对象,将 slab 的空闲对象队列指针复制到 kmem_cache_cpu 结构的 freelist 字段,把 slab 的空闲对象队列指针设置为空,从此以后只从 kmem_cache_cpu 结构的 freelist 字段获得空闲对象队列信息。
  • 取消当前活动 slab,将其加入到所在 NUMA 节点的 Partial 队列中。
  • 优先从指定 NUMA 节点上获得一个 Partial slab。
  • 加入 gfpflags 标志置有 __GFP_WAIT,开启中断,故后续创建 slab 操作可以睡眠。
  • 创建一个 slab,并初始化所有对象。
  • 如果内存不足,无法创建 slab,调用 kmalloc_large(实际调用物理页框分配器)分配对象。
清单 3. slab_free
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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);
    c = get_cpu_slab(s, smp_processor_id());
    debug_check_no_locks_freed(object, c->objsize);
    if (likely(page == c->page && c->node >= 0)) {                   (a)
        object[c->offset] = c->freelist;
        c->freelist = object;
        stat(c, FREE_FASTPATH);
    } else
        __slab_free(s, page, x, addr, c->offset);                     (b)

    local_irq_restore(flags);
}




  • 如果对象属于处理器当前活动的 slab,或处理器所在 NUMA 节点号不为 -1(调试使用的值),将对象放回空闲对象队列。
  • 否则调用 __slab_free 函数。
清单 4. __slab_free
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
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;

    c = get_cpu_slab(s, raw_smp_processor_id());
    stat(c, FREE_SLOWPATH);
    slab_lock(page);

    if (unlikely(SlabDebug(page)))
        goto debug;

checks_ok:
    prior = object[offset] = page->freelist;                          (a)
    page->freelist = object;
    page->inuse--;

    if (unlikely(SlabFrozen(page))) {
        stat(c, FREE_FROZEN);
        goto out_unlock;
    }

    if (unlikely(!page->inuse))                                          (b)
        goto slab_empty;

    if (unlikely(!prior)) {                                               (c)
        add_partial(get_node(s, page_to_nid(page)), page, 1);
        stat(c, FREE_ADD_PARTIAL);
    }

out_unlock:
    slab_unlock(page);
    return;

slab_empty:
    if (prior) {                                                            (d)
        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 并不是某个活动 slab。保存空闲对象队列的指针,将对象放回此队列,最后把已分配对象数目减一。
  • 如果已分配对象数为 0,说明 slab 处于 Empty 状态,转到 (d) 步骤。
  • 如果原空闲对象队列的指针为空,说明 slab 原来的状态为 Full,那么现在的状态应该是 Partial,将该 slab 加到所在节点的 Partial 队列中。
  • 如果 slab 状态转为 Empty,且先前位于节点的 Partial 队列中,则将其剔出并释放所占内存空间。
返回列表