在阅读 uboot 源码之前,肯定是要先看一下顶层 Makefile,分析 gcc 版本代码的时候一定是先从顶层 Makefile 开始的,然后再是子 Makefile,这样通过层层分析 Makefile 即可了解整个工程的组织结构。顶层 Makefile 也就是 uboot 根目录下的 Makefile 文件,由于顶层 Makefile 文件内容比较多,所以我们将其分开来看。
版本号
VERSION = 2016
PATCHLEVEL = 03
SUBLEVEL =
EXTRAVERSION =
NAME =
VERSION 是主版本号,PATCHLEVEL 是补丁版本号,SUBLEVEL 是次版本号,这三个一起构成了 uboot 的版本号,比如当前的 uboot 版本号就是“2016.03”。EXTRAVERSION 是附加版本信息,NAME 是和名字有关的,一般不使用这两个。
MAKEFLAGS变量
make是支持递归调用的,也就是在Makefile中使用“make”命令来执行其它的Makefile文件,一般都是子目录中的Makefile文件。
假如在当前目录下存在一个“subdir”子目录,这个子目录中又有其对应的 Makefile 文件,那么这个工程在编译的时候其主目录中的 Makefile 就可以调用子目录中的 Makefile,以此来完成所有子目录的编译。主目录的 Makefile 可以使用如下代码来编译这个子目录:
$(MAKE) -C subdir
$(MAKE)就是调用“make”命令,-C指定子目录,有时候我们需要向子make传递变量,这个时候使用export来导出要传递给子make的变量即可,如果不希望哪个变量传递给子make 的话就使用“unexport"声明不导出。
export VARIABLE …… //导出变量给子 make 。
unexport VARIABLE…… //不导出变量给子 make。
有两个特殊的变量:“SHELL和MAKEFLAGS”,这两个变量除非使用“unexport”声明,否则的话在整个make的执行过程中,它们的值始终自动的传递给子make。
在uboot的主Makefile中有如下代码:
MAKEFLAGS += -rR --include-dir=$(CURDIR)
+=给变量MAKEFLAGS追加了一些值
-rR表示禁止使用内置的隐含规则和变量定义
–include-dir指明搜索路径
$(CURDIR)当前目录
命令输出
uboot默认编译不会再终端显示完整命令,都是短命令。
在终端中输出短命令虽然看起来很清爽,但是不利于分析 uboot 的编译过程。
可以通过设置变量“V=1“来实现完整的命令输出,这个在调试 uboot 的时候很有用。
顶层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
使用iref判断“$(origin V)”和“command line”是否相等。
这里用到了 Makefile中的函数 origin,origin 和其他的函数不一样,它不操作变量的值,origin 用于告诉你变量是哪来的。
$(origin <variable>)
variable是变量名,origin函数的返回值就是变量来源,因此 ( o r i g i n V ) 就是变量 V 的来源。如果变量 V 是在命令行定义的 n a m e 它的来源就是 c o m m a n d l i n e ,这样两者的值就像等。当这两个相等的时候变量 K B U I L D V E R B O S E 就等于 V 的值,比如在命令行中输入“ V = 1 “的话那么 K B U I L D V E R B O S E = 1 。如果没有在命令行输入 V 的话 K B U I L D V E R B O S E = 0 。 i f e q ( (origin V)就是变量V的来源。 如果变量V是在命令行定义的name它的来源就是command line,这样两者的值就像等。 当这两个相等的时候变量 KBUILD_VERBOSE 就等于 V 的值,比如在命令行中输入“ V=1 “ 的 话 那 么 KBUILD_VERBOSE=1 。 如 果 没 有 在 命 令 行 输 入 V 的 话KBUILD_VERBOSE=0。 ifeq( (originV)就是变量V的来源。如果变量V是在命令行定义的name它的来源就是commandline,这样两者的值就像等。当这两个相等的时候变量KBUILDVERBOSE就等于V的值,比如在命令行中输入“V=1“的话那么KBUILDVERBOSE=1。如果没有在命令行输入V的话KBUILDVERBOSE=0。ifeq((KBUILD_VERBOSE),1),如果 KBUILD_VERBOSE 为 1 的话变量 quiet和 Q 都为空,如果 KBUILD_VERBOSE=0 的话变量 quiet 为“quiet_“,变量 Q 为“@”。
Makefile中会用到变量quiet和Q来控制编译的时候是否在终端输出完整的命令。
$(Q)$(MAKE)$(build)=tools
如果V=0,上述命令实际上为@ make $(build)=tools。
make在执行的时候默认会在终端输出命令,但在命令前面加上@就不会。
当 V=1 的时候 Q 就为空,上述命令就是“make $(build)=tools”,因此在 make 执行的过程,命令会被完整的输出在终端上。
有些命令会有两个版本,比如
quiet_cmd_sys ?= SYM $@
cmd_sym ?= $(OBJDUMP) -t $< >$@
sys命令分为quiet_cmd_sym和cmd_sym两个版本,这两个命令的功能都是一样的,区别在于make执行的时候输出的命令不同。
quiet_cmd_sys命令输出信息少,也就是短命令。
而cmd_xxx命令输出信息多,就是完整的命令。
- 如果变量quiet为空,整个命令都会输出。
- 如果变量 quiet 为“quiet_”的话,仅输出短版本。
- 如果变量 quiet 为“silent_”的话,整个命令都不会输出。
静默输出
设置V=0或者命令行中不定义V的话,编译uboot的时候终端中显示短命令,但是还是会有命令输出,有时候我们在编译uboot的时候不需要输出命令,这个就可以使用 uboot 的静默输出,编译的时候使用“make -s”即可实现静默输出。
设置编译结果输出目录
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
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),)
ifeq (“$(origin O)”, “command line”)
KBUILD_OUTPUT := $(O)
endif
- 判断O是否来自于命令行,如果来自命令行,KBUILD_OUTPUT =$(O),因此变量KBUILD_OUTPUT就是输出目录。
ifneq ($(KBUILD_OUTPUT),)
- 判断KBUILD_OUTPUT是否为空
- 调用mkdir命令,创建KBUILD_OUTPUT目录,并且创建成功以后的绝对路径赋值给KBUILD_OUTPUT,至此,通过O指定的输出目录就存在了。
模块编译
在uboot中也允许单独编译某个模块,使用命令“make M=dir”或旧语法“make SUBDIRS=dir”也支持。
获取主机架构和系统
接下来顶层Makefile会获取主机架构和系统,也就是我们电脑的架构和系统:
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/)
HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \
sed -e 's/\(cygwin\).*/cygwin/')
export HOSTARCH HOSTOS
- 定义了一个HOSTARCH变量,保存主机架构,这里调用shell命令“uname -m”获取架构名称。
可以看出电脑主机架构为x86_64,shell中的“|”表示管道,意思是将左边的输出作为右边的输入。
sed -e是替换命令,“sed -e s/i.86/x86/”表示将管道输入的字符串i.86替换为“x86”。
HOSTOS用于保存主机OS的值,使用管道将“Linux”作为后面tr ‘[:upper:]’ '[:lower:]'的输入。
tr ‘[:upper:]’ '[:lower:]'将所有的大小字母替换为小写字母,