综述
u-boot
自v2014.10
版本开始引入KBuild
系统,同更改前的编译系统相比,由于Kbuild
系统的原因,其Makefile
变得更加复杂。
u-boot的编译跟kernel编译一样,分两步执行:
- 第一步:配置,执行
make xxx_defconfig
进行各项配置,生成.config
文件 - 第二步:编译,执行
make
进行编译,生成可执行的二进制文件u-boot.bin或u-boot.elf
Makefile的核心是依赖和命令。对于每个目标,首先会检查依赖,如果依赖存在,则执行命令更新目标;如果依赖不存在,则会以依赖为目标,先生成依赖,待依赖生成后,再执行命令生成目标。
-
博客《u-boot-2016.09 make配置过程分析》详尽解释了第一步的操作,在这一步中,u-boot执行配置命令
make xxx_defconfig
时先搜集所有默认的Kconfig
配置,然后再用命令行指定的xxx_defconfig
配置进行更新并输出到根目录的.config
文件中。 -
配置完成后执行make命令生成二进制文件的过程,由于涉及的依赖和命令很多,也将make编译过程分析分为两部分,目标依赖和命令执行。
- 博客《u-boot-2016.09 make编译过程分析(一)》中描述了
make
过程中的依赖关系 - 本篇主要分析
make
过程中的通过命令生成各个目标的依赖,从而一步一步更新目标,直至更新并生成顶层目标u-boot.bin/u-boot.imx
。
- 博客《u-boot-2016.09 make编译过程分析(一)》中描述了
-
本文主要介绍第二步—编译之第二部分:命令执行。
第二部分、执行命令更新目标
将上面的依赖关系并到一起,就得到了一个完整的u-boot
目标依赖图:
(完整的关系图较大,可以将图片拖到浏览器的其他窗口看大图)
这些依赖有两类:
- 依赖本身通过执行命令生成,但不存在进一步的依赖;
- 依赖自身还有进一步的依赖,在生成了进一步依赖的基础上,执行命令生成依赖;
完成目标依赖分析后,剩下的就是基于完整的目标依赖关系图,从最底层的依赖开始,逐层运行命令生成目标,直到生成顶层目标。
《u-boot-2016.09 make编译过程分析(一)》分析依赖关系时采用自顶向下的方法,从顶层目标开始到最原始的依赖结束。
此处采用自下而上的方式,先从最原始的依赖开始,一步一步,执行命令生成目标。
1. prepare
系列目标依赖
完整的prepare
系列的目标依赖:
依次从最右边的依赖说起:
1.1 scripts/kconfig/conf
生成的文件
.config
.config
在执行make rpi_3_32b_defconfig
配置时生成,scripts/kconfig/Makefile
中有规则:
- 1
- 2
- 1
- 2
这里展开后为:
- 1
- 2
- 1
- 2
scripts/kconfig/conf
会从根目录开始读取Kconfig
,输出到根目录下的.config
中。
include/generated/autoconf.h
include/config/auto.conf.cmd
include/config/tristate.conf
include/config/auto.conf
以上4个文件在执行make
编译命令的开始会检查%.conf
的依赖规则:
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
调用make -f ./Makefile silentoldconfig
的最终结果是执行scripts/kconfig/Makefile
中的规则:
- 1
- 2
- 3
- 1
- 2
- 3
这个规则展开为:
- 1
- 2
- 3
- 1
- 2
- 3
scripts/kconfig/conf
会从根目录开始读取Kconfig
,同时检查并更新配置阶段生成的.config
文件,再把最终结果输出到以上的4个文件中。
所生成的4个文件中,
include/config/auto.conf
依赖于include/config/auto.conf.cmd
,但是这里的依赖文件include/config/auto.conf.cmd
文件并非由fixdep
生成,而是直接由conf
工具生成,算是*.cmd
文件生成的特例。
scripts/kconfig/conf
生成了图中右侧的依赖:include/config/auto.conf
,$(KCONIFG_CONFIG)/.config
和include/config/auto.conf.cmd
1.2 目标include/config/auto.conf
的规则
在生成include/config/auto.conf
的规则中:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
除了执行$(MAKE) -f $(srctree)/Makefile silentoldconfig
外,还执行$(MAKE) -f $(srctree)/scripts/Makefile.autoconf
在scripts/Makefile.autoconf
的头部是这样的:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
此处没有设置CONFIG_SPL=y
或CONFIG_TPL=y
,所以整个makefile
的__all
的依赖有:
include/autoconf.mk
include/autoconf.mk.dep
然而include/autoconf.mk
还要进一步依赖于config.h
1.2.1 include/config.h
的规则
所有的autoconf.mk
都依赖于include/config.h
(rpi_3_32b_defconfig
配置只需要include/autoconf.mk
):
- 1
- 2
- 1
- 2
实际上include/config.h
由宏filechk_config_h
生成:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
最终生成的include/config.h
也比较简单,不妨看看:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
生成config.h
之前,还要应用create_symlink
生成相应的符号链接。
1.2.2 create_symlink
的规则
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
注释已经很好解释了create_symlink
的行为:
- 如果
arch/$(ARCH)/math-$(SOC)/include/mach
存在,则生成符号链接:arch/$(ARCH)/include/asm/arch --> arch/$(ARCH)/math-$(SOC)
- 否则生成符号链接
arch/$(ARCH)/include/asm/arch --> arch/$(ARCH)
对基于arm v7
架构的bcm2837
芯片,arch/arm/math-bcm283x
文件夹存在,所以生成链接:
arch/arm/include/asm --> arch/arm/mach-bcm283x/include/mach
简单说来,create_symlink
就是将芯片指定的arch/$(ARCH)math-$(SOC)
连接到跟芯片名字无关的arch/$(ARCH)/include/asm
下。
1.2.3 include/autoconf.mk
的规则
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
从cmd_autoconf
来看,这里会根据include/common.h
的依赖,然后调用tools/scripts/define2mk.sed
,并合并之前生成的include/config/auto.conf
生成最终的autoconf.mk
1.2.4 include/autoconf.mk.dep
的规则
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
这个规则比较简单,由于autoconf.mk
由common.h
和auto.conf
而来,因此直接处理这两个文件的依赖并合并到autoconf.mk.dep
中。
1.3 include/config/uboot.release
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
命令$(call filechk,uboot.release)
展开后就是调用宏filechk_uboot.release
,最终将字符串2016.09
写入include/config/uboot.release
中。
1.4 timestamp.h
和version.h
的规则
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- include/generated/version_autogenerated.h
根据include/config/uboot.release
文件,规则调用filechk_version.h
宏生成版本相关字符串文件include/generated/version_autogenerated.h
,如下:
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
- include/generated/timestamp_autogenerated.h
调用宏filechk_timestamp.h
生成编译的时间戳文件,如下:
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
1.5 outputmakefile
的规则
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 如果编译没有设置
O
,即输出和代码都在同一个目录下,则outputmakefile
的规则什么都不做; - 如果编译指定了输出目录
O
,则调用scripts/mkmakefile
在O
选项指定的目录下生成一个简单的makefile
1.6 scripts_basic
的规则
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
scripts_basic
的执行结果就是编译生成scripts/basic/fixdep
工具,该工具是u-boot
编译系统中最常用的工具,用于在编译过程中修正每一个生成文件的依赖关系。
1.7 parepare0
的规则
- 1
- 2
- 1
- 2
展开后为:
- 1
- 2
- 1
- 2
编译时,命令make -f ./scripts/Makefile.build obj=.
不会生成任何目标。
1.8 prepare
系列目标总结
prepare
阶段主要做了以下工作:
scripts_basic
规则生成fixdep
工具,用于对整个系统生成目标文件相应依赖文件的更新;- 配置阶段,
scripts/kconfig/conf
根据传入的指定配置文件在根目录下生成.config
文件 - 编译阶段,
scripts/kconfig/conf
读取配置阶段生成的.config
,并检查最新配置生成以下文件:
include/generated/autoconf.h
include/config/auto.conf.cmd
include/config/tristate.conf
include/config/auto.conf
- 调用宏
filechk_config_h
生成include/config.h
文件 - 调用命令
cmd_autoconf_dep
生成autoconf.mk
和autoconf.mk.cmd
文件 - 调用宏
filechk_uboot.release
生成include/config/uboot.release
文件 - 调用宏
filechk_version.h
生成include/generated/version_autogenerated.h
文件 - 调用宏
filechk_timestamp.h
生成include/generated/timestamp_autogenerated.h
文件 - 调用宏
create_symlink
将芯片指定的arch/ (ARCH)math− (SOC)连接到跟芯片名字无关的arch/$(ARCH)/include/asm下
2. u-boot
文件系列目标依赖
从图上可见,除了prepare
依赖外,u-boot
还依赖于文件$(head-y)
,$(libs-y)
和$(LDSCRIPT)
,即依赖于:
- 启动文件
arch/arm/cpu/$(CPU)/start.o
- 各个目录下的
build-in.o
- 链接脚本文件
arch/arm/cpu/u-boot.lds
2.1 启动文件start.o
$(head-y)
在arch/arm/Makefile
中被直接指定:
- 1
- 1
在顶层makefile
中被指定给变量u-boot-init
:
- 1
- 1
2.2 各目录下的build-in.o
$(libs-y)
在顶层的makefile
中被指定为各个子目录下的build-in.o
的集合:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
以上脚本中,先将$(libs-y)
设置为各子目录的集合,最后调用patsubst
函数将$(libs-y)
设置为这些目录下的built-in.o
文件的集合,最后赋值给变量u-boot-main
作为链接的主体文件。
- 各目录下的
built-in.o
是如何生成的呢?
以drivers/mmc/built-in.o
为例,先查看生成的依赖文件drivers/mmc/.built-in.o.cmd
:
drivers/mmc/.built-in.o.cmd的生成方式还不了解,待确认后我会更新。
- 1
- 2
- 1
- 2
从生成命令cmd_drivers/mmc/built-in.o
可以看到,built-in.o
是由目录下各个编译生成的*.o
文件通过链接操作ld -r
而来。
ld
的-r
选项是什么作用呢?
在ld
的手册中是这样介绍-r
选项的:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
简单说来,ld
通过-r
选项来产生可重定位的输出,相当于部分链接。
在这里就是通过ld -r
选项将目录drivers/mmc/
下的*.o
文件先链接为单一文件build-in.o
,但其并不是最终的生成文件,而是一个可进行重定位的文件.在下一阶段的链接中,ld
会将各个目录下的built-in.o
链接生成最终的u-boot
。
built-in.o
的规则
生成built-in.o
的规则在scripts/Makefile.build
中定义:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
2.3 链接脚本u-boot.lds
链接脚本的规则如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
2.4 生成u-boot
规则
顶层Makefile
中定义了生成u-boot
文件的规则:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
u-boot
文件的生成很简单,调用ld
命令,将$(u-boot-init)
和$(u-boot-main)
指定的一系列文件通过脚本u-boot.lds
连接起来。
u-boot
针对raspberry pi 3
生成的命令是这样的(由于原命令太长,这里用\
分割为多行):
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
参数说明:
-Ttext 0x00008000:指定链接的起始地址为0x00008000,即代码存放地址;
-T u-boot.lds:指定了链接使用的脚本文件,实际上,在这个脚本文件的
.text
节开始前同样也指定了起始地址
生成了u-boot
文件后,后续就是针对u-boot
文件的各种处理了。
3. 顶层目标依赖
显然,在生成了u-boot
的基础上,进一步生成所需要的各种目标文件:
u-boot.srec
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
调用objcopy
命令,通过-O ihex
或-O srec
指定生成u-boot.hex
或u-boot.srec
格式文件。
u-boot.sym
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
调用$(OBJDUMP)
命令生成符号表文件u-boot.sym
。
System.map
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
调用$(NM)
命令打印u-boot
文件的符号表,并用grep -v
处理后得到System.map
文件,里面包含了最终使用到的各个符号的位置信息。
u-boot.bin
和u-boot-nodtb.bin
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
由于这里没有使用device tree
设置,即编译没有定义CONFIG_OF_SEPARATE
,因此u-boot.bin
和u-boot-nodtb.bin
是一样的。
至于生成u-boot-nodtb.bin
的规则:
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
显然,u-boot-nodtb.bin
是u-boot
文件通过objcopy
得到。
u-boot.cfg
u-boot.cfg
中包含了所有用到的宏定义,其生成规则如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
因此,阅读源码时如果不确定某个宏的值,可以检查u-boot.cfg
文件。
自此,生成了所有的目标文件,完成了整个编译过程的分析。
在原博主的基础上,有以下新增心得。
附注
1 u-boot.imx文件如何生成的?
对于有些开发板,如天嵌的IMX6Q、E9等,使用的烧写工具是mfgtools工具来烧写u-boot,烧写的u-boot文件为u-boot.imx。那么u-boot.imx文件是怎么生成的呢?
在u-boot-2017.05中(其他很多相邻版本u-boot一样的),顶层Makefile文件中有如下编译语句:
%.imx: %.bin
$(Q)$(MAKE) $(build)=arch/arm/imx-common $@
该编译语句展开后如下:
u-boot.imx: u-boot.bin
$(Q)make -f ./scripts/Makefile.build obj=arch/arm/imx-common u-boot.imx
由此得到了u-boot.imx文件。
该编译过程实际上就是在u-boot.bin文件编译后,在u-boot.bin的开头添加一个大小为1K的IVT头,用于告诉BOOT ROM找到uboot的位置和函数,要运行在什么模式,DRAM的配置数据等。新生成的文件就是u-boot.imx文件。