linux 系统移植(第十期)----Linux 顶层 Makefile详谈-- Ubuntu20.04

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

目录

前言

[一、make xxx_defconfig 过程](#一、make xxx_defconfig 过程)

[二、Makefile.build 脚本分析](#二、Makefile.build 脚本分析)

[2.1、scripts_basic 目标对应的命令](#2.1、scripts_basic 目标对应的命令)

[2.2、 %config 目标对应的命令](#2.2、 %config 目标对应的命令)

[三、make 过程](#三、make 过程)

3.1、head-y

[3.2、init-y、drivers-y 和 net-y](#3.2、init-y、drivers-y 和 net-y)

3.3、libs-y

3.4、core-y

[四、built-in.o 文件编译生成过程](#四、built-in.o 文件编译生成过程)

[五、 make zImage 过程](#五、 make zImage 过程)

总结


前言

上两期主要介绍了Linux 内核的获取,编译以及顶层 Makefile 的简单介绍,这一期开始会对顶层 Makefile进行更加全面细致的研究。


一、make xxx_defconfig 过程

第一次编译 Linux 之前都要使用"make xxx_defconfig"先配置 Linux 内核,在顶层 Makefile 中有"%config"这个目标,如下所示:

复制代码
490 config-targets := 0
491 mixed-targets := 0
492 dot-config := 0
493
494 ifneq ($(filter $(no-dot-config-targets), $(MAKECMDGOALS)),)
495   ifeq ($(filter-out $(no-dot-config-targets), $(MAKECMDGOALS)),)
496     dot-config := 0
497   endif
498 endif
499
500 ifneq ($(KBUILD_EXTMOD),)
501   ifneq ($(filter config %config,$(MAKECMDGOALS)),)
502     config-targets := 1
503   endif
504 endif
---
# 第二部分:混合目标处理
505 ifneq ($(words $(MAKECMDGOALS)),1)
506   mixed-targets := 1
507 endif
508 endif
509 endif
510
511 ifeq ($(mixed-targets),1)
512   # We're called with mixed targets (*config and build targets).
513   # Handle them one by one.
514   PHONY += $(MAKECMDGOALS) __build_one_by_one
515   $(filter-out __build_one_by_one, $(MAKECMDGOALS)): __build_one_by_one
516   __build_one_by_one:
517 	@:
518
519 _build_one_by_one:
520   for i in $(MAKECMDGOALS); do \
521     $(MAKE) -f $(srctree)/Makefile $$i; \
522   done
523
524 
525 else
526 ifeq ($(config-targets),1)
527 # ================================================================
528 # *config targets only - make sure prerequisites are updated, and
529 # descend in scripts/kconfig to make the *config target
530
531 # Read arch specific Makefile to set KBUILD_DEFCONFIG as needed.
532 # KBUILD_DEFCONFIG may point out an alternative default
533 # configuration used for 'make defconfig'
534 include arch/$(SRCARCH)/Makefile
535 export KBUILD_DEFCONFIG KBUILD_KCONFIG
536
537 config: scripts_basic outputmakefile FORCE
538 	$(Q)$(MAKE) $(build)=scripts/kconfig $@
539
540 %config: scripts_basic outputmakefile FORCE
541 	$(Q)$(MAKE) $(build)=scripts/kconfig $@
542
543 else

......
563 endif # KBUILD_EXTMOD

第 490~507 行和 uboot 一样,都是设置定义变量 config-targets、mixed-targets 和 dot-config

的值,最终这三个变量的值为:

复制代码
config-targets=1
mixed-targets=0
dot-config=1

因为 config-targets=1,因此第 534 行~541 行成立。第 534 行引用 arch/arm/Makefile 这个文

件,这个文件很重要,因为 zImage、uImage 等这些文件就是由 arch/arm/Makefile 来生成的。

第 535 行导出变量 KBUILD_DEFCONFIG KBUILD_KCONFIG。

第 537 行,没有目标与之匹配,因此不执行。

第 540 行,"make xxx_defconfig"与目标"%config"匹配,因此执行。"%config"依赖scripts_basic、outputmakefile 和 FORCE,"%config"真正有意义的依赖就只有 scripts_basic, scripts_basic 的规则如下:

复制代码
scripts_basic:
	$(Q)$(MAKE) $(build)=scripts/basic
	$(Q)rm -f .tmp_quiet_recordmcount

build 定义在文件 scripts/Kbuild.include 中,值为 build := -f $(srctree)/scripts/Makefile.build obj,因此将上述代码展开就是:

复制代码
scripts_basic:
	@make -f ./scripts/Makefile.build obj=scripts/basic    // 也可以没有@,视配置而定
	@rm -f .tmp_quiet_recordmcount                       // 也可以没有@

其实

复制代码
%config: scripts_basic outputmakefile FORCE
	$(Q)$(MAKE) $(build)=scripts/kconfig $@

展开就是:

复制代码
@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig

二、Makefile.build 脚本分析

从make xxx_defconfig的过程中可知,"make xxx_defconfig"配置 Linux 的时候如下两行命令会执行脚本 scripts/Makefile.build:

复制代码
@make -f ./scripts/Makefile.build obj=scripts/basic
@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig

依次分析一下这两行代码

2.1、 scripts_basic 目标对应的命令

scripts_basic 目标对应的命令为:@make -f ./scripts/Makefile.build obj=scripts/basic。打开文件 scripts/Makefile.build,有如下代码:

复制代码
41 # The filename Kbuild has precedence over Makefile
42 kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
43 kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
44 include $(kbuild-file)

将 kbuild-dir 展开后为:

复制代码
kbuild-dir=./scripts/basic

将 kbuild-file 展开后为:

复制代码
kbuild-file=./scripts/basic/Makefile

最后将44行展开如下:

复制代码
include ./scripts/basic/Makefile

继续分析scripts/Makefile.build 如下代码:

复制代码
__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。 这 5 个依赖的具体内容如下:

复制代码
builtin-target =
lib-target =
extra-y =
subdir-ym =
always = scripts/basic/fixdep scripts/basic/bin2c

只有 always 有效,因此__build 最终为:

复制代码
__build: scripts/basic/fixdep scripts/basic/bin2c
       @:

__build 依赖于 scripts/basic/fixdep 和 scripts/basic/bin2c,所以要先将 scripts/basic/fixdep 和

scripts/basic/bin2c.c 这两个文件编译成 fixdep 和 bin2c。

所以总结一下:make -f ./scripts/Makefile.build obj= scripts/basic ->生成fixdep和bin2c

2.2 %config 目标对应的命令

%config 目标对应的命令为:

复制代码
@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig

其实他的作用是编译scripts/kconfig/conf.c生成conf 这个软件

此软件就会将%_defconfig 中的配置输出到.config 文件中,最终生成 Linux kernel 根目录下

的.config 文件。

即:

make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig ->

scripts/kconfig/conf --defconfig=arch/arm/configs/%_defconfig Kconfig ->.config

三、make 过程

使用命令"make xxx_defconfig"配置好 Linux 内核以后就可以使用"make"或者"make all" 命令进行编译。顶层Makefile 有如下代码:

复制代码
125 PHONY := _all
126 _all:
......
192 PHONY += all
193 ifeq ($(KBUILD_EXTMOD),)
194 _all: all
195 else
196 _all: modules
197 endif
......
608 all: vmlinux

第 126 行,_all 是默认目标,如果使用命令"make"编译 Linux 的话此目标就会被匹配。

第 193 行,如果 KBUILD_EXTMOD 为空的话 194 行的代码成立。

第 194 行,默认目标_all 依赖 all。

第 608 行,目标 all 依赖 vmlinux,所以接下来的重点就是 vmlinux!

vmlinux相关代码也在顶层 Makefile 代码段,如下:

复制代码
# 第一部分:vmlinux 链接准备
901 # Externally visible symbols (used by link-vmlinux.sh)
902 export KBUILD_VMLINUX_INIT := $(head-y) $(init-y)
903 export KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y) $(drivers-y) $(net-y)
904 export KBUILD_LDS         := arch/$(SRCARCH)/kernel/vmlinux.lds
905 export LDFLAGS_vmlinux
906 # used by scripts/package/Makefile
907 export KBUILD_IMAGE   := $(if $(filter-out arch/%,$(vmlinux-
all-y)),arch/$(SRCARCH)/boot/$(KBUILD_IMAGE),$(KBUILD_IMAGE))
908 vmlinux-dirs  := arch Documentation include samples scripts tools virt
909
910 vmlinux: $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN)
$(KBUILD_VMLINUX_LIBS)
911
# 第二部分:vmlinux 链接及后续处理
913 # Final link of vmlinux
914 cmd_link-vmlinux = $(CONFIG_SHELL) $< $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux)
915 vmlinux: scripts/link-vmlinux.sh FORCE
916 	$(call if_changed,link-vmlinux)
917 	@:
918
919 # Include targets which we want to
920 # execute if the rest of the kernel build went well.
921 vmlinux: scripts/link-vmlinux.sh FORCE
922 ifdef CONFIG_HEADERS_CHECK
923 	$(Q)$(MAKE) -f $(srctree)/Makefile headers_check
924 endif
925 ifdef CONFIG_SAMPLES
926 	$(Q)$(MAKE) $(build)=samples
927 endif
928 ifdef CONFIG_BUILD_DOCSRC
929 	$(Q)$(MAKE) $(build)=Documentation
930 endif
931 ifdef CONFIG_GDB_SCRIPTS
932 	$(Q)ln -fsn $(srctree) $@.gdb /bin/pwd/scripts/gdb/vmlinux-gdb.py
933 endif
934 	$(call if_changed,link-vmlinux)

第 920 行可以看出目标 vmlinux 依赖 scripts/link-vmlinux.sh $(vmlinux-deps) FORCE。

912 行定义了 vmlinux-deps,值为:

复制代码
vmlinux-deps=$(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN)

第 905 行,KBUILD_VMLINUX_INIT= (head-y) (init-y)。

第 906 行,KBUILD_VMLINUX_MAIN = (core-y) (libs-y) (drivers-y) (net-y)。

第 907 行,KBUILD_LDS= arch/$(SRCARCH)/kernel/vmlinux.lds,其中 SRCARCH=arm,因

此 KBUILD_LDS= arch/arm/kernel/vmlinux.lds。

综上所述,vmlinux 的依赖为:scripts/link-vmlinux.sh、(head-y) 、(init-y)、$(core-y) 、

(libs-y) 、(drivers-y) 、$(net-y)、arch/arm/kernel/vmlinux.lds 和 FORCE。

第 933 行的命令用于链接生成 vmlinux。

所以重点来看一下这6个依赖:下(head-y) 、(init-y)、(core-y) 、(libs-y) 、(drivers-y) 和(net-y)

3.1 head-y

head-y 定义在文件 arch/arm/Makefile 中,内容如下:

复制代码
head-y := arch/arm/kernel/head$(MMUEXT).o

当不使能 MMU 的话 MMUEXT=-nommu,如果使能 MMU 的话为空,因此 head-y 最终的值为:

复制代码
head-y = arch/arm/kernel/head.o

3.2 init-y drivers-y net-y

在顶层 Makefile 中有如下代码:

复制代码
init-y        := init/
drivers-y     := drivers/ sound/ firmware/
net-y         := net/
......
init-y        := $(patsubst %/, %/built-in.o, $(init-y))
drivers-y     := $(patsubst %/, %/built-in.o, $(drivers-y))
net-y         := $(patsubst %/, %/built-in.o, $(net-y))

这样便知道这三个变量的值应该为:

复制代码
init-y    = init/built-in.o
drivers-y = drivers/built-in.o sound/built-in.o firmware/built-in.o
net-y     = net/built-in.o

3.3 libs-y

其过程和 init-y 也一样

libs-y其实为如下:

libs-y := lib/lib.a arch/arm/lib/lib.a lib/built-in.o arch/arm/lib/built-in.o

3.4、 core-y

core-y 和 init-y 也一样,在顶层 Makefile 中有如下代码:

复制代码
core-y    := usr/
......
core-y    += kernel/ mm/ fs/ ipc/ security/ crypto/ block/

但是在 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)

最后那几行就是被追加的值

在顶层 Makefile 中有如下一行:

复制代码
core-y    := $(patsubst %/, %/built-in.o, $(core-y))

经过上述代码的转换,最终 core-y 的值为:

复制代码
core-y = usr/built-in.o \
	arch/arm/vdso/built-in.o \
	arch/arm/mm/built-in.o \
	arch/arm/probes/built-in.o \
	arch/arm/crypto/built-in.o \
	arch/arm/mach-imx/built-in.o \
	mm/built-in.o \
	ipc/built-in.o \
	crypto/built-in.o \
	arch/arm/vfp/built-in.o \
	arch/arm/kernel/built-in.o \
	arch/arm/common/built-in.o \
	arch/arm/net/built-in.o \
	arch/arm/firmware/built-in.o \
	kernel/built-in.o \
	fs/built-in.o \
	security/built-in.o \
	block/built-in.o

这些变量都是将相应目录中的源码文件进行编译,然后在各自目录下生成 built-in.o 文件,有些生成了.a 库文件。最终将这些 built-in.o 和.a 文件进行链接即可形成 ELF 格式的可执行文件,也就是 vmlinux。

链接脚本为 arch/arm/kernel/vmlinux.lds, 链接过程是由shell脚本scripts/link-vmlinux.s来完成的。

各自目录下生成 built-in.o 文件,有些生成了.a 库文件,这些又是怎么编译出来的?

四、built-in.o 文件编译生成过程

由前几小节可知vmliux 依赖 vmlinux-deps,而 vmlinux-deps=

(KBUILD_LDS) (KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN)

KBUILD_LDS 是链接脚本

剩下的 KBUILD_VMLINUX_INIT 和 KBUILD_VMLINUX_MAIN 就是各个子目录下的 built-in.o、.a 等文件。最终 vmlinux-deps 的值如下:

复制代码
vmlinux-deps = arch/arm/kernel/vmlinux.lds \
	init/built-in.o \
	arch/arm/vfp/built-in.o \
	arch/arm/kernel/built-in.o \
	arch/arm/common/built-in.o \
	arch/arm/net/built-in.o \
	arch/arm/firmware/built-in.o \
	kernel/built-in.o \
	fs/built-in.o \
	security/built-in.o \
	block/built-in.o \
	lib/lib.a \
	lib/built-in.o \
	sound/built-in.o \
	net/built-in.o \
	arch/arm/kernel/head.o \
	usr/built-in.o \
	arch/arm/vdso/built-in.o \
	arch/arm/mm/built-in.o \
	arch/arm/probes/built-in.o \
	arch/arm/crypto/built-in.o \
	arch/arm/mach-imx/built-in.o \
	mm/built-in.o \
	ipc/built-in.o \
	crypto/built-in.o \
	arch/arm/lib/lib.a \
	arch/arm/lib/built-in.o \
	drivers/built-in.o \
	firmware/built-in.o

除了 arch/arm/kernel/vmlinux.lds 以外,其他都是要编译链接生成的

出vmlinux-deps 依赖 vmlinux-dirs,vmlinux-dirs此变量保存着生成 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 \
	mm \
	fs \
	ipc \
	security \
	crypto \
	block \
	drivers \
	sound \
	firmware \
	net \
	arch/arm/lib \
	lib

顶层Makefile文件有以下代码:

复制代码
$(vmlinux-dirs): prepare scripts
	$(Q)$(MAKE) $(build)=$@

build=-f ./scripts/Makefile.build obj

所以展开就是:

复制代码
@ make -f ./scripts/Makefile.build obj=$@

$@表示目标文件,也就是 vmlinux-dirs 的值,将 vmlinux-dirs 中的这些目录全部带入到命

令中,结果如下:

复制代码
@make -f ./scripts/Makefile.build obj=init
@make -f ./scripts/Makefile.build obj=usr
@make -f ./scripts/Makefile.build obj=arch/arm/vfp
@make -f ./scripts/Makefile.build obj=arch/arm/vdso
@make -f ./scripts/Makefile.build obj=arch/arm/kernel
@make -f ./scripts/Makefile.build obj=arch/arm/mm
@make -f ./scripts/Makefile.build obj=arch/arm/common
@make -f ./scripts/Makefile.build obj=arch/arm/probes
@make -f ./scripts/Makefile.build obj=arch/arm/net
@make -f ./scripts/Makefile.build obj=arch/arm/crypto
@make -f ./scripts/Makefile.build obj=arch/arm/firmware
@make -f ./scripts/Makefile.build obj=arch/arm/mach-imx
@make -f ./scripts/Makefile.build obj=kernel
@make -f ./scripts/Makefile.build obj=mm
@make -f ./scripts/Makefile.build obj=fs
@make -f ./scripts/Makefile.build obj=ipc
@make -f ./scripts/Makefile.build obj=security
@make -f ./scripts/Makefile.build obj=crypto
@make -f ./scripts/Makefile.build obj=block
@make -f ./scripts/Makefile.build obj=drivers
@make -f ./scripts/Makefile.build obj=sound
@make -f ./scripts/Makefile.build obj=firmware
@make -f ./scripts/Makefile.build obj=net
@make -f ./scripts/Makefile.build obj=arch/arm/lib
@make -f ./scripts/Makefile.build obj=lib

五、 make zImage 过程

前面几小节重点是讲 vmlinux 是如何编译出来的,vmlinux 是 ELF 格式的文件,但是在实际中我们不会使用 vmlinux,而是使用 zImage 或 uImage 这样的 Linux 内核镜像文件。那么 vmlinux、zImage、uImage 他们之间有什么区别呢?

vmlinux 是编译出来的最原始的内核文件,如下图:

Image 是 Linux 内核镜像文件,Image 保存在 arch/arm/boot 目录下,如下图:

zImage 是经过 gzip 压缩后的 Image

uImage 是老版本 uboot 专用的镜像文件

使用"make"、"make all"、"make zImage"这些命令就可以编译出 zImage 镜像


总结

对Linux 顶层 Makefile进行了详谈。

相关推荐
大柏怎么被偷了2 小时前
【Linux】线程的概念
java·linux·jvm
Script kid2 小时前
Redis(Remote Dictionary Server远程字典服务器)五种常见数据结构及常见用法和指令
服务器·数据结构·redis
踢足球09292 小时前
寒假打卡:2026-01-21
数据库·sql
驾驭人生2 小时前
Nacos 2.2.3 生产级部署指南(单机 + MySQL + 鉴权)
数据库·mysql
ID_180079054732 小时前
淘宝平台商品详情API(item_get)深度解析
java·服务器·前端
Q16849645152 小时前
Linux-周期任务易错问题
linux·运维·服务器
茁壮成长的露露2 小时前
pbm物理备份恢复
数据库·mongodb
运维有小邓@2 小时前
如何分析 Linux 系统登录失败事件
linux·运维·服务器
星梦清河2 小时前
MySQL--常见函数
数据库·mysql