第3章:x86_64 内核映像格式
vmlinux、bzImage 和 vmlinuz 的秘密
本章概述 Linux 内核有多种存在形式,本章详细讲解这些格式的区别、构建过程以及内部结构。
内核映像类型对比
名称
描述
格式
用途
vmlinux
未压缩的内核
ELF
调试、符号表
bzImage
大 zImage
自定义
实际启动
vmlinuz
bzImage 的链接
同 bzImage
传统命名
zImage
小内核映像
自定义
已废弃
uImage
U-Boot 镜像
U-Boot
嵌入式
Image
ARM64 原始映像
二进制
ARM64
3.1 构建流程详解 3.1.1 完整构建过程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 ┌─────────────────────────────────────────────────────────────────┐ │ 内核构建流程 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 源文件 (*.c, *.S) │ │ │ │ │ ├─ 编译 (gcc/clang) │ │ ↓ │ │ 目标文件 (*.o) │ │ │ │ │ ├─ 链接 (ld) │ │ ↓ │ │ vmlinux (ELF 可执行文件) │ │ │ │ │ ├─ objcopy (提取二进制) │ │ ↓ │ │ arch/x86/boot/compressed/vmlinux.bin │ │ │ │ │ ├─ 压缩 (gzip/xz/etc) │ │ ↓ │ │ arch/x86/boot/compressed/vmlinux.bin.gz │ │ │ │ │ ├─ mkpiggy (生成 piggy.S) │ │ ├─ 重新编译压缩内核 │ │ ↓ │ │ arch/x86/boot/compressed/vmlinux │ │ │ │ │ ├─ cat setup + compressed vmlinux │ │ ↓ │ │ arch/x86/boot/bzImage │ │ │ │ │ └─ 安装 (make install) │ │ ↓ │ │ /boot/vmlinuz-6.12.38 │ │ │ └─────────────────────────────────────────────────────────────────┘
3.1.2 Makefile 分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 VERSION = 6 PATCHLEVEL = 12 SUBLEVEL = 38 EXTRAVERSION = NAME = Baby Opossum Posse all: vmlinux vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o FORCE $(Q) $(MAKE) -f $(srctree) /scripts/Makefile.vmlinux \ quiet_cmd_vmlinux__ = VMLINUX $@ Image: vmlinux $(Q) $(MAKE) $(build) =arch/x86/boot $@ bzImage: vmlinux $(Q) $(MAKE) $(build) =arch/x86/boot $@
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 bzImage: asflags-y += $(BOOT_HEAP_SIZE) bzImage: $(obj) /setup.bin $(obj) /vmlinux.bin $(obj) /tools/build FORCE $(call if_changed,ld) $(obj) /tools/build $(obj) /setup.bin $(obj) /vmlinux.bin > $@ $(obj) /setup.bin: $(src) /setup.ld $(SETUP_OBJS) FORCE $(call if_changed,ld) $(obj) /vmlinux.bin: $(obj) /compressed/vmlinux FORCE $(call if_changed,objcopy) $(obj) /compressed/vmlinux: $(obj) /compressed/vmlinux.lds \ $(obj) /compressed/head_64.o \ $(obj) /compressed/misc.o \ $(obj) /compressed/string.o \ $(obj) /compressed/vmlinux.bin.gz FORCE $(call if_changed,ld) $(obj) /compressed/vmlinux.bin.gz: $(vmlinux) FORCE $(call if_changed,gzip)
3.1.3 压缩算法选择 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 # 支持的压缩算法 ifdef CONFIG_KERNEL_GZIP OBJS += ../../lib/decompress_inflate.o endif ifdef CONFIG_KERNEL_BZIP2 OBJS += ../../lib/decompress_bunzip2.o endif ifdef CONFIG_KERNEL_LZMA OBJS += ../../lib/decompress_unlzma.o endif ifdef CONFIG_KERNEL_XZ OBJS += ../../lib/decompress_unxz.o # XZ 是默认推荐 KBUILD_CFLAGS += -DCONFIG_KERNEL_XZ endif ifdef CONFIG_KERNEL_LZO OBJS += ../../lib/decompress_unlzo.o endif ifdef CONFIG_KERNEL_LZ4 OBJS += ../../lib/decompress_unlz4.o endif ifdef CONFIG_KERNEL_ZSTD OBJS += ../../lib/decompress_unzstd.o # ZSTD 是最新推荐 KBUILD_CFLAGS += -DCONFIG_KERNEL_ZSTD endif
压缩算法对比:
算法
压缩比
解压速度
内核支持
gzip
中等
快
是
bzip2
高
慢
是
lzma
很高
很慢
是
xz
很高
慢
是 (推荐)
lzo
低
很快
是
lz4
低
极快
是
zstd
高
快
是 (最新推荐)
3.2 vmlinux 详解 3.2.1 vmlinux 结构 vmlinux 是一个完整的 ELF 可执行文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 $ file vmlinux vmlinux: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped $ ls -lh vmlinux -rwxr-xr-x 1 root root 62M Jan 1 00:00 vmlinux $ readelf -h vmlinux ELF Header: Magic: 7f 45 4c 46 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Type: EXEC (Executable file) Machine: Advanced Micro Devices X86-64 Entry point address: 0x1000000 Start of program headers: 64 (bytes into file) Start of section headers: 0 (bytes into file)
3.2.2 vmlinux 段结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 struct vmlinux_layout { char _text[SIZE]; char _etext[SIZE]; char __start_rodata[SIZE]; char __end_rodata[SIZE]; char _sdata[SIZE]; char _edata[SIZE]; char __bss_start[SIZE]; char __bss_stop[SIZE]; char __init_begin[SIZE]; char __init_end[SIZE]; char __per_cpu_start[SIZE]; char __per_cpu_end[SIZE]; };
3.2.3 vmlinux 链接脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 /* arch/x86/kernel/vmlinux.lds.S */ ENTRY(startup_64) SECTIONS { /* 内核虚拟地址起始 */ . = __START_KERNEL_map; /* 代码段 */ .text : AT(ADDR(.text) - LOAD_OFFSET) { _text = .; /* 异常向量 */ IDTTEXT /* 修复代码 */ FIXUP_TEXT /* 核心代码 */ *(.text .text.*) *(.ref.text) } /* 只读数据 */ .rodata : AT(ADDR(.rodata) - LOAD_OFFSET) { __start_rodata = .; *(.rodata .rodata.*) /* 内核参数 */ __param : { *(__param) } __stop___param = .; __end_rodata = .; } /* 数据段 */ .data : AT(ADDR(.data) - LOAD_OFFSET) { _data = .; *(.data .data.*) } /* BSS 段 */ .bss : AT(ADDR(.bss) - LOAD_OFFSET) { __bss_start = .; *(.bss .bss.*) *(COMMON) __bss_stop = .; } /* 初始化段 */ .init.data : AT(ADDR(.init.data) - LOAD_OFFSET) { __init_begin = .; *(.init.data) *(.cpuinit.data) } .init.text : AT(ADDR(.init.text) - LOAD_OFFSET) { *(.init.text) *(.cpuinit.text) } __init_end = .; /* per-CPU 数据 */ .data..percpu : AT(ADDR(.data..percpu) - LOAD_OFFSET) { __per_cpu_load = .; *(.data..percpu..first) *(.data..percpu..page_aligned) *(.data..percpu) *(.data..percpu..shared_aligned) __per_cpu_end = .; } }
3.3 bzImage 详解 3.3.1 bzImage 结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 bzImage 文件结构: ┌─────────────────────────────────────────────────────┐ │ offset 0x0000 │ │ ┌───────────────────────────────────────────────┐ │ │ │ setup 代码 (实模式) │ │ │ │ - header.S │ │ │ │ - main.c │ │ │ │ - 视频初始化等 │ │ │ │ 大小: setup_sects * 512 字节 │ │ │ └───────────────────────────────────────────────┘ │ ├─────────────────────────────────────────────────────┤ │ offset: setup_size (通常是 0x2000) │ │ ┌───────────────────────────────────────────────┐ │ │ │ compressed/vmlinux (压缩内核) │ │ │ │ ┌─────────────────────────────────────────┐ │ │ │ │ │ head_64.S (解压入口) │ │ │ │ │ │ - startup_32 │ │ │ │ │ │ - startup_64 │ │ │ │ │ ├─────────────────────────────────────────┤ │ │ │ │ │ misc.c (解压代码) │ │ │ │ │ │ - decompress_kernel │ │ │ │ │ ├─────────────────────────────────────────┤ │ │ │ │ │ pgtable_64.c (早期页表) │ │ │ │ │ ├─────────────────────────────────────────┤ │ │ │ │ │ vmlinux.bin.gz (压缩的 vmlinux.bin) │ │ │ │ │ │ - piggy.S (通过 incbin 包含) │ │ │ │ │ └─────────────────────────────────────────┘ │ │ │ └───────────────────────────────────────────────┘ │ │ │ │ 注意: "bz" 中的 "b" 代表 "big", 不是 "bzip2" │ │ 最初的 "zImage" 由于大小限制被 "bzImage" 替代 │ └─────────────────────────────────────────────────────┘
3.3.2 bzImage 加载地址 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 struct setup_header { u32 code32_start; u32 initrd_addr_max; u32 kernel_alignment; u8 relocatable_kernel; u8 min_alignment; u32 pref_address; u32 init_size; u32 handover_offset; };
3.3.3 bzImage 生成过程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 $ make vmlinux CC init/main.o CC kernel/sched/core.o ... LD vmlinux OBJCOPY arch /x86/boot/compressed/vmlinux.bin $ gzip arch /x86/boot/compressed/vmlinux.bin → vmlinux.bin.gz $ mkpiggy vmlinux.bin.gz > piggy.S .section ".rodata..compressed" ,"a" ,@progbits .globl z_input_len z_input_len = 6241234 .globl z_output_len z_output_len = 0x2000000 .globl z_extract_offset z_extract_offset = 0 .globl input_data input_data: .incbin "arch/x86/boot/compressed/vmlinux.bin.gz" $ gcc -c piggy.S -o piggy.o $ ld -o arch /x86/boot/compressed/vmlinux \ head_64.o misc.o piggy.o $ cat arch /x86/boot/setup.bin \ arch /x86/boot/compressed/vmlinux \ > arch /x86/boot/bzImage $ make install sh /usr/src/linux-6.12/arch/x86/boot/install.sh 6.12.38 \ arch /x86/boot/bzImage \ System.map "/boot"
3.4 内核符号和调试 3.4.1 System.map 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 System.map 包含内核符号地址表: $ head System.map 0000000000001000 T _text 0000000000001000 T startup_64 0000000000001000 T _stext 0000000000001040 T secondary_startup_64 ... ffffffff81000000 T _text ffffffff81000000 T startup_64 ffffffff81000000 T _stext ... 格式: <地址> <类型> <符号> 类型: T = Text (代码段) D = Data (已初始化数据) B = BSS (未初始化数据) U = Undefined (外部符号)
3.4.2 使用符号表调试 1 2 3 4 5 6 7 8 9 10 11 12 $ addr2line -e vmlinux ffffffff81000123 arch /x86/kernel/head64.c:145$ nm vmlinux | grep start_kernel ffffffff8106c180 T start_kernel $ crash vmlinux /proc/kcore crash> sym ffffffff8106c180 ffffffff8106c180 (t) start_kernel
3.5 内核配置和大小优化 3.5.1 大小分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $ size vmlinux text data bss dec hex filename 12345678 2345678 1234567 15925723 f2f34b vmlinux $ bloat-o-meter vmlinux vmlinux.old Total: Before: 15925723 After: 15892345 Diff: -33378 (-0.21%) $ ls -lh arch /x86/boot/bzImage -rw-r--r-- 1 root root 8.5M Jan 1 00:00 bzImage $ du -h vmlinux 62M vmlinux
3.5.2 减小内核大小的配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 # 最小化内核配置的选项 # 禁用调试 CONFIG_DEBUG_KERNEL=n CONFIG_DEBUG_INFO=n # 禁用不需要的驱动 # 只保留必要的硬件支持 # 禁用不需要的文件系统 CONFIG_EXT4_FS=y # 保留必要的 CONFIG_NTFS_FS=n # 禁用不需要的 # 使用模块化 CONFIG_MODULES=y # 将不常用的功能编译为模块 # 使用 XZ 或 ZSTD 压缩 CONFIG_KERNEL_XZ=y # 或 CONFIG_KERNEL_ZSTD=y # 禁用不需要的内核功能 CONFIG_MODULES=n # 如果不需要模块支持 CONFIG_KALLSYMS=n # 如果不需要符号表 # 使用 strip $ strip --strip-debug vmlinux
3.6 ARM64 Image 格式 3.6.1 ARM64 Image 结构 ARM64 使用更简单的 Image 格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 ARM64 Image 格式: ┌─────────────────────────────────────────────────────┐ │ offset 0x0000 │ │ ┌───────────────────────────────────────────────┐ │ │ │ 内核头部 (64 字节) │ │ │ │ 0x00: b primary_entry │ │ │ │ 0x04: 0x00 (加载偏移) │ │ │ │ 0x08: _kernel_size_le (大小) │ │ │ │ 0x10: _kernel_flags_le (标志) │ │ │ │ 0x14: 0x00 (保留) │ │ │ │ 0x18: 0x00 (保留) │ │ │ │ 0x1C: 0x00 (保留) │ │ │ │ 0x20: "ARM\x64" (魔数) │ │ │ │ 0x24: PE 头偏移 │ │ │ └───────────────────────────────────────────────┘ │ ├─────────────────────────────────────────────────────┤ │ offset 0x0040 │ │ ┌───────────────────────────────────────────────┐ │ │ │ 内核代码 (Image/vmlinux.bin) │ │ │ │ - 不压缩 │ │ │ │ - 可以直接加载执行 │ │ │ └───────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────┘ 注意: ARM64 不使用 bzImage 格式 Image 就是 vmlinux 的二进制形式
3.6.2 ARM64 内核构建 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 all: Image.gz Image: vmlinux $(Q) $(MAKE) $(build) =$(boot) $(boot) /$@ $(call if_changed,objcopy) Image.gz: Image $(call if_changed,gzip) $(obj) /vmlinux.bin: vmlinux FORCE $(call if_changed,objcopy) $(obj) /vmlinux.bin.gz: $(obj) /vmlinux.bin FORCE $(call if_changed,gzip)
3.6.3 ARM64 EFI 头部 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 /* arch/arm64/kernel/efi-header.S */ .set offset, 0 /* * 内核头部 (在 PE 头之前) */ .globl efi_header_end efi_header: .byte 0x4d // "MZ" .byte 0x5a .space 0x3a // 填充到 0x3c /* * PE 偏移 (在 0x3c 处) */ .long pe_header - efi_header /* * PE 签名 (在 pe_header 处) */ pe_header: .long PE_MAGIC // "PE\0\0" coff_header: .set .Lcoff_offset, . - efi_header .short IMAGE_FILE_MACHINE_ARM64 // 0xAA64 .short section_count .long 0 .long 0 .long 0 .short optional_header_size .short 0x0206 // Characteristics /* * PE 可选头 */ optional_header: .set .Lopthdr_offset, . - efi_header .short PE_OPT_MAGIC_PE32PLUS .byte 0x02 .byte 0x14 .long 0 .long __initdata_begin - .head.text .long __initdata_end - __initdata_begin .long 0 .long 0x1000 .long 0x200 // Subsystem: EFI application .long 0 .long 1 .long 0 .long (0x200 + .Lpe_header - efi_header) & ~0x1ff .long (0x200 + .Lpe_header - efi_header) & ~0x1ff .quad 0 .quad 0 .quad 0 .quad 0 .long 0 .long 6 /* * 数据目录 */ .quad 0 .quad 0 .quad 0 .quad 0 .quad 0 .quad 0 /* * 节表 */ section_table: /* * .text 段 */ .ascii ".text\0\0\0" .long __initdata_end - .head.text .long 0 .long 0 .long 0 .long 0 .long 0 .long 0 .long IMAGE_SCN_CNT_CODE | \ IMAGE_SCN_MEM_READ | \ IMAGE_SCN_MEM_EXECUTE .set section_count, (. - section_table) / 40 /* * PE 头结束 */ .Lpe_header:
3.7 本章小结 内核映像总结
格式
平台
压缩
ELF
大小约
用途
vmlinux
x86/ARM64
否
是
~60MB
调试、符号
bzImage
x86
是
否
~8MB
实际启动
Image
ARM64
否
否
~20MB
实际启动
Image.gz
ARM64
是
否
~8MB
可选
构建命令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 make -j$(nproc ) make bzImage make Image make modules_install make install make clean make mrproper make distclean