1 概述
本文基于slab cache原理基础上,从内核源码角度探讨kmalloc的原理,包括kmalloc内存池初始化、kmalloc内存申请和释放过程,关于slab cache原理,参考笔者之前总结过的文章
null
https://blog.youkuaiyun.com/tang_vincent/article/details/155630942?spm=1001.2014.3001.5502
2 kmalloc介绍
kmalloc是基于slab缓存池创建的内存池,其在内核物理内存管理关系如下图。它是为解决内核通用小内存的频繁分配释放需求而专门创建的内存池,内核在启动阶段建立了不同大小的slab cache缓存池,根据用户申请大小不同,使用不同的内存池进行分配。

3 kmalloc在内核的初始化
3.1 kmalloc的组织形式
内核为kmalloc内存池声明了全局的内存组织信息kmalloc_info
mm\slab_common.c
#define INIT_KMALLOC_INFO(__size, __short_size) \
{ \
.name[KMALLOC_NORMAL] = "kmalloc-" #__short_size, \
.name[KMALLOC_RECLAIM] = "kmalloc-rcl-" #__short_size, \
KMALLOC_CGROUP_NAME(__short_size) \
KMALLOC_DMA_NAME(__short_size) \
.size = __size, \
}
/*
* kmalloc_info[] is to make slub_debug=,kmalloc-xx option work at boot time.
* kmalloc_index() supports up to 2^25=32MB, so the final entry of the table is
* kmalloc-32M.
*/
const struct kmalloc_info_struct kmalloc_info[] __initconst = {
INIT_KMALLOC_INFO(0, 0),
INIT_KMALLOC_INFO(96, 96),
INIT_KMALLOC_INFO(192, 192),
INIT_KMALLOC_INFO(8, 8),
INIT_KMALLOC_INFO(16, 16),
INIT_KMALLOC_INFO(32, 32),
INIT_KMALLOC_INFO(64, 64),
INIT_KMALLOC_INFO(128, 128),
INIT_KMALLOC_INFO(256, 256),
INIT_KMALLOC_INFO(512, 512),
INIT_KMALLOC_INFO(1024, 1k),
INIT_KMALLOC_INFO(2048, 2k),
INIT_KMALLOC_INFO(4096, 4k),
INIT_KMALLOC_INFO(8192, 8k),
INIT_KMALLOC_INFO(16384, 16k),
INIT_KMALLOC_INFO(32768, 32k),
INIT_KMALLOC_INFO(65536, 64k),
INIT_KMALLOC_INFO(131072, 128k),
INIT_KMALLOC_INFO(262144, 256k),
INIT_KMALLOC_INFO(524288, 512k),
INIT_KMALLOC_INFO(1048576, 1M),
INIT_KMALLOC_INFO(2097152, 2M),
INIT_KMALLOC_INFO(4194304, 4M),
INIT_KMALLOC_INFO(8388608, 8M),
INIT_KMALLOC_INFO(16777216, 16M),
INIT_KMALLOC_INFO(33554432, 32M)
};
mm\slab.h
extern const struct kmalloc_info_struct {
const char *name[NR_KMALLOC_TYPES];
unsigned int size;
} kmalloc_info[];
kmalloc_info从index=3开始,每一个 index 都表示其对应的 kmalloc_info[index] 指向的通用 slab cache 尺寸,即大小为2^index字节,例如kmalloc_info[3] 对应的通用 slab cache 中所管理的内存块尺寸为 8 字节。这里的 index = 1 和 index = 2 是个例外,内核单独支持了 kmalloc-96 和 kmalloc-192 这两个通用 slab cache。它们分别管理了 96 字节大小和 192 字节大小的通用内存块。
内核通过一个全局的size_index素组来根据当前申请的大小选取合适尺寸的内存块提供申请
mm\slab_common.c
static u8 size_index[24] __ro_after_init = {
3, /* 8 */
4, /* 16 */
5, /* 24 */
5, /* 32 */
6, /* 40 */
6, /* 48 */
6, /* 56 */
6, /* 64 */
1, /* 72 */
1, /* 80 */
1, /* 88 */
1, /* 96 */
7, /* 104 */
7, /* 112 */
7, /* 120 */
7, /* 128 */
2, /* 136 */
2, /* 144 */
2, /* 152 */
2, /* 160 */
2, /* 168 */
2, /* 176 */
2, /* 184 */
2 /* 192 */
};
例如,当用户申请内存小于8为7时,此时通过计算(大小-1/8,后续提到的size_index_elem即是根据大小计算偏移)得偏移是0,size_index[0] 存储的信息表示,如果内核申请的内存块低于 8 字节时,那么 kmalloc 将会到 kmalloc_info[3] 所指定的通用 slab cache —— kmalloc-8 中分配一个 8 字节大小的内存块,其他大小同理。
上述所有的内存信息确定后,调用slab cache缓存池创建接口,所有的缓存对象被维护在一个全局变量kmalloc_caches中
struct kmem_cache *
kmalloc_caches[NR_KMALLOC_TYPES][KMALLOC_SHIFT_HIGH + 1] __ro_after_init =
{ /* initialization for https://bugs.llvm.org/show_bug.cgi?id=42570 */ };
EXPORT_SYMBOL(kmalloc_caches);
3.2 kmalloc内存池初始化
kmalloc的初始化在内核启动过程调用关系如下

setup_kmalloc_cache_index_table主要初始化了size_index
void __init setup_kmalloc_cache_index_table(void)
{
unsigned int i;
BUILD_BUG_ON(KMALLOC_MIN_SIZE > 256 ||
(KMALLOC_MIN_SIZE & (KMALLOC_MIN_SIZE - 1)));
for (i = 8; i < KMALLOC_MIN_SIZE; i += 8) {
unsigned int elem = size_index_elem(i);
if (elem >= ARRAY_SIZE(size_index))
break;
size_index[elem] = KMALLOC_SHIFT_LOW;
}
if (KMALLOC_MIN_SIZE >= 64) {
/*
* The 96 byte size cache is not used if the alignment
* is 64 byte.
*/
for (i = 64 + 8; i <= 96; i += 8)
size_index[size_index_elem(i)] = 7;
}
if (KMALLOC_MIN_SIZE >= 128) {
/*
* The 192 byte sized cache is not used if the alignment
* is 128 byte. Redirect kmalloc to use the 256 byte cache
* instead.
*/
for (i = 128 + 8; i <= 192; i += 8)
size_index[size_index_elem(i)] = 8;
}
}
不同的CPU架构定义出来的size_index结果可能不一样,例如arm64中由于定了ARCH_DMA_MINALIGN,此时KMALLOC_MIN_SIZE 的值变成128,KMALLOC_SHIFT_LOW为7,即kmalloc提供最小的内存申请大小是128。
#if defined(ARCH_DMA_MINALIGN) && ARCH_DMA_MINALIGN > 8
#define ARCH_KMALLOC_MINALIGN ARCH_DMA_MINALIGN
#define KMALLOC_MIN_SIZE ARCH_DMA_MINALIGN
#define KMALLOC_SHIFT_LOW ilog2(ARCH_DMA_MINALIGN)
#else
#define ARCH_KMALLOC_MINALIGN __alignof__(unsigned long long)
#endif
arch\arm64\include\asm\cache.h
#define ARCH_DMA_MINALIGN (128)
kmalloc内存池内存可能来自不同区域例如DMA区、NORMAL或者可回收区域,根据区域不同malloc在内核定义了内存来源类型:
enum kmalloc_cache_type {
KMALLOC_NORMAL = 0,
#ifndef CONFIG_ZONE_DMA
KMALLOC_DMA = KMALLOC_NORMAL,
#endif
#ifndef CONFIG_MEMCG_KMEM
KMALLOC_CGROUP = KMALLOC_NORMAL,
#else
KMALLOC_CGROUP,
#endif
KMALLOC_RECLAIM,
#ifdef CONFIG_ZONE_DMA
KMALLOC_DMA,
#endif
NR_KMALLOC_TYPES
};
内核会为每个区域申请所有kmalloc_info定义的大小,总结如下关系图:

create_kmalloc_caches函数的主要任务就是创建和初始化用来管理所有kmalloc缓存池slab cache对象的kmalloc_caches这个二维数组
void __init create_kmalloc_caches(slab_flags_t flags)
{
int i;
enum kmalloc_cache_type type;
/*
* Including KMALLOC_CGROUP if CONFIG_MEMCG_KMEM defined
*/
// 初始化二维数组 kmalloc_caches,为每一个 kmalloc_cache_type 类型创建内存块尺寸从 KMALLOC_SHIFT_LOW 到 KMALLOC_SHIFT_HIGH 大小的 kmalloc 内存池
for (type = KMALLOC_NORMAL; type <= KMALLOC_RECLAIM; type++) {
// 这里会从 128B(2^KMALLOC_SHIFT_LOW 7) 尺寸的内存池开始创建,一直到创建完 8K(2^KMALLOC_SHIFT_HIGH 13) 尺寸的内存池
for (i = KMALLOC_SHIFT_LOW; i <= KMALLOC_SHIFT_HIGH; i++) {
if (!kmalloc_caches[type][i])
new_kmalloc_cache(i, type, flags);// 创建对应尺寸的 kmalloc 内存池,其中内存块大小为 2^i 字节
/*
* 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
*/
//arm64下KMALLOC_MIN_SIZE是128.这里在arm64下实际不会创建
// 创建 kmalloc-96 内存池管理 96B 尺寸的内存块
// 专门特意创建一个 96字节尺寸的内存池的目的是为了,应对 64B 到 128B 之间的内存分配需求,要不然超过 64B 就分配一个 128B 的内存块有点浪费
if (KMALLOC_MIN_SIZE <= 32 && i == 6 &&
!kmalloc_caches[type][1])
new_kmalloc_cache(1, type, flags);
// 创建 kmalloc-192 内存池管理 192B 尺寸的内存块
// 这里专门创建一个 192字节尺寸的内存池.是为了分配 128B 到 192B 之间的内存分配需求
// 要不然超过 128B 直接分配一个 256B 的内存块太浪费了
if (KMALLOC_MIN_SIZE <= 64 && i == 7 &&
!kmalloc_caches[type][2])
new_kmalloc_cache(2, type, flags);
}
}
// 当 kmalloc 体系全部创建完毕之后,slab 体系的状态就变为 up 状态了
/* Kmalloc array is now usable */
slab_state = UP;
#ifdef CONFIG_ZONE_DMA
// 如果配置了 DMA 内存区域,则需要为该区域也创建对应尺寸的内存池
for (i = 0; i <= KMALLOC_SHIFT_HIGH; i++) {
struct kmem_cache *s = kmalloc_caches[KMALLOC_NORMAL][i];
if (s) {
kmalloc_caches[KMALLOC_DMA][i] = create_kmalloc_cache(
kmalloc_info[i].name[KMALLOC_DMA],
kmalloc_info[i].size,
SLAB_CACHE_DMA | flags, 0,
kmalloc_info[i].size);
}
}
#endif
}
#endif /* !CONFIG_SLOB */
函数从KMALLOC_NORMAL到KMALLOC_RECLAIM循环每个区域,为每个区域从KMALLOC_SHIFT_LOW(前面得知为7)到KMALLOC_SHIFT_HIGH(13)创建对应大小区域的slab cache,函数最后还检查了CONFIG_ZONE_DMA,同样操作创建slab cache对象到kmalloc_caches中。
创建slab cache对象调用的是slab cache接口,最终会根据偏移与当前内存区域放置到kmalloc_caches中。
static void __init
new_kmalloc_cache(int idx, enum kmalloc_cache_type type, slab_flags_t flags)
{
//slab_flags_t flags 指定创建 slab cache 时的标志位,这里主要用来指定 slab cache 中的内存来源于哪个内存区域。
if (type == KMALLOC_RECLAIM) {
flags |= SLAB_RECLAIM_ACCOUNT;
} else if (IS_ENABLED(CONFIG_MEMCG_KMEM) && (type == KMALLOC_CGROUP)) {
if (cgroup_memory_nokmem) {
kmalloc_caches[type][idx] = kmalloc_caches[KMALLOC_NORMAL][idx];
return;
}
flags |= SLAB_ACCOUNT;
}
// // 底层调用 __kmem_cache_create 创建 kmalloc_info[idx].size 尺寸的 slab cache
kmalloc_caches[type][idx] = create_kmalloc_cache(
kmalloc_info[idx].name[type],
kmalloc_info[idx].size, flags, 0,
kmalloc_info[idx].size);
/*
* If CONFIG_MEMCG_KMEM is enabled, disable cache merging for
* KMALLOC_NORMAL caches.
*/
if (IS_ENABLED(CONFIG_MEMCG_KMEM) && (type == KMALLOC_NORMAL))
kmalloc_caches[type][idx]->refcount = -1;
}
3.3 内存申请kmalloc调用
kmalloc调用__kmalloc最重要的是根据申请的size,计算出对应的最合适的内存区域找到slab cache对象,根据slab cache对象申请内存。
static __always_inline void *kmalloc(size_t size, gfp_t flags)
{
...
return __kmalloc(size, flags);
}
void *__kmalloc(size_t size, gfp_t flags)
{
struct kmem_cache *s;
void *ret;
// KMALLOC_MAX_CACHE_SIZE 规定 kmalloc 内存池所能管理的内存块最大尺寸,在 slub 实现中是 2页 大小
// 如果使用 kmalloc 申请超过 2页 大小的内存,则直接走伙伴系统
if (unlikely(size > KMALLOC_MAX_CACHE_SIZE))
return kmalloc_large(size, flags);// 底层调用 alloc_pages 向伙伴系统申请超过 2页 的内存块
// 根据申请内存块的尺寸 size,在 kmalloc_caches 缓存中选择合适尺寸的内存池
s = kmalloc_slab(size, flags);
if (unlikely(ZERO_OR_NULL_PTR(s)))
return s;
// 向选取的 slab cache 申请内存块
ret = slab_alloc(s, flags, _RET_IP_, size);
trace_kmalloc(_RET_IP_, ret, size, s->size, flags);
ret = kasan_kmalloc(s, ret, size, flags);
return ret;
}
EXPORT_SYMBOL(__kmalloc);
需要注意的是,当申请的内存大于KMALLOC_MAX_CACHE_SIZE 2页即8k时,内核会直接从伙伴系统中申请,kmalloc_large最终会调用伙伴系统接口申请内存。否则则调用slab的接口slab_alloc进行内存分配。
关于计算合适的偏移得到合适内存池slab cache实现
struct kmem_cache *kmalloc_slab(size_t size, gfp_t flags)
{
unsigned int index;
// 如果申请的内存块 size 在 192 字节以下,则通过 size_index 数组定位 kmalloc_caches 缓存索引
// 从而获取到最佳合适尺寸的内存池 slab cache
if (size <= 192) {
if (!size)
return ZERO_SIZE_PTR;
// 根据申请的内存块 size,定义 size_index 数组索引,从而获取 kmalloc_caches 缓存的 index
index = size_index[size_index_elem(size)];
} else {
if (WARN_ON_ONCE(size > KMALLOC_MAX_CACHE_SIZE))
return NULL;
// 如果申请的内存块 size 超过 192 字节,则通过 fls 定位 kmalloc_caches 缓存的 index
// fls 可以获取参数的最高有效 bit 的位数,比如 fls(0)=0,fls(1)=1,fls(4) = 3
index = fls(size - 1);
}
// 根据 kmalloc_type 以及 index 获取最佳尺寸的内存池 slab cache
return kmalloc_caches[kmalloc_type(flags)][index];
}
3.4 kfree释放内存
释放内存较为简单,先判断当前释放的内存是否在slab管理范围,如果不在,则调用伙伴系统直接释放,如果属于slab管理则调用slab_free slab的释放接口进行释放。
void kfree(const void *x)
{
struct page *page;
// x 为要释放的内存块的虚拟内存地址
void *object = (void *)x;
trace_kfree(_RET_IP_, x);
if (unlikely(ZERO_OR_NULL_PTR(x)))
return;
// 通过虚拟内存地址找到内存块所在的 page
page = virt_to_head_page(x);
// 如果 page 不在 slab cache 的管理体系中,则直接释放回伙伴系统
if (unlikely(!PageSlab(page))) {
free_nonslab_page(page, object);
return;
}
// 将内存块释放回其所在的 slub 中
slab_free(page->slab_cache, page, object, NULL, 1, _RET_IP_);
}
EXPORT_SYMBOL(kfree);
1184

被折叠的 条评论
为什么被折叠?



