Skip to content

LV100-顶层Makefile基础解析 ​

U-Boot 顶层 Makefile 基础解析

一、版本号 ​

版本号定义在这里:Makefile

makefile
VERSION = 2019
PATCHLEVEL = 04
SUBLEVEL =
EXTRAVERSION =
NAME =

VERSION 是主版本号,PATCHLEVEL 是补丁版本号,SUBLEVEL 是次版本号,这三个一 起构成了 uboot 的版本号,比如当前的 uboot 版本号就是“2019.04”。EXTRAVERSION 是附加 版本信息,NAME 是和名字有关的,一般不使用这两个。

二、MAKEFLAGS 变量 ​

make 是支持递归调用的,也就是在 Makefile 中使用“make”命令来执行其他的 Makefile 文件,一般都是子目录中的 Makefile 文件。假如在当前目录下存在一个“subdir”子目录,这个 子目录中又有其对应的 Makefile 文件,那么这个工程在编译的时候其主目录中的 Makefile 就可 以调用子目录中的 Makefile,以此来完成所有子目录的编译。主目录的 Makefile 可以使用如下代码来编译这个子目录:

makefile
$(MAKE) -C subdir

$(MAKE)就是调用“make”命令,-C 指定子目录。有时候我们需要向子 make 传递变量, 这个时候使用“export”来导出要传递给子 make 的变量即可,如果不希望哪个变量传递给子 make 的话就使用“unexport”来声明不导出:

makefile
export VARIABLE   # 导出变量给子 make 
unexport VARIABLE # 不导出变量给子 make

有两个特殊的变量:“SHELL”和“MAKEFLAGS”,这两个变量除非使用“unexport”声明, 否则的话在整个 make 的执行过程中,它们的值始终自动的传递给子 make。在 uboot 的主 Makefile 中有如下代码:

makefile
MAKEFLAGS += -rR --include-dir=$(CURDIR)

上述代码使用“+=”来给变量 MAKEFLAGS 追加了一些值,“-rR”表示禁止使用内置的隐 含规则和变量定义,“--include-dir”指明搜索路径,”$(CURDIR)”表示当前目录。

三、命令输出 ​

1. 默认的输出情况 ​

uboot 默认编译是不会在终端中显示完整的命令,都是短命令:

shell
sumu@sumu-virtual-machine:~/7Linux/imx6ull-uboot$  make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16
scripts/kconfig/conf  --syncconfig Kconfig
  CHK     include/config.h
  UPD     include/config.h
  GEN     include/autoconf.mk.dep
  CFG     u-boot.cfg
  GEN     include/autoconf.mk
# ......
*** Your GCC is older than 6.0 and will not be supported
  HOSTCC  scripts/dtc/dtc.o
# ......
  HOSTCC  tools/zynqimage.o
# ......
  LD      fs/fat/built-in.o
  CC      lib/efi_loader/efi_unicode_collation.o
# ......
  OBJCOPY u-boot.srec
  SYM     u-boot.sym
  DTC     arch/arm/dts/imx6ull-14x14-ddr3-arm2.dtb
# ......
  COPY    u-boot.dtb
  COPY    u-boot.bin
  CFGS    u-boot-dtb.cfgout
  MKIMAGE u-boot-dtb.imx
  CFGCHK  u-boot.cfg

在终端中输出短命令虽然看起来很清爽,但是不利于分析 uboot 的编译过程。

2. 完整命令输出 ​

可以通过设 置变量“V = 1“来实现完整的命令输出,这个在调试 uboot 的时候很有用:

shell
sumu@sumu-virtual-machine:~/7Linux/imx6ull-uboot$  make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16
make -f ./Makefile syncconfig
#......
make -f ./scripts/Makefile.build obj=scripts/kconfig syncconfig
mkdir -p include/config include/generated
scripts/kconfig/conf  --syncconfig Kconfig
make -f ./scripts/Makefile.autoconf || \
	{ rm -f include/config/auto.conf; false; }
if [ -d arch/arm/mach-mx6/include/mach ]; then	\
	dest=../../mach-mx6/include/mach;			\
else								\
	dest=arch-mx6;			\
fi;								\
# ......
*** Your GCC is older than 6.0 and will not be supported
make -f ./scripts/Makefile.build obj=scripts
make -f ./scripts/Makefile.build obj=scripts/dtc
  cc -Wp,-MD,scripts/dtc/.dtc.o.d -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer  -std=gnu11    -Iscripts/dtc -Iscripts/dtc/libfdt -c -o scripts/dtc/dtc.o scripts/dtc/dtc.c
# ......
make -f ./scripts/Makefile.build obj=arch/arm/mach-imx u-boot-dtb.imx
  cp u-boot-dtb.bin u-boot.bin
#......
  ./tools/mkimage -n u-boot-dtb.cfgout -T imximage -e 0x87800000 -d u-boot-dtb.bin u-boot-dtb.imx >u-boot-dtb.imx.log  && cat u-boot-dtb.imx.log
Image Type:   Freescale IMX Boot Image
Image Ver:    2 (i.MX53/6/7 compatible)
Mode:         DCD
Data Size:    675840 Bytes = 660.00 KiB = 0.64 MiB
Load Address: 877ff420
Entry Point:  87800000
  ./scripts/check-config.sh u-boot.cfg ./scripts/config_whitelist.txt .

3. 命令输出控制 ​

3.1 命令输出控制命令 ​

顶层 Makefile 中控制命令输出的代码如下:

makefile
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

3.2 V 的来源 ​

V 的来源在 Makefile 中是这一部分:

makefile
ifeq ("$(origin V)", "command line")
  KBUILD_VERBOSE = $(V)
endif
ifndef KBUILD_VERBOSE
  KBUILD_VERBOSE = 0
endif

上述代码中先使用 ifeq 来判断 "$(origin V)" 和 "command line" 是否相等。这里用到了 Makefile 中的函数 origin,origin 和其他的函数不一样,它不操作变量的值,origin 用于告诉你变量是哪来的,语法为:

makefile
$(origin <variable>)

variable 是变量名,origin 函数的返回值就是变量来源,因此 $(origin V) 就是变量 V 的来源。 如果变量 V 是在命令行定义的那么它的来源就是 "command line",这样 "$(origin V)" 和 "command line" 就相等了。当这两个相等的时候变量 KBUILD_VERBOSE 就等于 V 的值,比如在命令行中 输入“ V = 1 “ 的 话 那 么 KBUILD_VERBOSE = 1 。如果没有在命令行输入 V 的话 KBUILD_VERBOSE = 0。

3.3 Q 与 quiet ​

这两个变量的控制在 Makefile 中是这一部分:

makefile
ifeq ($(KBUILD_VERBOSE),1)
  quiet =
  Q =
else
  quiet=quiet_
  Q = @
endif

判断 KBUILD_VERBOSE 是否为 1,如果 KBUILD_VERBOSE 为 1 的话变量 quiet 和 Q 都为空,如果 KBUILD_VERBOSE = 0 的话变量 quiet 为“quiet_“,变量 Q 为“@”,所以 V 的作用是:

  • V = 1
makefile
KBUILD_VERBOSE=1
quiet=空
Q=空
  • V = 0 或者命令行不定义 V
makefile
KBUILD_VERBOSE=0
quiet= quiet_
Q=@

4. 怎么控制输出信息? ​

Makefile 中会用到变量 quiet 和 Q 来控制编译的时候是否在终端输出完整的命令,在顶层 Makefile 中有很多如下所示的命令:

makefile
tools-only: scripts_basic $(version_h) $(timestamp_h)
	$(Q)$(MAKE) $(build)=tools

如果 V = 0 的话上述命令展开就是:

makefile
@ make $(build)=tools

make 在执行的时候默认会在终 端输出命令,但是在命令前面加上“@”就不会在终端输出命令了。

当 V = 1 的时候 Q 就为空, 上述命令就是:

makefile
make $(build)=tools

此时在 make 执行的过程,命令会被完整的输出在终端上。

有些命令会有两个版本,比如 Makefile 中的:

makefile
quiet_cmd_sym ?= SYM     $@
      cmd_sym ?= $(OBJDUMP) -t $< > $@

sym 命令分为“quiet_cmd_sym”和“cmd_sym”两个版本,这两个命令的功能都是一样的, 区别在于 make 执行的时候输出的命令不同。quiet_cmd_xxx 命令输出信息少,也就是短命令, 而 cmd_xxx 命令输出信息多,也就是完整的命令。 如果变量 quiet 为空的话,整个命令都会输出。 如果变量 quiet 为“quiet_”的话,仅输出短版本。 如果变量 quiet 为“silent_”的话,整个命令都不会输出。这部分在 Makefile - Beautify output 中有说明。

5. 静默输出 ​

5.1 静默输出命令 ​

设置 V = 0 或者在命令行中不定义 V 的话,编译 uboot 的时候终端中显示的短命令,但是还是会有命令输出,有时候我们在编译 uboot 的时候不需要输出命令,这个时候 就可以使用 uboot 的静默输出功能。编译的时候使用这个命令就可以实现:

shell
make -s

即可实现静默输出,顶层 Makefile 中相应的代码如下:

makefile
# If the user is running make -s (silent mode), suppress echoing of
# commands

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

5.2 make 版本号判断 ​

先看 Makefile 这一部分:

makefile
ifneq ($(filter 4.%,$(MAKE_VERSION)),)	# make-4
# (1)......
else					# make-3.8x
# (2)......
endif

这里在判断当前正在使用的 make 命令版本号是否为 4.x,首先进行如下判断:

makefile
判断 $(filter 4.%,$(MAKE_VERSION)) 和 " "(空)是否相等?

如果不相等的话就成立,执行里面的(1)语句。这里用到了 Makefile 中的 filter 函数,这是 个过滤函数,函数格式如下:

makefile
$(filter <pattern...>,<text>)

filter 函数表示以 pattern 模式过滤 text 字符串中的单词,仅保留符合模式 pattern 的单词, 可以有多个模式。函数返回值就是符合 pattern 的字符串。

因此 $(filter 4.%,$(MAKE_VERSION)) 的含义就是在字符串 “MAKE_VERSION” 中找出符合“4.%”的字符(%为通配符), MAKE_VERSION 是 make 工具的版本号,ubuntu16.04 里面默认自带的 make 工具版本号为 4.1,之后的版本应该都是 4.1 及以上了, 我们可以在 ubuntu 中输入“make -v”查看。所以:

makefile
$(filter 4.%,$(MAKE_VERSION)) != " "

这里条件成立。后续走(1)处的语句。

5.3 确定 quiet 的值 ​

上面我们已经确认条件成立,所以接下来走的是 Makefile 这一部分的命令:

makefile
ifneq ($(filter 4.%,$(MAKE_VERSION)),)	# make-4
ifneq ($(filter %s ,$(firstword x$(MAKEFLAGS))),)
  quiet=silent_
endif
else					# make-3.8x
# ......
endif

这里也是一个判断语句

makefile
$(filter %s ,$(firstword x$(MAKEFLAGS))) != " " (空)

如果上面的条件成立,变量 quiet 等于“silent_”。这里也用到了函数 filter:

makefile
$(firstword x$(MAKEFLAGS)))

在上面这个函数中过滤出符合“%s”的单词。到了函数 firstword,函数 firstword 是获取首单词,函数格式如下:

makefile
$(firstword <text>)

firstword 函数用于取出 text 字符串中的第一个单词,函数的返回值就是获取到的单词。当 使用“make -s”编译的时候,“-s”会作为 MAKEFLAGS 变量的一部分传递给 Makefile。在顶层 Makefile 的这段命令之后添加以下代码:

makefile
ifneq ($(filter 4.%,$(MAKE_VERSION)),)	# make-4
#......
else					# make-3.8x
# ......
endif
my_test:
	@echo "first word="$(firstword x$(MAKEFLAGS))

然后我们在源码目录执行以下命令,看一下这个到底输出的是啥:

shell
sumu@sumu-virtual-machine:~/7Linux/imx6ull-uboot$  make -s ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- my_test -j16
first word=xrRs

从输出可以看出第一个单词是“xrRs”,把下面的命令展开

makefile
$(filter %s ,$(firstword x$(MAKEFLAGS))) 
# 展开就是
$(filter %s, xrRs)

而$(filter %s, xrRs)的返回值肯定不为空,条件成立,所以 quiet = silent_。

6. 导出变量 ​

上面关于命令输出的变量的值都已经确定完毕了,最后使用 export 命令导出,导出命令在这里:Makefile

makefile
export quiet Q KBUILD_VERBOSE

使用 export 导出变量 quiet、Q 和 KBUILD_VERBOSE。

四、设置编译结果输出目录 ​

1. 输出目录控制命令 ​

uboot 可以将编译出来的目标文件输出到单独的目录中,在 make 的时候使用“O”来指定 输出目录。比如:

shell
make O=out

就是设置目标文件输出到 out 目录中。这么做是为了将源文件和编译产生的文件分开,当然也可以不指定 O 参数,不指定的话源文件和编译产生的文件都在 同一个目录内,一般在进行 uboot 编译的时候我们不指定 O 参数。在 Makefile 中实现设置输出目录的代码如下:

makefile
# ......
ifeq ($(KBUILD_SRC),)

# OK, Make called in directory where kernel src resides
# Do we want to locate output files in a separate directory?
ifeq ("$(origin O)", "command line")
  KBUILD_OUTPUT := $(O)
endif

# That's our default target when none is given on the command line
PHONY := _all
_all:

# Cancel implicit rules on top 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)
KBUILD_OUTPUT := $(shell mkdir -p $(KBUILD_OUTPUT) && cd $(KBUILD_OUTPUT) \
								&& /bin/pwd)
$(if $(KBUILD_OUTPUT),, \
     $(error failed to create output directory "$(saved-output)"))

PHONY += $(MAKECMDGOALS) sub-make

$(filter-out _all sub-make $(CURDIR)/Makefile, $(MAKECMDGOALS)) _all: sub-make
	@:

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
skip-makefile := 1
endif # ifneq ($(KBUILD_OUTPUT),)
endif # ifeq ($(KBUILD_SRC),)

2. O 参数来源判断 ​

Makefile 文件判断 O 参数来源的代码如下:

makefile
ifeq ("$(origin O)", "command line")
  KBUILD_OUTPUT := $(O)
endif

行判断“O”是否来自于命令行,如果来自命令行的话条件成立,KBUILD_OUTPUT 就为$(O),因此变量 KBUILD_OUTPUT 就是输出目录。

3. 创建 KBUILD_OUTPUT 目录 ​

还需要判断一下这个 KBUILD_OUTPUT 目录是否存在,不存在的话,会进行创建,Makefile 中对应的代码如下:

makefile
saved-output := $(KBUILD_OUTPUT)
KBUILD_OUTPUT := $(shell mkdir -p $(KBUILD_OUTPUT) && cd $(KBUILD_OUTPUT) \
								&& /bin/pwd)
$(if $(KBUILD_OUTPUT),, \
     $(error failed to create output directory "$(saved-output)"))

这里相当于将 KBUILD_OUTPUT 重新赋值了一下,从相对路径变为绝对路径。经过这里之后,一个不存在的输出目录就被创建好了。

五、代码检查 ​

1. 代码检查控制命令 ​

uboot 支持代码检查,命令如下:

shell
make C=1 # 使能代码检查,检查那些需要重新编译的文件。
make C=2 # 用于检查所有的源码文件

2. KBUILD_CHECKSRC ​

顶层 Makefile 中的代码如下:

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 "doc/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

和前面一样,判断一下 C 是否来源于命令行,如果 C 来源于命令行,那就将 C 赋值给变量 KBUILD_CHECKSRC,如果命令行没有 C 的话 KBUILD_CHECKSRC 就为 0。

六、模块编译 ​

1. 模块编译命令 ​

在 uboot 中允许单独编译某个模块,使用以下命令:

shell
make M=dir
make SUBDIRS=dir # 旧语法是这样的,也是支持的。

2. KBUILD_EXTMOD ​

我们看一下 Makefile 中对这个参数的定义:

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

首先判断是否定义了 SUBDIRS , 如果定义了 SUBDIRS , 变量 KBUILD_EXTMOD = SUBDIRS,这里是为了支持老语法“make SUBIDRS = dir” 。

接着后面是判断是否在命令行定义了 M,如果定义了的话 KBUILD_EXTMOD =$(M)。

3. 编译目标 ​

接着是根据上面的 KBUILD_EXTMOD 是否为空来确定编译目标,我们看一下 Makefile 文件:

makefile
# 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

判断 KBUILD_EXTMOD 时为空,如果为空的话目标_all 依赖 all,因此要先编译出 all。否则的话默认目标_all 依赖 modules,要先编译出 modules,也就是编译模块。一般情况 下我们不会在 uboot 中编译模块,所以此处会编译 all 这个目标。

七、VPATH 的配置 ​

看一下 Makefile 这一部分:

makefile
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

第 1~11 行判断 KBUILD_SRC 是否为空,如果为空的话就设置变量 srctree 为当前目录,即 srctree 为“.”,一般不设置 KBUILD_SRC。

第 12 行设置变量 objtree 为当前目录。

第 13~14 行分别设置变量 src 和 obj,根据前面的变量的值,这里都为当前目录。

第 16 行设置 VPATH。 这里会 KBUILD_EXTMOD 是否为空,要是非空,这里就会加入 KBUILD_EXTMOD 路径。也就是要编译的模块的路径。

第 18 行导出变量 scrtree、objtree 和 VPATH。

八、编译平台 ​

1. 主机架构 HOSTARCH ​

HOSTARCH 在 Makefile 中定义如下:

cmake
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/)

变量 HOSTARCH,用于保存主机架构,这里调用 shell 命令“uname -m”获取架构名称:

shell
sumu@sumu-virtual-machine:~/7Linux/imx6ull-uboot$  uname -m
x86_64

可以看出当前 ubuntu 主机架构为“x86_64”,shell 中的“|”表示管道,意思是将 左边的输出作为右边的输入,sed -e 是替换命令,“sed -e s/i.86/x86/”表示将管道输入的字符串 中的“i.86”替换为“x86”,其他的“sed -e s”命令同理。对于我现在运行的 ubuntu 系统而言,HOSTARCH = x86_64。

2. 主机操作系统 HOSTOS ​

HOSTOS 在 Makefile 中定义如下:

makefile
HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \
	    sed -e 's/\(cygwin\).*/cygwin/')

变量 HOSTOS,此变量用于保存主机 OS 的值,先使用 shell 命令“uname -s”来获取主机 OS,结果下所示:

shell
sumu@sumu-virtual-machine:~/7Linux/imx6ull-uboot$  uname -s
Linux

可以看出此时的主机 OS 为“Linux”,使用管道将“Linux”作为后面“ tr '[:upper:]' '[:lower:]' ”的输入,“ tr '[:upper:]' '[:lower:]' ”表示将所有的大写字母替换为小写字母,因此得到 “ linux ”。最后同样使用管道,将“ linux ”作为“ sed -e 's/(cygwin).*/cygwin/' ”的输入,用于将 cygwin.*替换为 cygwin。因此,现在在这里 HOSTOS = linux。

3. 导出 HOSTARCH 和 HOSTOS ​

接着导出这两个变量,在 Makefile 中定义如下:

makefile
export	HOSTARCH HOSTOS

4. 目标架构和编译器 ​

4.1 设置命令 ​

编译 uboot 的时候需要设置目标板架构和交叉编译器,我们一般在使用 make 命令的时候传入:

shell
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-

这个命令就是用于设置 ARCH 和 CROSS_COMPILE。

4.2 ARCH 与 CROSS_COMPILE ​

在顶层 Makefile 中代码如下:

makefile
ifeq ($(HOSTARCH),$(ARCH))
CROSS_COMPILE ?=
endif

这里判断 HOSTARCH 和 ARCH 这两个变量是否相等,前面我们知道主机架构(变量 HOSTARCH)是 x86_64,ARCH 就是通过 make 命令传入,要是没有传入,那 ARCH 就为空,我们编译的是 ARM 版本 uboot,肯定不相等,所以 CROSS_COMPILE = arm-linux-gnueabihf-。当然,要是我们在 ARM 开发板装了 GCC,这里就不需要前缀了,因为开发板上装的 GCC 一定就是 ARM 版本的,可以直接本地编译,不需要交叉编译。

可以看到,每次编译 uboot 的时候都要在 make 命令后面设置 ARCH 和 CROSS_COMPILE,使用起来很麻烦(当然,写 shell 脚本调用 Makefile 的除外),我们完全可以直接修改顶层 Makefile,在里面加入 ARCH 和 CROSS_COMPILE 的定义,如下所示:

makefile
ARCH          ?= arm
CROSS_COMPILE ?= arm-linux-gnueabihf-

这样直接在顶层 Makefile 里面定义 ARCH 和 CROSS_COMPILE,这样就不用每次编译的时候都要在 make 命令后面定义 ARCH 和 CROSS_COMPILE,直接一个 make 就可以了。

九、配置文件 ​

makefile
KCONFIG_CONFIG	?= .config
export KCONFIG_CONFIG

uboot 是可以配置的,这里设置配置文件为 .config。.config 文件默认是没有的,需要使用命令:

shell
make xxx_defconfig

对 uboot 进行配置,配置完成以后就会在 uboot 根目录下生成 .config。默认情况下.config 和 xxx_defconfig 内容是一样的,因为 .config 就是从 xxx_defconfig 复制过来的。如果后续自行调整 了 uboot 的一些配置参数,那么这些新的配置参数就添加到了.config 中,而不是 xxx_defconfig。 相当于 xxx_defconfig 只是一些初始配置,而 .config 里面的才是实时有效的配置。我们在图形配置界面进行的各种配置也会被写入到这个 .config 文件中。

十、通用定义 ​

1. Kbuild.include ​

这里还会有一些通用的定义在 scripts/Kbuild.include 文件中:

md
####
# kbuild: Generic definitions

# Convenient variables
comma   := ,
quote   := "
squote  := '
empty   :=
space   := $(empty) $(empty)
pound := \#

###
# Name of target with a '.' as filename prefix. foo/bar.o => foo/.bar.o
dot-target = $(dir $@).$(notdir $@)

###
# The temporary file to save gcc -MD generated dependencies must not
# contain a comma
depfile = $(subst $(comma),_,$(dot-target).d)

2. 怎么被包含到 Makefile 中? ​

在 Makefile 中调用的代码:

makefile
# We need some generic definitions (do not try to remake the file).
scripts/Kbuild.include: ;
include scripts/Kbuild.include

在 uboot 的编译过程中会用到 scripts/Kbuild.include 中的这些变量,后面用到的时候再分析。

十一、相关变量 ​

1. 交叉编译工具变量 ​

上面我们只是设置了 CROSS_COMPILE 的名字,但是交叉编译器其他的工具还没有设置,顶层 Makefile 中相关代码如下:

makefile
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
LEX		= flex
YACC		= bison
AWK		= awk
PERL		= perl
PYTHON		?= python
PYTHON2		= python2
PYTHON3		= python3
DTC		?= $(objtree)/scripts/dtc/dtc

2. 编译选项 ​

我们编译的时候还有一些选项的定义,这些在顶层的 Makefile 中也有定义,例如这里就有一些宏和标志:

makefile
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 $(CSTD_FLAG)
KBUILD_CFLAGS	+= -fshort-wchar -fno-strict-aliasing
KBUILD_AFLAGS   := -D__ASSEMBLY__

3. 变量导出 ​

接下来在顶层 Makefile 会导出很多变量:

makefile
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 LEX YACC AWK PERL PYTHON PYTHON2 PYTHON3
export HOSTCXX HOSTCXXFLAGS CHECK CHECKFLAGS DTC DTC_FLAGS

export KBUILD_CPPFLAGS NOSTDINC_FLAGS UBOOTINCLUDE OBJCOPYFLAGS LDFLAGS
export KBUILD_CFLAGS KBUILD_AFLAGS

# When compiling out-of-tree modules, put MODVERDIR in the module
# tree rather than in the kernel tree. The kernel tree might
# even be read-only.
export MODVERDIR := $(if $(KBUILD_EXTMOD),$(firstword $(KBUILD_EXTMOD))/).tmp_versions

# Files to ignore in find ... statements

export RCS_FIND_IGNORE := \( -name SCCS -o -name BitKeeper -o -name .svn -o    \
			  -name CVS -o -name .pc -o -name .hg -o -name .git \) \
			  -prune -o
export RCS_TAR_IGNORE := --exclude SCCS --exclude BitKeeper --exclude .svn \
			 --exclude CVS --exclude .pc --exclude .hg --exclude .git

这些变量中大部分都已经在前面定义了,我们重点来看一下下面这几个变量:

makefile
ARCH CPU BOARD VENDOR SOC CPUDIR BOARDDIR

这 7 个变量在顶层 Makefile 是找不到的,说明这 7 个变量是在其他文件里面定义的。先来看一下这 7 个变量都是什么内容,在顶层 Makefile 中导出变量结束的位置输入如图所示的内容:

makefile
# ......
export RCS_TAR_IGNORE := --exclude SCCS --exclude BitKeeper --exclude .svn \
			 --exclude CVS --exclude .pc --exclude .hg --exclude .git

my_test:
	@echo "ARCH"=$(ARCH) 
	@echo "CPU"=$(CPU) 
	@echo "BOARD"=$(BOARD) 
	@echo "VENDOR"=$(VENDOR) 
	@echo "SOC"=$(SOC) 
	@echo "CPUDIR"=$(CPUDIR) 
	@echo "BOARDDIR"=$(BOARDDIR)

然后执行以下命令看一下打印信息:

shell
sumu@sumu-virtual-machine:~/7Linux/imx6ull-uboot$  make -s ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- my_test -j16
ARCH=arm
CPU=armv7
BOARD=mx6ullevk
VENDOR=freescale
SOC=mx6
CPUDIR=arch/arm/cpu/armv7
BOARDDIR=freescale/mx6ullevk

可以看到这 7 个变量的值,这 7 个变量是从哪里来的呢?在 uboot 根目录下有个文件叫做 config.mk,这 7 个变量就是在 config.mk 里面定义的,打开 config.mk 内容如下:

makefile
# ......
# clear VENDOR for tcsh
VENDOR :=
#########################################################################

ARCH := $(CONFIG_SYS_ARCH:"%"=%)
CPU := $(CONFIG_SYS_CPU:"%"=%)
ifdef CONFIG_SPL_BUILD
ifdef CONFIG_TEGRA
CPU := arm720t
endif
endif
BOARD := $(CONFIG_SYS_BOARD:"%"=%)
ifneq ($(CONFIG_SYS_VENDOR),)
VENDOR := $(CONFIG_SYS_VENDOR:"%"=%)
endif
ifneq ($(CONFIG_SYS_SOC),)
SOC := $(CONFIG_SYS_SOC:"%"=%)
endif
# ......
CPUDIR=arch/$(ARCH)/cpu$(if $(CPU),/$(CPU),)
# ......
ifneq ($(BOARD),)
ifdef	VENDOR
BOARDDIR = $(VENDOR)/$(BOARD)
else
BOARDDIR = $(BOARD)
endif
endif
# ......

接下来需要找到 CONFIG_SYS_ARCH、 CONFIG_SYS_CPU、 CONFIG_SYS_BOARD、CONFIG_SYS_VENDOR 和 CONFIG_SYS_SOC 这 5 个变量的值。这 5 个变量在 uboot 根目录下的 .config 文件(这个文件需要执行配置命令后才会生成)中有定义,,可以打开看一下,搜索一下这个几个变量,会发现他们在 .config 文件中定义如下:

md
CONFIG_SYS_ARCH="arm"
CONFIG_SYS_CPU="armv7"
CONFIG_SYS_SOC="mx6"
CONFIG_SYS_VENDOR="freescale"
CONFIG_SYS_BOARD="mx6ull_alpha_emmc"
CONFIG_SYS_CONFIG_NAME="mx6ull_alpha_emmc"

主要是这里我是用的自己移植的板子,所以这些名字什么都不太一样,不过用 nxp evk 评估板的默认配置文件的话会是对应的。所以就有:

makefile
ARCH = arm
CPU = armv7
BOARD = mx6ullevk
VENDOR = freescale
SOC = mx6
CPUDIR = arch/arm/cpu/armv7
BOARDDIR = freescale/mx6ullevk

会发现和上面的打印信息是一样的。再来分析 config.mk ,就会发现在 config.mk 中读取的文件有 :

md
arch/arm/config.mk
arch/arm/cpu/armv7/config.mk
arch/arm/cpu/armv7/mx6/config.mk (此文件不存在)
board/ freescale/mx6ullevk/config.mk (此文件不存在)