---- 整理自 王利涛老师 课程
实验环境:宅学部落 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) :这是一个变量,通常用于控制命令输出的冗长程度,
# 可能被定义为$(Q)=@ 以静默执行命令。
#
# $(MAKE) $(build)=scripts/kconfig $@ :
# 这行命令调用 make 并将变量 $(build) 设置为 scripts/kconfig,
# 然后将当前的目标 $@ 传递给 make。这表示要在 scripts/kconfig 目录中执行与目标相关的构建过程。
#
# 这里的$(build)定义如下:
# linux-5.10.4/scripts/Kbuild.include
build := -f $(srctree)/scripts/Makefile.build obj
# 展开后得到:
vexpress_defconfig: outputmakefile scripts_basic FORCE
make -f $(srctree)/scripts/Makefile.build obj=scripts/kconfig vexpress_defconfig
menuconfig: outputmakefile scripts_basic FORCE
make -f $(srctree)/scripts/Makefile.build obj=scripts/kconfig menuconfig
这里来到 linux-5.10.4/scripts/kconfig 目录:
# linux-5.10.4/scripts/kconfig/Makefile
ifdef KBUILD_KCONFIG
Kconfig := $(KBUILD_KCONFIG)
else
Kconfig := Kconfig # 我们未定义KBUILD_KCONFIG,Kconfig等于Kconfig
endif
%_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/arm/configs/vexpress_defconfig Kconfig
menuconfig: scripts/kconfig/mconf
$(Q)scripts/kconfig/mconf $(silent) Kconfig
依赖于 scripts/kconfig/conf 和 scripts/kconfig/mconf,先分析一下 conf 和 mconf 程序:
# scripts/kconfig/conf.c
%_defconfig:
| --> main()
| --> conf_parse
| --> conf_read
| --> conf_write
| --> name = conf_get_configname();
# scripts/kconfig/mconf.c
menuconfig:
| --> main()
| --> conf_parse(name);
| --> conf_read
| --> set_config_filename(conf_get_configname());
| --> handle_exit()
| --> conf_write
| --> name = conf_get_configname();
// linux-5.10.4/scripts/kconfig/confdata.c
const char *conf_get_configname(void)
{
char *name = getenv("KCONFIG_CONFIG");
return name ? name : ".config";
}
6.2.2 .config 生成的第二阶段
- .config ==> syncconfig ==> Makefile
linux-5.10.4/include/config/auto.conf:用来配置 Makefilelinux-5.10.4/include/generated/autoconf.h:供 C 程序引用linux-5.10.4/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)
# 其中cmd的定义
# linux-5.10.4/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
%/config/auto.conf %/config/auto.conf.cmd %/generated/autoconf.h: .config
@set -e; $(echo-cmd) make -f $(srctree)/Makefile syncconfig
接着分析目标 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
# 展开得到:
syncconfig:scripts/kconfig/conf
$(Q)scripts/kconfig/conf $(silent) --syncconfig Kconfig
// linux-5.10.4/scripts/kconfig/conf.c
syncconfig:
| --> sync_kconfig = 1;
| --> if (conf_write_autoconf(sync_kconfig) && sync_kconfig)
| --> const char *autoconf_name = conf_get_autoconfig_name(); // linux-5.10.4/scripts/kconfig/confdata.c
| --> conf_write_dep("include/config/auto.conf.cmd");
| --> conf_touch_deps() // 根据include/config/auto.conf.cmd生成include/config/auto.conf
out_h = fopen(".tmpconfig.h", "w");
name = "include/generated/autoconf.h";
rename(".tmpconfig.h", name)
// 其中 linux-5.10.4/include/config/auto.conf.cmd 的内容:
// linux-5.10.4/include/config/auto.conf.cmd
deps_config := ... \
kernel/trace/Kconfig \
certs/Kconfig \
fs/udf/Kconfig \
... // 省略了很多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:
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 函数
cmd_link-vmlinux = \
$(CONFIG_SHELL) $< "$(LD)" "$(KBUILD_LDFLAGS)" "$(LDFLAGS_vmlinux)"; \
$(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true)
ARCH_POSTLINK := $(wildcard $(srctree)/arch/$(SRCARCH)/Makefile.postlink)
# 展开后:
vmlinux: scripts/link-vmlinux.sh autoksyms_recursive $(vmlinux-deps) FORCE
cmd_link-vmlinux = \
sh scripts/link-vmlinux.sh "$(LD)" "$(LDFLAGS)" "$(LDFLAGS_vmlinux)"; \
$(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) vmlinux, 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中)
# 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)
# 展开后:
vmlinux: scripts/link-vmlinux.sh autoksyms_recursive $(vmlinux-deps)
cmd_link-vmlinux = \
sh scripts/link-vmlinux.sh "$(LD)" "$(LDFLAGS)" "$(LDFLAGS_vmlinux)"; \
$(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true)
linux-5.10.4/scripts/link-vmlinux.sh脚本- 链接
$(KBUILD_VMLINUX_OBJS)中的所有built-in.a - 链接
$(KBUILD_VMLINUX_LIBS)中的所有lib.a - 符号处理:生成
linux-5.10.4/System.map、linux-5.10.4/include/generated/autoksyms.h等文件
- 链接
# linux-5.10.4/scripts/link-vmlinux.sh
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}
# 使用链接器(LD),结合链接器选项(KBUILD_LDFLAGS),
# 将上面的 objects 链接为一个目标文件(${1} 指输出文件名,即vmlinux.o)
}
# link vmlinux.o
info LD vmlinux.o
modpost_link vmlinux.o
objtool_link vmlinux.o
# 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:
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/
# 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:
$(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) \
single-build=$(if $(filter-out $@/, $(filter $@/%, $(KBUILD_SINGLE_TARGETS))),1) \
need-builtin=1 need-modorder=1
- 示例:编译 sound 目录
# linux-5.10.4/scripts/Makefile.build:
PHONY := __build
__build: $(if $(KBUILD_BUILTIN), $(targets-for-builtin)) \
$(if $(KBUILD_MODULES), $(targets-for-modules)) \
$(subdir-ym) $(always-y)
@:
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
# 在依赖的目标文件发生变化时,使用ar命令重新生成一个静态库文件built-in.a
# 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)))
# 这段代码的作用是递归展开 $(obj-y) 列表中的每一个对象文件,检查它们是否包含子对象文件(通过 -objs 或 -y 等变量)。
# 如果有子对象文件,则将这些子对象文件添加到 real-obj-y 中;如果没有,则直接使用该对象文件。
# 最终,real-obj-y 包含了实际参与编译的所有对象文件(包括递归展开的子对象文件)。
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
@$(kecho) ' Kernel: arch/arm/boot/zImage is ready'
Image: vmlinux
make -f scripts/Makefile.build obj=arch/arm/boot arch/arm/boot/Image
@$(kecho) ' Kernel: arch/arm/boot/Image is ready'
来到目录 arch/arm/boot 下:
# 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 生成分析
来到目录 arch/arm/boot/compressed 下:
# 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
# 展开后得到:
$(obj)/piggy_data: $(obj)/../Image FORCE
$(call if_changed,gzip) # 实际调用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
bswapsdi2 = $(obj)/bswapsdi2.o
lib1funcs = $(obj)/lib1funcs.o
ashldi3 = $(obj)/ashldi3.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)
# 展开后:
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 \
arch/arm/boot/compressed/lib1funcs.o
arch/arm/boot/compressed/ashldi3.o
arch/arm/boot/compressed/bswapsdi2.o
$(objtree)/drivers/firmware/efi/libstub/lib.a
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
- hello.o ==> hello.mod ==> modules.order ⇒ module.mod.c ==> Module.symvers
- hello.o + hello.mod.o ==> hello.ko
13.3 此前实验代码示例

13.4 模块编译对应的Makefile
- modules 目标对应的规则:
# linux-5.10.4/Makefile:
PHONY += modules
modules: $(MODORDER)
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost
来到文件 $(srctree)/scripts/Makefile.modpost 中:
# linux-5.10.4/scripts/Makefile.modpost:
PHONY := __modpost
__modpost: $(output-symdump)
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modfinal
来到 $(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)
# 实际调用 cmd_ld_ko_o
# 通过 ld 链接器将一组对象文件 .o 链接成一个内核模块 .ko
# 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 $@)
6117

被折叠的 条评论
为什么被折叠?



