共计 21238 个字符,预计需要花费 54 分钟才能阅读完成。
十、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 src
MAKEFLAGS += -rR --include-dir=$(CURDIR)
# unexport 示意前面的变量不导出给子 Makefile
# export 示意前面的变量导出给子 Makefile
# Avoid funny character set dependencies
unexport LC_ALL
LC_COLLATE=C
LC_NUMERIC=C
export LC_COLLATE LC_NUMERIC
# Avoid interference with shell env settings
unexport 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)
endif
ifndef KBUILD_VERBOSE
KBUILD_VERBOSE = 0
endif
ifeq ($(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-4
ifneq ($(filter %s ,$(firstword x$(MAKEFLAGS))),)
quiet=silent_
endif
else # make-3.8x
ifneq ($(filter s% -s%,$(MAKEFLAGS)),)
quiet=silent_
endif
endif
export 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 exists
saved-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 := 1
endif # 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)
endif
ifndef KBUILD_CHECKSRC
KBUILD_CHECKSRC = 0
endif
-
模块编译
在 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 precedence ifdef SUBDIRS KBUILD_EXTMOD ?= $(SUBDIRS) endif ifeq ("$(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 += all ifeq ($(KBUILD_EXTMOD),) _all: all else _all: modules endif # 个别不设置 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) endif endif objtree := . 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.* 转换为 cygwin
HOSTOS := $(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 builds
ifeq ($(HOSTARCH),$(ARCH))
CROSS_COMPILE ?=
endif
KCONFIG_CONFIG ?= .config
export 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 ld
ifneq ($(shell $(CROSS_COMPILE)ld.bfd -v 2> /dev/null),)
LD = $(CROSS_COMPILE)ld.bfd
else
LD = $(CROSS_COMPILE)ld
endif
CC = $(CROSS_COMPILE)gcc
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar
NM = $(CROSS_COMPILE)nm
LDR = $(CROSS_COMPILE)ldr
STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
AWK = awk
PERL = perl
PYTHON = python
DTC = dtc
CHECK = sparse
CHECKFLAGS := -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 -ffreestanding
KBUILD_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 UBOOTVERSION
export ARCH CPU BOARD VENDOR SOC CPUDIR BOARDDIR
export CONFIG_SHELL HOSTCC HOSTCFLAGS HOSTLDFLAGS CROSS_COMPILE AS LD CC
export CPP AR NM LDR STRIP OBJCOPY OBJDUMP
export MAKE AWK PERL PYTHON
export HOSTCXX HOSTCXXFLAGS DTC CHECK CHECKFLAGS
export KBUILD_CPPFLAGS NOSTDINC_FLAGS UBOOTINCLUDE OBJCOPYFLAGS LDFLAGS
export 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.h
timestamp_h := include/generated/timestamp_autogenerated.h
no-dot-config-targets := clean clobber mrproper distclean \
help %docs check% coccicheck \
ubootversion backup
config-targets := 0
mixed-targets := 0
dot-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 仍然为 1
ifneq ($(filter $(no-dot-config-targets), $(MAKECMDGOALS)),)
ifeq ($(filter-out $(no-dot-config-targets), $(MAKECMDGOALS)),)
dot-config := 0
endif
endif
# KBUILD_EXTMOD 未定义,会匹配到 %config,所以 config-targets = 1
# $(words <text>) 能够统计 text 所蕴含的单词个数,这里为 1,所以 mixed-targets 仍为 0
ifeq ($(KBUILD_EXTMOD),)
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
# config-targets = 1,这段代码会执行,而后会匹配到 %config,因为这里有依赖 FORCE,# 所以上面的命令总会执行
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 $@
# 在下面的依赖 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/basic
PHONY += scripts_basic
scripts_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 := tpl
src := $(patsubst $(prefix)/%,%,$(obj))
ifeq ($(obj),$(src))
prefix := spl
src := $(patsubst $(prefix)/%,%,$(obj))
ifeq ($(obj),$(src))
prefix := .
endif
endif
持续往下,到 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/kconfig
kbuild-dir = ./scripts/kconfig
kbuild-file = ./scripts/kconfig/Makefile
include ./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 line
PHONY := _all
_all:
而 _all 又有其余的依赖 all,定义在 194
# 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 的依赖又定义在 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),在 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 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
在 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)
else
u-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.o
endif
而 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 examples
u-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 -o
drivers/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 默认指标执行的流程如下图