Uboot编译过程分析
本文基于firefly-rk3399开发板的uboot,对其编译过程进行分析。
uboot的编译分为两个步骤:
make ARCHV=aarch64 rk3399_box_defconfig
make
因此,接下来主要分为两部分,对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 := tpl
src := $(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 := fixdep
always := $(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 : %.el
emacs -f batch-byte-compile $<
等效于
bar.o : bar.c
$(CC) -c $(CFLAGS) $< -o $@
lose.o : lose.c
$(CC) -c $(CFLAGS) $< -o $@
foo.elc : foo.el
emacs -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=$1
case $target in #target=rk3399_box_defconfig
*_defconfig)
do_board_defconfig $target;;
...
esac
do_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.o
hostprogs-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.cmd
make -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.mk
endif
包含进了源码*目录的config.mk,这个config.mk定义了一些变量,并且引入了cpu和board目录下的config.mk。
至此,所有的CONFIG_XXX变量(makefile中定义的和.h中的宏定义)就被make包含进了。
3、 u-boot-dirs
接下来,Makefile定义了一些变量
head-y := $(CPUDIR)/start.o
libs-$(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 examples
libs-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))"
endef
include/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)/Kbuild
● Kbuild定义了$(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.o
obj-y += cpu.o
ifndef CONFIG_ROCKCHIP
obj-y += generic_timer.o
endif
obj-y += cache_v8.o
obj-y += exceptions.o
obj-y += cache.o
obj-y += tlb.o
obj-y += transition.o
接着看Makefile.build:
ifneq ($(strip $(obj-y) $(obj-m) $(obj-n) $(obj-) $(subdir-m) $(lib-target)),)
builtin-target := $(obj)/built-in.o
endif
在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.map
u-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.o
endif
4-7、RKLoader_uboot.bin
RKLoader_uboot.bin: u-boot.bin
ifdef 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.img
else
./tools/boot_merger --subfix "$(RK_SUBFIX)" ./tools/rk_tools/RKBOOT/$(RKCHIP).ini
endif # 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镜像。