slab内存分配器初始化有函数kmem_cache_init来完成,本文会详细介绍slab内存分配器的初始化流程。
软件架构
kmem_cache_init
|----->kmem_cache = &kmem_cache_boot;
| 第一个kmem cache实例,静态定义,不过尚需必须要的初始化
|----->kmem_cache_node_init(&init_kmem_cache_node[i]);
| 初始化静态定义的struct kmem_cache_node实例,这里有两个node实例;它们是分别为前两个
| kmem cache实例准备的,第一个kmem cache实例用于为创建其他kmem cache实例分配空间,
| 第二个kmem cache实例用于为创建struct kmem_cache_node实例分配空间,所以前两个kmem cache
| 实例需要静态分配struct kmem_cache_node实例。
|----->create_boot_cache(kmem_cache, "kmem_cache", offsetof(struct kmem_cache, node) +
| nr_node_ids * sizeof(struct kmem_cache_node *),SLAB_HWCACHE_ALIGN);
| 完成第一个kmem cache实例kmem_cache的初始化,第三个参数很重要,是该kmem cache实例所维护
| 的object的size。
| |----->s->size = s->object_size = size;
| | 设置object size
| |----->s->align = calculate_alignment(flags, ARCH_KMALLOC_MINALIGN, size);
| | 计算按多少字节对齐,详见重要函数分析。
| |----->err = __kmem_cache_create(s, flags);
| | |----->set_objfreelist_slab_cache(cachep, size, flags)
| | | |----->left = calculate_slab_order(cachep, size, flags | CFLGS_OBJFREELIST_SLAB);
| | | | 计算该kmem cache实例对应的slab order,原则是slab尽量小,浪费尽量少,详见重要函数分析。
| | | | 这里没有设置CFLGS_OFF_SLAB,是因为还没有创建freelist index的kmem cache实例,freelist
| | | | 只能存放在slab内。
| | |----->cachep->freelist_size = cachep->num * sizeof(freelist_idx_t);
| | | freelist index占用空间大小
| | |----->err = setup_cpu_cache(cachep, gfp);
| | | |----->cachep->cpu_cache = alloc_kmem_cache_cpus(cachep, 1, 1);
| | | | 分配array_cache实例
| | | |----->set_up_node(kmem_cache, CACHE_CACHE);
| | | | 设置node
| | | |----->cpu_cache_get(cachep)->avail = 0;
| | | | 由于还没有申请slab,不存在可用的object,所以可用的object为0
|----->list_add(&kmem_cache->list, &slab_caches);
| kmem cache实例加入slab_caches链表
|----->slab_state = PARTIAL;
|----->kmalloc_caches[INDEX_NODE] = create_kmalloc_cache("kmalloc-node",
| kmalloc_size(INDEX_NODE), ARCH_KMALLOC_FLAGS);
| 创建第二个kmem cache实例,从object size可以看出,该实例用于为struct kmem_cache_node分配空间。
| 指针数组kmalloc_caches是kmalloc用的kmem cache实例数组,该数组十分重要,详见重要变量分析。
| |----->struct kmem_cache *s = kmem_cache_zalloc(kmem_cache, GFP_NOWAIT);
| | 从kmem cache kmem_cache中分配一个object
| | |----->return kmem_cache_alloc(k, flags | __GFP_ZERO);
| | | |----->void *ret = slab_alloc(cachep, flags, _RET_IP_);
| | | | |----->objp = __do_cache_alloc(cachep, flags);
| | | | | 分配一个object,该函数比较复杂,详情请参考重要函数分析
| |----->create_boot_cache(s, name, size, flags);
| |----->list_add(&s->list, &slab_caches);
|----->slab_state = PARTIAL_NODE;
|----->setup_kmalloc_cache_index_table();
|----->init_list(kmem_cache, &init_kmem_cache_node[CACHE_CACHE + nid], nid);
| |----->ptr = kmalloc_node(sizeof(struct kmem_cache_node), GFP_NOWAIT, nodeid);
| | 分配struct kmem_cache_node
| |----->memcpy(ptr, list, sizeof(struct kmem_cache_node));
| | 将静态定义的struct kmem_cache_node拷贝到新分配的struct kmem_cache_node中
| |----->cachep->node[nodeid] = ptr;
|----->init_list(kmalloc_caches[INDEX_NODE], &init_kmem_cache_node[SIZE_NODE + nid], nid);
|----->create_kmalloc_caches(ARCH_KMALLOC_FLAGS);
| 创建kmalloc所需要的kmem cache实例
重要函数分析
calculate_alignment
unsigned long calculate_alignment(unsigned long flags,
unsigned long align, unsigned long size)
{
/*
* If the user wants hardware cache aligned objects then follow that
* suggestion if the object is sufficiently large.
*
* The hardware cache alignment cannot override the specified
* alignment though. If that is greater then use it.
*/
if (flags & SLAB_HWCACHE_ALIGN) {
unsigned long ralign = cache_line_size();
while (size <= ralign / 2)
ralign /= 2;
align = max(align, ralign);
}
if (align < ARCH_SLAB_MINALIGN)
align = ARCH_SLAB_MINALIGN;
return ALIGN(align, sizeof(void *));
}
参数解析:
参数 | 说明 |
---|---|
flags | flag标记 |
align | 最小对齐要求,字节 |
size | object size |
功能解析
如果定义了SLAB_HWCACHE_ALIGN,说明要求按硬件的cache line对齐。这里并不是说每个object都按硬件cache line对齐,也可能是多个object按一个硬件cache line对齐。如果object size不小于硬件cache line size的一半,则object size按硬件cache line对齐;否则可能多个object按一个cache line对齐。
calculate_slab_order
static size_t calculate_slab_order(struct kmem_cache *cachep,
size_t size, unsigned long flags)
{
size_t left_over = 0;
int gfporder;
for (gfporder = 0; gfporder <= KMALLOC_MAX_ORDER; gfporder++) {
unsigned int num;
size_t remainder;
num = cache_estimate(gfporder, size, flags, &remainder);
if (!num)
continue;
/* Can't handle number of objects more than SLAB_OBJ_MAX_NUM */
if (num > SLAB_OBJ_MAX_NUM)
break;
if (flags & CFLGS_OFF_SLAB) {
struct kmem_cache *freelist_cache;
size_t freelist_size;
freelist_size = num * sizeof(freelist_idx_t);
freelist_cache = kmalloc_slab(freelist_size, 0u);
if (!freelist_cache)
continue;
/*
* Needed to avoid possible looping condition
* in cache_grow_begin()
*/
if (OFF_SLAB(freelist_cache))
continue;
/* check if off slab has enough benefit */
if (freelist_cache->size > cachep->size / 2)
continue;
}
/* Found something acceptable - save it away */
cachep->num = num;
cachep->gfporder = gfporder;
left_over = remainder;
/*
* A VFS-reclaimable slab tends to have most allocations
* as GFP_NOFS and we really don't want to have to be allocating
* higher-order pages when we are unable to shrink dcache.
*/
if (flags & SLAB_RECLAIM_ACCOUNT)
break;
/*
* Large number of objects is good, but very large slabs are
* currently bad for the gfp()s.
*/
if (gfporder >= slab_max_order)
break;
/*
* Acceptable internal fragmentation?
*/
if (left_over * 8 <= (PAGE_SIZE << gfporder))
break;
}
return left_over;
}
参数解析:
参数 | 说明 |
---|---|
cachep | kmem cache实例 |
size | object实例 |
flags | flag标记 |
功能解析:
从order=0开始选择满足要求的order值,如果order=0不满足,则继续选择order=1,直到order=KMALLOC_MAX_ORDER。某个order值是否满足要求,判断步骤如下:
- 计算当前order对应的slab可以拆分成的object的数量num,以及剩余的空间left_over。这里需要注意的是,如果每一个object的freelist index存放在slab内部的话,需要算上这部分的空间消耗。
- 如果object的freelist indes存放在slab外部的话,需要查找到一个合适的kmem cache为freelist index分配空间。
- 更新cachep->num,cachep->gfporder = gfporder,left_over = remainder。之所以先更新这些信息,再判断order是否满足,是为了保证在所有order都不满足的情况下,默认使用最后一个order。
- 判断order是否合适,如果剩余空间left_over不超过该order对应空间的八分之一,则认为满足条件。如果不满足条件,继续判断下一个order。
- 将剩余空间order返回。
那么如何决定freelist是放在slab内部还是放在slab外部呢?
首先,这个决定不是calculate_slab_order能做的。然而既然在该函数中提到了这个话题,我们就就地解决这个疑问好了!
freelist index选择放在slab内部还是外部的原则很简单,也很质朴,那就是怎么节省内存怎么来!要搞清楚怎么存放节省内存,首先需要搞清楚freelist index在slab内部和slab外部是如何存放的。
freelist存放在slab内部时,会放在slab的最后。
freelist存放在slab外部时,从别的kmem cache实例中为freelist分配空间。说到这里可以看出来,前面所说的怎么节省内存怎么来并不完全正确,因为freelist存放在外部是有条件的,那就是有可用的kmem cache实例可以为freelist分配空间。
首先调用set_objfreelist_slab_cache尝试把freelist放在slab内部,如果一个object不足以存放freelist index,则意味着freelist index不太合适,应该选择其他合适的kmem cache实例分配object,来存放freelist index。
如果上一步没有成功,则调用set_off_slab_cache尝试把freelist index存放在slab外部。set_off_slab_cache首先会查找合适的kmem cache实例,如果找不到,直接歇菜,freelist index无法存放在slab外部;如果找到了,判断当前kmem cache实例的剩余空间是否足以存放freelist index,如果空间不够,则将freelist存放在slab外面;如果空间足够,则倾向于将freelist存放在剩余空间。
如果上一步没有成功,则会调用set_on_slab_cache将freelist存放在slab内部,这一步跟第一步的区别在于,这一步不会判断freelist index是否超过一个object的大小。
kmalloc_index
static __always_inline int kmalloc_index(size_t size)
{
if (!size)
return 0;
if (size <= KMALLOC_MIN_SIZE)
return KMALLOC_SHIFT_LOW;
if (KMALLOC_MIN_SIZE <= 32 && size > 64 && size <= 96)
return 1;
if (KMALLOC_MIN_SIZE <= 64 && size > 128 && size <= 192)
return 2;
if (size <= 8) return 3;
if (size <= 16) return 4;
if (size <= 32) return 5;
if (size <= 64) return 6;
if (size <= 128) return 7;
if (size <= 256) return 8;
if (size <= 512) return 9;
if (size <= 1024) return 10;
if (size <= 2 * 1024) return 11;
if (size <= 4 * 1024) return 12;
if (size <= 8 * 1024) return 13;
if (size <= 16 * 1024) return 14;
if (size <= 32 * 1024) return 15;
if (size <= 64 * 1024) return 16;
if (size <= 128 * 1024) return 17;
if (size <= 256 * 1024) return 18;
if (size <= 512 * 1024) return 19;
if (size <= 1024 * 1024) return 20;
if (size <= 2 * 1024 * 1024) return 21;
if (size <= 4 * 1024 * 1024) return 22;
if (size <= 8 * 1024 * 1024) return 23;
if (size <= 16 * 1024 * 1024) return 24;
if (size <= 32 * 1024 * 1024) return 25;
if (size <= 64 * 1024 * 1024) return 26;
BUG();
/* Will never be reached. Needed because the compiler may complain */
return -1;
}
参数解析
参数 | 说明 |
---|---|
size | 需要分配的空间的大小 |
功能分析
该函数用于根据需要分配的内存的size来选择kmalloc_caches数组的下标,kmalloc_caches详情请参考重要变量分析:kmalloc_caches。
KMALLOC_MIN_SIZE是一个非常重要的参数,对于ARM64来说,它是128B,即L1 cache line的长度。为了保证object按L1 cache line对齐,所有1-128B的size所选择的下标都是7,即通过kmem cache实例kmalloc-128分配size为128B的object。
- 如果size为 0,则索引为0。
- 如果size为 1 - 128,则索引为7。
- 如果size为 129 - 256,则索引为8。
依此类推。
该函数很有意思的一点是,当KMALLOC_SHIFT_LOW不超过32B,并且申请内存在65-96范围内时,会选择kmem cache实例kmalloc-96来分配空间,而该kmem cache实例所维护的object size是96B。我们假设L1 cache line是32B,即KMALLOC_MIN_SIZE是32B,如果分配的内存size在65-96之间,则实际分配的object size是96B,96B也是L1 cache line对齐的,这样既节省了空间,又不违背cache对齐的要求。
同样,当KMALLOC_MIN_SIZE不超过64B,并且申请的内存在129-192B之间时,会选择kmalloc-192这个kmem cache实例来分配空间也是一样道理。
为什么单单针对65-96和129-192这两段memory size做了优化,而没有对其他的memory size做这种优化呢?这个问题我无法给出明确的回答,不过我自己猜测有两点可能性:
- 通过kmalloc分配的memory size很可能绝大多数不超过256B,所以针对65-96和129-192这两段memory size做优化作用明显。
- 该优化本质上在不违背cache line对齐的前提下,支持更多的object size,这样避免了浪费,但如果支持的object size过多的话,反而会导致内存浪费,比如对于某个size的object,可能只需要一个object,然而slab分配器从buddy sysem申请的时候确是一次申请多个object,这就会出现内存的浪费,支持的object size越多,这种浪费就越普遍也越严重。
create_kmalloc_caches
void __init create_kmalloc_caches(unsigned long flags)
{
int i;
for (i = KMALLOC_SHIFT_LOW; i <= KMALLOC_SHIFT_HIGH; i++) {
if (!kmalloc_caches[i])
new_kmalloc_cache(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)
new_kmalloc_cache(1, flags);
if (KMALLOC_MIN_SIZE <= 64 && !kmalloc_caches[2] && i == 7)
new_kmalloc_cache(2, flags);
}
/* Kmalloc array is now usable */
slab_state = UP;
#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
}
参数解析
参数 | 说明 |
---|---|
flags | flags标记 |
功能分析
该函数的目的是创建用于kmalloc的kmem cache实例,这里并不一定会创建kmalloc_info中定义的所有的kmem cache实例,其index从KMALLOC_SHIFT_LOW到KMALLOC_SHIFT_HIGH,起始下标是为了cache line对齐,最大的下标受限于buddy system所支持的最大的order,同时也会判断是否可以创建kmalloc_info[1]和kmalloc_info[2]对应的kmem cache实例,关于这两个kmem cache实例,请参考重要变量分析。
如果定义了CONFIG_ZONE_DMA,即定义了DMA内存域,则会创建用于DMA内存域内存分配的kmem cache实例,创建的kmem cache实例地址存放在指针数组kmalloc_dma_caches中。
__do_cache_alloc
__do_cache_alloc直接调用____cache_alloc,所以我们主要分析____cache_alloc
static inline void *____cache_alloc(struct kmem_cache *cachep, gfp_t flags)
{
void *objp;
struct array_cache *ac;
check_irq_off();
ac = cpu_cache_get(cachep);
if (likely(ac->avail)) {
ac->touched = 1;
objp = ac->entry[--ac->avail];
STATS_INC_ALLOCHIT(cachep);
goto out;
}
STATS_INC_ALLOCMISS(cachep);
objp = cache_alloc_refill(cachep, flags);
/*
* the 'ac' may be updated by cache_alloc_refill(),
* and kmemleak_erase() requires its correct value.
*/
ac = cpu_cache_get(cachep);
out:
/*
* To avoid a false negative, if an object that is in one of the
* per-CPU caches is leaked, we need to make sure kmemleak doesn't
* treat the array pointers as a reference to the object.
*/
if (objp)
kmemleak_erase(&ac->entry[ac->avail]);
return objp;
}
参数解析
参数 | 说明 |
---|---|
cachep | 分配object的kmem cache实例 |
flags | flags标记 |
功能分析
该函数从cache中分配object,首先到cachep->cpu_cache查看是否有可用的object,如果有则直接将object的地址返回,并将可用的object计数减一;如果没有则需要从buddy system分配slab,并拆分成object填充到cachep->cpu_cache中,这通过调用cache_alloc_refill函数实现。
重要变量分析
kmalloc_caches
详细定义
struct kmem_cache *kmalloc_caches[KMALLOC_SHIFT_HIGH + 1];
功能解析
kmalloc分配内存时,会根据所需要分配的空间的大小,从kmalloc_caches数组中选择一个kmem cache实例来分配object,这个很好理解,因为不同的kmem cache实例维护不同size的object。那么kmalloc_caches数组的下标必然与分配内存的size有某种联系,这种联系就体现在kmalloc_info数组中,定义如下:
static struct {
const char *name;
unsigned long size;
} const kmalloc_info[] __initconst = {
{NULL, 0}, {"kmalloc-96", 96},
{"kmalloc-192", 192}, {"kmalloc-8", 8},
{"kmalloc-16", 16}, {"kmalloc-32", 32},
{"kmalloc-64", 64}, {"kmalloc-128", 128},
{"kmalloc-256", 256}, {"kmalloc-512", 512},
{"kmalloc-1024", 1024}, {"kmalloc-2048", 2048},
{"kmalloc-4096", 4096}, {"kmalloc-8192", 8192},
{"kmalloc-16384", 16384}, {"kmalloc-32768", 32768},
{"kmalloc-65536", 65536}, {"kmalloc-131072", 131072},
{"kmalloc-262144", 262144}, {"kmalloc-524288", 524288},
{"kmalloc-1048576", 1048576}, {"kmalloc-2097152", 2097152},
{"kmalloc-4194304", 4194304}, {"kmalloc-8388608", 8388608},
{"kmalloc-16777216", 16777216}, {"kmalloc-33554432", 33554432},
{"kmalloc-67108864", 67108864}
};
索引0:代表kmem cache实例维护的object size为0。
索引1:代表kmem cache实例维护的object size为96。
索引2:代表kmem cache实例维护的object size为192。
索引3:代表kmem cache实例维护的object size为8。
以此类推。
这里要注意,kmalloc_info中定义的kmem cache实例并不一定全部会被创建,详情请参考函数create_kmalloc_caches,这个函数在重要函数分析中有介绍。