ARM linux解析之zImage镜像文件的生成 semilog@163.com 最开始做个约定:存放linux代码的根目录我们叫做:linux_src,代码用linux-3.0.46进行分析。对于arm系统,$(SRCARCH) = ARM。 可能你会想,究竟linux如何编译生成它的压缩内核镜像zImage的呢?哈哈,下面就来做个解析。 当我们在linux_src目录下输入命令: > make zImage 就会进入linux_src/makfile,然后找 zImage目标,这个目标在: linux_src/makfile包含的:include $(srctree)/arch/$(SRCARCH)/Makefile 里面, 位于:linux_src/arch/arm/makfile里: zImage Image xipImage bootpImage uImage: vmlinux 可以看到zImage 依赖于vmlinux,这里的vmlinux指的是linux_src/vmlinux,这是编译生成的linux内核的elf文件。那么vmlinux又是在哪生成的呢?请向下看: 它的生成规则在linux_src/makefile文件中,如下: #vmlinux image - including updated kernel symbols vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE ifdef CONFIG_HEADERS_CHECK endif ifdef CONFIG_SAMPLES endif ifdef CONFIG_BUILD_DOCSRC endif vmlinux的生成依赖于: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o),同样在linux_src/makefile文件中: vmlinux-init := $(head-y) $(init-y) vmlinux-main:= $(core-y) $(libs-y) $(drivers-y) $(net-y) vmlinux-all vmlinux-lds modpost-init := $(filter-out init/built-in.o, $(vmlinux-init)) vmlinux.o: $(modpost-init) $(vmlinux-main) FORCE kallsyms.o:= .tmp_kallsyms$(last_kallsyms).o 这里可以看到vmlinux就是由这些依赖文件通过arch/$(SRCARCH)/kernel/vmlinux.lds链接生成的,我就不再每往下解释了。 生成了linux_src/vmlinux之后,再回头看arch/arm/boot/compressed/Makefile文件中的: zImage Image xipImage bootpImage uImage: vmlinux 其中$(build)在linux_src/scripts/kbuild.include: ### # Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj= # Usage: # $(Q)$(MAKE) $(build)=dir build := -f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.build obj 因为 KBUILD_SRC 为空,srctree就是当前目录,故 boot := arch/arm/boot ifneq ($(machine-y),) MACHINE else MACHINE endif machine-$(CONFIG_ARCH_EVB_ARM) Kconfig中:CONFIG_ARCH_EVB_ARM = y 所以对于我们的平台: MACHINE 最开始的那一句最终解释为: zImage: vmlinux @make –f ./scripts/Makefile.build obj=arch/arm/boot MACHINE=arch/arm/mach-evb_arm arch/arm/boot/zImage 下面就是进入到linux_src/scripts/Makefile.build这个makefile文件中 src := $(obj) kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src)) kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile) include$(kbuild-file) 在这里: src := $(obj) :=arch/arm/boot kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src)) //这句的意思是,如果src是以/开头的,则 kbuild-dir :=$(src), 如果不是的就等于kbuild-dir := $(srctree)/$(src) 这里 kbuild-dir := ./arm/arm/boot kbuild-file := ./arch/arm/boot/Kbuild ./arch/arm/boot/Makefile 最后一句: include ./arch/arm/boot/Kbuild ./arch/arm/boot/Makefile 可以看到这里是要包含linux_src/arch/arm/boot/Makefile 文件,果然在里面找到了我们要的目标: $(obj)/zImage: $(obj)/compressed/vmlinux FORCE $(call if_changed,objcopy) @echo ' 再找$(obj)/compressed/vmlinux: $(obj)/compressed/vmlinux: $(obj)/Image FORCE $(Q)$(MAKE) $(build)=$(obj)/compressed $@ 再接着找$(obj)/Image: $(obj)/Image: vmlinux FORCE $(call if_changed,objcopy) @echo ' 到这里就可以知道了,哦,原来这个地方也是要vmlinux内核elf的啊,这就知道了,无论怎么样,vmlinux文件都要先生成,否则其他的文件都无法成生。下面来解释一下: $(obj)/Image: vmlinux FORCE $(call if_changed,objcopy) @echo ' 这里的Image也就是linux_src/arch/arm/boot/Image,它的生成是通过把 linux_src/vmlinux这个elf文件用通过objcopy生成bin文件Image。然后: $(obj)/compressed/vmlinux: $(obj)/Image FORCE $(Q)$(MAKE) $(build)=$(obj)/compressed $@ 这句和上面分析的类似,就是进入linux_src/arch/arm/boot/compress/mafile中生成: arch/arm/boot/compress/vmlinux文件,如下: $(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.$(suffix_y).o \ HEAD OBJS $(obj)/piggy.$(suffix_y): $(obj)/../Image FORCE $(obj)/piggy.$(suffix_y).o: $(obj)/vmlinux.lds: $(obj)/vmlinux.lds.in arch/arm/boot/Makefile $(KCONFIG_CONFIG) 插曲:关于if_changed的解释: Kbuild.include: # >'< substitution is for echo to work, # >$< substitution to preserve $ when reloading .cmd file # note: when using inline perl scripts [perl -e '...$$t=1;...'] # in $(cmd_xxx) double $$ your perl vars make-cmd = $(subst \\,\\\\,$(subst \#,\\\#,$(subst $$,$$$$,$(call escsq,$(cmd_$(1)))))) # Find any prerequisites that is newer than target or that does not exist. # PHONY targets skipped in both cases. any-prereq = $(filter-out $(PHONY),$?) $(filter-out $(PHONY) $(wildcard $^),$^) # Execute command if command has changed or prerequisite(s) are updated. # if_changed = $(if $(strip $(any-prereq) $(arg-check)), if_changed 函数在当发现规则的依赖有更新,或是目依赖不存在时,再或者是对应目标的命令行参数发生改变时($(strip $(any-prereq) $(arg-check)) 语句结果不为空),执行后面的语句。 set -e 表示如果命令执行有错那么命令停止执行并退出。 接着 $(echo-cmd) 用来打印出相关的编译命令,接着执行 $(cmd_$(1) 里的命令。 最后 echo 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd 将上面执行的命令写入一个叫 $(dot-target).cmd 的文件中,该文件为隐藏文件,在编译后的内核源码目录及其子目录下随处可见,比如在init/ 下可以看到 .initramfs.o.cmd, .version.o.cmd 等等。 那么而所有的命令测存在呢?答案是:scripts/Makefile.lib: scripts/Makefile.build里面:include scripts/Makefile.lib scripts/Makefile.lib: # Objcopy # --------------------------------------------------------------------------- quiet_cmd_objcopy = OBJCOPY cmd_objcopy = $(OBJCOPY) $(OBJCOPYFLAGS) $(OBJCOPYFLAGS_$(@F)) $< $@ # Gzip # --------------------------------------------------------------------------- quiet_cmd_gzip = GZIP cmd_gzip = (cat $(filter-out FORCE,$^) | gzip -n -f -9 > $@) || (rm -f $@ ; false) 言归正传,回到上面: $(obj)/piggy.$(suffix_y): $(obj)/../Image FORCE $(obj)/piggy.$(suffix_y).o: 这里的suffix_y在,arch/arm/boot/compressed: suffix_$(CONFIG_KERNEL_GZIP) = gzip suffix_$(CONFIG_KERNEL_LZO) suffix_$(CONFIG_KERNEL_LZMA) = lzma CONFIG_KERNEL_GZIP=y 是在 init/Kconfig文件里选择的,menuconfig时配置,我们这里选gzip,则上面的过程为 使用 gzip 对arch/arm/boot/Image文件进行压缩,压缩成piggy.gzip。然后再生成piggy.gzip.o: 这个是通过 piggy.gzip.S文件生成的,内容如下: input_data: input_data_end: 可以看到压缩后的内核文件piggy.gzip是以bin文件的形式编译成piggy.gzip.o的,并且注意这里的是放在.piggydata段中的,这个在arch/arm/boot/compressed/vmlinux.lds指定的。 内容如下: 是放在.text段的最后的,并且input_data和input_data_end 包含里他们的起止地址。 最后是arch/arm/boot/compressed/vmlinux.lds文件的生成,这个是以arch/arm/boot/compressed/vmlinux.lds.in为蓝本,只是修改了 这个是在arch/arm/boot/compressed/Makefile中 SEDFLAGS = s/TEXT_START/$(ZTEXTADDR)/;s/BSS_START/$(ZBSSADDR)/ $(obj)/vmlinux.lds: $(obj)/vmlinux.lds.in arch/arm/boot/Makefile $(KCONFIG_CONFIG) ifeq ($(CONFIG_ZBOOT_ROM),y) ZTEXTADDR ZBSSADDR := $(CONFIG_ZBOOT_ROM_BSS) else ZTEXTADDR ZBSSADDR := ALIGN(8) endif 就是:TEXT_START = $(ZTEXTADDR) 如果内枋是在nor flash中运行的,则选第一个,这时CONFIG_ZBOOT_ROM这个变量会定义,在RAM中运行的话,选第二个。在RAM中运行的代码是被编译成与位置无关的,所以可以加载到任何地方运行。 所以合起来的解释是: $(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.$(suffix_y).o \ 这个arch/arm/boot/compressed/vmlinux和前面的根目录下生成的vmlinux都是elf文件,但是却不是同一个东西。它是包令里解压用的头文件head.s, 解压程序misc.c,decompress.c的,再加入压缩内核的elf文件,是可以通过trace32加载运行的。 下面是最后一步: $(obj)/Image: vmlinux FORCE $(call if_changed,objcopy) @echo ' 这个时候再把arch/arm/boot/compressed/vmlinux通过objcopy生成bin文件zImage,到这里,zImage文件生成完毕。 最后用个图表示一下整个zImage的生成过程:
图1.ARM linux的zImage生成过程 |
|
来自: chengong510 > 《嵌入式》