0、说明
本文基于U-Boot 2022.01-v2.07
版本进行分析。
1、u-boot的编译过程
-
1.1、参考资料
u-boot的编译系统介绍可以参考:U-Boot 完全分析与移植,这篇文章。 -
1.2、编译入口
在执行make
编译u-boot时,由于没有指定目标,因此执行的是顶层Makefile中的第一个目标。在顶层Makefile中,从文件开头往下查找第一个编译目标,会找到如下部分:# That's our default target when none is given on the command line PHONY := _all _all:
会发现这里的
_all
没有做任何操作,也没有任何依赖,继续往下找,会发现_all
会重载:# Use make M=dir to specify directory of external module to build # Old syntax make ... SUBDIRS=$PWD is still supported # Setting the environment variable KBUILD_EXTMOD take precedence ifdef SUBDIRS KBUILD_EXTMOD ?= $(SUBDIRS) endif ifeq ("$(origin M)", "command line") KBUILD_EXTMOD := $(M) endif # If building an external module we do not care about the all: rule # but instead _all depend on modules PHONY += all ifeq ($(KBUILD_EXTMOD),) _all: all else _all: modules endif
由于执行
make
是我们没有指定M=dir
,因此这里的KBUILD_EXTMOD
就为空,ifeq ($(KBUILD_EXTMOD),)
就为真,最终_all:
被重载为_all: all
,如下:# 找到的第一个目标 PHONY := _all _all: # KBUILD_EXTMOD为空,进行了重载 PHONY += all _all: all
这里
_all
依赖于all
,需要确定all
做了哪些事情,继续往下找:all: .binman_stamp inputs ifeq ($(CONFIG_BINMAN),y) $(call if_changed,binman) endif
这里会发现,
all
依赖于.binman_stamp
和inputs
,重点在inputs
:PHONY += inputs inputs: $(INPUTS-y)
继续看
INPUTS-y
的赋值:INPUTS-y += u-boot.srec u-boot.bin u-boot.sym System.map binary_size_check
INPUTS-y
有多个值,重点关注u-boot.bin
,其取决于uboot的配置项。在我的配置项中,其取值如下:u-boot.bin: u-boot-dtb.bin FORCE $(call if_changed,copy)
继续看
u-boot-dtb.bin
,就在它上面:u-boot-dtb.bin: u-boot-nodtb.bin dts/dt.dtb FORCE $(call if_changed,cat)
dts/dt.dtb
是来编译设备树的,重点看u-boot-nodtb.bin
:u-boot-nodtb.bin: u-boot FORCE $(call if_changed,objcopy_uboot) $(BOARD_SIZE_CHECK)
继续看
u-boot
:u-boot: $(u-boot-init) $(u-boot-main) $(u-boot-keep-syms-lto) u-boot.lds FORCE +$(call if_changed,u-boot__) ifeq ($(CONFIG_KALLSYMS),y) $(call cmd,smap) $(call cmd,u-boot__) common/system_map.o endif
u-boot-keep-syms-lto
不用管,u-boot.lds
是用于生成链接脚本的,暂且不管。重点看u-boot-init
和u-boot-main
:u-boot-init := $(head-y) u-boot-main := $(libs-y)
head-y
的定义在arch\arm\Makefile文件中,用于编译对应目录中的start.s文件:head-y := arch/arm/cpu/$(CPU)/start.o
libs-y
还在顶层Makefile中,主要是指定需要编译哪些目录,部分内容如下:libs-$(CONFIG_API) += api/ libs-$(HAVE_VENDOR_COMMON_LIB) += board/$(VENDOR)/common/ libs-y += boot/ libs-y += cmd/ libs-y += common/ libs-$(CONFIG_OF_EMBED) += dts/ libs-y += env/ libs-y += lib/ libs-y += fs/ libs-y += net/ libs-y += disk/ libs-y += drivers/ libs-$(CONFIG_SYS_FSL_DDR) += drivers/ddr/fsl/ libs-$(CONFIG_SYS_FSL_MMDC) += drivers/ddr/fsl/ libs-$(CONFIG_$(SPL_)ALTERA_SDRAM) += drivers/ddr/altera/ libs-y += drivers/usb/cdns3/ libs-y += drivers/usb/dwc3/ libs-y += drivers/usb/common/ libs-y += drivers/usb/emul/ libs-y += drivers/usb/eth/ libs-$(CONFIG_USB_DEVICE) += drivers/usb/gadget/ libs-$(CONFIG_USB_GADGET) += drivers/usb/gadget/ libs-$(CONFIG_USB_GADGET) += drivers/usb/gadget/udc/ libs-y += drivers/usb/host/ libs-y += drivers/usb/mtu3/ libs-y += drivers/usb/musb/ libs-y += drivers/usb/musb-new/ libs-y += drivers/usb/phy/ libs-y += drivers/usb/ulpi/ ifdef CONFIG_POST libs-y += post/ endif libs-$(CONFIG_UNIT_TEST) += test/ libs-$(CONFIG_UT_ENV) += test/env/ libs-$(CONFIG_UT_OPTEE) += test/optee/ libs-$(CONFIG_UT_OVERLAY) += test/overlay/ libs-$(CONFIG_CMD_HTTPD) += httpd/ libs-y += $(if $(BOARDDIR),board/$(BOARDDIR)/) libs-y := $(sort $(libs-y)) u-boot-dirs := $(patsubst %/,%,$(filter %/, $(libs-y))) tools examples u-boot-alldirs := $(sort $(u-boot-dirs) $(patsubst %/,%,$(filter %/, $(libs-)))) libs-y := $(patsubst %/, %/built-in.o, $(libs-y))
至此,需要编译哪些文件/目录已经清楚了,接下来需要看如何编译了。
-
1.3、编译过程
需要编译的文件已经准备好了,如何编译需要从此处开始:$(sort $(u-boot-init) $(u-boot-main)): $(u-boot-dirs) ;
这里首先会对
u-boot-init
和u-boot-main
进行排序,然后它们的每一个值都依赖于u-boot-dirs
:PHONY += $(u-boot-dirs) $(u-boot-dirs): prepare scripts $(Q)$(MAKE) $(build)=$@
$(Q)$(MAKE) $(build)=$@
就是具体的编译动作了,其中build
定义在scripts\Kbuild.include
中,这个文件顶层Makefile有包含,build
如下:### # Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj= # Usage: # $(Q)$(MAKE) $(build)=dir build := -f $(srctree)/scripts/Makefile.build obj
最终变成了:
PHONY += $(u-boot-dirs) $(u-boot-dirs): prepare scripts $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.build obj=$@
具体的编译是由
scripts/Makefile.build
里完成的。 -
1.3、
scripts/Makefile.build
编译分析
scripts/Makefile.build
中的第一个目标为:PHONY := __build __build: __build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) $(subdir-ym) $(always) @:
builtin-target
的值:ifneq ($(strip $(obj-y) $(obj-m) $(obj-) $(subdir-m) $(lib-target)),) builtin-target := $(obj)/built-in.o endif
lib-target
的值:ifneq ($(strip $(lib-y) $(lib-m) $(lib-)),) lib-target := $(obj)/lib.a endif
重点看
subdir-ym
这个依赖:
subdir-ym
的值定义在scripts\Makefile.lib
中:__subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y))) subdir-y += $(__subdir-y) __subdir-m := $(patsubst %/,%,$(filter %/, $(obj-m))) subdir-m += $(__subdir-m) subdir-ym := $(sort $(subdir-y) $(subdir-m))
这里可以看出
subdir-ym
的值是取出子目录的Makefile中定义的obj-y
指定的.o文件。
在scripts/Makefile.build
中subdir-ym
的命令如下:PHONY += $(subdir-ym) $(subdir-ym): $(Q)$(MAKE) $(build)=$@
也即:
PHONY += $(subdir-ym) $(subdir-ym): $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.build obj=$@
也就是递归编译子目录中的文件。
这里需要注意子目录的Makefile文件是需要包含的,它的包含在scripts\Makefile.build
中定义:# 得到子目录路径 prefix := tpl src := $(patsubst $(prefix)/%,%,$(obj)) ifeq ($(obj),$(src)) prefix := spl src := $(patsubst $(prefix)/%,%,$(obj)) ifeq ($(obj),$(src)) prefix := . endif endif # 包含子目录Makefile kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src)) kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile) include $(kbuild-file)
至此,回到本小节的最开始处,
__build
的依赖就是由一堆.o
、.a
文件组成。那么
.c
转为.o
、.a
的规则呢?
在scripts\Makefile.build
中有定义:$(obj)/%.o: $(src)/%.c $(recordmcount_source) FORCE $(call cmd,force_checksrc) $(call if_changed_rule,cc_o_c)
最终会执行
rule_cc_o_c
,定义如下:define rule_cc_o_c $(call echo-cmd,checksrc) $(cmd_checksrc) \ $(call echo-cmd,cc_o_c) $(cmd_cc_o_c); \ $(cmd_modversions) \ $(call echo-cmd,record_mcount) \ $(cmd_record_mcount) \ scripts/basic/fixdep $(depfile) $@ '$(call make-cmd,cc_o_c)' > \ $(dot-target).tmp; \ rm -f $(depfile); \ mv -f $(dot-target).tmp $(dot-target).cmd endef
然后
rule_cc_o_c
会执行cmd_cc_o_c
:cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $<
-
1.4、总结
# 默认目标: PHONY := _all _all: # _all重载 PHONY += all _all: all # 依赖于all all: .binman_stamp inputs ifeq ($(CONFIG_BINMAN),y) $(call if_changed,binman) endif # 第一步: u-boot: $(u-boot-init) $(u-boot-main) $(u-boot-keep-syms-lto) u-boot.lds FORCE +$(call if_changed,u-boot__) ifeq ($(CONFIG_KALLSYMS),y) $(call cmd,smap) $(call cmd,u-boot__) common/system_map.o endif # 第二步: $(sort $(u-boot-init) $(u-boot-main)): $(u-boot-dirs) ; # 第三步: PHONY += $(u-boot-dirs) $(u-boot-dirs): prepare scripts $(Q)$(MAKE) $(build)=$@ # 第四步 # build定义在scripts\Kbuild.include中,开始递归目录编译 build := -f $(srctree)/scripts/Makefile.build obj