1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址:http://www.openedv.com/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自带 snapshot.commint ??? 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-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的启动流程了。 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 = 2016 6 PATCHLEVEL = 03 7 SUBLEVEL = 8 EXTRAVERSION = 9 NAME = VERSION是主版本号,PATCHLEVEL是补丁版本号,SUBLEVEL是次版本号,这三个一起构成了uboot的版本号,比如当前的uboot版本号就是“2016.03”。EXTRAVERSION是附加版本信息,NAME是和名字有关的,一般不使用这两个。 31.3.2 MAKEFLAGS变量 make是支持递归调用的,也就是在Makefile中使用“make”命令来执行其他的Makefile文件,一般都是子目录中的Makefile文件。假如在当前目录下存在一个“subdir”子目录,这个子目录中又有其对应的Makefile文件,那么这个工程在编译的时候其主目录中的Makefile就可以调用子目录中的Makefile,以此来完成所有子目录的编译。主目录的Makefile可以使用如下代码来编译这个子目录: $(MAKE) -C subdir
(
M
A
K
E
)
就
是
调
用
“
m
a
k
e
”
命
令
,
−
C
指
定
子
目
录
。
有
时
候
我
们
需
要
向
子
m
a
k
e
传
递
变
量
,
这
个
时
候
使
用
“
e
x
p
o
r
t
”
来
导
出
要
传
递
给
子
m
a
k
e
的
变
量
即
可
,
如
果
不
希
望
哪
个
变
量
传
递
给
子
m
a
k
e
的
话
就
使
用
“
u
n
e
x
p
o
r
t
”
来
声
明
不
导
出
:
e
x
p
o
r
t
V
A
R
I
A
B
L
E
…
…
/
/
导
出
变
量
给
子
m
a
k
e
。
u
n
e
x
p
o
r
t
V
A
R
I
A
B
L
E
…
…
/
/
不
导
出
变
量
给
子
m
a
k
e
。
有
两
个
特
殊
的
变
量
:
“
S
H
E
L
L
”
和
“
M
A
K
E
F
L
A
G
S
”
,
这
两
个
变
量
除
非
使
用
“
u
n
e
x
p
o
r
t
”
声
明
,
否
则
的
话
在
整
个
m
a
k
e
的
执
行
过
程
中
,
它
们
的
值
始
终
自
动
的
传
递
给
子
m
a
k
e
。
在
u
b
o
o
t
的
主
M
a
k
e
f
i
l
e
中
有
如
下
代
码
:
示
例
代
码
31.3.2.1
顶
层
M
a
k
e
f
i
l
e
代
码
20
M
A
K
E
F
L
A
G
S
+
=
−
r
R
−
−
i
n
c
l
u
d
e
−
d
i
r
=
(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 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用于告诉你变量是哪来的,语法为:
( o r i g i n < v a r i a b l e > ) v a r i a b l e 是 变 量 名 , o r i g i n 函 数 的 返 回 值 就 是 变 量 来 源 , 因 此 (origin ) variable是变量名,origin函数的返回值就是变量来源,因此 (origin)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 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,判断$(filter 4.%,$(MAKE_VERSION))和“ ”(空)是否相等,如果不相等的话就成立,执行里面的语句。也就是说$(filter 4.%,$(MAKE_VERSION))不为空的话条件就成立,这里用到了Makefile中的filter函数,这是个过滤函数,函数格式如下:
(
f
i
l
t
e
r
<
p
a
t
t
e
r
n
.
.
.
>
,
<
t
e
x
t
>
)
f
i
l
t
e
r
函
数
表
示
以
p
a
t
t
e
r
n
模
式
过
滤
t
e
x
t
字
符
串
中
的
单
词
,
仅
保
留
符
合
模
式
p
a
t
t
e
r
n
的
单
词
,
可
以
有
多
个
模
式
。
函
数
返
回
值
就
是
符
合
p
a
t
t
e
r
n
的
字
符
串
。
因
此
(filter ,) filter函数表示以pattern模式过滤text字符串中的单词,仅保留符合模式pattern的单词,可以有多个模式。函数返回值就是符合pattern的字符串。因此
(filter,)filter函数表示以pattern模式过滤text字符串中的单词,仅保留符合模式pattern的单词,可以有多个模式。函数返回值就是符合pattern的字符串。因此(filter 4.%,
(
M
A
K
E
V
E
R
S
I
O
N
)
)
的
含
义
就
是
在
字
符
串
“
M
A
K
E
V
E
R
S
I
O
N
”
中
找
出
符
合
“
4.
(MAKE_VERSION))的含义就是在字符串“MAKE_VERSION”中找出符合“4.%”的字符(%为通配符),MAKE_VERSION是make工具的版本号,ubuntu16.04里面默认自带的make工具版本号为4.1,大家可以输入“make -v”查看。因此
(MAKEVERSION))的含义就是在字符串“MAKEVERSION”中找出符合“4.(filter 4.%,
(
M
A
K
E
V
E
R
S
I
O
N
)
)
不
为
空
,
条
件
成
立
,
执
行
92
94
行
的
语
句
。
第
92
行
也
是
一
个
判
断
语
句
,
如
果
(MAKE_VERSION))不为空,条件成立,执行92~94行的语句。 第92行也是一个判断语句,如果
(MAKEVERSION))不为空,条件成立,执行92 94行的语句。第92行也是一个判断语句,如果(filter %s ,
(
f
i
r
s
t
w
o
r
d
x
(firstword x
(firstwordx(MAKEFLAGS)))不为空的话条件成立,变量quiet等于“silent_”。这里也用到了函数filter,在
(
f
i
r
s
t
w
o
r
d
x
(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中的两行代码用于输出
(
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。 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_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指定的输出目录就存在了。
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 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。
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 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。 31.3.8 获取主机架构和系统 接下来顶层Makefile会获取主机架构和系统,也就是我们电脑的架构和系统,代码如下: 示例代码31.3.8.1 顶层Makefile代码
227 HOSTARCH := $(shell uname -m | \
228 sed -e s/i.86/x86/ \
229 -e s/sun4u/sparc64/ \
230 -e s/arm.*/arm/ \
231 -e s/sa110/arm/ \
232 -e s/ppc64/powerpc/ \
233 -e s/ppc/powerpc/ \
234 -e s/macppc/powerpc/\
235 -e s/sh.*/sh/)
236
237 HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \
238 sed -e 's/\(cygwin\).*/cygwin/')
239
240 export HOSTARCH HOSTOS
第227行定义了一个变量HOSTARCH,用于保存主机架构,这里调用shell命令“uname -m”获取架构名称,结果如图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 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里面的才是实时有效的配置。 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)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
31.3.12 导出其他变量 接下来在顶层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,值为$(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 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
)
(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-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 ,, ) 此函数用于在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
(
f
i
l
t
e
r
/
因
为
没
有
以
“
/
”
为
开
头
的
单
词
,
所
以
(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
(
K
B
U
I
L
D
B
U
I
L
T
I
N
)
,
(KBUILD_BUILTIN),
(KBUILDBUILTIN),(builtin-target) $(lib-target) $(extra-y)) 117 $(if
(
K
B
U
I
L
D
M
O
D
U
L
E
S
)
,
(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)/conf
114 $(Q)$$$$#\#'< with >'\'' $(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,$
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?