1. 课程导论:Linux内核启动流程概述
1.1 启动流程全景图
Linux内核从加电到用户空间init进程启动,经历多个阶段。解压缩阶段位于内核自解压与重定位环节,是内核从压缩镜像到可执行ELF内核的桥梁。下图展示完整的启动链:
1.2 解压缩阶段在启动链中的位置
解压缩阶段位于Bootloader将控制权交给内核之后、内核自身初始化之前。具体位置由内核镜像的链接布局决定:
| 阶段序号 | 阶段名称 | 入口地址 | 主要职责 |
|---|---|---|---|
| 1 | Bootloader加载 | 物理内存起始(如0x8000) | 将压缩内核镜像加载到RAM,设置启动参数 |
| 2 | 解压缩阶段 | 解压缩代码入口(如startup_32) | 自解压、重定位、校验、跳转 |
| 3 | 内核入口 | 解压后内核基址(如0x1000000) | start_kernel(),体系结构初始化 |
| 4 | 内核初始化 | 虚拟地址空间 | 内存管理、调度、驱动、文件系统 |
关键理解:解压缩阶段是内核从"压缩镜像"到"可执行ELF内核"的转换器。它负责将Bootloader加载的压缩数据解压到正确地址,并完成地址重定位,使内核能够在任意物理地址运行。
1.3 解压缩阶段的核心作用
解压缩阶段承担以下三项核心任务:
- 自解压(Self-extraction):内核镜像头部包含解压缩代码,启动后自动解压gzip/lz4/lzma等格式的压缩数据。
- 地址重定位(Address Relocation):将解压后的内核移动到最终运行地址,修正符号表与全局偏移表(GOT),确保代码段、数据段、BSS段地址正确。
- 完整性校验(Integrity Verification):通过CRC32或SHA校验解压后数据,防止内存损坏或恶意篡改导致内核崩溃。
1.4 解压缩阶段的输入与输出
理解解压缩阶段的边界条件,有助于后续章节深入分析:
| 维度 | 输入 | 输出 |
|---|---|---|
| 镜像格式 | zImage / bzImage(ARM: zImage, x86: bzImage) | 原始ELF内核(vmlinux) |
| 压缩算法 | gzip(默认)、lz4、lzma、xz、zstd | 无压缩的二进制数据 |
| 地址空间 | Bootloader加载地址(可能非最终地址) | 重定位后的最终运行地址 |
| 校验信息 | 镜像尾部附加的校验值 | 校验通过/失败状态 |
| 控制权交接 | Bootloader跳转到解压缩入口 | 跳转到解压后内核入口(start_kernel) |
实践要点:嵌入式开发中,解压缩阶段是调试启动失败的关键切入点。常见问题包括:解压缩地址与加载地址重叠导致数据损坏、校验失败(内存ECC错误)、重定位后符号表未修正导致函数调用崩溃。建议在解压缩阶段增加串口打印(early_printk)以定位故障。
1.5 解压缩阶段的代码组织
解压缩代码位于内核源码的 arch/<arch>/boot/compressed/ 目录下,以ARM64为例:
arch/arm64/boot/compressed/
├── head.S # 解压缩入口,设置栈、调用解压函数
├── misc.c # 解压算法封装(gunzip等)
├── decompress.c # 解压核心逻辑
├── string.c # 轻量级字符串函数(避免依赖内核库)
├── vmlinux.lds.S # 链接脚本,定义解压缩代码布局
└── Makefile # 编译规则
关键代码执行顺序:
head.S中的startup_32(ARM)或_start(x86)设置CPU模式、栈指针。- 调用
decompress_kernel()执行解压,同时进行地址重定位。 - 解压完成后,调用
__inflate_kernel()进行CRC校验。 - 跳转到解压后内核入口
__mmap_switched(ARM)或startup_64(x86)。
注意:解压缩阶段运行在物理地址空间,未开启MMU。所有地址操作必须使用物理地址,不能依赖虚拟内存映射。这也是地址重定位必须手动修正GOT的原因。