3. memblock初始化流程:从start_kernel到memblock初始化

memblock 是 Linux 内核早期启动阶段使用的物理内存分配器,其初始化发生在 start_kernel 函数执行初期,早于页分配器(buddy system)的建立。理解 memblock 的初始化流程,是掌握内核内存管理子系统启动过程的关键。

核心要点:memblock 初始化是“从无到有”的过程——内核在无法依赖任何动态内存分配机制时,通过静态定义的数据结构和汇编阶段传递的内存信息,逐步建立起可用的物理内存管理框架。

3.1 初始化起点:start_kernel 中的调用链

memblock 的初始化入口位于 init/main.c 的 start_kernel 函数中。以下是关键调用序列:

// start_kernel 中与 memblock 相关的调用链(简化)
asmlinkage __visible void __init start_kernel(void)
{
    char *command_line;
    char *after_dashes;

    // ... 架构无关的早期初始化 ...

    setup_arch(&command_line);      // 架构相关初始化,包含 memblock 早期设置
    // ...
    mm_init_cpumask(&init_mm);     // 内存描述符初始化
    setup_command_line(command_line); // 命令行参数保存
    setup_nr_cpu_ids();            // CPU 数量检测
    setup_per_cpu_areas();         // per-CPU 区域分配(使用 memblock)
    // ...
    build_all_zonelists(NULL);     // 构建 zonelist(依赖 memblock 内存信息)
    page_alloc_init();             // 页分配器初始化
    // ...
    mem_init();                    // 打印内存信息,memblock 统计输出
    // ...
    mm_init();                     // 内存管理子系统完整初始化
    // ...
}

上述流程中,setup_arch 是 memblock 初始化的真正起点。不同架构的实现略有差异,但核心逻辑一致。

3.2 关键函数调用链全景

下表列出了从 start_kernel 到 memblock 就绪的核心函数调用链及其职责:

调用顺序 函数名称 所在文件 核心职责
1 start_kernel init/main.c 内核启动 C 语言入口,调度所有子系统初始化
2 setup_arch arch/arm64/kernel/setup.c (以 ARM64 为例) 架构相关初始化,解析设备树/BIOS 传递的内存信息
3 arm64_memblock_init arch/arm64/mm/init.c ARM64 架构的 memblock 初始化封装,调用通用接口
4 memblock_add mm/memblock.c 将物理内存区域添加到 memblock.memory 类型
5 memblock_reserve mm/memblock.c 将内核镜像、页表等保留区域标记为已分配
6 memblock_alloc mm/memblock.c 早期内存分配接口,供后续初始化使用
7 memblock_free mm/memblock.c 释放不再需要的早期保留内存
架构差异提示:x86 架构使用 setup_memory_map → e820__memblock_setup 路径;ARM32 使用 arm_memblock_init;RISC-V 使用 setup_bootmem。但最终都调用 memblock_add/memblock_reserve 等通用接口。

3.3 memblock 初始化流程 SVG 架构图

下图展示了从 start_kernel 到 memblock 就绪的完整调用链与数据流:

memblock 初始化流程:start_kernel → memblock 就绪 start_kernel() setup_arch(command_line) arm64_memblock_init() / e820__memblock_setup() memblock_add() 添加可用物理内存区域 memblock_reserve() 保留内核/页表等占用区域 memblock_alloc() 早期动态分配内存 memblock 分配器就绪 memory / reserved 双区域管理 setup_per_cpu_areas() / build_all_zonelists() / page_alloc_init() 说明:memblock_add 和 memblock_reserve 共同构建了物理内存的完整视图,memblock_alloc 为后续子系统提供临时分配能力

3.4 核心数据结构初始化

memblock 的全局实例 struct memblock 在编译时通过 #define 静态初始化,无需运行时分配:

// mm/memblock.c 中的静态定义
static struct memblock_region memblock_memory_init_regions[INIT_MEMBLOCK_REGIONS] __initdata_memblock;
static struct memblock_region memblock_reserved_init_regions[INIT_MEMBLOCK_REGIONS] __initdata_memblock;

struct memblock memblock __initdata_memblock = {
    .memory.regions     = memblock_memory_init_regions,
    .memory.cnt         = 1,    // 初始计数为1
    .memory.max         = INIT_MEMBLOCK_REGIONS,
    .memory.name        = "memory",

    .reserved.regions   = memblock_reserved_init_regions,
    .reserved.cnt       = 1,
    .reserved.max       = INIT_MEMBLOCK_REGIONS,
    .reserved.name      = "reserved",

    .bottom_up          = false, // 默认从高地址向下分配
    .current_limit      = MEMBLOCK_ALLOC_ANYWHERE,
};
关键约束:INIT_MEMBLOCK_REGIONS 默认值为 128。当添加的内存区域超过此数量时,memblock 会尝试通过 memblock_double_array 动态扩展 region 数组——这本身又需要调用 memblock_alloc,形成“先有鸡还是先有蛋”的问题。内核通过预留一个最小安全区域来解决此循环依赖。

3.5 初始化阶段的内存区域操作

在 setup_arch 执行过程中,memblock 主要完成三类区域操作:

  1. 添加可用区域(memblock_add):将固件/引导程序报告的可用物理内存范围加入 memblock.memory 类型。
  2. 保留已占用区域(memblock_reserve):将内核镜像、设备树/ACPI 表、页表、initrd 等占用的内存加入 memblock.reserved 类型。
  3. 标记特殊区域(memblock_mark_hotplug / memblock_mark_nomap):标识可热插拔或不可直接映射的区域。

以下是一个典型的 ARM64 平台初始化序列:

// arch/arm64/mm/init.c
void __init arm64_memblock_init(void)
{
    // 1. 从设备树获取内存信息并添加
    // 实际由 early_init_dt_scan_memory() 调用 memblock_add()
    
    // 2. 保留内核镜像所在区域
    memblock_reserve(__pa_symbol(_text), _end - _text);
    
    // 3. 保留 initrd 区域(如果存在)
    if (initrd_start)
        memblock_reserve(__pa(initrd_start), initrd_end - initrd_start);
    
    // 4. 保留设备树 blob
    memblock_reserve(__pa(fdt), fdt_totalsize(fdt));
    
    // 5. 保留 EFI 内存映射表(如果使用 UEFI)
    // ...
    
    // 6. 对某些区域应用特殊属性
    memblock_mark_nomap(phys, size); // 标记不可直接映射区域
}

3.6 初始化完成后的状态检查

memblock 初始化完成后,可以通过以下方式验证状态:

检查方式 命令/接口 输出示例
内核启动日志 dmesg | grep -i memblock memblock: memory: 0x0000004000000000..0x00000043ffffffff
proc 文件系统 cat /proc/meminfo (早期不可用) MemTotal / MemFree 等统计
调试接口 memblock_dump_all() 内核函数 打印所有 memory 和 reserved 区域
sysfs 节点 /sys/devices/system/memory/ 内存块信息(需内核配置)
调试技巧:在早期启动阶段,可以通过 early_param("memblock=debug", ...) 或 memblock=debug 内核参数启用 memblock 的详细调试输出,跟踪每次 add/reserve/alloc/free 操作。

3.7 初始化流程中的常见问题

  • 内存区域重叠:不同固件接口报告的内存范围可能重叠,memblock 会自动合并相邻区域,但交叉重叠可能导致计数异常。
  • 保留区域遗漏:如果内核镜像或关键数据结构未被正确保留,后续 memblock_alloc 可能覆盖这些区域,导致内核崩溃。
  • region 数组溢出:当内存区域碎片化严重时,128 个初始 region 可能不够用,触发 memblock_double_array 失败会导致系统挂起。
  • current_limit 设置不当:某些架构需要限制分配范围(如 DMA 区域),若 memblock_set_current_limit 调用时机错误,可能导致分配越界。