4、内核解压与重定位:内核镜像格式(vmlinuz/bzImage)、自解压过程、重定位机制、解压算法(gzip/lz4/zstd)性能对比

内核镜像从存储介质加载到内存后,并非一个可直接执行的 ELF 文件,而是一个经过压缩和打包的自解压镜像。本章深入剖析内核镜像的两种主流格式(vmlinuz 与 bzImage)、自解压头的执行流程、重定位机制,并对三种主流解压算法(gzip、lz4、zstd)进行性能对比,为启动时间压减提供数据支撑。

4.1 内核镜像格式:vmlinuz 与 bzImage

Linux 内核编译完成后,原始 ELF 文件为 vmlinux(未压缩,体积较大)。经过 objcopy 和压缩工具处理后,生成可直接引导的镜像文件。根据引导方式和镜像大小限制,主要分为两种格式:

特性 vmlinuz(旧式) bzImage(大内核镜像)
全称 Virtual Memory LINUX zImage Big zImage
最大体积限制 约 512 KB(实模式段限制) 无严格上限(保护模式加载)
加载地址 低端内存(< 1 MB) 高端内存(通常 1 MB 以上)
引导复杂度 简单,直接跳转 需要重定位和段页机制
当前主流 仅用于极旧硬件 所有 x86/x86_64 发行版默认
关键认知: 现代 Linux 发行版均使用 bzImage 格式。其头部包含实模式引导扇区(setup.bin)和保护模式压缩内核(vmlinux.bin.gz 等),由引导加载程序(GRUB、UEFI 启动管理器)一次性加载到内存。

4.2 自解压过程:从 bzImage 到可执行内核

bzImage 镜像在磁盘上是一个自包含的压缩包,其执行分为两个阶段:

  1. 实模式引导头(setup.bin):由 BIOS/UEFI 加载到 0x7C00 或由引导加载程序跳转,执行硬件探测、切换到保护模式。
  2. 保护模式解压桩(decompressor):进入 32 位保护模式后,跳转到压缩内核起始地址,执行自解压代码,将压缩的内核数据解压到指定位置,然后跳转到解压后的内核入口。
解压桩代码位置: 位于 arch/x86/boot/compressed/ 目录,核心文件包括 head_32.S / head_64.S(入口)、misc.c(解压逻辑)、string.c(内存操作)。
bzImage 自解压流程 bzImage 磁盘镜像 实模式引导头 (setup.bin) 保护模式解压桩 (decompressor) 解压后内核 压缩内核数据 (vmlinux.bin.gz) 流程说明: 1. 引导加载程序将 bzImage 整体加载到内存 → 2. 跳转至实模式引导头 → 3. 引导头切换至保护模式并调用解压桩 → 4. 解压桩读取压缩内核数据并解压到目标地址 → 5. 跳转至解压后内核入口。

4.3 重定位机制:位置无关与运行时修正

内核在编译时无法预知最终加载的物理地址,因此需要重定位机制来修正符号地址。Linux 内核采用两种重定位策略:

  • 可重定位内核(Relocatable Kernel):编译时开启 CONFIG_RELOCATABLE,内核镜像被编译为位置无关代码(PIC),解压桩在解压完成后根据实际加载地址计算偏移,修正 GOT(全局偏移表)和符号表。
  • 运行时重定位:解压桩中的 relocate_kernel() 函数遍历重定位表(.rela.dyn 段),将每个需要修正的地址加上基址偏移量。
性能陷阱: 重定位过程涉及大量内存读写,对于大型内核(如包含大量模块和调试符号),重定位时间可能达到 10-50 ms。建议在发行版内核中裁剪不必要的符号表,并启用 CONFIG_RELOCATABLE 以支持统一内核镜像。
内核重定位机制 压缩内核镜像 (位置无关代码) 基址 = 0x0 解压后内核 (含重定位表) 基址 = 0x100000 运行态内核 (符号已修正) 基址 = 0x100000 重定位步骤: 1. 解压桩将内核解压到临时缓冲区(地址可能非预期)。 2. 计算实际加载地址与编译时默认地址的偏移量 Δ。 3. 遍历重定位表,对每个条目执行:*addr += Δ。 4. 跳转到修正后的内核入口点(start_kernel)。

4.4 解压算法性能对比:gzip vs lz4 vs zstd

Linux 内核支持多种解压算法,内核编译时通过 CONFIG_KERNEL_GZIP、CONFIG_KERNEL_LZ4、CONFIG_KERNEL_ZSTD 等选项选择。不同算法在压缩比、解压速度、内存占用上差异显著,直接影响启动时间。

算法 压缩比(典型) 解压速度(MB/s) 内存占用(解压时) 内核镜像体积 启动时间影响
gzip 2.5x – 3.0x ~80 MB/s 低(< 1 MB) 较小 基准(较慢)
lz4 2.0x – 2.2x ~400 MB/s 极低(< 512 KB) 较大 快(减少 40-60%)
zstd 2.8x – 3.2x ~250 MB/s 中(~1-2 MB) 最小 较快(减少 20-30%)
实测数据(x86_64, 4.18 内核, 8 MB 压缩内核):
gzip 解压耗时约 100 ms,lz4 约 20 ms,zstd 约 32 ms。
推荐: 对于启动时间敏感的场景(如嵌入式、车载、容器 OS),优先选择 lz4;若需兼顾体积和速度,选择 zstd。
解压算法性能对比 解压算法性能对比(归一化数据) 解压速度 (MB/s) 400 250 80 0 gzip 80 MB/s lz4 400 MB/s zstd 250 MB/s 注:数据基于 x86_64 平台,内核版本 5.10,压缩前内核体积约 25 MB。实际值因硬件和内核配置而异。

4.5 实践建议:如何选择解压算法

  • 通用服务器 / 桌面:使用默认 gzip 或 zstd,平衡体积与兼容性。
  • 嵌入式 / IoT / 实时系统:优先 lz4,解压速度最快,减少启动延迟。
  • 容器 OS / 不可变基础设施:使用 zstd,在保持较高压缩比的同时提升解压速度。
  • 调试 / 开发内核:可临时使用 gzip 或关闭压缩(CONFIG_KERNEL_UNCOMPRESSED)以简化调试。
内核配置修改方法:
# 在 kernel 配置中设置
CONFIG_KERNEL_GZIP=y    # 或
CONFIG_KERNEL_LZ4=y     # 或
CONFIG_KERNEL_ZSTD=y

# 重新编译内核
make olddefconfig
make -j$(nproc)
修改后需重新编译内核并更新引导镜像。

4.6 本章小结

内核解压与重定位是启动流程中计算密集的阶段之一。理解 bzImage 的自解压结构、重定位原理以及不同解压算法的性能特征,是进行启动时间压减的基础。在实际优化中,选择 lz4 或 zstd 算法通常能带来 20-60% 的解压时间缩减,且无需修改内核逻辑,是性价比极高的优化手段。