我们在分析 uboot 源码之前一定要
先在 Ubuntu 中编译一下 uboot 源码,因为编译过程会生成一些文件,而生成的这些恰恰是分析
uboot 源码不可或缺的文件。使用上一章创建的 shell 脚本来完成编译工作,命令如下:
cd alientek_uboot //进入正点原子 uboot 源码目录
./mx6ull_alientek_emmc.sh //编译 uboot
cd ../ //返回上一级目录
tar -vcjf alientek_uboot.tar.bz2 alientek_uboot //压缩
这些文件夹或文件的含义见表 31.1.1 所示:
名字 | 描述 | 备注 |
---|---|---|
api | 与硬件无关的 API 函数。 | uboot 自带 |
arch | 与架构体系有关的代码。 | uboot 自带 |
board | 不同板子(开发板)的定制代码。 | uboot 自带 |
cmd | 命令相关代码。 | uboot 自带 |
common | 通用代码。 | uboot 自带 |
configs | 配置文件。 | uboot 自带 |
disk | 磁盘分区相关代码 | uboot 自带 |
doc | 文档。 | uboot 自带 |
drivers | 驱动代码。 | uboot 自带 |
dts | 设备树。 | uboot 自带 |
examples | 示例代码。 | uboot 自带 |
fs | 文件系统。 | uboot 自带 |
include | 头文件。 | uboot 自带 |
lib | 库文件。 | uboot 自带 |
Licenses | 许可证相关文件。 | uboot 自带 |
net | 网络相关代码。 | uboot 自带 |
post | 上电自检程序。 | uboot 自带 |
scripts | 脚本文件。 | uboot 自带 |
test | 测试代码。 | uboot 自带 |
tools | 工具文件夹。 | uboot 自带 |
.config 配置文件, | 重要的文件。 | 编译生成的文件 |
.gitignore git | 工具相关文件。 | uboot 自带 |
.mailmap | 邮件列表。 | uboot 自带 |
.u-boot.xxx.cmd(一系列) | 这是一系列的文件,用于保存着一些命令。 | 编译生成的文件 |
config.mk | 某个 Makefile 会调用此文件。 | uboot 自带 |
imxdownload | 正点原子编写的 SD 卡烧写软件。 | 正点原子提供 |
Kbuild | 用于生成一些和汇编有关的文件。 | uboot 自带 |
Kconfig | 图形配置界面描述文件。 | uboot 自带 |
MAINTAINERS | 维护者联系方式文件。 | uboot 自带 |
MAKEALL | 一个 shell 脚本文件,帮助编译uboot 的。 | uboot 自带 |
Makefile | 主 Makefile,重要文件! | uboot 自带 |
mx6ull_alientek_emmc.sh | 上一章编写的编译脚本文件 | 上一章编写的。 |
mx6ull_alientek_nand.sh | 上一章编写的编译脚本文件 | 上一章编写的。 |
README | 相当于帮助文档。 | uboot 自带 |
snapshot.commint | ??? | uboot 自带 |
System.map | 系统映射文件 | 编译出来的文件 |
u-boot | 编译出来的 u-boot 文件。 | 编译出来的文件 |
u-boot.xxx(一系列) | 生成的一些 u-boot 相关文件,包括u-boot.bin、 u-boot.imx.等 | 编译出来的文件 |
我们要关注的文件夹或文件如下:
1、 arch 文件夹
这个文件夹里面存放着和架构有关的文件,我们现在用的是 ARM 芯
片,所以只需要关心 arm 文件夹即可。mach 开头的文件夹是跟具体
的设备有关的,比如“ mach-exynos”就是跟三星的 exyons 系列 CPU 有关的文件。我们使用的
是 I.MX6ULL,所以要关注“ imx-common”这个文件夹。另外“ cpu”这个文件夹也是和 cpu 架
构有关的,I.MX6ULL 使用的 Cortex-A7 内核,Cortex-A7 属于 armv7,所以我们要关心“ armv7”这个文件夹。
cpu 文件夹里面有个名为“ uboot.lds”的链接脚本文件,这个就是 ARM 芯片所使用的 u-boot 链接脚本文件! armv7 这个文
件夹里面的文件都是跟 ARMV7 架构有关的,是我们分析 uboot 启动源码的时候需要重点关注
的。
2、 board 文件夹
board 文件夹就是和具体的板子有关的,打开此文件夹,里面全是不同的板子,毫无疑问正
点原子的开发板肯定也在里面(正点原子添加的), borad 文件夹里面有个名为“ freescale”的文
件夹。
所有使用 freescale 芯片的板子都放到此文件夹中, I.MX 系列以前属于 freescale,只是
freescale 后来被 NXP 收购了。打开此 freescale 文件夹,在里面找到和 mx6u(I.MX6UL/ULL)有
关的文件夹。
以“ mx6ul”开头的表示使用
I.MX6UL 芯片的板子,以 mx6ull 开头的表示使用 I.MX6ULL 芯片的板子。 mx6ullevk 是 NXP
官方的 I.MX6ULL开发板,正点原子的 ALPHA开发板就是在这个基础上开发的,因此 mx6ullevk
也是正点原子的开发板。我们后面移植 uboot 到时候就是参考 NXP 官方的开发板,也就是要参考 mx6ullevk 这个文件夹来定义我们的板子。
3、 configs 文件夹
此文件夹为 uboot 配置文件, uboot 是可配置的,但是你要是自己从头开始一个一个项目的
配置,那就太麻烦了,因此一般半导体或者开发板厂商都会制作好一个配置文件。我们可以在
这个做好的配置文件基础上来添加自己想要的功能,这些半导体厂商或者开发板厂商制作好的
配置文件统一命名为“ xxx_defconfig”,xxx 表示开发板名字,这些 defconfig 文件都存放在 configs
文件夹,因此, NXP 官方开发板和正点原子的开发板配置文件肯定也在这个文件夹中。
我们只关心 mx6ull_14x14_ddr512_emmc_defconfig 和 mx6ull_14x14_ddr256_nand_defconfig
这两个文件,分别是正点原子 I.MX6ULL EMMC 核心板和 NAND 核心板的配置文件。使用
“ make xxx_defconfig”命令即可配置 uboot,比如:
make mx6ull_14x14_ddr512_emmc_defconfig
上述命令就是配置正点原子的 I.MX6ULL EMMC 核心板所使用的 uboot。
在编译 uboot 之前一定要使用 defconfig 来配置 uboot!
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.bin.cmd 里面定义了一个变量: cmd_u-boot.bin,此变量的值为“ cp u-boot-nodtb.bin
u-boot.bin”,也就是拷贝一份 u-boot-nodtb.bin 文件,并且重命名为 u-boot.bin,这个就是 u-boot.bin
的来源,来自于文件 u-boot-nodtb.bin。
那 么 u-boot-nodtb.bin 是 怎 么 来 的 呢 ? 文 件 .u-boot-nodtb.bin.cmd 就 是 用 于 生 成 uboot.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-bootnodtb.bin
这里用到了 arm-linux-gnueabihf-objcopy,使用 objcopy 将 ELF 格式的 u-boot 文件转换为二
进制的 u-boot-nodtb.bin 文件。
文件 u-boot 是 ELF 格式的文件,文件.u-boot.cmd 用于生成 u-boot,文件内容如下:
cmd_u-boot := arm-linux-gnueabihf-ld.bfd -pie --gc-sections -
Bstatic -Ttext 0x87800000 -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/imx-common/built-in.o
arch/arm/lib/built-in.o board/freescale/common/built-in.o
board/freescale/mx6ull_alientek_emmc/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/builtin.o drivers/mmc/built-in.o drivers/mtd/built-in.o
drivers/mtd/onenand/built-in.o drivers/mtd/spi/built-in.o
drivers/net/built-in.o drivers/net/phy/built-in.o drivers/pci/builtin.o drivers/power/built-in.o drivers/power/battery/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/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 fs/built-in.o
lib/built-in.o net/built-in.o test/built-in.o test/dm/built-in.o --
end-group arch/arm/lib/eabi_compat.o -L /usr/local/arm/gcc-linaro-
4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin/../lib/gcc/arm-linuxgnueabihf/4.9.4 -lgcc -Map u-boot.map
.u-boot.cmd 使用到了 arm-linux-gnueabihf-ld.bfd,也就是链接工具,使用 ld.bfd 将各个 builtin.o 文件链接在一起就形成了 u-boot 文件。 uboot 在编译的时候会将同一个目录中的所有.c 文件
都编译在一起,并命名为 built-in.o,相当于将众多的.c 文件对应的.o 文件集合在一起,这个就
是 u-boot 文件的来源。
如果我们要用 NXP 提供的 MFGTools 工具向开发板烧写 uboot,此时烧写的是 u-boot.imx
文件,而不是 u-boot.bin 文件。u-boot.imx 是在 u-boot.bin 文件的头部添加了 IVT、 DCD 等信息。
这个工作是由文件.u-boot.imx.cmd 来完成的,此文件内容如下:
cmd_u-boot.imx := ./tools/mkimage -n
board/freescale/mx6ull_alientek_emmc/imximage.cfg.cfgtmp -T imximage -
e 0x87800000 -d u-boot.bin u-boot.imx
可以看出,这里用到了工具 tools/mkimage,而 IVT、 DCD 等数据保存在了文件
board/freescale/mx6ullevk/imximage-ddr512.cfg.cfgtmp 中 ( 如 果 是 NAND 核 心 板 的 话 就 是
imximage-ddr256.cfg.cfgtmp),工具 mkimage 就是读取文件 imximage-ddr512.cfg.cfgtmp 里面的
信息,然后将其添加到文件 u-boot.bin 的头部,最终生成 u-boot.imx。
文件.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 文件。 Makefile 嵌套在大项目中很常见,一般大项目里面所有的源代码都不会放
到同一个目录中,各个功能模块的源代码都是分开的,各自存放在各自的目录中。每个功能模
块目录下都有一个 Makefile,这个 Makefile 只处理本模块的编译链接工作,这样所有的编译链
接工作就不用全部放到一个 Makefile 中,可以使得 Makefile 变得简洁明了。
uboot 源码根目录下的 Makefile 是顶层 Makefile,他会调用其它的模块的 Makefile 文件,
比如 drivers/adc/Makefile。当然了,顶层 Makefile 要做的工作可远不止调用子目录 Makefile 这
么简单,关于顶层 Makefile 的内容我们稍后会有详细的讲解。
6、 u-boot.xxx 文件
u-boot.xxx 同样也是一系列文件,包括 u-boot、u-boot.bin、u-boot.cfg、u-boot.imx、u-boot.lds、
u-boot.map、 u-boot.srec、 u-boot.sym 和 u-boot-nodtb.bin,这些文件的含义如下:
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 文件中都是以“ CONFIG_”开始的配置项,这些配置项就是 Makefile 中的
变量,因此后面都跟有相应的值, uboot 的顶层 Makefile 或子 Makefile 会调用这些变量值。
在.config 中会有大量的变量值为‘ y’,这些为‘ y’的变量一般用于控制某项功能是否使能,为
‘ y’的话就表示功能使能,比如:
CONFIG_CMD_BOOTD=y
如果使能了 bootd 这个命令的话, CONFIG_CMD_BOOTM 就为‘ y’。在 cmd/Makefile 中
有如下代码:
示例代码 31.1.6 cmd/Makefile 代码
1 ifndef CONFIG_SPL_BUILD
2 # core command
3 obj-y += boot.o
4 obj-$(CONFIG_CMD_BOOTM) += bootm.o
5 obj-y += help.o
6 obj-y += version.o
在示例代码 31.1.6 中,有如下所示一行代码:
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 内核
中都是采用这种方法来选择使能某个功能,编译对应的源码文件。
8、 README
README 文件描述了 uboot 的详细信息,包括 uboot 该如何编译、 uboot 中各文件夹的含
义、相应的命令等等。建议大家详细的阅读此文件,可以进一步增加对 uboot 的认识。
在阅读 uboot 源码之前,肯定是要先看一下顶层 Makefile,分析 gcc 版本代码的时候一定是
先从顶层 Makefile 开始的,然后再是子 Makefile,这样通过层层分析 Makefile 即可了解整个工
程的组织结构。
1、版本号
顶层 Makefile 一开始是版本号
5 VERSION = 2016
6 PATCHLEVEL = 03
7 SUBLEVEL =
8 EXTRAVERSION =
9 NAME =
VERSION 是主版本号, PATCHLEVEL 是补丁版本号, SUBLEVEL 是次版本号,这三个一
起构成了 uboot 的版本号,比如当前的 uboot 版本号就是“ 2016.03”。 EXTRAVERSION 是附加
版本信息, NAME 是和名字有关的,一般不使用这两个。
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
中有如下代码:
示例代码 31.3.2.1 顶层 Makefile 代码
20 MAKEFLAGS += -rR --include-dir=$(CURDIR)
上述代码使用“ +=”来给变量 MAKEFLAGS 追加了一些值,“ -rR”表示禁止使用内置的隐
含规则和变量定义,“ --include-dir”指明搜索路径, ”$(CURDIR)”表示当前目录。
3、命令输出
uboot 默认编译是不会在终端中显示完整的命令,都是短命令。在终端中输出短命令虽然看起来很清爽,但是不利于分析 uboot 的编译过程。可以通过设置变量“ V=1“来实现完整的命令输出,这个在调试 uboot 的时候很有用。
顶层 Makefile 中控制命令输出的代码如下:
示例代码 31.3.3.1 顶层 Makefile 代码
73 ifeq ("$(origin V)", "command line")
74 KBUILD_VERBOSE = $(V)
75 endif
76 ifndef KBUILD_VERBOSE
77 KBUILD_VERBOSE = 0
78 endif
79
80 ifeq ($(KBUILD_VERBOSE),1)
81 quiet =
82 Q =
83 else
84 quiet=quiet_
85 Q = @
86 endif
上述代码中先使用 ifeq 来判断"$(origin V)"和"command line"是否相等。这里用到了 Makefile
中的函数 origin, origin 和其他的函数不一样,它不操作变量的值, origin 用于告诉你变量是哪
来的,语法为:
$(origin <variable>)
variable 是变量名, origin 函数的返回值就是变量来源,因此$(origin V)就是变量 V 的来源。
如果变量 V 是在命令行定义的那么它的来源就是"command line",这样"$(origin V)"和"command
line"就相等了。当这两个相等的时候变量 KBUILD_VERBOSE 就等于 V 的值,比如在命令行中
输 入 “V=1 “ 的 话 那 么 KBUILD_VERBOSE=1 。 如 果 没 有 在 命 令 行 输 入 V 的 话
KBUILD_VERBOSE=0。
第 80 行判断 KBUILD_VERBOSE 是否为 1,如果 KBUILD_VERBOSE 为 1 的话变量 quiet和 Q 都为空,如果 KBUILD_VERBOSE=0 的话变量 quiet 为“ quiet_“,变量 Q 为“ @” ,综上
所述:
V=1 的话:
KBUILD_VERBOSE=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、静默输出
上一小节讲了,设置 V=0 或者在命令行中不定义 V 的话,编译 uboot 的时候终端中显示的
短命令,但是还是会有命令输出,有时候我们在编译 uboot 的时候不需要输出命令,这个时候
就可以使用 uboot 的静默输出功能。编译的时候使用“ make -s”即可实现静默输出,顶层 Makefile
中相应的代码如下:
示例代码 31.3.4.1 顶层 Makefile 代码
88 # If the user is running make -s (silent mode), suppress echoing of
89 # commands
90
91 ifneq ($(filter 4.%,$(MAKE_VERSION)),) # make-4
92 ifneq ($(filter %s ,$(firstword x$(MAKEFLAGS))),)
93 quiet=silent_
94 endif
95 else # make-3.8x
96 ifneq ($(filter s% -s%,$(MAKEFLAGS)),)
97 quiet=silent_
98 endif
99 endif
100
101 export quiet Q KBUILD_VERBOSE
第 91 行判断当前正在使用的编译器版本号是否为 4.x。
这里用到了 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))不为空,条件成立,执行
92~94 行的语句。
第 92 行也是一个判断语句,如果$(filter %s ,$(firstword x$(MAKEFLAGS)))不为空的话条件
成立,变量 quiet 等于“ silent_”。这里也用到了函数 filter,在$(firstword x$(MAKEFLAGS)))中
过滤出符合“ %s”的单词。到了函数 firstword,函数 firstword 是获取首单词,函数格式如下:
$(firstword <text>)
firstword 函数用于取出 text 字符串中的第一个单词,函数的返回值就是获取到的单词。当
使用“ make -s”编译的时候,“ -s”会作为 MAKEFLAGS 变量的一部分传递给 Makefile。
可以看出第一个单词是“ xrRs”,将$(filter %s ,$(firstword x$(MAKEFLAGS)))
展开就是$(filter %s, xrRs),而$(filter %s, xrRs)的返回值肯定不为空,条件成立, quiet=silent_。
第 101 行 使用 export 导出变量 quiet、 Q 和 KBUILD_VERBOSE。
5、设置编译结果输出目录
uboot 可以将编译出来的目标文件输出到单独的目录中,在 make 的时候使用“ O”来指定
输出目录,比如“ make O=out”就是设置目标文件输出到 out 目录中。这么做是为了将源文件
和编译产生的文件分开,当然也可以不指定 O 参数,不指定的话源文件和编译产生的文件都在
同一个目录内,一般我们不指定 O 参数。
124 ifeq ("$(origin O)", "command line")
125 KBUILD_OUTPUT := $(O)
126 endif
127
128 # That's our default target when none is given on the command line
129 PHONY := _all
130 _all:
131
132 # Cancel implicit rules on top Makefile
133 $(CURDIR)/Makefile Makefile: ;
134
135 ifneq ($(KBUILD_OUTPUT),)
136 # Invoke a second make in the output directory, passing relevant
variables
137 # check that the output directory actually exists
138 saved-output := $(KBUILD_OUTPUT)
139 KBUILD_OUTPUT := $(shell mkdir -p $(KBUILD_OUTPUT) && cd
$(KBUILD_OUTPUT) \
140 && /bin/pwd)
第 124 行判断“ O”是否来自于命令行,如果来自命令行的话条件成立, KBUILD_OUTPUT
就为$(O),因此变量 KBUILD_OUTPUT 就是输出目录。
第 135 行判断 KBUILD_OUTPUT 是否为空。
第 139 行调用 mkdir 命令,创建 KBUILD_OUTPUT 目录,并且将创建成功以后的绝对路
径赋值给 KBUILD_OUTPUT。至此,通过 O 指定的输出目录就存在了。
6、代码检查
uboot 支持代码检查,使用命令“ make C=1”使能代码检查,检查那些需要重新编译的文
件。“ make C=2”用于检查所有的源码文件,顶层 Makefile 中的代码如下:
176 ifeq ("$(origin C)", "command line")
177 KBUILD_CHECKSRC = $(C)
178 endif
179 ifndef KBUILD_CHECKSRC
180 KBUILD_CHECKSRC = 0
181 endif
第 176 行判断 C 是否来源于命令行,如果 C 来源于命令行,那就将 C 赋值给变量
KBUILD_CHECKSRC,如果命令行没有 C 的话 KBUILD_CHECKSRC 就为 0。
7、模块编译
在 uboot 中允许单独编译某个模块,使用命令“make M=dir”即可,旧语法“make
SUBDIRS=dir”也是支持的。顶层 Makefile 中的代码如下:
186 ifdef SUBDIRS
187 KBUILD_EXTMOD ?= $(SUBDIRS)
188 endif
189
190 ifeq ("$(origin M)", "command line")
191 KBUILD_EXTMOD := $(M)
192 endif
193
194 # If building an external module we do not care about the all: rule
195 # but instead _all depend on modules
196 PHONY += all
197 ifeq ($(KBUILD_EXTMOD),)
198 _all: all
199 else
200 _all: modules
201 endif
202
203 ifeq ($(KBUILD_SRC),)
204 # building in the source tree
205 srctree := .
206 else
207 ifeq ($(KBUILD_SRC)/,$(dir $(CURDIR)))
208 # building in a subdirectory of the source tree
209 srctree := ..
210 else
211 srctree := $(KBUILD_SRC)
212 endif
213 endif
214 objtree := .
215 src := $(srctree)
216 obj := $(objtree)
217
218 VPATH := $(srctree)$(if $(KBUILD_EXTMOD),:$(KBUILD_EXTMOD))
219
220 export srctree objtree VPATH
第 186 行 判 断 是 否 定 义 了 SUBDIRS , 如 果 定 义 了 SUBDIRS , 变 量
KBUILD_EXTMOD=SUBDIRS,这里是为了支持老语法“ make SUBIDRS=dir”
第 190 行判断是否在命令行定义了 M,如果定义了的话 KBUILD_EXTMOD=$(M)。
第 197 行判断 KBUILD_EXTMOD 时为空,如果为空的话目标_all 依赖 all,因此要先编译
出 all。否则的话默认目标_all 依赖 modules,要先编译出 modules,也就是编译模块。一般情况
下我们不会在 uboot 中编译模块,所以此处会编译 all 这个目标。
第 203 行判断 KBUILD_SRC 是否为空,如果为空的话就设置变量 srctree 为当前目录,即
srctree 为“ .”,一般不设置 KBUILD_SRC。
第 214 行设置变量 objtree 为当前目录。
第 215 和 216 行分别设置变量 src 和 obj,都为当前目录。
第 218 行设置 VPATH。
第 220 行导出变量 scrtree、 objtree 和 VPATH。
8、获取主机架构和系统
接下来顶层 Makefile 会获取主机架构和系统,也就是我们电脑的架构和系统,代码如下:
示例代码 31.3.8.1 顶层 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
第 227 行定义了一个变量 HOSTARCH,用于保存主机架构,这里调用 shell 命令“ uname -m”获取架构名称。
shell 中的“ |”表示管道,意思是将左边的输出作为右边的输入, sed -e 是替换命令,“ sed -e s/i.86/x86/”表示将管道输入的字符串中的“ i.86”替换为“ x86”,其他的“ sed -s”命令同理。对于我的电脑而言, HOSTARCH=x86_64。
第 237 行定义了变量 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。
第 240 行导出 HOSTARCH=x86_64, HOSTOS=linux。
9、设置目标架构、交叉编译器和配置文件
编 译 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,肯定不相等,所以 CROS_COMPILE= arm-linuxgnueabihf-。
每次编译 uboot 的时候都要在 make 命令后面设置
ARCH 和 CROS_COMPILE,使用起来很麻烦,可以直接修改顶层 Makefile,在里面加入 ARCH
和 CROSS_COMPILE 的定义。
第 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 里面的才是实时有效的配置。
10、调用 scripts/Kbuild.include
主 Makefile 会调用文件 scripts/Kbuild.include 这个文件,顶层 Makefile 中代码如下:
328 scripts/Kbuild.include: ;
329 include scripts/Kbuild.include
使用“ include”包含了文件 scripts/Kbuild.include,此文件里面定义了
很多变量。
11、交叉编译工具变量设置
上面我们只是设置了 CROSS_COMPILE 的名字,但是交叉编译器其他的工具还没有设置
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
12、导出其他变量
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
这些变量中大部分都已经在前面定义了,我们重点来看一下下面这几个变量:
ARCH CPU BOARD VENDOR SOC CPUDIR BOARDDIR
这 7 个变量在顶层 Makefile 是找不到的,说明这 7 个变量是在其他文件里面定义的。
这 7 个变量是从哪里来的呢?在 uboot 根目录下
有个文件叫做 config.mk,这 7 个变量就是在 config.mk 里面定义的,打开 config.mk 内容如下:
26 CPU := $(CONFIG_SYS_CPU:"%"=%)
44 CPUDIR=arch/$(ARCH)/cpu$(if $(CPU),/$(CPU),)
46 sinclude $(srctree)/arch/$(ARCH)/config.mk
47 sinclude $(srctree)/$(CPUDIR)/config.mk
50 sinclude $(srctree)/$(CPUDIR)/$(SOC)/config.mk
第 25 行 定 义 变 量 ARCH , 值 为 $(CONFIG_SYS_ARCH:"%"=%) , 也 就 是 提 取
CONFIG_SYS_ARCH 里面双引号“”之间的内容。比如 CONFIG_SYS_ARCH=“ arm”的话,
ARCH=arm。
第 26 行定义变量 CPU,值为$(CONFIG_SYS_CPU:"%"=%)。
第 32 行定义变量 BOARD,值为(CONFIG_SYS_BOARD:"%"=%)。
第 34 行定义变量 VENDOR,值为$(CONFIG_SYS_VENDOR:"%"=%)。
第 37 行定义变量 SOC,值为$(CONFIG_SYS_SOC:"%"=%)。
第 44 行定义变量 CPUDIR,值为 arch/$(ARCH)/cpu$(if $(CPU),/$(CPU),)。
第 46 行 sinclude 和 include 的功能类似,在 Makefile 中都是读取指定文件内容,这里读取
文件$(srctree)/arch/$(ARCH)/config.mk 的内容。 sinclude 读取的文件如果不存在的话不会报错。
第 47 行读取文件$(srctree)/$(CPUDIR)/config.mk 的内容。
第 50 行读取文件$(srctree)/$(CPUDIR)/$(SOC)/config.mk 的内容。
第 54 行 定 义 变 量 BOARDDIR , 如 果 定 义 了 VENDOR 那 么
BOARDDIR=$(VENDOR)/$(BOARD),否则的 BOARDDIR=$(BOARD)。
第 60 行读取文件$(srctree)/board/$(BOARDDIR)/config.mk。
接下来需要找到 CONFIG_SYS_ARCH、 CONFIG_SYS_CPU、 CONFIG_SYS_BOARD、
CONFIG_SYS_VENDOR 和 CONFIG_SYS_SOC 这 5 个变量的值。这 5 个变量在 uboot 根目录
下的.config 文件中有定义,定义如下:
示例代码 31.3.12.3 .config 文件代码
23 CONFIG_SYS_ARCH="arm"
24 CONFIG_SYS_CPU="armv7"
25 CONFIG_SYS_SOC="mx6"
26 CONFIG_SYS_VENDOR="freescale"
27 CONFIG_SYS_BOARD="mx6ullevk "
28 CONFIG_SYS_CONFIG_NAME="mx6ullevk"
根据示例代码 31.3.12.3 可知:
ARCH = arm
CPU = armv7
BOARD = mx6ullevk
VENDOR = freescale
SOC = mx6
CPUDIR = arch/arm/cpu/armv7
BOARDDIR = freescale/mx6ullevk
在 config.mk 中读取的文件有:
arch/arm/config.mk
arch/arm/cpu/armv7/config.mk
arch/arm/cpu/armv7/mx6/config.mk (此文件不存在)
board/ freescale/mx6ullevk/config.mk (此文件不存在)
13、 make xxx_defconfig 过程
422 version_h := include/generated/version_autogenerated.h
423 timestamp_h := include/generated/timestamp_autogenerated.h
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
465 ifeq ($(config-targets),1)
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 $@
第 422 行定义了变量 version_h,这变量保存版本号文件,此文件是自动生成的。
第 423 行定义了变量 timestamp_h,此变量保存时间戳文件,此文件也是自动生成的。
第 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 <text>)
很明显, 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 行有如下定义:
1610 PHONY += FORCE
1611 FORCE:
可以看出 FORCE 是没有规则和依赖的,所以每次都会重新生成 FORCE。当 FORCE 作为
其他目标的依赖时,由于 FORCE 总是被更新过的,因此依赖所在的规则总是会执行的。
依赖 scripts_basic 和 outputmakefile 在顶层 Makefile 中的内容如下:
395 PHONY += scripts_basic
396 scripts_basic:
397 $(Q)$(MAKE) $(build)=scripts/basic
398 $(Q)rm -f .tmp_quiet_recordmcount
403 PHONY += outputmakefile
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 文件中有定义,定义如下:
181 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/Makefile.build。
①、 scripts_basic 目标对应的命令
@make -f ./scripts/Makefile.build obj=scripts/basic
②、 %config 目标对应的命令
@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig
14、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,有如下代码:
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,有如下代码:
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)
将 kbuild-dir 展开后为:
$(if $(filter /%, scripts/basic), scripts/basic, ./scripts/basic),
因为没有以“/”为开头的单词,所以 $(filter /%, scripts/basic)的结果为空, kbuilddir=./scripts/basic。
将 kbuild-file 展开后为:
$(if $(wildcard ./scripts/basic/Kbuild), ./scripts/basic/Kbuild, ./scripts/basic/Makefile)
因为 scrpts/basic 目录中没有 Kbuild 这个文件,所以 kbuild-file= ./scripts/basic/Makefile。最
后将 59 行展开,即:
include ./scripts/basic/Makefile
也就是读取 scripts/basic 下面的 Makefile 文件。
继续分析 scripts/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 中输入图
31.3.14.1 所示内容,将这 5 个变量的值打印出来:
因此__build 最终为:
__build: scripts/basic/fixdep
@:
__build 依赖于 scripts/basic/fixdep,所以要先 scripts/basic/fixdep.c 编译,生成 fixdep,前面
已经读取了 scripts/basic/Makefile 文件。
综上所述, scripts_basic 目标的作用就是编译出 scripts/basic/fixdep 这个软件。
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
可以看出, Makefilke.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 这种主机所使用的工具类软件我们一般不关心它是如何编译产生的。
得到 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 执行流程
15、make 过程
配置好 uboot 以后就可以直接 make 编译了,因为没有指明目标,所以会使用默认目标,主
Makefile 中的默认目标如下:
129 PHONY := _all
130 _all:
目标_all 又依赖于 all,如下所示:
196 PHONY += all
197 ifeq ($(KBUILD_EXTMOD),)
198 _all: all
199 else
200 _all: modules
201 endif
如 果 KBUILD_EXTMOD 为 空 的 话 _all 依 赖 于 all 。 这 里 不 编 译 模 块 , 所 以
KBUILD_EXTMOD 肯定为空, _all 的依赖就是 all。
在主 Makefile 中 all 目标规则如下:
802 all: $(ALL-y)
803 ifneq ($(CONFIG_SYS_GENERIC_BOARD),y)
804 @echo "===================== WARNING ======================"
805 @echo "Please convert this board to generic board."
806 @echo "Otherwise it will be removed by the end of 2014."
807 @echo "See doc/README.generic-board for further information"
808 @echo "===================================================="
809 endif
810 ifeq ($(CONFIG_DM_I2C_COMPAT),y)
811 @echo "===================== WARNING ======================"
812 @echo "This board uses CONFIG_DM_I2C_COMPAT. Please remove"
813 @echo "(possibly in a subsequent patch in your series)"
814 @echo "before sending patches to the mailing list."
815 @echo "===================================================="
816 endif
从 802 行可以看出, all 目标依赖$(ALL-y),而在顶层 Makefile 中, ALL-y 如下:
730 # Always append ALL so that arch config.mk's can add custom ones
731 ALL-y += u-boot.srec u-boot.bin u-boot.sym System.map u-boot.cfg
binary_size_check
732
733 ALL-$(CONFIG_ONENAND_U_BOOT) += u-boot-onenand.bin
734 ifeq ($(CONFIG_SPL_FSL_PBL),y)
735 ALL-$(CONFIG_RAMBOOT_PBL) += u-boot-with-spl-pbl.bin
736 else
737 ifneq ($(CONFIG_SECURE_BOOT), y)
738 # For Secure Boot The Image needs to be signed and Header must also
739 # be included. So The image has to be built explicitly
740 ALL-$(CONFIG_RAMBOOT_PBL) += u-boot.pbl
741 endif
742 endif
743 ALL-$(CONFIG_SPL) += spl/u-boot-spl.bin
744 ALL-$(CONFIG_SPL_FRAMEWORK) += u-boot.img
745 ALL-$(CONFIG_TPL) += tpl/u-boot-tpl.bin
可以看出, ALL-y 包含 u-boot.srec、 u-boot.bin、 u-boot.sym、
System.map、 u-boot.cfg 和 binary_size_check 这几个文件。根据 uboot 的配置情况也可能包含其
他的文件,比如:
ALL-$(CONFIG_ONENAND_U_BOOT) += u-boot-onenand.bin
CONFIG_ONENAND_U_BOOT 就是 uboot 中跟 ONENAND 配置有关的,如果我们使能了
ONENAND,那么在.config 配置文件中就会有“ CONFIG_ONENAND_U_BOOT=y”这一句。相
当于 CONFIG_ONENAND_U_BOOT 是个变量,这个变量的值为“ y”,所以展开以后就是:
ALL-y += u-boot-onenand.bin
这个就是.config 里面的配置参数的含义,这些参数其实都是变量,后面跟着变量值,会在
顶层 Makefile 或者其他 Makefile 中调用这些变量。
ALL-y 里面有个 u-boot.bin,这个就是我们最终需要的 uboot 二进制可执行文件,所作的所
有工作就是为了它。在顶层 Makefile 中找到 u-boot.bin 目标对应的规则,如下所示:
825 ifeq ($(CONFIG_OF_SEPARATE),y)
826 u-boot-dtb.bin: u-boot-nodtb.bin dts/dt.dtb FORCE
827 $(call if_changed,cat)
829 u-boot.bin: u-boot-dtb.bin FORCE
830 $(call if_changed,copy)
831 else
832 u-boot.bin: u-boot-nodtb.bin FORCE
833 $(call if_changed,copy)
834 endif
第 825 行判断 CONFIG_OF_SEPARATE 是否等于 y,如果相等,那条件就成立,在.config
中搜索“ CONFIG_OF_SEPARAT”,没有找到,说明条件不成立。
第 832 行就是目标 u-boot.bin 的规则,目标 u-boot.bin 依赖于 u-boot-nodtb.bin,命令为$(call
if_changed,copy) , 这 里 调 用 了 if_changed , if_changed 是 一 个 函 数 , 这 个 函 数 在
scripts/Kbuild.include 中有定义,而顶层 Makefile 中会包含 scripts/Kbuild.include 文件,这个前
面已经说过了。
if_changed 在 Kbuild.include 中的定义如下:
257 if_changed = $(if $(strip $(any-prereq) $(arg-check)), \
258 @set -e; \
259 $(echo-cmd) $(cmd_$(1)); \
260 printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd)
第 257 行就是函数 if_changed, if_changed 函数引用的变量比较多,也比较绕,我们只需要
知道它可以从 u-boot-nodtb.bin 生成 u-boot.bin 就行了。
既然 u-boot.bin 依赖于 u-boot-nodtb.bin,那么肯定要先生成 u-boot-nodtb.bin 文件,顶层
Makefile 中相关代码如下:
866 u-boot-nodtb.bin: u-boot FORCE
867 $(call if_changed,objcopy)
868 $(call DO_STATIC_RELA,$<,$@,$(CONFIG_SYS_TEXT_BASE))
869 $(BOARD_SIZE_CHECK)
目标 u-boot-nodtb.bin 又依赖于 u-boot,顶层 Makefile 中 u-boot 相关规则如下:
1170 u-boot: $(u-boot-init) $(u-boot-main) u-boot.lds FORCE
1171 $(call if_changed,u-boot__)
1172 ifeq ($(CONFIG_KALLSYMS),y)
1173 $(call cmd,smap)
1174 $(call cmd,u-boot__) common/system_map.o
1175 endif
目标 u-boot 依赖于 u-boot_init、 u-boot-main 和 u-boot.lds, u-boot_init 和 u-boot-main 是两个
变量,在顶层 Makefile 中有定义,值如下:
678 u-boot-init := $(head-y)
679 u-boot-main := $(libs-y)
$(head-y)跟 CPU 架构有关,我们使用的是 ARM 芯片,所以 head-y 在 arch/arm/Makefile 中
被指定为:
head-y := arch/arm/cpu/$(CPU)/start.o
根据 31.3.12 小节的分析,我们知道 CPU=armv7,因此 head-y 展开以后就是:
head-y := arch/arm/cpu/armv7/start.o
因此:
u-boot-init= arch/arm/cpu/armv7/start.o
$(libs-y)在顶层 Makefile 中被定义为 uboot 所有子目录下 build-in.o 的集合,代码如下:
620 libs-y += lib/
621 libs-$(HAVE_VENDOR_COMMON_LIB) += board/$(VENDOR)/common/
622 libs-$(CONFIG_OF_EMBED) += dts/
623 libs-y += fs/
624 libs-y += net/
625 libs-y += disk/
626 libs-y += drivers/
627 libs-y += drivers/dma/
628 libs-y += drivers/gpio/
629 libs-y += drivers/i2c/
......
660 libs-y += cmd/
661 libs-y += common/
662 libs-$(CONFIG_API) += api/
.....
676 libs-y := $(patsubst %/, %/built-in.o, $(libs-y))
从上面的代码可以看出, libs-y 都是 uboot 各子目录的集合,最后:
libs-y := $(patsubst %/, %/built-in.o, $(libs-y))
这里调用了函数 patsubst,将 libs-y 中的“ /”替换为”/built-in.o”,比如“ drivers/dma/”就变
为了“ drivers/dma/built-in.o”,相当于将 libs-y 改为所有子目录中 built-in.o 文件的集合。那么 uboot-main 就等于所有子目录中 built-in.o 的集合。
这个规则就相当于将以 u-boot.lds 为链接脚本,将 arch/arm/cpu/armv7/start.o 和各个子目录
下的 built-in.o 链接在一起生成 u-boot。
u-boot.lds 的规则如下:
u-boot.lds: $(LDSCRIPT) prepare FORCE
$(call if_changed_dep,cpp_lds)
接下来的重点就是各子目录下的 built-in.o 是怎么生成的,以 drivers/gpio/built-in.o 为例,在
drivers/gpio/目录下会有个名为.built-in.o.cmd 的文件,此文件内容如下:
示例代码 31.3.15.11 drivers/gpio/.built-in.o.cmd 代码
1 cmd_drivers/gpio/built-in.o := arm-linux-gnueabihf-ld.bfd -r -o
drivers/gpio/built-in.o drivers/gpio/mxc_gpio.o
从命令“ cmd_drivers/gpio/built-in.o”可以看出, drivers/gpio/built-in.o 这个文件是使用 ld 命
令由文件 drivers/gpio/mxc_gpio.o 生成而来的, mxc_gpio.o 是 mxc_gpio.c 编译生成的.o 文件,
这个是 NXP 的 I.MX 系列的 GPIO 驱动文件。这里用到了 ld 的“ -r”参数,参数含义如下:
-r –relocateable: 产生可重定向的输出,比如,产生一个输出文件它可再次作为‘ ld’ 的输
入,这经常被叫做“ 部分链接”,当我们需要将几个小的.o 文件链接成为一个.o 文件的时候,需
要使用此选项。
最终将各个子目录中的 built-in.o 文件链接在一起就形成了 u-boot,使用如下命令编译 uboot
就可以看到链接的过程:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_ddr512_emmc_
defconfig V=1
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- V=1
可以看出最终是用 arm-linux-gnueabihf-ld.bfd 命令将 arch/arm/cpu/armv7/start.o 和其他众多
的 built_in.o 链接在一起,形成 u-boot。
目标 all 除了 u-boot.bin 以外还有其他的依赖,比如 u-boot.srec 、u-boot.sym 、System.map、
u-boot.cfg 和 binary_size_check 等等,这些依赖的生成方法和 u-boot.bin 很类似,大家自行查看
一下顶层 Makefile,我们就不详细的讲解了。
【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.3.pdf