先看终极目标
在配置完毕的情况下,我们在uboot根目录敲下”make”命令,就会执行一系列动作,产生一系列结果。这结果之一,便是u-boot.bin。这个u-boot.bin是一个二进制执行代码文件,理论上可以被CPU直接执行。但是对于imx6来说,不拿这个直接烧写,还是需要再处理一下才行,这个后续会提到。
只要是查看主Makefile的执行流程,我们第一要做的,是找到这个Makefile的”终极目标”,找到了终极目标,就跟找到C语言程序的main函数一样,找到了入口了。我来看下这个终极目标,主Makefile第129行,如下:
# That's our default target when none is given on the command line
PHONY := _all
_all:
就跟注释说明的一样,这个就是默认目标,也就是终极目标。但是这里的_all目标,既没有依赖,也没有命令,这是在干嘛?其实,熟悉make的朋友一定清楚,所谓终极目标,是写在Makefile里面的第一个目标,假设我们把上面这个不知道干嘛的规则去掉,我们会发现,第一个规则就编程了下面这条了,133行:
$(CURDIR)/Makefile Makefile: ;
这个变成了终极目标,也就是这个make的最终目标,是为了构建这两个Makefile?那岂不是搞错方向了啊。但是诸如这样的规则,其实还是有很多的,如果少了上面的空_all目标,底下指不定谁就莫名其妙的成了终极目标。所以,有了以上_all空目标,就好比占了个坑位,意思是说,终极目标这个位置,我”_all”已经占用了,你们不要再惦记了,想也不要想,想也有罪。但是,事实就是事实,这个规则终归是空的呀。别急,坑我们先占了,坑里的内容嘛,我们后面可以补上,我们看Makefile第196行:
# 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
怎么样,这里比刚才要好一点了,多了个all依赖。终归是更进了一步。我们再看Makefile第802行:
all: $(ALL-y)
ifneq ($(CONFIG_SYS_GENERIC_BOARD),y)
@echo "===================== WARNING ======================"
@echo "Please convert this board to generic board."
@echo "Otherwise it will be removed by the end of 2014."
@echo "See doc/README.generic-board for further information"
@echo "===================================================="
endif
ifeq ($(CONFIG_DM_I2C_COMPAT),y)
@echo "===================== WARNING ======================"
@echo "This board uses CONFIG_DM_I2C_COMPAT. Please remove"
@echo "(possibly in a subsequent patch in your series)"
@echo "before sending patches to the mailing list."
@echo "===================================================="
endif
这里all成为一个新规则的目标,这个规则的依赖是$(ALL-y)。于是,我们的目标就是展开这个ALL-y,看看这里面有些什么。且看下一小节分解。
找到我们的u-boot.bin
上面说到,all依赖的那个ALL-y变量。我们找到这个变量赋值的地方,Makefile第731行开始:
# Always append ALL so that arch config.mk's can add custom ones
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
ifeq ($(CONFIG_SPL_FSL_PBL),y)
ALL-$(CONFIG_RAMBOOT_PBL) += u-boot-with-spl-pbl.bin
else
ifneq ($(CONFIG_SECURE_BOOT), y)
# For Secure Boot The Image needs to be signed and Header must also
# be included. So The image has to be built explicitly
ALL-$(CONFIG_RAMBOOT_PBL) += u-boot.pbl
endif
endif
ALL-$(CONFIG_SPL) += spl/u-boot-spl.bin
ALL-$(CONFIG_SPL_FRAMEWORK) += u-boot.img
ALL-$(CONFIG_TPL) += tpl/u-boot-tpl.bin
ALL-$(CONFIG_OF_SEPARATE) += u-boot.dtb
ifeq ($(CONFIG_SPL_FRAMEWORK),y)
ALL-$(CONFIG_OF_SEPARATE) += u-boot-dtb.img
endif
ALL-$(CONFIG_OF_HOSTFILE) += u-boot.dtb
ifneq ($(CONFIG_SPL_TARGET),)
ALL-$(CONFIG_SPL) += $(CONFIG_SPL_TARGET:"%"=%)
endif
ALL-$(CONFIG_REMAKE_ELF) += u-boot.elf
ALL-$(CONFIG_EFI_APP) += u-boot-app.efi
ALL-$(CONFIG_EFI_STUB) += u-boot-payload.efi
ifneq ($(BUILD_ROM),)
ALL-$(CONFIG_X86_RESET_VECTOR) += u-boot.rom
endif
# enable combined SPL/u-boot/dtb rules for tegra
ifeq ($(CONFIG_TEGRA)$(CONFIG_SPL),yy)
ALL-y += u-boot-tegra.bin u-boot-nodtb-tegra.bin
ALL-$(CONFIG_OF_SEPARATE) += u-boot-dtb-tegra.bin
endif
# Add optional build target if defined in board/cpu/soc headers
ifneq ($(CONFIG_BUILD_TARGET),)
ALL-y += $(CONFIG_BUILD_TARGET:"%"=%)
endif
看来这个变量赋值还是挺多的,首先,一定依赖的目标有:
u-boot.srec/u-boot.bin/u-boot.sym/System.map/u-boot.cfg/binary_size_check
其它都是根据配置来选择。我们看到,这里出现了我们要寻找的那个目标:u-boot.bin。其它依赖因为跟本文关系不大,我们先不管它。我们来找下u-boot.bin目标,见Makefile第825行开始:
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
这里的CONFIG_OF_SEPARATE其实是没有设置的,所以u-boot.bin的规则如下:
u-boot.bin: u-boot-nodtb.bin FORCE
$(call if_changed,copy)
这里u-boot.bin依赖u-boot-nodtb.bin,且使用if_changed函数来更新u-boot.bin。在
《kbuild框架简要分析》
里面,我们分析过这个函数,在此我们可以把这个函数展开看看,到底长成啥样:
$(if $(strip $(any-prereq) $(arg-check)), \
@set -e; \
$(echo-cmd) $(cmd_copy); \
printf '%s\n' 'cmd_u-boot.bin := $(make-cmd)' > $(dot-target).cmd)
这里any-prereq表示所有比目标更新或者还未生成的依赖文件,u-boot.bin还没有,所以这里展开就不为空,那么上面这条命令就会展开为:
@set -e; $(echo-cmd) $(cmd_copy); printf '%s\n' 'cmd_u-boot.bin := $(make-cmd)' > $(dot-target).cmd)
很显然,这里调用了cmd_copy命令,我们在Makefile第823行找到了cmd_copy:
quiet_cmd_copy = COPY $@
cmd_copy = cp $< $@
这条命令仅仅就是试用cp命令,拷贝依赖文件为目标文件,也就是说,u-boot.bin和u-boot-nodtb.bin应该是一样的文件。好了,我们再去看下这个依赖文件u-boot-nodtb.bin是怎么生成的,见Makefile第866行:
u-boot-nodtb.bin: u-boot FORCE
$(call if_changed,objcopy)
$(call DO_STATIC_RELA,$<,$@,$(CONFIG_SYS_TEXT_BASE))
$(BOARD_SIZE_CHECK)
这个u-boot-nodtb.bin依赖u-boot,且使用了底下三个命令来更新自己。第一个是使用cmd_objcopy命令,我们在Makefile第780行找到了这个命令:
# Normally we fill empty space with 0xff
quiet_cmd_objcopy = OBJCOPY $@
cmd_objcopy = $(OBJCOPY) --gap-fill=0xff $(OBJCOPYFLAGS) \
$(OBJCOPYFLAGS_$(@F)) $< $@
看到没有,这里调用了objcopy程序,在我们的交叉编译环境下,这个命令是arm-poky-linux-gnueabi-objcopy。具体这个objcopy命令怎么使用的,大家还是自己搜索下看看,这里不展开了。这里还使用了DO_STATIC_RELA函数,道理都是一样的,能在Makefile里面找到定义,大家不妨自己找一下,看看展开是怎么样的,我们把目光集中在过程上面,接下重点跟踪下u-boot。
千呼万唤始出来
其实我们发现,最重要的角色,往往都是后面出场的,需要一些小兵来打个头阵。这个u-boot,可以说是相当重要了,因为它直接决定我们的u-boot是怎么构建出来的,我们来看下Makefile第1170行:
u-boot: $(u-boot-init) $(u-boot-main) 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依赖有4个,FORCE是个伪目标,用来保证这个规则的命令总是会被执行,实际上,真正有价值的依赖是前面三个,我们划重点划起来:
(1)$(u-boot-init)
(2)$(u-boot-main)
(3)u-boot.lds
这里我们面临一个两难的选择,是先分析这三个依赖呢,还是先分析规则的命令?因为任何一个展开去,都是一大堆东西,我怕几匹马都拉不回来,那很容易走丢的。万般纠结之后,我决定还是先分析命令,因为这个命令直接关系到”u-boot”的生成方法,我得先了解一下。了解命令之前,我们暂且记住这三个依赖,这笔账我们后面再算,会回来的。
话说if_changed函数我们已经不陌生了,这个函数会根据依赖是否非空,构造一个用参数替换的命令出来,我们展开下,看得清楚些:
@set -e; $(echo-cmd) $(cmd_u-boot__); printf '%s\n' 'cmd_u-boot := $(make-cmd)' > $(dot-target).cmd)
这里会展开执行$(cmd_u-boot__)命令,我找下这个命令的定义,Makefile第1158行,如下:
# Rule to link u-boot
# May be overridden by arch/$(ARCH)/config.mk
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
这个cmd_u-boot__是不是挺容易看懂的?就是调用ld链接器,生成u-boot,使用u-boot.lds作为链接脚本,链接的目标对象应该是分布在$(u-boot-init)和$(u-boot-main)里面,也会链接进去$(PLATFORM_LIBS)作为库,最后还会生成u-boot.map。这个ld链接之后,应该会生成一个可执行的ELF程序,就是我们分析的”u-boot”。好了,u-boot的命令我先分析到这里。接下来我们回到之前岔开的地方,去分析那三个依赖。
把马甲一层层扒开,看个清楚
这三个依赖,最后一个是u-boot.lds,是个链接脚本,这很明确,没有展开的必要了。那么,我们的工作变成要展开前两个变量,就是$(u-boot-init)和$(u-boot-main)。在主Makefile第678行,如下:
u-boot-init := $(head-y)
u-boot-main := $(libs-y)
这里的变量定义已经非常清楚了,这个libs-y倒是很清楚,有很多地方定义了,但是这个head-y我们并没有在主Makefile里面找到。实际上,这个难找的家伙还是被我找到了,具体的方法这里不展开,这里就讲结果吧,我们看到主Makefile第524行:
include arch/$(ARCH)/Makefile
这里的ARCH我们已经配置为arm,那么这里就是:
include arch/arm/Makefile
我们打开这个Makefile,看第70行,如下:
head-y := arch/arm/cpu/$(CPU)/start.o
这个就是head-y的定义了。收藏。
那么libs-y呢,这个定义的地方就多了,除了主Makefile里面,各个子目录的Makefile里面,都可能定义libs-y。我通过打印,得到这个u-boot-main变量的值,如下:
arch/arm/cpu/built-in.o
arch/arm/cpu/armv7/built-in.o
arch/arm/imx-common/built-in.o
arch/arm/lib/built-in.o
board/myir/mys_imx6ull/built-in.o
cmd/built-in.o
common/built-in.o
disk/built-in.o
drivers/built-in.o
drivers/dma/built-in.o
drivers/gpio/built-in.o
drivers/i2c/built-in.o
drivers/mmc/built-in.o
drivers/mtd/built-in.o
drivers/mtd/nand/built-in.o
drivers/mtd/onenand/built-in.o
drivers/mtd/spi/built-in.o
drivers/net/built-in.o
drivers/net/phy/built-in.o
drivers/pci/built-in.o
drivers/power/built-in.o
drivers/power/battery/built-in.o
drivers/power/fuel_gauge/built-in.o
drivers/power/mfd/built-in.o
drivers/power/pmic/built-in.o
drivers/power/regulator/built-in.o
drivers/serial/built-in.o
drivers/spi/built-in.o
drivers/usb/dwc3/built-in.o
drivers/usb/emul/built-in.o
drivers/usb/eth/built-in.o
drivers/usb/gadget/built-in.o
drivers/usb/gadget/udc/built-in.o
drivers/usb/host/built-in.o
drivers/usb/musb-new/built-in.o
drivers/usb/musb/built-in.o
drivers/usb/phy/built-in.o
drivers/usb/ulpi/built-in.o
fs/built-in.o
lib/built-in.o
net/built-in.o
test/built-in.o
test/dm/built-in.o
这一堆的目标文件,而且格式还高度统一,都是built-in.o,只是分布在不同的目录而已。
好了,这两个变量我们都展开了,接下来我们总结下,就是说这个u-boot吧,是由head-y、libs-y和一些内部库通过ld链接而成的。这个head-y是一个叫做start.o的目标文件,libs-y是一堆叫做built-in.o的目标文件。为啥这两个要这么分开处理,原因是这个链接是讲究顺序的,关于启动的代码,比较关键,需要链接放到执行程序的最前面。
依赖依赖,还是依赖
其实你们发现没有,这个kbuild构建的makefile,就是一层层不停地依赖下去,每个依赖完成特定的功能,以此达到逻辑的相对分离,再一次佩服这帮人的软件工程能力。这里我想多说一点,我们国内为什么在底层软件,特别是OS层面这么弱?其实跟我们的软件工程能力有关系,我们对软件工程太不重视了,软件工程就好比建筑立面的建筑框架,一个好的框架搭建方式,是可以完美撑起一座高楼大厦的,框架搭不好,这个楼是盖不起来的。国内对于算法的重视程度,要远远高于软件工程,这个其实是很不科学的。
好吧,不多说了,说多了也没啥用,我们看我们的。其实我们发现,这个u-boot-init和u-boot-main有共同的依赖,主Makefile第1190行:
$(sort $(u-boot-init) $(u-boot-main)): $(u-boot-dirs) ;
u-boot-dirs变量定义在Makefile第672行,如下:
u-boot-dirs := $(patsubst %/,%,$(filter %/, $(libs-y))) tools examples
这里通过make函数取得libs-y里面的所有目录,再加上tools,examples。我也对这个变量进行的打印,实际输出为:
arch/arm/cpu
arch/arm/cpu/armv7
arch/arm/imx-common
arch/arm/lib
board/myir/mys_imx6ull
cmd
common
disk
drivers
drivers/dma
drivers/gpio
drivers/i2c
drivers/mmc
drivers/mtd
drivers/mtd/nand
drivers/mtd/onenand
drivers/mtd/spi
drivers/net
drivers/net/phy
drivers/pci
drivers/power
drivers/power/battery
drivers/power/fuel_gauge
drivers/power/mfd
drivers/power/pmic
drivers/power/regulator
drivers/serial
drivers/spi
drivers/usb/dwc3
drivers/usb/emul
drivers/usb/eth
drivers/usb/gadget
drivers/usb/gadget/udc
drivers/usb/host
drivers/usb/musb-new
drivers/usb/musb
drivers/usb/phy
drivers/usb/ulpi
fs
lib
net
test
test/dm
tools
examples
然后,这个u-boot-dirs又作为目标,形成一个新的规则。Makefile第1199行,有这个依赖的规则:
PHONY += $(u-boot-dirs)
$(u-boot-dirs): prepare scripts
$(Q)$(MAKE) $(build)=$@
这个又依赖prepare和scripts。这里有个命令,通过build变量来引入Makefile.build。这里我们要注意的是,这个u-boot-dirs展开是上面打印的那么多个目录,通过这里的命令及Makefile.build会各自包含进各自目录下面的Makefile。这样就达到了编译各个目录内部代码的一个方法。后面我们详细分析built-in.o时具体分析。
在执行这个命令之前,需要去执行prepare,这个prepare做了很多准备工作,我们代码放这里,就不展开分析了:
# Things we need to do before we recursively start building the kernel
# or the modules are listed in "prepare".
# A multi level approach is used. prepareN is processed before prepareN-1.
# archprepare is used in arch Makefiles and when processed asm symlink,
# version.h and scripts_basic is processed / created.
# Listed in dependency order
PHONY += prepare archprepare prepare0 prepare1 prepare2 prepare3
# prepare3 is used to check if we are building in a separate output directory,
# and if so do:
# 1) Check that make has not been executed in the kernel src $(srctree)
prepare3: include/config/uboot.release
ifneq ($(KBUILD_SRC),)
@$(kecho) ' Using $(srctree) as source for U-Boot'
$(Q)if [ -f $(srctree)/.config -o -d $(srctree)/include/config ]; then \
echo >&2 " $(srctree) is not clean, please run 'make mrproper'"; \
echo >&2 " in the '$(srctree)' directory.";\
/bin/false; \
fi;
endif
# prepare2 creates a makefile if using a separate output directory
prepare2: prepare3 outputmakefile
prepare1: prepare2 $(version_h) $(timestamp_h) \
include/config/auto.conf
ifeq ($(CONFIG_HAVE_GENERIC_BOARD),)
ifeq ($(CONFIG_SYS_GENERIC_BOARD),y)
@echo >&2 " Your architecture does not support generic board."
@echo >&2 " Please undefine CONFIG_SYS_GENERIC_BOARD in your board config file."
@/bin/false
endif
endif
ifeq ($(wildcard $(LDSCRIPT)),)
@echo >&2 " Could not find linker script."
@/bin/false
endif
archprepare: prepare1 scripts_basic
prepare0: archprepare FORCE
$(Q)$(MAKE) $(build)=.
# All the preparing..
prepare: prepare0