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.内核镜像构建
Linux Kernel image
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 文件的内部布局。
Linux Kernel imageLinux Kernel image
内核编译:
Linux Kernel image
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 [email protected]
cmd_objcopy = $(OBJCOPY) $(OBJCOPYFLAGS) $(OBJCOPYFLAGS_$(@F)) $< [email protected]

  测试:在 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    [email protected]
      cmd_gzip = cat $(filter-out FORCE,$^) | gzip -n -f -9 > [email protected]

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.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 。

参考资料:
https://www.jianshu.com/p/d4e9b87c409d
https://biscuitos.github.io/blog/ARM-Kernel-Image/