Linux Kernel image
- 学习Linux Kernel image in different forms
1.内核镜像介绍
通常一个可启动的内核镜像 (bootable kernel image) 是经过算法压缩的,2.6.30 之后采用 LZMA 或者 BZIP2,vmlinuz 最后的 z 表示内核是压缩的,这也意味着内核中会有一段解压程序。
内核中包含了各种内核镜像的格式,如 vmlinux、zImage、bzImage、uImage 等,首先介绍内核中常见内核文件。
-
vmlinux是最原始,静态链接的,可执行的,不能bootable的,未压缩的内核镜像。vm代表Virtual Memory。Linux支持虚拟内存,因此得名vm。它是通过源码经过编译汇编, 链接而成的 ELF 文件。
-
zImage是 vmlinux 经过 gzip 压缩后的文件,适用于小内核。
-
bzImage 是 vmlinux 经过 gzip 压缩后的文件,适用于大内核,”bz” 表示 “big zImage”。
-
uImage 是 uboot 专用的镜像文件,它是在 zImage 之前加上一个长度为 0x40 的头信息,包括了该镜像文件的类型、加载位置、生成时间、大小等信息。
2.内核镜像构建
2.1.文件描述
1.vmlinux
vmlinux是最原始,未压缩的内核镜像。vm代表Virtual Memory。Linux支持虚拟内存,因此得名vm。它是通过源码经过编译汇编, 链接而成的 ELF 文件。因此这个 vmlinux 文件包含了 ELF 的属性,以及各种调试信息等,因此这个阶段的内核镜像 vmlinux 特别大,而且不能直接在 arm 上直接运行。
2.Image
由于 vmlinux 镜像体积巨大而且不能在 arm 上运行,因此需要使用 objcopy工具将不需要 的 section 从 vmlinux 里面剥离出来,最终在 arch/arm64/boot/目录下生成 Image 文件, 此时 Image 是可以在 arm 平台上运行的,该镜像文件也是未压缩。由于历史原因,当年制作出 Image 的大小正好比一个软盘大一点,为了让内核镜像能够装在一张软盘上,所以就将 Image 进行压缩, 生成 piggy.gz 或者 piggy_data.
3.piggy.gz/piggy_data
The file Image compressed with gzip.一开始只支持 gzip 压缩方法,所以将压缩之后的 Image 称为 piggy.gz,但随着内核的不断 发展,内核支持更多的压缩算法,因此把压缩之后的 Image 称为 piggy_data.
4.piggy.o
之前说过 Image 可以在 arm 上运行,当不能直接运行,因为 Image 运行前需要一些已知 初始化环境,这就需要特定功能的代码实现这些功能,这里称这些代码为 bootstrap。 于是内核在 arch/arm/boot/compressed/ 目录下增加了 bootstrap 功能的代码。和制作 vmlinux 一样,需要将这个目录下的源文件编译汇编成目标文件,然后再链接成一个文件。 为了构造这个,内核将 piggy_data 直接塞到了一个汇编文件 piggy.S 中,然后这个文件 经过汇编之后,就生成了 piggy.o。
2.2.vmlinux 构建过程
vmlinux 文件是 Kbuild 编译系统将源码经过编译链接所获得的目标文件,所以它是一个 ELF 文件,因此 vmlinux 文件包含了各种调试信息和各种有用的 section。vmlinux 文件的链接过程由 arch/$(ARCH)/kernel/vmlinux.lds.S 链接脚本决定,可以通过该文件知道 vmlinux 文件的内部布局。
let’s look at how vmlinux is produced on x86-64:
内核编译:
2.3.Image 构建
Image文件是 vmlinux 使用 objcopy 工具转换后得到的二进制文件。由于 vmlinux 不能直接在 arm 上运行,需要丢弃一些与运行无关的 section,所以使用 objcopy 工具完成这个任务。Image 文件相比 vmlinux,除了格式不同之外,vmlinux 的调试信息和 许多注释以及与运行无关的 section 都被移除,所以体积会变小很多。构建过程如下所示:
arch/arm/boot/Makefile :
$(obj)/Image: vmlinux FORCE
$(call if_changed,objcopy)
Image 就是通过 vmlinux objcopy 获得,这里 objcopy 对应的命令是 位于 scripts/Makefile.lib 文件中获得,定义如下:
quiet_cmd_objcopy = OBJCOPY $@
cmd_objcopy = $(OBJCOPY) $(OBJCOPYFLAGS) $(OBJCOPYFLAGS_$(@F)) $< $@
测试:在 Image 生成过程中添加打印消息,以此查看整个 object 过程, 添加调试代码如下:
$(obj)/Image: vmlinux FORCE
$(warning "OBJCOPYFLAGS: $(OBJCOPYFLAGS)")
$(warning "OBJCOPYFLAGS_$(@F): $(OBJCOPYFLAGS_$(@F))")
$(call if_changed,objcopy)
编译内核时生成如下信息:
LD vmlinux
SORTEX vmlinux
SYSMAP System.map
arch/arm/boot/Makefile:61: "OBJCOPYFLAGS: -O binary -R .comment -S"
arch/arm/boot/Makefile:61: "OBJCOPYFLAGS_Image: "
OBJCOPY arch/arm/boot/Image
Building modules, stage 2.
MODPOST 6 modules
Kernel: arch/arm/boot/Image is ready
vmlinux 使用 objcopy变成 Image 时,使用的参数 是 “-O binary -R .comment -S”,这个参数的意思是:
- -O binary 表示生成二进制文件
- -R .comment 表示移除 .comment section
- -S 表示移除所有的标志以及重定位信息
2.4.piggy_data构建
piggy_data是 Image 经过压缩之后得到的压缩文件,具体如下:
//arch/arm/boot/compressed/Makefile:
$(obj)/piggy_data: $(obj)/../Image FORCE
$(call if_changed,$(compress-y))
通过上面的内容可知,内核采用的压缩方法由 compress-y 变量决定,其定义如下:
//arch/arm/boot/compressed/Makefile :
compress-$(CONFIG_KERNEL_GZIP) = gzip
compress-$(CONFIG_KERNEL_LZO) = lzo
compress-$(CONFIG_KERNEL_LZMA) = lzma
compress-$(CONFIG_KERNEL_XZ) = xzkern
compress-$(CONFIG_KERNEL_LZ4) = lz4
因此内核支持 gzip,lzo,lzma,xzkern, 和 lz4 的压缩方法,具体使用哪种,因此开发者可以在 命令执行处添加调试代码如下:
$(obj)/piggy_data: $(obj)/../Image FORCE
$(wraning "compress-y: $(compress-y)")
$(call if_changed,$(compress-y))
Image 常采用了gzip 方法,在 scripts/Makefile.lib 文件中获得具体的 gzip 过程,如下:
quiet_cmd_gzip = GZIP $@
cmd_gzip = cat $(filter-out FORCE,$^) | gzip -n -f -9 > $@
gizp 的参数含义如下:
- -n 压缩文件时,不保存原来文件名称以及时间戳
- -f 强制压缩文件。不理会文件名称或硬链接是否存在以及文件是否为符号链接
- -9 用 9 调整压缩的速度,-1 或 --fast 表示最快压缩方法 (低压缩比), -9 或者 --best 表示最慢的压缩方法 (高压缩比)
2.5.Bootstrap ELF kernel (vmlinux) 构建过程
只有纯粹的内核是无法启动的,所以需要在内核的头部加入一些用于 bootstrap loader 功能的代码。 Kbuild 编译系统在 arch/arm/boot/compressed/ 目录下,将 head.S, misc.S, compressed.S 等多个汇编文件汇编成多个可链接的 ELF 目标文件,以此作为内核的 bootstrap loader。在这个步骤,Kbuid 编译系统将这些可链接的目标文件与 piggy.o 文件按链接脚本的内容进行链接,制作出一个带 bootstrap loader 的内核ELF 文件。对于过程要参考 arch/arm/boot/compressed/ 目录下的 Makefile 和 vmlinux.lds.S 文件。 首先通过分析 Makefile 知道链接的文件,具体源码如下:
$(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.o \
$(addprefix $(obj)/, $(OBJS)) $(lib1funcs) $(ashldi3) \
$(bswapsdi2) $(efi-obj-y) FORCE
@$(check_for_multiple_zreladdr)
$(call if_changed,ld)
@$(check_for_bad_syms)
2.5.1.vmlinux.lds 理解
vmlinux.lds总共有两个,分别是:
- arch/arm/kernel/vmlinux.lds(用于生成未压缩的内核image)
- arch/arm/boot/compressed/vmlinux.lds(用于生成经过压缩的内核image)
这两个lds 文件中的链接地址区别:
arch/arm/kernel/vmlinux.lds:
421 OUTPUT_ARCH(arm)
422 ENTRY(stext)
423 jiffies = jiffies_64;
424 SECTIONS
425 {
426 /*
427 * XXX: The linker does not define how output sections are
428 * assigned to input sections when there are multiple statements
429 * matching the same input section name. There is no documented
430 * order of matching.
431 *
432 * unwind exit sections must be discarded before the rest of the
433 * unwind sections get included.
434 */
435 /DISCARD/ : {
436 *(.ARM.exidx.exit.text)
437 *(.ARM.extab.exit.text)
438
439
440
441
442 *(.exitcall.exit)
443 *(.alt.smp.init)
444 *(.discard)
445 *(.discard.*)
446 }
447 . = 0xC0000000 + 0x00008000;
448 .head.text : {
449 _text = .;
450 *(.head.text)
451 }
arch/arm/boot/compressed/vmlinux.lds:
10 OUTPUT_ARCH(arm)
11 ENTRY(_start)
12 SECTIONS
13 {
14 /DISCARD/ : {
15 *(.ARM.exidx*)
16 *(.ARM.extab*)
17 /*
18 * Discard any r/w data - this produces a link error if we have any,
19 * which is required for PIC decompression. Local data generates
20 * GOTOFF relocations, which prevents it being relocated independently
21 * of the text/got segments.
22 */
23 *(.data)
24 }
25
26 . = 0;
27 _text = .;
压缩内核的链接地址是从0开始的,未压缩的内核链接地址是从0xC0000000+0x00008000开始的。
首先uboot使用到的内核是压缩过的内核也就是uimage,此时内核链接到的是0地址,入口是_start。而arch/arm/boot/compressed/vmlinux.lds中有如下定义:
.text : {
30 _start = .;
31 *(.start)
32 *(.text)
33 *(.text.*)
34 *(.fixup)
35 *(.gnu.warning)
36 *(.glue_7t)
37 *(.glue_7)
38 }
所以压缩内核的入口就是链接地址是从0开始的。压缩内核来源于压缩的vmlinux,而压缩的vmlinux是通过head.o, misc.o piggy.o加工而来的。这个压缩的vmlinux连接顺序可以从arch/arm/boot/compressed/Makefile中得到如下信息:
HEAD = head.o
OBJS += misc.o decompress.o
vmlinux: $(obj)/vmlinux.lds ( o b j ) / (obj)/ (obj)/(HEAD) ( o b j ) / p i g g y . (obj)/piggy. (obj)/piggy.(suffix_y).o
所以其实压缩内核的入口就是arch/arm/boot/compressed/head.s文件,并且这个文件应该也就是uboot跳转到内核之后的总入口,这部分代码是位置无关,所以链接到0地址也没关系。
内核的入口的这段代码应该还包含搬运内核到高地址空间的代码,搬运的目的地址应该就是arch/arm/kernel/vmlinux.lds文件中的连接地址0xC0000000+0x00008000,这也应该改是内核最终的运行地址。
2.6 zImage构建
zImage 是通过带 bootstrap loader 的内核 ELF 文件经过 objcopy 命令之后制作生成 的二进制文件,用于在 arm 上直接运行,其生成过程可以查看
arch/arm/boot/Makefile:
$(obj)/zImage: $(obj)/compressed/vmlinux FORCE
$(call if_changed,objcopy)
同原始 vmlinux 转换为 Image 过程一致。制作完 zImage 之后, 可以将 zImage 在 arm 上运行。
最终在内存SDRAM中的内核镜像是经过压缩的,只是在运行时再将其解压。所以编译时会先使用gzip将镜像文件image进行压缩,再将压缩后的镜像文件和源码中的两个文件(如下所示)一起链接生成压缩后的镜像文件compress/vmlinux,注意,这两个源码文件是解压程序,用于将内存SDRAM中的压缩镜像zImage进行解压,然后再执行kernel 的第一阶段启动代码arch/arm/kernel/head.S。简而言之,在内存中运行内核时,kernel先自身解压,再执行第一阶段启动代码。
- arch/arm/boot/compressed/head.S
- arch/arm/boot/compressed/misc.c
3.vmlinuz
vmlinuz是可引导的、压缩的,能bootable的内核。vmlinux是用来生成vmlinuz的中间步骤。vmlinuz是Linux kernel文件的历史名字,它实际上就是zImage或bzImage。
vmlinuz 的建立有两种方式:
-
编译内核时执行"make zImage",zImage适用于小内核的情况,它的存在是为了向后的兼容性。
-
内核编译时通过命令make bzImage创建。
zImage、bzImage 中均包含一个微型的 gzip 用于解压缩内核并引导,两者的不同之处在于: zImage 解压缩内核到低端内存 (第一个640K),bzImage 解压缩内核到高端内存 (1M以上)。也就是,它们之间最大的差别是对于内核体积大小的限制。
由于 zImage 内核需要放在实模式 1MB 的内存之内,所以其体积受到了限制,目前采用的内核格式大多采用的是 bzImage ,这种格式没有 1MB 内存限制。arm 中常用的是 zImage,而 x86 中常用的是 bzImage 。
4.Linux 编译流程图
参考资料:
https://www.jianshu.com/p/d4e9b87c409d
https://jin-yang.github.io/post/kernel-compile.html
https://biscuitos.github.io/blog/ARM-Kernel-Image/
更多推荐
所有评论(0)