Linux内核构建系统之八

yihect | 11 元月, 2011 12:59


在编译 vmlinux 以及 modules 的过程中,构建系统已经产生了很多输出:vmlinux elf 映像、System.map符号表文件、各种内部模块等等。这些东西是编译支持任何架构的Linux内核过程中都要产生的,但是如果要想真正去使用Linux内核,光用前面产生的 vmlinux 是不行的。因为前面产生的 vmlinux 是一个ELF映像,不能拿来直接执行。这个不像我们平常所开发出来的上层应用程序,比方一个hello程序,代码编写完毕后,编译成ELF可执行文件,然后直接放到Linux操作系统下面去执行(具体执行之前,需先由放在内核中的加载器加载)。为什么不能直接拿 vmlinux ELF映像来使用?就是因为不存在另外一个已经运行好的Linux环境(加载器)来帮助我们加载。我们真正需要的是能放到内存里面直接执行的二进制指令序列,也就是不带任何格式的纯粹二进制文件。通常情况下,在使用这个二进制内核时,它总是会被压缩存储的。业内有人将前面过程中产生的 vmlinux 称为 the kernel proper,为了后面叙述的方便,我们也使用这样的称呼。

虽然存在有很少的架构和bootload能直接引导启动这个二进制内核文件,但是更通常的情况是:我们需要在这一二进制文件的基础之上,加上其他工具性的代码以完成加载和引导Linux内核。加上这些工具性的代码后,最终出来一个完整的内核映像。不幸的是,这个内核映像名字也叫 vmlinux,为了区分清楚,内核开发者通常称之为 composite kerel image。最后,为了让bootloader能真正的去引导启动Linux内核,构建系统还会将 composite kernel image 做一下处理,形成诸如 zImage、bzImage、xipImage、uImage等等众多的映像。注意在嵌入式应用中用的最多的是 zImage 和 xipImage。在x86平台中使用最多的bzImage。而uImage是zImage经过mkimage工具处理过,搭配uboot来使用的内核映像。

说了这么多,也许你还没怎么明白,不要紧,以下我们以 arm 平台(s3c2410_defconfig缺省配置)上的 zImage 来分析构建系统的构建动作。熟悉这个后,其他的你自己就可以进行分析了。前面已经说过在 arm架构的对应的架够Makefile中规定了 all 要依赖于 zImage,所以我们在 .../arch/arm/Makefile 中找到 zImage 的对应规则:

zImage Image xipImage bootpImage uImage: vmlinux
$(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@

上面规则中说 zImage 依赖于 vmlinux。这个 vmlinux 就是我们前面构建好的在顶层目录中的 vmlinux,也就是所谓的 kernel proper。由于 boot 变量被定义成arch/arm/boot,而且 MACHINE 变量被定义成 arch/arm/mach-s3c2410,所以该规则的命令部分可以简化成:

make -f scripts/Makefile.build obj=arch/arm/boot MACHINE=arch/arm/mach-s3c2410 arch/arm/boot/zImage。

我们遵循以前的做法,到 .../arch/arm/boot/Makefile 中找到关于 zImage 的规则:

$(obj)/zImage:  $(obj)/compressed/vmlinux FORCE
$(call if_changed,objcopy)
@echo ' Kernel: $@ is ready'

由上可知,zImage 的构建依赖于 .../arch/arm/boot/compressed/ 下的 vmlinux。:)别急,是的,很不幸,这个也叫做 vmlinux,名字与我们的 kernel proper 同名。其实这个就是我们刚才讲到的 composite kernel image。我们在 .../arch/arm/boot/Makefile 下找出它的处理规则:

$(obj)/compressed/vmlinux: $(obj)/Image FORCE
$(Q)$(MAKE) $(build)=$(obj)/compressed $@

从中看出它要依赖于 .../arch/arm/boot/ 下面的 Image,我们继续追踪找到 .../arch/arm/boot/Image 的构建规则:

$(obj)/Image: vmlinux FORCE
$(call if_changed,objcopy)
@echo ' Kernel: $@ is ready'

呵呵,坚持住别抓狂!这里又出现了一个 vmlinux。其实,这就是我们之前所说的 kernel proper,它已经在内核源码树的顶层目录中生成了。看到这里我们终于可以松一口气了,因为到这里为止,目标之间的依赖关系终于可以告一个段落,剩下的就是要沿着规则的命令部分倒着回去分析。这个规则的命令部分其实就是调用变量 cmd_obcopy 所定义的命令。变量 cmd_objcopy 定义在 .../scripts/Makefile.lib 中:

# Objcopy
# ---------------------------------------------------------------------------

quiet_cmd_objcopy = OBJCOPY $@
cmd_objcopy = $(OBJCOPY) $(OBJCOPYFLAGS) $(OBJCOPYFLAGS_$(@F)) $< $@

而变量 OBJCOPYFLAGS 的定义在文件 .../arch/arm/Makefile 中:

OBJCOPYFLAGS :=-O binary -R .note -R .note.gnu.build-id -R .comment -S

变量 OBJCOPYFLAGS_$(@F) 可以扩展成 OBJCOPYFLAGS_Image,而该变量在构建系统中没有定义,所以 cmd_objcopy 所定义的命令就简化成:

arm-linux-objcopy -O binary -R .note -R .note.gnu.build-id -R .comment -S vmlinux arch/arm/boot/Image

这个命令的作用是用 objcopy 工具从 ELF 格式的 vmlinux(kernel proper) 中取出纯粹的二进制指令生成Image文件。做这个过程的同时,使用 -R 选项去掉vmlinux中的一些 elf section:.note和 .note.gnu.build-id;另外使用 -S 选项将其中的调试符号信息去掉。

既然已经生成了 .../arch/arm/boot/Image,那接下来,便可用命令 $(Q)$(MAKE) $(build)=$(obj)/compressed $@ 来生成 composite kernel image 了。该命令可以简化成:

make -f scripts/Makefile.build obj=arch/arm/boot/compressed/ arch/arm/boot/compressed/vmlinux。

按照我们一贯的做法,从 .../arch/arm/boot/compressed/Makefile 中找出构造 vmlinux(composite kernel image) 的规则来:

$(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.o \
$(addprefix $(obj)/, $(OBJS)) FORCE
$(call if_changed,ld)
@:

从该规则可以看出,vmlinux(composite kernel image)的处理需要依赖于其他一些东西,咱们分别来看:

a) .../arch/arm/boot/compressed/vmlinux.lds

对于 composite kernel image 的处理,实际上是一个链接的过程。既然是链接,就得有一个连接脚本来指示如何来链接。前面我们在讨论处理 vmlinux 的时候已经接错过一个 vmlinux.lds,它是由 .../arch/arm/kernel/ 目录下的 vmlinux.lds.S 预处理而来的。但是这里的 vmlinux.lds 则是用来规范 composite kernel image 的链接过程的,它是由流编辑器处理 .../arch/arm/boot/compressed/ 目录下的 vmlinux.lds.in 文件得来的,希望同学们不要将其混淆。在 .../arch/arm/boot/compressed/Makefile 中找到对应的处理规则如下:


​​


b) $(HEAD)

变量 HEAD 被定义成 head.o,它是构建系统使用 .../script/Makefile.build 文件中下面的规则从 head.S 生成的。后面你会知道,构建系统将 head.o 放在整个 composite kernel image 的最前面,并将 head.S 中定义的标号 start 作为整个 composite kernel image 的入口。你如果去看 .../arch/arm/boot/compressed/vmlinux.ds 的话,会看到,head.o 中定义的 .start section 将会被放在整个 composite image kernel 的最前面。

quiet_cmd_as_o_S = AS $(quiet_modtag)  $@
cmd_as_o_S = $(CC) $(a_flags) -c -o $@ $<

$(obj)/%.o: $(src)/%.S FORCE
$(call if_changed_dep,as_o_S)

head.S 里面完成了从 bootloader 到内核启动(第一条内核代码开始运行)的所有工作,包括 kernel proper 的解压缩和重定位,我们会在后面的文章中详细分析这个文件。

c) piggy.o

这是在构建 zImage 整个过程中最有意思的地方。piggy 一词,原指小猪、贪心的之意。但在这里,强调的是 piggy-back 的意思,也即背负的意思。这很好理解,长征-2F运载火箭背负着神州七号飞向太空,神州七号是负载。按照同样的理解,这里也有负载。那是什么?是前面的 kernel proper。那这里的长征火箭又是什么呢?后面会看到,那就是所谓的 bootstrap loader。我们先来看看构建系统是如何帮助实现这一 piggy-back 的,.../arch/arm/boot/compressed/Makefile 中有这样的规则:

$(obj)/piggy.gz: $(obj)/../Image FORCE
$(call if_changed,gzip)

$(obj)/piggy.o: $(obj)/piggy.gz FORCE

从这些规则上看出,有一个目标 piggy.gz,是经由压缩 kernel proper 产生的,具体使用的命令是:cmd_gzip。定义在 .../scripts/Makefile.lib 中:

# Gzip
# ---------------------------------------------------------------------------

quiet_cmd_gzip = GZIP $@
cmd_gzip = (cat $(filter-out FORCE,$^) | gzip -f -9 > $@) || \
(rm -f $@ ; false)

另外你也看到,piggy.o 要依赖于 piggy.z。这个地方,有些同学一下找不到构建 piggy.o 的规则就以为它是 make 的内嵌规则所致。实际上,正和前面构造 head.o 一样,构建系统也是用同样的规则来将 piggy.S 生成 piggy.o 的。打开 piggy.S,你会发现这里正是实现背负的关键地方:

.section .piggydata,#alloc
.globl input_data
input_data:
.incbin "arch/arm/boot/compressed/piggy.gz"
.globl input_data_end
input_data_end:

可以发现,正是这段代码将前面压缩得到的 piggy.gz 当做一段数据镶嵌到编译 piggy.S 后生成的 piggy.o 对象文件的 .piggydata ELF section中去的。

d) $(OBJS)

变量 OBJS 被定义成 misc.o。在其他一些ARM架构的芯片中,它还有可能包含 big-endian.o 对象文件,但是就s3c2410_defconfig来说,因为没有定义 CONFIG_CPU_ENDIAN_BE32,所以 OBJS 变量并不包含 big-endia.o。构建系统使用下面 .../scripts/Makefile.build 中的规则从 misc.c 生成 misc.o。misc.o 里面包含的主要是用来解压缩 kernel proper 的函数代码。这些函数由 head.o 中的代码来调用。

# Built-in and composite module parts
$(obj)/%.o: $(src)/%.c FORCE
$(call cmd,force_checksrc)
$(call if_changed_rule,cc_o_c)

在前面我们已经介绍过类似的规则,所以这里就不再细讲。

好,回到构建 composite kernel image 的规则上来,既然所有的依赖都已经准备妥当,那就该用最后的链接命令 cmd_ld 来生成 composite kernel image 了。cmd_ld 定义在 .../scripts/Makefile.lib 中:

# Linking
# ---------------------------------------------------------------------------

quiet_cmd_ld = LD $@
cmd_ld = $(LD) $(LDFLAGS) $(ldflags-y) $(LDFLAGS_$(@F)) \
$(filter-out FORCE,$^) -o $@

实际上执行的这个命令简化一下就是:


​​


为了让您更明白 composite kernel image 的构建,在这里用一个图来描述它的构建过程(原图取自elp,稍做修改):


​​


注意,如该图所示,上面这些head.o、misc.o等构成了 bootstrap loader。在 composite kernel image 中,正是由 bootstrap loader 背负(piggy-back)了真正的负载,也即 kernel proper。

最后,内核构建系统根据下面 .../arch/arm/boot/Makefile 中的规则,使用 objcopy 工具将没用的ELF section从 composite image kernel 中去除掉,形成最后可供 bootloader 引导的内核映像 zImage。关于这个规则中涉及到的 cmd_objcopy 命令,我们前面已经讨论过,这里不再讨论。

$(obj)/zImage:  $(obj)/compressed/vmlinux FORCE
$(call if_changed,objcopy)
@echo ' Kernel: $@ is ready'

好,至此,内部模块已经产生出来,可供引导的内核映像也已经产生出来了。所以我们为讲述构建目标而举的第一个例子:"make ARCH=arm CROSS_COMPILE=arm-linux-"已全部介绍完毕。接下来,让我们来看看另外一个例子,也就是编译外部模块的命令:"make ARCH=arm CROSS_COMPILE=arm-linux- -C KERNELDIR M=dir"。