[ARM Linux系统移植] U-Boot make 分析
配置生成 .config 文件的编译过程在上一篇笔记:[ARM Linux系统移植] U-Boot make ???_defconfig 分析。
生成配置文件的下一步就是编译生成 u-boot.bin 文件,接下来就跟着学习文档过一遍 u-boot.bin 文件是如何生成的。
make 过程
由于此时 make 命令没有指定参数,所以会将第一个目标(_all)作为默认目标。因为不单独编译模块,所以 KBUILD_EXTMOD 变量为空;这时,_all 目标依赖于 all 目标。
- # That's our default target when none is given on the command line
- PHONY := _all
- _all:
- PHONY += all
- ifeq ($(KBUILD_EXTMOD),)
- _all: all
- else
- _all: modules
- endif
因为 all 目标规则中的命令都是打印命令,所以这边对命令先不做关注,将注意力放在 ALL-y 这个依赖上。ALL-y 变量包括很多内容,这边只摘取了一部分。如第 733 行这样用到 CONFIG_ 前缀变量的形式,如果在 .config 文件中定义了这个变量为 y,那么展开就为 ALL-y,会为其添加特定的依赖目标。在第 731 行发现了生成 u-boot.bin 文件的 u-boot.bin 目标,所以再将注意力转移到这个目标上来。
- all: $(ALL-y)
- ALL-y += u-boot.srec u-boot.bin u-boot.sym System.map u-boot.cfg binary_size_check
- ALL-$(CONFIG_ONENAND_U_BOOT) += u-boot-onenand.bin
因为在指定生成的 .config 文件中没有定义 CONFIG_OF_SEPARATE 变量,所以以下选择分支选择的 else 分支。此时 u-boot.bin 目标依赖于 u-boot-nodtb.bin 和 FORCE 目标。因为 FORCE 目标只是个伪目标,确保指令每次都能执行,所以只需要关注 u-boot-nodtb.bin 目标。规则中的命令是将 u-boot-nodtb.bin 文件复制到 u-boot.bin 文件,详细信息可见附录里关于 if_changed 函数的分析。
- ifeq ($(CONFIG_OF_SEPARATE),y)
- u-boot-dtb.bin: u-boot-nodtb.bin dts/dt.dtb FORCE
- $(call if_changed,cat)
- u-boot.bin: u-boot-dtb.bin FORCE
- $(call if_changed,copy)
- else
- u-boot.bin: u-boot-nodtb.bin FORCE
- $(call if_changed,copy)
- endif
u-boot-nodtb.bin 目标又依赖于 u-boot 目标。u-boot 依赖的 u-boot.lds 目标和链接脚本文件的生成相关,这篇笔记先关注依赖中 u-boot-init 和 u-boot-main 这两个变量。u-boot-init 变量等于 head-y 变量,u-boot-main 变量等于 libs-y 变量。
- u-boot-nodtb.bin: u-boot FORCE
- u-boot: $(u-boot-init) $(u-boot-main) u-boot.lds FORCE
- $(call if_changed,u-boot__)
- u-boot-init := $(head-y)
- u-boot-main := $(libs-y)
当前开发板环境下 head-y 变量定义在 arch/arm/Makefile 文件里,其值为 arch/arm/cpu/armv7/start.o 。
- head-y := arch/arm/cpu/$(CPU)/start.o
libs-y 变量的内容非常多,下面摘取了一部分,可以看到都是首先定义了各个目录。然后在第 676 行会将 libs-y 变量转化成这些目录下的 built-in.o 文件名。built-in.o 文件是如何而来的,这部分会在 linux 顶层 makefile 中详细说明,这边可以先了解一个大概:以 drivers\gpio 目录举例,依据附录里的分析,可以看到其目录下有一个 .built-in.o.cmd 文件。从这个文件里看到 built-in.o 文件是由目录下各个由 .c 文件编译得到的 .o 文件链接所得到的。
- libs-y += lib/
- libs-$(HAVE_VENDOR_COMMON_LIB) += board/$(VENDOR)/common/
- libs-$(CONFIG_OF_EMBED) += dts/
- libs-y += fs/
- libs-y += net/
- libs-y += disk/
- libs-y += drivers/
- libs-y += drivers/dma/
- libs-y += drivers/gpio/
- libs-y += drivers/i2c/
- libs-y += drivers/mmc/
- libs-y += drivers/mtd/
- libs-y := $(patsubst %/, %/built-in.o, $(libs-y))
- cmd_drivers/gpio/built-in.o := arm-linux-gnueabihf-ld.bfd -r -o drivers/gpio/built-in.o drivers/gpio/mxc_gpio.o
总结
由图 1 和以下 u-boot 目标执行的命令来看,最终会将各个 .o 文件链接成 u-boot.bin 文件。
- 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
同时可以通过打印的信息查看一下最终的链接命令,打印出来的内容如图 2 所示,符合上述的理论分析。

附录 if_changed
在 u-boot.bin 的相关规则中有用到 if_changed 这个函数。就下方这条规则中,if_changed 的主要作用就是将 u-boot-nodtb.bin 文件拷贝到 u-boot.bin 文件。
- u-boot.bin: u-boot-nodtb.bin FORCE
- $(call if_changed,copy)
if_changed 函数在 Kbuild.include 里定义。里面的 if 函数判断如 if_changed 函数的名称一样,只要发生变化(依赖比目标新)的时候才会生效,这边就先不关注 any-prereq 等变量的定义了。命令的第一句 set -e 是设置命令执行没有返回 true 就退出,以免引发更大的错误。
- # 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)); \
- printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd)
if_changed 函数中第二句的 echo-cmd 变量定义如下,它会回显这句执行命令。其中的 escsq 函数是为了将 ' 转义成 \':因此就例子中的 cp 命令而言,还是原本的命令。需要特别注意的是 echo-cmd 变量最后面的分号,makefile 中命令如果不换行的话可以使用分号分割命令,所以 echo-cmd 变量后面跟着的 cmd_$(1) 变量就起到执行命令的作用:例子中对应的 cmd_copy 变量,起到复制文件的作用。
- # echo command.
- # Short version is used, if $(quiet) equals `quiet_', otherwise full one.
- echo-cmd = $(if $($(quiet)cmd_$(1)),\
- echo ' $(call escsq,$($(quiet)cmd_$(1)))$(echo-why)';)
- quiet_cmd_copy = COPY $@
- cmd_copy = cp $< $@
这是一个不具通配符能力的“搜索和替换”函数。它最常用于在文件名列表中将一个扩展名替换成另一个扩展名:
sources := count_words.c counter.c lexer.c
object := $(subst .c,.o,$(sources))
这可以在 $(sources) 中将所有出现 .c 字样的地方都替换成 .o,或者较一般的说法是,以“替换字符串”取代所有出现“搜索字符串”的地方。
if_changed 函数中最后一句是将执行命令的相关信息输出到文件里,而文件名由 dot-target 变量指定。dot-target 变量的制定规则是目标的目录名和文件名之间加一个点号,如注释里举的例子,foo/bar.o 会对应 foo/.bar.o 。因此 u-boot.bin 规则对应的写入文件名称是 .u-boot.bin.cmd,里面的内容也正是之前理论分析出来的内容。
- ###
- # Name of target with a '.' as filename prefix. foo/bar.o => foo/.bar.o
- dot-target = $(dir $@).$(notdir $@)
- cmd_u-boot.bin := cp u-boot-nodtb.bin u-boot.bin