直接参考【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.81
本文仅作为个人笔记使用,方便进一步记录自己的实践总结。
上一章我们详细的讲解了 uboot 的使用方法,其实就是各种命令的使用,学会 uboot 使用以后就可以尝试移植 uboot 到自己的开发板上了,但是在移植之前需要我们得先分析一遍 uboot的启动流程源码,得捋一下 uboot 的启动流程,否则移植的时候都不知道该修改那些文件。本章我们就来分析一下正点原子提供的 uboot 源码,重点是分析 uboot 启动流程,而不是整个 uboot源码,uboot 整个源码非常大,我们只看跟我们相关的部分即可。
U-Boot 工程目录分析
本书我们以 EMMC 版本的核心板为例讲解,为了方便,uboot 启动源码分析就在 Windows下进行,将正点原子提供的 uboot 源码进行解压,解压完成以后的目录如图 31.1.1 所示:
图 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 和图 31.1.1,可以看出编译后的 uboot 要比没编译之前多了好多文件,这些文件夹或文件的含义见表 31.1.1 所示:
表 31.1.1 中的很多文件夹和文件我们都不需要去关心,我们要关注的文件夹或文件如下:
1、arch 文件夹
这个文件夹里面存放着和架构有关的文件,如图 31.1.3 所示:
从图 31.1.3 可以看出有很多架构,比如 arm、avr32、m68k 等,我们现在用的是 ARM 芯片,所以只需要关心 arm 文件夹即可,打开 arm 文件夹里面内容如图 31.1.4 所示:
图 31.1.4 只截取了一部分,还有一部分 mach-xxx 的文件夹。mach 开头的文件夹是跟具体的设备有关的,比如“mach-exynos”就是跟三星的 exyons 系列 CPU 有关的文件。我们使用的是 I.MX6ULL,所以要关注“imx-common”这个文件夹。另外“cpu”这个文件夹也是和 cpu 架构有关的,打开以后如图 31.1.5 所示:
从图 31.1.5 可以看出有多种 ARM 架构相关的文件夹,I.MX6ULL 使用的 Cortex-A7 内核,Cortex-A7 属于 armv7,所以我们要关心“armv7”这个文件夹。cpu 文件夹里面有个名为“uboot.lds”的链接脚本文件,这个就是 ARM 芯片所使用的 u-boot 链接脚本文件!armv7 这个文件夹里面的文件都是跟 ARMV7 架构有关的,是我们分析 uboot 启动源码的时候需要重点关注的。
2、board 文件夹
board 文件夹就是和具体的板子有关的,打开此文件夹,里面全是不同的板子,毫无疑问正点原子的开发板肯定也在里面(正点原子添加的),borad 文件夹里面有个名为“freescale”的文件夹,如图 31.1.6 所示:
所有使用 freescale 芯片的板子都放到此文件夹中,I.MX 系列以前属于 freescale,只是freescale 后来被 NXP 收购了。打开此 freescale 文件夹,在里面找到和 mx6u(I.MX6UL/ULL)有关的文件夹,如图 31.1.7 所示:
图 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 中这 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 就是用于生成 uboot.nodtb.bin 的,此文件内容如下:
待补充。
这里用到了 arm-linux-gnueabihf-objcopy,使用 objcopy 将 ELF 格式的 u-boot 文件转换为二进制的 u-boot-nodtb.bin 文件。
文件 u-boot 是 ELF 格式的文件,文件.u-boot.cmd 用于生成 u-boot,文件内容如下:
待补充。
.u-boot.cmd 使用到了 arm-linux-gnueabihf-ld.bfd,也就是链接工具,使用 ld.bfd 将各个 builtin.o 文件链接在一起就形成了 u-boot 文件。uboot 在编译的时候会将同一个目录中的所有.c 文件都编译在一起,并命名为 built-in.o,相当于将众多的.c 文件对应的.o 文件集合在一起,这个就是 u-boot 文件的来源。
如果我们要用 NXP 提供的 MFGTools 工具向开发板烧写 uboot,此时烧写的是 u-boot.imx文件,而不是 u-boot.bin 文件。u-boot.imx 是在 u-boot.bin 文件的头部添加了 IVT、DCD 等信息。这个工作是由文件.u-boot.imx.cmd 来完成的,此文件内容如下:
待补充。
可以看出,这里用到了工具 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 内容如下:
待补充。
可以看出.config 文件中都是以“CONFIG_”开始的配置项,这些配置项就是 Makefile 中的变量,因此后面都跟有相应的值,uboot 的顶层 Makefile 或子 Makefile 会调用这些变量值。
在.config 中会有大量的变量值为‘y’,这些为‘y’的变量一般用于控制某项功能是否使能,为‘y’的话就表示功能使能,比如:
CONFIG_CMD_BOOTM=y
如果使能了 bootd 这个命令的话,CONFIG_CMD_BOOTM 就为‘y’。在 cmd/Makefile 中有如下代码:
示例代码 31.1.6 cmd/Makefile 代码 1 ifndef CONFIG_SPL_BUILD 2 # core command 3 obj-y += boot.o 4 obj-$(CONFIG_CMD_BOOTM) += bootm.o 5 obj-y += help.o 6 obj-y += version.o
在示例代码 31.1.6 中,有如下所示一行代码:
obj-$(CONFIG_CMD_BOOTM) += bootm.o CONFIG_CMD_BOOTM=y,将其展开就是: obj-y += bootm.o
也就是给 obj-y 追加了一个“bootm.o”,obj-y 包含着所有要编译的文件对应的.o 文件,这里表示需要编译文件 cmd/bootm.c。相当于通过“CONFIG_CMD_BOOTD=y”来使能 bootm 这个命令,进而编译 cmd/bootm.c 这个文件,这个文件实现了命令 bootm。在 uboot 和 Linux 内核中都是采用这种方法来选择使能某个功能,编译对应的源码文件。
8、README
README 文件描述了 uboot 的详细信息,包括 uboot 该如何编译、uboot 中各文件夹的含义、相应的命令等等。建议大家详细的阅读此文件,可以进一步增加对 uboot 的认识。
关于 uboot 根目录中的文件和文件夹的含义就讲解到这里,接下来就要开始分析 uboot 的启动流程了。
VScode 工程创建
先在 Ubuntu 下编译一下 uboot,然后将编译后的 uboot 文件夹复制到 windows 下,并创建VScode 工程。打开 VScode,选择:文件->打开文件夹…,选中 uboot 文件夹,如图 31.2.1 所示:
打开 uboot 目录以后,VSCode 界面如图 31.2.2 所示:
点击“文件->将工作区另存为…”,打开保存工作区对话框,将工作区保存到 uboot 源码根目录下,设置文件名为“uboot”,如图 31.2.3 所示:
保存成功以后就会在 uboot 源码根目录下存在一个名为 uboot.code-workspace 的文件。这样一个完整的 VSCode 工程就建立起来了。但是这个 VSCode 工程包含了 uboot 的所有文件,uboot中有些文件是不需要的,比如 arch 目录下是各种架构的文件夹,如图 31.2.4 所示:
在 arch 目录下,我们只需要 arm 文件夹,所以需要将其它的目录从 VSCode 中给屏蔽掉,比如将 arch/avr32 这个目录给屏蔽掉。
在 VSCode 上建名为“.vscode”的文件夹,如图 31.2.5 所示:
输入新建文件夹的名字,完成以后如图 31.2.6 所示。
在.vscode 文件夹中新建一个名为“settings.json”的文件,然后在 settings.json 中输入如下内容:结果如图 31.2.7 所示:
其中"search.exclude"里面是需要在搜索结果中排除的文件或者文件夹,"files.exclude"是左侧工程目录中需要排除的文件或者文件夹。我们需要将 arch/avr32 文件夹下的所有文件从搜索结果和左侧的工程目录中都排除掉,因此在"search.exclude"和"files.exclude"中输入如图 31.2.8 所示内容:
保存一下 settings.json 文件,然后再看一下左侧的工程目录,发现 arch 目录下没有 avr32 这个文件夹了,说明 avr32 这个文件夹被排除掉了,如图 31.2.9 所示:
我们只是在"search.exclude"和"files.exclude"中加入了:"arch/avr32": true,冒号前面的是要排除的文件或者文件夹,冒号后面为是否将文件排除,true 表示排除,false 表示不排除。用这种方法即可将不需要的文件,或者文件夹排除掉,对于本章我们分析 uboot 而言,在上述代码用到了通配符“*”,比如“**/*.o”表示所有.o 结尾的文件。“configs/[a-l]*”表示 configs 目录下所有以‘a’~‘l’开头的文件或者文件夹。上述配置只是排除了一部分文件夹,大家在实际的使用中可以根据自己的实际需求来选择将哪些文件或者文件夹排除掉。排除以后我们的工程就会清爽很多,搜索的时候也不会跳出很多文件了。
其实,没必要这么麻烦,直接拷贝一份uboot,然后将不要的目录给删除就行了,然后直接在vscode里打开精简后的目录。
U-Boot 顶层 Makefile 分析
在阅读 uboot 源码之前,肯定是要先看一下顶层 Makefile,分析 gcc 版本代码的时候一定是先从顶层 Makefile 开始的,然后再是子 Makefile,这样通过层层分析 Makefile 即可了解整个工程的组织结构。顶层 Makefile 也就是 uboot 根目录下的 Makefile 文件,由于顶层 Makefile 文件内容比较多,所以我们将其分开来看。
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 是和名字有关的,一般不使用这两个。
2 MAKEFLAGS 变量
make 是支持递归调用的,也就是在 Makefile 中使用“make”命令来执行其他的 Makefile文件,一般都是子目录中的 Makefile 文件。假如在当前目录下存在一个“subdir”子目录,这个子目录中又有其对应的 Makefile 文件,那么这个工程在编译的时候其主目录中的 Makefile 就可以调用子目录中的 Makefile,以此来完成所有子目录的编译。主目录的 Makefile 可以使用如下代码来编译这个子目录:
$(MAKE) -C subdir
$(MAKE)就是调用“make”命令,-C 指定子目录。有时候我们需要向子 make 传递变量,这个时候使用“export”来导出要传递给子 make 的变量即可,如果不希望哪个变量传递给子make 的话就使用“unexport”来声明不导出:
export VARIABLE …… //导出变量给子 make 。
unexport VARIABLE…… //不导出变量给子 make。
有两个特殊的变量:“SHELL”和“MAKEFLAGS”,这两个变量除非使用“unexport”声明,否则的话在整个make的执行过程中,它们的值始终自动的传递给子make。在uboot的主Makefile中有如下代码:
示例代码 31.3.2.1 顶层 Makefile 代码
20 MAKEFLAGS += -rR --include-dir=$(CURDIR)
上述代码使用“+=”来给变量 MAKEFLAGS 追加了一些值,“-rR”表示禁止使用内置的隐含规则和变量定义,“--include-dir”指明搜索路径,”$(CURDIR)”表示当前目录。
3 命令输出
uboot 默认编译是不会在终端中显示完整的命令,都是短命令,如图 31.3.3 所示:
在终端中输出短命令虽然看起来很清爽,但是不利于分析 uboot 的编译过程。可以通过设置变量“V=1“来实现完整的命令输出,这个在调试 uboot 的时候很有用,结果如图 31.3.3.2 所示:
顶层 Makefile 中控制命令输出的代码如下:
示例代码 31.3.3.1 顶层 Makefile 代码 73 ifeq ("$(origin V)", "command line") 74 KBUILD_VERBOSE = $(V) 75 endif 76 ifndef KBUILD_VERBOSE 77 KBUILD_VERBOSE = 0 78 endif 79 80 ifeq ($(KBUILD_VERBOSE),1) 81 quiet = 82 Q = 83 else 84 quiet=quiet_ 85 Q = @ 86 endif
上述代码中先使用 ifeq 来判断"$(origin V)"和"command line"是否相等。这里用到了 Makefile中的函数 origin,origin 和其他的函数不一样,它不操作变量的值,origin 用于告诉你变量是哪来的,语法为:
$(origin <variable>)
variable 是变量名,origin 函数的返回值就是变量来源,因此$(origin V)就是变量 V 的来源。如果变量 V 是在命令行定义的那么它的来源就是"command line",这样"$(origin V)"和"command line"就相等了。当这两个相等的时候变量 KBUILD_VERBOSE 就等于 V 的值,比如在命令行中输 入 “ V=1 “ 的 话 那 么 KBUILD_VERBOSE=1 。 如 果 没 有 在 命 令 行 输 入 V 的 话KBUILD_VERBOSE=0。第 80 行判断 KBUILD_VERBOSE 是否为 1,如果 KBUILD_VERBOSE 为 1 的话变量 quiet和 Q 都为空,如果 KBUILD_VERBOSE=0 的话变量 quiet 为“quiet_“,变量 Q 为“@”,综上所述:
V=1 的话:
KBUILD_VERBOSE=1
quiet= 空 。
Q= 空。
V=0 或者命令行不定义 V 的话:
KBUILD_VERBOSE=0
quiet= quiet_。
Q= @。
Makefile 中会用到变量 quiet 和 Q 来控制编译的时候是否在终端输出完整的命令,在顶层Makefile 中有很多如下所示的命令:
$(Q)$(MAKE) $(build)=tools
如果 V=0 的话上述命令展开就是“@ make $(build)=tools”,make 在执行的时候默认会在终端输出命令,但是在命令前面加上“@”就不会在终端输出命令了。当 V=1 的时候 Q 就为空,上述命令就是“make $(build)=tools”,因此在 make 执行的过程,命令会被完整的输出在终端上。
有些命令会有两个版本,比如:
quiet_cmd_sym ?= SYM $@ cmd_sym ?= $(OBJDUMP) -t $< > $@
sym 命令分为“quiet_cmd_sym”和“cmd_sym”两个版本,这两个命令的功能都是一样的,区别在于 make 执行的时候输出的命令不同。quiet_cmd_xxx 命令输出信息少,也就是短命令,而 cmd_xxx 命令输出信息多,也就是完整的命令。
如果变量 quiet 为空的话,整个命令都会输出。
如果变量 quiet 为“quiet_”的话,仅输出短版本。
如果变量 quiet 为“silent_”的话,整个命令都不会输出。
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))和“ ”(空)是否相等,如果不相等的话就成立,执行里面的语句。也就是说$(filter4.%,$(MAKE_VERSION))不为空的话条件就成立,这里用到了 Makefile 中的 filter 函数,这是个过滤函数,函数格式如下:
$(filter <pattern...>,<text>)
filter 函数表示以 pattern 模式过滤 text 字符串中的单词,仅保留符合模式 pattern 的单词,可以有多个模式。函数返回值就是符合 pattern 的字符串。因此$(filter 4.%,$(MAKE_VERSION))的含义就是在字符串“MAKE_VERSION”中找出符合“4.%”的字符(%为通配符),MAKE_VERSION 是make 工具的版本号,ubuntu16.04里面默认自带的make 工具版本号为4.1,大家可以输入“make -v”查看。因此$(filter 4.%,$(MAKE_VERSION))不为空,条件成立,执行92~94 行的语句。
第 92 行也是一个判断语句,如果$(filter %s ,$(firstword x$(MAKEFLAGS)))不为空的话条件成立,变量 quiet 等于“silent_”。这里也用到了函数 filter,在$(firstword x$(MAKEFLAGS)))中过滤出符合“%s”的单词。到了函数 firstword,函数 firstword 是获取首单词,函数格式如下:
$(firstword <text>)
firstword 函数用于取出 text 字符串中的第一个单词,函数的返回值就是获取到的单词。当使用“make -s”编译的时候,“-s”会作为 MAKEFLAGS 变量的一部分传递给 Makefile。在顶层 Makfile 中添加如图 31.3.4.1 所示的代码:
图 31.3.4.1 中的两行代码用于输出$(firstword x$(MAKEFLAGS))的结果,最后修改文件mx6ull_alientek_emmc.sh,在里面加入“-s”选项,结果如图 31.3.4.2 所示:
修改完成以后执行 mx6ull_alientek_emmc.sh,结果如图 31.3.4.3 所示:
从图 31.3.4.3 可以看出第一个单词是“xrRs”,将$(filter %s ,$(firstword x$(MAKEFLAGS)))展开就是$(filter %s, xrRs),而$(filter %s, xrRs)的返回值肯定不为空,条件成立,quiet=silent_。
第 101 行使用 export 导出变量 quiet、Q 和 KBUILD_VERBOSE。
5 设置编译结果输出目录
uboot 可以将编译出来的目标文件输出到单独的目录中,在 make 的时候使用“O”来指定输出目录,比如“make O=out”就是设置目标文件输出到 out 目录中。这么做是为了将源文件和编译产生的文件分开,当然也可以不指定 O 参数,不指定的话源文件和编译产生的文件都在同一个目录内,一般我们不指定 O 参数。顶层 Makefile 中相关的代码如下:
待补充。
第 124 行判断“O”是否来自于命令行,如果来自命令行的话条件成立,KBUILD_OUTPUT就为$(O),因此变量 KBUILD_OUTPUT 就是输出目录。
第 135 行判断 KBUILD_OUTPUT 是否为空。
第 139 行调用 mkdir 命令,创建 KBUILD_OUTPUT 目录,并且将创建成功以后的绝对路径赋值给 KBUILD_OUTPUT。至此,通过 O 指定的输出目录就存在了。
6 代码检查
uboot 支持代码检查,使用命令“make C=1”使能代码检查,检查那些需要重新编译的文件。“make C=2”用于检查所有的源码文件,顶层 Makefile 中的代码如下:
待补充。
第 176 行判断 C 是否来源于命令行,如果 C 来源于命令行,那就将 C 赋值给变量KBUILD_CHECKSRC,如果命令行没有 C 的话 KBUILD_CHECKSRC 就为 0。
7 模块编译
在 uboot 中允许单独编译某个模块,使用命令“make M=dir”即可,旧语法“make SUBDIRS=dir”也是支持的。顶层 Makefile 中的代码如下:
待补充。
第 186 行 判 断 是 否 定 义 了 SUBDIRS , 如 果 定 义 了 SUBDIRS ,变量KBUILD_EXTMOD=SUBDIRS,这里是为了支持老语法“make SUBIDRS=dir”
第 190 行判断是否在命令行定义了 M,如果定义了的话 KBUILD_EXTMOD=$(M)。
第 197 行判断 KBUILD_EXTMOD 时为空,如果为空的话目标_all 依赖 all,因此要先编译出 all。否则的话默认目标_all 依赖 modules,要先编译出 modules,也就是编译模块。一般情况下我们不会在 uboot 中编译模块,所以此处会编译 all 这个目标。
第 203 行判断 KBUILD_SRC 是否为空,如果为空的话就设置变量 srctree 为当前目录,即srctree 为“.”,一般不设置 KBUILD_SRC。
第 214 行设置变量 objtree 为当前目录。
第 215 和 216 行分别设置变量 src 和 obj,都为当前目录。
第 218 行设置 VPATH。
第 220 行导出变量 scrtree、objtree 和 VPATH。
8 获取主机架构和系统
接下来顶层 Makefile 会获取主机架构和系统,也就是我们电脑的架构和系统,代码如下:
待补充。
第 227 行定义了一个变量 HOSTARCH,用于保存主机架构,这里调用 shell 命令“uname -m”获取架构名称,结果如图 31.3.8.1 所示:
从图 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 可以看出此时的主机 OS 为“Linux”,使用管道将“Linux”作为后面“tr '[:upper:]' '[:lower:]'”的输入,“tr '[:upper:]' '[:lower:]'”表示将所有的大写字母替换为小写字母,因此得到“linux”。最后同样使用管道,将“linux”作为“sed -e 's/\(cygwin\).*/cygwin/'”的输入,用于将cygwin.*替换为 cygwin。因此,HOSTOS=linux。
第 240 行导出 HOSTARCH=x86_64,HOSTOS=linux。
9 设置目标架构、交叉编译器和配置文件
编 译 uboot 的 时 候 需 要 设 置 目 标 板 架 构 和 交 叉 编 译 器 ,“ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-”就是用于设置 ARCH 和 CROSS_COMPILE,在顶层Makefile 中代码如下:
示例代码 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-linuxgnueabihf-。从示例代码 31.3.9.1 可以看出,每次编译 uboot 的时候都要在 make 命令后面设置ARCH 和 CROSS_COMPILE,使用起来很麻烦,可以直接修改顶层 Makefile,在里面加入 ARCH和 CROSS_COMPILE 的定义,如图 31.3.9.1 所示:
按照图 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 里面的才是实时有效的配置。
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 所示:
在 uboot 的编译过程中会用到 scripts/Kbuild.include 中的这些变量,后面用到的时候再分析。
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
更多待补充。
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 所示的内容:
修改好顶层 Makefile 以后执行如下命令:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mytest
结果如图 31.3.12.2 所示:
从图 31.3.12.2 可以看到这 7 个变量的值,这 7 个变量是从哪里来的呢?在 uboot 根目录下有个文件叫做 config.mk,这 7 个变量就是在 config.mk 里面定义的,打开 config.mk 内容如下:
待补充。
第 25 行 定 义 变 量 ARCH , 值 为 $(CONFIG_SYS_ARCH:"%"=%) , 也 就 是 提 取CONFIG_SYS_ARCH 里面双引号“”之间的内容。比如 CONFIG_SYS_ARCH=“arm”的话,ARCH=arm。
第 26 行定义变量 CPU,值为$(CONFIG_SYS_CPU:"%"=%)。
第 32 行定义变量 BOARD,值为(CONFIG_SYS_BOARD:"%"=%)。
第 34 行定义变量 VENDOR,值为$(CONFIG_SYS_VENDOR:"%"=%)。
第 37 行定义变量 SOC,值为$(CONFIG_SYS_SOC:"%"=%)。
第 44 行定义变量 CPUDIR,值为 arch/$(ARCH)/cpu$(if $(CPU),/$(CPU),)。
第 46 行 sinclude 和 include 的功能类似,在 Makefile 中都是读取指定文件内容,这里读取文件$(srctree)/arch/$(ARCH)/config.mk 的内容。sinclude 读取的文件如果不存在的话不会报错。
第 47 行读取文件$(srctree)/$(CPUDIR)/config.mk 的内容。
第 50 行读取文件$(srctree)/$(CPUDIR)/$(SOC)/config.mk 的内容。
第54行定义变量BOARDDIR ,如果定义了VENDOR那 么BOARDDIR=$(VENDOR)/$(BOARD),否则的 BOARDDIR=$(BOARD)。
第 60 行读取文件$(srctree)/board/$(BOARDDIR)/config.mk。
接下来需要找到 CONFIG_SYS_ARCH、CONFIG_SYS_CPU、CONFIG_SYS_BOARD、CONFIG_SYS_VENDOR 和 CONFIG_SYS_SOC 这 5 个变量的值。这 5 个变量在 uboot 根目录下的.config 文件中有定义,定义如下:
示例代码 31.3.12.3 .config 文件代码 23 CONFIG_SYS_ARCH="arm" 24 CONFIG_SYS_CPU="armv7" 25 CONFIG_SYS_SOC="mx6" 26 CONFIG_SYS_VENDOR="freescale" 27 CONFIG_SYS_BOARD="mx6ullevk " 28 CONFIG_SYS_CONFIG_NAME="mx6ullevk"
根据示例代码 31.3.12.3 可知:
ARCH = arm CPU = armv7 BOARD = mx6ullevk VENDOR = freescale SOC = mx6 CPUDIR = arch/arm/cpu/armv7 BOARDDIR = freescale/mx6ullevk 在 config.mk 中读取的文件有: arch/arm/config.mk arch/arm/cpu/armv7/config.mk arch/arm/cpu/armv7/mx6/config.mk (此文件不存在) board/ freescale/mx6ullevk/config.mk (此文件不存在)
更多待补充。
13 make xxx_defconfig 过程
在编译 uboot 之前要使用“make xxx_defconfig”命令来配置 uboot,那么这个配置过程是如何运行的呢?在顶层 Makefile 中有如下代码:
待补充。
第 422 行定义了变量 version_h,这变量保存版本号文件,此文件是自动生成的。文件include/generated/version_autogenerated.h 内容如图 31.3.13.1 所示:
第 423 行定义了变量 timestamp_h,此变量保存时间戳文件,此文件也是自动生成的。文件include/generated/timestamp_autogenerated.h 内容如图 31.3.13.2 所示:
这部分看得我脑壳子疼,具体直接看正点原子的文档吧。
15 make 过程
配置好 uboot 以后就可以直接 make 编译了,因为没有指明目标,所以会使用默认目标,主Makefile 中的默认目标如下:
示例代码 31.3.15.1 顶层 Makefile 代码段 128 # That's our default target when none is given on the command line 129 PHONY := _all 130 _all:
目标_all 又依赖于 all,如下所示:
示例代码 31.3.15.2 顶层 Makefile 代码段 194 # If building an external module we do not care about the all: rule 195 # but instead _all depend on modules 196 PHONY += all 197 ifeq ($(KBUILD_EXTMOD),) 198 _all: all 199 else 200 _all: modules 201 endif
如 果 KBUILD_EXTMOD 为 空 的 话 _all 依 赖 于 all 。 这 里 不 编 译 模 块 , 所 以KBUILD_EXTMOD 肯定为空,_all 的依赖就是 all。在主 Makefile 中 all 目标规则如下:
看得脑壳子疼,暂时略。
具体看正点原子文档。
关于 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 的流程。至于更具体的实现,有兴趣的可以参考一下其他资料。