3. slab分配器初始化:slab系统初始化流程,kmem_cache_init与kmem_cache_create源码分析

slab分配器是Linux内核中用于管理小对象(通常小于一页)的核心内存分配机制。其初始化过程分为两个关键阶段:slab系统自举(kmem_cache_init)和通用缓存创建(kmem_cache_create)。理解这两个函数的执行流程,是掌握slab分配器工作原理的基石。

3.1 slab系统初始化整体流程

在系统启动早期,内存管理子系统尚未完全就绪时,slab分配器需要通过一个“自举”过程来建立自身所需的数据结构。整个初始化流程分为四个主要阶段,如下表所示:

阶段 函数/步骤 核心任务 关键数据结构
1 kmem_cache_init (早期) 创建静态的cache_cache,用于管理slab缓存描述符自身 struct kmem_cache (静态实例)
2 kmem_cache_init (中期) 创建malloc_sizes数组中的前几个通用缓存(如32B、64B) cache_sizes、kmem_list3
3 kmem_cache_init (后期) 创建剩余的通用缓存,并启用per-CPU缓存(array_cache) array_cache、kmem_list3
4 kmem_cache_create 为驱动或内核子系统创建专用缓存 struct kmem_cache (动态分配)
关键概念:slab分配器的初始化是一个“鸡生蛋”问题——分配器本身需要内存来管理内存。内核通过先使用静态定义的数据结构,再逐步过渡到动态分配的方式解决这一矛盾。

3.2 kmem_cache_init 源码分析

kmem_cache_init 是slab分配器的入口函数,位于 mm/slab.c。它负责建立slab分配器运行所需的所有基础设施。其核心执行逻辑如下:

  1. 初始化 cache_cache:这是一个静态定义的 struct kmem_cache 实例,专门用于管理“slab缓存描述符”本身。内核通过 init_cache 宏或直接赋值来设置其初始值。
  2. 创建 malloc_sizes 中的前几个缓存:使用 kmem_cache_create 创建大小为32、64、96、128字节的通用缓存。此时,kmem_cache_create 内部会从 cache_cache 中分配新的 struct kmem_cache 对象。
  3. 启用per-CPU缓存:为每个CPU分配 array_cache 结构,并挂接到对应的 struct kmem_cache 上。这大大提高了多核系统下的分配性能。
  4. 创建剩余通用缓存:创建所有 malloc_sizes 中定义的缓存(如256B、512B、1KB等),完成通用内存分配器的初始化。
源码关键片段 (简化逻辑):
// mm/slab.c
void __init kmem_cache_init(void)
{
    int i;

    // 1. 初始化 cache_cache (静态定义)
    cache_cache = (struct kmem_cache) {
        .objsize = sizeof(struct kmem_cache),
        .flags = SLAB_NO_REAP,
        .num = 0, // 稍后计算
        .gfporder = 0,
        // ... 其他字段初始化
    };

    // 2. 创建前几个通用缓存 (使用 cache_cache 分配)
    for (i = 0; i < NUM_INITIAL_CACHES; i++) {
        kmem_cache_create(malloc_sizes[i].name,
                          malloc_sizes[i].size,
                          0, 0, NULL);
    }

    // 3. 启用 per-CPU 缓存
    for_each_online_cpu(cpu) {
        for (i = 0; i < NUM_CACHES; i++) {
            // 分配并初始化 array_cache
            // 设置 shared 和 alien 指针
        }
    }

    // 4. 创建剩余通用缓存
    for (; i < NUM_CACHES; i++) {
        kmem_cache_create(malloc_sizes[i].name,
                          malloc_sizes[i].size,
                          0, 0, NULL);
    }

    // 标记初始化完成
    slab_state = UP;
}

3.3 kmem_cache_create 源码分析

kmem_cache_create 是驱动开发者最常接触的slab API。它用于创建一个新的专用缓存,并返回一个 struct kmem_cache * 指针。其核心流程包括参数校验、slab计算、对象布局和per-CPU缓存初始化。

3.3.1 函数原型与参数

struct kmem_cache *
kmem_cache_create(const char *name, size_t size, size_t align,
                  slab_flags_t flags, void (*ctor)(void *));
参数 含义 典型值/说明
name 缓存名称,用于/proc/slabinfo标识 如"my_driver_cache"
size 每个对象的大小(字节) 必须大于0,通常对齐到sizeof(void *)
align 对象对齐要求(字节) 0表示使用默认对齐(L1缓存行)
flags 控制slab行为的标志位 SLAB_HWCACHE_ALIGN、SLAB_PANIC等
ctor 构造函数,每次分配对象时调用 通常为NULL,用于初始化对象

3.3.2 核心执行步骤

  1. 参数校验与对齐:检查 size 是否合法,根据 align 和 flags 计算最终的对象对齐值。如果 flags 包含 SLAB_HWCACHE_ALIGN,则对齐到L1缓存行大小。
  2. 计算slab布局:确定每个slab中包含多少个对象(num),以及需要多少个物理页面(gfporder)。这是通过 calculate_slab_order 函数完成的,它会尝试不同的 gfporder,直到找到一个能容纳整数个对象且碎片最小的配置。
  3. 分配 struct kmem_cache:从 cache_cache 中分配一个新的缓存描述符。如果 kmem_cache_init 尚未完成,则使用静态预分配的空间。
  4. 初始化per-CPU缓存:为每个在线CPU分配 array_cache 结构,并设置其容量(limit)和批量传输大小(batchcount)。
  5. 初始化节点列表:为每个内存节点初始化 kmem_list3 结构,用于管理该节点上的slab链表(full、partial、free)。
  6. 注册到全局链表:将新创建的缓存添加到 cache_chain 全局链表中,使其对 kmem_cache_alloc 可用。
驱动开发注意:在模块卸载时,必须调用 kmem_cache_destroy 销毁通过 kmem_cache_create 创建的缓存。如果缓存中仍有未释放的对象,内核会打印警告并阻止卸载。

3.4 slab初始化流程SVG架构图

下图展示了从系统启动到驱动创建专用缓存的完整初始化流程:

slab分配器初始化流程架构图 slab分配器初始化流程架构图 阶段1: 早期初始化 kmem_cache_init() 创建静态 cache_cache 用于管理slab描述符 阶段2: 中期初始化 创建前几个通用缓存 32B, 64B, 96B, 128B 从 cache_cache 分配 阶段3: 后期初始化 启用 per-CPU 缓存 创建剩余通用缓存 slab_state = UP 阶段4: 驱动调用 kmem_cache_create() 创建专用缓存,如 "my_driver_cache" 参数校验与对齐 size, align, flags 计算slab布局 calculate_slab_order() 分配描述符 从 cache_cache 分配 初始化 per-CPU 缓存 & 节点列表 array_cache, kmem_list3, cache_chain 说明:slab分配器通过“自举”方式解决初始化依赖问题,先创建管理自身的缓存,再逐步建立完整的通用分配体系。 驱动开发者只需关注阶段4,调用 kmem_cache_create 即可获得高性能的专用对象缓存。

3.5 关键数据结构关系

在初始化过程中,以下几个核心数据结构相互关联,构成了slab分配器的骨架:

  • struct kmem_cache:每个缓存的描述符,包含对象大小、slab布局、per-CPU缓存指针、节点列表等。
  • struct array_cache:per-CPU的本地对象缓存,用于无锁快速分配。
  • struct kmem_list3:每个内存节点的slab链表管理结构,包含full、partial、free三个链表。
  • struct slab:描述一个物理连续的内存块(通常为1页或多页),包含对象分配位图。
初始化顺序依赖关系:
cache_cache (静态) 
    → 用于分配 struct kmem_cache 
        → 用于创建通用缓存 (malloc_sizes) 
            → 用于创建 array_cache (per-CPU) 
                → 完整的 slab 分配器就绪
                    → 驱动调用 kmem_cache_create 创建专用缓存

3.6 驱动开发实践要点

对于Linux驱动开发者,理解slab初始化流程有助于正确使用内存分配API:

场景 推荐做法 原因
频繁分配/释放固定大小结构体 使用 kmem_cache_create + kmem_cache_alloc 避免内存碎片,提高分配效率
对象大小超过一页 使用 __get_free_pages 或 vmalloc slab分配器针对小对象优化
需要对齐到特定边界 设置 align 参数或 SLAB_HWCACHE_ALIGN 标志 避免伪共享,提高缓存性能
模块初始化时创建缓存 在 module_init 函数中调用 kmem_cache_create 确保在首次分配前缓存已就绪
模块卸载时销毁缓存 在 module_exit 函数中调用 kmem_cache_destroy 防止内存泄漏
常见错误:在 kmem_cache_create 中指定 SLAB_PANIC 标志时,如果内存不足,内核会直接 panic。在驱动中应谨慎使用,或确保在系统内存充足时调用。

通过深入理解 kmem_cache_init 和 kmem_cache_create 的源码实现,驱动开发者可以更高效地利用slab分配器,避免常见的性能陷阱和内存管理错误。