6.2 U-boot 顶层 Makefile

一、U-boot工程目录分析

  如果要分析uboot源码,首先要将uboot源码进行编译,编译需要在Ubuntu进行,把uboot文件放在一个目录下。

编译完成后的文件是这样:


我们需要看的文件夹如下。

1. arch 文件夹

  从上图可以看出有很多架构,比如 armm68kx86 等,我们现在用的是 ARM 芯片,所以只需要关心 arm 文件夹即可。

  mach 开头的文件夹是跟具体的设备有关的,比如“mach-exynos”就是跟三星的 exyons 系列 CPU 有关的文件。我们使用的是 STM32MP1,所以要关注“mach-stm32mp”这个文件夹。另外“cpu”这个文件夹也是和 cpu架构有关的。

  可以看出有多种 ARM 架构相关的文件夹, STM32MP1 使用的 Cortex-A7 内核,Cortex-A7 属于 armv7,所以我们要关心“armv7”这个文件夹。 cpu 文件夹里面有个名为“uboot.lds”的链接脚本文件,这个就是 ARM 芯片所使用的 u-boot 链接脚本文件! armv7 这个文件夹里面的文件都是跟 ARMV7 架构有关的,是我们分析 uboot 启动源码的时候需要重点关注 

 

2. board 文件夹

  board 文件夹就是和具体的板子有关的。打开board/st/stm32mp1,这个文件夹就是针对STM32MP1系列芯片对应的板子。

 

3. configs 文件夹

 此文件夹为 uboot 配置文件夹, uboot 是可配置的,但是你要是自己从头开始一个一个项目的配置,那就太麻烦了,因此一般半导体或者开发板厂商都会制作好一个配置文件。我们可以在这个做好的配置文件基础上来添加自己想要的功能,这些半导体厂商或者开发板厂商制作好的配置文件统一命名为“xxx_defconfig”, xxx 表示开发板名字,这些 defconfig 文件都存放在configs 文件夹。

图中的框就是STM32MP157开发板对应的配置文件。

 

4.  .u-boot.xxx_cmd 文件

  .u-boot.xxx_cmd 是一系列的文件,这些文件都是编译生成的,都是一些命令文件,比如文件.u-boot.bin.cmd,看名字应该是和 u-boot.bin 有关的,此文件的内容如下: 

cmd_u-boot.bin := cp u-boot-dtb.bin u-boot.bin

  .u-boot.bin.cmd 里面定义了一个变量: cmd_u-boot.bin,此变量的值为“cp u-boot-dtb.bin uboot.bin”,也就是拷贝一份 u-boot-dtb.bin 文件,并且重命名为 u-boot.bin,这个就是 u-boot.bin的来源,来自于文件 u-boot-dtb.bin 

  那么u-boot-dtb.bin是怎么来的呢?文件.u-boot-dtb.bin.cmd就是用于生成u-boot.dtb.bin的,此文件内容如下: 

cmd_u-boot-dtb.bin := cat u-boot-nodtb.bin dts/dt.dtb > u-boot-dtb.bin

  cmd_u-boot-dtb.bin 用于将设备树.dtb 文件和 uboot bin 文件结合起来, STM32MP157 uboot 用到了设备树(Device Tree),因此最终生成的 uboot bin 文件里面要有.dtb 内容。 u-boot-nodtb.bin 就是原始的、不含 dtb uboot bin 文件。 

   u-boot-nodtb.bin 是用.u-boot-nodtb.bin.cmd 文件生成的,内容如下:

cmd_u-boot-nodtb.bin := arm-none-linux-gnueabihf-objcopy --gap-fill=0xff  -j .text -j .secure_text -j .secure_data -j .rodata -j .hash -j .data -j .got -j .got.plt -j .u_boot_list -j .rel.dyn -j .binman_sym_table -j .text_rest -j .dtb.init.rodata -j .efi_runtime -j .efi_runtime_rel -O binary   u-boot u-boot-nodtb.bin

  这里用到了 arm-none-linux-gnueabihf-objcopy,使用 objcopy ELF 格式的 u-boot 文件转换为二进制的 u-boot-nodtb.bin 文件。 

  文件 u-boot ELF 格式的文件,文件.u-boot.cmd 用于生成 u-boot,文件内容如下: 

cmd_u-boot := arm-none-linux-gnueabihf-ld.bfd   -pie  --gc-sections -Bstatic  --no-dynamic-linker -Ttext 0xC0100000 -o u-boot -T u-boot.lds arch/arm/cpu/armv7/start.o --start-group  arch/arm/cpu/built-in.o  arch/arm/cpu/armv7/built-in.o  arch/arm/lib/built-in.o  arch/arm/mach-stm32mp/built-in.o  board/st/common/built-in.o  board/st/stm32mp1/built-in.o  cmd/built-in.o  common/built-in.o  disk/built-in.o  drivers/built-in.o  drivers/dma/built-in.o  drivers/gpio/built-in.o  drivers/i2c/built-in.o  drivers/net/built-in.o  drivers/net/phy/built-in.o  drivers/power/built-in.o  drivers/power/battery/built-in.o  drivers/power/domain/built-in.o  drivers/power/fuel_gauge/built-in.o  drivers/power/mfd/built-in.o  drivers/power/pmic/built-in.o  drivers/power/regulator/built-in.o  drivers/serial/built-in.o  drivers/spi/built-in.o  drivers/usb/cdns3/built-in.o  drivers/usb/common/built-in.o  drivers/usb/dwc3/built-in.o  drivers/usb/emul/built-in.o  drivers/usb/eth/built-in.o  drivers/usb/gadget/built-in.o  drivers/usb/gadget/udc/built-in.o  drivers/usb/host/built-in.o  drivers/usb/musb-new/built-in.o  drivers/usb/musb/built-in.o  drivers/usb/phy/built-in.o  drivers/usb/ulpi/built-in.o  env/built-in.o  fs/built-in.o  lib/built-in.o  net/built-in.o --end-group arch/arm/lib/eabi_compat.o  arch/arm/lib/lib.a -Map u-boot.map;  true

  u-boot.cmd 使用到了 arm-none-linux-gnueabihf-ld.bfd,也就是链接工具,使用 ld.bfd 将各个built-in.o 文件链接在一起就形成了 u-boot 文件。 uboot 在编译的时候会将同一个目录中的所有.c文件都编译在一起,并命名为 built-in.o,相当于将众多的.c 文件对应的.o 文件集合在一起,这个就是 u-boot 文件的来源。 

  如果我们要向 STM32MP157 内部烧写 uboot,此时烧写的是 u-boot.stm32 文件,而不是 uboot.bin 文件。 u-boot.stm32 是由文件.u-boot.stm32.cmd 来完成的,此文件内容如下: 

cmd_u-boot.stm32 := ./tools/mkimage -T stm32image -a 0xC0100000 -e 0xC0100000 -d u-boot.bin u-boot.stm32 >u-boot.stm32.log  && cat u-boot.stm32.log

  可以看出,这里用到了工具 tools/mkimage,和 stm32image,通过这两个工具将 u-boot.bin转换为 u-boot.stm32 

  文件.u-boot.lds.cmd 就是用于生成 u-boot.lds 链接脚本,uboot 根目录下的 u-boot.lds 链接脚本就是来源于 arch/arm/cpu/u-boot.lds文件。 

 

5.  Makefile 文件

  这个是顶层 Makefile 文件, Makefile 是支持嵌套的,也就是顶层 Makefile 可以调用子目录中的 Makefile 文件。 Makefile 嵌套在大项目中很常见,一般大项目里面所有的源代码都不会放到同一个目录中,各个功能模块的源代码都是分开的,各自存放在各自的目录中。每个功能模块目录下都有一个 Makefile,这个 Makefile 只处理本模块的编译链接工作,这样所有的编译链接工作就不用全部放到一个 Makefile 中,可以使得 Makefile 变得简洁明。

  uboot 源码根目录下的 Makefile 是顶层 Makefile,他会调用其它的模块的 Makefile 文件,比如 drivers/adc/Makefile 

 

6.   u-boot.xxx 文件

  u-boot.xxx 同样也是一系列文件,包括 u-bootu-boot-dtb.binu-boot-nodtb.binu-boot.binu-boot.cfgu-boot.dtbu-boot.ldsu-boot.mapu-boot.srecu-boot.stm32 u-boot.sym,这些文件的含义如下: 

u-boot:编译出来的 ELF 格式的 uboot 镜像文件。

u-boot-dtb.bin:编译出来的含有设备树.dtb 的 uboot 镜像文件。

u-boot-nodtb.bin:编译出来的不含有设备树.dtb 的 uboot 镜像文件,和 u-boot.bin 一样。

u-boot.bin:编译出来的二进制格式的 uboot 可执行镜像文件。

u-boot.cfg: uboot 的另外一种配置文件。

u-boot.dtb: uboot 设备树编译后的.dtb 文件。

u-boot.lds:链接脚本。

u-boot.map: uboot 映射文件,通过查看此文件可以知道某个函数被链接到了哪个地址上。

u-boot.srec: S-Record 格式的镜像文件。

u-boot.stm32: 最终要写到 STM32MP157 的uboot 文件。

u-boot.sym: uboot 符号文件。

 

7.   .config 文件

  uboot 配置文件,使用命令“make xxx_defconfig”配置 uboot 以后就会自动生成, .config 内容(部分)如下: 

#
# Automatically generated file; DO NOT EDIT.
# U-Boot 2020.01-stm32mp-r1 Configuration
#
CONFIG_CREATE_ARCH_SYMLINK=y
# CONFIG_ARC is not set
CONFIG_ARM=y
# CONFIG_M68K is not set
# CONFIG_MICROBLAZE is not set
# CONFIG_MIPS is not set
# CONFIG_NDS32 is not set
# CONFIG_NIOS2 is not set
# CONFIG_PPC is not set
# CONFIG_RISCV is not set
# CONFIG_SANDBOX is not set
# CONFIG_SH is not set
# CONFIG_X86 is not set
# CONFIG_XTENSA is not set
CONFIG_SYS_ARCH="arm"
CONFIG_SYS_CPU="armv7"
CONFIG_SYS_SOC="stm32mp"
CONFIG_SYS_VENDOR="st"
CONFIG_SYS_BOARD="stm32mp1"
CONFIG_SYS_CONFIG_NAME="stm32mp1"
# CONFIG_SYS_ICACHE_OFF is not set
# CONFIG_SYS_DCACHE_OFF is not set

#
# ARM architecture
#
CONFIG_HAS_VBAR=y
CONFIG_HAS_THUMB2=y
CONFIG_ARM_ASM_UNIFIED=y
CONFIG_SYS_ARM_CACHE_CP15=y
CONFIG_SYS_ARM_MMU=y
# CONFIG_SYS_ARM_MPU is not set
CONFIG_CPU_V7A=y
CONFIG_SYS_ARM_ARCH=7
CONFIG_SYS_CACHE_SHIFT_6=y
CONFIG_SYS_CACHELINE_SIZE=64

  .config 文件中都是以“CONFIG_”开始的配置项,这些配置项就是 Makefile 中的变量,因此后面都跟有相应的值, uboot 的顶层 Makefile 或子 Makefile 会调用这些变量值。在.config 中会有大量的变量值为‘y’,这些为‘y’的变量一般用于控制某项功能是否使能,为‘y’的话就表示功能使能,比如: 

CONFIG_CMD_BOOTM=y

  如果使能了 bootd 这个命令的话, CONFIG_CMD_BOOTM 就为‘y’。在 cmd/Makefile 中有如下代码: 

# 这个需要在Ubuntu中用vim打开或者用Vscode打开
ifndef CONFIG_SPL_BUILD
# core command
obj-y += boot.o
obj-$(CONFIG_CMD_BOOTM) += bootm.o
obj-y += help.o
obj-y += version.o

......
CFLAGS_ethsw.o := -Wno-enum-conversion        // 这个是在226行
obj-$(CONFIG_CMD_BOOTM) += bootm.o
CONFIG_CMD_BOOTM=y,将其展开就是:
obj-y += bootm.o

  也就是给 obj-y 追加了一个“bootm.o”, obj-y 包含着所有要编译的文件对应的.o 文件,这里表示需要编译文件 cmd/bootm.c。相当于通过“CONFIG_CMD_BOOTD=y”来使能 bootm 这个命令,进而编译 cmd/bootm.c 这个文件,这个文件实现了命令 bootm。在 uboot Linux 内核中都是采用这种方法来选择使能某个功能,编译对应的源码文件。 

 

二、U-boot 顶层 Makefile 分析 

  在阅读 uboot 源码之前,肯定是要先看一下顶层 Makefile,分析 gcc 版本代码的时候一定是先从顶层 Makefile 开始的,然后再是子 Makefile,这样通过层层分析 Makefile 即可了解整个工程的组织结构。 

1. 版本号

  用VScode打开顶层Makefile:

 

 

2.  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)”表示当前目录。

 

3.  命令输出

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

  在终端中输出短命令虽然看起来很清爽,但是不利于分析 uboot 的编译过程。可以通过设置变量“V=1“来实现完整的命令输出,这个在调试 uboot 的时候很有用:

顶层Makefile控制命令输出代码:

ifeq ("$(origin V)", "command line")		# 判断$(origin V)和command line是否相等,这里用到了Makefile中的origin函数
  # 这里我的理解是origin V是代表V的来源,command line代表命令行定义,如果在命令行输入了V=1那么就说明V的来源是command line,与之相等,那么变量KBUILD_VERBOSE就会等于1
  KBUILD_VERBOSE = $(V)
endif
ifndef KBUILD_VERBOSE
  KBUILD_VERBOSE = 0
endif

ifeq ($(KBUILD_VERBOSE),1)           # 判断 KBUILD_VERBOSE 是否为1,如果是变量quite和Q为空,反之,变为quite_和@
  quiet =
  Q =
else
  quiet=quiet_
  Q = @
endif
这里用到了Makefile中的origin函数,语法为:
$(origin <variable>)

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

  如果 V=1的话:

quiet = 
Q = 
# V=0 或者命令行不定义 V 的话:
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_”的话,整个命令都不会输出。 

 

4.  静默输出(make -s

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

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

ifneq ($(filter 4.%,$(MAKE_VERSION)),)	# make-4		# 判断当前编译器版本是是否为4.x,去找MAKE_VERSION里面的符合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            # 使用 export 导出变量 quiet、 Q 和 KBUILD_VERBOSE。

  这里用到了 Makefile 中的 filter函数,这是个过滤函数,函数格式如下: 

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

  filter 函数表示以 pattern 模式过滤 text 字符串中的单词,仅保留符合模式 pattern 的单词,可以有多个模式。函数返回值就是符合 pattern 的字符串。因此$(filter 4.%,$(MAKE_VERSION))的含义就是在字符串“ MAKE_VERSION”中找出符合“ 4.%”的字符(%为通配符)MAKE_VERSION make工具的版本号。因此$(filter 4.%,$(MAKE_VERSION))不为空,条件成立。

  如果$(filter %s ,$(firstword x$(MAKEFLAGS)))不为空的话条件成立,变量 quiet 等于“silent_”。这里也用到了函数 filter,在$(firstword x$(MAKEFLAGS)))中过滤出符合“%s”的单词。到了函数 firstword,函数 firstword 是获取首单词,函数格式如下: 

$(firstword <text>)

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

  框中的两行代码用于输出$(firstword x$(MAKEFLAGS))的结果,我们用以下命令去测试静脉输出: 

make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- -s

  可以看出第一个单词是“xrRs”,将$(filter %s ,$(firstword x$(MAKEFLAGS)))展开就是$(filter %s, xrRs),而$(filter %s, xrRs)的返回值肯定不为空,条件成立, quiet=silent_

 

5.  设置编译结果输出目录 

  uboot 可以将编译出来的目标文件输出到单独的目录中,在 make 的时候使用“O”来指定输出目录,比如“make O=out”就是设置目标文件输出到 out 目录中。这么做是为了将源文件和编译产生的文件分开,当然也可以不指定 O 参数,不指定的话源文件和编译产生的文件都在同一个目录内,一般我们不指定 O 参数。顶层 Makefile 中相关的代码如下: 

# 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")        # 判断'O'是否来自于命令行,如果来自命令行的话条件成立, KBUILD_OUTPUT就为$(O),因此变量 KBUILD_OUTPUT 就是输出目录。
  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),)        # 判断 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) \  # 调用 mkdir 命令,创建 KBUILD_OUTPUT 目录,并且将创建成功以后的绝对路径赋值给 KBUILD_OUTPUT。至此,通过 O 指定的输出目录就存在了。
								&& /bin/pwd)
......
endif # ifneq ($(KBUILD_OUTPUT),)
endif # ifeq ($(KBUILD_SRC),)

 

6.  代码检查

  uboot 支持代码检查,使用命令“make C=1”使能代码检查,检查那些需要重新编译的文件。“make C=2”用于检查所有的源码文件,顶层 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")        # 判断 C 是否来源于命令行,如果 C 来源于命令行,那就将 C 赋值给变量KBUILD_CHECKSRC,如果命令行没有 C 的话 KBUILD_CHECKSRC 就为 0。
  KBUILD_CHECKSRC = $(C)
endif
ifndef KBUILD_CHECKSRC
  KBUILD_CHECKSRC = 0
endif

 

7.  模块编译 

  在 uboot 中允许单独编译某个模块,使用命令“ make M=dir”即可。顶层 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                    # 判断是否定义了SUBDIRS,如果定义了,那就KBUILD_EXTMOD=SUBDIRS
  KBUILD_EXTMOD ?= $(SUBDIRS)
endif

ifeq ("$(origin M)", "command line")    # 判断是否在命令行定义了 M,如果定义了的话 KBUILD_EXTMOD=$(M)。
  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),)    # 判断 KBUILD_EXTMOD 是否为空,如果为空的话目标_all 依赖 all,因此要先编译出 all。否则的话默认目标_all 依赖 modules,要先编译出 modules,也就是编译模块。一般情况下不会在 uboot 中编译模块,所以此处会编译 all 这个目标
_all: all
else
_all: modules
endif

ifeq ($(KBUILD_SRC),)        # 判断 KBUILD_SRC 是否为空,如果为空的话就设置变量 srctree 为当前目录,即srctree 为“.”,一般不设置 KBUILD_SRC。
        # building in the source tree
        srctree := .
else
        ifeq ($(KBUILD_SRC)/,$(dir $(CURDIR)))    # 检查 KBUILD_SRC 加上斜杠 / 是否与当前目录 $(CURDIR) 的父目录相同
                # building in a subdirectory of the source tree
                srctree := ..            # 表示将 srctree 变量设置为当前目录的父目录 ..
        else
                srctree := $(KBUILD_SRC)    # 表示将 srctree 变量设置为 KBUILD_SRC 变量的值
        endif
endif
objtree		:= .                # 设置变量 objtree 为当前目录
src		:= $(srctree)           # 分别设置变量 src 和 obj,都为当前目录
obj		:= $(objtree)

VPATH		:= $(srctree)$(if $(KBUILD_EXTMOD),:$(KBUILD_EXTMOD))    # 将变量 src 设置为变量 srctree 的值

export srctree objtree VPATH        # 导出变量 scrtree、 objtree 和 VPATH。

 

8.  获取主机架构和系统

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

HOSTARCH := $(shell uname -m | \            # 定义了一个变量 HOSTARCH,用于保存主机架构
	sed -e s/i.86/x86/ \                # sed -e 是替换命令,“sed -e s/i.86/x86/”表示将管道输入的字符串中的“i.86”替换为“x86”,其他的“sed -e s”命令同理
	    -e s/sun4u/sparc64/ \           # uname -m 命令用于获取主机的架构类型,然后通过一系列的 sed 替换命令对结果进行处理,将不同的架构类型转换为统一的名称。最终将处理后的值赋给 HOSTARCH 变量
	    -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/')     # shell 中的'|'表示管道,意思是将左边的输出作为右边的输入
                                             # uname -s 命令用于获取主机的操作系统类型,然后通过 tr 命令将操作系统类型的字母转换为小写,再通过一系列的 sed 替换命令对结果进行处理,将不同的操作系统类型转换为统一的名称。最终将处理后的值赋给 HOSTOS 变量。
export	HOSTARCH HOSTOS                    # export 用于将变量导出,使其在子进程的环境中可见

  这里调用 shell 命令“uname -m”获取架构名称:

 

  使用 shell 命令“uname-s”来获取主机 OS:

 

 

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

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

# set default to nothing for native builds
ifeq ($(HOSTARCH),$(ARCH))            # 判断 HOSTARCH 和 ARCH 这两个变量是否相等,主机架构(变量 HOSTARCH)是x86_64,而我们编译的是 ARM 版本 uboot,肯定不相等
CROSS_COMPILE ?=
endif

KCONFIG_CONFIG	?= .config
export KCONFIG_CONFIG

  每次编译 uboot 的时候都要在 make 命令后面设置 ARCH CROSS_COMPILE,使用起来很麻烦,可以直接修改顶层 Makefile,在里面加入ARCH CROSS_COMPILE 的定义。

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

 

10.  导出其他变量

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

  7 个变量在顶层 Makefile 是找不到的,说明这 7 个变量是在其他文件里面定义的,先来看一下这 7 个变量都是什么内容,在顶层 Makefile 中输入以下内容:

修改完成后在下面的路径执行如下命令:

make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- mytest

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

# SPDX-License-Identifier: GPL-2.0+
#
# (C) Copyright 2000-2013
# Wolfgang Denk, DENX Software Engineering, wd@denx.de.
#########################################################################

# This file is included from ./Makefile and spl/Makefile.
# Clean the state to avoid the same flags added twice.
#
# (Tegra needs different flags for SPL.
#  That's the reason why this file must be included from spl/Makefile too.
#  If we did not have Tegra SoCs, build system would be much simpler...)
PLATFORM_RELFLAGS :=
PLATFORM_CPPFLAGS :=
PLATFORM_LDFLAGS :=
LDFLAGS :=
LDFLAGS_FINAL :=
LDFLAGS_STANDALONE :=
OBJCOPYFLAGS :=
# clear VENDOR for tcsh
VENDOR :=
#########################################################################

ARCH := $(CONFIG_SYS_ARCH:"%"=%)	# 提取CONFIG_SYS_ARCH 里面双引号“”之间的内容。比如 CONFIG_SYS_ARCH=“arm”, ARCH=arm
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

# Some architecture config.mk files need to know what CPUDIR is set to,
# so calculate CPUDIR before including ARCH/SOC/CPU config.mk files.
# Check if arch/$ARCH/cpu/$CPU exists, otherwise assume arch/$ARCH/cpu contains
# CPU-specific code.
CPUDIR=arch/$(ARCH)/cpu$(if $(CPU),/$(CPU),)		# CPUDIR的值是根据ARCH和CPU变量的值构造而成的。如果CPU为空,则CPUDIR为arch/$(ARCH),否则为arch/$(ARCH)/cpu/$(CPU)。这样可以根据不同的架构和CPU类型,指定相应的CPU相关代码所在的目录路径。

sinclude $(srctree)/arch/$(ARCH)/config.mk	# include architecture dependend rules	 # sinclude 和 include 的功能类似,不相同的点在于sinclude文件不存在,程序将不会终止运行,继续执行接下来的内容。
sinclude $(srctree)/$(CPUDIR)/config.mk		# include  CPU	specific rules

ifdef	SOC
sinclude $(srctree)/$(CPUDIR)/$(SOC)/config.mk	# include  SoC	specific rules
endif
ifneq ($(BOARD),)
ifdef	VENDOR
BOARDDIR = $(VENDOR)/$(BOARD)
else
BOARDDIR = $(BOARD)
endif
endif
ifdef	BOARD
sinclude $(srctree)/board/$(BOARDDIR)/config.mk	# include board specific rules
endif

ifdef FTRACE
PLATFORM_CPPFLAGS += -finstrument-functions -DFTRACE
endif

#########################################################################

RELFLAGS := $(PLATFORM_RELFLAGS)

PLATFORM_CPPFLAGS += $(RELFLAGS)
PLATFORM_CPPFLAGS += -pipe

LDFLAGS += $(PLATFORM_LDFLAGS)
LDFLAGS_FINAL += -Bstatic

export PLATFORM_CPPFLAGS
export RELFLAGS
export LDFLAGS_FINAL
export LDFLAGS_STANDALONE
export CONFIG_STANDALONE_LOAD_ADDR

  接下来需要找到 CONFIG_SYS_ARCHCONFIG_SYS_CPUCONFIG_SYS_BOARDCONFIG_SYS_VENDOR CONFIG_SYS_SOC 5 个变量的值。这 5 个变量在 uboot 根目录下的.config 文件中有定义,定义如下: 

CONFIG_SYS_ARCH="arm"
CONFIG_SYS_CPU="armv7"
CONFIG_SYS_SOC="stm32mp"
CONFIG_SYS_VENDOR="st"
CONFIG_SYS_BOARD="stm32mp1"
CONFIG_SYS_CONFIG_NAME="stm32mp1"
ARCH = arm
CPU = armv7
BOARD = stm32mp1
VENDOR = st
SOC = stm32mp
CPUDIR = arch/arm/cpu/armv7
BOARDDIR = st/stm32mp1

# 因此可以推导出 config.mk 中读取的文件有:
arch/arm/config.mk
arch/arm/cpu/armv7/config.mk
arch/arm/cpu/armv7/stm32mp/config.mk (此文件不存在)
board/st/stm32mp1/config.mk (此文件不存在)

11.  make xxx_defconfig 执行流程 

 

 

12.  make 过程

  make xxx_defconfig: 用于配置 uboot,这个命令最主要的目的就是生成.config 文件。
  make:用于编译 uboot,这个命令的主要工作就是生成二进制的 u-boot.bin 文件和其他的一些与 uboot 有关的文件,比如 u-boot.stm32 等等

本文作者:烟儿公主

本文链接:https://www.cnblogs.com/toutiegongzhu/p/17593179.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   烟儿公主  阅读(269)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 夏日大冒险 暴躁的兔子
夏日大冒险 - 暴躁的兔子
00:00 / 00:00
An audio error has occurred.

作词 : 暴躁的兔子

作曲 : 暴躁的兔子

编曲 : IOF

混音:Gfanfan

出品:网易飓风

夏天 不要再浪费时间

实现 你承诺过的改变

别再 找一堆借口拖延

现在就和我一起飞向海边

人生苦短 你应该学会如何作乐

低着头还怎么应对挫折

人应该为自己活着

不用去迎合

要去寻欢作乐

撮合我的浪漫和悲欢

把这荒诞人生都塞满

生活难免磕磕绊绊

对抗生活的平庸就是浪漫

学会取悦自己逆风翻盘

去反抗变态的三观

把条条框框都砸烂

建立新的规则推翻谈判

无可救药的人呐

和我一起去海边

看那日出和晚霞 海天一线

看阳光穿越地平线

现实交织的明天

就在这个夏天

为自己改变

别怕山高路远

去冒险

我真的不care你是否会喜欢我

不跟风被定义的美 全都是灾祸

我才不讨好大多数绝不与示弱

过好你的生活

你管我应该怎么快活

没有人能有资格审判

别人的生活和牵绊

快闭上你的高谈阔论

乘风破浪吧 理想的风帆

我就是肆意张扬又如何

我就是锋芒毕露又如何

我就是离经叛道又如何

我就是要出格 你管我要如何

我就是与众不同又如何

我就是特立独行又如何

我就是不知好歹又如何

你管我怎样出格 你管我如何

无可救药的人呐

和我一起去海边

看那日出和晚霞 海天一线

看阳光穿越地平线

现实交织的明天

就在这个夏天

为自己改变

别怕山高路远

不知进退的人呐

和我一起去海边

聊聊曾经的理想 一起想当年

那曾想改变世界的人

是否还满腔热忱

不羁的我们放肆着

反抗那命运的指针

解放灵魂

推广:网易飓风

企划:贾焱祺

监制:徐思灵

出品人:谢奇笛