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 生成流程总览

  1. 编译 vmlinux:make vmlinux 生成 ELF 格式的未压缩内核。
  2. 剥离符号:objcopy -O binary vmlinux vmlinux.bin 将 ELF 转换为纯二进制,去除节区头和符号表。
  3. 压缩:gzip -9 vmlinux.bin 生成 vmlinux.bin.gz(或其他压缩算法)。
  4. 链接解压缩 stub:将压缩后的内核与 arch/x86/boot/compressed/ 中的解压缩代码(head_32.S 或 head_64.S、misc.c)链接,生成 vmlinux.bin.gz 的包装体。
  5. 生成 setup 头部:arch/x86/boot/setup.elf 包含实模式代码和引导协议头部。
  6. 合并为 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 架构图

内核镜像格式演变与解压缩流程 内核镜像格式演变与解压缩流程 vmlinux (ELF) objcopy -O binary vmlinux.bin gzip / LZ4 / LZMA vmlinux.bin.gz 链接 piggy.S + stub zImage (加载到 0x10000) bzImage (加载到 0x100000) 实模式解压缩 搬移到 0x100000 保护模式解压缩 原地解压或 relocs 最终可引导内核镜像 说明: zImage 因体积限制(< 512 KB)和实模式地址冲突,已被 bzImage 取代。 bzImage 通过加载到 1 MB 以上,简化了解压缩阶段的地址重定位逻辑。

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 不同,需根据引导器和架构规范选择。