3. 启动流程第二阶段:内核解压与入口点(startup_32/64)、早期页表与保护模式切换

当引导加载程序(如 GRUB)将压缩后的内核映像(vmlinuz)加载到物理内存后,CPU 通常仍处于实模式(16 位)或保护模式(32 位)的早期阶段。第二阶段的核心任务是将内核从压缩状态释放、切换到保护模式(或长模式),并建立早期页表,为执行 C 语言编写的内核初始化代码铺平道路。这一阶段是性能分析的第一个关键瓶颈点。

3.1 内核解压流程与入口点

Linux 内核的启动入口点根据架构和模式不同而有所区分。在 x86 架构下,启动过程遵循以下关键步骤:

  1. 实模式入口(startup_32 实模式变体):引导程序跳转到内核映像的偏移 0x200(即 arch/x86/boot/header.S 中的 _start)。此时 CPU 处于实模式,代码执行 16 位指令,负责设置栈、检测内存布局(通过 BIOS 中断)、将内核从低端内存移动到合适位置。
  2. 切换到保护模式(startup_32 保护模式变体):在实模式代码末尾,CPU 通过设置 CR0 寄存器的 PE 位切换到保护模式。此时执行 32 位代码,位于 arch/x86/boot/compressed/head_32.S 或 head_64.S。
  3. 内核解压(decompress_kernel):保护模式下的入口点调用 C 函数 decompress_kernel(),将压缩的内核数据(vmlinux.bin.gz 或 .xz 等)解压到物理内存的高端区域(通常为 16MB 或更高)。
  4. 跳转到解压后的内核(startup_64 或 startup_32):解压完成后,代码跳转到解压后的内核映像的入口点,开始执行架构无关的 C 代码(start_kernel)。
性能要点: 内核解压是纯 CPU 密集型操作,解压算法(LZ4、LZMA、XZ)的选择直接影响启动时间。在 QEMU 中,使用 -kernel 直接加载未压缩内核(vmlinux)可以跳过此阶段,显著加速启动,适用于频繁重启的调试场景。
QEMU 调优提示: 使用 -append "nokaslr" 可以禁用内核地址空间布局随机化(KASLR),减少启动时的重定位计算开销,对性能分析有稳定可重复的好处。

3.2 早期页表建立与保护模式切换

在解压完成后,内核需要建立早期页表,以启用分页机制。这是从物理地址空间到虚拟地址空间的关键转换,也是保护模式(或长模式)下内存管理的基础。

阶段 页表级别 地址空间 关键寄存器 目的
早期 32 位保护模式 二级页表(PGD + PT) 0-4GB 物理映射 CR3(指向 swapper_pg_dir) 启用分页,保持恒等映射
64 位长模式切换 四级页表(PML4 + PDP + PD + PT) 规范地址空间(0xffff888000000000 等) EFER.LME、CR0.PG、CR3 启用 64 位长模式,映射内核虚拟地址
内核初始化完成 最终页表(固定映射 + vmalloc 区域) 完整内核虚拟地址空间 CR3 更新为 init_mm.pgd 支持模块、动态映射、用户空间

在 arch/x86/kernel/head_64.S 中,早期页表的建立过程如下:

/* 早期页表建立(head_64.S 简化片段) */
__startup_64:
    /* 计算当前物理地址与编译时虚拟地址的偏移 */
    leaq    _text(%rip), %rbp
    subq    $__START_KERNEL_map, %rbp

    /* 设置 PML4 表项:将物理地址 0-1GB 映射到虚拟地址空间 */
    leaq    level2_kernel_pgt(%rip), %rdi
    /* ... 填充页表项,设置 Present、R/W、User 等标志位 ... */

    /* 设置 CR3 指向早期页表 */
    movq    $early_top_pgt, %rax
    movq    %rax, %cr3

    /* 设置 EFER 寄存器启用长模式 */
    movl    $MSR_EFER, %ecx
    rdmsr
    btsl    $_EFER_LME, %eax
    wrmsr

    /* 设置 CR0 启用分页 */
    movq    %cr0, %rax
    btsl    $X86_CR0_PG_BIT, %eax
    movq    %rax, %cr0
    /* 此时 CPU 已处于长模式(64 位) */
常见陷阱: 早期页表使用恒等映射(identity mapping),即物理地址与虚拟地址相同。如果页表项设置错误(如未设置 Present 位或地址对齐错误),CPU 会立即触发三重故障(Triple Fault)并重启。在 QEMU 调试时,使用 -d int 可以捕获异常信息,帮助定位页表问题。

3.3 启动流程第二阶段整体架构

下图展示了从引导程序跳转到内核解压、页表建立、模式切换的完整数据流与控制流:

第二阶段启动流程架构图 第二阶段:内核解压与保护模式切换流程 引导程序 (GRUB) 实模式 startup_32 保护模式切换 decompress_kernel 早期页表建立 长模式切换 (x86_64) start_kernel (C 入口) 性能关键点 解压算法选择 页表 TLB 刷新开销

3.4 性能分析关键指标

在 QEMU 环境下,第二阶段启动性能可以通过以下指标进行量化分析:

  • 解压耗时:从 decompress_kernel() 入口到返回的时钟周期数。使用 rdtsc 指令在汇编层面插桩测量。
  • 页表建立时间:包括页表内存分配、页表项填充、TLB 刷新(INVLPG 指令)的总开销。
  • 模式切换延迟:从保护模式切换到长模式时,CR0、CR3、EFER 等 MSR 寄存器的写入序列带来的序列化开销。
  • QEMU 虚拟化开销:在 KVM 加速下,模式切换和页表操作会触发 VM-exit,增加额外延迟。使用 perf kvm stat 可以捕获这些事件。
QEMU 调优建议: 对于启动性能敏感的测试,建议使用 -cpu host 和 -enable-kvm 以最大化硬件虚拟化支持。同时,使用 -m 1024 分配足够内存,避免早期页表分配时触发 guest 内部的内存压力。

3.5 常见问题与排查方法

问题现象 可能原因 QEMU 调试方法
启动后立即重启(三重故障) 页表项错误、长模式切换顺序错误 -d int,cpu_reset 查看异常向量
解压阶段卡死 内存不足、解压算法 CRC 校验失败 -trace events=decompress* 跟踪解压事件
启动极慢(>10 秒) 未使用 KVM、解压算法为 XZ -machine accel=kvm 启用加速
内核 panic:无法挂载根文件系统 早期页表未正确映射控制台 I/O 地址 -serial stdio 查看早期控制台输出

掌握第二阶段的内核解压与页表建立机制,是后续分析内存初始化、调度器启动、设备驱动加载等阶段性能的基础。在 QEMU 中,通过精确控制启动参数和硬件模拟选项,可以隔离并测量这一阶段的各项开销,为内核启动性能优化提供数据支撑。