×
嵌入式 > 嵌入式开发 > 详情

ARM linux解析之zImage镜像文件的生成

发布时间:2020-08-14 发布时间:
|
最开始做个约定:存放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

$(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@

可以看到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

$(Q)$(MAKE) -f $(srctree)/Makefile headers_check

endif

ifdef CONFIG_SAMPLES

$(Q)$(MAKE) $(build)=samples

endif

ifdef CONFIG_BUILD_DOCSRC

$(Q)$(MAKE) $(build)=Documentation

endif

$(call vmlinux-modpost)

$(call if_changed_rule,vmlinux__)

$(Q)rm -f .old_version

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-init) $(vmlinux-main)

vmlinux-lds:= arch/$(SRCARCH)/kernel/vmlinux.lds

modpost-init := $(filter-out init/built-in.o, $(vmlinux-init))

vmlinux.o: $(modpost-init) $(vmlinux-main) FORCE

$(call if_changed_rule,vmlinux-modpost)

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

$(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@

其中$(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就是当前目录,故
build := -f ./scripts/Makefile.build obj

boot := arch/arm/boot

ifneq ($(machine-y),)

MACHINE:= arch/arm/mach-$(word 1,$(machine-y))/

else

MACHINE:=

endif

machine-$(CONFIG_ARCH_EVB_ARM):= evb_arm

Kconfig中:CONFIG_ARCH_EVB_ARM = y

所以对于我们的平台:

MACHINE:= arch/arm/mach-evb_arm

最开始的那一句最终解释为:

zImage:vmlinux

@make –f./scripts/Makefile.build obj=arch/arm/boot MACHINE=arch/arm/mach-evb_armarch/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//这个就是上一个makefile传下来的值

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 Kernel: $@ is ready

再找$(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 Kernel: $@ is ready

到这里就可以知道了,哦,原来这个地方也是要vmlinux内核elf的啊,这就知道了,无论怎么样,vmlinux文件都要先生成,否则其他的文件都无法成生。下面来解释一下:

$(obj)/Image: vmlinux FORCE

$(call if_changed,objcopy)

@echo Kernel: $@ is ready

这里的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 \

$(addprefix $(obj)/, $(OBJS)) $(lib1funcs) FORCE

$(call if_changed,ld)

@$(check_for_bad_syms)

HEAD= head.o

OBJS+= misc.o decompress.o

$(obj)/piggy.$(suffix_y): $(obj)/../Image FORCE

$(call if_changed,$(suffix_y))

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

$(obj)/vmlinux.lds: $(obj)/vmlinux.lds.in arch/arm/boot/Makefile $(KCONFIG_CONFIG)

@sed "$(SEDFLAGS)" < $< > $@

插曲:关于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)),\

@set -e;\

$(echo-cmd) $(cmd_$(1));\

echo cmd_$@ := $(make-cmd) > $(dot-target).cmd)

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

$(call if_changed,$(suffix_y))

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

这里的suffix_y在,arch/arm/boot/compressed:

suffix_$(CONFIG_KERNEL_GZIP) = gzip

suffix_$(CONFIG_KERNEL_LZO)= 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文件生成的,内容如下:

.section.piggydata,#alloc

.globlinput_data

input_data:

.incbin"arch/arm/boot/compressed/piggy.gzip"

.globlinput_data_end

input_data_end:

可以看到压缩后的内核文件piggy.gzip是以bin文件的形式编译成piggy.gzip.o的,并且注意这里的是放在.piggydata段中的,这个在arch/arm/boot/compressed/vmlinux.lds指定的。

内容如下:

.text : {

_start = .;

*(.start)

*(.text)

*(.text.*)

*(.fixup)

*(.gnu.warning)

*(.rodata)

*(.rodata.*)

*(.glue_7)

*(.glue_7t)

*(.piggydata)

. = ALIGN(4);

}

是放在.text段的最后的,并且input_data和input_data_end包含里他们的起止地址。

最后是arch/arm/boot/compressed/vmlinux.lds文件的生成,这个是以arch/arm/boot/compressed/vmlinux.lds.in为蓝本,只是修改了

. = TEXT_START;

这个是在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)

@sed "$(SEDFLAGS)" < $< > $@

ifeq ($(CONFIG_ZBOOT_ROM),y)

ZTEXTADDR:= $(CONFIG_ZBOOT_ROM_TEXT)

ZBSSADDR:= $(CONFIG_ZBOOT_ROM_BSS)

else

ZTEXTADDR:= 0

ZBSSADDR:= ALIGN(8)

endif

就是:TEXT_START = $(ZTEXTADDR)BSS_START = $(ZBSSADDR)

如果内枋是在nor flash中运行的,则选第一个,这时CONFIG_ZBOOT_ROM这个变量会定义,在RAM中运行的话,选第二个。在RAM中运行的代码是被编译成与位置无关的,所以可以加载到任何地方运行。

所以合起来的解释是:

通过以下规则,最终生成arch/arm/boot/compress/vmlinux

$(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.$(suffix_y).o \

$(addprefix $(obj)/, $(OBJS)) $(lib1funcs) FORCE

$(call if_changed,ld)

@$(check_for_bad_syms)

这个arch/arm/boot/compressed/vmlinux和前面的根目录下生成的vmlinux都是elf文件,但是却不是同一个东西。它是包令里解压用的头文件head.s,解压程序misc.c,decompress.c的,再加入压缩内核的elf文件,是可以通过trace32加载运行的。

下面是最后一步:

$(obj)/Image: vmlinux FORCE

$(call if_changed,objcopy)

@echo Kernel: $@ is ready

这个时候再把arch/arm/boot/compressed/vmlinux通过objcopy生成bin文件zImage,到这里,zImage文件生成完毕。

最后用个图表示一下整个zImage的生成过程:

图1.ARM linux的zImage生成过程



『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
CC-Link现场总线及应用实例