U-boot顶层Makefile分析及编译流程

目录

    • 一、U-boot顶层Makefile分析
        • 1.1 命令输出
        • 1.2 设置编译结果输出文件夹
        • 1.3 代码检查
        • 1.4 单独编译模块
        • 1.5 设置目标架构、交叉编译器和配置文件
        • 1.6 调用 scripts/Kbuild.include
        • 1.7 交叉编译工具变量设置
    • 二、 make xxx_defconfig过程
    • 三、U-boot的make过程

本篇文章作为对正点原子的驱动开发教程中u-boot的编译流程的梳理,方便回头翻阅。有些放上源码的过程的解析比较简单,但是我在源码中写的注释比较清晰,注意看注释。

一、U-boot顶层Makefile分析

1.1 命令输出

make V=1,即可输出完整命令,make V=0则输出短命令,make -s静默输出(短命令都不输出)

1.2 设置编译结果输出文件夹

make O=out,out为输出文件夹名

1.3 代码检查

uboot 支持代码检查,使用命令“make C=1”使能代码检查,检查那些需要重新编译的文件。
“make C=2”用于检查所有的源码文件

1.4 单独编译模块

make M=dir/make SUBDIRS=dir

1.5 设置目标架构、交叉编译器和配置文件

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
可以直接修改顶层 Makefile,在里面加入 ARCH和CROSS_COMPILE 的定义

1.6 调用 scripts/Kbuild.include

主 Makefile 会调用文件 scripts/Kbuild.include 这个文件,在 uboot 的编译过程中会用到 scripts/Kbuild.include 中的这些变量

1.7 交叉编译工具变量设置

设置-ld,-gcc,-objdump这些

二、 make xxx_defconfig过程

当从顶层文件夹以命令行传入make xxx_defconfig命令的时候,肯定在顶层Makefile中是首先找以xxx_defconfig为目标的规则,但是并没有。不过有这么一段代码:

config-targets := 0
mixed-targets  := 0
dot-config     := 1
# MAKECMDGOALS 是 make 的一个环境变量,这个变量会保存你所指定的终极目标列表,比如执行“make mx6ull_alientek_emmc_defconfig”
# 那么MAKECMDGOALS就为 mx6ull_alientek_emmc_defconfig
...
ifeq ($(KBUILD_EXTMOD),)
        # 如果MAKECMDGOALS中有config %config,就不为空,条件就成立,config-targets变为1
        ifneq ($(filter config %config,$(MAKECMDGOALS)),)
                config-targets := 1
                ifneq ($(words $(MAKECMDGOALS)),1)
                        mixed-targets := 1
                endif
        endif
endif
...
...
ifeq ($(config-targets),1)
# ===========================================================================
# *config targets only - make sure prerequisites are updated, and descend
# in scripts/kconfig to make the *config target

KBUILD_DEFCONFIG := sandbox_defconfig
export KBUILD_DEFCONFIG KBUILD_KCONFIG

config: scripts_basic outputmakefile FORCE
	$(Q)$(MAKE) $(build)=scripts/kconfig $@

%config: scripts_basic outputmakefile FORCE
	$(Q)$(MAKE) $(build)=scripts/kconfig $@ 

else

从代码中我写的注释来看,如果当你命令行执行make xxx_defconfig的时候,config-targets会被改为1,然后ifeq ($(config-targets),1)就会通过。其中以%config目标的规则就会执行,%就是匹配符,只要命令行输入的命令有config这个单词就会匹配到。单独摘出来看看:

%config: scripts_basic outputmakefile FORCE
	$(Q)$(MAKE) $(build)=scripts/kconfig $@ 

可以看到,%config这个目标依赖于scripts_basicoutputmakefileFORCE这三个依赖,这三个依赖全在顶层Makefile中:

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

PHONY += outputmakefile
# outputmakefile generates a Makefile in the output directory, if using a
# separate output directory. This allows convenient use of make in the
# output directory.
outputmakefile:
ifneq ($(KBUILD_SRC),)
	$(Q)ln -fsn $(srctree) source
	$(Q)$(CONFIG_SHELL) $(srctree)/scripts/mkmakefile \
	    $(srctree) $(objtree) $(VERSION) $(PATCHLEVEL)
endif

...
...

PHONY += FORCE
FORCE:

先分析FORCE,因为FORCE最简单。FORCE 是没有规则和依赖的,所以每次make的时候都会重新生成 FORCE。当 FORCE 作为其他目标的依赖时,由于 FORCE 总是被更新过的,因此依赖所在的规则总是会执行的。

再分析outputmakefile,单独摘出来与这个规则相关的:

# KBUILD_SRC is set on invocation of make in OBJ directory
# KBUILD_SRC is not intended to be used by the regular user (for now)
# 就是支持设置编译输出的目录,使用命令make O=out***************#
ifeq ($(KBUILD_SRC),)

...
...

outputmakefile:
ifneq ($(KBUILD_SRC),)
	$(Q)ln -fsn $(srctree) source
	$(Q)$(CONFIG_SHELL) $(srctree)/scripts/mkmakefile \
	    $(srctree) $(objtree) $(VERSION) $(PATCHLEVEL)
endif

u-boot自带的注释也说了:KBUILD_SRC is not intended to be used by the regular user (for now),所以不建议普通用户使用,也就是说你最好就别指定编译输出目录,所以KBUILD_SRC是为空的,也就是说,以outputmakefile为目标的规则的命令,根本不会执行。但是KBUILD_SRC这个变量在哪里定义的我没有找到

接下来分析scripts_basic这个依赖,摘出相关的:

scripts/Kbuild.include: ;
include scripts/Kbuild.include

...
...

# Basic helpers built in scripts/
# build是在 scripts/Kbuild.include中定义的,最终$(build)展开是-f ./scripts/Makefile.build obj
# 所以最终执行的命令是make -f ./scripts/Makefile.build obj=scripts/basic,在这里会调用./scripts/Makefile.build文件中的东西
# 然后删除.tmp_quiet_recordmcount文件
PHONY += scripts_basic
scripts_basic:
	$(Q)$(MAKE) $(build)=scripts/basic
	$(Q)rm -f .tmp_quiet_recordmcount

其中build变量定义于scripts/Kbuild.include文件中:

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

那执行的命令make -f ./scripts/Makefile.build obj=scripts/basic具体做了什么事情呢?很显然要去./scripts/Makefile.build这个文件中找答案。接下来的分析同时对以%config为目标的规则后面执行的命令一块分析,因为两者都调用了同一个文件。先解释一下%config的命令:

# 下面这个规则执行的命令$(Q)$(MAKE) $(build)=scripts/kconfig $@ 中的$@是规则中的目标,即%config,在这里展开后就是mx6ull_14x14_ddr512_emmc_defconfig):
# 最终该命令展开为:@make -f ./scripts/Makefile.build obj=scripts/kconfig mx6ull_14x14_ddr512_emmc_defconfig
%config: scripts_basic outputmakefile FORCE
	$(Q)$(MAKE) $(build)=scripts/kconfig $@ 

可以看到展开后同样也需要调用./scripts/Makefile.build这个文件,那就去看一下这个文件:

# patsubst是这个意思:patsubst,,:replace pattern in text with replacement
# example:pattern:tql/%,replacement:%,text:abc/tql/cba; result:abc/cba
# 执行scripts_basic的命令的时候src :=$(patsubst tpl/%,%,scripts/basic) = scripts/basic
# 执行%config的命令的时候src :=$(patsubst tpl/%,%,scripts/kconfig) = scripts/kconfig
src := $(patsubst $(prefix)/%,%,$(obj))

# 明显执行完上面一行代码之后obj = src,所以下面这个if通过
ifeq ($(obj),$(src))
prefix := spl
src := $(patsubst $(prefix)/%,%,$(obj))
# 执行完上面一行代码后后src :=scripts/basic或者src := cripts/kconfig,下面if仍然成立
ifeq ($(obj),$(src))
prefix := .
endif
endif

...
...

# The filename Kbuild has precedence over Makefile
# $(if (判断式),执行表达式1(判断式成立),执行表达式2(判断式不成立))
kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
#wildcard:将后面的文件全部展开
kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
include $(kbuild-file)
# 最终会通过上面的include包含进来这个kbuild-file
# 在执行%config的依赖scripts_basic的命令的时候,最终获取的kbuild-file是./scripts/basic/Makefile,也就是读取这个scripts/basic文件夹下面的Makefile文件,这个子Makefile里没什么东西,就定义了两个变量
# 在执行%config的命令的时候,最终获取的kbuild-file是./scripts/kconfig/Makefile, 这里面有个目标是%_deconfig: xxx,所以在命令行运行make xxx_deconfig的时候这个子Makefile里面要执行的东西多一点

...
...

# We keep a list of all modules in $(MODVERDIR)
# __build是默认目标,因为@make -f ./scripts/Makefile.build obj=scripts/basic没有指定目标(即makefile中的目标:规则(all:xxx),指定目标例子: make all)
# 所以会执行默认目标也就是__build(一般是makefile中的第一个目标,但是可以更改默认目标)
# 在顶层Makefile中KBUILD_BUILTIN 为 1,KBUILD_MODULES 为 0,所以第一个if成立,第二个if不成立。最终执行这个目标的依赖为:__build:$(builtin-target) $(lib-target) $(extra-y)) $(subdir-ym) $(always)
# 在这个规则的命令中加入打印信息,打印这五个变量,打印出来之后只有always不为空,所以虽然有五个依赖,但实际上只依赖于always(打印出来为scripts/basic/fixdep),依赖这个文件,所以要先编译出这个文件
__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
	 $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
	 $(subdir-ym) $(always)
	@:
	@echo builtin-target = $(builtin-target)
	@echo lib-target = $(lib-target)
	@echo extra-y = $(extra-y)
	@echo subdir-ym = $(subdir-ym)
	@echo always = $(always)

到这里对于依赖scripts_basic执行的命令的分析就结束了,最终目标就是编译出scripts/basic/fixdep这个文件。

但是%config的命令执行的时候虽然也是调用./scripts/Makefile.build这个文件,但是因为传入的obj不同,最终include的文件不同,%config命令执行的时候会在调用时./scripts/Makefile.build引用了./scripts/kconfig/Makefile这个文件,这个文件中有这么一段:

#================命令行使用make xxx_deconfig的话会有一步到这里,执行下面的目标:依赖 以及下面的命令
# 依赖展开是scripts/kconfig/conf,不要管这个依赖是什么了,这个依赖是主机软件
%_defconfig: $(obj)/conf
	$(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)
# 命令展开就是:@ scripts/kconfig/conf --defconfig=arch/../configs/xxx_defconfig Kconfig
# 这个kconfig/conf是个二进制文件,不知道具体是用来干嘛的
# 但是知道它的作用是将mx6ull_alientek_emmc_defconfig中的配置输出到.config文件中,生成uboot根目录下的.config文件

很明显,会匹配到以%_defconfig为目标的规则,这个目标有个依赖叫$(obj)/conf,展开后为scripts/kconfig/conf,具体怎么编译生成这个文件,正点教程里没有细究。有兴趣的可以看一下这篇博客,写的非常详细。conf依赖于conf.c和zconf.tab.c,看conf.c中的主函数能看明白conf具体做了什么工作,有空可以细看。zconf.tab.c用于读取并分析整个Kconfig系统的文件,较为复杂。

到此为止,也知道了以%config为目标的规则执行的命令做了什么事情,主要就是将mx6ull_alientek_emmc_defconfig中的配置输出到.config文件中,生成uboot根目录下的.config文件,正点总结的流程图如下:
U-boot顶层Makefile分析及编译流程_第1张图片

三、U-boot的make过程

在命令上执行make的时候,由于没有指定目标,所以会执行Makefile中的第一个目标,顶层Makefile中的第一个目标如下:

ifeq ($(KBUILD_SRC),)

# OK, Make called in directory where kernel src resides
# Do we want to locate output files in a separate directory?
ifeq ("$(origin O)", "command line")
  KBUILD_OUTPUT := $(O)
endif

# 不过这个目标不会执行,因为ifeq ($(KBUILD_SRC),)这个判断通过不了,显然$(KBUILD_SRC)是空
PHONY := _all
_all:

但是这个_all不会执行,因为ifeq ($(KBUILD_SRC),)$(KBUILD_SRC)为空,不会执行下面的命令。那么第一个目标就变成了这个:

# If building an external module we do not care about the all: rule
# but instead _all depend on modules
# 如果没有单独编译某个模块(uboot的顶层Makefile中是支持单独编译某个模块的,有一段代码是),那么_all的依赖就是all,否则_all的依赖就是要b单独编译的那个module
PHONY += all
ifeq ($(KBUILD_EXTMOD),)
_all: all
else
_all: modules
endif

这里的这个_all就是第一个目标,依赖于all:

# 这就是如果不单独编译模块的话,_all的依赖就是(ALL-y),ALL-y的代码就在上面几行
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):

# Always append ALL so that arch config.mk's can add custom ones
# 这就是all的依赖ALL-y
# 重点关注依赖u-boot.bin,因为最后向版子里烧录的时候就是要把uboot-bin烧录进去
ALL-y += u-boot.srec u-boot.bin u-boot.sym System.map u-boot.cfg binary_size_check

# 如果在配置中使能了相关的功能,比如使能了ONENAND,那么CONFIG_OENAND_U_BOOT=y
# 那么下面展开就变成了ALL-y += u-boot-onenand-bin,这些CONFIG_xx基本上都是.config里面的配置
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

LDFLAGS_u-boot += $(LDFLAGS_FINAL)
ifneq ($(CONFIG_SYS_TEXT_BASE),)
LDFLAGS_u-boot += -Ttext $(CONFIG_SYS_TEXT_BASE)
endif

上面这段代码的第一行ALL-y += u-boot.srec u-boot.bin u-boot.sym System.map u-boot.cfg binary_size_check是不管你用什么配置文件,都有的依赖,其余的是根据你自己板子的配置文件可能有的一些依赖。

因为最终烧录到板子上的是u-boot.bin,所以重点看一下这个依赖:

# 下面就是u-boot.bin的依赖,比较重要
# 如果在config里不配置CONFIG_OF_SEPARATE(如果启用此选项,设备树将被构建并作为单独的u-boot.dtb文件放置在U-boot映像旁边)的话,if就不成立,就会去执行else里u-boot的依赖
# .config文件中没找到CONFIG_OF_SEPARATE的配置,所以执行else, u-boot-nodtb.bin:960行左右
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)
# if_changed是一个函数,在scripts/Kbuild.include 中有定义,而顶层 Makefile 中会包含 scripts/Kbuild.include 文件
endif

CONFIG_OF_SEPARATE这个选项是没启用的,所以设备树不会被单独构建为u-boot.dtb。if_changed及相关的函数我也没有深入了解,正点上说只需要知道他能使u-boot-nodtb.bin生成u-boot.bin。那么u-boot.bin的实际依赖就是u-boot-nodtb.bin(FORCE前面提过了)。:

# uboot-bin的依赖,行,又依赖u-boot,又得去找这个规则。
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

# u-boot-nodtb.bin的依赖
# u-boot又有三个依赖,其中两个变量u-boot-init和u-boot-main,这两个变量在顶层Makefile中都有定义,在七百多行左右
# u-boot.lds:在一千四百多行左右
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有三个依赖:$(u-boot-init)$(u-boot-main)u-boot.lds

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/usb/ulpi/
libs-y += cmd/
libs-y += common/
libs-$(CONFIG_API) += api/
libs-$(CONFIG_HAS_POST) += post/
libs-y += test/
libs-y += test/dm/
libs-$(CONFIG_UT_ENV) += test/env/

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-))))

# patsubst用法:$(patsubst ,,),把text中的pattern换成replacement
# 把所有的/替换成/built-in.o,比如原来是xx/xx1/、xx/xx2/,现在变成了xx/xx1/built-in.o,xx/xx2/built-in.o
# 也就是说找到了所有原libs-y文件下的built-in.o文件,并赋给了libs-y
libs-y		:= $(patsubst %/, %/built-in.o, $(libs-y)) 

...
...

# 下面两个是u-boot后面的两个依赖
# head-y跟CPU架构有关,所以在arch/arm/Makefile中被指定为head-y := arch/arm/cpu/$(CPU)/start.o ,CPU=armv7
# 找了一圈,发现u-boot这个文件夹下还真的只有这个arc/arm/Makefile中有head-y,我还以为通过make ARCH=arm 把这个文件include进来的呢
# $(libs-y)在顶层 Makefile 中被定义为 uboot 所有子目录下 build-in.o 的集合
u-boot-init := $(head-y)
u-boot-main := $(libs-y)

...
...

u-boot.lds: $(LDSCRIPT) prepare FORCE
	$(call if_changed_dep,cpp_lds)

首先libs-y原来是u-boot的所有子目录的集合,但是使用patsubst之后变成了所有子目录下的built-in.o文件的集合。u-boot: $(u-boot-init) $(u-boot-main) u-boot.lds FORCE这个规则的作用就是相当于将以 u-boot.lds 为链接脚本,将arch/arm/cpu/armv7/start.o 和各个子目录下的 built-in.o 链接在一起生成 u-boot。

那每个子目录下的built-in.o 是怎么来的呢?u-boot每个子目录下都有一个隐藏文件.built-in.o.cmd,以drivers/gpio/.built-in.o.cmd来讲:

cmd_drivers/gpio/built-in.o :=  arm-linux-gnueabihf-ld.bfd     -r -o drivers/gpio/built-in.o drivers/gpio/mxc_gpio.o 

drivers/gpio/built-in.o 这个文件是使用 ld 命令由文件 drivers/gpio/mxc_gpio.o 生成而来的,mxc_gpio.o是mxc_gpio.c 编译生成的.o 文件,是NXP的I.MX系列的GPIO驱动文件。

其中-r选项指的是产生可重定向的输出,比如,产生一个输出文件它可再次作为链接器‘ld’的输入,这经常被叫做“部分链接”,当我们需要将几个小的.o 文件链接成为一个.o 文件的时候,需要使用此选项。那在这里的意思应该就是生成的built-in.o文件还要作为其他链接器命令的输入,事实也确实是这样。

最终是用 arm-linux-gnueabihf-ld.bfd 命令将 arch/arm/cpu/armv7/start.o 和其他众多的built_in.o链接在一起,形成u-boot,然后生成u-boot-nodtb.bin,再生成u-boot.bin

正点最后总结的流程图如下:
U-boot顶层Makefile分析及编译流程_第2张图片

你可能感兴趣的:(U-boot系列,linux,嵌入式硬件,开源,经验分享)