1)实验平台:正点原子阿尔法Linux开发板
2)平台购买地址:/item.htm?id=603672744434
2)全套实验源码+手册+视频下载地址:/thread-300792-1-1.html
3)对正点原子Linux感兴趣的同学可以加群讨论:935446741
4)关注正点原子公众号,获取最新资料更新
第三十一章 U-Boot顶层Makefile详解
上一章我们详细的讲解了uboot的使用方法,其实就是各种命令的使用,学会uboot使用以后就可以尝试移植uboot到自己的开发板上了,但是在移植之前需要我们得先分析一遍uboot的启动流程源码,得捋一下uboot的启动流程,否则移植的时候都不知道该修改那些文件。本章我们就来分析一下正点原子提供的uboot源码,重点是分析uboot启动流程,而不是整个uboot源码,uboot整个源码非常大,我们只看跟我们关心的部分即可。
31.1 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所示:
类型 名字 描述 备注
文件夹 api 与硬件无关的API函数。 uboot自带
arch 与架构体系有关的代码。
board 不同板子(开发板)的定制代码。
cmd 命令相关代码。
common 通用代码。
configs 配置文件。
disk 磁盘分区相关代码
doc 文档。
drivers 驱动代码。
dts 设备树。
examples 示例代码。
fs 文件系统。
include 头文件。
lib 库文件。
Licenses 许可证相关文件。
net 网络相关代码。
post 上电自检程序。
scripts 脚本文件。
test 测试代码。
tools 工具文件夹。
文件 .config 配置文件,重要的文件。 编译生成的文件
.gitignore git工具相关文件。 uboot自带
.mailmap 邮件列表。
.u-boot.xxx.cmd
(一系列) 这是一系列的文件,用于保存着一些命令。 编译生成的文件
config.mk 某个Makefile会调用此文件。 uboot自带
imxdownload 正点原子编写的SD卡烧写软件。 正点原子提供
Kbuild 用于生成一些和汇编有关的文件。 uboot自带
Kconfig 图形配置界面描述文件。
MAINTAINERS 维护者联系方式文件。
MAKEALL 一个shell脚本文件,帮助编译uboot的。
Makefile 主Makefile,重要文件!
mx6ull_alientek_emmc.sh 上一章编写的编译脚本文件 上一章编写的。
mx6ull_alientek_nand.sh 上一章编写的编译脚本文件
README 相当于帮助文档。 uboot自带
mint ???
System.map 系统映射文件 编译出来的文件
u-boot 编译出来的u-boot文件。
u-boot.xxx
(一系列) 生成的一些u-boot相关文件,包括u-boot.bin、u-boot.imx.等
表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-.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 .03 Configuration4 #5 CONFIG_CREATE_ARCH_SYMLINK=y6 CONFIG_HAVE_GENERIC_BOARD=y7 CONFIG_SYS_GENERIC_BOARD=y8 # CONFIG_ARC is not set9 CONFIG_ARM=y10 # CONFIG_AVR32 is not set11 # CONFIG_BLACKFIN is not set12 # CONFIG_M68K is not set13 # CONFIG_MICROBLAZE is not set14 # CONFIG_MIPS is not set15 # CONFIG_NDS32 is not set16 # CONFIG_NIOS2 is not set17 # CONFIG_OPENRISC is not set18 # CONFIG_PPC is not set19 # CONFIG_SANDBOX is not set20 # CONFIG_SH is not set21 # CONFIG_SPARC is not set22 # CONFIG_X86 is not set23 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 commands34 #35 CONFIG_CMD_BOOTD=y36 CONFIG_CMD_BOOTM=y37 CONFIG_CMD_ELF=y38 CONFIG_CMD_GO=y39 CONFIG_CMD_RUN=y40 CONFIG_CMD_IMI=y41 CONFIG_CMD_IMLS=y42 CONFIG_CMD_XIMG=y43 44 #45 # Environment commands46 #47 CONFIG_CMD_EXPORTENV=y48 CONFIG_CMD_IMPORTENV=y49 CONFIG_CMD_EDITENV=y50 CONFIG_CMD_SAVEENV=y51 CONFIG_CMD_ENV_EXISTS=y52 53 ......54 55 #56 # Library routines57 #58 # CONFIG_CC_OPTIMIZE_LIBS_FOR_SPEED is not set59 CONFIG_HAVE_PRIVATE_LIBGCC=y60 # CONFIG_USE_PRIVATE_LIBGCC is not set61 CONFIG_SYS_HZ=100062 # CONFIG_USE_TINY_PRINTF is not set63 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_BUILD2 # core command3 obj-y += boot.o4 obj-$(CONFIG_CMD_BOOTM) += bootm.o5 obj-y += help.o6 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的启动流程了。
31.2 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所示内容:
图31.2.8 排除arch/avr32目录
保存一下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’开头的文件或者文件夹。上述配置只是排除了一部分文件夹,大家在实际的使用中可以根据自己的实际需求来选择将哪些文件或者文件夹排除掉。排除以后我们的工程就会清爽很多,搜索的时候也不会跳出很多文件了。
31.3 U-Boot顶层Makefile分析
在阅读uboot源码之前,肯定是要先看一下顶层Makefile,分析gcc版本代码的时候一定是先从顶层Makefile开始的,然后再是子Makefile,这样通过层层分析Makefile即可了解整个工程的组织结构。顶层Makefile也就是uboot根目录下的Makefile文件,由于顶层Makefile文件内容比较多,所以我们将其分开来看。
31.3.1 版本号
顶层Makefile一开始是版本号,内容如下(为了方便分析,顶层Makefile代码段前段行号采用Makefile中的行号,因为uboot会更新,因此行号可能会与你所看的顶层Makefile有所不同):
示例代码31.3.1.1 顶层Makefile代码
5 VERSION =
6 PATCHLEVEL = 03
7 SUBLEVEL =
8 EXTRAVERSION =
9 NAME =
VERSION是主版本号,PATCHLEVEL是补丁版本号,SUBLEVEL是次版本号,这三个一起构成了uboot的版本号,比如当前的uboot版本号就是“.03”。EXTRAVERSION是附加版本信息,NAME是和名字有关的,一般不使用这两个。
31.3.2 MAKEFLAGS变量
make是支持递归调用的,也就是在Makefile中使用“make”命令来执行其他的Makefile文件,一般都是子目录中的Makefile文件。假如在当前目录下存在一个“subdir”子目录,这个子目录中又有其对应的Makefile文件,那么这个工程在编译的时候其主目录中的Makefile就可以调用子目录中的Makefile,以此来完成所有子目录的编译。主目录的Makefile可以使用如下代码来编译这个子目录:
$(MAKE) -C subdir
(MAKE)就是调用“make”命令,−C指定子目录。有时候我们需要向子make传递变量,这个时候使用“export”来导出要传递给子make的变量即可,如果不希望哪个变量传递给子make的话就使用“unexport”来声明不导出:exportVARIABLE……//导出变量给子make。unexportVARIABLE……//不导出变量给子make。有两个特殊的变量:“SHELL”和“MAKEFLAGS”,这两个变量除非使用“unexport”声明,否则的话在整个make的执行过程中,它们的值始终自动的传递给子make。在uboot的主Makefile中有如下代码:示例代码31.3.2.1顶层Makefile代码20MAKEFLAGS+=−rR−−include−dir=(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=(MAKE)就是调用“make”命令,−C指定子目录。有时候我们需要向子make传递变量,这个时候使用“export”来导出要传递给子make的变量即可,如果不希望哪个变量传递给子make的话就使用“unexport”来声明不导出:exportVARIABLE……//导出变量给子make。unexportVARIABLE……//不导出变量给子make。有两个特殊的变量:“SHELL”和“MAKEFLAGS”,这两个变量除非使用“unexport”声明,否则的话在整个make的执行过程中,它们的值始终自动的传递给子make。在uboot的主Makefile中有如下代码:示例代码31.3.2.1顶层Makefile代码20MAKEFLAGS+=−rR−−include−dir=(CURDIR)
上述代码使用“+=”来给变量MAKEFLAGS追加了一些值,“-rR”表示禁止使用内置的隐含规则和变量定义,“–include-dir”指明搜索路径,”$(CURDIR)”表示当前目录。
31.3.3 命令输出
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 endif76 ifndef KBUILD_VERBOSE77 KBUILD_VERBOSE = 078 endif79 80 ifeq ($(KBUILD_VERBOSE),1)81 quiet =82 Q =83 else84 quiet=quiet_85 Q = @86 endif
上述代码中先使用ifeq来判断"$(origin V)"和"command line"是否相等。这里用到了Makefile中的函数origin,origin和其他的函数不一样,它不操作变量的值,origin用于告诉你变量是哪来的,语法为:
(origin<variable>)variable是变量名,origin函数的返回值就是变量来源,因此(origin <variable>) variable是变量名,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)(Q)(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_”的话,整个命令都不会输出。
31.3.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 of89 # commands90 91 ifneq ($(filter 4.%,$(MAKE_VERSION)),) # make-492 ifneq ($(filter %s ,$(firstword x$(MAKEFLAGS))),)93 quiet=silent_94 endif95 else# make-3.8x96 ifneq ($(filter s% -s%,$(MAKEFLAGS)),)97 quiet=silent_98 endif99 endif100 101 export quiet Q KBUILD_VERBOSE
第91行判断当前正在使用的编译器版本号是否为4.x,判断$(filter 4.%,$(MAKE_VERSION))和“ ”(空)是否相等,如果不相等的话就成立,执行里面的语句。也就是说$(filter 4.%,$(MAKE_VERSION))不为空的话条件就成立,这里用到了Makefile中的filter函数,这是个过滤函数,函数格式如下:
(filter<pattern...>,<text>)filter函数表示以pattern模式过滤text字符串中的单词,仅保留符合模式pattern的单词,可以有多个模式。函数返回值就是符合pattern的字符串。因此(filter <pattern...>,<text>) filter函数表示以pattern模式过滤text字符串中的单词,仅保留符合模式pattern的单词,可以有多个模式。函数返回值就是符合pattern的字符串。因此(filter<pattern...>,<text>)filter函数表示以pattern模式过滤text字符串中的单词,仅保留符合模式pattern的单词,可以有多个模式。函数返回值就是符合pattern的字符串。因此(filter 4.%,(MAKEVERSION))的含义就是在字符串“MAKEVERSION”中找出符合“4.(MAKE_VERSION))的含义就是在字符串“MAKE_VERSION”中找出符合“4.%”的字符(%为通配符),MAKE_VERSION是make工具的版本号,ubuntu16.04里面默认自带的make工具版本号为4.1,大家可以输入“make -v”查看。因此(MAKEVERSION))的含义就是在字符串“MAKEVERSION”中找出符合“4.(filter 4.%,(MAKEVERSION))不为空,条件成立,执行9294行的语句。第92行也是一个判断语句,如果(MAKE_VERSION))不为空,条件成立,执行92~94行的语句。 第92行也是一个判断语句,如果(MAKEVERSION))不为空,条件成立,执行9294行的语句。第92行也是一个判断语句,如果(filter %s ,(firstwordx(firstword x(firstwordx(MAKEFLAGS)))不为空的话条件成立,变量quiet等于“silent_”。这里也用到了函数filter,在(firstwordx(firstword x(firstwordx(MAKEFLAGS)))中过滤出符合“%s”的单词。到了函数firstword,函数firstword是获取首单词,函数格式如下:
$(firstword firstword函数用于取出text字符串中的第一个单词,函数的返回值就是获取到的单词。当使用“make -s”编译的时候,“-s”会作为MAKEFLAGS变量的一部分传递给Makefile。在顶层Makfile中添加如图31.3.4.1所示的代码:
图31.3.4.1 顶层Makefile添加代码
图31.3.4.1中的两行代码用于输出(firstwordx(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”,将(filter(filter %s ,(filter(firstword x(MAKEFLAGS)))展开就是(MAKEFLAGS)))展开就是(MAKEFLAGS)))展开就是(filter %s, xrRs),而$(filter %s, xrRs)的返回值肯定不为空,条件成立,quiet=silent_。
第101行 使用export导出变量quiet、Q和KBUILD_VERBOSE。
31.3.5 设置编译结果输出目录
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_OUTPUT110 # Set the environment variable KBUILD_OUTPUT to point to the directory111 # where the output files shall be placed.112 # export KBUILD_OUTPUT=dir/to/store/output/files/113 # make114 #115 # The O= assignment takes precedence over the KBUILD_OUTPUT environment116 # variable.117 118 # KBUILD_SRC is set on invocation of make in OBJ directory119 # 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 resides123 # Do we want to locate output files in a separate directory?124 ifeq ("$(origin O)", "command line")125 KBUILD_OUTPUT := $(O)126 endif127 128 # That's our default target when none is given on the command line129 PHONY := _all130 _all:131 132 # Cancel implicit rules on top Makefile133 $(CURDIR)/Makefile Makefile: ;134 135 ifneq ($(KBUILD_OUTPUT),)136 # Invoke a second make in the output directory, passing relevant variables137 # check that the output directory actually exists138 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指定的输出目录就存在了。
31.3.6 代码检查
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 the167 # 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, regardless171 # of whether they are re-compiled or not.172 #173 # See the file "Documentation/sparse.txt" for more details, including174 # where to get the "sparse" utility.175 176 ifeq ("$(origin C)", "command line")177 KBUILD_CHECKSRC = $(C)178 endif179 ifndef KBUILD_CHECKSRC180 KBUILD_CHECKSRC = 0181 endif
第176行判断C是否来源于命令行,如果C来源于命令行,那就将C赋值给变量KBUILD_CHECKSRC,如果命令行没有C的话KBUILD_CHECKSRC就为0。
31.3.7 模块编译
在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 build184 # Old syntax make ... SUBDIRS=$PWD is still supported185 # Setting the environment variable KBUILD_EXTMOD take precedence186 ifdef SUBDIRS187 KBUILD_EXTMOD ?= $(SUBDIRS)188 endif189 190 ifeq ("$(origin M)", "command line")191 KBUILD_EXTMOD := $(M)192 endif193 194 # If building an external module we do not care about the all: rule195 # but instead _all depend on modules196 PHONY += all197 ifeq ($(KBUILD_EXTMOD),)198 _all: all199 else200 _all: modules201 endif202 203 ifeq ($(KBUILD_SRC),)204 # building in the source tree205 srctree := .206 else207 ifeq ($(KBUILD_SRC)/,$(dir $(CURDIR)))208 # building in a subdirectory of the source tree209 srctree := ..210 else211 srctree := $(KBUILD_SRC)212 endif213 endif214 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。
31.3.8 获取主机架构和系统
接下来顶层Makefile会获取主机架构和系统,也就是我们电脑的架构和系统,代码如下:
示例代码31.3.8.1 顶层Makefile代码
227 HOSTARCH := $(shell uname -m | \228sed -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。
31.3.9 设置目标架构、交叉编译器和配置文件
编译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 builds245 ifeq ($(HOSTARCH),$(ARCH))246 CROSS_COMPILE ?=247 endif248249 KCONFIG_CONFIG ?= .config250 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里面的才是实时有效的配置。
31.3.10 调用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中的这些变量,后面用到的时候再分析。
31.3.11 交叉编译工具变量设置
上面我们只是设置了CROSS_COMPILE的名字,但是交叉编译器其他的工具还没有设置,顶层Makefile中相关代码如下:
示例代码31.3.11.1 顶层Makefile代码
331 # Make variables (CC, etc...)332 333 AS= $(CROSS_COMPILE)as334 # Always use GNU ld335 ifneq ($(shell $(CROSS_COMPILE)ld.bfd -v 2> /dev/null),)336 LD= $(CROSS_COMPILE)ld.bfd337 else338 LD= $(CROSS_COMPILE)ld339 endif340 CC= $(CROSS_COMPILE)gcc341 CPP= $(CC) -E342 AR= $(CROSS_COMPILE)ar343 NM= $(CROSS_COMPILE)nm344 LDR= $(CROSS_COMPILE)ldr345 STRIP= $(CROSS_COMPILE)strip346 OBJCOPY = $(CROSS_COMPILE)objcopy347 OBJDUMP = $(CROSS_COMPILE)objdump
31.3.12 导出其他变量
接下来在顶层Makefile会导出很多变量,代码如下:
示例代码31.3.12.1 顶层Makefile代码
368 export VERSION PATCHLEVEL SUBLEVEL UBOOTRELEASE UBOOTVERSION369 export ARCH CPU BOARD VENDOR SOC CPUDIR BOARDDIR370 export CONFIG_SHELL HOSTCC HOSTCFLAGS HOSTLDFLAGS CROSS_COMPILE AS LD CC371 export CPP AR NM LDR STRIP OBJCOPY OBJDUMP372 export MAKE AWK PERL PYTHON373 export HOSTCXX HOSTCXXFLAGS DTC CHECK CHECKFLAGS374 375 export KBUILD_CPPFLAGS NOSTDINC_FLAGS UBOOTINCLUDE OBJCOPYFLAGS LDFLAGS376 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-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 tcsh22 VENDOR :=23 #########################################################################24 25 ARCH := $(CONFIG_SYS_ARCH:"%"=%)26 CPU := $(CONFIG_SYS_CPU:"%"=%)27 ifdef CONFIG_SPL_BUILD28 ifdef CONFIG_TEGRA29 CPU := arm720t30 endif31 endif32 BOARD := $(CONFIG_SYS_BOARD:"%"=%)33 ifneq ($(CONFIG_SYS_VENDOR),)34 VENDOR := $(CONFIG_SYS_VENDOR:"%"=%)35 endif36 ifneq ($(CONFIG_SYS_SOC),)37 SOC := $(CONFIG_SYS_SOC:"%"=%)38 endif39 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 contains43 # CPU-specific code.44 CPUDIR=arch/$(ARCH)/cpu$(if $(CPU),/$(CPU),)45 46 sinclude $(srctree)/arch/$(ARCH)/config.mk 47 sinclude $(srctree)/$(CPUDIR)/config.mk48 49 ifdef SOC50 sinclude $(srctree)/$(CPUDIR)/$(SOC)/config.mk 51 endif52 ifneq ($(BOARD),)53 ifdef VENDOR54 BOARDDIR = $(VENDOR)/$(BOARD)55 else56 BOARDDIR = $(BOARD)57 endif58 endif59 ifdef BOARD60 sinclude $(srctree)/board/$(BOARDDIR)/config.mk # include board specific rules61 endif62 63 ifdef FTRACE64 PLATFORM_CPPFLAGS += -finstrument-functions -DFTRACE65 endif66 67 # Allow use of stdint.h if available68 ifneq ($(USE_STDINT),)69 PLATFORM_CPPFLAGS += -DCONFIG_USE_STDINT70 endif71 72 #########################################################################73 74 RELFLAGS := $(PLATFORM_RELFLAGS)75 76 PLATFORM_CPPFLAGS += $(RELFLAGS)77 PLATFORM_CPPFLAGS += -pipe78 79 LDFLAGS += $(PLATFORM_LDFLAGS)80 LDFLAGS_FINAL += -Bstatic81 82 export PLATFORM_CPPFLAGS83 export RELFLAGS84 export LDFLAGS_FINAL
第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 (此文件不存在)
31.3.13 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 targets415 # catch them early, and hand them over to scripts/kconfig/Makefile416 # It is allowed to specify more targets when calling make, including417 # mixing *config targets and build targets.418 # For example 'make oldconfig all'.419 # Detect when mixed targets is specified, and make a second invocation420 # of make so .config is not included in this case either (for *config).421 422 version_h := include/generated/version_autogenerated.h423 timestamp_h := include/generated/timestamp_autogenerated.h424 425 no-dot-config-targets := clean clobber mrproper distclean \426 help %docs check% coccicheck \427 ubootversion backup428 429 config-targets := 0430 mixed-targets := 0431 dot-config:= 1432 433 ifneq ($(filter $(no-dot-config-targets), $(MAKECMDGOALS)),)434ifeq ($(filter-out $(no-dot-config-targets), $(MAKECMDGOALS)),)435 dot-config := 0436endif437 endif438 439 ifeq ($(KBUILD_EXTMOD),)440 ifneq ($(filter config %config,$(MAKECMDGOALS)),)441 config-targets := 1442 ifneq ($(words $(MAKECMDGOALS)),1)443mixed-targets := 1444 endif445 endif446 endif447 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_one454 455 $(filter-out __build_one_by_one, $(MAKECMDGOALS)): __build_one_by_one456@:457 458 __build_one_by_one:459$(Q)set -e; \460for i in $(MAKECMDGOALS); do \461 $(MAKE) -f $(srctree)/Makefile $$i; \462done463 464 else465 ifeq ($(config-targets),1)466 # ================================================================467 # *config targets only - make sure prerequisites are updated, and descend468 # in scripts/kconfig to make the *config target469 470 KBUILD_DEFCONFIG := sandbox_defconfig471 export KBUILD_DEFCONFIG KBUILD_KCONFIG472 473 config: scripts_basic outputmakefile FORCE474$(Q)$(MAKE) $(build)=scripts/kconfig $@475 476 %config: scripts_basic outputmakefile FORCE477$(Q)$(MAKE) $(build)=scripts/kconfig $@478 479 else480 #==================================================================481 # Build targets only - this includes vmlinux, arch specific targets, clean482 # targets and others. In general all targets except *config targets.483 484 ifeq ($(dot-config),1)485 # Read in config486 -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_basic396 scripts_basic:397$(Q)$(MAKE) $(build)=scripts/basic398$(Q)rm -f .tmp_quiet_recordmcount399 400 # To avoid any implicit rule to kick in, define an empty command.401 scripts/basic/%: scripts_basic ;402 403 PHONY += outputmakefile404 # 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) source410$(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)=dir181 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)(Q)(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
31.3.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,有如下代码:
示例代码31.3.14.1 Makefile.build代码段
8 # Modified for U-Boot9 prefix := tpl10 src := $(patsubst $(prefix)/%,%,$(obj))11 ifeq ($(obj),$(src))12 prefix := spl13 src := $(patsubst $(prefix)/%,%,$(obj))14 ifeq ($(obj),$(src))15 prefix := .16 endif17 endif
第9行定义了变量prefix值为tpl。第10行定义了变量src,这里用到了函数patsubst,此行代码展开后为:
$(patsubst tpl/%,%, scripts/basic)
patsubst是替换函数,格式如下:
$(patsubst ,, 此函数用于在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 Makefile57 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/因为没有以“/”为开头的单词,所以(filter /%, scripts/basic), scripts/basic, ./scripts/basic), 因为没有以“/”为开头的单词,所以(filter/因为没有以“/”为开头的单词,所以(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 (KBUILDBUILTIN),(KBUILD_BUILTIN),(KBUILDBUILTIN),(builtin-target) $(lib-target) $(extra-y))
117 $(if (KBUILDMODULES),(KBUILD_MODULES),(KBUILDMODULES),(obj-m) $(modorder-target))
118 $(subdir-ym) KaTeX parse error: Expected group after '_' at position 22: …s) 119 @: _̲_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)/conf114$(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)115 116 # Added for U-Boot (backward compatibility)117 %_config: %_defconfig118@:
目标%_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)(Q)(Q)< (silent)−−defconfig=arch/(silent) --defconfig=arch/(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是怎么生成的了。
31.3.15 make过程
配置好uboot以后就可以直接make编译了,因为没有指明目标,所以会使用默认目标,主Makefile中的默认目标如下:
示例代码31.3.15.1 顶层Makefile代码段
128 # That's our default target when none is given on the command line129 PHONY := _all130 _all:
目标_all又依赖于all,如下所示:
示例代码31.3.15.2 顶层Makefile代码段
194 # If building an external module we do not care about the all: rule195 # but instead _all depend on modules196 PHONY += all197 ifeq ($(KBUILD_EXTMOD),)198 _all: all199 else200 _all: modules201 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 ."807@echo "See doc/README.generic-board for further information"808@echo "===================================================="809 endif810 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 ones731 ALL-y += u-boot.srec u-boot.bin u-boot.sym System.map u-boot.cfg binary_size_check732 733 ALL-$(CONFIG_ONENAND_U_BOOT) += u-boot-onenand.bin734 ifeq ($(CONFIG_SPL_FSL_PBL),y)735 ALL-$(CONFIG_RAMBOOT_PBL) += u-boot-with-spl-pbl.bin736 else737 ifneq ($(CONFIG_SECURE_BOOT), y)738 # For Secure Boot The Image needs to be signed and Header must also739 # be included. So The image has to be built explicitly740 ALL-$(CONFIG_RAMBOOT_PBL) += u-boot.pbl741 endif742 endif743 ALL-$(CONFIG_SPL) += spl/u-boot-spl.bin744 ALL-$(CONFIG_SPL_FRAMEWORK) += u-boot.img745 ALL-$(CONFIG_TPL) += tpl/u-boot-tpl.bin746 ALL-$(CONFIG_OF_SEPARATE) += u-boot.dtb747 ifeq ($(CONFIG_SPL_FRAMEWORK),y)748 ALL-$(CONFIG_OF_SEPARATE) += u-boot-dtb.img749 endif750 ALL-$(CONFIG_OF_HOSTFILE) += u-boot.dtb751 ifneq ($(CONFIG_SPL_TARGET),)752 ALL-$(CONFIG_SPL) += $(CONFIG_SPL_TARGET:"%"=%)753 endif754 ALL-$(CONFIG_REMAKE_ELF) += u-boot.elf755 ALL-$(CONFIG_EFI_APP) += u-boot-app.efi756 ALL-$(CONFIG_EFI_STUB) += u-boot-payload.efi757 758 ifneq ($(BUILD_ROM),)759 ALL-$(CONFIG_X86_RESET_VECTOR) += u-boot.rom760 endif761 762 # enable combined SPL/u-boot/dtb rules for tegra763 ifeq ($(CONFIG_TEGRA)$(CONFIG_SPL),yy)764 ALL-y += u-boot-tegra.bin u-boot-nodtb-tegra.bin765 ALL-$(CONFIG_OF_SEPARATE) += u-boot-dtb-tegra.bin766 endif767 768 # Add optional build target if defined in board/cpu/soc headers769 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 FORCE827$(call if_changed,cat)828 829 u-boot.bin: u-boot-dtb.bin FORCE830$(call if_changed,copy)831 else832 u-boot.bin: u-boot-nodtb.bin FORCE833$(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 than228 # target, or command line has changed229 # if_changed_dep - as if_changed, but uses fixdep to reveal 230 # dependencies including used config symbols231 # if_changed_rule - as if_changed but execute rule instead232 # See Documentation/kbuild/makefiles.txt for more info233 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=1237 arg-check = $(strip $(filter-out $(cmd_$(1)), $(cmd_$@)) \238 $(filter-out $(cmd_$@), $(cmd_$(1))) )239 else240 arg-check = $(if $(strip $(cmd_$@)),,1)241 endif242 243 # Replace >$< with >$$< to preserve $ when reloading the .cmd file244 # (needed for make)245 # Replace >#< with >\#< to avoid starting a comment in the .cmd file246 # (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)); \260printf '%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 FORCE867$(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 FORCE1171 $(call if_changed,u-boot__)1172 ifeq ($(CONFIG_KALLSYMS),y)1173 $(call cmd,smap)1174 $(call cmd,u-boot__) common/system_map.o1175 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/(head-y)跟CPU架构有关,我们使用的是ARM芯片,所以head-y在arch/arm/Makefile中被指定为: head-y := arch/arm/cpu/(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 examples673 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所示内容输出:
![
](https://img-/83ec0760f50542569cb51fa94afb2e65.png)
图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-.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的流程。至于更具体的实现,有兴趣的可以参考一下其他资料。
【正点原子Linux连载】第三十一章 U-Boot顶层Makefile详解 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0
如果觉得《【正点原子Linux连载】第三十一章 U-Boot顶层Makefile详解 -摘自【正点原子】I.MX6U》对你有帮助,请点赞、收藏,并留下你的观点哦!