LV05-03-Kernel-03-01-顶层Makefile基础解析
本文主要是kernel——顶层Makefile基础解析的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
PC端开发环境 | Windows | Windows11 |
Ubuntu | Ubuntu20.04.2的64位版本 | |
VMware® Workstation 17 Pro | 17.6.0 build-24238078 | |
终端软件 | MobaXterm(Professional Edition v23.0 Build 5042 (license)) | |
Win32DiskImager | Win32DiskImager v1.0 | |
Linux开发板环境 | Linux开发板 | 正点原子 i.MX6ULL Linux 阿尔法开发板 |
uboot | NXP官方提供的uboot,使用的uboot版本为U-Boot 2019.04 | |
linux内核 | linux-4.19.71(NXP官方提供) |
点击查看本文参考资料
分类 | 网址 | 说明 |
官方网站 | 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内核的仓库 |
nxp-imx/linux-imx/releases/tag/v4.19.71 | NXP linux内核仓库tags中的v4.19.71 | |
nxp-imx/uboot-imx/releases/tag/rel_imx_4.19.35_1.1.0 | NXP u-boot仓库tags中的rel_imx_4.19.35_1.1.0 | |
I.MX6ULL | i.MX 6ULL Applications Processors for Industrial Products | I.MX6ULL 芯片手册(datasheet,可以在线查看) |
i.MX 6ULL Applications ProcessorReference Manual | I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网) | |
Source Code | https://elixir.bootlin.com/linux/latest/source | linux kernel源码 |
https://elixir.bootlin.com/u-boot/latest/source | uboot源码 |
一、概述
其实大概翻一下linux内核的顶层 Makefile 就会发现,它和u-boot的顶层 Makefile 结构是很类似的,大部分都差不多,之前已经详细去分析过u-boot的顶层Makefile了,可以看这里:LV05-02-U-Boot-06-01-顶层Makefile基础解析 | 苏木。这里一些基础的东西就不再详细分析了。
二、make xxx_defconfig
1. 打印信息
先看一下配置过程的打印信息吧:
1 | make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean |
我们会在终端看到以下打印信息:
1 | make -f ./scripts/Makefile.build obj=scripts/basic |
后面我们分析的时候会用到。
2. %config目标
第一次编译 Linux 之前都要使用“make xxx_defconfig”先配置 Linux 内核,在顶层 Makefile 中有“%config”这个目标。它定义在 Makefile - %config:
1 | %config: scripts_basic outputmakefile FORCE |
“%config”依赖 scripts_basic、outputmakefile 和 FORCE,“%config”真正有意义的依赖就只有 scripts_basic。其他的这几个变量这里直接加打印信息来看吧,就不逐个分析了:
1 | %config: scripts_basic outputmakefile FORCE |
然后执行:
1 | make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean |
就会看到如下信息:
1 | Q= |
所以上面的规则展开就是:
1 | %config: scripts_basic outputmakefile FORCE |
3. build变量
这个build变量定义在 Kbuild.include - scripts/Kbuild.include - build:
1 | ### |
这个srctree是定义在顶层 Makefile - srctree 中,分析之后srctree=.
。所以这里就是:
1 | build := -f ./scripts/Makefile.build obj |
4. scripts_basic
接下来看一下这个scripts_basic规则,它定义在 Makefile - scripts_basic :
1 | # Basic helpers built in scripts/basic/ |
前面我们已经打印了相关的变量的值,所以这里其实就是:
1 | scripts_basic: |
5. Makefile.build
从前面的%config和scripts_basic目标可以知道,“make xxx_defconfig“配置 Linux 的时候如下两行命令会执行脚本 Makefile.build - scripts/Makefile.build :
1 | @make -f ./scripts/Makefile.build obj=scripts/basic |
5.1 scripts_basic 目标对应的命令
scripts_basic 目标对应的命令为:
1 | @make -f ./scripts/Makefile.build obj=scripts/basic |
我们打开 Makefile.build - scripts/Makefile.build 文件,会看到这两个变量:
1 | # The filename Kbuild has precedence over Makefile |
这里的src变量定义在 Makefile.build - scripts/Makefile.build - src
1 | src := $(obj) |
所以 src=scripts/basic,此时我们将 kbuild-dir 和 kbuild-file 展开:
1 | kbuild-dir=./scripts/basic |
继续往后分析,就可以看到这个目标Makefile.build - scripts/Makefile.build - __build:
1 | # We keep a list of all modules in $(MODVERDIR) |
__build 是默认目标,因为命令“@make -f ./scripts/Makefile.build obj=scripts/basic”没有指定目标,所以会使用到默认目标__build。在顶层 Makefile 中,KBUILD_BUILTIN 为 1, KBUILD_MODULES 为空,因此展开后目标__build 为:
1 | __build:$(builtin-target) $(lib-target) $(extra-y)) $(subdir-ym) $(always) |
可以看出目标__build 有 5 个依赖:builtin-target、lib-target、extra-y、subdir-ym 和 always。我们直接添加打印信息看一下:
1 | __build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \ |
然后执行:
1 | make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean |
会看到这 5 个依赖的具体内容如下:
1 | builtin-target= |
所以最终的__build为:
1 | __build: scripts/basic/fixdep |
__build 依赖于 scripts/basic/fixdep,所以要先将 scripts/basic/fixdep文件编译成 fixdep。
综上所述,scripts_basic 目标的作用就是编译出 scripts/basic/fixdep软件。
5.2 %config 目标对应的命令
%config 目标对应的命令为:
1 | make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig |
我们推测一下,Makefile.build - scripts/Makefile.build里面应该有一个defconfig相关的目标才对,但是去搜一下好像没有,我们和前面一样,先看一下Makefile.build - scripts/Makefile.build 中的kbuild-dir和kbuild-file:
1 | kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src)) |
我们知道srctree=.
,src=scripts/kconfig,所以这里展开就是:
1 | kbuild-dir = ./scripts/kconfig |
可以看出,Makefile.build 会读取 scripts/kconfig/Makefile 中的内容,此文件有如下所示内容Makefile - scripts/kconfig/Makefile - %_defconfig:
1 | %_defconfig: $(obj)/conf |
目标%_defconfig 与 xxx_defconfig 匹配,所以会执行这条规则,SRCARCH定义在Makefile - SRCARCH,我们编译的是ARM平台,这里就有ARCARCH=arm。Kconfig定义在Makefile - scripts/kconfig/Makefile - Kconfig,最后它的值为Kconfig,这里将其展开就是:
1 | %_defconfig: scripts/kconfig/conf |
%_defconfig依赖scripts/kconfig/conf,所以会编译scripts/kconfig/conf.c生成conf这个软件。 此软件就会将 %_defconfig 中的配置输出到.config 文件中,最终生成 Linux kernel 根目录下的.config 文件。具体怎么生成的这里就没有仔细去研究了,大概知道这里是这样生成的就可以了。
三、make all
使用命令“make xxx_defconfig”配置好 Linux 内核以后就可以使用“make”或者“make all” 命令进行编译。
1. _all目标
我们先来看一下顶层Makefile中关于_all目标的一些相关的内容,它在 Makefile - _all 和Makefile - _all:
1 | # That's our default target when none is given on the command line |
_all 是默认目标,如果使用命令“make”编译 Linux 的话此目标就会被匹配。继续往下的话,我们看到还有一个变量来控制默认目标的依赖,如果 KBUILD_EXTMOD 为空的话 ifeq中的(1)代码成立。添加打印信息就会发现这里我们执行make all的话,(1)是成立的,所以_all目标又依赖于all。
2. all目标
我们再看一下all目标,它定义在 Makefile - all :
1 | # The all: target is the default when no target is given on the |
目标 all 依赖 vmlinux,所以接下来的重点就是 vmlinux!
3. vmlinux目标
3.1 vmlinux简单展开
vmlinux目标定义在 Makefile - vmlinux:
1 | vmlinux: scripts/link-vmlinux.sh autoksyms_recursive $(vmlinux-deps) FORCE |
可以看出目标 vmlinux 依赖 scripts/link-vmlinux.sh、vmlinux-deps和FORCE,其中vmlinux-deps定义在 Makefile - vmlinux-deps :
1 | vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN) $(KBUILD_VMLINUX_LIBS) |
这几个变量都定义在这个位置 Makefile - Externally visible symbols:
1 | # Externally visible symbols (used by link-vmlinux.sh) |
前面我们知道 SRCARCH=arm,所以KBUILD_LDS:= arch/arm/kernel/vmlinux.lds,所以vmlinux的依赖为:
1 | scripts/link-vmlinux.sh autoksyms_recursive $(head-y) $(init-y) $(core-y) $(libs-y2) $(drivers-y) $(net-y) $(virt-y) $(libs-y1) arch/$(SRCARCH)/kernel/vmlinux.lds FORCE |
我们搜索一下 CONFIG_HEADERS_CHECK 和 CONFIG_GDB_SCRIPTS,会发现NXP官方的evk评估板所对应的默认配置文件 imx_v6_v7_defconfig 是没有这两个定义的,最终的.config中也没有配置这两个选项。这里就先不管了,然后我们展开一下这个vmlinux:
1 | vmlinux: scripts/link-vmlinux.sh autoksyms_recursive $(head-y) $(init-y) $(core-y) $(libs-y2) $(drivers-y) $(net-y) $(virt-y) $(libs-y1) arch/$(SRCARCH)/kernel/vmlinux.lds FORCE |
当依赖准备好后,调用if_changed这个函数就是生成vmlinux。
3.2 依赖的变量
3.2.1 head-y
head-y这个变量是定义在 Makefile - arch/arm/Makefile - head-y:
1 | #Default value |
当不使能 MMU 的话 MMUEXT=-nommu,如果使能 MMU 的话为空,因此 head-y 最终的 值为:
1 | head-y = arch/arm/kernel/head.o |
3.2.2 init-y、drivers-y 和 net-y
init-y、drivers-y 和 net-y这三个变量是定义在 Makefile - Objects we will link into vmlinux / subdirs we need to visit:
1 | init-y := init/ |
还有一个位置也有,在这里Makefile:
1 | init-y := $(patsubst %/, %/built-in.a, $(init-y)) |
所以init-y、drivers-y 和 net-y 最终的值为:
1 | nit-y = init/built-in.a |
3.2.3 libs-y1和libs-y2
libs-y1、 libs-y2基本和 init-y 一样,在顶层 Makefile - libs-y1和libs-y2 中有如下定义:
1 | libs-y1 := $(patsubst %/, %/lib.a, $(libs-y)) |
其中libs-y定义在Makefile - libs-y:
1 | libs-y := lib/ |
另外,这个libs-y在Makefile - arch/arm/Makefile - libs-y 中也有定义:
1 | libs-y := arch/arm/lib/ $(libs-y) |
最终libs-y的值为:
1 | libs-y = arch/arm/lib lib/ |
libs-y2中filter-out是反过滤函数,就是匹配除.a结尾的文件以外的文件,然后再匹配目录中的built-in.a,所以这里就有
1 | libs-y1 := arch/arm/lib/lib.a lib/lib.a |
3.2.4 core-y
core-y定义在 Makefile - Objects we will link into vmlinux / subdirs we need to visit:
1 | core-y := usr/ |
在 Makefile - core-y 这里会追加一些值:
1 | core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/ |
在 Makefile - arch/arm/Makefile - core-y 也会追加一些值:
1 | core-$(CONFIG_FPE_NWFPE) += arch/arm/nwfpe/ |
这里有一部分根据不同的配置向 core-y 追加不同的值,比如使能 VFP 的话就会在.config 中有 CONFIG_VFP=y 这一行,那么 core-y 就会追加“arch/arm/vfp/”。另外一部分就是对 core-y 直接追加的值。所以总的来说:
1 | core-y := usr/ |
最后有一句Makefile - core-y:
1 | core-y := $(patsubst %/, %/built-in.a, $(core-y)) |
这里是core-y最终匹配的文件,就是匹配上面所有的目录中的built-in.a文件。
3.2.5 总结
这些变量都是一些 built-in.o 或.a 等文件,这个和 uboot 一样,都是将相应目录中的源码文件进行编译,然后在各自目录下生成 built-in.o 文件,有些生成了.a 库文件。最终将这些 built-in.o 和.a 文 件进行链接即可形成 ELF 格式的可执行文件,也就是 vmlinux!但是链接是需要链接脚本的, vmlinux 的依赖 arch/arm/kernel/vmlinux.lds 就是整个 Linux 的链接脚本。
3.3 if_changed函数
接下来我们来看一下if_changed函数,前面我们将vmlinux目标的规则展开得到:
1 | vmlinux: scripts/link-vmlinux.sh autoksyms_recursive $(head-y) $(init-y) $(core-y) $(libs-y2) $(drivers-y) $(net-y) $(virt-y) $(libs-y1) arch/$(SRCARCH)/kernel/vmlinux.lds FORCE |
这里+
表示该命令结果不可忽略。这里表示将if_changed函数的结果作为最终生成 vmlinux 的命令。,link-vmlinux 是函数 if_changed 的 参数,函数 if_changed 定义在文件 Kbuild.include - scripts/Kbuild.include - if_changed 中:
1 | # Execute command if command has changed or prerequisite(s) are updated. |
any-prereq 用于检查依赖文件是否有变化,如果依赖文件有变化那么 any-prereq 就不为 空,否则就为空。
arg-check 用于检查参数是否有变化,如果没有变化那么 arg-check 就为空。
@set -e
告诉 bash,如果任何语句的执行结果不为 true(也就是执行出错)的 话就直接退出。$(echo-cmd)
用于打印命令执行过程,比如在链接 vmlinux 的时候就会输出 “LINK vmlinux”。$(cmd_$(1))
中的$(1)
表示参数,也就是 link-vmlinux,因此$(cmd_$(1))
表示 执行 cmd_link-vmlinux 的内容。
3.3.1 any-prereq
any-prereq定义在 Kbuild.include - scripts/Kbuild.include - any-prereq:
1 | # Find any prerequisites that is newer than target or that does not exist. |
3.3.2 arg-check
arg-check定义在 Kbuild.include - scripts/Kbuild.include - arg-check:
1 | ifneq ($(KBUILD_NOCMDDEP),1) |
3.3.3 cmd_$(1)
接下来看一下这个cmd_$(1)
,前面我们知道这个是 cmd_link-vmlinux,它定义在 Makefile - cmd_link-vmlinux:
1 | # Final link of vmlinux with optional arch pass after final link |
- CONFIG_SHELL定义在Makefile - CONFIG_SHELL
1 | # SHELL used by kbuild |
最终CONFIG_SHELL=/bin/bash。
$<
表示目标 vmlinux 的第一个依赖文件:
1 | vmlinux: scripts/link-vmlinux.sh autoksyms_recursive $(vmlinux-deps) FORCE |
在这里$<
就是scripts/link-vmlinux.sh。
- LD就是链接工具,就定义在Makefile - LD,这里就是LD= arm-linux-gnueabihf-ld。
- KBUILD_LDFLAGS定义在Makefile - KBUILD_LDFLAGS,在Makefile - arch/arm/Makefile - KBUILD_LDFLAGS中有追加,最终它的值为-EL。
- LDFLAGS_vmlinux的值由顶层 Makefile 和 Makefile - arch/arm/Makefile 这两个文件共同决定。
其实这个地方由于涉及的地方较多,我们可以加个打印信息,这个 Makefile - arch/arm/Makefile 文件在Makefile中被调用,LDFLAGS_vmlinux在 Makefile - LDFLAGS_vmlinux 这里被导出,我们可以加个打印信息看一下:
1 | echo "LDFLAGS_vmlinux=$(LDFLAGS_vmlinux)" |
然后我们执行以下命令:
1 | make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean |
会看到以下输出信息。
1 | LDFLAGS_vmlinux=-p --no-undefined -X --pic-veneer --build-id |
- ARCH_POSTLINK定义在Makefile - ARCH_POSTLINK
1 | ARCH_POSTLINK := $(wildcard $(srctree)/arch/$(SRCARCH)/Makefile.postlink) |
srctree=.
,SRCARCH=arm,所以这里就是./arch/arm/Makefile.postlink,这个应该是编译生成的文件吧,我也不是很清楚,我去使用V=1参数编译了一下,没有在编译过程中发现这个文件,也没有在编译完成后在 ./arch/arm 目录下发现这个文件,所以这里这个ARCH_POSTLINK应该是为空。
$@
就是目标文件vmlinux
所以展开就是:
1 | # Final link of vmlinux with optional arch pass after final link |
3.3.4 link-vmlinux.sh
我们来看一下这个脚本,它在 link-vmlinux.sh - scripts/link-vmlinux.sh:
1 | Link of vmlinux |
vmliux_link 就是最终链接出 vmlinux 的函数。
第 9 行判断 SRCARCH 是否等于“um”,如 果不相等的话就执行 20~34 行的代码。因为 SRCARCH=arm,因此条件成立,执行10~19 行的代码。
第18 ~ 19行的代码就是我们之前的链接操作。链接脚本lds= ./arch/arm/kernel/vmlinux.lds。需要链接的文件由变量 KBUILD_VMLINUX_INIT 和 KBUILD_VMLINUX_MAIN 来决定。
KBUILD_VMLINUX_INIT定义在Makefile - KBUILD_VMLINUX_INIT:
1 | export KBUILD_VMLINUX_INIT := $(head-y) $(init-y) |
KBUILD_VMLINUX_MAIN定义在Makefile - KBUILD_VMLINUX_MAIN:
1 | export KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y2) $(drivers-y) $(net-y) $(virt-y) |
前面基本已经分析过了,但 $(virt-y)
这个刚才没分析,不过都是一样的道理。
- 第 37 行调用 vmlinux_link 函数来链接出 vmlinux。
3.4 打印信息
上面分析了那么多,我们详细来看一下打印信息吧,我们添加个起始和结尾标记,方便查找:
1 | vmlinux: scripts/link-vmlinux.sh autoksyms_recursive $(vmlinux-deps) FORCE |
然后我们执行以下命令:
1 | make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean |
然后就可以看到打印信息中这个vmlinux规则都在做些什么,这里是一些简化后的重要打印信息:
1 | echo "========== vmlinux-start ==========" |
从这里看,前面分析的一些变量的值都是正确的。
4. built-in.a生成过程
4.1 vmlinux-deps变量
前面在分析Makefile - vmlinux的时候:
1 | vmlinux: scripts/link-vmlinux.sh autoksyms_recursive $(vmlinux-deps) FORCE |
vmlinux是依赖vmlinux-deps的,而vmlinux-deps定义在 Makefile - vmlinux-deps :
1 | vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN) $(KBUILD_VMLINUX_LIBS) |
这几个变量都定义 Makefile - Externally visible symbols:
1 | # Externally visible symbols (used by link-vmlinux.sh) |
上面些我们前面都分析过了,他们最终是一系列的built-in.a
文件。我们可以加个打印信息看一眼:
1 | vmlinux: scripts/link-vmlinux.sh autoksyms_recursive $(vmlinux-deps) FORCE |
然后我们执行以下命令:
1 | make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean |
可以看到有如下打印信息:
1 | vmlinux-deps=arch/arm/kernel/vmlinux.lds arch/arm/kernel/head.o init/built-in.a usr/built-in.a arch/arm/vfp/built-in.a arch/arm/vdso/built-in.a arch/arm/kernel/built-in.a arch/arm/mm/built-in.a arch/arm/common/built-in.a arch/arm/probes/built-in.a arch/arm/net/built-in.a arch/arm/crypto/built-in.a arch/arm/firmware/built-in.a arch/arm/mach-imx/built-in.a kernel/built-in.a certs/built-in.a mm/built-in.a fs/built-in.a ipc/built-in.a security/built-in.a crypto/built-in.a block/built-in.a arch/arm/lib/built-in.a lib/built-in.a drivers/built-in.a sound/built-in.a firmware/built-in.a net/built-in.a virt/built-in.a arch/arm/lib/lib.a lib/lib.a |
除了 arch/arm/kernel/vmlinux.lds 以外,其他都是要编译链接生成的。
4.2 vmlinux-deps的依赖
我们继续往下看vmlinux-deps,会看到
1 | # The actual objects are generated when descending, |
sort函数就是用来排序的,它一般格式如下:
1 | $(sort <list>) |
给字符串 list 中的单词排序(升序),会返回排序后的字符串。另外sort函数会去掉 list 中相同的单词。另外可以看出 vmlinux-deps 依赖 vmlinux-dirs。
4.3 vmlinux-dirs变量
vmlinux-dirs 也定义在顶层 Makefile - vmlinux-dirs:
1 | vmlinux-dirs := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \ |
vmlinux-dirs 看名字就知道和目录有关,此变量保存着生成 vmlinux 所需源码文件的目录。这里不分析了,直接添加打印信息吧,还是在这个vmlinux目标这里加:
1 | vmlinux: scripts/link-vmlinux.sh autoksyms_recursive $(vmlinux-deps) FORCE |
和前面一样,我们编译一遍,会得到如下打印信息:
1 | vmlinux-dirs=init usr arch/arm/vfp arch/arm/vdso arch/arm/kernel arch/arm/mm arch/arm/common arch/arm/probes arch/arm/net arch/arm/crypto arch/arm/firmware arch/arm/mach-imx kernel certs mm fs ipc security crypto block drivers sound firmware net arch/arm/lib lib virt |
4.4 vmlinux-dirs的依赖
4.4.1 规则展开
我们继续往后看,会看到vmlinux-dirs也是有依赖的,它定义在Makefile - vmlinux-dirs:
1 | # Handle descending into subdirectories listed in $(vmlinux-dirs) |
目标 vmlinux-dirs 依赖 prepare 和 scripts 。这两个依赖就暂时不去详细了解了。我们重点看一下第9行的命令。build前面已经说过了,它定义在Kbuild.include - scripts/Kbuild.include - build,值为” -f ./scripts/Makefile.build obj “,所以这一行命令展开就是:
1 | @make -f ./scripts/Makefile.build obj=$@ need-builtin=1 |
$@
表示目标文件,也就是 vmlinux-dirs 的值,将 vmlinux-dirs 中的这些目录全部带入到命令中,结果如下:
1 | @make -f ./scripts/Makefile.build obj=init need-builtin=1 |
这些命令运行过程其实都是一样的,我们就以“@ make -f ./scripts/Makefile.build obj=init need-builtin=1”这个命令为例 来分析一下。
4.4.2 命令分析
我们先看这个 Makefile.build - scripts/Makefile.build中的默认目标__build:
1 | __build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \ |
当只编译 Linux 内核镜像文件,也就是使用“ make zImage ” 编译的时候,KBUILD_BUILTIN=1, KBUILD_MODULES 为空。“make”命令是会编译所有的东西,包括 Linux 内核镜像文件和一些模块文件。我们这里简单一点,如果只编译 Linux 内核镜像的话, __build 目标简化为:
1 | __build: $(builtin-target) $(lib-target) $(extra-y)) $(subdir-ym) $(always) |
重点来看一下 builtin-target 这个依赖, builtin-target 同样定义在文件 Makefile.build - scripts/Makefile.build - builtin-target 中 :
1 | ifneq ($(strip $(real-obj-y) $(need-builtin)),) |
第2行就是 builtin-target 变量的值,为$(obj)/built-in.a
,这就是这些 built-in.a 的来源了。要生成 built-in.a,要求real-obj-y 和need-builtin 变量不能全部为空。我们以这条命令为例:
1 | @make -f ./scripts/Makefile.build obj=init need-builtin=1 |
可以看到这里obj=init,need-builtin=1,所以上面的builtin-target可以展开一下:
1 | ifneq ($(strip $(real-obj-y) 1),) |
那 built-in.a 是怎么生成的?我们继续看这个builtin-target,在文件 Makefile.build - scripts/Makefile.build 中有如下代码:
1 | # |
可以看到第14行,目标就是 builtin-target,依赖为 real-obj-y,命令为“$(call if_changed,ar_builtin)
”,前面我们分析过这个if_changed函数,这里参数为 ar_builtin,其返回值就是具体的命令。 我们可以放到这里再看一下 Kbuild.include - scripts/Kbuild.include - if_changed :
1 | # Execute command if command has changed or prerequisite(s) are updated. |
它会调用 cmd_$(1)
所对应的命令($(1)
就是函数的第 1 个参数),在这里就是调用 cmd_ar_builtin 所对应的命令。这里面的命令就是使用AR工具生成.a
文件
4.5 打印信息
我们在终端执行:
1 | make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean |
然后我们会看到以下打印信息:
1 | rm -f init/built-in.a; arm-linux-gnueabihf-ar rcSTPD init/built-in.a init/main.o init/version.o init/do_mounts.o init/do_mounts_rd.o init/do_mounts_initrd.o init/initramfs.o init/calibrate.o init/init_task.o |
这个就是最后将相关的.o链接在一起生成的init/built-in.a文件的打印信息。命令 arm-linux-gnueabihf-ar rcSTPD
中,rcSTPD
是一系列选项的组合,用于指定 arm-linux-gnueabihf-ar
工具的操作模式。下面是每个选项的含义:
r
:替换。如果指定的库文件中已经存在要添加的文件,则替换它。c
:创建。创建一个新的库文件。如果指定的库文件不存在,则创建它;如果已存在,则根据其他选项(如r
)更新它。S
:创建索引。为库文件创建一个目标文件索引,这可以加快之后链接时库文件的搜索速度。T
:使用给定的文件名作为库文件的临时索引文件。通常,这个选项后面会跟着一个文件名。但在你提供的命令中,T
后面没有直接跟随文件名,这可能是一个错误或者遗漏,或者是在特定上下文中有特殊用法。P
:保留路径。在将文件添加到库中时,保留文件的路径信息。默认情况下,ar
工具会将文件路径剥离,只保留文件名。使用P
选项可以保留完整的路径。D
:通常D
选项用于删除库中的文件。这里提及是为了完整性,因为D
是ar
工具的一个常用选项。
四、make zImage
1. vmlinux、 Image, zImage、 uImage 的区别
其实前面已经大概了解过了:
- vmlinux 是编译出来的最原始的 ELF格式内核文件,是未压缩的。
- Image 是 Linux 内核镜像文件,但是 Image 仅包含可执行的二进制数据。 Image 就是使用 objcopy 取消掉 vmlinux 中的一些其他信息,比如符号表什么的。但是 Image 是没有压缩过的, Image 保存在 arch/arm/boot 目录下 。
- zImage 是经过 gzip 压缩后的 Image 。
- uImage 是老版本 uboot 专用的镜像文件, uImag 是在 zImage 前面加了一个长度为 64字节的“头”,这个头信息描述了该镜像文件的类型、加载位置、生成时间、大小等信息。但是新的 uboot 已经支持了 zImage 启动!所以已经很少用到 uImage 了,除非我们用的很古老的 uboot。
所以一般我们还是使用zImage这个镜像文件。
2. zImage
使用“ make”、“ make all”、“ make zImage”这些命令都可以编译出 zImage 镜像, 我们来看一下 BOOT_TARGETS 变量,它定义在Makefile - arch/arm/Makefile - BOOT_TARGETS:
1 | BOOT_TARGETS = zImage Image xipImage bootpImage uImage |
第 1 行。变量 BOOT_TARGETS 包含 zImage, Image, xipImage 等镜像文件。
第 9 行, BOOT_TARGETS 依赖 vmlinux,因此如果使用“make zImage”编译的 Linux 内核的话,首先肯定要先编译出 vmlinux。
第 10 行,具体的命令,比如要编译 zImage,那么命令展开以后如下所示:
1 | @ make -f ./scripts/Makefile.build obj=arch/arm/boot MACHINE=arch/arm/boot/zImage |
和前面一样,都是使用scripts/Makefile.build完成vmlinux到zImage的转换。