Linux编程入门--正点原子Linux驱动开发指南学习2021W25

十、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 工程

  1. 先用 Vscode 打开文件夹,然后在File中另存为workspace
  2. 在当前目录源码根目录下创建一个新目录 .vscode,然后这个目录下创建一个文件settings.json
  3. settings.json 可以配置当前工程要排除的文件和目录,"files.exclude":"search.exclude": 中填写要排除的文件,* 可以通配路径,可以通配字符,[0-9/a-z]可以通配连续的字符。
(3)顶层 Makefile 分析
  1. 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

  1. 命令输出方式(简介输出、静默输出)
    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 endififeq ($(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匹配的字符串,这里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 endifexport quiet Q KBUILD_VERBOSE

  1. 指定编译结果输出目录
    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 ,) 或是 $(if ,,)。 # 函数error的语法是:$(error ; ) $(if $(KBUILD_OUTPUT),, $(error failed to create output directory ''$(saved-output)''))# MAKECMDGOALS 变量是 make 的一个内置变量,它表示的是所要构建目标的一个终极列表。 # 这里是表示,将 MAKECMDGOALS 所表示的所有目标以及 sub-make 都定义为伪目标。 PHONY += $(MAKECMDGOALS) sub-make# 反过滤函数——filter-out,语法是:$(filter-out ,), # 这里使用 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=https://www.it610.com/article/`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),)

  1. 代码检查
    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 = https://www.it610.com/article/$(C) endif ifndef KBUILD_CHECKSRC KBUILD_CHECKSRC = 0 endif

  1. 模块编译
    在 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) 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 += 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

  2. 获得主机架构和系统
# 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/')exportHOSTARCH HOSTOS

  1. 设置目标架构,交叉编译器和配置文件
编 译 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 ?= endifKCONFIG_CONFIG?= .config export KCONFIG_CONFIG

  1. 调用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

  1. 变量设置
# 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= 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 -ffreestanding KBUILD_AFLAGS:= -D__ASSEMBLY__

  1. 导出变量
这些变量 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 CHECKFLAGSexport KBUILD_CPPFLAGS NOSTDINC_FLAGS UBOOTINCLUDE OBJCOPYFLAGS LDFLAGS export KBUILD_CFLAGS KBUILD_AFLAGS

  1. 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.hno-dot-config-targets := clean clobber mrproper distclean \ help %docs check% coccicheck \ ubootversion backupconfig-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 所包含的单词个数,这里为1,所以 mixed-targets 仍为 0 ifeq ($(KBUILD_EXTMOD),) ifneq ($(filter config %config,$(MAKECMDGOALS)),) config-targets := 1 ifneq ($(words $(MAKECMDGOALS)),1) mixed-targets := 1 endif endif endififeq ($(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_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_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

  1. 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=https://www.it610.com/article/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)

【Linux编程入门--正点原子Linux驱动开发指南学习2021W25】继续往下,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) @:

  1. Makefile.build 脚本分析 - %config 目标对应的命令
%config对应的命令为 @make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig,其相关的变量
src= https://www.it610.com/article/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 @:

  1. 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_checkALL-$(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.efiifneq ($(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 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 -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 默认目标执行的流程如下图
Linux编程入门--正点原子Linux驱动开发指南学习2021W25
文章图片

    推荐阅读