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_node 、 kmem_cache 和 kmalloc_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_cache
,mm/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种类的个数
- const char name = "kmem_cache_node"
kmem_cache_open
,mm/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系统,值越小,越倾向于在本节点分配对象
- unsigned long flags
calculate_sizes
,mm/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的数量,这个是最大值
- int inuse = 64
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
- early_kmem_cache_node_alloc / kmem_cache_alloc_node
- alloc_kmem_cache_cpus
- init_kmem_cache_nodes
- kmem_cache_open
- __kmem_cache_create
- create_boot_cache(kmem_cache)
- create_kmalloc_caches -- kmalloc_caches[]
开启 CONFIG_SLUB 时, create_boot_cache
只有创建 kmem_cache_node 、 kmem_cache 和 kmalloc_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_nodes
和 alloc_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_node 和 kmem_cache_node 时,会执行 early_kmem_cache_node_alloc
函数。
这个函数首先通过 new_slab
函数从指定的内存节点通过buddy allocator分配一个新的page给 kmem_cache_node 分配器,然后初始化struct page中和SLUB相关的信息,并且将page保存到 kmem_cache_node 的 node[] 域中。
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_node 和 kmem_cache 初始化完成,可以用来响应其他 struct kmem_cache_node 和 struct 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.c中 static 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/
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_zalloc
从 kmem_cache slab cache中分配一个 struct kmem_cache 对象使用。
4. 其他slab cache的初始化
除了 kmem_cache_node 、 kmem_cache 、 kmalloc_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_cache 的 node 变量和 cpu_slab 变量;前者以节点为单位统计slab的信息,后者以CPU为单位统计slab的信息。
整个slab系统的建立过程从 kmem_cache_node 、 kmem_cache 、 kmalloc_caches 三个slab cache的初始化开始。这三个分配器通过 create_boot_cache
函数建立;其他的分配器通过函数 kmem_cache_create
函数创建。
两个函数最终都会调用 __kmem_cache_create
来初始化新创建 struct kmem_cache 对象,区别在于获取 struct kmem_cache 对象的方式。
slab最终通过伙伴系统获取内存页,介绍slab系统之后介绍伙伴系统。