为了保证内核其它模块能够无缝迁移到 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_alloc1
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_alloc1
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_free1
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_free1
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 队列中,则将其剔出并释放所占内存空间。
|