4. Kernel 分区打包:zImage/Image.gz、DTB 打包、内核启动参数与 cmdline
Kernel 分区,是整个镜像打包里最需要小心伺候的部分。我见过太多人把精力全放在 rootfs 上,结果 Kernel 分区出问题,板子直接变砖。说白了,Kernel 分区就是系统的发动机,发动机装错了,车再好也开不动。
这一章,我们重点聊三件事:内核镜像怎么选、DTB 怎么绑、cmdline 怎么写。这三样东西,任何一个出岔子,系统都起不来。
4.1 内核镜像格式:zImage vs Image.gz
先说说内核镜像本身。你从编译链拿到的内核,通常有两种形态:zImage 和 Image.gz。很多人搞不清区别,其实没那么玄乎。
| 特性 | zImage | Image.gz |
|---|---|---|
| 本质 | 自解压镜像,头部自带解压代码 | 纯 gzip 压缩的 ELF 镜像 |
| 加载地址 | 通常固定(如 0x30008000) | 由 bootloader 决定 |
| 解压方式 | CPU 执行头部代码自行解压 | 需要 bootloader 或硬件解压 |
| 适用场景 | 老旧 bootloader、无解压支持 | 现代 bootloader(如 U-Boot) |
我个人习惯,只要 bootloader 支持,优先用 Image.gz。为什么?因为 zImage 的加载地址是硬编码的,一旦你换了内存布局,就得重新编译内核。而 Image.gz 是位置无关的,bootloader 可以把它放到任意地址,灵活性高得多。
bootm 命令加载 Image.gz,而老旧版本才需要 zImage。
4.2 DTB 打包:分离还是绑定?
DTB(Device Tree Blob)是内核的硬件描述文件。没有它,内核不知道你的板子上有哪些外设、内存多大、GPIO 怎么接。DTB 的打包方式,直接决定了你的系统能不能适配不同硬件。
这里有两种主流做法:
- 分离式: DTB 单独作为一个分区,或者由 bootloader 从文件系统加载。
- 绑定式: DTB 直接附加到内核镜像尾部,打包成一个文件。
我在项目中遇到过一种情况:同一套内核要跑在三个硬件版本上,每个版本的 DTB 都不一样。如果采用绑定式,就得为每个版本单独编译内核,维护成本极高。后来我们改用分离式,bootloader 根据硬件版本号动态加载对应的 DTB,问题就解决了。
但绑定式也有它的优势——简单、可靠。对于量产产品,硬件固定不变,绑定式可以减少一个分区,降低出错概率。
# 将 DTB 附加到 Image.gz 尾部
cat Image.gz > kernel_with_dtb
cat board.dtb >> kernel_with_dtb
# 或者用 U-Boot 的 mkimage 工具
mkimage -A arm -O linux -T kernel -C gzip \
-a 0x80008000 -e 0x80008000 \
-n "Linux Kernel with DTB" \
-d kernel_with_dtb uImage
4.3 内核启动参数与 cmdline
cmdline 是内核启动时的一串参数,告诉内核根分区在哪、控制台用哪个串口、内存大小等等。它通常由 bootloader 传递给内核,但也可以硬编码在内核配置中。
一个典型的 cmdline 长这样:
console=ttyS0,115200 root=/dev/mmcblk0p2 rootwait rw
每个参数的含义:
console=ttyS0,115200:指定串口控制台,波特率 115200。root=/dev/mmcblk0p2:根文件系统在 eMMC 的第二个分区。rootwait:等待根设备就绪再挂载(对 SD 卡/eMMC 很重要)。rw:以读写方式挂载根文件系统。
你想想看,如果 root 参数写错了,内核启动时找不到根分区,就会 panic。我见过一个同事,把 mmcblk0p2 写成了 mmcblk1p2,结果板子死活起不来,最后发现是 SD 卡和 eMMC 的设备号搞混了。
4.4 打包流程与校验
好了,现在我们把内核镜像、DTB、cmdline 都准备好了。接下来就是打包。我建议的流程如下:
- 编译内核,生成
Image.gz。 - 编译 DTB,生成
board.dtb。 - 将 DTB 附加到内核尾部(如果采用绑定式)。
- 用
mkimage或mksquashfs打包成 U-Boot 可识别的格式。 - 计算整个分区的哈希值,写入校验文件。
校验这一步,很多人会忽略。但你想,如果烧录过程中数据损坏,内核镜像少了一个字节,系统就起不来了。所以,我每次打包都会生成一个 SHA256 校验文件,烧录后由 bootloader 或上位机工具验证。
# 生成校验文件
sha256sum kernel_partition.bin > kernel_partition.sha256
# 烧录后验证
sha256sum -c kernel_partition.sha256
4.5 知识体系图
下面这张图,把 Kernel 分区打包的核心逻辑串起来了。你可以把它当作一个检查清单,每次打包时对照着走一遍。
4.6 避坑指南
最后,分享几个我踩过的坑,希望能帮你省点时间:
- DTB 版本不匹配: 内核版本和 DTB 版本必须对应。我曾经把 5.10 内核的 DTB 用在 5.4 内核上,结果 GPIO 驱动全部失效。
- cmdline 过长: 内核 cmdline 有长度限制(通常是 2048 字节)。如果你塞了太多参数,后面的会被截断。我建议用
cat /proc/cmdline检查实际生效的参数。 - 打包顺序: 绑定 DTB 时,一定要先放内核镜像,再追加 DTB。顺序反了,内核会尝试把 DTB 当成代码执行,直接崩溃。
- 校验文件丢失: 生成校验文件后,记得和镜像一起归档。我见过有人只存了镜像,校验文件丢了,烧录后出了问题也没法验证。
嗯,Kernel 分区打包这部分,说复杂也复杂,说简单也简单。核心就是三件事:选对镜像、绑好 DTB、写对 cmdline。每一步都做扎实了,系统启动就不会出大问题。