mem_init是 x86-64 架构内存初始化流程中的收官函数,位于arch/x86/mm/init_64.c。它在伙伴系统(buddy allocator)已经就绪之后被调用,负责完成几项关键的“收尾”工作,将系统从“启动模式”正式切换到“运行模式”。
核心作用:启动模式到运行模式的切换
这个函数执行时,memblock已经把空闲内存全部释放给了伙伴系统。此时需要做的是:标记启动阶段结束、注册特殊内存区域、预分配页表页,为后续进程管理铺平道路。
逐行解析
void __init mem_init(void) { /* clear_bss() already clear the empty_zero_page */ after_bootmem = 1; x86_init.hyper.init_after_bootmem();after_bootmem = 1:这是最关键的标志切换。这个标志决定了内存分配走memblock_alloc()还是get_zeroed_page()。置为 1 后,所有分配都走伙伴系统路径。x86_init.hyper.init_after_bootmem():调用 Hypervisor 相关的钩子。在 Xen 等虚拟化环境下,此时需要通知 Hypervisor 完成一些收尾工作(如释放早期预留的页表页)。
register_page_bootmem_info();
register_page_bootmem_info():为 NUMA 节点或大页优化场景注册 bootmem 元数据。注释解释了为什么必须放在这里:因为此时memblock_free_all()已经将所有保留的延迟页初始化完毕,可以安全地操作这些struct page了。
if (get_gate_vma(&init_mm)) kclist_add(&kcore_vsyscall, (void *)VSYSCALL_ADDR, PAGE_SIZE, KCORE_USER);
注册 vsyscall 页面到
/proc/kcore:如果 vsyscall 页面存在(get_gate_vma返回非空),将其添加到 kcore 列表,类型为KCORE_USER。这让调试工具能通过/proc/kcore读取这个特殊页面。
preallocate_vmalloc_pages(); }
preallocate_vmalloc_pages():预分配 vmalloc 区域的顶层页表页。它的目的是避免运行时昂贵的页表同步操作,让所有进程创建时直接继承这些预分配的页表页。
在启动流程中的位置
setup_arch() ├── paging_init() // 建立内存管理数据结构 └── ... mem_init() // ← 启动模式 → 运行模式 ├── after_bootmem = 1 ├── register_page_bootmem_info() ├── kclist_add(vsyscall) └── preallocate_vmalloc_pages()
mem_init()执行完毕后,内核的内存管理子系统就完全就绪了。后续的mm_init()会初始化 SLAB/SLUB 分配器、页表缓存等更高级的机制,然后内核就可以开始创建用户进程了。
为什么这些工作要放在这里?
这个函数的每一项工作都有一个共同特点:它们都必须在伙伴系统就绪之后、但在任何用户进程创建之前完成。
after_bootmem = 1必须在 memblock 释放完内存后设置。register_page_bootmem_info()必须在memblock_free_all()初始化完所有延迟页之后调用。preallocate_vmalloc_pages()必须在第一个用户进程创建之前完成,否则该进程的页表会缺少 vmalloc 区域的顶层条目。
这个函数是 x86-64 内存初始化的“最后一公里”,完成后系统就真正具备了管理内存和创建进程的能力。