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
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匹配的字符串,这里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
  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=`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 = $(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)
    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
  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/')

export    HOSTARCH 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 ?=
endif

KCONFIG_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        = 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__
  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 CHECKFLAGS

export 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.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 所包含的单词个数,这里为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
  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=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)
    @:
  1. 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
    @:
  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_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 默认目标执行的流程如下图
Linux编程入门--正点原子Linux驱动开发指南学习2021W25_第1张图片

你可能感兴趣的:(linux编程)