UBoot顶层Makefile解析

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中有如下代码

目录

UBoot顶层Makefile解析

1.1 Makefile前期所做的内容

1.MAKEFLAGS

2.是否输出完整的编译过程

3.静默输出

4.指定编译生成文件的存放目录

5.代码检查

6.模块编译

7.获取主机架构和系统

8.设置目标架构、交叉编译器和配置文件

9.调用scripts/Kbuild.include

9.交叉编译工具变量设置

1.2 make xxx_defconfig过程

1.3 Makefile.build脚本分析-20210417

1.scripts_basic目标对应的命令

2.%config目标对应的命令

1.4 make过程


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 导出变量 quietQ 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猿神出世

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值