make V=1,即可输出完整命令,make V=0则输出短命令,make -s静默输出(短命令都不输出)
make O=out,out为输出文件夹名
uboot 支持代码检查,使用命令“make C=1”使能代码检查,检查那些需要重新编译的文件。
“make C=2”用于检查所有的源码文件
make M=dir/make SUBDIRS=dir
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
可以直接修改顶层 Makefile,在里面加入 ARCH和CROSS_COMPILE 的定义
主 Makefile 会调用文件 scripts/Kbuild.include 这个文件,在 uboot 的编译过程中会用到 scripts/Kbuild.include 中的这些变量
设置-ld,-gcc,-objdump这些
当从顶层文件夹以命令行传入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_basic
、outputmakefile
、FORCE
这三个依赖,这三个依赖全在顶层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文件,正点总结的流程图如下:
在命令上执行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
。