3. 启动流程第二阶段:内核解压与入口点(startup_32/64)、早期页表与保护模式切换
当引导加载程序(如 GRUB)将压缩后的内核映像(vmlinuz)加载到物理内存后,CPU 通常仍处于实模式(16 位)或保护模式(32 位)的早期阶段。第二阶段的核心任务是将内核从压缩状态释放、切换到保护模式(或长模式),并建立早期页表,为执行 C 语言编写的内核初始化代码铺平道路。这一阶段是性能分析的第一个关键瓶颈点。
3.1 内核解压流程与入口点
Linux 内核的启动入口点根据架构和模式不同而有所区分。在 x86 架构下,启动过程遵循以下关键步骤:
- 实模式入口(startup_32 实模式变体):引导程序跳转到内核映像的偏移 0x200(即 arch/x86/boot/header.S 中的 _start)。此时 CPU 处于实模式,代码执行 16 位指令,负责设置栈、检测内存布局(通过 BIOS 中断)、将内核从低端内存移动到合适位置。
- 切换到保护模式(startup_32 保护模式变体):在实模式代码末尾,CPU 通过设置 CR0 寄存器的 PE 位切换到保护模式。此时执行 32 位代码,位于 arch/x86/boot/compressed/head_32.S 或 head_64.S。
- 内核解压(decompress_kernel):保护模式下的入口点调用 C 函数
decompress_kernel(),将压缩的内核数据(vmlinux.bin.gz 或 .xz 等)解压到物理内存的高端区域(通常为 16MB 或更高)。 - 跳转到解压后的内核(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 启动流程第二阶段整体架构
下图展示了从引导程序跳转到内核解压、页表建立、模式切换的完整数据流与控制流:
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 中,通过精确控制启动参数和硬件模拟选项,可以隔离并测量这一阶段的各项开销,为内核启动性能优化提供数据支撑。