目录

  • U-Boot 工程目录分析
  • VScode 工程创建
  • U-Boot 顶层Makefile 分析
    • 版本号
    • MAKEFLAGS 变量
    • 命令输出
    • 静默输出
    • 设置编译结果输出目录
    • 代码检查
    • 模块编译
    • 获取主机架构和系统
    • 设置目标架构、交叉编译器和配置文件
    • 调用scripts/Kbuild.include
    • 交叉编译工具变量设置
    • 导出其他变量
    • make xxx_defconfig 过程
    • Makefile.build 脚本分析
    • make 过程

上一章我们详细的讲解了uboot 的使用方法,其实就是各种命令的使用,学会uboot 使用以后就可以尝试移植uboot 到自己的开发板上了,但是在移植之前需要我们得先分析一遍uboot的启动流程源码,得捋一下uboot 的启动流程,否则移植的时候都不知道该修改那些文件。本章我们就来分析一下正点原子提供的uboot 源码,重点是分析uboot 启动流程,而不是整个uboot源码,uboot 整个源码非常大,我们只看跟我们关心的部分即可。

U-Boot 工程目录分析

本书我们以EMMC 版本的核心板为例讲解,为了方便,uboot 启动源码分析就在Windows下进行,将正点原子提供的uboot 源码进行解压,解压完成以后的目录如图31.1.1 所示:

图31.1.1 未编译的uboot
图31.1.1 是正点原子提供的未编译的uboot 源码目录,我们在分析uboot 源码之前一定要先在Ubuntu 中编译一下uboot 源码,因为编译过程会生成一些文件,而生成的这些恰恰是分析uboot 源码不可或缺的文件。使用上一章创建的shell 脚本来完成编译工作,命令如下:

cd alientek_uboot //进入正点原子uboot 源码目录
./mx6ull_alientek_emmc.sh //编译uboot
cd ../ //返回上一级目录
tar -vcjf alientek_uboot.tar.bz2 alientek_uboot //压缩

最终会生成一个名为alientek_uboot.tar.bz2 的压缩包,将alientek_uboot.tar.bz2 拷贝到windows系统中并解压,解压后的目录如图31.1.2 所示:

图31.1.2 编译后的uboot 源码文件
对比图31.1.2 和图31.1.1,可以看出编译后的uboot 要比没编译之前多了好多文件,这些文件夹或文件的含义见表31.1.1 所示:


表31.1.1 uboot 目录列表
表31.1.1 中的很多文件夹和文件我们都不需要去关心,我们要关注的文件夹或文件如下:
1、arch 文件夹
这个文件夹里面存放着和架构有关的文件,如图31.1.3 所示:

图31.1.3 arch 文件夹
从图31.1.3 可以看出有很多架构,比如arm、avr32、m68k 等,我们现在用的是ARM 芯片,所以只需要关心arm 文件夹即可,打开arm 文件夹里面内容如图31.1.4 所示:

图31.1.4 arm 文件夹
图31.1.4 只截取了一部分,还有一部分mach-xxx 的文件夹。mach 开头的文件夹是跟具体的设备有关的,比如“mach-exynos”就是跟三星的exyons 系列CPU 有关的文件。我们使用的是I.MX6ULL,所以要关注“imx-common”这个文件夹。另外“cpu”这个文件夹也是和cpu 架构有关的,打开以后如图31.1.5 所示:

图31.1.5 cpu 文件夹
从图31.1.5 可以看出有多种ARM 架构相关的文件夹,I.MX6ULL 使用的Cortex-A7 内核,
Cortex-A7 属于armv7,所以我们要关心“armv7”这个文件夹。cpu 文件夹里面有个名为“u-boot.lds”的链接脚本文件,这个就是ARM 芯片所使用的u-boot 链接脚本文件!armv7 这个文件夹里面的文件都是跟ARMV7 架构有关的,是我们分析uboot 启动源码的时候需要重点关注的。
2、board 文件夹
board 文件夹就是和具体的板子有关的,打开此文件夹,里面全是不同的板子,毫无疑问正点原子的开发板肯定也在里面(正点原子添加的),borad 文件夹里面有个名为“freescale”的文
件夹,如图31.1.6 所示:

图31.1.6 freescale 文件夹
所有使用freescale 芯片的板子都放到此文件夹中,I.MX 系列以前属于freescale,只是
freescale 后来被NXP 收购了。打开此freescale 文件夹,在里面找到和mx6u(I.MX6UL/ULL)有关的文件夹,如图31.1.7 所示:

图31.1.7 mx6u 相关板子
图31.1.7 中有5 个文件夹,这5 个文件夹对应5 种板子,以“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 官方开发板和正点原子的开发板配置文件肯定也在这个文件夹中,如图31.1.8 所示:

图31.1.8 正点原子开发板配置文件
图31.1.8 中这6 个文件就是正点原子I.MX6U-ALPHA 开发板所对应的uboot 默认配置文件。我们只关心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!在mx6ull_alientek_emmc.sh 中就有下面这一句:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_ddr512_emmc_
defconfig

这个就是调用mx6ull_14x14_ddr512_emmc_defconfig 来配置uboot,只是这个命令还带了一些其它参数而已。
4、.u-boot.xxx_cmd 文件
.u-boot.xxx_cmd 是一系列的文件,这些文件都是编译生成的,都是一些命令文件,比如文件.u-boot.bin.cmd,看名字应该是和u-boot.bin 有关的,此文件的内容如下:

示例代码31.1.1 .u-boot.bin.cmd 代码
1 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 就是用于生成u-boot.nodtb.bin 的,此文件内容如下:

示例代码31.1.2 .u-boot-nodtb.bin.cmd 代码
1 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

这里用到了arm-linux-gnueabihf-objcopy,使用objcopy 将ELF 格式的u-boot 文件转换为二进制的u-boot-nodtb.bin 文件。
文件u-boot 是ELF 格式的文件,文件.u-boot.cmd 用于生成u-boot,文件内容如下:

示例代码31.1.3 .u-boot.cmd 代码
1 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/built-in.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/built-in.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-linux-gnueabihf/4.9.4 -lgcc -Map u-boot.map

.u-boot.cmd 使用到了arm-linux-gnueabihf-ld.bfd,也就是链接工具,使用ld.bfd 将各个built-in.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 来完成的,此文件内容如下:
示例代码31.1.4 .u-boot.imx.cmd 代码

1 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文件。
还有一些其它的.u-boot.lds.xxx.cmd 文件,大家自行分析一下,关于.u-boot.lds.xxx.cmd 文件就讲解到这里。
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 内容如下:

示例代码31.1.5 .config 代码
1 #
2 # Automatically generated file; DO NOT EDIT.
3 # U-Boot 2016.03 Configuration
4 #
5 CONFIG_CREATE_ARCH_SYMLINK=y
6 CONFIG_HAVE_GENERIC_BOARD=y
7 CONFIG_SYS_GENERIC_BOARD=y
8 # CONFIG_ARC is not set
9 CONFIG_ARM=y
10 # CONFIG_AVR32 is not set
11 # CONFIG_BLACKFIN is not set
12 # CONFIG_M68K is not set
13 # CONFIG_MICROBLAZE is not set
14 # CONFIG_MIPS is not set
15 # CONFIG_NDS32 is not set
16 # CONFIG_NIOS2 is not set
17 # CONFIG_OPENRISC is not set
18 # CONFIG_PPC is not set
19 # CONFIG_SANDBOX is not set
20 # CONFIG_SH is not set
21 # CONFIG_SPARC is not set
22 # CONFIG_X86 is not set
23 CONFIG_SYS_ARCH="arm"
24 CONFIG_SYS_CPU="armv7"
25 CONFIG_SYS_SOC="mx6"
26 CONFIG_SYS_VENDOR="freescale"
27 CONFIG_SYS_BOARD="mx6ull_alientek_emmc"
28 CONFIG_SYS_CONFIG_NAME="mx6ull_alientek_emmc"
29
30 ......
31
32 #
33 # Boot commands
34 #
35 CONFIG_CMD_BOOTD=y
36 CONFIG_CMD_BOOTM=y
37 CONFIG_CMD_ELF=y
38 CONFIG_CMD_GO=y
39 CONFIG_CMD_RUN=y
40 CONFIG_CMD_IMI=y
41 CONFIG_CMD_IMLS=y
42 CONFIG_CMD_XIMG=y
43
44 #
45 # Environment commands
46 #
47 CONFIG_CMD_EXPORTENV=y
48 CONFIG_CMD_IMPORTENV=y
49 CONFIG_CMD_EDITENV=y
50 CONFIG_CMD_SAVEENV=y
51 CONFIG_CMD_ENV_EXISTS=y
52
53 ......
54
55 #
56 # Library routines
57 #
58 # CONFIG_CC_OPTIMIZE_LIBS_FOR_SPEED is not set
59 CONFIG_HAVE_PRIVATE_LIBGCC=y
60 # CONFIG_USE_PRIVATE_LIBGCC is not set
61 CONFIG_SYS_HZ=1000
62 # CONFIG_USE_TINY_PRINTF is not set
63 CONFIG_REGEX=y

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

CONFIG_CMD_BOOTM=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 根目录中的文件和文件夹的含义就讲解到这里,接下来就要开始分析uboot 的启动流程了。

VScode 工程创建

先在Ubuntu 下编译一下uboot,然后将编译后的uboot 文件夹复制到windows 下,并创建VScode 工程。打开VScode,选择:文件->打开文件夹…,选中uboot 文件夹,如图31.2.1 所示:

图31.2.1 选择uboot 源码文件夹
打开uboot 目录以后,VSCode 界面如图31.2.2 所示:

图31.2.2 VScode 界面
点击“文件->将工作区另存为…”,打开保存工作区对话框,将工作区保存到uboot 源码根目录下,设置文件名为“uboot”,如图31.2.3 所示:

图31.2.3 保存工作区
保存成功以后就会在uboot 源码根目录下存在一个名为uboot.code-workspace 的文件。这样一个完整的VSCode 工程就建立起来了。但是这个VSCode 工程包含了uboot 的所有文件,uboot中有些文件是不需要的,比如arch 目录下是各种架构的文件夹,如图31.2.4 所示:

图31.2.4 arch 目录
在arch 目录下,我们只需要arm 文件夹,所以需要将其它的目录从VSCode 中给屏蔽掉,比如将arch/avr32 这个目录给屏蔽掉。
在VSCode 上建名为“.vscode”的文件夹,如图31.2.5 所示:

图31.2.5 新建.vscode 文件夹
输入新建文件夹的名字,完成以后如图31.2.6 所示。

图31.2.6 新建的.vscode 文件夹
在.vscode 文件夹中新建一个名为“settings.json”的文件,然后在settings.json 中输入如下内容:

示例代码31.2.1settings.json 文件代码
1 {
2 "search.exclude": {
3 "**/node_modules": true,
4 "**/bower_components": true,
5 },
6 "files.exclude": {
7 "**/.git": true,
8 "**/.svn": true,
9 "**/.hg": true,
10 "**/CVS": true,
11 "**/.DS_Store": true,
12 }
13 }

结果如图31.2.7 所示:

图31.2.7 settings.json 文件默认内容
其中"search.exclude"里面是需要在搜索结果中排除的文件或者文件夹,"files.exclude"是左侧工程目录中需要排除的文件或者文件夹。我们需要将arch/avr32 文件夹下的所有文件从搜索结果和左侧的工程目录中都排除掉,因此在"search.exclude"和"files.exclude"中输入如图31.2.8 所示内容:

保存一下settings.json 文件,然后再看一下左侧的工程目录,发现arch 目录下没有avr32 这个文件夹了,说明avr32 这个文件夹被排除掉了,如图31.2.9 所示:

图31.2.9 arch/avr32 目录排除
我们只是在"search.exclude"和"files.exclude"中加入了:“arch/avr32”: true,冒号前面的是要排除的文件或者文件夹,冒号后面为是否将文件排除,true 表示排除,false 表示不排除。用这种方法即可将不需要的文件,或者文件夹排除掉,对于本章我们分析uboot 而言,在"search.exclude"和"files.exclude"中需要输入的完成的内容如下:

示例代码31.2.2 settings.json 文件代码
1 "**/*.o":true,
2 "**/*.su":true,
3 "**/*.cmd":true,
4 "arch/arc":true,
5 "arch/avr32":true,
6 "arch/blackfin":true,
7 "arch/m68k":true,
8 "arch/microblaze":true,
9 "arch/mips":true,
10 "arch/nds32":true,
11 "arch/nios2":true,
12 "arch/openrisc":true,
13 "arch/powerpc":true,
14 "arch/sandbox":true,
15 "arch/sh":true,
16 "arch/sparc":true,
17 "arch/x86":true,
18 "arch/arm/mach*":true,
19 "arch/arm/cpu/arm11*":true,
20 "arch/arm/cpu/arm720t":true,
21 "arch/arm/cpu/arm9*":true,
22 "arch/arm/cpu/armv7m":true,
23 "arch/arm/cpu/armv8":true,
24 "arch/arm/cpu/pxa":true,
25 "arch/arm/cpu/sa1100":true,
26 "board/[a-e]*":true,
27 "board/[g-z]*":true,
28 "board/[0-9]*":true,
29 "board/[A-Z]*":true,
30 "board/fir*":true,
31 "board/freescale/b*":true,
32 "board/freescale/l*":true,
33 "board/freescale/m5*":true,
34 "board/freescale/mp*":true,
35 "board/freescale/c29*":true,
36 "board/freescale/cor*":true,
37 "board/freescale/mx7*":true,
38 "board/freescale/mx2*":true,
39 "board/freescale/mx3*":true,
40 "board/freescale/mx5*":true,
41 "board/freescale/p*":true,
42 "board/freescale/q*":true,
43 "board/freescale/t*":true,
44 "board/freescale/v*":true,
45 "configs/[a-l]*":true,
46 "configs/[n-z]*":true,
47 "configs/[A-Z]*":true,
48 "configs/M[a-z]*":true,
49 "configs/M[A-Z]*":true,
50 "configs/M[0-9]*":true,
51 "configs/m[a-w]*":true,
52 "configs/m[0-9]*":true,
53 "configs/[0-9]*":true,
54 "include/configs/[a-l]*":true,
55 "include/configs/[n-z]*":true,
56 "include/configs/[A-Z]*":true,
57 "include/configs/m[a-w]*":true,

上述代码用到了通配符“”,比如“**/.o”表示所有.o 结尾的文件。“configs/[a-l]*”表示configs 目录下所有以‘a’~‘l’开头的文件或者文件夹。上述配置只是排除了一部分文件夹,大家在实际的使用中可以根据自己的实际需求来选择将哪些文件或者文件夹排除掉。排除以后我们的工程就会清爽很多,搜索的时候也不会跳出很多文件了。

U-Boot 顶层Makefile 分析

在阅读uboot 源码之前,肯定是要先看一下顶层Makefile,分析gcc 版本代码的时候一定是先从顶层Makefile 开始的,然后再是子Makefile,这样通过层层分析Makefile 即可了解整个工程的组织结构。顶层Makefile 也就是uboot 根目录下的Makefile 文件,由于顶层Makefile 文件内容比较多,所以我们将其分开来看。

版本号

顶层Makefile 一开始是版本号,内容如下(为了方便分析,顶层Makefile 代码段前段行号采用Makefile 中的行号,因为uboot 会更新,因此行号可能会与你所看的顶层Makefile 有所不同):

示例代码31.3.1.1 顶层Makefile 代码
5 VERSION = 2016
6 PATCHLEVEL = 03
7 SUBLEVEL =
8 EXTRAVERSION =
9 NAME =

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

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

命令输出

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

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

图31.3.3.2 终端完整命令输出
顶层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_”的话,整个命令都不会输出。

静默输出

上一小节讲了,设置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,判断 ( f i l t e r 4. (filter 4.%, (filter4.(MAKE_VERSION))和“”( 空) 是否相等,如果不相等的话就成立,执行里面的语句。也就是说 ( f i l t e r 4. (filter 4.%, (filter4.(MAKE_VERSION))不为空的话条件就成立,这里用到了Makefile 中的filter 函数,这是个过滤函数,函数格式如下:

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

filter 函数表示以pattern 模式过滤text 字符串中的单词,仅保留符合模式pattern 的单词,可以有多个模式。函数返回值就是符合pattern 的字符串。因此 ( f i l t e r 4. (filter4.%, (filter4.(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。在顶层Makfile 中添加如图31.3.4.1 所示的代码:

图31.3.4.1 顶层Makefile 添加代码
图31.3.4.1 中的两行代码用于输出 ( f i r s t w o r d x (firstword x (firstwordx(MAKEFLAGS))的结果,最后修改文件mx6ull_alientek_emmc.sh,在里面加入“-s”选项,结果如图31.3.4.2 所示:

图31.3.4.2 加入-s 选项
修改完成以后执行mx6ull_alientek_emmc.sh,结果如图31.3.4.3 所示:

图31.3.4.3 修改顶层Makefile 后的执行结果
从图31.3.4.3 可以看出第一个单词是“xrRs”,将 ( f i l t e r (filter %s , (filter(firstword x ( M A K E F L A G S ) ) ) 展开就是 (MAKEFLAGS))) 展开就是 (MAKEFLAGS)))展开就是(filter %s, xrRs),而$(filter %s, xrRs)的返回值肯定不为空,条件成立,quiet=silent_。
第101 行使用export 导出变量quiet、Q 和KBUILD_VERBOSE。

设置编译结果输出目录

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

示例代码31.3.5.1 顶层Makefile 代码
103 # kbuild supports saving output files in a separate directory.
104 # To locate output files in a separate directory two syntaxes are supported.
105 # In both cases the working directory must be the root of the kernel src.
106 # 1) O=
107 # Use "make O=dir/to/store/output/files/"
108 #
109 # 2) Set KBUILD_OUTPUT
110 # Set the environment variable KBUILD_OUTPUT to point to the directory
111 # where the output files shall be placed.
112 # export KBUILD_OUTPUT=dir/to/store/output/files/
113 # make
114 #
115 # The O= assignment takes precedence over the KBUILD_OUTPUT environment
116 # variable.
117
118 # KBUILD_SRC is set on invocation of make in OBJ directory
119 # KBUILD_SRC is not intended to be used by the regular user (for now)
120 ifeq ($(KBUILD_SRC),)
121
122 # OK, Make called in directory where kernel src resides
123 # Do we want to locate output files in a separate directory?
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)
......
155 endif # ifneq ($(KBUILD_OUTPUT),)
156 endif # ifeq ($(KBUILD_SRC),)

第124 行判断“O”是否来自于命令行,如果来自命令行的话条件成立,KBUILD_OUTPUT就为$(O),因此变量KBUILD_OUTPUT 就是输出目录。
第135 行判断KBUILD_OUTPUT 是否为空。
第139 行调用mkdir 命令,创建KBUILD_OUTPUT 目录,并且将创建成功以后的绝对路径赋值给KBUILD_OUTPUT。至此,通过O 指定的输出目录就存在了。

代码检查

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

示例代码31.3.6.1 顶层Makefile 代码
166 # Call a source code checker (by default, "sparse") as part of the
167 # C compilation.
168 #
169 # Use 'make C=1' to enable checking of only re-compiled files.
170 # Use 'make C=2' to enable checking of *all* source files, regardless
171 # of whether they are re-compiled or not.
172 #
173 # See the file "Documentation/sparse.txt" for more details, including
174 # where to get the "sparse" utility.
175
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。

模块编译

在uboot 中允许单独编译某个模块,使用命令“make M=dir”即可,旧语法“make SUBDIRS=dir”也是支持的。顶层Makefile 中的代码如下:

示例代码31.3.7.1 顶层Makefile 代码
183 # Use make M=dir to specify directory of external module to build
184 # Old syntax make ... SUBDIRS=$PWD is still supported
185 # Setting the environment variable KBUILD_EXTMOD take precedence
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。

获取主机架构和系统

接下来顶层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”获取架构名称,结果如图31.3.8.1 所示:

图31.3.8.1 uname -m 命令
从图31.3.8.1 可以看出当前电脑主机架构为“x86_64”,shell 中的“|”表示管道,意思是将左边的输出作为右边的输入,sed -e 是替换命令,“sed -e s/i.86/x86/”表示将管道输入的字符串中的“i.86”替换为“x86”,其他的“sed -e s”命令同理。对于我的电脑而言,HOSTARCH=x86_64。
第237 行定义了变量HOSTOS,此变量用于保存主机OS 的值,先使用shell 命令“uname -s”来获取主机OS,结果如图31.3.8.2 所示:

图31.3.8.2 uname -s 命令
从图31.3.8.2 可以看出此时的主机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。

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

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

示例代码31.3.9.1 顶层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-。从示例代码31.3.9.1 可以看出,每次编译uboot 的时候都要在make 命令后面设置ARCH 和CROSS_COMPILE,使用起来很麻烦,可以直接修改顶层Makefile,在里面加入ARCH和CROSS_COMPILE 的定义,如图31.3.9.1 所示:

图31.3.9.1 定义ARCH 和CROSS_COMPILE

按照图31.3.9.1 所示,直接在顶层Makefile 里面定义ARCH 和CROSS_COMPILE,这样就不用每次编译的时候都要在make 命令后面定义ARCH 和CROSS_COMPILE。
继续回到示例代码31.3.9.1 中,第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 里面的才是实时有效的配置。

调用scripts/Kbuild.include

主Makefile 会调用文件scripts/Kbuild.include 这个文件,顶层Makefile 中代码如下:

示例代码31.3.10.1 顶层Makefile 代码
327 # We need some generic definitions (do not try to remake the file).
328 scripts/Kbuild.include: ;
329 include scripts/Kbuild.include

示例代码31.3.10.1 中使用“include”包含了文件scripts/Kbuild.include,此文件里面定义了很多变量,如图31.3.10.1 所示:

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

交叉编译工具变量设置

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

示例代码31.3.11.1 顶层Makefile 代码
331 # Make variables (CC, etc...)
332
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 会导出很多变量,代码如下:

示例代码31.3.12.1 顶层Makefile 代码
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 个变量都是什么内容,在顶层Makefile 中输入如图31.3.12.1 所示的内容:

图31.3.12.1 输出变量值
修改好顶层Makefile 以后执行如下命令:

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

结果如图31.3.12.2 所示:

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

示例代码31.3.12.2 config.mk 代码
1 #
2 # (C) Copyright 2000-2013
3 # Wolfgang Denk, DENX Software Engineering, wd@denx.de.
4 #
5 # SPDX-License-Identifier: GPL-2.0+
6 #
7 #########################################################################
8
9 # This file is included from ./Makefile and spl/Makefile.
10 # Clean the state to avoid the same flags added twice.
11 #
12 # (Tegra needs different flags for SPL.
13 # That's the reason why this file must be included from spl/Makefile too.
14 # If we did not have Tegra SoCs, build system would be much simpler...)
15 PLATFORM_RELFLAGS :=
16 PLATFORM_CPPFLAGS :=
17 PLATFORM_LDFLAGS :=
18 LDFLAGS :=
19 LDFLAGS_FINAL :=
20 OBJCOPYFLAGS :=
21 # clear VENDOR for tcsh
22 VENDOR :=
23 #########################################################################
24
25 ARCH := $(CONFIG_SYS_ARCH:"%"=%)
26 CPU := $(CONFIG_SYS_CPU:"%"=%)
27 ifdef CONFIG_SPL_BUILD
28 ifdef CONFIG_TEGRA
29 CPU := arm720t
30 endif
31 endif
32 BOARD := $(CONFIG_SYS_BOARD:"%"=%)
33 ifneq ($(CONFIG_SYS_VENDOR),)
34 VENDOR := $(CONFIG_SYS_VENDOR:"%"=%)
35 endif
36 ifneq ($(CONFIG_SYS_SOC),)
37 SOC := $(CONFIG_SYS_SOC:"%"=%)
38 endif
39
40 # Some architecture config.mk files need to know what CPUDIR is set to,
41 # so calculate CPUDIR before including ARCH/SOC/CPU config.mk files.
42 # Check if arch/$ARCH/cpu/$CPU exists, otherwise assume arch/$ARCH/cpu contains
43 # CPU-specific code.
44 CPUDIR=arch/$(ARCH)/cpu$(if $(CPU),/$(CPU),)
45
46 sinclude $(srctree)/arch/$(ARCH)/config.mk
47 sinclude $(srctree)/$(CPUDIR)/config.mk
48
49 ifdef SOC
50 sinclude $(srctree)/$(CPUDIR)/$(SOC)/config.mk
51 endif
52 ifneq ($(BOARD),)
53 ifdef VENDOR
54 BOARDDIR = $(VENDOR)/$(BOARD)
55 else
56 BOARDDIR = $(BOARD)
57 endif
58 endif
59 ifdef BOARD
60 sinclude $(srctree)/board/$(BOARDDIR)/config.mk # include board specific rules
61 endif
62
63 ifdef FTRACE
64 PLATFORM_CPPFLAGS += -finstrument-functions -DFTRACE
65 endif
66
67 # Allow use of stdint.h if available
68 ifneq ($(USE_STDINT),)
69 PLATFORM_CPPFLAGS += -DCONFIG_USE_STDINT
70 endif
71
72 #########################################################################
73
74 RELFLAGS := $(PLATFORM_RELFLAGS)
75
76 PLATFORM_CPPFLAGS += $(RELFLAGS)
77 PLATFORM_CPPFLAGS += -pipe
78
79 LDFLAGS += $(PLATFORM_LDFLAGS)
80 LDFLAGS_FINAL += -Bstatic
81
82 export PLATFORM_CPPFLAGS
83 export RELFLAGS
84 export LDFLAGS_FINAL

第25 行定义变量ARCH ,值为 ( C O N F I G S Y S A R C H : " 第 26 行定义变量 C P U ,值为 (CONFIG_SYS_ARCH:"%"=%) ,也就是提取CONFIG_SYS_ARCH 里面双引号“”之间的内容。比如CONFIG_SYS_ARCH=“arm”的话,ARCH=arm。 第26 行定义变量CPU,值为 (CONFIGSYSARCH:"26行定义变量CPU,值为(CONFIG_SYS_CPU:“%”=%)。
第32 行定义变量BOARD,值为(CONFIG_SYS_BOARD:“%”=%)。
第34 行定义变量VENDOR,值为 ( C O N F I G S Y S V E N D O R : " 第 37 行定义变量 S O C ,值为 (CONFIG_SYS_VENDOR:"%"=%)。 第37 行定义变量SOC,值为 (CONFIGSYSVENDOR:"37行定义变量SOC,值为(CONFIG_SYS_SOC:“%”=%)。
第44 行定义变量CPUDIR,值为arch/ ( A R C H ) / c p u (ARCH)/cpu (ARCH)/cpu(if ( C P U ) , / (CPU),/ (CPU),/(CPU),)。
第46 行sinclude 和include 的功能类似,在Makefile 中都是读取指定文件内容,这里读取文$ (srctree)/arch/ ( A R C H ) / c o n f i g . m k 的内容。 s i n c l u d e 读取的文件如果不存在的话不会报错。第 47 行读取文件 (ARCH)/config.mk 的内容。sinclude 读取的文件如果不存在的话不会报错。 第47 行读取文件 (ARCH)/config.mk的内容。sinclude读取的文件如果不存在的话不会报错。第47行读取文件(srctree)/ ( C P U D I R ) / c o n f i g . m k 的内容。第 50 行读取文件 (CPUDIR)/config.mk 的内容。 第50 行读取文件 (CPUDIR)/config.mk的内容。第50行读取文件(srctree)/ ( C P U D I R ) / (CPUDIR)/ (CPUDIR)/(SOC)/config.mk 的内容。
第54 行定义变量BOARDDIR ,如果定义了VENDOR 那么BOARDDIR= ( V E N D O R ) / (VENDOR)/ (VENDOR)/(BOARD),否则的BOARDDIR= ( B O A R D ) 。第 60 行读取文件 (BOARD)。 第60 行读取文件 (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 (此文件不存在)

make xxx_defconfig 过程

在编译uboot 之前要使用“make xxx_defconfig”命令来配置uboot,那么这个配置过程是如何运行的呢?在顶层Makefile 中有如下代码:

示例代码31.3.13.1 顶层Makefile 代码段
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 内容如图31.3.13.1 所示:

图31.3.13.1 版本号文件
第423 行定义了变量timestamp_h,此变量保存时间戳文件,此文件也是自动生成的。文件
include/generated/timestamp_autogenerated.h 内容如图31.3.13.2 所示:

图31.3.13.2 时间戳文件
第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。当FORCE 作为其他目标的依赖时,由于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

配置过程如图31.3.13.1 所示:

图31.3.13.1 uboot 配置过程
从图31.3.13.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

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),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
59 include $(kbuild-file)

将kbuild-dir 展开后为:

$(if $(filter /%, scripts/basic), scripts/basic, ./scripts/basic)

因为没有以“/ ”为开头的单词,所以$(filter /%, scripts/basic) 的结果为空,kbuild-dir=./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,如下代码:

示例代码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 中输入图31.3.14.1 所示内容,将这5 个变量的值打印出来:

图31.3.14.1 输出变量
执行如下命令:

make mx6ull_14x14_ddr512_emmc_defconfig V=1

结果如图31.3.14.2 所示:

图31.3.14.2 输出结果
从上图可以看出,只有always 有效,因此__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 这种主机所使用的工具类软件我们一般不关心它是如何编译产生的。如果一定要看是conf 是怎么生成的,可以输入如下命令重新配置uboot,在重新配置uboot 的过程中就会输出conf 编译信息。

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

结果如图31.3.14.3 所示:

图31.3.14.3 编译过程
得到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 执行流程,总结一下如图31.3.14.4 所示:

图31.3.14.4 make xxx_defconfig 执行流程图
至此,make xxx_defconfig 就分析完了,接下来就要分析一下u-boot.bin 是怎么生成的了。

make 过程

配置好uboot 以后就可以直接make 编译了,因为没有指明目标,所以会使用默认目标,主Makefile 中的默认目标如下:

示例代码31.3.15.1 顶层Makefile 代码段
128 # That's our default target when none is given on the command line
129 PHONY := _all
130 _all:

目标_all 又依赖于all,如下所示:

示例代码31.3.15.2 顶层Makefile 代码段
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

如果KBUILD_EXTMOD 为空的话_all 依赖于all 。这里不编译模块,所以KBUILD_EXTMOD 肯定为空,_all 的依赖就是all。在主Makefile 中all 目标规则如下:

示例代码31.3.15.2 顶层Makefile 代码段
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 如下:

示例代码31.3.15.3 顶层Makefile 代码段
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
746 ALL-$(CONFIG_OF_SEPARATE) += u-boot.dtb
747 ifeq ($(CONFIG_SPL_FRAMEWORK),y)
748 ALL-$(CONFIG_OF_SEPARATE) += u-boot-dtb.img
749 endif
750 ALL-$(CONFIG_OF_HOSTFILE) += u-boot.dtb
751 ifneq ($(CONFIG_SPL_TARGET),)
752 ALL-$(CONFIG_SPL) += $(CONFIG_SPL_TARGET:"%"=%)
753 endif
754 ALL-$(CONFIG_REMAKE_ELF) += u-boot.elf
755 ALL-$(CONFIG_EFI_APP) += u-boot-app.efi
756 ALL-$(CONFIG_EFI_STUB) += u-boot-payload.efi
757
758 ifneq ($(BUILD_ROM),)
759 ALL-$(CONFIG_X86_RESET_VECTOR) += u-boot.rom
760 endif
761
762 # enable combined SPL/u-boot/dtb rules for tegra
763 ifeq ($(CONFIG_TEGRA)$(CONFIG_SPL),yy)
764 ALL-y += u-boot-tegra.bin u-boot-nodtb-tegra.bin
765 ALL-$(CONFIG_OF_SEPARATE) += u-boot-dtb-tegra.bin
766 endif
767
768 # Add optional build target if defined in board/cpu/soc headers
769 ifneq ($(CONFIG_BUILD_TARGET),)
770 ALL-y += $(CONFIG_BUILD_TARGET:"%"=%)
771 endif

从示例代码代码31.3.15.3 可以看出,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 目标对应的规则,如下所示:

示例代码31.3.15.4 顶层Makefile 代码段
825 ifeq ($(CONFIG_OF_SEPARATE),y)
826 u-boot-dtb.bin: u-boot-nodtb.bin dts/dt.dtb FORCE
827 $(call if_changed,cat)
828
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 中的定义如下:

示例代码31.3.15.5 Kbuild.include 代码段
226 ###
227 # if_changed - execute command if any prerequisite is newer than
228 # target, or command line has changed
229 # if_changed_dep - as if_changed, but uses fixdep to reveal
230 # dependencies including used config symbols
231 # if_changed_rule - as if_changed but execute rule instead
232 # See Documentation/kbuild/makefiles.txt for more info
233
234 ifneq ($(KBUILD_NOCMDDEP),1)
235 # Check if both arguments has same arguments. Result is empty string if equal.
236 # User may override this check using make KBUILD_NOCMDDEP=1
237 arg-check = $(strip $(filter-out $(cmd_$(1)), $(cmd_$@)) \
238 $(filter-out $(cmd_$@), $(cmd_$(1))) )
239 else
240 arg-check = $(if $(strip $(cmd_$@)),,1)
241 endif
242
243 # Replace >$< with >$$< to preserve $ when reloading the .cmd file
244 # (needed for make)
245 # Replace >#< with >\#< to avoid starting a comment in the .cmd file
246 # (needed for make)
247 # Replace >'< with >'\''< to be able to enclose the whole string in '...'
248 # (needed for the shell)
249 make-cmd = $(call escsq,$(subst \#,\\\#,$(subst $$,$$$$,$(cmd_$(1)))))
250
251 # Find any prerequisites that is newer than target or that does not exist.
252 # PHONY targets skipped in both cases.
253 any-prereq = $(filter-out $(PHONY),$?) $(filter-out $(PHONY) $(wildcard $^),$^)
254
255 # Execute command if command has changed or prerequisite(s) are updated.
256 #
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)
261

第227 行为if_changed 的描述,根据描述,在一些先决条件比目标新的时候,或者命令行有改变的时候,if_changed 就会执行一些命令。
第257 行就是函数if_changed,if_changed 函数引用的变量比较多,也比较绕,我们只需要知道它可以从u-boot-nodtb.bin 生成u-boot.bin 就行了。
既然u-boot.bin 依赖于u-boot-nodtb.bin,那么肯定要先生成u-boot-nodtb.bin 文件,顶层Makefile 中相关代码如下:

示例代码31.3.15.6 顶层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 相关规则如下:

示例代码31.3.15.7 顶层Makefile 代码段
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 中有定义,值如下:

示例代码31.3.15.8 顶层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 的集合,代码如下:

示例代码31.3.15.9 顶层Makefile 代码段
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/
663 libs-$(CONFIG_HAS_POST) += post/
664 libs-y += test/
665 libs-y += test/dm/
666 libs-$(CONFIG_UT_ENV) += test/env/
667
668 libs-y += $(if $(BOARDDIR),board/$(BOARDDIR)/)
669
670 libs-y := $(sort $(libs-y))
671
672 u-boot-dirs := $(patsubst %/,%,$(filter %/, $(libs-y))) tools examples
673
674 u-boot-alldirs := $(sort $(u-boot-dirs) $(patsubst %/,%,$(filter %/, $(libs-))))
675
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 文件的集合。那么u-boot-main 就等于所有子目录中built-in.o 的集合。
这个规则就相当于将以u-boot.lds 为链接脚本,将arch/arm/cpu/armv7/start.o 和各个子目录下的built-in.o 链接在一起生成u-boot。
u-boot.lds 的规则如下:

示例代码31.3.15.10 顶层Makefile 代码段
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

编译的时候会有如图31.3.15.1 所示内容输出:

图31.3.15.1 编译内容输出
将其整理一下,内容如下:

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/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-linux-gnueabihf/4.9.4 -lgcc -Map u-boot.map

可以看出最终是用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,我们就不详细的讲解了。
总结一下“make”命令的流程,如图31.3.15.2 所示:

图31.3.15.2 make 命令流程
图31.3.15.2 就是“make”命令的执行流程,关于uboot 的顶层Makefile 就分析到这里,重点是“make xxx_defconfig”和“make”这两个命令的执行流程:
make xxx_defconfig:用于配置uboot,这个命令最主要的目的就是生成.config 文件。
make:用于编译uboot,这个命令的主要工作就是生成二进制的u-boot.bin 文件和其他的一些与uboot 有关的文件,比如u-boot.imx 等等。
关于uboot 的顶层Makefile 就分析到这里,有些内容我们没有详细、深入的去研究,因为我们的重点是使用uboot,而不是uboot 的研究者,我们要做的是缕清uboot 的流程。至于更具体的实现,有兴趣的可以参考一下其他资料。

U-Boot 顶层Makefile 详解相关推荐

  1. 【正点原子Linux连载】第三十五章 Linux内核顶层Makefile详解 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  2. linux内核顶层Makefile详解

    文章目录 一.linux内核获取 二.linux内核初次编译 三.linux工程目录分析 1.获取源码 2.目录介绍 1.总体浏览 2.arch 目录 3.block 目录 4.crypto 目录 5 ...

  3. Linux 内核顶层 Makefile 详解

    Linux 内核获取 Linux 由 Linux 基金会管理与发布, Linux 官网为 https://www.kernel.org,所以你想获取最新的 Linux 版本就可以在这个网站上下载 最新 ...

  4. 【正点原子Linux连载】第三十一章 U-Boot顶层Makefile详解 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  5. U-Boot 顶层 Makefile 详解

    U-Boot 顶层 Makefile 详解 1.U-Boot 工程目录分析 我们在分析 uboot 源码之前一定要 先在 Ubuntu 中编译一下 uboot 源码,因为编译过程会生成一些文件,而生成 ...

  6. Linux 内核顶层Makefile 详解

    目录 前602行分析 make xxx_defconfig 过程 Makefile.build 脚本分析 make 过程 built-in.o 文件编译生成过程 make zImage 过程 前几章我 ...

  7. linux .pc文件make,简单的驱动makefile详解

    简单的驱动makefile详解 一个工程中的源文件不计数,其按类型.功能.模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编 ...

  8. Linux驱动程序Makefile详解

    驱动程序Makefile详解 在写驱动模块的Makefile是通常都是找一个现成的Makefile修改一下就可以了,该文章主要是为了弄清楚驱动程序的Makefile的原理. 例:一个简单的hello内 ...

  9. Makefile详解(一)-介绍及总述

    以下内容为转载:http://zhan.renren.com/ilinux?from=template   Makefile详解 序       编译时,编译器需要的是语法的正确,函数与变量的声明的正 ...

  10. SpringBoot2.1.5(16)--- Spring Boot的日志详解

    SpringBoot2.1.5(16)--- Spring Boot的日志详解 市面上有许多的日志框架,比如 JUL( java.util.logging), JCL( Apache Commons ...

最新文章

  1. win7压缩包安装mysql_win7怎么安装mysql5.7.13压缩文件图解
  2. mysql数据导库常用操作
  3. linux定时关机命令_电脑设置定时关机你会吗?Windows自带的这行命令真好用
  4. 4.5 搭建深层神经网络块-深度学习-Stanford吴恩达教授
  5. 华为SAP解决方案为海澜之家带来新的科技创新
  6. 计算机与广播电视论文,计算机技术在广播电视节目的应用论文
  7. In-Loop Filters in HEVC
  8. mysql多个on_在多个查询中插入多行的MySQL ON DUPLICATE KEY UPDATE
  9. 使用jQuery发送POST,Ajax请求返回JSON格式数据
  10. iOS开发--正则表达式
  11. Linux-Ubuntu下设置ufw防火墙
  12. 如何查看域控是谁,域控是哪台机器
  13. 我的XX游戏面试之旅
  14. 利用Python进行数据分析--数据加载、存储与文件格式
  15. 好用的在线 java 编译网站,编辑器(亲测)
  16. 07 Halcon 点云平面角测量
  17. 解决微信网页授权:出现errcode:40163
  18. 【真正的解决方法】error: RPC failed; curl 56 OpenSSL SSL_read: SSL_ERROR_SYSCALL, errno 10054
  19. IRP IO_STACK_LOCATION 《寒江独钓》内核学习笔记(1)
  20. Spatial4j简介

热门文章

  1. redis如何设置定时过期_Redis 设置过期时间注意事项
  2. FaceToJava直白讲解:
  3. 2020 = 1024 + 996
  4. 2020年3月全国计算机等级考试真题(C语言二级)
  5. 文件上传下载和ognl
  6. 单元自动化测试1- 编写程序
  7. 人类首张黑洞照片发布,像甜圈圈,顺便恭喜爱因斯坦...
  8. matlab 加回声,基于matlab的语音信号加回声技术的实现.doc
  9. 小白解决Anaconda更新速度慢问题,内含详细步骤
  10. SparkContext 源码分析