3、内核解压机制:vmlinuz与zImage的区别,解析自解压代码如何将内核镜像放到正确位置
说到内核启动,有个环节特别有意思——解压。你想想看,我们平时编译出来的内核镜像,动不动就几兆甚至十几兆。但嵌入式设备的存储空间和内存都有限,总不能直接塞个大家伙进去吧?所以就有了压缩内核这回事。
我刚开始接触内核时,就被 vmlinuz 和 zImage 这两个名字搞晕过。它们到底啥关系?自解压又是怎么玩的?今天咱们就把它彻底讲清楚。
3.1 vmlinuz 与 zImage:名字背后的故事
先说说这两个东西的本质区别。
vmlinuz 是压缩后的内核镜像。它前面有一段自解压代码,后面跟着压缩过的内核主体。说白了,它就是一个能自己把自己解开的压缩包。
zImage 是 vmlinuz 的一种特殊形式。它被限制在 640KB 以内,专门给那些内存小的老式 x86 系统用。我当年在 486 机器上折腾 Linux 时,就经常跟 zImage 打交道。
现在的主流架构,比如 ARM、ARM64、x86_64,基本都用 vmlinuz 了。zImage 已经很少见了。但它们的解压原理是一样的。
核心区别一句话总结:
- vmlinuz:通用压缩内核,包含自解压代码
- zImage:vmlinuz 的小内存版本,限制 640KB
- bzImage:大内核版本,突破了 640KB 限制,现在 x86 上用的就是它
3.2 自解压代码:内核的"自我救赎"
自解压代码,说白了就是一段小程序。它被链接在压缩内核的前面。系统启动时,这段代码先跑起来,负责把后面的压缩数据解压到正确位置。
我画了一张流程图,帮你理清这个过程:
3.3 自解压代码的核心逻辑
自解压代码主要在 arch/x86/boot/compressed/head_64.S(x86_64)或 arch/arm/boot/compressed/head.S(ARM)里。我挑几个关键点说说。
3.3.1 地址计算:别把自己覆盖了
自解压代码要做的第一件事,就是算清楚自己现在在哪,目标地址在哪。如果目标地址和当前地址重叠了,那就麻烦了——解压到一半把自己给覆盖了。
我记得在调试一个 ARM 板子时,就遇到过这个问题。内核镜像加载到了 0x20000000,但解压目标地址也是 0x20000000。结果解压到一半,自解压代码自己先被覆盖了,系统直接崩溃。后来加了重定位逻辑才搞定。
代码里是这样处理的:
// 伪代码示意
if (target_addr == current_addr) {
// 地址重叠,需要先把自己挪走
memmove(temp_addr, current_addr, size);
// 然后从 temp_addr 解压到 target_addr
decompress(temp_addr, target_addr);
} else {
// 直接解压
decompress(current_addr, target_addr);
}
3.3.2 解压算法选择
内核支持多种压缩算法。你可以在配置时选:
| 算法 | 压缩比 | 解压速度 | 典型场景 |
|---|---|---|---|
| gzip | 中等 | 快 | 通用,兼容性最好 |
| lzma | 高 | 慢 | 存储空间紧张时 |
| lzo | 低 | 极快 | 启动速度要求高时 |
| xz | 最高 | 最慢 | 极致压缩需求 |
我个人习惯用 lzo。虽然压缩比不高,但解压速度快。嵌入式设备启动时间本来就紧张,能省一点是一点。
3.3.3 解压后的清理工作
解压完成后,自解压代码还要做几件事:
- 清 BSS 段——把未初始化的全局变量清零
- 设置页表——如果内核需要虚拟地址映射
- 跳转到解压后的内核入口——通常是
startup_64或start_kernel
调试小技巧:
如果你想看自解压过程到底干了什么,可以在内核启动参数里加 earlyprintk。这样在解压阶段就能看到打印信息。我曾经靠这个定位过一个解压地址算错的 bug。
3.4 实际项目中的避坑指南
做嵌入式开发这么多年,我在内核解压这块踩过不少坑。挑几个典型的说说。
坑一:解压地址与内存布局冲突
我曾经在一个项目里,把内核解压地址设在了 0x80000000。结果那个地址正好是外设寄存器的映射区。解压完一运行,外设全乱套了。后来查了芯片手册,才发现内存区域是 0x80000000 到 0x90000000,但前 16MB 被外设占了。最后把解压地址改到 0x81000000 才搞定。
坑二:压缩算法不匹配
有次我换了内核版本,忘了改压缩算法配置。编译出来的内核用的是 xz 压缩,但 bootloader 里自解压代码只支持 gzip。结果启动时解压失败,卡在 "Decompressing Linux..." 就不动了。嗯,这种问题查起来特别费时间。
3.5 总结一下
内核解压机制,说白了就是一段小程序把自己后面的压缩数据解开,放到正确位置。vmlinuz 和 zImage 的区别主要在于大小限制,但原理一样。
理解了这个过程,你就能明白:
- 为什么内核镜像要压缩——省存储、省传输时间
- 为什么自解压代码要处理地址重叠——防止把自己覆盖
- 为什么不同压缩算法会影响启动速度——解压时间不一样
下次你看到内核启动时打印 "Decompressing Linux...",就知道背后发生了什么事。这感觉,挺踏实的。