UBoot顶层Makefile解析
1.1 Makefile前期所做的内容
1.MAKEFLAGS
make是支持递归调用的,也就是在Makefile中使用“make”命令来执行其他的Makefile文件,一般都是子目录中的Makefile文件。加入在当前目录下存在一个“subdir”子目录,这个子目录中又有其对应的Makefile文件,那么这个工程在编译的时候其主目录中的Makefile就可以调用子目录中的Makefile,以此来完成所有子目录的编译。主目录的Makefile可以使用如下代码来编译这个子目录:
| $(MAKE) –C subdir |
$(MAKE)就是调用“make”命令,-C指定子目录。有时候我们需要向子make传递变量,这时使用“export”来导出要传递给子make的变量即可,如果不希望传递给子make,则使用“unexport”。
有两个特殊的变量:“SHELL”和“MAKEFLAGS”,这两个变量除非使用“unexport”声明,否则的话在整个make的执行过程中,它们的值始终自动的传递给子make。在uboot的主Makefile中有如下代码:
目录
1.3 Makefile.build脚本分析-20210417
| MAKEFLAGS += -rR --include-dir=$(CURDIR) |
使用+=来给变量MAKEFLAGS追加了一些值,“-rR”表示禁止使用内置的隐含规则和变量定义,“--include-dir”指明搜索了路径,“$(CURDIR)”表示当前目录。
2.是否输出完整的编译过程
关于是否输出完整的编译过程的判断有如下语句:
| #判断V是否来自命令行 ifeq ("$(origin V)", "command line") KBUILD_VERBOSE = $(V) endif ifndef KBUILD_VERBOSE KBUILD_VERBOSE = 0 endif #如果V=0,则在加了@的命令不会再终端输出;如果V=1,则命令都会输出 ifeq ($(KBUILD_VERBOSE),1) quiet = Q = else quiet=quiet_ Q = @ endif |
上述代码中先使用ifeq来判断“$(origin V)”和“command line”是否相等。这里用到了Makefile中的函数origin,origin和其他的函数不一样,它不操作变量的值,origin用于告诉你变量是哪来的,语法为:
| $(origin <variable>) |
variable是变量名,o函数的返回值就是变量来源,因此$(origin V)就是变量V的来源。如果变量V是在命令行定义的,那么它的来源就是“command line”,这样“$(origin V)”和“command line”就相等了。当这两个相等的时候变量KBUILD_VERBOSE就等于V的值,比如在命令行输入V=1的话,那么KBUILD_VERBOSE=1。如果没有在命令行输入V的话,KBUILD_VERBOSE=0。而之后又有判断语句,若KBUILD_VERBOSE=1的话,变量quiet的值就为空、变量Q的值就为空;若KBUILD_VERBOSE=0的话,变量quiet的值就为quiet_、变量Q的值就为@。
Makefile中会用到变量quiet和Q来控制编译的时候是否在终端输出完整的命令,在顶层Makefile 中有很多如下所示的命令:
| $(Q)$(MAKE) $(build)=tools |
如果V=0的话上述命令展开就是“@ make $(build)=tools”,make在执行的时候默认会在终端输出命令,但是在命令前面加上“@”就不会在终端输出命令了。当V=1的时候Q就为空,上述命令就是“make $(build)=tools”,因此在make执行的过程,命令会被完整的输出在终端上。
有些命令会有两个版本,比如:
| 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_”的话,整个命令都不会输出。
3.静默输出
如果在编译时不想看到编译输出信息,则在输入编译命令时,在后面加上-s选项:make –s。顶层 Makefile中相应的代码如下:
| 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行语句判断当前正在使用的编译器版本号是否为 4.x,判断$(filter 4.%,$(MAKE_VERSION))和“ ” (空)是否相等,如果不相等的话就成立,执行里面的语句。也就是说 $(filter 4.%,$(MAKE_VERSION))不为空的话条件就成立,这里用到了Makefile中的filter函数,这是个过滤函数,函数格式如下:
| $(filter <pattern...>,<text>) |
filter 函数表示以 pattern 模式过滤 text 字符串中的单词,仅保留符合模式pattern的单词,可以有多个模式。函数返回值就是符合pattern的字符串。因此$(filter 4.%,$(MAKE_VERSION))的含义就是在字符串“MAKE_VERSION”中找出符合“4.%”的字符 (%为通配符 ),MAKE_VERSION 是 make工具的版本号,ubuntu16.04里面默认自带的 make工具版本号为 4.1,大家可以输入“make -v”查看。因此$(filter 4.%,$(MAKE_VERSION))不为空,条件成立,执行第2~4行语句。
第2行也是一个判断语句,如果$(filter %s ,$(firstword x$(MAKEFLAGS)))不为空的话条件成立,变量 quiet 等于“silent_”。这里也用到了函数filter,在$(firstword x$(MAKEFLAGS)))中过滤出符合“%s”的单词。到了函数 firstword,函数 firstword 是获取首单词,函数格式如下:
| $(firstword <text>) |
firstword 函数用于取出text字符串中的第一个单词,函数的返回值就是获取到的单词。当使用“make -s”编译的时候,“-s”会作为MAKEFLAGS变量的一部分传递给Makefile。最终获取的结果是$(filter %s, xrRs),肯定不为空,因此quiet=silent_。
最后一行使用 export 导出变量 quiet、 Q 和 KBUILD_VERBOSE。
4.指定编译生成文件的存放目录
用法:make O=路径。顶层 Makefile 中相关的代码如下:
| ifeq ($(KBUILD_SRC),) # OK, Make called in directory where kernel src resides # Do we want to locate output files in a separate directory? # 如果在命令行输入了O的值,则将KBUILD_OUTPUT的值赋为O指定的路径 ①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),) |
语句①判断“O”是否来自于命令行,如果来自命令行的话条件成立, KBUILD_OUTPUT就为$(O),因此变量 KBUILD_OUTPUT 就是输出目录。
语句②判断KBUILD_OUTPUT 是否为空。
语句③调用 mkdir 命令,创建 KBUILD_OUTPUT 目录,并且将创建成功以后的绝对路径赋值给 KBUILD_OUTPUT。至此,通过 O 指定的输出目录就存在了。
5.代码检查
uboot 支持代码检查,使用命令“make C=1”使能代码检查,检查那些需要重新编译的文件。“make C=2”用于检查所有的源码文件,顶层 Makefile 中的代码如下:
| ifeq ("$(origin C)", "command line") KBUILD_CHECKSRC = $(C) endif ifndef KBUILD_CHECKSRC KBUILD_CHECKSRC = 0 endif |
第1行判断C是否来源于命令行,如果C来源于命令行,那就将C赋值给变量KBUILD_CHECKSRC,如果命令行没有C的话KBUILD_CHECKSRC就为 0。
6.模块编译
在 uboot 中允许单独编译某个模块,使用命令“make M=dir”即可,旧语法“make SUBDIRS=dir”也是支持的。顶层 Makefile 中的代码如下:
| ①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 PHONY += all ③ifeq ($(KBUILD_EXTMOD),) _all: all else _all: modules endif ④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 |
语句①表示判断是否定义了SUBDIRS,如果定义了SUBDIRS,变量KBUILD_EXTMOD=SUBDIRS,这里是为了支持老语法“make SUBIDRS=dir”
语句②判断是否在命令行定义了 M,如果定义了的话 KBUILD_EXTMOD=$(M)。
语句③判断KBUILD_EXTMOD是否为空,如果为空的话目标_all依赖all,因此要先编译出all。否则的话默认目标_all依赖modules,要先编译出modules,也就是编译模块。一般情况下我们不会在uboot中编译模块,所以此处会编译 all 这个目标。
语句④判断 KBUILD_SRC 是否为空,如果为空的话就设置变量srctree为当前目录,即srctree 为“.”,一般不设置 KBUILD_SRC。
语句⑤设置变量objtree为当前目录。
语句⑥和语句⑦分别设置变量src和obj,都为当前目录。
语句⑧设置VPATH。
语句⑨导出变量scrtree、objtree和VPATH。
使用echo把这三个变量打印出来得到:
srctree = .
objtree = .
VPATH = .
也就是都是表示当前目录。
7.获取主机架构和系统
以下几行语句用于获取主机架构和系统,也就是我们电脑的架构和系统。
| ①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”获取架构名称可知,shell 中的“|”表示管道,意思是将左边的输出作为右边的输入, sed -e 是替换命令,“sed -e s/i.86/x86/”表示将管道输入的字符串中的“i.86”替换为“x86”,其他的“sed -s”命令同理。对于我的电脑而言, HOSTARCH=x86_64。
语句②定义了变量 HOSTOS,此变量用于保存主机 OS 的值,先使用 shell 命令“name -s”来获取主机OS,可以看出此时的主机 OS 为“Linux”,使用管道将“Linux”作为后面“tr '[:upper:]' '[:lower:]'”的输入,“tr '[:upper:]' '[:lower:]'”表示将所有的大写字母替换为小写字母,因此得到“linux”。最后同样使用管道,将“linux”作为“sed -e 's/\(cygwin\).*/cygwin/'”的输入,用于将cygwin.*替换为 cygwin。因此, HOSTOS=linux。
语句③导出HOSTARCH=x86_64,HOSTOS=linux。
8.设置目标架构、交叉编译器和配置文件
编译uboot的时候需要设置目标板架构和交叉编译器,“make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-”就是用于设置 ARCH 和 CROSS_COMPILE,在顶层Makefile 中代码如下:
| # set default to nothing for native builds ①ifeq ($(HOSTARCH),$(ARCH)) CROSS_COMPILE ?= endif ②KCONFIG_CONFIG ?= .config ③export KCONFIG_CONFIG |
语句①判断 HOSTARCH 和 ARCH 这两个变量是否相等,主机架构(变量 HOSTARCH)是x86_64,而我们编译的是 ARM 版本 uboot,肯定不相等,所以CROS_COMPILE= arm-none-linux-gnueabihf-。每次编译uboot的时候都要在make命令后面设置ARCH和CROS_COMPILE,使用起来很麻烦,可以直接修改顶层 Makefile,在里面加入ARCH和CROSS_COMPILE的定义:
| # set default to nothing for native builds ①ifeq ($(HOSTARCH),$(ARCH)) CROSS_COMPILE ?= endif ARCH ?= arm CROSS_COMPILE ?= arm-none-linux-gnueabihf- KCONFIG_CONFIG ?= .config export KCONFIG_CONFIG |
语句②定义变量KCONFIG_CONFIG,uboot是可以配置的,这里设置配置文件为.config,.config 默认是没有的,需要使用命令“make xxx_defconfig”对uboot进行配置,配置完成以后就会在uboot根目录下生成.config。默认情况下.config和xxx_defconfig内容是一样的,因为.config就是从xxx_defconfig 复制过来的。如果后续自行调整了uboot的一些配置参数,那么这些新的配置参数就添加到了.config中,而不是 xxx_defconfig。相当于xxx_defconfig只是一些初始配置,而.config里面的才是实时有效的配置。
9.调用scripts/Kbuild.include
主Makefile会调用文件scripts/Kbuild.include这个文件,顶层Makefile中代码如下:
| # We need some generic definitions (do not try to remake the file). scripts/Kbuild.include: ; include scripts/Kbuild.include |
此文件里面定义了很多变量,在 uboot 的编译过程中会用到 scripts/Kbuild.include中的这些变量,后面用到的时候再分析。
9.交叉编译工具变量设置
上面我们只是设置了CROSS_COMPILE的名字,但是交叉编译器其他的工具还没有设置,顶层Makefile中相关代码如下:
| # Make variables (CC, etc...) #交叉编译工具链用到的一些工具名,如CC=arm-none-linux-gnueabihf-gcc 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 |

最低0.47元/天 解锁文章
1609

被折叠的 条评论
为什么被折叠?



