02 - Kbuild 子系统

---- 整理自 王利涛老师 课程
实验环境:宅学部落 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 生成的第一阶段

  1. make vexpress_defconfig ==> .config
  2. make menuconfig ==> .config

分析如下:编译时执行 make vexpress_defconfigmake 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 工作流程

  1. 根据 ARCH 变量,首先 include arch/$(ARCH)/Makefile
  2. 读取 .config 文件:读取用户的各种配置变量
  3. 解析预定义目标、变量,构建依赖关系
  4. 编译各个模块或组件(使用 scripts/Makefile.*)
    • 将每个目录下的源文件编译为对应的 .o 目标文件
    • 将 .o 目标文件归档为 built-in.a
  5. 将所有对象链接成 vmlinux
  6. 编译模块…

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

主要是以下几种形式的编译:

  1. 单文件模块:
obj-y=hello.o
  1. 复合模块:
obj-y=hello.o  hello-y= a.o b.o c.o
obj-y=hello.o  hello-objs= a.o b.o c.o
  1. 子目录:
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 地址运行。
  • 这里有几种情况:
      1. uImage 下载到内存的地址,和 -a 指定的地址相同,uboot 不会对镜像做内存搬移。
      1. uImage 下载到内存的地址,和 -a 指定的地址不同,uboot 会将下载地址的 uImage 镜像文件(去掉64字节头部信息)搬移到 -a 指定的地址。
      1. -a 和 -e 参数地址相同时,理论上,需要 uImage 的下载地址和 -a 地址不同,镜像做过搬移后,到 -e 地址运行是正常的。
      1. -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 内核模块编译步骤

  1. 步骤一:
    将每个源文件编译为对应的 .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
  1. 步骤二:
    从 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
  1. 步骤三:
    生成和内核模块相关的信息:版本魔幻数
    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
    跟踪三种依赖关系:(修改下面的任意一项,编译都能够跟踪到修改的依赖项
  1. 编译所需要的所有源文件:*.c
  2. 源文件 .c 中包含的各种头文件:.h
  3. 所有程序中使用的配置选项: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 $@)
  • 28
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

uuxiang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值