LV05-01-uboot-06-顶层Makefile-02-相关过程分析
本文主要是学习uboot顶层Makefile的相关笔记。若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
Windows版本 | windows11 |
Ubuntu版本 | Ubuntu16.04的64位版本 |
VMware® Workstation 16 Pro | 16.2.3 build-19376536 |
终端软件 | MobaXterm(Professional Edition v23.0 Build 5042 (license)) |
Linux开发板 | 正点原子 i.MX6ULL Linux 阿尔法开发板 |
uboot | NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03) |
linux内核 | linux-4.15(NXP官方提供) |
Win32DiskImager | Win32DiskImager v1.0 |
点击查看本文参考资料
分类 | 网址 | 说明 |
官方网站 | https://www.arm.com/ | ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档 |
https://www.nxp.com.cn/ | NXP官方网站 | |
https://www.nxpic.org.cn/ | NXP 官方社区 | |
https://u-boot.readthedocs.io/en/latest/ | u-boot官网 | |
https://www.kernel.org/ | linux内核官网 |
点击查看相关文件下载
分类 | 网址 | 说明 |
NXP | https://github.com/nxp-imx | NXP imx开发资源GitHub组织,里边会有u-boot和linux内核的仓库 |
https://elixir.bootlin.com/linux/latest/source | 在线阅读linux kernel源码 | |
nxp-imx/linux-imx/releases/tag/rel_imx_4.1.15_2.1.0_ga | NXP linux内核仓库tags中的rel_imx_4.1.15_2.1.0_ga | |
nxp-imx/uboot-imx/releases/tag/rel_imx_4.1.15_2.1.0_ga | NXP u-boot仓库tags中的rel_imx_4.1.15_2.1.0_ga | |
I.MX6ULL | i.MX 6ULL Applications Processors for Industrial Products | I.MX6ULL 芯片手册(datasheet,可以在线查看) |
i.MX 6ULL Applications ProcessorReference Manual | I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网) |
一、make xxx_defconfig 过程
在编译 uboot 之前要使用“make xxx_defconfig”命令来配置 uboot,那么这个配置过程是如何运行的呢?
1. 顶层Makefile内容
1.1 422~437行
第 422 行定义了变量 version_h,这变量保存版本号文件,此文件是自动生成的。文件 include/generated/version_autogenerated.h(编译后才会生成)内容如下所示:
1 |
第 423 行定义了变量 timestamp_h,此变量保存时间戳文件,此文件也是自动生成的。文件include/generated/timestamp_autogenerated.h 内容如下:
1 |
第 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_14x14_evk_emmc_defconfig”,那么 MAKECMDGOALS就为mx6ull_14x14_evk_emmc_defconfig。很明显过滤后为空,所以条件不成立,变量 dot-config 依旧为 1。
1.2 439~446行
第439行判断KBUILD_EXTMOD是否为空,如果KBUILD_EXTMOD为空的话条件成立,经过前面的分析,我们知道 KBUILD_EXTMOD 为空,所以条件成立。
第 440 行将 MAKECMDGOALS 中不符合“config”和“%config”的部分过滤掉,如果剩下的部分不为空条件就成立,很明显此处条件成立,变量 config-targets=1。
第 442 行统计 MAKECMDGOALS 中的单词个数,如果不为 1 的话条件成立。此处调用Makefile 中的 words 函数来统计单词个数, words 函数格式如下:
1 | $(words <text>) |
很明显, MAKECMDGOALS 的单词个数是 1 个,所以条件不成立, mixed-targets 继续为0。综上所述,这些变量值如下:
1 | config-targets = 1 |
1.3 448~527行
第 448 行如果变量 mixed-targets 为 1 的话条件成立,很明显,条件不成立。
第 465 行如果变量 config-targets 为 1 的话条件成立,很明显,条件成立,执行这个分支。
第 473 行,没有目标与之匹配,所以不执行。
第 476 行,有目标与之匹配,当输入“make xxx_defconfig”的时候就会匹配到%config 目标,目标“%config”依赖于 scripts_basic、 outputmakefile 和 FORCE。 FORCE 在顶层 Makefile的 1610 行有如下定义:
1 | PHONY += FORCE |
可以看出 FORCE 是没有规则和依赖的,所以每次都会重新生成 FORCE。当 FORCE 作为其他目标的依赖时,由于 FORCE 总是被更新过的,因此依赖所在的规则总是会执行的。
scripts_basic 和 outputmakefile
依赖 scripts_basic 和 outputmakefile 在顶层 Makefile 中的内容如下
第 408 行,判断 KBUILD_SRC 是否为空,只有变量 KBUILD_SRC 不为空的时候outputmakefile 才有意义,经过我们前面的分析 KBUILD_SRC 为空,所以 outputmakefile 无效。只有 scripts_basic 是有效的。
第 396~398 行是 scripts_basic 的规则,其对应的命令用到了变量 Q、 MAKE 和 build,其中:
1 | Q=@或为空 |
变量 build 是在 scripts/Kbuild.include 文件中有定义,定义如下:
从上图的示例代码(Kbuild.include 代码段)可以看出 build=-f $(srctree)/scripts/Makefile.build obj,经过前面的分析可知,变量 srctree 为”.”,因此:
1 | build=-f ./scripts/Makefile.build obj |
scripts_basic 展开以后如下:
1 | scripts_basic: |
scripts_basic 会调用文件./scripts/Makefile.build 这个后边会再分析。
接着回到继续看上边顶层Makefile中的%config 处,内容如下:
1 | %config: scripts_basic outputmakefile FORCE |
将命令展开就是 :
1 | @make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig |
同样也跟文件./scripts/Makefile.build 有关,我们后面再分析此文件。使用如下命令配置 uboot,并观察其配置过程:
1 | make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean |
然后就可以看到如下配置过程:
(1)scripts_basic 目标对应的命令
1 | @make -f ./scripts/Makefile.build obj=scripts/basic |
(2) %config 目标对应的命令
1 | @make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig |
2. Makefile.build 脚本分析
从前边知道“ make xxx_defconfig“配置 uboot 的时候如下两行命令会执行脚本scripts/Makefile.build:
1 | @make -f ./scripts/Makefile.build obj=scripts/basic |
2.1 scripts_basic 目标对应的命令
2.1.1 8~17行
scripts_basic 目标对应的命令为: @make -f ./scripts/Makefile.build obj=scripts/basic。打开文件 scripts/Makefile.build,有如下代码:
第 9 行定义了变量 prefix 值为 tpl。
第 10 行定义了变量 src,这里用到了函数 patsubst,此行代码展开后为:
1 | $(patsubst tpl/%,%, scripts/basic) |
patsubst 是替换函数,格式如下:
1 | $(patsubst <pattern>,<replacement>,<text>) |
此函数用于在 text 中查找符合 pattern 的部分,如果匹配的话就用 replacement 替换掉。pattenr 是可以包含通配符“%”,如果 replacement 中也包含通配符“%”,那么 replacement 中的这个“%”将是 pattern 中的那个“%”所代表的字符串。函数的返回值为替换后的字符串。因此,第 10 行就是在“scripts/basic”中查找符合“tpl/%”的部分,然后将“tpl/”取消掉,但是“scripts/basic”没有“tpl/”,所以 src= scripts/basic。
第 11 行判断变量 obj 和 src 是否相等,相等的话条件成立,很明显,此处条件成立。
第 12 行和第 9 行一样,只是这里处理的是“spl”,“scripts/basic”里面也没有“spl/”,所以src 继续为 scripts/basic。
第 15 行因为变量 obj 和 src 相等,所以 prefix=.。
2.1.2 56~59行
将 kbuild-dir 展开后为:
1 | $(if $(filter /%, scripts/basic), scripts/basic, ./scripts/basic) |
因为没有以“ / ”为开头的单词,所以$(filter /%, scripts/basic)的结果为空, kbuilddir=./scripts/basic。
将 kbuild-file 展开后为:
1 | $(if $(wildcard ./scripts/basic/Kbuild), ./scripts/basic/Kbuild, ./scripts/basic/Makefile) |
因为 scrpts/basic 目录中没有 Kbuild 这个文件,所以 kbuild-file= ./scripts/basic/Makefile。最后将 59 行展开,即:
1 | include ./scripts/basic/Makefile |
也就是读取 scripts/basic 下面的 Makefile 文件。
2.1.3 116~119行
__build 是默认目标,因为命令“@make -f ./scripts/Makefile.build obj=scripts/basic”没有指定目标,所以会使用到默认目标: __build。在顶层 Makefile 中, KBUILD_BUILTIN 为 1,KBUILD_MODULES 为 0,因此展开后目标__build 为:
1 | __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 中输入图所示内容,将这 5 个变量的值打印出来:
执行以下命令:
1 | make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean |
从上图可以看出,只有 always 有效,因此__build 最终为:
1 | __build: scripts/basic/fixdep |
__build 依赖于 scripts/basic/fixdep,所以要先编译 scripts/basic/fixdep.c,生成 fixdep,前面已经读取了 scripts/basic/Makefile 文件。综上所述, scripts_basic 目标的作用就是编译出 scripts/basic/fixdep 这个软件。
2.2 %config 目标对应的命令
%config 目 标 对 应 的 命 令 为 :
1 | @make -f ./scripts/Makefile.build obj=scripts/kconfigxxx_defconfig |
各个变量值如下:
1 | src= scripts/kconfig |
可以看出, Makefilke.build 会读取 scripts/kconfig/Makefile 中的内容,此文件有如下所示内容:
目标%_defconfig 刚好和我们输入的 xxx_defconfig 匹配,所以会执行这条规则。依赖为$(obj)/conf,展开后就是 scripts/kconfig/conf。接下来就是检查并生成依赖 scripts/kconfig/conf。conf 是主机软件,到这里我们就打住,不要纠结 conf 是怎么编译出来的,否则就越陷越深,太绕了,像 conf 这种主机所使用的工具类软件我们一般不关心它是如何编译产生的。如果一定要看是 conf 是怎么生成的,可以输入如下命令重新配置 uboot,在重新配置 uboot 的过程中就会输出 conf 编译信息。
1 | make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean |
得到 scripts/kconfig/conf 以后就要执行目标%_defconfig 的命令:
1 | $(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig) |
相关的变量值如下:
1 | silent=-s 或为空 |
将其展开就是 :
1 | @ scripts/kconfig/conf --defconfig=arch/../configs/xxx_defconfig Kconfig |
上述命令用到了 xxx_defconfig 文件,比如 mx6ull_14x14_evk_emmc_defconfig 。这里会将mx6ull_14x14_evk_emmc_defconfig 中的配置输出到.config 文件中,最终生成 uboot 根目录下的.config 文件。
3. 过程总结
这个就是命令 make xxx_defconfig 执行流程 :
二、 make过程分析
1. 目标
配置好 uboot 以后就可以直接 make 编译了,因为没有指明目标,所以会使用默认目标,主Makefile 中的默认目标如下:
目标_all 又依赖于 all,如下所示 :
如果 KBUILD_EXTMOD 为空的话 _all 依赖于 all 。这里不编译模块,所 以KBUILD_EXTMOD 肯定为空, _all 的依赖就是 all。在主 Makefile 中 all 目标规则如下:
从 802 行可以看出, all 目标依赖$(ALL-y),而在顶层 Makefile 中, ALL-y 如下 :
可以看出, ALL-y 包含 u-boot.srec、 u-boot.bin、 u-boot.sym、System.map、 u-boot.cfg 和 binary_size_check 这几个文件。根据 uboot 的配置情况也可能包含其他的文件,比如:
1 | 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”,所以展开以后就是:
1 | ALL-y += u-boot-onenand.bin |
这个就是.config 里面的配置参数的含义,这些参数其实都是变量,后面跟着变量值,会在顶层 Makefile 或者其他 Makefile 中调用这些变量。
2. u-boot.bin
ALL-y 里面有个 u-boot.bin,这个就是我们最终需要的 uboot 二进制可执行文件,所作的所有工作就是为了它。在顶层 Makefile 中找到 u-boot.bin 目标对应的规则,如下所示:
第 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 中的定义如下:
点击查看if_changed 定义
1 | 226 ### |
第 227 行为 if_changed 的描述,根据描述,在一些先决条件比目标新的时候,或者命令行有改变的时候, if_changed 就会执行一些命令。
第 257 行就是函数 if_changed, if_changed 函数引用的变量比较多,也比较绕,我们只需要知道它可以从 u-boot-nodtb.bin 生成 u-boot.bin 就行了。
3. u-boot-nodtb.bin
既然 u-boot.bin 依赖于 u-boot-nodtb.bin,那么肯定要先生成 u-boot-nodtb.bin 文件,顶层Makefile 中相关代码如下:
目标 u-boot-nodtb.bin 又依赖于 u-boot,顶层 Makefile 中 u-boot 相关规则如下:
目标 u-boot 依赖于 u-boot_init、 u-boot-main 和 u-boot.lds, u-boot_init 和 u-boot-main 是两个变量,在顶层 Makefile 中有定义,值如下:
$(head-y)跟 CPU 架构有关,我们使用的是 ARM 芯片,所以 head-y 在 arch/arm/Makefile 中被指定为:
1 | head-y := arch/arm/cpu/$(CPU)/start.o |
根据《LV05-01-uboot-06-顶层Makefile-01-基础分析》——六、变量分析一小节的分析,我们知道 CPU=armv7,因此 head-y 展开以后就是:
1 | head-y := arch/arm/cpu/armv7/start.o |
所以就有:
1 | u-boot-init= arch/arm/cpu/armv7/start.o |
$(libs-y)在顶层 Makefile 中被定义为 uboot 所有子目录下 build-in.o 的集合,代码如下:
从上面的代码可以看出, libs-y 都是 uboot 各子目录的集合,最后:
1 | 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 文件的集合。那么 uboot-main 就等于所有子目录中 built-in.o 的集合。
这个规则就相当于将以 u-boot.lds 为链接脚本,将 arch/arm/cpu/armv7/start.o 和各个子目录下的 built-in.o 链接在一起生成 u-boot。u-boot.lds 的规则如下:
接下来的重点就是各子目录下的 built-in.o 是怎么生成的,以 drivers/gpio/built-in.o 为例,在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就可以看到链接的过程:
1 | make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean |
可以看出最终是用 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 很类似,这里就不再详细说明了。
4. 过程总结
make xxx_defconfig: 用于配置 uboot,这个命令最主要的目的就是生成.config 文件。
make:用于编译 uboot,这个命令的主要工作就是生成二进制的 u-boot.bin 文件和其他的一些与 uboot 有关的文件,比如 u-boot.imx 等等。