十、U-Boot 顶层 Makefile 详解
这里参考一个博主的文章
(1)编译后的 U-Boot 文件构造
编译后的 U-Boot 目录下有如下文件夹和文件,作用备注在前面
| U-boot 自带的目录
├── api ---与硬件无关的 API 函数。
├── arch ---与架构体系无关的代码。
├── board ---不同板子(开发板)的定制代码。
├── cmd ---命令相干代码。
├── common ---通用代码。
├── configs ---配置文件。
├── disk ---磁盘分区相干代码。
├── doc ---文档。
├── drivers ---驱动代码。
├── dts ---设施树。
├── examples ---示例代码。
├── fs ---文件系统。
├── include ---头文件。
├── lib ---库文件。
├── Licenses ---许可证相干文件。
├── net ---网络相干代码。
├── post ---上电自检程序。
├── scripts ---脚本文件。
├── test ---测试代码。
└── tools ---工具文件夹。
| 编译生成
├── .config 配置文件,重要的文件。
├── .u-boot.bin.cmd 一系列的文件,用于保留着一些命令。
├── .u-boot.cfg.cmd
├── .u-boot.imx.cmd
├── .u-boot.lds.cmd
├── .u-boot-nodtb.bin.cmd
├── .u-boot.srec.cmd
├── .u-boot.sym.cmd
├── System.map 零碎映射文件
├── u-boot* 编译进去的 u-boot 文件。
├── u-boot.bin 生成的一些 u-boot 相干文件
├── u-boot.cfg
├── .u-boot.cfg.d
├── .u-boot.cmd
├── u-boot.imx
├── u-boot.lds
├── u-boot.map
├── u-boot-nodtb.bin
├── u-boot.srec
├── u-boot.sym
└── .u-boot.sym.cmd
├── build.sh*
├── config.mk*
|U-Boot 自带
├── .gitignore* git 工具相干文件。
├── .mailmap* 邮件列表。
├── config.mk* 某个 Makefile 会调用此文件。
├── Kbuild* 用于生成一些和汇编无关的文件。
├── Kconfig* 图形配置界面形容文件。
├── MAINTAINERS* 维护者联系方式文件。
├── MAKEALL* 一个 shell 脚本文件,帮忙编译uboot 的。
├── Makefile* 主 Makefile,重要文件!
├── README* 相当于帮忙文档。
└── snapshot.commit*
顶层 Makefile 文件, Makefile 是反对嵌套的,也就是顶层 Makefile 能够调用子目录中的 Makefile 文件。 Makefile 嵌套在大我的项目中很常见,个别大我的项目外面所有的源代码都不会放到同一个目录中,各个功能模块的源代码都是离开的,各自寄存在各自的目录中。每个功能模块目录下都有一个 Makefile,这个 Makefile 只解决本模块的编译链接工作,这样所有的编译链接工作就不必全副放到一个 Makefile 中,能够使得 Makefile 变得简洁明了。
(2)创立 Vscode 工程
- 先用 Vscode 关上文件夹,而后在File中另存为workspace
- 在当前目录源码根目录下创立一个新目录 .vscode,而后这个目录下创立一个文件settings.json
- settings.json 能够配置以后工程要排除的文件和目录,
"files.exclude":
和"search.exclude":
中填写要排除的文件,* 能够通配门路,能够通配字符,[0-9/a-z]能够通配间断的字符。
(3)顶层 Makefile 剖析
- MAKEFLAGS 变量
make 是反对递归调用的,也就是在 Makefile 中应用“make”命令来执行其余的 Makefile文件,个别都是子目录中的 Makefile 文件。如果在当前目录下存在一个“subdir”子目录,这个子目录中又有其对应的 Makefile 文件,那么这个工程在编译的时候其主目录中的 Makefile 就能够调用子目录中的 Makefile,以此来实现所有子目录的编译。主Makefile能够应用命令$(MAKE) -C subdir
来调用底层的Makefile。在 make 递归执行的过程中,最上层的 make 称为 主控make ,它的命令行选项,如 "-k", "-s" 等会通过环境变量 "MAKEFLAGS" 传递给子 make 过程。变量 "MAKEFLAGS" 的值会被主控 make 主动的设置为蕴含所执行 make 时的命令行选项的字符串。比方主控执行 make 时应用 "-k" 和 "-s" 选项,那么 "MAKEFLAGS" 的值就为 ks 。子 make 过程解决时,会把此环境变量的值作为执行的命令行选项,因而子 make 过程就应用 "-k" 和 "-s" 这两个命令行选项。
# 下述代码应用“+=”来给变量 MAKEFLAGS 追加了一些值,“-rR”示意禁止应用内置的隐# 含规定和变量定义,“--include-dir”指明搜寻门路, ”$(CURDIR)”示意以后Makefile所在目录。# o Do not use make's built-in rules and variables# (this increases performance and avoids hard-to-debug behaviour);# o Look for make include files relative to root of kernel srcMAKEFLAGS += -rR --include-dir=$(CURDIR)
# unexport 示意前面的变量不导出给子Makefile# export 示意前面的变量导出给子Makefile# Avoid funny character set dependenciesunexport LC_ALLLC_COLLATE=CLC_NUMERIC=Cexport LC_COLLATE LC_NUMERIC# Avoid interference with shell env settingsunexport GREP_OPTIONS
- 命令输入形式(简介输入、静默输入)
uboot 默认编译是不会在终端中显示残缺的命令,都是短命令。在终端中输入短命令尽管看起来很清新,然而不利于剖析 uboot 的编译过程。能够通过设置变量“V=1“来实现残缺的命令输入,这个在调试 uboot 的时候很有用。顶层 Makefile 中管制命令输入的代码如下:
# $(origin V) 判断变量 V 的起源,命令行中定义的就会返回"command line"# 在命令行中定义 V=1 ,则会在上面的判断中,让变量quiet 和 Q 都为空,在顶层命令中存在很多# $(Q)$(MAKE) $(build)=tools ,当Q = @时,就不会在终端上显示该命令。# 还有另外一种简化的形式,quiet_cmd_sym ?= SYM $@ 和 cmd_sym ?= $(OBJDUMP) -t $< > $@# 当定义了quiet=quiet_则依照简介的形式输入,否则依照残缺输入# 另外,当定义quiet=silent_,因为没有相似 silent_cmd_sym 的命令,则依照静默形式,没有指令输入ifeq ("$(origin V)", "command line") KBUILD_VERBOSE = $(V)endififndef KBUILD_VERBOSE KBUILD_VERBOSE = 0endififeq ($(KBUILD_VERBOSE),1) quiet = Q =else quiet=quiet_ Q = @endif# If the user is running make -s (silent mode), suppress echoing of commands# 应用$(filter <pattern...>,<text>) 函数返回与pattern匹配的字符串,这里MAKE_VERSION # 的版本为4.1,则返回4.1,这个ifneq 成立。函数 $(firstword x$(MAKEFLAGS)) 返回 # x$(MAKEFLAGS) 字符串中第一个单词,则为 x...,若在命令行输出 -s,则 # x$(MAKEFLAGS) 就会为 xs... ,则ifneq ($(filter %s ,$(firstword x$(MAKEFLAGS))),) 成立# quiet=silent_ ,在终端就不会输入任何指令。ifneq ($(filter 4.%,$(MAKE_VERSION)),) # make-4ifneq ($(filter %s ,$(firstword x$(MAKEFLAGS))),) quiet=silent_endifelse # make-3.8xifneq ($(filter s% -s%,$(MAKEFLAGS)),) quiet=silent_endifendifexport quiet Q KBUILD_VERBOSE
- 指定编译后果输入目录
uboot 能够将编译进去的指标文件输入到独自的目录中,在 make 的时候应用“O”来指定输入目录,比方“make O=out”就是设置指标文件输入到 out 目录中。这么做是为了将源文件和编译产生的文件离开,当然也能够不指定 O 参数,不指定的话源文件和编译产生的文件都在同一个目录内,个别咱们不指定 O 参数。
# kbuild supports saving output files in a separate directory.# To locate output files in a separate directory two syntaxes are supported.# In both cases the working directory must be the root of the kernel src.# 1) O=# Use "make O=dir/to/store/output/files/"## 2) Set KBUILD_OUTPUT# Set the environment variable KBUILD_OUTPUT to point to the directory# where the output files shall be placed.# export KBUILD_OUTPUT=dir/to/store/output/files/# make## The O= assignment takes precedence over the KBUILD_OUTPUT environment# variable.# 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)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# That's our default target when none is given on the command line# 减少伪指标 _all# 没有依赖文件的指标称为“伪指标”。伪指标并不是一个文件,只是一个标签。# 因为伪指标不是一个文件,所以make无奈生成它的依赖关系和决定它是否要执行,# 只有在命令行中输出(即显示地指明)这个“指标”能力让其失效,此处为"make _all"。PHONY := _all_all:# Cancel implicit rules on top Makefile# 这只是为了防止顶层 Makefile 规定抵触。$(CURDIR)/Makefile Makefile: ;ifneq ($(KBUILD_OUTPUT),)# Invoke a second make in the output directory, passing relevant variables# check that the output directory actually existssaved-output := $(KBUILD_OUTPUT)# 函数shell是make与外部环境的通信工具,它用于命令的扩大。# shell函数起着调用shell命令(mkdir -p $(KBUILD_OUTPUT) && cd $(KBUILD_OUTPUT) && /bin/pwd)# 和返回命令输入后果的参数的作用。# Make仅仅解决返回后果,再返回后果替换调用点之前,make将每一个换行符或者一对回车/换行符# 解决为单个空格;如果返回后果最初是换行符(和回车符),make将把它们去掉。KBUILD_OUTPUT := $(shell mkdir -p $(KBUILD_OUTPUT) && cd $(KBUILD_OUTPUT) \ && /bin/pwd)# if函数的语法是:$(if <condition>,<then-part>) 或是 $(if <condition>,<then-part>,<else-part>)。 # 函数error的语法是:$(error <text ...>;) $(if $(KBUILD_OUTPUT),, $(error failed to create output directory ''$(saved-output)''))# MAKECMDGOALS 变量是 make 的一个内置变量,它示意的是所要构建指标的一个终极列表。# 这里是示意,将 MAKECMDGOALS 所示意的所有指标以及 sub-make 都定义为伪指标。PHONY += $(MAKECMDGOALS) sub-make# 反过滤函数——filter-out,语法是:$(filter-out <pattern...>,<text>),# 这里应用 filter-out 函数将 $(MAKECMDGOALS) 示意的所有指标中去掉 _all sub-make # $(CURDIR)/Makefile 这 3 个指标,尔后剩下的指标和 _all 这两个指标都将依赖于# sub-make ,而 @: 示意什么都不做,它的作用仅仅是提醒要先去实现 sub-make 这个依赖,# 而实现了 sub-make 这个依赖后也就相当于实现了 $(MAKECMDGOALS) 和 _all 这两个指标了。$(filter-out _all sub-make $(CURDIR)/Makefile, $(MAKECMDGOALS)) _all: sub-make @:# 在这里,实际上是对 sub-make 指标的实现。上面的语句等价于# make -C [输入的内部目录] KBUILD_SRC=`pwd` -f `pwd`/Makefile [要生成的指标]# 在此咱们从新调用了顶层 Makefile ,这是递归调用。当咱们再次调用顶层 Makefile 时,# 因为咱们在下面的命令中的 KBUILD_SRC 变量曾经被赋值,所以当再次来到第 120 行中的 # ifeq ($(KBUILD_SRC),) 判断时,是不会再进去到它的代码块中(从 120-156 行)# 这样,程序就会间接跳到 159 行的 ifeq ($(skip-makefile),) ,因为此时 # skip-makefile 还未定义,故为空,所以程序得以持续往下执行,这次会执行159-1608所有# 的内容,这部分波及到了具体指标文件的构建过程。sub-make: FORCE $(Q)$(MAKE) -C $(KBUILD_OUTPUT) KBUILD_SRC=$(CURDIR) \ -f $(CURDIR)/Makefile $(filter-out _all sub-make,$(MAKECMDGOALS))# Leave processing to above invocation of make# 当 “下半部” 执行结束后会返回到 154-156 行,这时,skip-makefile 变量被赋值为 1,# 当再运行到159行,ifeq ($(skip-makefile),) 不成立,当下面的递归返回到第 1 层 # Makefile 时,这里因为 skip-makefile 值为 1,程序间接跳到 Makefile 最底部,# 从而完结了 Makefile 这个过程。O 选项所对应的代码块从 135-155 这里,如果带 O 选项# 就剖析这一块代码,如果不带 O,也就没有递归。skip-makefile := 1endif # ifneq ($(KBUILD_OUTPUT),)endif # ifeq ($(KBUILD_SRC),)
- 代码查看
uboot 反对代码查看,应用命令make C=1
使能代码查看,查看那些须要从新编译的文件,make C=2
用于查看所有的源码文件。查看工具Sparse 诞生于 2004 年, 是由 linux 之父开发的, 目标就是提供一个动态查看代码的工具, 从而缩小 linux 内核的隐患。 其实在 Sparse 之前, 曾经有了一个不错的代 码动态查看工具 (“SWAT”), 只不过这个工具不是免费软件, 应用上有一些限度, 所以 linus 还是本人开发了一个动态查看工具。顶层 Makefile 中的代码如下:
# Call a source code checker (by default, "sparse") as part of the# C compilation.## Use 'make C=1' to enable checking of only re-compiled files.# Use 'make C=2' to enable checking of *all* source files, regardless# of whether they are re-compiled or not.## See the file "Documentation/sparse.txt" for more details, including# where to get the "sparse" utility.ifeq ("$(origin C)", "command line") KBUILD_CHECKSRC = $(C)endififndef KBUILD_CHECKSRC KBUILD_CHECKSRC = 0endif
模块编译
在 uboot 中容许独自编译某个模块,应用命令“make M=dir”即可,旧语法“make SUBDIRS=dir”也是反对的。顶层 Makefile 中的代码如下:# 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 precedenceifdef SUBDIRS KBUILD_EXTMOD ?= $(SUBDIRS)endififeq ("$(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# 个别状况下咱们不会在 uboot 中编译模块,所以此处会编译 all 这个指标。PHONY += allifeq ($(KBUILD_EXTMOD),)_all: allelse_all: modulesendif# 个别不设置 KBUILD_SRC,执行 srctree := .ifeq ($(KBUILD_SRC),) # building in the source tree srctree := .else ifeq ($(KBUILD_SRC)/,$(dir $(CURDIR))) # building in a subdirectory of the source tree srctree := .. else srctree := $(KBUILD_SRC) endifendifobjtree := .src := $(srctree)obj := $(objtree)VPATH := $(srctree)$(if $(KBUILD_EXTMOD),:$(KBUILD_EXTMOD))export srctree objtree VPATH
- 取得主机架构和零碎
# uname -m 取得主机的架构,x86_64# sed -e s/i.86/x86/ 示意将i.86替换成x86,-e选项容许对输出数据利用多条sed命令编辑HOSTARCH := $(shell uname -m | \ sed -e s/i.86/x86/ \ -e s/sun4u/sparc64/ \ -e s/arm.*/arm/ \ -e s/sa110/arm/ \ -e s/ppc64/powerpc/ \ -e s/ppc/powerpc/ \ -e s/macppc/powerpc/\ -e s/sh.*/sh/)# uname -s 取得零碎主机的零碎,为Linux# tr '[:upper:]' '[:lower:]' 将大写转换为小写# sed -e 's/\(cygwin\).*/cygwin/' 将cygwin.* 转换为 cygwinHOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \ sed -e 's/\(cygwin\).*/cygwin/')export HOSTARCH HOSTOS
- 设置指标架构,穿插编译器和配置文件
编 译 uboot 的 时 候 需 要 设 置 目 标 板 架 构 和 交 叉 编 译 器 ,“make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-”就是用于设置 ARCH 和 CROSS_COMPILE。
变量 KCONFIG_CONFIG, uboot 是能够配置的,这里设置配置文件为.config, .config 默认是没有的,须要应用命令“make xxx_defconfig”,对 uboot 进行配置,配置实现当前就会在 uboot 根目录下生成.config。默认状况下.config 和 xxx_defconfig 内容是一样的,因为.config 就是从 xxx_defconfig 复制过去的。如果后续自行调整了 uboot 的一些配置参数,那么这些新的配置参数就增加到了.config 中,而不是 xxx_defconfig。相当于xxx_defconfig 只是一些初始配置,而.config 外面的才是实时无效的配置。
# set default to nothing for native buildsifeq ($(HOSTARCH),$(ARCH))CROSS_COMPILE ?=endifKCONFIG_CONFIG ?= .configexport KCONFIG_CONFIG
- 调用scripts/Kbuild.include
主 Makefile 会调用文件 scripts/Kbuild.include 这个文件,此文件外面定义了很多变量,在编译过程中会应用这些变量。
# We need some generic definitions (do not try to remake the file).scripts/Kbuild.include: ;include scripts/Kbuild.include
- 变量设置
# Make variables (CC, etc...)AS = $(CROSS_COMPILE)as# Always use GNU ldifneq ($(shell $(CROSS_COMPILE)ld.bfd -v 2> /dev/null),)LD = $(CROSS_COMPILE)ld.bfdelseLD = $(CROSS_COMPILE)ldendifCC = $(CROSS_COMPILE)gccCPP = $(CC) -EAR = $(CROSS_COMPILE)arNM = $(CROSS_COMPILE)nmLDR = $(CROSS_COMPILE)ldrSTRIP = $(CROSS_COMPILE)stripOBJCOPY = $(CROSS_COMPILE)objcopyOBJDUMP = $(CROSS_COMPILE)objdumpAWK = awkPERL = perlPYTHON = pythonDTC = dtcCHECK = sparseCHECKFLAGS := -D__linux__ -Dlinux -D__STDC__ -Dunix -D__unix__ \ -Wbitwise -Wno-return-void -D__CHECK_ENDIAN__ $(CF)KBUILD_CPPFLAGS := -D__KERNEL__ -D__UBOOT__KBUILD_CFLAGS := -Wall -Wstrict-prototypes \ -Wno-format-security \ -fno-builtin -ffreestandingKBUILD_AFLAGS := -D__ASSEMBLY__
- 导出变量
这些变量 ARCH CPU BOARD VENDOR SOC CPUDIR BOARDDIR 在以后的 Makefile 并没有定义,他们是在 Uboot 根目录的 config.mk 中定义的,而在 config.mk 中这几个变量又是由CONFIG_SYS_ARCH、 CONFIG_SYS_CPU、 CONFIG_SYS_BOARD、CONFIG_SYS_VENDOR 和 CONFIG_SYS_SOC 定义的,这5个变量又是在 Uboot/.config 中定义。
在 config.mk 有相似这样的语句 sinclude $(srctree)/board/$(BOARDDIR)/config.mk
。sinclude 读取的文件如果不存在的话不会报错。
# Read UBOOTRELEASE from include/config/uboot.release (if it exists)UBOOTRELEASE = $(shell cat include/config/uboot.release 2> /dev/null)UBOOTVERSION = $(VERSION)$(if $(PATCHLEVEL),.$(PATCHLEVEL)$(if $(SUBLEVEL),.$(SUBLEVEL)))$(EXTRAVERSION)export VERSION PATCHLEVEL SUBLEVEL UBOOTRELEASE UBOOTVERSIONexport ARCH CPU BOARD VENDOR SOC CPUDIR BOARDDIRexport CONFIG_SHELL HOSTCC HOSTCFLAGS HOSTLDFLAGS CROSS_COMPILE AS LD CCexport CPP AR NM LDR STRIP OBJCOPY OBJDUMPexport MAKE AWK PERL PYTHONexport HOSTCXX HOSTCXXFLAGS DTC CHECK CHECKFLAGSexport KBUILD_CPPFLAGS NOSTDINC_FLAGS UBOOTINCLUDE OBJCOPYFLAGS LDFLAGSexport KBUILD_CFLAGS KBUILD_AFLAGS
- make xxx_defconfig 过程
在编译 uboot 之前要应用“make xxx_defconfig”命令来配置 uboot,相干的配置过程在上面的代码中。
这部分的代码最初开展都和 ./scripts/Makefile.build 无关。
# To make sure we do not include .config for any of the *config targets# catch them early, and hand them over to scripts/kconfig/Makefile# It is allowed to specify more targets when calling make, including# mixing *config targets and build targets.# For example 'make oldconfig all'.# Detect when mixed targets is specified, and make a second invocation# of make so .config is not included in this case either (for *config).# version_autogenerated.h 和 timestamp_autogenerated.h 是make执行时主动生成的version_h := include/generated/version_autogenerated.htimestamp_h := include/generated/timestamp_autogenerated.hno-dot-config-targets := clean clobber mrproper distclean \ help %docs check% coccicheck \ ubootversion backupconfig-targets := 0mixed-targets := 0dot-config := 1# 执行 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_ddr512_emmc_defconfig # MAKECMDGOALS 是 make 的一个环境变量,这个变量会保留你所指定的终极目标列表,# 比方执行“make mx6ull_14x14_ddr512_emmc_defconfig ”,那么 MAKECMDGOALS# 就为 mx6ull_14x14_ddr512_emmc_defconfig # 执行完,dot-config 仍然为 1ifneq ($(filter $(no-dot-config-targets), $(MAKECMDGOALS)),) ifeq ($(filter-out $(no-dot-config-targets), $(MAKECMDGOALS)),) dot-config := 0 endifendif# KBUILD_EXTMOD 未定义,会匹配到 %config ,所以 config-targets = 1# $(words <text>) 能够统计 text 所蕴含的单词个数,这里为1,所以 mixed-targets 仍为 0ifeq ($(KBUILD_EXTMOD),) ifneq ($(filter config %config,$(MAKECMDGOALS)),) config-targets := 1 ifneq ($(words $(MAKECMDGOALS)),1) mixed-targets := 1 endif endifendififeq ($(config-targets),1)# ===========================================================================# *config targets only - make sure prerequisites are updated, and descend# in scripts/kconfig to make the *config target# config-targets = 1,这段代码会执行,而后会匹配到 %config,因为这里有依赖 FORCE ,# 所以上面的命令总会执行KBUILD_DEFCONFIG := sandbox_defconfigexport KBUILD_DEFCONFIG KBUILD_KCONFIGconfig: scripts_basic outputmakefile FORCE $(Q)$(MAKE) $(build)=scripts/kconfig $@%config: scripts_basic outputmakefile FORCE $(Q)$(MAKE) $(build)=scripts/kconfig $@ # 在下面的依赖 scripts_basic outputmakefile,在394行# Basic helpers built in scripts/# 下面的两个依赖,只会执行这个# $(build) 是在 scripts/Kbuild.include 中定义的 ,其开展为 # build=-f ./scripts/Makefile.build obj# 其命令开展为 @make -f ./scripts/Makefile.build obj=scripts/basicPHONY += scripts_basicscripts_basic: $(Q)$(MAKE) $(build)=scripts/basic $(Q)rm -f .tmp_quiet_recordmcount# To avoid any implicit rule to kick in, define an empty command.scripts/basic/%: scripts_basic ;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.# KBUILD_SRC 为空,所以 outputmakefile 没有指令执行outputmakefile:ifneq ($(KBUILD_SRC),) $(Q)ln -fsn $(srctree) source $(Q)$(CONFIG_SHELL) $(srctree)/scripts/mkmakefile \ $(srctree) $(objtree) $(VERSION) $(PATCHLEVEL)endif
- Makefile.build 脚本剖析 - scripts_basic 指标对应的命令
在上一节,命令开展后为
@make -f ./scripts/Makefile.build obj=scripts/basic@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig
关上 scripts/Makefile.build 文件,如下
# Modified for U-Boot# $(patsubst $(prefix)/%,%,$(obj)) 从$(obj)匹配tpl/%, 替换成%,这里匹配为空,所以# src=scripts/basic# 上面再次判断,最初的prefix = .prefix := tplsrc := $(patsubst $(prefix)/%,%,$(obj))ifeq ($(obj),$(src))prefix := splsrc := $(patsubst $(prefix)/%,%,$(obj))ifeq ($(obj),$(src))prefix := .endifendif
持续往下,到56行,
# The filename Kbuild has precedence over Makefile# 第一行因为没有 /% 结尾,所以 kbuild-dir = ./scripts/basic# 第二行因为在 ./scripts/basic 目录下没有 Kbuild 文件,# 所以 kbuild-file=./scripts/basic/Makefile# 接下来读取 ./scripts/basic/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)
持续往下,116行失去论断,scripts_basic 指标的作用就是编译出 scripts/basic/fixdep 这个软件。
# We keep a list of all modules in $(MODVERDIR)# 在顶层 Makefile 中, KBUILD_BUILTIN 为 1,KBUILD_MODULES 为 0,# 因而开展后指标__build 为 # __build:$(builtin-target) $(lib-target) $(extra-y)) $(subdir-ym) $(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) @:
- Makefile.build 脚本剖析 - %config 指标对应的命令
%config对应的命令为 @make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig
,其相干的变量
src= scripts/kconfigkbuild-dir = ./scripts/kconfigkbuild-file = ./scripts/kconfig/Makefileinclude ./scripts/kconfig/Makefile
在后面的剖析中,有一个 include $(kbuild-file)
,这里解析进去就是 include ./scripts/basic/Makefile
,看到这个文件的113行
# 第一行的指标文件就能够匹配到 make mx6ull_14x14_ddr512_emmc_defconfig # 第一行的依赖为 scripts/kconfig/conf,conf 是主机软件,临时疏忽其生成过程# 第二行的命令开展为 # @ scripts/kconfig/conf --defconfig=arch/../configs/xxx_defconfig Kconfig# 上述命令用到了 xxx_defconfig 文件,比方 mx6ull_alientek_emmc_defconfig。# 这里会将mx6ull_alientek_emmc_defconfig 中的配置输入到.config 文件中,# 最终生成 uboot 根目录下的.config 文件。%_defconfig: $(obj)/conf $(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)# Added for U-Boot (backward compatibility)%_config: %_defconfig @:
- Make过程
在顶层的 Makefile 中有一个默认的指标,在 128 行
# That's our default target when none is given on the command linePHONY := _all_all:
而 _all 又有其余的依赖 all,定义在194
# If building an external module we do not care about the all: rule# but instead _all depend on modulesPHONY += allifeq ($(KBUILD_EXTMOD),)_all: allelse_all: modulesendif
而 all 的依赖又定义在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 "===================================================="endififeq ($(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),在 730 行能够看到 ALL-y 蕴含了 u-boot.srec u-boot.bin u-boot.sym System.map u-boot.cfg binary_size_check,另外还有一些就时独自配置的,比方 CONFIG_ONENAND_U_BOOT ,如果使能 ONENAND ,在 .config 配置文件中就会有“CONFIG_ONENAND_U_BOOT=y” 这一句,于是 ALL-y += u-boot-onenand.bin。
# Always append ALL so that arch config.mk's can add custom onesALL-y += u-boot.srec u-boot.bin u-boot.sym System.map u-boot.cfg binary_size_checkALL-$(CONFIG_ONENAND_U_BOOT) += u-boot-onenand.binifeq ($(CONFIG_SPL_FSL_PBL),y)ALL-$(CONFIG_RAMBOOT_PBL) += u-boot-with-spl-pbl.binelseifneq ($(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 explicitlyALL-$(CONFIG_RAMBOOT_PBL) += u-boot.pblendifendifALL-$(CONFIG_SPL) += spl/u-boot-spl.binALL-$(CONFIG_SPL_FRAMEWORK) += u-boot.imgALL-$(CONFIG_TPL) += tpl/u-boot-tpl.binALL-$(CONFIG_OF_SEPARATE) += u-boot.dtbifeq ($(CONFIG_SPL_FRAMEWORK),y)ALL-$(CONFIG_OF_SEPARATE) += u-boot-dtb.imgendifALL-$(CONFIG_OF_HOSTFILE) += u-boot.dtbifneq ($(CONFIG_SPL_TARGET),)ALL-$(CONFIG_SPL) += $(CONFIG_SPL_TARGET:"%"=%)endifALL-$(CONFIG_REMAKE_ELF) += u-boot.elfALL-$(CONFIG_EFI_APP) += u-boot-app.efiALL-$(CONFIG_EFI_STUB) += u-boot-payload.efiifneq ($(BUILD_ROM),)ALL-$(CONFIG_X86_RESET_VECTOR) += u-boot.romendif# enable combined SPL/u-boot/dtb rules for tegraifeq ($(CONFIG_TEGRA)$(CONFIG_SPL),yy)ALL-y += u-boot-tegra.bin u-boot-nodtb-tegra.binALL-$(CONFIG_OF_SEPARATE) += u-boot-dtb-tegra.binendif# Add optional build target if defined in board/cpu/soc headersifneq ($(CONFIG_BUILD_TARGET),)ALL-y += $(CONFIG_BUILD_TARGET:"%"=%)endif
在 ALL-y 的依赖外面有个 u-boot.bin ,这是咱们所要生成的最终目标。在825行,
# 在.config中搜寻“CONFIG_OF_SEPARAT”,没有找到,阐明条件不成立。# if_changed 是 一 个 函 数 , 这 个 函 数 在scripts/Kbuild.include 226行中有定义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)elseu-boot.bin: u-boot-nodtb.bin FORCE $(call if_changed,copy)endif
u-boot.bin 的生成依赖于 u-boot-nodtb.bin ,再找到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 ,再到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.oendif
而 u-boot-init 和 u-boot-main 又是在678行定义。
$(head-y)跟 CPU 架构无关,咱们应用的是 ARM 芯片,所以 head-y 在 arch/arm/Makefile 中被指定为 head-y := arch/arm/cpu/$(CPU)/start.o ,又后面提到 CPU = armv7,所以 u-boot-init= arch/arm/cpu/armv7/start.o。
u-boot-init := $(head-y)u-boot-main := $(libs-y)
$(libs-y)是在620行定义的, libs-y 都是 uboot 各子目录的汇合,最初一行 libs-y := $(patsubst %/, %/built-in.o, $(libs-y))
调用了函数 patsubst,将 libs-y 中的“/”替换为”/built-in.o”,比方“drivers/dma/”就变为了“drivers/dma/built-in.o”,相当于将 libs-y 改为所有子目录中 built-in.o 文件的汇合。那么 uboot-main 就等于所有子目录中 built-in.o 的汇合。
这个规定就相当于将以 u-boot.lds 为链接脚本,将 arch/arm/cpu/armv7/start.o 和各个子目录下的 built-in.o 链接在一起生成 u-boot。
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-$(CONFIG_CMD_NAND) += drivers/mtd/nand/libs-y += drivers/mtd/onenand/libs-$(CONFIG_CMD_UBI) += drivers/mtd/ubi/libs-y += drivers/mtd/spi/libs-y += drivers/net/libs-y += drivers/net/phy/libs-y += drivers/pci/libs-y += drivers/power/ \ drivers/power/fuel_gauge/ \ drivers/power/mfd/ \ drivers/power/pmic/ \ drivers/power/battery/ \ drivers/power/regulator/libs-y += drivers/spi/libs-$(CONFIG_FMAN_ENET) += drivers/net/fm/libs-$(CONFIG_SYS_FSL_DDR) += drivers/ddr/fsl/libs-$(CONFIG_ALTERA_SDRAM) += drivers/ddr/altera/libs-y += drivers/serial/libs-y += drivers/usb/dwc3/libs-y += drivers/usb/emul/libs-y += drivers/usb/eth/libs-y += drivers/usb/gadget/libs-y += drivers/usb/gadget/udc/libs-y += drivers/usb/host/libs-y += drivers/usb/musb/libs-y += drivers/usb/musb-new/libs-y += drivers/usb/phy/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 examplesu-boot-alldirs := $(sort $(u-boot-dirs) $(patsubst %/,%,$(filter %/, $(libs-))))libs-y := $(patsubst %/, %/built-in.o, $(libs-y))
接下来的重点就是各子目录下的 built-in.o 是怎么生成的,以 drivers/gpio/built-in.o 为例,在 drivers/gpio/目录下会有个名为.built-in.o.cmd 的文件,该文件内容如下:
cmd_drivers/gpio/built-in.o := arm-linux-gnueabihf-ld.bfd -r -odrivers/gpio/built-in.o drivers/gpio/mxc_gpio.o
从命令“cmd_drivers/gpio/built-in.o”能够看出, drivers/gpio/built-in.o 这个文件是应用 ld 命令由文件 drivers/gpio/mxc_gpio.o 生成而来的, mxc_gpio.o 是 mxc_gpio.c 编译生成的.o 文件,这个是 NXP 的 I.MX 系列的 GPIO 驱动文件。这里用到了 ld 的“-r”参数,参数含意如下:-r –relocateable: 产生可重定向的输入,比方,产生一个输入文件它可再次作为‘ld’ 的输出,这常常被叫做“局部链接”,当咱们须要将几个小的.o 文件链接成为一个.o 文件的时候,须要应用此选项。
最初 make 默认指标执行的流程如下图