Linux内核分析之基础知识-3

韩乔落

第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
# Makefile (顶层)

# 内核版本
VERSION = 6
PATCHLEVEL = 12
SUBLEVEL = 38
EXTRAVERSION =
NAME = Baby Opossum Posse

# 默认目标
all: vmlinux

# 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
# arch/x86/boot/Makefile

# bzImage 构建规则
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 > $@

# setup.bin 是实模式代码
$(obj)/setup.bin: $(src)/setup.ld $(SETUP_OBJS) FORCE
$(call if_changed,ld)

# vmlinux.bin 是压缩内核
$(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
/* arch/x86/boot/compressed/Makefile */

# 支持的压缩算法
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
/* vmlinux 的关键段 */
struct vmlinux_layout {
/*
* .text: 内核代码段
* - 包含所有编译后的内核代码
* - 通常从 __START_KERNEL_map 开始
*/
char _text[SIZE]; /* 代码起始 */
char _etext[SIZE]; /* 代码结束 */

/*
* .rodata: 只读数据
* - 常量、字符串
* - 内核参数
*/
char __start_rodata[SIZE];
char __end_rodata[SIZE];

/*
* .data: 数据段
* - 已初始化的全局变量
* - 可读写
*/
char _sdata[SIZE];
char _edata[SIZE];

/*
* .bss: 未初始化数据段
* - 零初始化的全局变量
* - 不占用文件空间
*/
char __bss_start[SIZE];
char __bss_stop[SIZE];

/*
* .init: 初始化代码/数据
* - __init 函数
* - 启动后可释放
*/
char __init_begin[SIZE];
char __init_end[SIZE];

/*
* .data..percpu: per-CPU 数据
* - 每个 CPU 的独立数据
*/
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
/* boot_params.hdr 加载地址相关字段 */
struct setup_header {
/* ... */
u32 code32_start; /* 32/64位入口点 */
/* 默认: 0x100000 (1MB) */

u32 initrd_addr_max; /* initrd 最大地址 */
/* 0x7fffffff for legacy */
/* 0xffffffff for EFI */

u32 kernel_alignment; /* 内核对齐 */
/* CONFIG_PHYSICAL_ALIGN */
/* 最小 0x100000 (1MB) */
/* 典型值: 0x200000 (2MB) */

u8 relocatable_kernel; /* 可重定位内核 */
/* 1 = 支持任意地址加载 */

u8 min_alignment; /* 最小对齐 */
/* 通常与 kernel_alignment 相同 */

u32 pref_address; /* 首选加载地址 */
/* CONFIG_PHYSICAL_START */

u32 init_size; /* 初始化代码+数据大小 */

u32 handover_offset; /* EFI handover 偏移 */
/* ... */
};

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
# 1. 构建 vmlinux
$ make vmlinux
CC init/main.o
CC kernel/sched/core.o
...
LD vmlinux
OBJCOPY arch/x86/boot/compressed/vmlinux.bin

# 2. 压缩 vmlinux.bin
$ gzip arch/x86/boot/compressed/vmlinux.bin
→ vmlinux.bin.gz

# 3. 生成 piggy.S (包含压缩数据)
$ mkpiggy vmlinux.bin.gz > piggy.S

# 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"

# 4. 编译压缩内核
$ gcc -c piggy.S -o piggy.o
$ ld -o arch/x86/boot/compressed/vmlinux \
head_64.o misc.o piggy.o

# 5. 生成 bzImage
$ cat arch/x86/boot/setup.bin \
arch/x86/boot/compressed/vmlinux \
> arch/x86/boot/bzImage

# 6. 安装
$ 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 工具
$ 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
# arch/arm64/boot/Makefile

# 默认目标
all: Image.gz

# Image 是原始二进制
Image: vmlinux
$(Q)$(MAKE) $(build)=$(boot) $(boot)/$@
$(call if_changed,objcopy)

# Image.gz 是压缩版本
Image.gz: Image
$(call if_changed,gzip)

# vmlinux.bin 是 ELF 转二进制
$(obj)/vmlinux.bin: vmlinux FORCE
$(call if_changed,objcopy)

# vmlinux.bin.gz 是压缩的 vmlinux.bin
$(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 # x86_64
make Image # ARM64

# 安装
make modules_install
make install

# 清理
make clean # 清理生成文件
make mrproper # 深度清理 (保留配置)
make distclean # 完全清理 (包括配置)
  • Title: Linux内核分析之基础知识-3
  • Author: 韩乔落
  • Created at : 2026-01-08 02:46:52
  • Updated at : 2026-01-19 13:40:25
  • Link: https://jelasin.github.io/2026/01/08/Linux内核分析之基础知识-3/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments