---- 整理自 王利涛老师 课程
实验环境:宅学部落 www.zhaixue.cc
文章目录
1. Kbuild简介
- Kernel build,用来编译 Linux 内核
- 基于 GNU make 设计,对 Makefile 进行扩充
- 菜单式配置:Kconfig
- 预定义目标和变量:xx_defconfig、menuconfig、obj-y
- 跨平台工具、递归式 Makefile
- Linux 模块化设计、高度可以裁剪
- 模块机制
- Kbuild 子系统
2. Kbuild 工作流程
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- vexpress_defconfig
make menuconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- uImage LOADADDR=0x60003000
2.1 编译三步骤
- 配置阶段:编译平台、目标、配置文件
- 编译阶段:解析 Makefile、建立目标依赖关系、按照依赖关系依次生成各个目标及目标依赖
- 安装阶段:
- 桌面 PC:内核镜像安装、模块安装、头文件安装
- 嵌入式:根文件系统、Flash镜像制作等
2.2 Makefile 中的预定义
- 预定义目标:
- xxx_defconfig 、menuconfig 、gconfig
- vmlinux、bzImage、zImage
- modules、install、modules_install
- clean、mrproper、distclean
- 预定义变量:
- ARCH、CROSS_COMPILE
- obj-m、obj-y、xxx-objs
2.3 Config symbols
- Config symbols:CONFIG_XXX,由 Kconfig 文件描述。
- xxx_defconfig:内核中默认的配置文件,内核编译时的默认配置选项
- Kconfig:内核配置使用的配置语言,描述了有哪些选项及其依赖等,存在于 Linux 源码树的各个子目录中,组成不同的层级结构,可以通过 make menuconfig 等命令打开 Kconfig 配置界面,并进行相应的配置操作。
- .config:.config 文件是 Linux 内核编译过程中生成的配置文件。由 make xxxconfig 等生成,里面列出了各配置选项的取值。
3. Kbuild 编译系统构成
- Kbuild 本质
一个可扩展、可配置的 Makefile 框架
递归式 Makefile、菜单式配置 - 构成:
- Makefile:顶层目录下的 Makefile
- Kconfig:配置菜单,定义每个 config symbol 的属性(类型、描述、依赖等)
- .config:内核的配置文件,一般通过 make menuconfig 等生成,里面列出了各选项的取值。
- arch/$(ARCH)/Makefile:跟平台架构相关的 Makefile
- scripts/Makefile.*:通用编译规则
- Kbuild Makefiles:分布在各个子目录下
4. Kconfig 简介
4.1 Kconfig 作用
- 用来生成配置菜单,配置各种 config symbol,生成对应的配置变量:CONFIG_XXX
- 每个目录下都有一个 Kconfig 文件
- 各个 Kconfig 文件通过 source 命令构建多级菜单
- 解析工具:scripts/kconfig/*conf
4.2 实验:内核模块添加配置菜单
4.3 Kconfig 语法
- config:用来定义菜单选项
- menuconfig
- choice / endchooice
- comment
- if / endif
- source:生成一个树型菜单
- (后面几节内容讲解)
4.3.1 Kconfig 菜单条目
- 菜单示例
config HELLO
tristate "A hello module test"
help
a simple kernel module test
- 实验:
在内核目录下新建一个 test 目录,用来实验 Kconfig。
4.3.2 依赖关系:depends on
4.3.3 反向依赖:select / imply
4.3.3.1 select
如果 TEST 被选中的话,RTC 默认也会被 “强制性”选中
具体效果可以自行实验。
4.3.3.2 弱反向依赖:imply
具体效果可以自行实验。
4.3.4 Kconfig 菜单:menuconfig
- 等价于如下使用 if 的方式:
A 和 A0、A1 必须紧跟着,才能保持依赖关系,同理 B 和 B0、B1。如果是如下的方式,会破坏 A 和 A0、A1 的依赖的关系,同理 B 和 B0、B1。
4.3.5 Kconfig 互斥选择:choice / endchoice
4.3.6 Kbuild 子菜单
4.3.6.1 方法一
- 通过依赖关系生成菜单
- 若菜单条目依赖前项,则其为该选项的子菜单
config A
bool "A configuration"
config B
bool "B configuration"
depends on A
4.3.6.2 方法二
- 子菜单:menu / endmenu
- 所有的菜单条目都在 menu 和 endmenu 之间的块中
- 子菜单会继承父菜单的依赖关系
menu "test menu"
config xxx_1
…
config xxx_2
endmenu
5. 更多编译目标
make config
make nconfig # 基于文本的菜单配置
make menuconfig # 依赖 ncurses 图形库
# apt-get install libncurses5-dev
make xconfig # 基于窗口的配置菜单,依赖Qt库
# add-apt-repository ppa:rock-core/qt4
# apt-get install libqt4-dev
make gconfig # 基于GTK的菜单配置
# apt-get install gtk+-2.0 glib-2.0 libglade2-dev
make clean # Remove most generated files but keep the config and enough build support to build external modules
make mrproper # Remove all generated files + config + various backup files
make distclean # mrproper + remove editor backup and patch files
6. 文件 .config
6.1 简介
- .config 文件是如何生成的?
- .config 文件里都是什么?
- .config 文件有什么用?如何参与编译工作?
- 参考:
scripts/kconfig/mconf.c
scripts/kconfig/conf.c
6.2 .config 的生成
6.2.1 .config 生成的第一阶段
- make vexpress_defconfig ==> .config
- make menuconfig ==> .config
分析如下:编译时执行 make vexpress_defconfig
和 make menuconfig
# /linux-5.10.4/Makefile:
%config: outputmakefile scripts_basic FORCE
$(Q)$(MAKE) $(build)=scripts/kconfig $@
# $(Q)当make没有指定V=1的时候,$(Q)=@,@的作用是不显示后面的指令;当有V=1的时候$(Q)为空
# MAKE就是预先指定的make程序。可能是make的不同版本或不同厂商的make程序(如qmake nmake等)。
# /linux-5.10.4/scripts/Kbuild.include
build := -f $(srctree)/scripts/Makefile.build obj
# 展开后得到:
make -f $(srctree)/scripts/Makefile.build obj=scripts/kconfig vexpress_defconfig
make -f $(srctree)/scripts/Makefile.build obj=scripts/kconfig menuconfig
# /linux-5.10.4/scripts/kconfig/Makefile
ifdef KBUILD_KCONFIG
Kconfig := $(KBUILD_KCONFIG)
else
Kconfig := Kconfig
endif
# 未定义KBUILD_KCONFIG,Kconfig等于Kconfig
%_defconfig: $(obj)/conf
$(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)
menuconfig: $(obj)/mconf
$(Q)$< $(silent) $(Kconfig)
# 展开后得到:
%_defconfig: scripts/kconfig/conf
$(Q)scripts/kconfig/conf $(silent) --defconfig=arch/$(SRCARCH)/configs/vexpress_defconfig Kconfig
menuconfig: $(obj)/mconf
$(Q)scripts/kconfig/mconf $(silent) Kconfig
分析一下 conf 和 mconf 程序:
// linux-5.10.4/scripts/kconfig/confdata.c
const char *conf_get_configname(void)
{
char *name = getenv("KCONFIG_CONFIG");
return name ? name : ".config";
}
# scripts/kconfig/conf.c
%_defconfig:
| --> main()
| --> conf_parse
| --> conf_read
| --> conf_write
| --> name = conf_get_configname();
# scripts/kconfig/mconf.c
menuconfig:
| --> main()
| --> conf_parse
| --> conf_read
| --> set_config_filename(conf_get_configname());
| --> handle_exit()
| --> conf_write
| --> name = conf_get_configname();
6.2.2 .config 生成的第二阶段
- .config ==> syncconfig ==> Makefile
include/config/auto.conf # 用来配置Makefile
include/generated/autoconf.h # 供C程序引用
include/config/*.h # 空头文件,用于构建依赖关系
# linux-5.10.4/Makefile
KCONFIG_CONFIG ?= .config
export KCONFIG_CONFIG
cmd_syncconfig = $(MAKE) -f $(srctree)/Makefile syncconfig
PHONY += include/config/auto.conf
%/config/auto.conf %/config/auto.conf.cmd %/generated/autoconf.h: $(KCONFIG_CONFIG)
+$(call cmd,syncconfig)
# scripts/Kbuild.include:
cmd = @set -e; $(echo-cmd) $(cmd_$(1))
# 展开得到:
%/config/auto.conf %/config/auto.conf.cmd %/generated/autoconf.h: $(KCONFIG_CONFIG)
@set -e; $(echo-cmd) $(cmd_syncconfig)
# 实际就是调用cmd_syncconfig,,上面的cmd_syncconfig = $(MAKE) -f $(srctree)/Makefile syncconfig
# linux-5.10.4/Makefile
simple-targets := oldconfig allnoconfig allyesconfig allmodconfig \
alldefconfig randconfig listnewconfig olddefconfig syncconfig \
helpnewconfig yes2modconfig mod2yesconfig
PHONY += $(simple-targets)
$(simple-targets): $(obj)/conf
$(Q)$< $(silent) --$@ $(Kconfig)
# 这里的分析可以参考第一阶段的xxx_defconfig和menuconfig
// linux-5.10.4/scripts/kconfig/conf.c
syncconfig:
| --> sync_kconfig = 1;
| --> if (conf_write_autoconf(sync_kconfig) && sync_kconfig)
// linux-5.10.4/scripts/kconfig/confdata.c
| --> const char *autoconf_name = conf_get_autoconfig_name();
/*
static const char *conf_get_autoconfig_name(void)
{
char *name = getenv("KCONFIG_AUTOCONFIG");
return name ? name : "include/config/auto.conf";
}
*/
| --> conf_write_dep("include/config/auto.conf.cmd"); // ⇒ linux-5.10.4/include/config/auto.conf.cmd
| --> name = "include/generated/autoconf.h"; // ⇒ linux-5.10.4/include/generated/autoconf.h
# linux-5.10.4/include/config/auto.conf.cmd
deps_config := ... \
kernel/trace/Kconfig \
certs/Kconfig \
fs/udf/Kconfig \
...
include/config/auto.conf: $(deps_config)
6.3 .config 如何参与编程
- .config ==> syncconfig ==> linux-5.10.4/include/config/auto.conf
在 Makefile 中引用 auto.conf 定义的配置变量(config symbols)。示例 USB,根据 auto.conf 定义的 CONFIG_USB=y,将 usb 编译进内核中:
# linux-5.10.4/Makefile:
...
need-config := 1
...
ifdef need-config
include include/config/auto.conf
endif
...
# linux-5.10.4/include/config/auto.conf:
...
CONFIG_USB=y
...
# linux-5.10.4/drivers/usb/Makefile:
...
obj-$(CONFIG_USB) += core/
...
6.4 .config 如何被 C 语言引用
- .config ==> syncconfig ==> linux-5.10.4/include/generated/autoconf.h
- 配置变量(config symbols) ==> C 语言的宏定义
在 C 程序中引用 autoconf.h 定义的宏:
// linux-5.10.4/include/generated/autoconf.h:
...
#define CONFIG_USB_MON 1
...
// linux-5.10.4/include/linux/usb.h:
...
#if defined(CONFIG_USB_MON) || defined(CONFIG_USB_MON_MODULE)
struct mon_bus *mon_bus; /* non-null when associated */
int monitored; /* non-zero when monitored */
#endif
...
7. Kbuild Makefile 工作流程
7.1 Linux 内核镜像的流程
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- vexpress_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- menuconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- -j4
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- uImage LOADADDR=0x60003000
7.2 Kbuild Makefile 的构成
- 顶层 Makefile:主要用来调用相应规则的 Makefile
- .config:用户配置的各种选项
- arch/$(ARCH)/Makefile:跟平台相关的 Makefile
- 各个目录下的 Makefile:负责编译各个模块
- scripts/Makefile.* :定义各种通用规则
7.3 scripts/Makefile.* :各类规则文件
- scripts/Makefile.build:通用规则,用来编译 built-in.a、lib.a
- scripts/Makefile.lib:负责分析 obj-y、obj-y 和子目录中的 subdir-y 等
- scripts/Makefile.include:一些通用定义,被 Makefile.* 包含使用
- scripts/Makefile.host:编译各种主机工具
- scripts/Makefile.headerinst:头文件安装规则
- scripts/Makefile.modinst:模块 install 规则
- scripts/Makefile.modpost:模块编译,由 .o 和 .mod 生成 module.ko
- scripts/Makefile.modsign:模块签名
- scripts/Makefile.clean:clean 规则,make clean 时调用
7.4 Kbuild Makefile 预定义目标和变量
- obj-m:将当前文件编译为独立的模块
- obj-y:将当前文件编译进内核
- xxx-objs:一个模块依赖的多个源文件
- bzImage:
- menuconfig:
- CONFIG_xxx:
- include include/config/auto.conf
- include/config/auto.conf.cmd
7.5 Kbuild Makefile 工作流程
- 根据 ARCH 变量,首先 include arch/$(ARCH)/Makefile
- 读取 .config 文件:读取用户的各种配置变量
- 解析预定义目标、变量,构建依赖关系
- 编译各个模块或组件(使用 scripts/Makefile.*)
- 将每个目录下的源文件编译为对应的 .o 目标文件
- 将 .o 目标文件归档为 built-in.a
- 将所有对象链接成 vmlinux
- 编译模块…
8. vmlinux 编译过程分析
8.1 内核镜像编译流程
8.2 默认目标的依赖:vmlinux
# linux-5.10.4/Makefile:
# That's our default target when none is given on the command line
PHONY := __all
__all: all
all: vmlinux
vmlinux: scripts/link-vmlinux.sh autoksyms_recursive $(vmlinux-deps) FORCE
+$(call if_changed,link-vmlinux)
# 实际调用cmd_link-vmlinux函数
ARCH_POSTLINK := $(wildcard $(srctree)/arch/$(SRCARCH)/Makefile.postlink)
cmd_link-vmlinux = \
$(CONFIG_SHELL) $< "$(LD)" "$(KBUILD_LDFLAGS)" "$(LDFLAGS_vmlinux)"; \
$(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true)
# 展开后:
cmd_link-vmlinux = \
sh scripts/link-vmlinux.sh $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux); \
$(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true)
# 定义了ARCH_POSTLINK变量,如果定义了就执行相应的Makefile文件,否则执行 true
vmlinux 主要依赖 $(KBUILD_VMLINUX_OBJS) 和 $(KBUILD_VMLINUX_LIBS):
# linux-5.10.4/Makefile:
vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_OBJS) $(KBUILD_VMLINUX_LIBS)
# Objects we will link into vmlinux / subdirs we need to visit
core-y := init/ usr/
core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/
drivers-y := drivers/ sound/
drivers-y += net/ virt/
libs-y := lib/
KBUILD_VMLINUX_OBJS := $(head-y) $(patsubst %/,%/built-in.a, $(core-y))
KBUILD_VMLINUX_OBJS += $(addsuffix built-in.a, $(filter %/, $(libs-y)))
# $(filter %/, $(libs-y)):
# 这个部分使用filter函数,从libs-y中筛选出以斜杠(/)结尾的字符串,即以目录形式存在的库名称。
# $(addsuffix built-in.a, $(filter %/, $(libs-y))):
# 这个部分使用addsuffix函数,将每个以目录形式存在的库名称的末尾加上字符串built-in.a。这样就形成了以built-in.a结尾的库文件路径列表。
# 例如,如果libs-y包含了foo/和bar/,那么这个命令会生成foo/built-in.a和bar/built-in.a这两个路径。
ifdef CONFIG_MODULES
KBUILD_VMLINUX_OBJS += $(patsubst %/, %/lib.a, $(filter %/, $(libs-y)))
# $(patsubst %/, %/lib.a, $(filter %/, $(libs-y))):
# 这个部分使用patsubst函数,将以目录形式存在的库名称替换成相应的库文件路径。
# 具体地说,它会在每个以斜杠结尾的字符串后面加上lib.a,形成最终的库文件路径。
# 如果libs-y包含了foo/和bar/,那么这个命令会生成foo/lib.a和bar/lib.a这两个路径。
KBUILD_VMLINUX_LIBS := $(filter-out %/, $(libs-y))
# $(filter-out %/, $(libs-y)):
# 这个部分使用filter-out函数,从libs-y中过滤掉以斜杠结尾的字符串。换句话说,它会返回那些不是以目录形式存在的库名称。
else
KBUILD_VMLINUX_LIBS := $(patsubst %/,%/lib.a, $(libs-y))
endif
KBUILD_VMLINUX_OBJS += $(patsubst %/,%/built-in.a, $(drivers-y))
export KBUILD_LDS := arch/$(SRCARCH)/kernel/vmlinux.lds
8.2.1 KBUILD_VMLINUX_OBJS 变量
# linux-5.10.4/arch/arm/Makefile:
MMUEXT := -nommu
# linux-5.10.4/arch/arm/kernel/Makefile:
head-y := arch/arm/kernel/head$(MMUEXT).o
core-y += arch/arm/
core-y += $(machdirs) $(platdirs)
libs-y := arch/arm/lib/ $(libs-y)
# linux-5.10.4/Makefile:
core-y := init/ usr/
core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/
libs-y := lib/
drivers-y := drivers/ sound/
drivers-y += net/ virt/
展开后:
KBUILD_VMLINUX_OBJS := arch/arm/kernel/head-nommu.o arch/arm/built-in.a init/built-in.a usr/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 lib/built-in.a arch/arm/lib/built-in.a drivers/built-in.a sound/built-in.a net/built-in.a virt/built-in.a
8.2.2 KBUILD_VMLINUX_LIBS 变量
# linux-5.10.4/Makefile:
KBUILD_VMLINUX_LIBS := $(patsubst %/, %/lib.a, $(libs-y))
libs-y := lib/
libs-y := arch/arm/lib/ $(libs-y)
展开后:
KBUILD_VMLINUX_LIBS := lib/lib.a arch/arm/lib/lib.a
8.2.3 autoksyms_recursive
# linux-5.10.4/Makefile:
autoksyms_recursive: descend modules.order
$(Q)$(CONFIG_SHELL) $(srctree)/scripts/adjust_autoksyms.sh \
"$(MAKE) -f $(srctree)/Makefile vmlinux"
8.3 生成 vmlinux 的规则
# linux-5.10.4/Makefile:
vmlinux: scripts/link-vmlinux.sh autoksyms_recursive $(vmlinux-deps)
+$(call if_changed,link-vmlinux) # 实际调用cmd_link-vmlinux
# linux-5.10.4/scripts/Kbuild.include:
if_changed = $(if $(newer-prereqs)$(cmd-check), \
$(cmd); \
printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd, @:)
# printing commands
cmd = @set -e; $(echo-cmd) $(cmd_$(1))
# linux-5.10.4/Makefile:
ARCH_POSTLINK := $(wildcard $(srctree)/arch/$(SRCARCH)/Makefile.postlink)
cmd_link-vmlinux = \
$(CONFIG_SHELL) $< "$(LD)" "$(KBUILD_LDFLAGS)" "$(LDFLAGS_vmlinux)";
$(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true)
# 展开后:
cmd_link-vmlinux = \
sh scripts/link-vmlinux.sh $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux); \
$(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true)
# 定义了ARCH_POSTLINK变量,如果定义了就执行相应的Makefile文件,否则执行true
展开后:
cmd_link-vmlinux = sh scripts/link-vmlinux.sh "ld.lld""--build-id=sha1 --orphan-handling=warn";true
- linux-5.10.4/scripts/link-vmlinux.sh 脚本
- 链接 $(KBUILD_VMLINUX_OBJS) 中的所有 built-in.a
- 链接 $(KBUILD_VMLINUX_LIBS)
- 符号处理:生成 linux-5.10.4/System.map、linux-5.10.4/include/generated/autoksyms.h 等文件
modpost_link()
{
local objects
objects="--whole-archive \
${KBUILD_VMLINUX_OBJS} \
--no-whole-archive \
--start-group \
${KBUILD_VMLINUX_LIBS} \
--end-group"
${LD} ${KBUILD_LDFLAGS} -r -o ${1} ${objects}
}
# vmlinux
# ^
# |
# +--< $(KBUILD_VMLINUX_OBJS)
# | +--< init/built-in.a drivers/built-in.a mm/built-in.a + more
# |
# +--< $(KBUILD_VMLINUX_LIBS)
# | +--< lib/lib.a + more
# |
# +-< ${kallsymso} (see description in KALLSYMS section)
9. built-in.a 生成分析
默认目标的依赖分析:
# linux-5.10.4/Makefile:
vmlinux-dirs := $(patsubst %/,%,$(filter %/, \
$(core-y) $(core-m) $(drivers-y) $(drivers-m) \
$(libs-y) $(libs-m)))
build-dirs:= $(vmlinux-dirs)
# 展开后:
build-dirs:= init lib drivers net sound certs crypto ipc kernel mm ......
# linux-5.10.4/Makefile:
PHONY += descend $(build-dirs)
$(build-dirs): prepare
$(Q)$(MAKE) $(build)=$@ \
...
# linux-5.10.4/scripts/Kbuild.include:
build := -f $(srctree)/scripts/Makefile.build obj
# 展开后:
$(build-dirs): prepare
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.build obj=$(build-dirs) \
...
- 示例:编译 sound 目录
# linux-5.10.4/scripts/Makefile.build:
__build: $(if $(KBUILD_BUILTIN), $(targets-for-builtin)) \
$(if $(KBUILD_MODULES), $(targets-for-modules)) \
$(subdir-ym) $(always-y)
@:
targets-for-builtin := $(extra-y)
ifneq ($(strip $(lib-y) $(lib-m) $(lib-)),)
targets-for-builtin += $(obj)/lib.a
endif
ifdef need-builtin
targets-for-builtin += $(obj)/built-in.a
endif
__build 展开后:
__build: ... sound/built-in.a ...
- sound/built-in.a:
# SPDX-License-Identifier: GPL-2.0
# Makefile for the Linux sound card driver
#
obj-$(CONFIG_SOUND) += soundcore.o
obj-$(CONFIG_DMASOUND) += oss/dmasound/
obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ ppc/ arm/ sh/ synth/ usb/ \
firewire/ sparc/ spi/ parisc/ pcmcia/ mips/ soc/ atmel/ hda/ x86/ xen/
obj-$(CONFIG_SND_AOA) += aoa/
# This one must be compilable even if sound is configured out
obj-$(CONFIG_AC97_BUS) += ac97_bus.o
obj-$(CONFIG_AC97_BUS_NEW) += ac97/
ifeq ($(CONFIG_SND),y)
obj-y += last.o
endif
soundcore-objs := sound_core.o
主要是以下几种形式的编译:
- 单文件模块:
obj-y=hello.o
- 复合模块:
obj-y=hello.o hello-y= a.o b.o c.o
obj-y=hello.o hello-objs= a.o b.o c.o
- 子目录:
obj-y=subdir
# linux-5.10.4/scripts/Makefile.build:
$(obj)/built-in.a: $(real-obj-y) FORCE
$(call if_changed,ar_builtin) # 实际调用cmd_ar_builtin
cmd_ar_builtin = rm -f $@; $(AR) cDPrST $@ $(real-prereqs)
real-prereqs = $(filter-out $(PHONY), $^) # $(filter-out $(PHONY), $^): 这部分使用filter-out函数,从$^中过滤掉所有在$(PHONY)中声明的伪目标,得到真实的前提条件。
# linux-5.10.4/scripts/Makefile.lib
real-obj-y := $(foreach m, $(obj-y), $(if $(strip $($(m:.o=-objs)) $($(m:.o=-y)) $($(m:.o=-))),$($(m:.o=-objs)) $($(m:.o=-y)),$(m)))
real-obj-y := $(addprefix $(obj)/,$(real-obj-y))
10. 单个目标文件生成分析
# linux-5.10.4/scripts/Makefile.build:
$(obj)/%.o: $(src)/%.c $(recordmcount_source) $(objtool_dep) FORCE
$(call if_changed_rule,cc_o_c) # 实际调用rule_cc_o_c
$(call cmd,force_checksrc) # 实际调用cmd_force_checksrc
# linux-5.10.4/scripts/Kbuild.include:
if_changed_rule = $(if $(newer-prereqs)$(cmd-check),$(rule_$(1)),@:)
# linux-5.10.4/scripts/Makefile.build:
define rule_cc_o_c
$(call cmd_and_fixdep,cc_o_c)
$(call cmd,gen_ksymdeps)
$(call cmd,checksrc)
$(call cmd,checkdoc)
$(call cmd,objtool)
$(call cmd,modversions_c)
$(call cmd,record_mcount)
endef
# linux-5.10.4/scripts/Kbuild.include:
cmd_and_fixdep =
$(cmd); \
scripts/basic/fixdep $(depfile) $@ '$(make-cmd)' > $(dot-target).cmd;\
rm -f $(depfile)
make-cmd = $(call escsq,$(subst $(pound),$$(pound),$(subst $$,$$$$,$(cmd_$(1))))) # 实际调用到cmd_cc_o_c
# linux-5.10.4/scripts/Makefile.build:
quiet_cmd_cc_o_c = CC $(quiet_modtag) $@
cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $<
quiet_cmd_force_checksrc = CHECK $<
cmd_force_checksrc = $(CHECK) $(CHECKFLAGS) $(c_flags) $<
# linux-5.10.4/Makefile:
CHECK = sparse
CHECKFLAGS := -D__linux__ -Dlinux -D__STDC__ -Dunix -D__unix__ \
-Wbitwise -Wno-return-void -Wno-unknown-attribute $(CF)
# linux-5.10.4/scripts/Makefile.build:
$(obj)/%.o: $(src)/%.c $(recordmcount_source) $(objtool_dep) FORCE
$(call if_changed_rule,cc_o_c) # 实际调用rule_cc_o_c
$(call cmd,force_checksrc) # 实际调用cmd_force_checksrc
# 命令展开后:
%.o : %.c
gcc –c –o $@ %.c
sparse -D__linux__ -Dlinux -D__STDC__ …
- 总结:
.c ==> .o ==> 递归生成 built-in.a ==> vmlinux
11. zImage 生成分析
11.1 内核镜像生成过程
…
CC sound/xx.o
CC xxxx/xxx.o
…
LD vmlinux
SORTTAB vmlinux
SYSMAP System.map
OBJCOPY arch/arm/boot/Image
Kernel: arch/arm/boot/Image is ready
GZIP arch/arm/boot/compressed/piggy_data
LD arch/arm/boot/compressed/vmlinux
OBJCOPY arch/arm/boot/zImage
Kernel: arch/arm/boot/zImage is ready
UIMAGE arch/arm/boot/uImage
11.2 Image 镜像生成分析
# linux-5.10.4/arch/arm/Makefile:
boot := arch/arm/boot
KBUILD_IMAGE := $(boot)/zImage
all: $(notdir $(KBUILD_IMAGE))
zImage: Image
BOOT_TARGETS = zImage Image xipImage bootpImage uImage
$(BOOT_TARGETS): vmlinux
$(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@
@$(kecho) ' Kernel: $(boot)/$@ is ready'
# linux-5.10.4/scripts/Kbuild.include:
build := -f $(srctree)/scripts/Makefile.build obj
# 展开后:
zImage: vmlinux
make -f scripts/Makefile.build obj=arch/arm/boot arch/arm/boot/zImage
Image: vmlinux
make -f scripts/Makefile.build obj=arch/arm/boot arch/arm/boot/Image
# linux-5.10.4/arch/arm/boot/Makefile:
$(obj)/Image: vmlinux FORCE
$(call if_changed,objcopy)
$(obj)/compressed/vmlinux: $(obj)/Image FORCE
$(Q)$(MAKE) $(build)=$(obj)/compressed $@
$(obj)/zImage: $(obj)/compressed/vmlinux FORCE
$(call if_changed,objcopy)
# 展开后:
arch/arm/boot/Image: vmlinux
arm-linux-gnueabi-objcopy -O binary -R .comment -S vmlinux Image
# 参数说明:
# -O:生成一个二进制文件
# -R:从一个目标文件中删除指定的section
# -S:--strip-all,全方位压缩vmlinux文件
arch/arm/boot/compressed/vmlinux:arch/arm/boot/Image
make -f scripts/Makefile.build obj=arch/arm/boot/compressed vmlinux
arch/arm/boot/zImage: arch/arm/boot/compressed/vmlinux
arm-linux-gnueabi-objcopy -O binary -R .comment -S vmlinux zImage
11.3 piggy.o 生成分析
# linux-5.10.4/arch/arm/boot/compressed/Makefile
$(obj)/piggy_data: $(obj)/../Image FORCE
$(call if_changed,$(compress-y))
$(obj)/piggy.o: $(obj)/piggy_data
compress-$(CONFIG_KERNEL_GZIP) = gzip
# linux-5.10.4/.config:
CONFIG_KERNEL_GZIP=y
# linux-5.10.4/scripts/Kbuild.include
make-cmd = $(call escsq,$(subst $(pound),$$(pound),$(subst $$,$$$$,$(cmd_$(1)))))
if_changed = $(if $(newer-prereqs)$(cmd-check), \
$(cmd); \
printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd, @:)
# 展开后实际调用cmd_gzip:
$(obj)/piggy_data: $(obj)/../Image FORCE
$(call if_changed,cmd_gzip)
# linux-5.10.4/scripts/Makefile.lib
cmd_gzip = cat $(real-prereqs) | $(KGZIP) -n -f -9 > $@
# linux-5.10.4/Makefile
KGZIP = gzip
11.4 arch/arm/boot/compressed/vmlinux 生成分析
# linux-5.10.4/arch/arm/boot/compressed/Makefile
arch/arm/boot/compressed/vmlinux: arch/arm/boot/Image
make -f scripts/Makefile.build obj=arch/arm/boot/compressed vmlinux
# linux-5.10.4/arch/arm/boot/compressed/Makefile
OBJS =
HEAD = head.o
OBJS += misc.o decompress.o
efi-obj-$(CONFIG_EFI_STUB) := $(objtree)/drivers/firmware/efi/libstub/lib.a
$(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.o \
$(addprefix $(obj)/, $(OBJS)) $(lib1funcs) $(ashldi3) \
$(bswapsdi2) $(efi-obj-y) FORCE
@$(check_for_multiple_zreladdr)
$(call if_changed,ld) # 实际调用linux-5.10.4/scripts/Makefile.lib/cmd_ld
@$(check_for_bad_syms)
# 其中:
# cmd_ld = $(LD) $(ld_flags) $(real-prereqs) -o $@
# LD = $(CROSS_COMPILE)ld
# ld_flags = $(KBUILD_LDFLAGS) $(ldflags-y) $(LDFLAGS_$(@F))
# ldflags-y += $(EXTRA_LDFLAGS),其中EXTRA_LDFLAGS为空
# real-prereqs = $(filter-out $(PHONY), $^)
# 展开后:
arch/arm/boot/compressed/vmlinux: arch/arm/boot/compressed/vmlinux.lds \
arch/arm/boot/compressed/head.o \
arch/arm/boot/compressed/piggy.o \
arch/arm/boot/compressed/misc.o arch/arm/boot/compressed/decompress.o
arm-linux-gnueabi-ld -EL vmlinux.lds head.o piggy.o misc.o decompress.o
11.5 zImage 镜像生成分析
# linux-5.10.4/arch/arm/boot/Makefile:
arch/arm/boot/zImage: arch/arm/boot/compressed/vmlinux
arm-linux-gnueabi-objcopy -O binary -R .comment -S vmlinux zImage
# 参数说明:
# -O:生成一个二进制文件
# -R:从一个目标文件中删除指定的section
# -S:--strip-all,全方位压缩vmlinux文件
12. uImage 镜像生成分析
12.1 uImage 镜像生成分析
# linux-5.10.4/scripts/Makefile.lib
cmd_uimage = $(BASH) $(MKIMAGE) -A $(UIMAGE_ARCH) -O linux \
-C $(UIMAGE_COMPRESSION) $(UIMAGE_OPTS-y) \
-T $(UIMAGE_TYPE) \
-a $(UIMAGE_LOADADDR) -e $(UIMAGE_ENTRYADDR) \
-n $(UIMAGE_NAME) -d $< $@
# 展开后:
mkimage –A arm -O linux –T kernel –C none –a 0x60003000 –e 0x60003000 -d zImage uImage
# mkimage 参数说明:
# -A:指定CPU架构类型
# -O:指定操作系统类型
# -T:指定image类型
# -C:采用的压缩方式:none、gzip、bzip2等
# -a:内核加载地址
#-e:内核镜像入口地址
12.2 uImage 启动过程
-a ==> set load address to ‘addr’ (hex) —— 镜像的加载地址
-e ==> set entry point to ‘ep’ (hex) —— 镜像的运行地址
- 首先要知道 mkimage 做出来的文件,会有 64 字节的描述信息在文件头部。而 Uboot 会将镜像文件加载到 -a 指定的地址,并跳到 -e 地址运行。
- 这里有几种情况:
-
- uImage 下载到内存的地址,和 -a 指定的地址相同,uboot 不会对镜像做内存搬移。
-
- uImage 下载到内存的地址,和 -a 指定的地址不同,uboot 会将下载地址的 uImage 镜像文件(去掉64字节头部信息)搬移到 -a 指定的地址。
-
- -a 和 -e 参数地址相同时,理论上,需要 uImage 的下载地址和 -a 地址不同,镜像做过搬移后,到 -e 地址运行是正常的。
-
- -a 和 -e 参数地址不同,固定为 -a + 0x40 = -e,镜像不会搬移(头部 64 字节信息不会去掉),到 -e 地址运行才会正常。
-
13. 内核模块编译分析
13.1 内核模块 hello.ko 编译信息
CC [M] drivers/char/hello.o
MODPOST Module.symvers
CC [M] drivers/char/hello.mod.o
LD [M] drivers/char/hello.ko
13.2 内核模块编译步骤
- 步骤一:
将每个源文件编译为对应的 .o 目标文件
将单个或多个 .o 目标文件链接成模块文件 module.o
生成对应的 module.mod 文件
生成 modules.order 文件,里面保存所有的 ko 文件信息
- 流程示例:
add.o、sub.o、... ==> hello.o ==> hello.mod ==> modules.order
或者 hello.o ==> hello.mod ==> modules.order
- 步骤二:
从 modules.order 文件中查找所有的 ko 文件
使用 modpost,为每个 ko 模块创建 module.mod.c 文件
创建 Module.symvers 文件,保存导出的符号(EXPORT_SYMBOL)及 CRC 值
将 module.o 和 module.mod.o 链接成 module.ko
- 流程示例:
modules.order ⇒ hello.mod.c ⇒ Module.symvers
hello.o + hello.mod.o ⇒ hello.ko
- 步骤三:
生成和内核模块相关的信息:版本魔幻数
License、versions、alias
13.3 此前实验代码示例
13.4 模块编译对应的Makefile
- modules 目标对应的规则:
# linux-5.10.4/Makefile:
PHONY += modules
modules: $(MODORDER)
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost
# linux-5.10.4/scripts/Makefile.modpost:
PHONY := __modpost
__modpost: $(output-symdump)
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modfinal
# linux-5.10.4/scripts/Makefile.modfinal:
PHONY := __modfinal
# find all modules listed in modules.order
modules := $(sort $(shell cat $(MODORDER)))
__modfinal: $(modules)
@:
$(modules): %.ko: %.o %.mod.o scripts/module.lds FORCE
+$(call if_changed,ld_ko_o)
# cat modules.order
drivers/char/hello.ko
14. modules_install 过程分析
模块安装信息:
- 模块安装对应的 Makefile
# linux-5.10.4/Makefile:
# Target to install modules
PHONY += modules_install
modules_install: _modinst_ _modinst_post
# External module support.
PHONY += modules_install
modules_install: _emodinst_ _emodinst_post
PHONY += _modinst_
_modinst_:
@rm -rf $(MODLIB)/kernel
@rm -f $(MODLIB)/source
@mkdir -p $(MODLIB)/kernel
@ln -s $(abspath $(srctree)) $(MODLIB)/source
@if [ ! $(objtree) -ef $(MODLIB)/build ]; then \
rm -f $(MODLIB)/build ; \
ln -s $(CURDIR) $(MODLIB)/build ; \
fi
@sed 's:^:kernel/:' modules.order > $(MODLIB)/modules.order
@cp -f modules.builtin $(MODLIB)/
@cp -f $(objtree)/modules.builtin.modinfo $(MODLIB)/
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modinst
# This depmod is only for convenience to give the initial
# boot a modules.dep even before / is mounted read-write. However the
# boot script depmod is the master version.
PHONY += _modinst_post
_modinst_post: _modinst_
$(call cmd,depmod)
PHONY += _emodinst_
_emodinst_:
$(Q)mkdir -p $(MODLIB)/$(install-dir)
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modinst
PHONY += _emodinst_post
_emodinst_post: _emodinst_
$(call cmd,depmod)
# linux-5.10.4/scripts/Makefile.modinst:
PHONY := __modinst
__modinst:
modules := $(sort $(shell cat $(if $(KBUILD_EXTMOD),$(KBUILD_EXTMOD)/)modules.order))
PHONY += $(modules)
__modinst: $(modules)
@:
modinst_dir = $(if $(KBUILD_EXTMOD),$(ext-mod-dir),kernel/$(@D))
$(modules):
$(call cmd,modules_install,$(MODLIB)/$(modinst_dir)) # 实际调用cmd_modules_install
cmd_modules_install = \
mkdir -p $(2) ; \
cp $@ $(2) ; \
$(mod_strip_cmd) $(2)/$(notdir $@) ; \
$(mod_sign_cmd) $(2)/$(notdir $@) $(patsubst %,|| true,$(KBUILD_EXTMOD)) ; \
$(mod_compress_cmd) $(2)/$(notdir $@)
15. headers_install 过程分析
- 目标 header 对应的规则
# linux-5.10.4/Makefile:
hdr-inst := -f $(srctree)/scripts/Makefile.headersinst obj
PHONY += headers
headers: $(version_h) scripts_unifdef uapi-asm-generic archheaders archscripts
$(if $(wildcard $(srctree)/arch/$(SRCARCH)/include/uapi/asm/Kbuild),, \
$(error Headers not exportable for the $(SRCARCH) architecture))
$(Q)$(MAKE) $(hdr-inst)=include/uapi
$(Q)$(MAKE) $(hdr-inst)=arch/$(SRCARCH)/include/uapi
# 简化一下
headers: $(version_h) scripts_unifdef uapi-asm-generic archheaders archscripts
make -f scripts/Makefile.headersinst obj=include/uapi
make -f scripts/Makefile.headersinst obj=arch/arm/include/uapi
# linux-5.10.4/scripts/Makefile.headersinst:
PHONY := __headers
__headers: $(all-headers)
ifneq ($(unwanted),)
$(call cmd,remove)
endif
@:
# Add dst path prefix
all-subdirs := $(addprefix $(dst)/, $(all-subdirs))
src-headers := $(addprefix $(dst)/, $(src-headers))
gen-headers := $(addprefix $(dst)/, $(gen-headers))
all-headers := $(src-headers) $(gen-headers)
src := $(srctree)/$(obj)
gen := $(objtree)/$(subst include/,include/generated/,$(obj))
dst := usr/include
$(src-headers): $(dst)/%.h: $(src)/%.h $(srctree)/scripts/headers_install.sh FORCE
$(call if_changed,install) # 实际调用cmd_install
$(gen-headers): $(dst)/%.h: $(gen)/%.h $(srctree)/scripts/headers_install.sh FORCE
$(call if_changed,install)
# 其中:
# src = include/uapi
# src-headers = include/uapi/$(src-subdirs)/*.h
# src-headers := $(filter-out $(no-export-headers), $(src-headers))
# src-headers := include/uapi/asm-generic/*.h include/uapi/linux/*.hinclude/uapi/sound/*.h …
cmd_install = $(CONFIG_SHELL) $(srctree)/scripts/headers_install.sh $< $@
- gen-headers 对应的规则:
# linux-5.10.4/scripts/Makefile.headersinst:
gen := $(objtree)/$(subst include/,include/generated/,$(obj))
gen-headers := $(if $(gen-subdirs), $(shell cd $(gen) && find $(gen-subdirs) -name '*.h'))
$(gen-headers): $(dst)/%.h: $(gen)/%.h $(srctree)/scripts/headers_install.sh FORCE
$(call if_changed,install)
# 其中:
# gen = include/generated/uapi
# dst := usr/include
cmd_install = $(CONFIG_SHELL) $(srctree)/scripts/headers_install.sh $< $@
16. 内核中的空头文件探秘
- 前面内容:.config 生成的三个主要文件:
include/config/auto.conf # 用来配置Makefile
include/generated/autoconf.h # 在C程序中引用
include/config/*.h # 空头文件
- Kbuild Makefile
跟踪三种依赖关系:(修改下面的任意一项,编译都能够跟踪到修改的依赖项)
- 编译所需要的所有源文件:*.c
- 源文件 .c 中包含的各种头文件:.h
- 所有程序中使用的配置选项:CONFIG_XX(内核会生成对应的空头文件)
#include <xx.h>
#ifdef CONFIG_SMP
__boot_cpu_id = cpu;
#endif
16.1 示例:头文件依赖
16.1.1 方式一
没有添加对 pi.h 文件的依赖,修改 PI 从 3.14 到 3.1415926 时,a.out 没有对 pi.h 产生依赖关系,所以:
在 Makefile 中添加对 pi.h 的依赖:
16.1.2 方式二
- gcc 的 -MD 编译选项
生成文件关联的信息,输出的信息导入到 hello.d 的文件里面。
- 在 Makefile 中包含 hello.d 文件
16.2 内核中头文件依赖
# linux-5.10.4/scripts/Makefile.lib
c_flags = -Wp,-MMD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) \
-include $(srctree)/include/linux/compiler_types.h \
$(_c_flags) $(modkern_cflags) \
$(basename_flags) $(modname_flags)
- 内核文件编译示例
# linux-5.10.4/scripts/Makefile.lib
c_flags = -Wp,-MMD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) \
-include $(srctree)/include/linux/compiler_types.h \
$(_c_flags) $(modkern_cflags) \
$(basename_flags) $(modname_flags)
# .header_uuxiang.o.cmd:
......
source_drivers/char/header_uuxiang.o := drivers/char/header_uuxiang.c
deps_drivers/char/header_uuxiang.o := \
$(wildcard include/config/smp.h) \
$(wildcard include/config/uuxiang.h) \
include/linux/kconfig.h \
$(wildcard include/config/cc/version/text.h) \
......
drivers/char/pi.h \
drivers/char/header_uuxiang.o: $(deps_drivers/char/header_uuxiang.o)
$(deps_drivers/char/header_uuxiang.o):
# Makefile.build:
$(obj)/%.o: $(src)/%.c $(recordmcount_source) $(objtool_dep) FORCE
$(call if_changed_rule,cc_o_c)
-include $(foreach f,$(existing-targets),$(dir $(f)).$(notdir $(f)).cmd)
existing-targets := $(wildcard $(sort $(targets)))
targets += $(targets-for-builtin) $(targets-for-modules)
# Kbuild.include:
# Execute the command and also postprocess generated .d dependencies file.
if_changed_dep = $(if $(newer-prereqs)$(cmd-check),$(cmd_and_fixdep),@:)
cmd_and_fixdep = \
$(cmd); \
scripts/basic/fixdep $(depfile) $@ '$(make-cmd)' > $(dot-target).cmd;\
rm -f $(depfile)
depfile = $(subst $(comma),_,$(dot-target).d) # depfile保存gcc -MD生成的依赖文件
# Name of target with a '.' as filename prefix. foo/bar.o => foo/.bar.o
dot-target = $(dir $@).$(notdir $@)