LV080-顶层Makefile基础解析
本节使用的是 4.19.71 版本的内核。
一、概述
其实大概翻一下 linux 内核的顶层 Makefile 就会发现,它和 u-boot 的顶层 Makefile 结构是很类似的,大部分都差不多,之前已经详细去分析过 u-boot 的顶层 Makefile 了,可以看这里:15-系统镜像/10-uboot/LV100-顶层Makefile基础解析.md。这里一些基础的东西就不再详细分析了。
二、make xxx_defconfig
1. 打印信息
先看一下配置过程的打印信息吧:
make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v6_v7_defconfig我们会在终端看到以下打印信息:
make -f ./scripts/Makefile.build obj=scripts/basic
gcc -Wp,-MD,scripts/basic/.fixdep.d -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu89 -o scripts/basic/fixdep scripts/basic/fixdep.c
rm -f .tmp_quiet_recordmcount
make -f ./scripts/Makefile.build obj=scripts/kconfig imx_v6_v7_defconfig
gcc -Wp,-MD,scripts/kconfig/.conf.o.d -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu89 -c -o scripts/kconfig/conf.o scripts/kconfig/conf.c
bison -oscripts/kconfig/zconf.tab.c -t -l scripts/kconfig/zconf.y
flex -oscripts/kconfig/zconf.lex.c -L scripts/kconfig/zconf.l
gcc -Wp,-MD,scripts/kconfig/.zconf.tab.o.d -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu89 -Iscripts/kconfig -c -o scripts/kconfig/zconf.tab.o scripts/kconfig/zconf.tab.c
gcc -o scripts/kconfig/conf scripts/kconfig/conf.o scripts/kconfig/zconf.tab.o
scripts/kconfig/conf --defconfig=arch/arm/configs/imx_v6_v7_defconfig Kconfig
#
# configuration written to .config
#后面我们分析的时候会用到。
2. %config 目标
第一次编译 Linux 之前都要使用“make xxx_defconfig”先配置 Linux 内核,在顶层 Makefile 中有“%config”这个目标。它定义在 Makefile - %config:
%config: scripts_basic outputmakefile FORCE
$(Q)$(MAKE) $(build)=scripts/kconfig $@“%config”依赖 scripts_basic、outputmakefile 和 FORCE,“%config”真正有意义的依赖就只有 scripts_basic。其他的这几个变量这里直接加打印信息来看吧,就不逐个分析了:
%config: scripts_basic outputmakefile FORCE
@echo "Q=$(Q)"
@echo "MAKE=$(MAKE)"
@echo "build=$(build)"
@echo "@=$@"
$(Q)$(MAKE) $(build)=scripts/kconfig $@然后执行:
make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v6_v7_defconfig就会看到如下信息:
Q=
MAKE=make
build=-f ./scripts/Makefile.build obj
@=imx_v6_v7_defconfig所以上面的规则展开就是:
%config: scripts_basic outputmakefile FORCE
make -f ./scripts/Makefile.build obj=scripts/kconfig imx_v6_v7_defconfig # 也可以有@,视配置而定3. build 变量
这个 build 变量定义在 Kbuild.include - scripts/Kbuild.include - build:
###
# Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj=
# Usage:
# $(Q)$(MAKE) $(build)=dir
build := -f $(srctree)/scripts/Makefile.build obj这个 srctree 是定义在顶层 Makefile - srctree 中,分析之后 srctree = .。所以这里就是:
build := -f ./scripts/Makefile.build obj4. scripts_basic
接下来看一下这个 scripts_basic 规则,它定义在 Makefile - scripts_basic :
# Basic helpers built in scripts/basic/
PHONY += scripts_basic
scripts_basic:
$(Q)$(MAKE) $(build)=scripts/basic
$(Q)rm -f .tmp_quiet_recordmcount前面我们已经打印了相关的变量的值,所以这里其实就是:
scripts_basic:
@make -f ./scripts/Makefile.build obj=scripts/basic # 也可以没有@,视配置而定
@rm -f . tmp_quiet_recordmcount # 也可以没有@5. Makefile.build
从前面的%config 和 scripts_basic 目标可以知道,“make xxx_defconfig“配置 Linux 的时候如下两行命令会执行脚本 Makefile.build - scripts/Makefile.build :
@make -f ./scripts/Makefile.build obj=scripts/basic
@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig5.1 scripts_basic 目标对应的命令
scripts_basic 目标对应的命令为:
@make -f ./scripts/Makefile.build obj=scripts/basic我们打开 Makefile.build - scripts/Makefile.build 文件,会看到这两个变量:
# The filename Kbuild has precedence over Makefile
kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
include $(kbuild-file)这里的 src 变量定义在 Makefile.build - scripts/Makefile.build - src
src := $(obj)所以 src = scripts/basic,此时我们将 kbuild-dir 和 kbuild-file 展开:
kbuild-dir=./scripts/basic
kbuild-file=./scripts/basic/Makefile
include ./scripts/basic/Makefile继续往后分析,就可以看到这个目标 Makefile.build - scripts/Makefile.build - __build:
# We keep a list of all modules in $(MODVERDIR)
__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
$(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
$(subdir-ym) $(always)
@:__build 是默认目标,因为命令“@make -f ./scripts/Makefile.build obj = scripts/basic”没有指定目标,所以会使用到默认目标__build。在顶层 Makefile 中,KBUILD_BUILTIN 为 1, KBUILD_MODULES 为空,因此展开后目标__build 为:
__build:$(builtin-target) $(lib-target) $(extra-y)) $(subdir-ym) $(always)
@:可以看出目标__build 有 5 个依赖:builtin-target、lib-target、extra-y、subdir-ym 和 always。我们直接添加打印信息看一下:
__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
$(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
$(subdir-ym) $(always)
@:
@echo "builtin-target="$(builtin-target)
@echo "lib-target="$(lib-target)
@echo "extra-y="$(extra-y)
@echo "subdir-ym="$(subdir-ym)
@echo "always="$(always)然后执行:
make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v6_v7_defconfig会看到这 5 个依赖的具体内容如下:
builtin-target=
lib-target=
extra-y=
subdir-ym=
always=scripts/basic/fixdep所以最终的__build 为:
__build: scripts/basic/fixdep
@:__build 依赖于 scripts/basic/fixdep,所以要先将 scripts/basic/fixdep 文件编译成 fixdep。
综上所述,scripts_basic 目标的作用就是编译出 scripts/basic/fixdep 软件。
5.2 %config 目标对应的命令
%config 目标对应的命令为:
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:
kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
include $(kbuild-file)我们知道 srctree = .,src = scripts/kconfig,所以这里展开就是:
kbuild-dir = ./scripts/kconfig
kbuild-file = ./scripts/kconfig/Makefile
include ./scripts/kconfig/Makefile可以看出,Makefile.build 会读取 scripts/kconfig/Makefile 中的内容,此文件有如下所示内容 Makefile - scripts/kconfig/Makefile - %_defconfig:
%_defconfig: $(obj)/conf
$(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)目标%_defconfig 与 xxx_defconfig 匹配,所以会执行这条规则,SRCARCH 定义在 Makefile - SRCARCH,我们编译的是 ARM 平台,这里就有 ARCARCH = arm。Kconfig 定义在 Makefile - scripts/kconfig/Makefile - Kconfig,最后它的值为 Kconfig,这里将其展开就是:
%_defconfig: scripts/kconfig/conf
@ scripts/kconfig/conf --defconfig=arch/arm/configs/%_defconfig Kconfig%_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:
# That's our default target when none is given on the command line
PHONY := _all
_all:
# ......
# If building an external module we do not care about the all: rule
# but instead _all depend on modules
PHONY += all
ifeq ($(KBUILD_EXTMOD),)
_all: all # (1)
else
_all: modules # (2)
endif_all 是默认目标,如果使用命令“make”编译 Linux 的话此目标就会被匹配。继续往下的话,我们看到还有一个变量来控制默认目标的依赖,如果 KBUILD_EXTMOD 为空的话 ifeq 中的(1)代码成立。添加打印信息就会发现这里我们执行 make all 的话,(1)是成立的,所以_all 目标又依赖于 all。
2. all 目标
我们再看一下 all 目标,它定义在 Makefile - all :
# The all: target is the default when no target is given on the
# command line.
# This allow a user to issue only 'make' to build a kernel including modules
# Defaults to vmlinux, but the arch makefile usually adds further targets
all: vmlinux目标 all 依赖 vmlinux,所以接下来的重点就是 vmlinux!
3. vmlinux 目标
3.1 vmlinux 简单展开
vmlinux 目标定义在 Makefile - vmlinux:
vmlinux: scripts/link-vmlinux.sh autoksyms_recursive $(vmlinux-deps) FORCE
ifdef CONFIG_HEADERS_CHECK
$(Q)$(MAKE) -f $(srctree)/Makefile headers_check
endif
ifdef CONFIG_GDB_SCRIPTS
$(Q)ln -fsn $(abspath $(srctree)/scripts/gdb/vmlinux-gdb.py)
endif
+$(call if_changed,link-vmlinux)可以看出目标 vmlinux 依赖 scripts/link-vmlinux.sh、vmlinux-deps 和 FORCE,其中 vmlinux-deps 定义在 Makefile - vmlinux-deps :
vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN) $(KBUILD_VMLINUX_LIBS)这几个变量都定义在这个位置 Makefile - Externally visible symbols:
# Externally visible symbols (used by link-vmlinux.sh)
export KBUILD_VMLINUX_INIT := $(head-y) $(init-y)
export KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y2) $(drivers-y) $(net-y) $(virt-y)
export KBUILD_VMLINUX_LIBS := $(libs-y1)
export KBUILD_LDS := arch/$(SRCARCH)/kernel/vmlinux.lds前面我们知道 SRCARCH = arm,所以 KBUILD_LDS:= arch/arm/kernel/vmlinux.lds,所以 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我们搜索一下 CONFIG_HEADERS_CHECK 和 CONFIG_GDB_SCRIPTS,会发现 NXP 官方的 evk 评估板所对应的默认配置文件 imx_v6_v7_defconfig 是没有这两个定义的,最终的.config 中也没有配置这两个选项。这里就先不管了,然后我们展开一下这个 vmlinux:
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
+$(call if_changed,link-vmlinux)当依赖准备好后,调用 if_changed 这个函数就是生成 vmlinux。
3.2 依赖的变量
3.2.1 head-y
head-y 这个变量是定义在 Makefile - arch/arm/Makefile - head-y:
#Default value
head-y := arch/arm/kernel/head$(MMUEXT).o当不使能 MMU 的话 MMUEXT =-nommu,如果使能 MMU 的话为空,因此 head-y 最终的 值为:
head-y = arch/arm/kernel/head.o3.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:
init-y := init/
drivers-y := drivers/ sound/ firmware/
net-y := net/还有一个位置也有,在这里 Makefile:
init-y := $(patsubst %/, %/built-in.a, $(init-y))
core-y := $(patsubst %/, %/built-in.a, $(core-y))
drivers-y := $(patsubst %/, %/built-in.a, $(drivers-y))
net-y := $(patsubst %/, %/built-in.a, $(net-y))所以 init-y、drivers-y 和 net-y 最终的值为:
nit-y = init/built-in.a
drivers-y = drivers/built-in.a sound/built-in.a firmware/built-in.a
net-y = net/built-in.a3.2.3 libs-y1 和 libs-y2
libs-y1、 libs-y2 基本和 init-y 一样,在顶层 Makefile - libs-y1 和 libs-y2 中有如下定义:
libs-y1 := $(patsubst %/, %/lib.a, $(libs-y))
libs-y2 := $(patsubst %/, %/built-in.a, $(filter-out %.a, $(libs-y)))其中 libs-y 定义在 Makefile - libs-y:
libs-y := lib/另外,这个 libs-y 在 Makefile - arch/arm/Makefile - libs-y 中也有定义:
libs-y := arch/arm/lib/ $(libs-y)最终 libs-y 的值为:
libs-y = arch/arm/lib lib/libs-y2 中 filter-out 是反过滤函数,就是匹配除.a 结尾的文件以外的文件,然后再匹配目录中的 built-in.a,所以这里就有
libs-y1 := arch/arm/lib/lib.a lib/lib.a
libs-y2 := arch/arm/lib/built-in.a lib/built-in.a3.2.4 core-y
core-y 定义在 Makefile - Objects we will link into vmlinux / subdirs we need to visit:
core-y := usr/在 Makefile - core-y 这里会追加一些值:
core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/
#......
core-y := $(patsubst %/, %/built-in.a, $(core-y))在 Makefile - arch/arm/Makefile - core-y 也会追加一些值:
core-$(CONFIG_FPE_NWFPE) += arch/arm/nwfpe/
core-$(CONFIG_FPE_FASTFPE) += $(FASTFPE_OBJ)
core-$(CONFIG_VFP) += arch/arm/vfp/
core-$(CONFIG_XEN) += arch/arm/xen/
core-$(CONFIG_KVM_ARM_HOST) += arch/arm/kvm/
core-$(CONFIG_VDSO) += arch/arm/vdso/
# If we have a machine-specific directory, then include it in the build.
core-y += arch/arm/kernel/ arch/arm/mm/ arch/arm/common/
core-y += arch/arm/probes/
core-y += arch/arm/net/
core-y += arch/arm/crypto/
core-y += arch/arm/firmware/
core-y += $(machdirs) $(platdirs)这里有一部分根据不同的配置向 core-y 追加不同的值,比如使能 VFP 的话就会在.config 中有 CONFIG_VFP = y 这一行,那么 core-y 就会追加“arch/arm/vfp/”。另外一部分就是对 core-y 直接追加的值。所以总的来说:
core-y := usr/
core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/
core-y += arch/arm/kernel/ arch/arm/mm/ arch/arm/common/
core-y += arch/arm/probes/
core-y += arch/arm/net/
core-y += arch/arm/crypto/
core-y += arch/arm/firmware/
core-y += $(machdirs) $(platdirs)
core-$(CONFIG_FPE_NWFPE) += arch/arm/nwfpe/
core-$(CONFIG_FPE_FASTFPE) += $(FASTFPE_OBJ)
core-$(CONFIG_VFP) += arch/arm/vfp/
core-$(CONFIG_XEN) += arch/arm/xen/
core-$(CONFIG_KVM_ARM_HOST) += arch/arm/kvm/
core-$(CONFIG_VDSO) += arch/arm/vdso/最后有一句 Makefile - core-y:
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 目标的规则展开得到:
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
+$(call if_changed,link-vmlinux)这里 + 表示该命令结果不可忽略。这里表示将 if_changed 函数的结果作为最终生成 vmlinux 的命令。,link-vmlinux 是函数 if_changed 的 参数,函数 if_changed 定义在文件 Kbuild.include - scripts/Kbuild.include - if_changed 中:
# Execute command if command has changed or prerequisite(s) are updated.
if_changed = $(if $(strip $(any-prereq) $(arg-check)), \
@set -e; \
$(echo-cmd) $(cmd_$(1)); \
printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd, @:)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:
# Find any prerequisites that is newer than target or that does not exist.
# PHONY targets skipped in both cases.
any-prereq = $(filter-out $(PHONY),$?) $(filter-out $(PHONY) $(wildcard $^),$^)3.3.2 arg-check
arg-check 定义在 Kbuild.include - scripts/Kbuild.include - arg-check:
ifneq ($(KBUILD_NOCMDDEP),1)
# Check if both arguments are the same including their order. Result is empty
# string if equal. User may override this check using make KBUILD_NOCMDDEP=1
arg-check = $(filter-out $(subst $(space),$(space_escape),$(strip $(cmd_$@))), \
$(subst $(space),$(space_escape),$(strip $(cmd_$1))))
else
arg-check = $(if $(strip $(cmd_$@)),,1)
endif3.3.3 cmd_$(1)
接下来看一下这个 cmd_$(1),前面我们知道这个是 cmd_link-vmlinux,它定义在 Makefile - cmd_link-vmlinux:
# Final link of vmlinux with optional arch pass after final link
cmd_link-vmlinux = \
$(CONFIG_SHELL) $< $(LD) $(KBUILD_LDFLAGS) $(LDFLAGS_vmlinux) ; \
$(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true)- CONFIG_SHELL 定义在 Makefile - CONFIG_SHELL
# SHELL used by kbuild
CONFIG_SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \
else if [ -x /bin/bash ]; then echo /bin/bash; \
else echo sh; fi ; fi)最终 CONFIG_SHELL =/bin/bash。
$<表示目标 vmlinux 的第一个依赖文件:
vmlinux: scripts/link-vmlinux.sh autoksyms_recursive $(vmlinux-deps) FORCE
ifdef CONFIG_HEADERS_CHECK
$(Q)$(MAKE) -f $(srctree)/Makefile headers_check
endif
ifdef CONFIG_GDB_SCRIPTS
$(Q)ln -fsn $(abspath $(srctree)/scripts/gdb/vmlinux-gdb.py)
endif
+$(call if_changed,link-vmlinux)在这里 $< 就是 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 这里被导出,我们可以加个打印信息看一下:
echo "LDFLAGS_vmlinux=$(LDFLAGS_vmlinux)"
+$(call if_changed,link-vmlinux)然后我们执行以下命令:
make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v6_v7_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16会看到以下输出信息。
LDFLAGS_vmlinux=-p --no-undefined -X --pic-veneer --build-id- ARCH_POSTLINK 定义在 Makefile - ARCH_POSTLINK
ARCH_POSTLINK := $(wildcard $(srctree)/arch/$(SRCARCH)/Makefile.postlink)srctree = .,SRCARCH = arm,所以这里就是./arch/arm/Makefile.postlink,这个应该是编译生成的文件吧,我也不是很清楚,我去使用 V = 1 参数编译了一下,没有在编译过程中发现这个文件,也没有在编译完成后在 ./arch/arm 目录下发现这个文件,所以这里这个 ARCH_POSTLINK 应该是为空。
$@就是目标文件 vmlinux
所以展开就是:
# Final link of vmlinux with optional arch pass after final link
cmd_link-vmlinux = \
/bin/bash scripts/link-vmlinux.sh -p --no-undefined -X --pic-veneer --build-id; \
true3.3.4 link-vmlinux.sh
我们来看一下这个脚本,它在 link-vmlinux.sh - scripts/link-vmlinux.sh:
# Link of vmlinux
# ${1} - optional extra .o files
# ${2} - output file
vmlinux_link()
{
local lds="${objtree}/${KBUILD_LDS}"
local objects
if [ "${SRCARCH}" != "um" ]; then
objects="--whole-archive \
built-in.a \
--no-whole-archive \
--start-group \
${KBUILD_VMLINUX_LIBS} \
--end-group \
${1}"
${LD} ${KBUILD_LDFLAGS} ${LDFLAGS_vmlinux} -o ${2} \
-T ${lds} ${objects}
else
objects="-Wl,--whole-archive \
built-in.a \
-Wl,--no-whole-archive \
-Wl,--start-group \
${KBUILD_VMLINUX_LIBS} \
-Wl,--end-group \
${1}"
${CC} ${CFLAGS_vmlinux} -o ${2} \
-Wl,-T,${lds} \
${objects} \
-lutil -lrt -lpthread
rm -f linux
fi
}
# ......
info LD vmlinux
vmlinux_link "${kallsymso}" vmlinuxvmliux_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:
export KBUILD_VMLINUX_INIT := $(head-y) $(init-y)KBUILD_VMLINUX_MAIN 定义在 Makefile - KBUILD_VMLINUX_MAIN:
export KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y2) $(drivers-y) $(net-y) $(virt-y)前面基本已经分析过了,但 $(virt-y) 这个刚才没分析,不过都是一样的道理。
- 第 37 行调用 vmlinux_link 函数来链接出 vmlinux。
3.4 打印信息
上面分析了那么多,我们详细来看一下打印信息吧,我们添加个起始和结尾标记,方便查找:
vmlinux: scripts/link-vmlinux.sh autoksyms_recursive $(vmlinux-deps) FORCE
echo "========== vmlinux-start =========="
ifdef CONFIG_HEADERS_CHECK
$(Q)$(MAKE) -f $(srctree)/Makefile headers_check
endif
ifdef CONFIG_GDB_SCRIPTS
$(Q)ln -fsn $(abspath $(srctree)/scripts/gdb/vmlinux-gdb.py)
endif
echo "========== vmlinux-if_changed =========="
+$(call if_changed,link-vmlinux)
echo "========== vmlinux-end =========="然后我们执行以下命令:
make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v6_v7_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16然后就可以看到打印信息中这个 vmlinux 规则都在做些什么,这里是一些简化后的重要打印信息:
echo "========== vmlinux-start =========="
========== vmlinux-start ==========
echo "========== vmlinux-if_changed =========="
========== vmlinux-if_changed ==========
/bin/sh scripts/link-vmlinux.sh arm-linux-gnueabihf-ld -EL -p --no-undefined -X --pic-veneer --build-id ; true
+ '[' arm-linux-gnueabihf-ld = clean ']'
+ case "${KCONFIG_CONFIG}" in
+ . ./.config
++ CONFIG_CC_IS_GCC=y
++ CONFIG_GCC_VERSION=40902
# 中间部分省略......
+ local objects
+ '[' arm '!=' um ']'
# 中间部分省略......
+ arm-linux-gnueabihf-ld -EL -p --no-undefined -X --pic-veneer --build-id -o vmlinux -T ./arch/arm/kernel/vmlinux.lds --whole-archive built-in.a --no-whole-archive --start-group arch/arm/lib/lib.a lib/lib.a --end-group .tmp_kallsyms2.o
+ '[' -n y ']'
+ info SORTEX vmlinux
+ '[' '' '!=' silent_ ']'
+ printf ' %-7s %s\n' SORTEX vmlinux
SORTEX vmlinux
+ sortextable vmlinux
+ ./scripts/sortextable vmlinux
+ info SYSMAP System.map
# 中间部分省略......
echo "========== vmlinux-end =========="
========== vmlinux-end ==========从这里看,前面分析的一些变量的值都是正确的。
4. built-in.a 生成过程
4.1 vmlinux-deps 变量
前面在分析 Makefile - vmlinux 的时候:
vmlinux: scripts/link-vmlinux.sh autoksyms_recursive $(vmlinux-deps) FORCE
ifdef CONFIG_HEADERS_CHECK
$(Q)$(MAKE) -f $(srctree)/Makefile headers_check
endif
ifdef CONFIG_GDB_SCRIPTS
$(Q)ln -fsn $(abspath $(srctree)/scripts/gdb/vmlinux-gdb.py)
endif
+$(call if_changed,link-vmlinux)vmlinux 是依赖 vmlinux-deps 的,而 vmlinux-deps 定义在 Makefile - vmlinux-deps :
vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN) $(KBUILD_VMLINUX_LIBS)这几个变量都定义 Makefile - Externally visible symbols:
# Externally visible symbols (used by link-vmlinux.sh)
export KBUILD_VMLINUX_INIT := $(head-y) $(init-y)
export KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y2) $(drivers-y) $(net-y) $(virt-y)
export KBUILD_VMLINUX_LIBS := $(libs-y1)
export KBUILD_LDS := arch/$(SRCARCH)/kernel/vmlinux.lds上面些我们前面都分析过了,他们最终是一系列的 built-in.a 文件。我们可以加个打印信息看一眼:
vmlinux: scripts/link-vmlinux.sh autoksyms_recursive $(vmlinux-deps) FORCE
@echo "vmlinux-deps=$(vmlinux-deps)"
ifdef CONFIG_HEADERS_CHECK
$(Q)$(MAKE) -f $(srctree)/Makefile headers_check
endif
ifdef CONFIG_GDB_SCRIPTS
$(Q)ln -fsn $(abspath $(srctree)/scripts/gdb/vmlinux-gdb.py)
endif
+$(call if_changed,link-vmlinux)然后我们执行以下命令:
make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v6_v7_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16可以看到有如下打印信息:
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,会看到
# The actual objects are generated when descending,
# make sure no implicit rule kicks in
$(sort $(vmlinux-deps)): $(vmlinux-dirs) ;sort 函数就是用来排序的,它一般格式如下:
$(sort <list>)给字符串 list 中的单词排序(升序),会返回排序后的字符串。另外 sort 函数会去掉 list 中相同的单词。另外可以看出 vmlinux-deps 依赖 vmlinux-dirs。
4.3 vmlinux-dirs 变量
vmlinux-dirs 也定义在顶层 Makefile - vmlinux-dirs:
vmlinux-dirs := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \
$(core-y) $(core-m) $(drivers-y) $(drivers-m) \
$(net-y) $(net-m) $(libs-y) $(libs-m) $(virt-y)))
# ......
# Build samples along the rest of the kernel. This needs headers_install.
ifdef CONFIG_SAMPLES
vmlinux-dirs += samples
samples: headers_install
endifvmlinux-dirs 看名字就知道和目录有关,此变量保存着生成 vmlinux 所需源码文件的目录。这里不分析了,直接添加打印信息吧,还是在这个 vmlinux 目标这里加:
vmlinux: scripts/link-vmlinux.sh autoksyms_recursive $(vmlinux-deps) FORCE
@echo "vmlinux-dirs=$(vmlinux-dirs)"
ifdef CONFIG_HEADERS_CHECK
$(Q)$(MAKE) -f $(srctree)/Makefile headers_check
endif
ifdef CONFIG_GDB_SCRIPTS
$(Q)ln -fsn $(abspath $(srctree)/scripts/gdb/vmlinux-gdb.py)
endif
+$(call if_changed,link-vmlinux)和前面一样,我们编译一遍,会得到如下打印信息:
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 virt4.4 vmlinux-dirs 的依赖
4.4.1 规则展开
我们继续往后看,会看到 vmlinux-dirs 也是有依赖的,它定义在 Makefile - vmlinux-dirs:
# Handle descending into subdirectories listed in $(vmlinux-dirs)
# Preset locale variables to speed up the build process. Limit locale
# tweaks to this spot to avoid wrong language settings when running
# make menuconfig etc.
# Error messages still appears in the original language
PHONY += $(vmlinux-dirs)
$(vmlinux-dirs): prepare scripts
$(Q)$(MAKE) $(build)=$@ need-builtin=1目标 vmlinux-dirs 依赖 prepare 和 scripts 。这两个依赖就暂时不去详细了解了。我们重点看一下第 9 行的命令。build 前面已经说过了,它定义在 Kbuild.include - scripts/Kbuild.include - build,值为 " -f ./scripts/Makefile.build obj ",所以这一行命令展开就是:
@make -f ./scripts/Makefile.build obj=$@ need-builtin=1$@ 表示目标文件,也就是 vmlinux-dirs 的值,将 vmlinux-dirs 中的这些目录全部带入到命令中,结果如下:
@make -f ./scripts/Makefile.build obj=init need-builtin=1
@make -f ./scripts/Makefile.build obj=usr need-builtin=1
@make -f ./scripts/Makefile.build obj=arch/arm/vfp need-builtin=1
@make -f ./scripts/Makefile.build obj=arch/arm/vdso need-builtin=1
@make -f ./scripts/Makefile.build obj=arch/arm/kernel need-builtin=1
@make -f ./scripts/Makefile.build obj=arch/arm/mm need-builtin=1
@make -f ./scripts/Makefile.build obj=arch/arm/common need-builtin=1
@make -f ./scripts/Makefile.build obj=arch/arm/probes need-builtin=1
@make -f ./scripts/Makefile.build obj=arch/arm/net need-builtin=1
@make -f ./scripts/Makefile.build obj=arch/arm/crypto need-builtin=1
@make -f ./scripts/Makefile.build obj=arch/arm/firmware need-builtin=1
@make -f ./scripts/Makefile.build obj=arch/arm/mach-imx need-builtin=1
@make -f ./scripts/Makefile.build obj=kernel need-builtin=1
@make -f ./scripts/Makefile.build obj=certs need-builtin=1
@make -f ./scripts/Makefile.build obj=mm need-builtin=1
@make -f ./scripts/Makefile.build obj=fs need-builtin=1
@make -f ./scripts/Makefile.build obj=ipc need-builtin=1
@make -f ./scripts/Makefile.build obj=security need-builtin=1
@make -f ./scripts/Makefile.build obj=crypto need-builtin=1
@make -f ./scripts/Makefile.build obj=block need-builtin=1
@make -f ./scripts/Makefile.build obj=drivers need-builtin=1
@make -f ./scripts/Makefile.build obj=sound need-builtin=1
@make -f ./scripts/Makefile.build obj=firmware need-builtin=1
@make -f ./scripts/Makefile.build obj=net need-builtin=1
@make -f ./scripts/Makefile.build obj=arch/arm/lib need-builtin=1
@make -f ./scripts/Makefile.build obj=lib need-builtin=1
@make -f ./scripts/Makefile.build obj=virt need-builtin=1这些命令运行过程其实都是一样的,我们就以“@ make -f ./scripts/Makefile.build obj = init need-builtin = 1”这个命令为例 来分析一下。
4.4.2 命令分析
我们先看这个 Makefile.build - scripts/Makefile.build 中的默认目标__build:
__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
$(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
$(subdir-ym) $(always)
@:当只编译 Linux 内核镜像文件,也就是使用“ make zImage ” 编译的时候,KBUILD_BUILTIN = 1, KBUILD_MODULES 为空。“make”命令是会编译所有的东西,包括 Linux 内核镜像文件和一些模块文件。我们这里简单一点,如果只编译 Linux 内核镜像的话, __build 目标简化为:
__build: $(builtin-target) $(lib-target) $(extra-y)) $(subdir-ym) $(always)
@:重点来看一下 builtin-target 这个依赖, builtin-target 同样定义在文件 Makefile.build - scripts/Makefile.build - builtin-target 中 :
ifneq ($(strip $(real-obj-y) $(need-builtin)),)
builtin-target := $(obj)/built-in.a
endif第 2 行就是 builtin-target 变量的值,为 $(obj)/built-in.a,这就是这些 built-in.a 的来源了。要生成 built-in.a,要求 real-obj-y 和 need-builtin 变量不能全部为空。我们以这条命令为例:
@make -f ./scripts/Makefile.build obj=init need-builtin=1可以看到这里 obj = init,need-builtin = 1,所以上面的 builtin-target 可以展开一下:
ifneq ($(strip $(real-obj-y) 1),)
builtin-target := init/built-in.a
endif那 built-in.a 是怎么生成的?我们继续看这个 builtin-target,在文件 Makefile.build - scripts/Makefile.build 中有如下代码:
#
# Rule to compile a set of .o files into one .o file
#
ifdef builtin-target
# built-in.a archives are made with no symbol table or index which
# makes them small and fast, but unable to be used by the linker.
# scripts/link-vmlinux.sh builds an aggregate built-in.a with a symbol
# table and index.
quiet_cmd_ar_builtin = AR $@
cmd_ar_builtin = rm -f $@; \
$(AR) rcSTP$(KBUILD_ARFLAGS) $@ $(filter $(real-obj-y), $^)
$(builtin-target): $(real-obj-y) FORCE
$(call if_changed,ar_builtin)
targets += $(builtin-target)
endif # builtin-target可以看到第 14 行,目标就是 builtin-target,依赖为 real-obj-y,命令为“$(call if_changed,ar_builtin)”,前面我们分析过这个 if_changed 函数,这里参数为 ar_builtin,其返回值就是具体的命令。 我们可以放到这里再看一下 Kbuild.include - scripts/Kbuild.include - if_changed :
# Execute command if command has changed or prerequisite(s) are updated.
if_changed = $(if $(strip $(any-prereq) $(arg-check)), \
@set -e; \
$(echo-cmd) $(cmd_$(1)); \
printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd, @:)它会调用 cmd_$(1) 所对应的命令($(1) 就是函数的第 1 个参数),在这里就是调用 cmd_ar_builtin 所对应的命令。这里面的命令就是使用 AR 工具生成 .a 文件
4.5 打印信息
我们在终端执行:
make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v6_v7_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16然后我们会看到以下打印信息:
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:
BOOT_TARGETS = zImage Image xipImage bootpImage uImage
INSTALL_TARGETS = zinstall uinstall install
PHONY += bzImage $(BOOT_TARGETS) $(INSTALL_TARGETS)
bootpImage uImage: zImage
zImage: Image
$(BOOT_TARGETS): vmlinux
$(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@
@$(kecho) ' Kernel: $(boot)/$@ is ready'第 1 行。变量 BOOT_TARGETS 包含 zImage, Image, xipImage 等镜像文件。
第 9 行, BOOT_TARGETS 依赖 vmlinux,因此如果使用“make zImage”编译的 Linux 内核的话,首先肯定要先编译出 vmlinux。
第 10 行,具体的命令,比如要编译 zImage,那么命令展开以后如下所示:
@ make -f ./scripts/Makefile.build obj=arch/arm/boot MACHINE=arch/arm/boot/zImage和前面一样,都是使用 scripts/Makefile.build 完成 vmlinux 到 zImage 的转换。