一、U-boot工程目录分析
如果要分析uboot源码,首先要将uboot源码进行编译,编译需要在Ubuntu进行,把uboot文件放在一个目录下。
编译完成后的文件是这样:
我们需要看的文件夹如下。
1. arch 文件夹
从上图可以看出有很多架构,比如 arm、 m68k、 x86 等,我们现在用的是 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-boot、 u-boot-dtb.bin、 u-boot-nodtb.bin、 u-boot.bin、u-boot.cfg、 u-boot.dtb、 u-boot.lds、 u-boot.map、 u-boot.srec、 u-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
可以看到这 7 个变量的值,这 7 个变量是从哪里来的呢?在 uboot 根目录下有个文件叫做 config.mk,这 7个变量就是在 config.mk 里面定义的,打开 config.mk 内容如下:
# SPDX-License-Identifier: GPL-2.0+
#
# (C) Copyright 2000-2013
# Wolfgang Denk, DENX Software Engineering, [email protected].
#########################################################################
# 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_ARCH、 CONFIG_SYS_CPU、 CONFIG_SYS_BOARD、CONFIG_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 等等。