2. vmlinuz 与 zImage/bzImage:内核镜像格式的演变
2.1 从 ELF 到可引导镜像:格式演变的动因
编译完成的内核原始产物是 ELF 格式的 vmlinux,它包含完整的符号表和节区信息,体积通常在数 MB 到数十 MB 之间。但引导加载程序(如 GRUB、U-Boot)和 PC BIOS 无法直接解析 ELF 结构,且早期 x86 实模式下的寻址空间仅有 1 MB。因此,内核必须被转换为一种自包含、可压缩、且能被引导器加载的扁平镜像格式。
格式演变的核心驱动力包括:
- 体积约束:早期软盘和 ROM 容量有限,必须压缩内核。
- 寻址限制:x86 实模式只能访问 1 MB 以下内存,内核镜像必须拆分为头部(实模式)和主体(保护模式)。
- 引导协议:需要标准化的头部结构,使引导器能识别内核版本、加载地址和入口点。
关键区分:
vmlinux 是未压缩的 ELF 内核;vmlinuz 是压缩后的可引导内核;zImage 是小于 512 KB 的压缩内核(加载到低端内存);bzImage(Big zImage)是突破 512 KB 限制的大内核(加载到高端内存,1 MB 以上)。
2.2 三种镜像格式的对比
| 特性 | vmlinux | zImage | bzImage |
|---|---|---|---|
| 格式 | ELF(未压缩) | 压缩的扁平镜像 | 压缩的扁平镜像 |
| 最大体积 | 无限制 | 约 512 KB | 无限制(通常 < 4 MB) |
| 加载地址 | 任意(需引导器解析) | 0x10000(低端 64 KB) | 0x100000(1 MB 以上) |
| 解压缩阶段 | 无 | 实模式解压到 0x100000 | 保护模式解压到 0x100000 |
| 适用架构 | 调试、模拟器 | 旧 x86 系统(已废弃) | 现代 x86/x86_64 |
| 引导协议版本 | 无标准头部 | v1.x(setup header) | v2.x+(扩展 setup header) |
现状: 自 Linux 2.0 起,
bzImage 成为 x86 架构的默认内核格式。zImage 仅用于极少数内存受限的嵌入式系统。vmlinux 主要用于内核调试和符号解析。
2.3 压缩内核的生成过程:Makefile 与 objcopy
从 vmlinux 到 bzImage 的生成过程由内核顶层 Makefile 和 arch/x86/boot/Makefile 协同完成。核心步骤涉及 objcopy、gzip(或 LZ4、LZMA 等)和链接器。
2.3.1 生成流程总览
- 编译 vmlinux:
make vmlinux生成 ELF 格式的未压缩内核。 - 剥离符号:
objcopy -O binary vmlinux vmlinux.bin将 ELF 转换为纯二进制,去除节区头和符号表。 - 压缩:
gzip -9 vmlinux.bin生成vmlinux.bin.gz(或其他压缩算法)。 - 链接解压缩 stub:将压缩后的内核与
arch/x86/boot/compressed/中的解压缩代码(head_32.S或head_64.S、misc.c)链接,生成vmlinux.bin.gz的包装体。 - 生成 setup 头部:
arch/x86/boot/setup.elf包含实模式代码和引导协议头部。 - 合并为 bzImage:将 setup 头部与压缩内核包装体拼接,生成最终
arch/x86/boot/bzImage。
注意: 压缩算法可通过
CONFIG_KERNEL_GZIP、CONFIG_KERNEL_LZ4 等内核配置选项切换。不同算法影响解压缩速度和镜像体积,但生成流程结构一致。
2.3.2 关键 Makefile 片段解析
以下为 arch/x86/boot/compressed/Makefile 中的核心规则(简化版):
# 将 vmlinux 转换为纯二进制并压缩
$(obj)/vmlinux.bin.gz: $(obj)/vmlinux.bin FORCE
$(call if_changed,gzip)
# 将压缩后的内核与解压缩 stub 链接
$(obj)/vmlinux.bin.gz.o: $(obj)/vmlinux.bin.gz $(obj)/piggy.S FORCE
$(call if_changed,ld)
# 最终 bzImage 由 setup 头部和压缩内核组成
$(obj)/bzImage: $(obj)/setup.bin $(obj)/vmlinux.bin.gz.o
$(call if_changed,objcopy)
其中 piggy.S 是一个汇编文件,它通过 .incbin 指令将压缩后的内核二进制直接嵌入到解压缩 stub 的代码段中:
# arch/x86/boot/compressed/piggy.S
.section .data
.globl input_data, input_data_end
input_data:
.incbin "vmlinux.bin.gz"
input_data_end:
2.3.3 objcopy 的角色
objcopy 是 binutils 工具链中的核心工具,在内核构建中承担以下关键任务:
- 格式转换:
objcopy -O binary将 ELF 转换为纯二进制,丢弃所有元数据。 - 节区提取:
objcopy -j .text -j .data仅保留指定节区,用于生成 setup 头部。 - 符号剥离:
objcopy --strip-all移除调试符号,减小镜像体积。 - 字节序处理:在交叉编译时,
objcopy确保输出与目标架构的字节序一致。
2.4 镜像格式与地址重定位的关系
镜像格式直接决定了内核在解压缩阶段的地址重定位策略:
- zImage:加载到 0x10000,解压缩时需将自身从低端内存搬移到 0x100000,避免覆盖解压缩代码。
- bzImage:加载到 0x100000 以上,解压缩时直接在原地解压,或通过
relocs工具修正绝对地址引用。 - vmlinuz:通常指压缩后的内核,其内部包含一个自解压头部,该头部在启动时负责将内核解压到正确位置。
核心结论: 地址重定位的复杂度与镜像格式强相关。bzImage 通过将加载地址提高到 1 MB 以上,避免了实模式下的地址冲突,简化了解压缩阶段的搬移逻辑。这也是 bzImage 取代 zImage 成为主流格式的根本原因。
2.5 镜像格式演变与解压缩流程的 SVG 架构图
2.6 嵌入式系统中的镜像格式差异
在嵌入式 Linux 中(ARM、RISC-V、MIPS 等),镜像格式与 x86 有显著不同:
| 架构 | 常见镜像格式 | 引导器 | 解压缩方式 |
|---|---|---|---|
| x86 | bzImage | GRUB / Syslinux | 内核自带解压缩 stub |
| ARM | zImage (ARM 变体) | U-Boot | U-Boot 解压或内核自解压 |
| RISC-V | Image.gz + 头部 | OpenSBI + U-Boot | 引导器解压或内核自解压 |
| MIPS | vmlinuz (CFE 格式) | CFE / U-Boot | 引导器解压 |
嵌入式提示: 在 ARM 和 RISC-V 中,
zImage 的含义与 x86 不同——它通常指代一个包含解压缩头部的压缩内核,而非特指加载地址。嵌入式开发者应查阅具体架构的 Documentation/arm/Booting 或 Documentation/riscv/boot.rst 以确认格式细节。
2.7 本章小结
- vmlinux 是 ELF 格式的未压缩内核,用于调试;vmlinuz 是压缩后的可引导内核。
- zImage 受限于 512 KB 和低端加载地址,已基本被 bzImage 取代。
- bzImage 通过加载到 1 MB 以上,简化了地址重定位,成为 x86 标准格式。
- 生成过程依赖 objcopy 进行格式转换和节区提取,Makefile 协调编译、压缩和链接步骤。
- 嵌入式架构的镜像格式与 x86 不同,需根据引导器和架构规范选择。