Uboot编译过程分析

来源:互联网 发布:淘宝运费险多少钱 编辑:程序博客网 时间:2024/05/29 09:46

Uboot编译过程分析

本文基于firefly-rk3399开发板的uboot,对其编译过程进行分析。
uboot的编译分为两个步骤:

make ARCHV=aarch64 rk3399_box_defconfigmake

因此,接下来主要分为两部分,对uboot的编译过程进行剖析。


一、配置

1. make ARCHV=aarch64 rk3399_box_defconfig

有两个信息:

1-1 ARCHV=aarch64

定义了一个变量ARCHV,其值为aarch64。Makefile根据这个变量,决定使用哪种交叉编译工具:

ifeq ($(ARCHV),aarch64)ifneq ($(wildcard ../toolchain/aarch64-linux-android-4.9),)CROSS_COMPILE   ?= $(shell pwd)/../toolchain/aarch64-linux-android-4.9/bin/aarch64-linux-android-endif...else...endif
1-2 构建的终极目标为rk3399_box_defconfig

rk3399_box_defconfig匹配如下规则

%config: scripts_basic outputmakefile FORCE    +$(Q)$(CONFIG_SHELL) $(srctree)/scripts/multiconfig.sh $@ #前面“+”的意思是,这条命令始终会执行,#即使使用的make -n。(-n的含义是只打印命令,不实际执行)

rk3399_box_defconfig依赖scripts_basic,因此先构建scripts_basic

scripts_basic:    $(Q)$(MAKE) $(build)=scripts/basic    $(Q)rm -f .tmp_quiet_recordmcount

build在scripts/Kbuild.include中定义:

build := -f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.build obj

因此执行scripts/basic/Makefile.build,指定obj变量为scripts/basic,没有明确指定目标,默认构建Makefile.build中的第一个目标__build。

2. scripts/Makefile.build

在该文件中:

prefix := tplsrc := $(patsubst $(prefix)/%,%,$(obj))   #此时src=obj=scripts/basic...__build:...kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)include $(kbuild-file)...

意思就是在scripts/basic下,如果有Kbuild文件,就include该文件,否则include目录下的Makefile文件。
scripts/basic没有Kbuild文件,因此include scripts/basic/Makefile
scripts/basic/Makefile内容如下:

hostprogs-y := fixdepalways      := $(hostprogs-y)

接着往下看Makefile.build:

ifneq ($(hostprogs-y)$(hostprogs-m),)include scripts/Makefile.host   # hostprogs-y不为空,所以include进该文件endif...__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \     $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \     $(subdir-ym) $(always)    @:      #@取消命令回显,:是空命令,什么也不做。

由于在scripts/basic/Makefile中定义了always:=fixdep,__build依赖fixdep,所以先构建fixdep,构建的规则在scripts/Makefile.host中定义。

3. scripts/Makefile.host

quiet_cmd_host-csingle  = HOSTCC  $@      cmd_host-csingle  = $(HOSTCC) $(hostc_flags) -o $@ $< \        $(HOST_LOADLIBES) $(HOSTLOADLIBES_$(@F))$(host-csingle): $(obj)/%: $(src)/%.c FORCE  #host-csingle=hostprogs-y=scripts/basic/fixdep    $(call if_changed_dep,host-csingle)

这是一条静态模式规则,意思就是从 $(host-csingle)中找出匹配$(obj)/%的字符串作为目标,匹配$(src)/%.c的文件作为依赖,生成一条规则
例如:

files = foo.elc bar.o lose.o$(filter %.o,$(files)) : %.o:%.c$(CC) -c $(CFLAGS) $< -o $@$(filter %.elc,$(files)) : %.elc : %.elemacs -f batch-byte-compile $<

等效于

bar.o : bar.c$(CC) -c $(CFLAGS) $< -o $@lose.o : lose.c$(CC) -c $(CFLAGS) $< -o $@foo.elc : foo.elemacs -f batch-byte-compile $<

因此

$(host-csingle): $(obj)/%: $(src)/%.c FORCE    $(call if_changed_dep,host-csingle)

展开后就是

scripts/basic/fixdep : scripts/basic/fixdep.c FORCE$(call if_changed_dep,host-csingle)

if_changed_dep在scripts/Kbuild.include中定义,大致意思就是如果目标的依赖有发生变化,就会执行host-csingle定义的命令。
最后,在scripts/basic下生成了fixdep程序文件。

4.构建rk3399_box_defconfig

回过头来,scripts_basic已经构建,接着构建rk3399_box_defconfig:

%config: scripts_basic outputmakefile FORCE    +$(Q)$(CONFIG_SHELL) $(srctree)/scripts/multiconfig.sh $@

这条命令意思就是执行scripts/multiconfig.sh rk3399_box_defconfig。执行流程如下:

progname=$(basename $0)target=$1case $target in            #target=rk3399_box_defconfig*_defconfig)    do_board_defconfig $target;;...esacdo_board_defconfig () {...    run_make_config .tmp_defconfig || {        cleanup_after_defconfig        exit 1    }...}run_make_config () {    target=$1    objdir=$2    build scripts/kconfig $options $target}build () {    debug $progname: $MAKE -f $srctree/scripts/Makefile.build obj="$@"    $MAKE -f $srctree/scripts/Makefile.build obj="$@"}

最终执行

make -f scripts/basic/Makefile.build obj=scripts/kconfig SRCARCH=.. KCONFIG_OBJDIR=  .tmp_defconfig

即执行scripts/basic/Makefile.build,构建 .tmp_defconfig

5. 构建 .tmp_defconfig

scripts/basic/Makefile.build引入scripts/kconfig/Makefile,在scripts/kconfig/Makefile中定义了构建.tmp_defconfig的规则:

%_defconfig: $(obj)/conf    $(Q)$< --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)

.tmp_defconfig依赖scripts/kconfig/conf,先构建scripts/kconfig/conf。
在该Makefile中:

conf-objs   := conf.o  zconf.tab.ohostprogs-y := conf$(obj)/zconf.tab.o: $(obj)/zconf.lex.c $(obj)/zconf.hash.c

与构建fixdep类似,使用scripts/Makefile.host中定义的规则构建。conf是由conf.o和zconf.tab.o链接而成

$(host-cobjs): $(obj)/%.o: $(src)/%.c FORCE    $(call if_changed_dep,host-cobjs)

上面的规则构建conf.o和zconf.tab.o,最后由下面的规则链接成conf:

$(host-cmulti): $(obj)/%: $(host-cobjs) $(host-cshlib) FORCE    $(call if_changed,host-cmulti)

需要注意,scripts/kconfig下没有zconf.tab.c文件,只有一个zconf.tab.c_shipped文件,会使用scripts/Makefile.lib中定义的规则

$(obj)/%: $(src)/%_shipped    $(call cmd,shipped)

先构建zconf.tab.c,$(obj)/zconf.lex.c $(obj)/zconf.hash.c也一样。
生成conf后,就最终执行构建.tmp_defconfig的规则了:

%_defconfig: $(obj)/conf    $(Q)$< --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)

展开命令就是

scripts/kconfig/conf --defconfig=arch/../configs/.tmp_defconfig Kconfig

该命令引入了所有Kconfig和.tmp_defconfig(内容和rk3399_box_defconfig一样)定义的CONFIG_XXX变量,最后输出到.config中。
至此,make rk3399_box_defconfig执行完成。

二、make 编译

1、Makefile

先看Makefile的这几行:

-include include/config/auto.conf-include include/config/auto.conf.cmd$(KCONFIG_CONFIG) include/config/auto.conf.cmd: ;include/config/%.conf: $(KCONFIG_CONFIG) include/config/auto.conf.cmd    $(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig

在执行make后(见下图),没有开始执行构建终极目标的流程,而是最先执行了构建include/config/%.conf的操作,这是为什么呢?
这里写图片描述
原因在于这一行

-include include/config/auto.conf

make在读入所有的makefile文件之后,首先将所读取的每个makefile作为一个目标,如果存在更新makefile的规则,则更新makefile文件。更新之后make清除本次执行的状态,重新读取一遍所有的makefile文件。
make执行前没有include/config/auto.conf文件,因此在读入所有的Makefile文件之后,会寻找构建include/config/auto.conf的规则,即

include/config/%.conf: $(KCONFIG_CONFIG) include/config/auto.conf.cmd    $(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig

生成include/config/auto.conf后,make重新读取一次所有的Makefile,这时再-include include/config/auto.conf就会引入auto.conf中的内容了。

2、构建include/config/auto.conf

在Makefile中:

$(KCONFIG_CONFIG) include/config/auto.conf.cmd: ;include/config/%.conf: $(KCONFIG_CONFIG) include/config/auto.conf.cmd    $(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig

展开后

.config include/config/auto.conf.cmd: ;include/config/auto.conf : .config include/config/auto.conf.cmdmake -f Makefile silentoldconfig

也就是执行make silentoldconfig。接下来的流程与make rk3399_box_defconfig类似

%config: scripts_basic outputmakefile FORCE    +$(Q)$(CONFIG_SHELL) $(srctree)/scripts/multiconfig.sh $@

执行multiconfig.sh silentoldconfig,调用到multiconfig.sh中的do_silentoldconfig ()函数

do_silentoldconfig () {    ...    run_make_config silentoldconfig    ...    autoconf include include/autoconf.mk include/autoconf.mk.dep || {        rm -f include/config/auto.conf        exit 1    }    touch include/config/auto.conf}

主要做了两个工作:

2-1、 run_make_config silentoldconfig

run_make_config silentoldconfig最终执行scripts/kconfig/Makefile中定义的规则

silentoldconfig: $(obj)/conf    $(Q)mkdir -p include/config include/generated    $< --$@ $(Kconfig)

也就是执行了之前说的那条命令:scripts/kconfig/conf --silendoldconfig Kconfig

2-2、autoconf include include/autoconf.mk include/autoconf.mk.dep
autoconf () {    debug $progname: $MAKE -f $srctree/scripts/Makefile.autoconf obj="$@"    $MAKE -f $srctree/scripts/Makefile.autoconf obj="$@"}

命令展开后:

make -f scripts/Makefile.autoconf obj=include include/autoconf.mk include/autoconf.mk.dep

根据在Makefile.autoconf中定义的规则生成include/autoconf.mk和include/autoconf.mk.dep。

比较autoconf和.config文件内容,发现两者基本上是一样的;make rk3399_box_defconfig汇集了CONFIG_XXX变量到.config文件,在make时由-incldue include/config/autoconf引入到make,这样make就可以使用这些变量的定义了。

然而CONFIG_XXX变量不止在makefile中定义,更多情况实在.h头文件中定义为宏,例如CONFIG_RAM_PHY_START在include/configs/rk30plat.h中定义:

#define CONFIG_RAM_PHY_START         0x60000000

那么,make如何获取这些在头文件中定义的宏呢?就是这个autoconf.mk文件,这个文件将所有相关头文件中的CONFIG_XXX宏,转化为makefile变量,再被-include include/autoconf.mk这句话包含进Makefile,供make使用。

接下来在Makefile中:

ifneq ($(autoconf_is_current),)include $(srctree)/config.mkendif

包含进了源码顶级目录的config.mk,这个config.mk定义了一些变量,并且引入了cpu和board目录下的config.mk。
至此,所有的CONFIG_XXX变量(makefile中定义的和.h中的宏定义)就被make包含进了。

3、 u-boot-dirs

接下来,Makefile定义了一些变量

head-y := $(CPUDIR)/start.olibs-$(CONFIG_OF_EMBED) += dts/libs-y += arch/$(ARCH)/lib/libs-y += fs/libs-y += net/libs-y += disk/...u-boot-dirs := $(patsubst %/,%,$(filter %/, $(libs-y))) tools exampleslibs-y      := $(patsubst %/, %/built-in.o, $(libs-y))u-boot-init := $(head-y)u-boot-main := $(libs-y)

有些变量根据CONFIG_XXX的值来决定是否追加定义,例如如果CONFIG_OF_EMBED=y,则libs-$(CONFIG_OF_EMBED) += dts/就会展开成libs-y += dts/。lib-y指定了本次编译需要编译的工程目录,u-boot-main就是各工程目录下的built-in.o文件,例如fs/build-in.o、net/build-in.o,这些build-in.o最后会被链接成uboot。

4、编译终极目标

make没有指定目标,默认构建终极目标_all。_all依赖all,al又依赖$(ALL-y),$(ALL-y)展开后为checkarmreloc RKLoader-uboot.bin u-boot.srec u-boot.bin System.map binary_size_check。依赖树如下图所示:

这里写图片描述

4-1、include/config/uboot.release

define filechk_uboot.release    echo "$(UBOOTVERSION)$$($(CONFIG_SHELL) $(srctree)/scripts/setlocalversion $(srctree))"endefinclude/config/uboot.release: include/config/auto.conf FORCE    $(call filechk,uboot.release)

调用filechk_uboot.release生成include/config/uboot.release,该文件包含了uboot的版本信息。

4-2、调用filechk_version.h生成$(verison_h)
4-3、调用filechk_timestamp.h生成$(timestamp_h)
4-4、prepare0的构建
prepare0: archprepare FORCE    $(Q)$(MAKE) $(build)=.

make -f scripts/basic/Makefile.build obj=. 

没有指定目标,默认构建__build.

  ●  在Makefile.build中,引入$(srctree)/KbuildKbuild定义了$(always)  ● 构建__build  ● 构建__build的依赖$(always),即generic-asm-offsets.h和asm-offsets.h
4-5、 构建$(u-boot-dirs)

这一步完成了uboot编译的主要工作:

$(u-boot-dirs): prepare scripts    $(Q)$(MAKE) $(build)=$@tools: prepare$(filter-out tools, $(u-boot-dirs)): tools

$(Q)$(MAKE) $(build)=$@这个命令,引入各个目录下的Kbuild或Makefile,依据Makefile.build中定义的规则构建目标

  ● 如果定义了hostprogs-y,则引入Makefile.host,构建目标  ● 如果定义了obj-y,依据Makefile.build中的规则构建各个.o文件,并最终链接成build-in.o文件

下面以构建arch/arm/cpu/armv8为例,简单说明:

arch/arm/cpu/armv8 : prepare scripts    $(Q)$(MAKE) $(build)=$@

执行make -f scripts/basic/Makefile.build obj=arch/arm/cpu/armv8
在Makefile.build中,include arch/arm/cpu/armv8/Makefile:

extra-y := start.oobj-y   += cpu.oifndef  CONFIG_ROCKCHIPobj-y   += generic_timer.oendifobj-y   += cache_v8.oobj-y   += exceptions.oobj-y   += cache.oobj-y   += tlb.oobj-y   += transition.o

接着看Makefile.build:

ifneq ($(strip $(obj-y) $(obj-m) $(obj-n) $(obj-) $(subdir-m) $(lib-target)),)builtin-target := $(obj)/built-in.oendif

在arch/arm/cpu/armv8/Makefile中定义了obj-y,所以builtin-target := arch/arm/cpu/armv8/built-in.o
所以,__build的构建规则

__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \     $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \     $(subdir-ym) $(always)    @:

展开后

__build: arch/arm/cpu/armv8/built-in.o arch/arm/cpu/armv8/start.o    @:

build-in.o依赖$(obj-y),也就是在arch/arm/cpu/armv8/Makefile中定义的那些.o文件:

cmd_link_o_target = $(if $(strip $(obj-y)),\              $(LD) $(ld_flags) -r -o $@ $(filter $(obj-y), $^) \              $(cmd_secanalysis),\              rm -f $@; $(AR) rcs$(KBUILD_ARFLAGS) $@)$(builtin-target): $(obj-y) FORCE    $(call if_changed,link_o_target)

.o文件使用如下规则构建:

cmd_cc_o_c = $(CC) $(c_flags) -c -o $(@D)/.tmp_$(@F) $<$(obj)/%.o: $(src)/%.c $(recordmcount_source) FORCE    $(call cmd,force_checksrc)    $(call if_changed_rule,cc_o_c)cmd_as_o_S       = $(CC) $(a_flags) -c -o $@ $<$(obj)/%.o: $(src)/%.S FORCE    $(call if_changed_dep,as_o_S)  

执行cmd_cc_o_c定义的命令将.c编译成.o,执行cmd_as_o_S定义的命令将.S编译成.o。
最后,将编译出来的.o文件使用cmd_link_o_target链接成arch/arm/cpu/armv8/build-in.o。
其他目录的的执行过程也是类似的,就不做进一步分析了。

4-6、构建u-boot

执行完构建$(u-boot-dirs)的规则之后,将源文件编译成了.o目标文件,下一步就是链接了。先构建链接脚本u-boot.lds:

quiet_cmd_cpp_lds = LDS     $@cmd_cpp_lds = $(CPP) -Wp,-MD,$(depfile) $(cpp_flags) $(LDPPFLAGS) -ansi \        -D__ASSEMBLY__ -x assembler-with-cpp -P -o $@ $<u-boot.lds: $(LDSCRIPT) prepare FORCE    $(call if_changed_dep,cpp_lds)

接着链接生成u-boot:

quiet_cmd_u-boot__ ?= LD      $@      cmd_u-boot__ ?= $(LD) $(LDFLAGS) $(LDFLAGS_u-boot) -o $@ \      -T u-boot.lds $(u-boot-init)                             \      --start-group $(u-boot-main) --end-group                 \      $(PLATFORM_LIBS) -Map u-boot.mapu-boot: $(u-boot-init) $(u-boot-main) u-boot.lds    $(call if_changed,u-boot__)ifeq ($(CONFIG_KALLSYMS),y)    $(call cmd,smap)    $(call cmd,u-boot__) common/system_map.oendif
4-7、RKLoader_uboot.bin
RKLoader_uboot.bin: u-boot.binifdef CONFIG_SECOND_LEVEL_BOOTLOADER    $(if $(CONFIG_MERGER_MINILOADER), ./tools/boot_merger ./tools/rk_tools/RKBOOT/$(RKCHIP)MINIALL.ini &&) \    $(if $(CONFIG_MERGER_TRUSTIMAGE), ./tools/trust_merger $(if $(CONFIG_RK_TRUSTOS), --subfix) \                            ./tools/rk_tools/RKTRUST/$(RKCHIP)TRUST.ini &&) \    $(if $(CONFIG_MERGER_TRUSTOS), ./tools/loaderimage --pack --trustos $(RK_TOS_BIN) trust.img &&) \    ./tools/loaderimage --pack --uboot u-boot.bin uboot.imgelse    ./tools/boot_merger --subfix "$(RK_SUBFIX)" ./tools/rk_tools/RKBOOT/$(RKCHIP).iniendif # CONFIG_SECOND_LEVEL_BOOTLOADER
u-boot.bin: u-boot FORCE    $(call if_changed,objcopy)    $(call DO_STATIC_RELA,$<,$@,$(CONFIG_SYS_TEXT_BASE))    $(BOARD_SIZE_CHECK)

使用objcopy从u-boot生成u-boot.bin二进制文件,根据CONFIG_SECOND_LEVEL_BOOTLOADER是否配置成二级Loader,生成最终的Uboot镜像文件。

三、总结

make rk3399_box_defconfig导入默认的配置,生成.config文件;在make时,根据.config、Makefile和各个.h头文件中定义的CONFIG_XXX变量,决定要编译哪些模块;编译时进入各个工程目录,根据工程目录下的Makefile内容,执行编译过程。如果Makefile中定义了hostprogs-y,则使用scripts/Makefile.host中定义的规则构建目标;如果定义了obj-y,则根据scripts/basic/Makefile.build中的规则构建目标,最终生成uboot镜像。

原创粉丝点击