Linux mm_init内核内存子系统初始化顺序mm_init定义在init/main.c中,是start_kernel调用的核心内存初始化函数。它负责建立内核内存管理所需的全部基础架构,从引导时使用的简易memblock分配器平滑过渡到完整的slab分配器。mm_init的调用顺序和内部子步骤代表了内存子系统初始化的层级依赖关系:c// init/main.cstatic void __init mm_init(void){// 步骤1: 初始化页分配器(伙伴系统)// 依赖于setup_arch中完成的memblock填充mem_init();// 步骤2: 初始化slab分配器// 依赖于伙伴系统已就绪kmem_cache_init();// 步骤3: 设置PGD和页表缓存大小// 依赖于体系结构定义的页表层级pgtable_init();// 步骤4: 初始化vmalloc区域// 依赖于页分配器vmalloc_init();// 步骤5: 初始化ioremap映射区域// 依赖于vmalloc机制ioremap_huge_init();// 步骤6: 初始化slab热缓存// 依赖于kmem_cache已初始化kmem_cache_init_late();// 报告最终内存布局mem_debugging_and_hardening();}mem_init是伙伴系统初始化的入口,它终结memblock分配器的使用,将管理权移交给伙伴系统:c// arch/x86/mm/init_64.cvoid __init mem_init(void){unsigned long code_start, code_end, data_start, data_end;unsigned long rodata_start, rodata_end;// 设置内存页最大/最小值// max_low_pfn和max_pfn在setup_arch中已计算BUG_ON(!max_low_pfn);// 初始化全部内存节点// 在UMA架构下仅一个节点for_each_online_node(nid) {pg_data_t *pgdat NODE_DATA(nid);// 在node中初始化伙伴系统free_area_init_node(nid, NULL,pgdat-node_start_pfn,pgdat-node_spanned_pages);}// 释放memblock中未被预留的所有页面到伙伴系统memblock_free_all();// 标记内核代码段、数据段、rodata段的页面属性code_start (unsigned long)_text;code_end (unsigned long)_etext;rodata_start (unsigned long)__start_rodata;rodata_end (unsigned long)__end_rodata;data_start (unsigned long)_sdata;data_end (unsigned long)_edata;// 设置内存属性set_memory_ro(rodata_start, (rodata_end - rodata_start) PAGE_SHIFT);set_memory_nx(code_start, (code_end - code_start) PAGE_SHIFT);set_memory_nx(data_start, (data_end - data_start) PAGE_SHIFT);set_memory_ro(rodata_start, (rodata_end - rodata_start) PAGE_SHIFT);}kmem_cache_init是slab分配器(SLUB)的初始化函数,负责建立slab缓存体系:c// mm/slub.cvoid __init kmem_cache_init(void){int i;// 第一阶段: 使用静态初始化的kmem_cache_boot// 此时kernel_cache尚不可用,需要自举kmem_cache_node kmem_cache_node_boot;// 创建基本的kmem_cache缓存// 这是整个slab体系的基础create_boot_cache(kmem_cache, kmem_cache,offsetof(struct kmem_cache, node) nr_node_ids * sizeof(struct kmem_cache_node *),SLAB_HWCACHE_ALIGN, 0, 0);// 第二阶段: 创建kmalloc缓存数组// kmalloc_caches[0..12]对应8B到4KB// kmalloc_caches[13..26]对应8KB到32MBfor (i 0; i KMALLOC_SHIFT_HIGH; i) {int size 1 i;if (size KMALLOC_MIN_SIZE)continue;// 创建每个大小级别的kmalloc缓存if (!kmalloc_caches[i]) {kmalloc_caches[i] create_kmalloc_cache(kmalloc_info[i].name, size,SLAB_HWCACHE_ALIGN, 0, 0);}}// 第三阶段: 替换引导时的静态缓存// 使用通过kmalloc分配的新缓存替换for (i 0; i KMALLOC_SHIFT_HIGH; i) {struct kmem_cache *s kmalloc_caches[i];if (!s)continue;// 现在可以使用kmalloc分配缓存节点对象if (s-node) {struct kmem_cache_node *n;n kmalloc(sizeof(struct kmem_cache_node) *nr_node_ids, GFP_KERNEL);memcpy(n, s-node,sizeof(struct kmem_cache_node) *nr_node_ids);s-node n;}}}// kmalloc_cache创建辅助函数struct kmem_cache *__init create_kmalloc_cache(const char *name,unsigned int size, slab_flags_t flags,unsigned int useroffset, unsigned int usersize){struct kmem_cache *s kmem_cache_zalloc(kmem_cache,GFP_NOWAIT);if (!s)panic(kmem_cache_zalloc failed for %s\n, name);create_boot_cache(s, name, size, flags,useroffset, usersize);list_add(s-list, slab_caches);s-refcount 1;return s;}pgtable_init设置页表分配所需的内存缓存:c// mm/memory.cvoid __init pgtable_init(void){// 初始化pte/pmd/pud/p4d/pgd缓存// 这些是quicklist中用于页表分配的缓存if (PTRS_PER_PTE 1) {pte_cache_init();}if (PTRS_PER_PMD 1) {pmd_cache_init();}if (PTRS_PER_PUD 1) {pud_cache_init();}if (PTRS_PER_P4D 1) {p4d_cache_init();}// 初始化页表锁page_table_lock_init();}vmalloc_init初始化vmalloc区域,用于非连续内存映射:c// mm/vmalloc.cvoid __init vmalloc_init(void){struct vmap_area *va;struct vm_struct *tmp;int i;// 遍历预分配的vmalloc区域for (tmp vmlist; tmp; tmp tmp-next) {// 为每个vmalloc区域创建vmap_areava kmem_cache_zalloc(vmap_area_cachep,GFP_NOWAIT);if (va) {va-va_start (unsigned long)tmp-addr;va-va_end (unsigned long)tmp-addr tmp-size;va-vm tmp;// 插入vmap_area根树insert_vmap_area(va, vmap_area_root,vmap_area_list);}}// 初始化vmalloc锁和workqueuespin_lock_init(vmap_area_lock);mutex_init(vmap_init_mutex);}// 预分配的vmalloc区域列表static struct vm_struct *vmlist __initdata;kmem_cache_init_late作为slab子系统的后期初始化,在CPU热插拔和scheduler完全初始化后,启用每个CPU的热缓存:c// mm/slub.cvoid __init kmem_cache_init_late(void){// 初始化每个CPU的slub热缓存// 在启动后,sche_on_each_cpu会刷新这些缓存int cpu;for_each_possible_cpu(cpu) {struct kmem_cache_cpu *c per_cpu_ptr(kmem_cache-cpu_slab, cpu);c-page NULL;c-freelist NULL;c-partial NULL;}// 启用SLUB统计(如果配置)if (IS_ENABLED(CONFIG_SLUB_STATS)) {for_each_possible_cpu(cpu) {struct kmem_cache_cpu *c per_cpu_ptr(kmem_cache-cpu_slab, cpu);memset(c-stat, 0, sizeof(c-stat));}}}mm_init完成后,slab分配器完全可用,伙伴系统已经准备就绪,vmalloc和ioremap机制可以正常使用。内核可以从memblock分配器完全过渡到通用的页分配器和slab分配器,为后续fork_init、sched_init等子系统创建各自的专用缓存奠定基础。