【嵌入式Linux】U-Boot源码分析
编译出来之后的U-Boot的文件结构及其作用如下:

其中比较重要的文件夹有如下几个:
1、arch文件夹
主要存放架构相关文件,存储着包括不同架构下不同CPU的内容,包括arm\x86等等。在存放着arm架构相关设置的文件夹arm下,(\uboot\arch\arm\cpu
),包含着各个arm版本的设置文件夹,以及根本的ARM 芯片所使用的 u-boot 链接脚本文件u-boot.lds
2、board文件夹
board 文件夹就是和具体的板子有关的,打开此文件夹,里面全是不同的板子,这是对开发板做的适配文件,本文使用的是mx6ullevk
这个开发板。
3、configs文件夹
此文件夹为 uboot 配置文件,uboot 是可配置的,但是你要是自己从头开始一个一个项目的配置,那就太麻烦了,因此一般半导体或者开发板厂商都会制作好一个配置文件。我们可以在这个做好的配置文件基础上来添加自己想要的功能,这些半导体厂商或者开发板厂商制作好的配置文件统一命名为“xxx_defconfig”,xxx 表示开发板名字。
我们只关心 mx6ull_14x14_ddr512_emmc_defconfig 和 mx6ull_14x14_ddr256_nand_defconfig这两个文件,分别是正点原子 I.MX6ULL EMMC 核心板和 NAND 核心板的配置文件。
4、 .u-boot.xxx_cmd 文件
.u-boot.xxx_cmd
是一系列的文件,这些文件都是编译生成的,都是一些命令文件,比如文件.u-boot.bin.cmd
,看名字应该是和 u-boot.bin
有关的。
cmd_u-boot.bin := cp u-boot-nodtb.bin u-boot.bin
u-boot-nodtb.bin
是 怎么来的呢?文件 .u-boot-nodtb.bin.cmd
就 是 用 于 生 成 u-boot.nodtb.bin
的,此文件内容如下:
cmd_u-boot-nodtb.bin := arm-linux-gnueabihf-objcopy \
--gap-fill=0xff -j .text -j .secure_text \
-j .rodata -j .hash -j .data -j .got \
-j .got.plt -j .u_boot_list -j \
.rel.dyn -O binary u-boot u-boot-nodtb.bin
文件.u-boot.lds.cmd 就是用于生成 u-boot.lds 链接脚本的,由于.u-boot.lds.cmd 文件内容太多,这里就不列出来了。uboot 根目录下的 u-boot.lds 链接脚本就是来源于 arch/arm/cpu/u-boot.lds文件。
5、Makefile文件
这个是顶层 Makefile 文件,Makefile 是支持嵌套的,也就是顶层 Makefile 可以调用子目录中的 Makefile 文件。
6、u-boot.xxx文件
- u-boot:编译出来的 ELF 格式的 uboot 镜像文件。
- u-boot.bin:编译出来的二进制格式的 uboot 可执行镜像文件。
- u-boot.cfg:uboot 的另外一种配置文件。
- u-boot.imx:u-boot.bin 添加头部信息以后的文件,NXP 的 CPU 专用文件。
- u-boot.lds:链接脚本。
- u-boot.map:uboot 映射文件,通过查看此文件可以知道某个函数被链接到了哪个地址上。
- u-boot.srec:S-Record 格式的镜像文件。
- u-boot.sym:uboot 符号文件。
- u-boot-nodtb.bin:和 u-boot.bin 一样,u-boot.bin 就是 u-boot-nodtb.bin 的复制文件。
7、 .config
uboot 配置文件,使用命令“make xxx_defconfig”配置 uboot 以后就会自动生成。.config中的众多选项会决定uboot编译时会编译什么模块或某些功能,
8.fs
file system,存放uboot支持的文件系统的相关支持文件
其实我们移植uboot的时候重点关注的就是board和.config的内容
U-Boot顶层Makefile分析
顶层Makefile位于uboot/arch/arm/Makefile
1.递归make设置
首先是第二十行的
MAKEFLAGS += -rR --include-dir=$(CURDIR)
make是支持递归调用的,可以使用Makefile的make命令递归调用其他子目录或者同一目录的Makefile。在顶层目录的Makefile中可以使用
$(MAKE) -C subdir
来编译子目录,其中subdir是子目录的相对地址。这时候如果需要将本级Makefile的变量传递给子Makefile,可以使用export来声明导出标量
export VARIABLE …… //导出变量给子 make 。
unexport VARIABLE…… //不导出变量给子 make。
在这种递归调用makefile的结构中,有两个特殊的变量。“SHELL”和“MAKEFLAGS”,这两个变量除非使用“unexport”声明,
否则的话在整个make的执行过程中,它们的值始终自动的传递给子make,回到上面的第20行命令,里面还有一些别的参数,其中“-rR”表示禁止使用内置的隐含规则和变量定义,“–include-dir”指明搜索路径,”$(CURDIR)”表示当前目录。
2.设置编译结果输出目录
uboot 可以将编译出来的目标文件输出到单独的目录中,在 make 的时候使用“O”来指定输出目录,比如“make O=out”就是设置目标文件输出到 out 目录中。这么做是为了将源文件和编译产生的文件分开,当然也可以不指定 O 参数,不指定的话源文件和编译产生的文件都在同一个目录内,一般我们不指定 O 参数。
124 ifeq ("$(origin O)", "command line")
125 KBUILD_OUTPUT := $(O)
126 endif
135 ifneq ($(KBUILD_OUTPUT),)
139 KBUILD_OUTPUT := $(shell mkdir -p $(KBUILD_OUTPUT) && cd $(KBUILD_OUTPUT) && /bin/pwd)
155 endif # ifneq ($(KBUILD_OUTPUT),)
注意看行号,
- 第 124 行判断“O”是否来自于命令行,如果来自命令行的话条件成立,KBUILD_OUTPUT就为$(O),因此变量 KBUILD_OUTPUT 就是输出目录。
- 第 135 行判断 KBUILD_OUTPUT 是否为空。如果是空,则第 139 行调用 mkdir 命令,创建 KBUILD_OUTPUT 目录,并且将创建成功以后的绝对路径赋值给 KBUILD_OUTPUT。至此,通过 O 指定的输出目录就存在了。
3.模块编译
4.获取主机架构和操作系统
5.设置目标架构、交叉编译器和配置文件
编译uboot的时候 需要设置目标板架构和交叉编译器 ,“ make ARCH=arm CROSS_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
第 245 行判断 HOSTARCH 和 ARCH 这两个变量是否相等,主机架构(变量 HOSTARCH)是x86_64,而我们编译的是 ARM 版本 uboot,肯定不相等,所以 CROSS_COMPILE= arm-linux-gnueabihf-,也就是需要启动交叉编译。我们每次编译 uboot 的时候都要在 make 命令后面设置ARCH 和 CROSS_COMPILE,使用起来很麻烦,可以直接修改顶层 Makefile,在里面加入 ARCH和 CROSS_COMPILE 的定义。
ifeq ($(HOSTARCH),$(ARCH))
CROSS_COMPILE ?=
endif
ARCH ?= arm
CROSS_COMPILE ?= arm-linux-gnueabihf-
第 249 行定义变量 KCONFIG_CONFIG,用于设置uboot编译使用的配置文件,这里设置配置文件为.config,.config 默认是没有的,需要使用命令“make xxx_defconfig”对 uboot 进行配置,配置完成以后就会在 uboot 根目录下生成.config。默认情况下.config 和xxx_defconfig 内容是一样的,因为.config 就是从 xxx_defconfig 复制过来的。如果后续自行调整了 uboot 的一些配置参数,那么这些新的配置参数就添加到了.config 中,而不是 xxx_defconfig。相当于 xxx_defconfig 只是一些初始配置,而.config 里面的才是实时有效的配置
6.交叉编译工具变量设置
上面我们只是设置了 CROSS_COMPILE 的名字,但是交叉编译器其他的工具还没有设置,顶层 Makefile 中相关代码如下:
# Make variables (CC, etc...)
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
这些变量主要是进行交叉编译的时候的各类工具,比如
- CC:编译器,用于编译C语言源文件。
- CPP:C预处理器,用于处理源文件中的预处理指令。
- AR:归档器,用于创建、修改静态库文件。
- NM:符号表提取工具,用于列出目标文件或库文件中的符号。
这也解释了为什么交叉编译器只需要写到arm-linux-gnueabihf-
,因为如果需要编译C语言文件,会调用CC变量,也就是arm-linux-gnueabihf-gcc,其他的归档、连接、符号表提取也同理
7.导出其他变量
这些变量中大部分都已经在前面定义了,我们重点来看一下下面这几个变量:
ARCH CPU BOARD VENDOR SOC CPUDIR BOARDDIR
这些变量主要是存储在根目录下的config.mk文件中,从25行开始有如下代码:
ARCH := $(CONFIG_SYS_ARCH:"%"=%)
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
第 25 行定义变量 ARCH ,值为 $(CONFIG_SYS_ARCH:“%”=%) , 也 就 是 提 取CONFIG_SYS_ARCH 里面双引号“”之间的内容。比如 CONFIG_SYS_ARCH=“arm”的话,ARCH=arm。其他的几个也类似,那么接下来就是要找接下来需要找到 CONFIG_SYS_ARCH、CONFIG_SYS_CPU、CONFIG_SYS_BOARD、CONFIG_SYS_VENDOR 和 CONFIG_SYS_SOC 这 5 个变量的值。这 5 个变量在 uboot 根目录下的.config 文件中有定义
好吧,其实是根Makefile找到config.mk,然后config.mk又找.config,最终取得规定的值的故事。
7.make xxx_defconfig 过程
在编译 uboot 之前要使用“make xxx_defconfig”命令来配置 uboot,那么这个配置过程是如何运行的呢?