U-Boot Makefile分析

当我们拿到开发板以后,是有三种 uboot 的,这三种 uboot的区别如表所示:
在这里插入图片描述

U-Boot 初次编译

首先在 Ubuntu 中安装 ncurses 库, 否则编译会报错:

sudo apt-get install libncurses5-dev

将正点原子提供的uboot-imx-2016.03-2.1.0-ge468cdc-v1.5.tar.bz2拷贝到自己建的文件夹下,并进行解压。

tar -vxjf uboot-imx-2016.03-2.1.0-g8b546e4.tar.bz2

这里使用的是 512MB+8GB 的 EMMC 核心板,使用如下命令来编译对应的 uboot:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-  mx6ull_14x14_ddr512_emmc_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j12

上面的命令序列是在使用Make工具来编译一个针对特定硬件(如基于ARM架构的设备)的Linux内核或其他软件项目。这些命令通常在Linux内核开发或为嵌入式系统编译软件时使用。

1. make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean

  • make:调用Make工具,它根据Makefile(一个定义了编译规则的文件)来执行编译任务。
  • ARCH=arm:设置ARCH变量为arm,告诉Make工具当前的目标架构是ARM。这对于编译针对特定架构的代码非常重要。
  • CROSS_COMPILE=arm-linux-gnueabihf-:设置CROSS_COMPILE变量,指定交叉编译工具链的前缀。这里arm-linux-gnueabihf-前缀意味着将使用名为arm-linux-gnueabihf-gcc(等等)的交叉编译器。交叉编译是在一种架构(如x86)上编译另一种架构(如ARM)上运行的代码的过程。
  • distclean:这是一个Makefile中定义的目标,它的作用通常是清除所有编译生成的文件和配置,使项目回到初始状态。

2. make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_ddr512_emmc_defconfig

  • 这条命令几乎与第一条命令相同,不同之处在于最后的目标是mx6ull_14x14_ddr512_emmc_defconfig
  • mx6ull_14x14_ddr512_emmc_defconfig是一个特定的配置目标,用于生成一个预设的配置文件。这个配置文件包含了为特定硬件(在这个例子中是搭载了512MB DDR内存和eMMC存储的MX6ULL 14x14芯片)编译内核所需的选项和设置。

3. make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j12

  • V=1:设置V变量为1,通常用于使make命令输出更详细的编译信息,帮助开发者更好地理解编译过程中发生了什么。
  • -j12:这个选项告诉Make工具并行执行多个任务(在这个例子中是12个)。这可以显著减少编译时间,特别是在多核心处理器上。
  • 这条命令没有指定目标,所以Make将会尝试编译默认目标,通常是完整的软件或内核。这个命令实际上开始了编译过程,使用了前面命令设置的配置和交叉编译工具链。
    我们可以新建一个 shell 脚本文件,将这些命令写到 shell 脚本文件里面,然后每次只需要执行 shell 脚本即可完成编译工作。新建
    名为 mx6ull_alientek_emmc.sh 的 shell 脚本文件,然后在里面输入如下内容:
#!/bin/bash
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_ddr512_emmc_defconfig
make -s ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j12

U-Boot 顶层 Makefile 详解

编译后的uboot比编译前的uboot多了一些文件夹。具体作用如下图所示:
在这里插入图片描述
在这里插入图片描述
接下来我们主要分析Makefile这个重要文件。在阅读 uboot 源码之前,肯定是要先看一下顶层 Makefile,分析 gcc 版本代码的时候一定是
先从顶层 Makefile 开始的,然后再是子 Makefile,这样通过层层分析 Makefile 即可了解整个工程的组织结构。
在这里插入图片描述

版本号

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

MAKEFLAGS 变量

$(MAKE) -C subdir

在 Makefile 中使用 $(MAKE) -C subdir 这条命令,是为了在当前的 make 过程中启动一个新的 make 子进程,这个子进程会切换到 subdir 子目录下,并在那里查找并执行 Makefile。这种方式常用于大型项目,其中项目被分解为多个子项目或模块,每个子项目或模块都有自己的 Makefile。下面我们分步骤详细解释这个命令的每个部分:

  1. $(MAKE)
  • $(MAKE) 是一个特殊的变量,它引用了当前正在使用的 make 工具的命令。这可能是 make,也可能是 gmake 或其他的 make 工具的名称,具体取决于系统和环境。
  • 使用 $(MAKE) 而不是直接写 make 是一个好习惯。这样做的主要原因是,当 make 文件中包含对其他 Makefile 的递归调用时,$(MAKE) 可以确保使用的是与顶层相同的 make 版本。这在不同系统之间移植项目时尤其重要,因为不同系统可能安装了不同版本的 make 工具。
  1. -C subdir
  • -C 是 make 命令的一个选项,它告诉 make 在执行操作之前切换到指定的目录。在这个例子中,subdir 就是要切换到的目录。
  • 这意味着 make 将在 subdir 目录中查找 Makefile 并执行它,而不是在当前目录。这对于组织有多个组件或子项目的大型项目非常有用,每个组件可以有自己的 Makefile,负责构建该组件。

使用场景

假设你有一个项目,它包含几个子目录,每个子目录都有自己的源代码和 Makefile。项目的目录结构可能如下所示:

project/
│
├── subdir1/
│   ├── Makefile
│   └── ...
│
├── subdir2/
│   ├── Makefile
│   └── ...
│
└── Makefile

在项目的根目录下的 Makefile 中,你可以使用 $(MAKE) -C subdir1 来构建 subdir1 中的代码,使用 $(MAKE) -C subdir2 来构建 subdir2 中的代码。这样,每个子目录的构建过程都是独立的,由其自己的 Makefile 控制,而顶层 Makefile 负责协调这些构建过程。有时候我们需要向子 make 传递变量,这个时候使用“export”来导出要传递给子 make 的变量即可,如果不希望哪个变量传递给子make 的话就使用“unexport”来声明不导出。
在 Makefile 中,exportunexport 指令用于控制环境变量的传递给子 make 进程。这是因为 make 工具允许通过递归调用(即从一个 Makefile 中调用另一个 Makefile)来构建复杂的项目。在这种情况下,父 make 进程可能需要将一些变量传递给它所调用的子 make 进程,使得这些子进程可以在相同的环境下运行或者有条件地修改其行为。
在大型或复杂的项目中,项目通常被分解为多个子项目或模块,每个子项目或模块都有自己的 Makefile。这些子项目可能需要一些公共的配置信息,比如编译器选项、目标架构、特定的路径等。通过在顶层 Makefile 中导出这些变量,然后在子 Makefile 中使用这些已导出的变量,可以确保所有子项目都使用一致的配置,这样做可以提高项目的可维护性和一致性。

  • export: 当你在 Makefile 中使用 export 指令时,后面跟着的变量会被导出到环境中,这样任何由当前 Makefile 启动的子 make 进程都能访问这些变量。这些变量成为了环境变量,对子进程是可见的。

  • unexport: 相反,unexport 指令用于防止一个变量被导出到子 make 进程。如果你不希望某个变量被子进程继承,就可以使用这个指令。
    假设你有一个顶层 Makefile,它调用了子目录中的另一个 Makefile:

export CFLAGS=-Wall

all:
    $(MAKE) -C subdir

在这个例子中,CFLAGS 被导出为环境变量,当执行 $(MAKE) -C subdir 时,子 make 进程会继承 CFLAGS 变量。在 subdir 的 Makefile 中,就可以直接使用这个变量,而不需要重新定义它。
有两个特殊的变量:“SHELL”和“MAKEFLAGS”,这两个变量除非使用“unexport”声明,否则的话在整个make的执行过程中,它们的值始终自动的传递给子make。

  • SHELL:
    作用:SHELL 变量指定了 make 使用的 shell 程序。默认情况下,make 使用 /bin/sh 作为其 shell,但用户可以通过设置 SHELL 变量来改变这一行为。
    为什么自动传递:自动传递 SHELL 变量确保了所有的 make 进程(无论父进程还是子进程)都使用相同的 shell 程序来执行 shell 命令。这对于保证构建脚本的一致性和可预测性至关重要,尤其是当构建脚本依赖于特定 shell 的特性时。
  • MAKEFLAGS:
    作用:MAKEFLAGS 变量包含了传递给 make 的标志(flags),这些标志可以修改 make 的行为。例如,如果你在命令行上运行 make 时使用了 -j(指定并行构建的作业数)或 -k(即使出现错误也继续进行其他的目标)等选项,这些选项会被自动加入到 MAKEFLAGS 变量中。
    为什么自动传递:自动传递 MAKEFLAGS 变量确保了所有的 make 子进程都将以与父进程相同的方式执行,包括错误处理、输出控制、并行作业数等。这是保持整个构建过程一致性的关键,尤其是在复杂的项目中,不同层级的 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

这段代码是从 Makefile 中提取的,通常用于控制编译过程中命令的显示方式。它允许用户通过命令行参数或环境变量来控制构建过程的冗余级别。让我们逐步解析这段代码的含义和作用:

第一部分:判断 V 变量的来源

ifeq ("$(origin V)", "command line")
  KBUILD_VERBOSE = $(V)
endif
  • 这部分代码检查变量 V 是否是从命令行传递给 make 的。$(origin V) 是一个特殊的函数,用于返回变量 V 的定义来源(例如,环境、命令行、Makefile 等)。
  • 如果 V 是在命令行上定义的(例如,通过 make V=1),则将 KBUILD_VERBOSE 设置为 V 的值。这允许用户在调用 make 时动态地控制输出的详细程度。

第二部分:设置 KBUILD_VERBOSE 的默认值

ifndef KBUILD_VERBOSE
  KBUILD_VERBOSE = 0
endif
  • 这部分代码检查 KBUILD_VERBOSE 是否未定义。如果未定义,则将其设置为 0。这意味着默认情况下,构建过程将不显示所有命令(即,非冗余模式)。

第三部分:根据 KBUILD_VERBOSE 的值设置 quietQ

ifeq ($(KBUILD_VERBOSE),1)
  quiet =
  Q =
else
  quiet=quiet_
  Q = @
endif
  • 这部分代码根据 KBUILD_VERBOSE 的值来设置两个变量:quietQ
  • 如果 KBUILD_VERBOSE 等于 1,则 quietQ 都被设置为空字符串。这表示构建过程将显示所有执行的命令,因为在 Makefile 的命令行前没有 @ 符号时,make 会打印该命令。
  • 如果 KBUILD_VERBOSE 不等于 1(通常意味着它是 0),则 quiet 被设置为 quiet_,而 Q 被设置为 @@ 符号用于在执行命令时不显示该命令本身,这样构建过程就更加“安静”。quiet 变量的值(quiet_)可能用于定义一些特定的打印前缀或用于其他目的,具体取决于 Makefile 的其他部分如何使用它。

静默输出

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

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

这段代码用于检测 GNU Make 的版本以及是否在命令行上指定了“silent”模式(通常是通过 -s--silent 选项),并据此设置一个变量 quiet,以控制 Makefile 中命令的输出。这是一种在不同版本的 Make 程序中保持兼容性的技巧,同时允许开发者通过命令行选项控制输出的详细程度。下面是对这段代码的逐行解释:

1: 检测 GNU Make 的版本

ifneq ($(filter 4.%,$(MAKE_VERSION)),)  # make-4
  • 这行代码检查 MAKE_VERSION 变量是否以 4. 开头。MAKE_VERSION 是一个内置变量,包含了当前 GNU Make 的版本号。$(filter 4.%,$(MAKE_VERSION)) 的作用是从 MAKE_VERSION 中筛选出所有以 4. 开头的版本号。
  • 如果结果非空,意味着当前 GNU Make 的版本至少是 4.x,代码块进入“make-4”部分。

2: 检测是否在命令行上指定了“silent”模式(GNU Make 4.x)

ifneq ($(filter %s ,$(firstword x$(MAKEFLAGS))),)
  quiet=silent_
endif
  • 这里,$(MAKEFLAGS) 包含了传递给 make 的所有标志(即命令行选项)。通过在 $(MAKEFLAGS) 前添加一个 x 并使用 $(firstword ...),确保即使 MAKEFLAGS 为空,firstword 函数也能返回一个非空字符串(即 x),这样 filter 函数总是有一个字符串可以检查。
  • $(filter %s ,x$(MAKEFLAGS)) 的目的是检查 MAKEFLAGS 中是否包含一个以 s 结尾的选项(这里的空格是为了避免匹配像 --some-option 这样实际上并不是表示 silent 的选项)。如果找到了,quiet 被设置为 silent_

3: 对于早期版本的 GNU Make(3.8x)

else                    # make-3.8x
ifneq ($(filter s% -s%,$(MAKEFLAGS)),)
  quiet=silent_
endif
endif
  • 如果 GNU Make 的版本低于 4.x,代码进入这个分支。
  • 这里使用 $(filter s% -s%,$(MAKEFLAGS)) 来检查 MAKEFLAGS 是否包含以 s 开头的选项或者是 -s。这是因为在 GNU Make 的早期版本中,MAKEFLAGS 的格式可能与 4.x 有所不同,需要使用不同的方法来检测 silent 模式。
  • 如果条件满足,同样将 quiet 设置为 silent_

设置编译结果输出目录

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

代码检查

uboot 支持代码检查,使用命令“make C=1”使能代码检查,检查那些需要重新编译的文件。“make C=2”用于检查所有的源码文件。

模块编译

在 uboot 中允许单独编译某个模块,使用命令“ make M=dir”即可,旧语法“ make SUBDIRS=dir”也是支持的.

获取主机架构和系统

接下来顶层 Makefile 会获取主机架构和系统,也就是我们电脑的架构和系统:

227 HOSTARCH := $(shell uname -m | \
228 sed -e s/i.86/x86/ \
229 -e s/sun4u/sparc64/ \
230 -e s/arm.*/arm/ \
231 -e s/sa110/arm/ \
232 -e s/ppc64/powerpc/ \
233 -e s/ppc/powerpc/ \
234 -e s/macppc/powerpc/\
235 -e s/sh.*/sh/)
236
237 HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \
238 sed -e 's/\(cygwin\).*/cygwin/')
239
240 export HOSTARCH HOSTOS

这段代码是一部分常见于构建系统和自动化脚本中的Shell脚本,其目的是确定运行脚本的机器的架构 (HOSTARCH) 和操作系统 (HOSTOS)。这些信息通常用于配置和编译跨平台的软件。下面是对每一行的详细解释:

第 227-235 行:确定 HOSTARCH

227 HOSTARCH := $(shell uname -m | \
228 sed -e s/i.86/x86/ \
229 -e s/sun4u/sparc64/ \
230 -e s/arm.*/arm/ \
231 -e s/sa110/arm/ \
232 -e s/ppc64/powerpc/ \
233 -e s/ppc/powerpc/ \
234 -e s/macppc/powerpc/\
235 -e s/sh.*/sh/)
  • HOSTARCH := $(shell uname -m | ...):这行代码使用 uname -m 命令获取当前机器的硬件名称(架构),然后通过管道传递给 sed 命令进行处理。
  • sed 命令用于处理文本数据。它接收 uname -m 的输出,并根据一系列的替换规则修改这些输出:
    • s/i.86/x86/:将任何以 i 开头,后接一个字符,再接 86 的架构名称(如 i386i486 等)替换为 x86
    • s/sun4u/sparc64/:将 sun4u 替换为 sparc64
    • s/arm.*/arm/:将以 arm 开头的任何架构名称简化为 arm
    • s/sa110/arm/:将 sa110 替换为 arm
    • s/ppc64/powerpc/s/ppc/powerpc/:将 ppc64ppc 都替换为 powerpc
    • s/macppc/powerpc/:将 macppc 替换为 powerpc
    • s/sh.*/sh/:将以 sh 开头的任何架构名称简化为 sh

第 237-238 行:确定 HOSTOS

237 HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \
238 sed -e 's/\(cygwin\).*/cygwin/')
  • HOSTOS := $(shell uname -s | ...):这行代码使用 uname -s 命令获取当前操作系统的名称。
  • tr '[:upper:]' '[:lower:]':这个命令将所有大写字母转换为小写字母,确保操作系统名称的一致性,不受大小写影响。
  • sed -e 's/\(cygwin\).*/cygwin/':如果操作系统名称中包含 cygwin,则将整个名称简化为 cygwin。这里使用了正则表达式的捕获组 \( ... \).* 来匹配 cygwin 后的任何字符,并将整个匹配替换为 cygwin

第 240 行:导出变量

240 export HOSTARCH HOSTOS
  • 这行代码使用 export 命令将 HOSTARCHHOSTOS 变量导出,使得这些变量在当前脚本启动的子进程中也可用。这对于构建系统中的子脚本和程序来说是必要的,因为它们可能需要根据不同的架构和操作系统进行不同的处理。

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

编 译 uboot 的 时 候 需 要 设 置 目 标 板 架 构 和 交 叉 编 译 器 ,“ make ARCH=armCROSS_COMPILE=arm-linux-gnueabihf-”就是用于设置 ARCH 和 CROSS_COMPILE,在顶层
Makefile 中代码如下:

244 # set default to nothing for native builds
245 ifeq ($(HOSTARCH),$(ARCH))
246 CROSS_COMPILE ?=
247 endif
248
249 KCONFIG_CONFIG ?= .config
250 export KCONFIG_CONFIG

第 244 行:注释

244 # set default to nothing for native builds
  • 这行是一个注释,说明了接下来代码的目的:为本地构建(即目标架构与宿主架构相同的情况)设置默认值为空。

第 245-247 行:条件判断和设置变量

245 ifeq ($(HOSTARCH),$(ARCH))
246 CROSS_COMPILE ?=
247 endif
  • ifeq ($(HOSTARCH),$(ARCH)):这是一个条件判断语句,检查 HOSTARCH(宿主机器的架构)是否与 ARCH(目标架构)相同。这里 HOSTARCHARCH 是在 Makefile 或其包含的环境中定义的变量。
  • CROSS_COMPILE ?=:如果条件为真(即构建目标与宿主机器架构相同),则设置 CROSS_COMPILE 变量为空。CROSS_COMPILE 通常用于指定交叉编译工具链的前缀。例如,如果目标是 ARM 架构,CROSS_COMPILE 可能被设置为 arm-linux-gnueabihf-。通过设置为空,这一行表明不需要交叉编译工具链,因为我们正在进行本地构建。
  • ?= 是 Makefile 中的条件赋值操作符,仅当 CROSS_COMPILE 未被之前的 Makefile 或命令行定义时才设置其值。
    第 249 行:设置默认配置文件路径
249 KCONFIG_CONFIG ?= .config
  • 这行设置 KCONFIG_CONFIG 变量的默认值为 .config,除非它已经在其他地方被定义。.config 文件通常包含内核或应用的配置选项,这是 Linux 内核和许多其他基于 Kconfig 系统的项目的标准配置文件名。

第 250 行:导出变量

250 export KCONFIG_CONFIG
  • export KCONFIG_CONFIG:这条指令将 KCONFIG_CONFIG 变量导出到环境中,使其在由这个 Makefile 触发的任何子进程中都可用。这对于确保所有子 Makefile 和脚本都使用相同的配置文件非常重要。

调用 scripts/Kbuild.include

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

交叉编译工具变量设置

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

333 AS = $(CROSS_COMPILE)as
334 # Always use GNU ld
335 ifneq ($(shell $(CROSS_COMPILE)ld.bfd -v 2> /dev/null),)
336 LD = $(CROSS_COMPILE)ld.bfd
337 else
338 LD = $(CROSS_COMPILE)ld
339 endif
340 CC = $(CROSS_COMPILE)gcc
341 CPP = $(CC) -E
342 AR = $(CROSS_COMPILE)ar
343 NM = $(CROSS_COMPILE)nm
344 LDR = $(CROSS_COMPILE)ldr
345 STRIP = $(CROSS_COMPILE)strip
346 OBJCOPY = $(CROSS_COMPILE)objcopy
347 OBJDUMP = $(CROSS_COMPILE)objdump

这段代码是 Makefile 中的一部分,主要用于设置编译环境中的各种工具,特别是针对可能的交叉编译场景。每个变量都关联到特定的编译工具,而 CROSS_COMPILE 变量的前缀则用于指定特定架构的工具链。下面是对每一行的详细解释:

第 333 行:汇编器设置

333 AS = $(CROSS_COMPILE)as
  • AS 变量被设置为使用 CROSS_COMPILE 前缀的汇编器 as。这意味着如果进行交叉编译,as 将是为目标架构定制的版本。

第 334-339 行:链接器选择

334 # Always use GNU ld
335 ifneq ($(shell $(CROSS_COMPILE)ld.bfd -v 2> /dev/null),)
336 LD = $(CROSS_COMPILE)ld.bfd
337 else
338 LD = $(CROSS_COMPILE)ld
339 endif
  • 这段代码通过检测 ld.bfd 版本的存在来决定使用哪个链接器。ld.bfd 是 GNU ld 链接器的一种变体,通常提供更多的功能或对特定架构有更好的支持。
  • 如果 $(shell $(CROSS_COMPILE)ld.bfd -v 2> /dev/null) 返回的是非空字符串,说明 ld.bfd 可用,那么 LD 将被设置为 $(CROSS_COMPILE)ld.bfd
  • 如果 ld.bfd 不可用,则回退到使用标准的 ld 链接器。

第 340-347 行:其他编译工具设置

340 CC = $(CROSS_COMPILE)gcc
341 CPP = $(CC) -E
342 AR = $(CROSS_COMPILE)ar
343 NM = $(CROSS_COMPILE)nm
344 LDR = $(CROSS_COMPILE)ldr
345 STRIP = $(CROSS_COMPILE)strip
346 OBJCOPY = $(CROSS_COMPILE)objcopy
347 OBJDUMP = $(CROSS_COMPILE)objdump
  • CC 设置为使用 CROSS_COMPILE 前缀的 GCC 编译器。
  • CPP 是 C 预处理器,这里设置为调用 CC(即 gcc)并加上 -E 选项,仅进行预处理。
  • AR 用于创建静态库。
  • NM 显示符号信息。
  • LDR 可能是在这个特定环境中定义的一个工具,这不是一个标准的 GNU 工具名称,可能是特定项目或工具链的一部分。
  • STRIP 用于去除二进制文件中的符号信息,减小尺寸。
  • OBJCOPY 用于复制和转换目标文件。
  • OBJDUMP 用于显示二进制文件中的信息。

导出其他变量

368 export VERSION PATCHLEVEL SUBLEVEL UBOOTRELEASE UBOOTVERSION
369 export ARCH CPU BOARD VENDOR SOC CPUDIR BOARDDIR
370 export CONFIG_SHELL HOSTCC HOSTCFLAGS HOSTLDFLAGS CROSS_COMPILE AS
LD CC
371 export CPP AR NM LDR STRIP OBJCOPY OBJDUMP
372 export MAKE AWK PERL PYTHON
373 export HOSTCXX HOSTCXXFLAGS DTC CHECK CHECKFLAGS
374
375 export KBUILD_CPPFLAGS NOSTDINC_FLAGS UBOOTINCLUDE OBJCOPYFLAGS
LDFLAGS
376 export KBUILD_CFLAGS KBUILD_AFLAGS

make xxx_defconfig 过程

414 # To make sure we do not include .config for any of the *config
targets
415 # catch them early, and hand them over to scripts/kconfig/Makefile
416 # It is allowed to specify more targets when calling make,
including
417 # mixing *config targets and build targets.
418 # For example 'make oldconfig all'.
419 # Detect when mixed targets is specified, and make a second
invocation
420 # of make so .config is not included in this case either (for
*config).
421
422 version_h := include/generated/version_autogenerated.h
423 timestamp_h := include/generated/timestamp_autogenerated.h
424
425 no-dot-config-targets := clean clobber mrproper distclean \
426 help %docs check% coccicheck \
427 ubootversion backup
428
429 config-targets := 0
430 mixed-targets := 0
431 dot-config := 1
432
433 ifneq ($(filter $(no-dot-config-targets), $(MAKECMDGOALS)),)
434 ifeq ($(filter-out $(no-dot-config-targets), $(MAKECMDGOALS)),)
435 dot-config := 0
436 endif
437 endif
438
439 ifeq ($(KBUILD_EXTMOD),)
440 ifneq ($(filter config %config,$(MAKECMDGOALS)),)
441 config-targets := 1
442 ifneq ($(words $(MAKECMDGOALS)),1)
443 mixed-targets := 1
444 endif
445 endif
446 endif
447
448 ifeq ($(mixed-targets),1)
449 # ================================================================
450 # We're called with mixed targets (*config and build targets).
451 # Handle them one by one.
452
453 PHONY += $(MAKECMDGOALS) __build_one_by_one
454
455 $(filter-out __build_one_by_one, $(MAKECMDGOALS)):
__build_one_by_one
456 @:
457
458 __build_one_by_one:
459 $(Q)set -e; \
460 for i in $(MAKECMDGOALS); do \
461 $(MAKE) -f $(srctree)/Makefile $$i; \
462 done
463
464 else
465 ifeq ($(config-targets),1)
466 # ================================================================
467 # *config targets only - make sure prerequisites are updated, and
descend
468 # in scripts/kconfig to make the *config target
469
470 KBUILD_DEFCONFIG := sandbox_defconfig
471 export KBUILD_DEFCONFIG KBUILD_KCONFIG
472
473 config: scripts_basic outputmakefile FORCE
474 $(Q)$(MAKE) $(build)=scripts/kconfig $@
475
476 %config: scripts_basic outputmakefile FORCE
477 $(Q)$(MAKE) $(build)=scripts/kconfig $@
478
479 else
480 #==================================================================
481 # Build targets only - this includes vmlinux, arch specific
targets, clean
482 # targets and others. In general all targets except *config
targets.
483
484 ifeq ($(dot-config),1)
485 # Read in config
486 -include include/config/auto.conf

第 422 行定义了变量 version_h,这变量保存版本号文件,此文件是自动生成的。文件include/generated/version_autogenerated.h 内容如图 所示:
在这里插入图片描述
第 423 行定义了变量 timestamp_h,此变量保存时间戳文件,此文件也是自动生成的。文件
include/generated/timestamp_autogenerated.h 内容如图所示:
在这里插入图片描述
第 425 行定义了变量 no-dot-config-targets。
第 429 行定义了变量 config-targets,初始值为 0。
第 430 行定义了变量 mixed-targets,初始值为 0。
第 431 行定义了变量 dot-config,初始值为 1。
第 433 行将 MAKECMDGOALS 中不符合 no-dot-config-targets 的部分过滤掉,剩下的如果不为空的话条件就成立。 MAKECMDGOALS 是 make 的一个环境变量,这个变量会保存你所指定的终极目标列表,比如执行“make mx6ull_alientek_emmc_defconfig”,那么 MAKECMDGOALS就为 mx6ull_alientek_emmc_defconfig。很明显过滤后为空,所以条件不成立,变量 dot-config 依旧为 1。
第439行判断KBUILD_EXTMOD是否为空,如果KBUILD_EXTMOD 为空的话条件成立,经过前面的分析,我们知道 KBUILD_EXTMOD 为空,所以条件成立。
第 440 行将 MAKECMDGOALS 中不符合“config”和“%config”的部分过滤掉,如果剩下的部分不为空条件就成立,很明显此处条件成立,变量 config-targets=1。
第 442 行统计 MAKECMDGOALS 中的单词个数,如果不为 1 的话条件成立。此处调用
Makefile 中的 words 函数来统计单词个数, words 函数格式如下:
$(words )
很明显, MAKECMDGOALS 的单词个数是 1 个,所以条件不成立, mixed-targets 继续为
0。综上所述,这些变量值如下:

config-targets = 1
mixed-targets = 0
dot-config = 1

第 448 行如果变量 mixed-targets 为 1 的话条件成立,很明显,条件不成立。
第 465 行如果变量 config-targets 为 1 的话条件成立,很明显,条件成立,执行这个分支。
第 473 行,没有目标与之匹配,所以不执行。
第 476 行,有目标与之匹配,当输入“make xxx_defconfig”的时候就会匹配到%config 目
标,目标“%config”依赖于 scripts_basic、 outputmakefile 和 FORCE。 FORCE 在顶层 Makefile
的 1610 行有如下定义:

示例代码 31.3.13.2 顶层 Makefile 代码段
1610 PHONY += FORCE
1611 FORCE:

由于 FORCE 没有定义任何依赖,并且它没有与之关联的文件(即没有物理文件名为 FORCE 的文件),Make 无法找到一个实际的文件来检查时间戳。因此,Make 每次运行时都会认为 FORCE 需要被更新,因为它总是缺少一个有效的时间戳来进行比较。
依赖 scripts_basic 和 outputmakefile 在顶层 Makefile 中的内容如下:

示例代码 31.3.13.3 顶层 Makefile 代码段
394 # Basic helpers built in scripts/
395 PHONY += scripts_basic
396 scripts_basic:
397 $(Q)$(MAKE) $(build)=scripts/basic
398 $(Q)rm -f .tmp_quiet_recordmcount
399
400 # To avoid any implicit rule to kick in, define an empty command.
401 scripts/basic/%: scripts_basic ;
402
403 PHONY += outputmakefile
404 # outputmakefile generates a Makefile in the output directory, if
405 # using a separate output directory. This allows convenient use of
406 # make in the output directory.
407 outputmakefile:
408 ifneq ($(KBUILD_SRC),)
409 $(Q)ln -fsn $(srctree) source
410 $(Q)$(CONFIG_SHELL) $(srctree)/scripts/mkmakefile \
411 $(srctree) $(objtree) $(VERSION) $(PATCHLEVEL)
412 endif

第 408 行,判断 KBUILD_SRC 是否为空,只有变量 KBUILD_SRC 不为空的时候,outputmakefile 才有意义,经过我们前面的分析 KBUILD_SRC 为空,所以 outputmakefile 无效。只有 scripts_basic 是有效的。
第 396~398 行是 scripts_basic 的规则,其对应的命令用到了变量 Q、 MAKE 和 build,其中:

Q=@或为空
MAKE=make

变量 build 是在 scripts/Kbuild.include 文件中有定义,定义如下:
示例代码 31.3.13.3 Kbuild.include 代码段

177 ###
178 # Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj=
179 # Usage:
180 # $(Q)$(MAKE) $(build)=dir
181 build := -f $(srctree)/scripts/Makefile.build obj

从示例代码 31.3.13.3 可以看出 build=-f $(srctree)/scripts/Makefile.build obj,经过前面的分析可知,变量 srctree 为”.”,因此:

build=-f ./scripts/Makefile.build obj

scripts_basic 展开以后如下:

scripts_basic:
@make -f ./scripts/Makefile.build obj=scripts/basic //也可以没有@,视配置而定
@rm -f . tmp_quiet_recordmcount //也可以没有@

scripts_basic 会调用文件./scripts/Makefile.build,这个我们后面在分析。
接着回到示例代码 31.3.13.1 中的%config 处,内容如下:

%config: scripts_basic outputmakefile FORCE
$(Q)$(MAKE) $(build)=scripts/kconfig $@

将命令展开就是:

@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig

同样也跟文件./scripts/Makefile.build 有关,我们后面再分析此文件。使用如下命令配置 uboot,
并观察其配置过程:

make mx6ull_14x14_ddr512_emmc_defconfig V=1

①、 scripts_basic 目标对应的命令

@make -f ./scripts/Makefile.build obj=scripts/basic

②、 %config 目标对应的命令

@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig

Makefile.build 脚本分析

从上一小节可知,“ make xxx_defconfig“配置 uboot 的时候如下两行命令会执行脚本
scripts/Makefile.build:
@make -f ./scripts/Makefile.build obj=scripts/basic
@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig
1、 scripts_basic 目标对应的命令
scripts_basic 目标对应的命令为: @make -f ./scripts/Makefile.build obj=scripts/basic。打开文
件 scripts/Makefile.build,有如下代码:

示例代码 31.3.14.1 Makefile.build 代码段
8 # Modified for U-Boot
9 prefix := tpl
10 src := $(patsubst $(prefix)/%,%,$(obj))
11 ifeq ($(obj),$(src))
12 prefix := spl
13 src := $(patsubst $(prefix)/%,%,$(obj))
14 ifeq ($(obj),$(src))
15 prefix := .
16 endif
17 endif

第 9 行定义了变量 prefix 值为 tpl。
第 10 行定义了变量 src,这里用到了函数 patsubst,此行代码展开后为:

$(patsubst tpl/%,%, scripts/basic)

patsubst 是替换函数,格式如下:

$(patsubst <pattern>,<replacement>,<text>)

此函数用于在 text 中查找符合 pattern 的部分,如果匹配的话就用 replacement 替换掉。pattenr 是可以包含通配符“%”,如果 replacement 中也包含通配符“%”,那么 replacement 中的这个“%”将是 pattern 中的那个“%”所代表的字符串。函数的返回值为替换后的字符串。因此,第 10 行就是在“scripts/basic”中查找符合“tpl/%”的部分,然后将“tpl/”取消掉,但是“scripts/basic”没有“tpl/”,所以 src= scripts/basic。
第 11 行判断变量 obj 和 src 是否相等,相等的话条件成立,很明显,此处条件成立。
第 12 行和第 9 行一样,只是这里处理的是“spl”,“scripts/basic”里面也没有“spl/”,所以
src 继续为 scripts/basic。
第 15 行因为变量 obj 和 src 相等,所以 prefix=.。
继续分析 scripts/Makefile.build:

示例代码 31.3.14.2 Makefile.build 代码段
56 # The filename Kbuild has precedence over Makefile
57 kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
58 kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuilddir)/Kbuild,$(kbuild-dir)/Makefile)
59 include $(kbuild-file)

继续分析 scripts/Makefile.build,如下代码:

示例代码 31.3.14.3 Makefile.build 代码段
116 __build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target)
$(extra-y)) \
117 $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
118 $(subdir-ym) $(always)
119 @:

__build 是默认目标,因为命令“@make -f ./scripts/Makefile.build obj=scripts/basic”没有指定目标,所以会使用到默认目标: __build。在顶层 Makefile 中, KBUILD_BUILTIN 为 1,KBUILD_MODULES 为 0,因此展开后目标__build 为:

__build:$(builtin-target) $(lib-target) $(extra-y)) $(subdir-ym) $(always)
@:

可以看出目标__build 有 5 个依赖: builtin-target、 lib-target、 extra-y、 subdir-ym 和 always。这 5 个依赖的具体内容我们就不通过源码来分析了,直接在 scripts/Makefile.build 中输入图所示内容,将这 5 个变量的值打印出来:
执行如下命令:
make mx6ull_14x14_ddr512_emmc_defconfig V=1
从上图可以看出,只有 always 有效,因此__build 最终为:

__build: scripts/basic/fixdep
@:

__build 依赖于 scripts/basic/fixdep,所以要先编译 scripts/basic/fixdep.c,生成 fixdep,前面
已经读取了 scripts/basic/Makefile 文件。
2、 %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

可以看出, Makefile.build 会读取 scripts/kconfig/Makefile 中的内容,此文件有如下所示内容:

示例代码 31.3.14.4 scripts/kconfig/Makefile 代码段
113 %_defconfig: $(obj)/conf
114 $(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@
$(Kconfig)
115
116 # Added for U-Boot (backward compatibility)
117 %_config: %_defconfig
118 @:

目标%_defconfig 刚好和我们输入的 xxx_defconfig 匹配,所以会执行这条规则。依赖为$(obj)/conf,展开后就是 scripts/kconfig/conf。接下来就是检查并生成依赖 scripts/kconfig/conf。conf 是主机软件,到这里我们就打住,不要纠结 conf 是怎么编译出来的,否则就越陷越深,太绕了,像 conf 这种主机所使用的工具类软件我们一般不关心它是如何编译产生的。如果一定要看是 conf 是怎么生成的,可以输入如下命令重新配置 uboot,在重新配置 uboot 的过程中就会输出 conf 编译信息。

make distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_ddr512_emmc_
defconfig V=1

得到 scripts/kconfig/conf 以后就要执行目标%_defconfig 的命令:

$(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)

相关的变量值如下:

silent=-s 或为空
SRCARCH=..
Kconfig=Kconfig

将其展开就是:
@ scripts/kconfig/conf --defconfig=arch/../configs/xxx_defconfig Kconfig
上述命令用到了 xxx_defconfig 文件,比如 mx6ull_alientek_emmc_defconfig。这里会将mx6ull_alientek_emmc_defconfig 中的配置输出到.config 文件中,最终生成 uboot 根目录下
的.config 文件。
这个就是命令 make xxx_defconfig 执行流程,
在这里插入图片描述
这部分看的迷迷糊糊 有点难以理解。等到之后还要重新再看。

posted @ 2024-05-25 23:34  Bathwind_W  阅读(152)  评论(0编辑  收藏  举报