构建¶
辅助工具¶
- Linux source code - Bootlin Elixir Cross Referencer:在这里对比多版本源码比 GitHub 方便些。
获取源码¶
-
Git
-
RedHat:Building a Custom Kernel :: Fedora Docs
dnf builddep kernel dnf download --source kernel rpm2cpio kernel*.rpm | cpio -idmv 'linux-*.tar.xz' tar xf linux-* cd linux-*
若要在
/usr/src
下放置源码:
配置¶
内核模块¶
如果只需要特定的内核模块,参考 Building External Modules¶
cd drivers/net/bonding
make -C /lib/modules/`uname -r`/build M=$PWD
make -C /lib/modules/`uname -r`/build M=$PWD modules_install
此时模块会被安装到 /lib/modules/$(uname -r)/update/*.ko
。直接用其覆盖目标模块即可。
Kernel Build System¶
KBuild 的所有规则放置在 scripts/Makefile.*
中。
一些小细节:
- 为了支持构建命令变化时重构,Kbuild 令 Target 依赖
FORCE
并使用$(call if_changed, <command>)
。 - 为了支持不同选项的编译器,Kbuild 使用
cflags-y += $(call cc-option,<option>)
来指定编译器选项。如果不支持,就不会添加。
obj-
的用法¶
内核 Makefile
中的各个模块使用下面的语法:
其中 CONFIG_
的值从 Kconfig 中读取。如果值为 y
,则会添加到 obj-y
列表。
obj-y
列表就是内核构建要编译的所有 .o
文件,最终会被全部 ar
成一个 .a
链入内核。
obj-m
将作为可动态加载的内核模块构建。
该列表除了接受 .o
文件,也接受子文件夹,以此实现递归构建。
除了 obj-
,内核也识别 <module>-
、ccflags-
和 ldflags-
等。
模块构建过程¶
我们知道模块构建需要传递 M=
参数,内核 Makefile 会这样处理:
# 赋值给 KBUILD_EXTMOD,接下来用它控制分支
ifeq ("$(origin M)", "command line")
KBUILD_EXTMOD := $(M)
endif
# 设置构建目标为 modules
PHONY := __all
__all:
PHONY += all
ifeq ($(KBUILD_EXTMOD),)
__all: all
else
__all: modules
endif
# 将 build-dir 设置为模块目录
ifeq ($(KBUILD_EXTMOD),)
build-dir := .
# 内核构建...
else # KBUILD_EXTMOD
KBUILD_BUILTIN :=
KBUILD_MODULES := 1
build-dir := $(KBUILD_EXTMOD)
# 模块构建...
endif # KBUILD_EXTMOD
# 定义 modules 目标,依赖链:__all -> modules -> modpost
# -> modules_check -> MODORDER -> build-dir -> 递归构建
PHONY += modules modules_install modules_sign modules_prepare
export MODORDER := $(extmod_prefix)modules.order
ifdef CONFIG_MODULES # 内核开启了 CONFIG_MODULES
$(MODORDER): $(build-dir)
@:
modules: modpost
ifneq ($(KBUILD_MODPOST_NOFINAL),1)
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modfinal
endif
PHONY += modules_check
modules_check: $(MODORDER)
$(Q)$(CONFIG_SHELL) $(srctree)/scripts/modules-check.sh $<
else # 如果内核未开启 CONFIG_MODULES,则不构建 modules 目标
modules:
@:
KBUILD_MODULES :=
endif # CONFIG_MODULES
# 定义结束目标
PHONY += modpost
modpost: $(if $(single-build),, $(if $(KBUILD_BUILTIN), vmlinux.o)) \
$(if $(KBUILD_MODULES), modules_check)
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost
# 从 build-dir 开始启动递归构建
PHONY += $(build-dir)
$(build-dir): prepare
$(Q)$(MAKE) $(build)=$@ need-builtin=1 need-modorder=1 $(single-goals)
经常出现的 $(build)
定义如下:
###
# Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj=
# Usage:
# $(Q)$(MAKE) $(build)=dir
build := -f $(srctree)/scripts/Makefile.build obj
这个文件被很多 Makefile 使用,提供一些通用函数。
使用 V=1
开启详细输出,可以看到 Makefile 的递归过程:
$ make -C /lib/modules/<kernel>/build M="/root/rpmbuild/BUILD/<module>/obj/default"
make[1]: Entering directory '/usr/src/kernels/<kernel>'
make --no-print-directory -C /usr/src/kernels/<kernel> \
-f /usr/src/kernels/<kernel>/Makefile modules
make -f ./scripts/Makefile.build obj=/root/rpmbuild/BUILD/<module>/obj/default need-builtin=1 need-modorder=1
make -f ./scripts/Makefile.build obj=/root/rpmbuild/BUILD/<module>/obj/default/compat \
need-builtin=1 \
need-modorder=1 \
make -f ./scripts/Makefile.build obj=/root/rpmbuild/BUILD/<module>/obj/default/drivers/infiniband \
need-builtin= \
need-modorder=1 \
实例:infiniband 驱动构建¶
记住,Kernel 构建的目标是 obj-y
等列表。当 Make 递归向下时,传递给 Makefile.build
的 obj
是它构目标列表的基础。比如,它会检测 obj
下的配置文件,决定构建顺序:
ifdef need-builtin
targets-for-builtin += $(obj)/built-in.a
endif
ifdef need-modorder
targets-for-modules += $(obj)/modules.order
endif
# 如果 obj 是子文件夹,用 subdir
$(obj)/: $(if $(KBUILD_BUILTIN), $(targets-for-builtin)) \
$(if $(KBUILD_MODULES), $(targets-for-modules)) \
$(subdir-ym) $(always-y)
@:
# Built-in and composite module parts
$(obj)/%.o: $(src)/%.c $(recordmcount_source) FORCE
$(call if_changed_rule,cc_o_c)
$(call cmd,force_checksrc)
接下来列出部分 Makefile 中的列表:
# obj/default/Makefile
obj-y := compat$(CONFIG_COMPAT_VERSION)/
obj-$(CONFIG_INFINIBAND) += drivers/infiniband/
# obj/default/drivers/infiniband/Makefile
obj-$(CONFIG_INFINIBAND) += core/
obj-$(CONFIG_INFINIBAND) += hw/
# obj/default/drivers/infiniband/core/Makefile
obj-$(CONFIG_INFINIBAND) += ib_core.o ib_cm.o iw_cm.o \
$(infiniband-y)
进入叶子 Makefile 时,仅有 $(obj)/%.o
目标,将经过下面的处理:
```makefile title="scripts/Kbuild.include
Usage: $(call if_changed_rule,foo)¶
Will check if $(cmd_foo) or any of the prerequisites changed,¶
and if so will execute $(rule_foo).¶
if_changed_rule = \((if \((if-changed-cond),\)(rule_\)(1)),@:)
C (.c) files¶
The C file is compiled and updated dependency information is generated.¶
(See cmd_cc_o_c + relevant part of rule_cc_o_c)¶
is-single-obj-m = $(and \((part-of-module),\)(filter $@, $(obj-m)),y)
When a module consists of a single object, there is no reason to keep LLVM IR.¶
Make $(LD) covert LLVM IR to ELF here.¶
ifdef CONFIG_LTO_CLANG cmd_ld_single_m = $(if $(is-single-obj-m), ; $(LD) $(ld_flags) -r -o $(tmp-target) $@; mv $(tmp-target) $@) endif
```makefile title="scripts/Makefile.lib"
cmd_objtool = $(if $(objtool-enabled), ; $(objtool) $(objtool-args) $@)
quiet_cmd_cc_o_c = CC $(quiet_modtag) $@
cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $< \
$(cmd_ld_single_m) \
$(cmd_objtool)
最终的命令例:
gcc \
-Wp,-MMD,/root/rpmbuild/BUILD/<module>/obj/default/drivers/infiniband/sw/rxe/.rdma_rxe_dummy.o.d -nostdinc -D__OFED_BUILD__ -D__KERNEL__ -O2 -flto=auto -ffat-lto-objects -fexceptions -g -grecord-gcc-switches -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -specs=/usr/lib/rpm/TencentOS/TencentOS-hardened-cc1 -fstack-protector-strong -m64 -march=x86-64-v2 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protection -DCOMPAT_BASE="\"mlnx-ofa_kernel-compat-20230220-1058-4ff99ef\"" -DCOMPAT_BASE_TREE="\"mlnx_ofed/mlnx-ofa_kernel-4.0.git\"" -DCOMPAT_BASE_TREE_VERSION="\"4ff99ef\"" -DCOMPAT_PROJECT="\"Compat-mlnx-ofed\"" -DCOMPAT_VERSION="\"4ff99ef\"" -include /lib/modules/<kernel>/build/include/generated/autoconf.h -include /lib/modules/<kernel>/build/include/linux/kconfig.h -include /root/rpmbuild/BUILD/<module>/obj/default/include/linux/compat-2.6.h -I/root/rpmbuild/BUILD/<module>/obj/default/include -I/root/rpmbuild/BUILD/<module>/obj/default/include/uapi -I/root/rpmbuild/BUILD/<module>/obj/default/drivers/infiniband/debug -I./arch/x86/include -Iarch/x86/include/generated -Iinclude -I./arch/x86/include/uapi -Iarch/x86/include/generated/uapi -I./include -I./include/uapi -Iinclude/generated/uapi -I./arch/x86/include -Iarch/x86/include/generated -include ./include/linux/compiler_types.h -D__KERNEL__ -fmacro-prefix-map=./= -std=gnu11 -fshort-wchar -funsigned-char -fno-common -fno-PIE -fno-strict-aliasing -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -mno-avx -fcf-protection=none -m64 -falign-jumps=1 -falign-loops=1 -mno-80387 -mno-fp-ret-in-387 -mpreferred-stack-boundary=3 -mskip-rax-setup -mtune=generic -mno-red-zone -mcmodel=kernel -Wno-sign-compare -fno-asynchronous-unwind-tables -mindirect-branch=thunk-extern -mindirect-branch-register -mindirect-branch-cs-prefix -mfunction-return=thunk-extern -fno-jump-tables -fpatchable-function-entry=16,16 -fno-delete-null-pointer-checks -O2 -fno-allow-store-data-races -fstack-protector-strong -fno-stack-clash-protection -pg -mrecord-mcount -mfentry -DCC_USING_FENTRY -fno-inline-functions-called-once -falign-functions=16 -fno-strict-overflow -fno-stack-check -fconserve-stack -fno-builtin-wcslen -Wall -Wundef -Werror=implicit-function-declaration -Werror=implicit-int -Werror=return-type -Werror=strict-prototypes -Wno-format-security -Wno-trigraphs -Wno-frame-address -Wno-address-of-packed-member -Wframe-larger-than=2048 -Wno-main -Wno-unused-but-set-variable -Wno-unused-const-variable -Wno-dangling-pointer -Wvla -Wno-pointer-sign -Wcast-function-type -Wno-array-bounds -Wno-alloc-size-larger-than -Wimplicit-fallthrough=5 -Werror=date-time -Werror=incompatible-pointer-types -Werror=designated-init -Wenum-conversion -Wno-unused-but-set-variable -Wno-unused-const-variable -Wno-restrict -Wno-packed-not-aligned -Wno-format-overflow -Wno-format-truncation -Wno-stringop-overflow -Wno-stringop-truncation -Wno-missing-field-initializers -Wno-type-limits -Wno-shift-negative-value -Wno-maybe-uninitialized -Wno-sign-compare -g -Wno-date-time -DMODULE -DKBUILD_BASENAME='"rdma_rxe_dummy"' -DKBUILD_MODNAME='"rdma_rxe"' -D__KBUILD_MODNAME=kmod_rdma_rxe \
-c -o \
/root/rpmbuild/BUILD/<module>/obj/default/drivers/infiniband/sw/rxe/rdma_rxe_dummy.o \
/root/rpmbuild/BUILD/<module>/obj/default/drivers/infiniband/sw/rxe/rdma_rxe_dummy.c \
; \
./tools/objtool/objtool \
--hacks=jump_label --hacks=noinstr --hacks=skylake --orc --retpoline --rethunk --static-call --uaccess --prefix=16 --module \
/root/rpmbuild/BUILD/<module>/obj/default/drivers/infiniband/sw/rxe/rdma_rxe_dummy.o
编译选项¶
接下来探究 c_flags
,它定义为:
c_flags = -Wp,-MMD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) \
-include $(srctree)/include/linux/compiler_types.h \
$(_c_flags) $(modkern_cflags) \
$(basename_flags) $(modname_flags)
逐个展开:
# $(depfile)
depfile = $(subst $(comma),_,$(dot-target).d)
# $(NOSTDINC_FLAGS)
-nostdinc
# $(LINUXINCLUDE)
LINUXINCLUDE := \
-I$(srctree)/arch/$(SRCARCH)/include \
-I$(objtree)/arch/$(SRCARCH)/include/generated \
$(if $(building_out_of_srctree),-I$(srctree)/include) \
-I$(objtree)/include \
$(USERINCLUDE)
USERINCLUDE := \
-I$(srctree)/arch/$(SRCARCH)/include/uapi \
-I$(objtree)/arch/$(SRCARCH)/include/generated/uapi \
-I$(srctree)/include/uapi \
-I$(objtree)/include/generated/uapi \
-include $(srctree)/include/linux/compiler-version.h \
-include $(srctree)/include/linux/kconfig.h
# -include $(srctree)/include/linux/compiler_types.h
# $(_c_flags)
见下文
# $(modkern_cflags)
modkern_cflags = \
$(if $(part-of-module), \
$(KBUILD_CFLAGS_MODULE) $(CFLAGS_MODULE), \
$(KBUILD_CFLAGS_KERNEL) $(CFLAGS_KERNEL) $(modfile_flags))
# $(basename_flags)
basename_flags = -DKBUILD_BASENAME=$(call name-fix,$(basetarget))
其中,_c_flags
的展开有几个步骤:
-
汇集
KBUILD_CPPFLAGS
、KBUILD_CFLAGS
、ccflags-y
和CFLAGS_$(target-stem).o
,然后基于ccflags-remove-y
和CFLAGS_REMOVE_$(target-stem).o
进行过滤scripts/Makefile.lib_c_flags = $(filter-out $(CFLAGS_REMOVE_$(target-stem).o), \ $(filter-out $(ccflags-remove-y), \ $(KBUILD_CPPFLAGS) $(KBUILD_CFLAGS) $(ccflags-y)) \ $(CFLAGS_$(target-stem).o))
其中
KBUILD_*FLAGS
在各个 Makefile 中定义,比如不同的体系结构需要不同的 FLAG。并且,用户可以通过环境变量导入更多的 Flag:# Add user supplied CPPFLAGS, AFLAGS, CFLAGS and RUSTFLAGS as the last assignments KBUILD_CPPFLAGS += $(KCPPFLAGS) KBUILD_AFLAGS += $(KAFLAGS) KBUILD_CFLAGS += $(KCFLAGS) KBUILD_RUSTFLAGS += $(KRUSTFLAGS)
此外,
ccflags-y
也可以受用户传入的EXTRA_CFLAGS
控制: -
添加 KConfig 控制的选项:
-
out-of-tree 添加 Include 结尾:
# $(srctree)/$(src) for including checkin headers from generated source files # $(objtree)/$(obj) for including generated headers from checkin source files ifeq ($(KBUILD_EXTMOD),) ifdef building_out_of_srctree _c_flags += -I $(srctree)/$(src) -I $(objtree)/$(obj) _a_flags += -I $(srctree)/$(src) -I $(objtree)/$(obj) _cpp_flags += -I $(srctree)/$(src) -I $(objtree)/$(obj) endif endif
modpost 和 modules_check¶
回顾:
modpost: $(if $(single-build),, $(if $(KBUILD_BUILTIN), vmlinux.o)) \
$(if $(KBUILD_MODULES), modules_check)
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost
Makefile.modpost
负责的功能包括:模块的版本信息、符号依赖关系以及模块的 CRC 校验等。
- 模块版本信息生成:
- 通过
modpost
工具生成<module>.mod.c
文件,其中包含模块的版本信息(如MODULE_VERSION
、MODULE_ALIAS
、MODULE_LICENSE
等)。 -
这些信息会被嵌入到模块的 ELF 段中,供内核加载时使用。
-
符号依赖与版本控制:
- 生成
Module.symvers
文件,记录所有导出符号的 CRC 值,用于模块间的版本控制。 -
如果启用了
CONFIG_MODVERSIONS
,还会处理符号版本信息,确保模块间的兼容性。 -
依赖关系处理:
- 读取
modules.order
文件,获取所有模块的构建顺序和依赖关系。 -
如果某些输入文件缺失(如
vmlinux.o
或Module.symvers
),会发出警告,但不会中断构建(除非显式设置KBUILD_MODPOST_WARN=1
)。 -
特殊配置支持:
- 支持
CONFIG_TRIM_UNUSED_KSYMS
,通过白名单过滤未使用的内核符号。 -
支持外部模块构建(
KBUILD_EXTMOD
),允许从外部目录加载额外的符号信息(KBUILD_EXTRA_SYMBOLS
)。 -
错误处理与警告:
- 如果某些输入文件缺失,会输出警告信息,但默认情况下不会终止构建。
-
可以通过设置
KBUILD_MODPOST_WARN=1
将错误降级为警告。 -
性能优化:
-
通过
-T
参数传递modules.order
文件,避免因参数过长导致的性能问题。 -
模块构建的阶段性:
- 该文件是模块构建的第二阶段,第一阶段生成
.o
文件和.mod
文件,第二阶段通过modpost
处理这些文件。
值得一提的是,该阶段部分文件使用的 FLAGS 有可能与构建阶段不同。以 .mod.o
为例:
quiet_cmd_cc_o_c = CC [M] $@
cmd_cc_o_c = $(CC) $(filter-out $(CC_FLAGS_CFI) $(CFLAGS_GCOV), $(c_flags)) -c -o $@ $<
%.mod.o: %.mod.c FORCE
$(call if_changed_dep,cc_o_c)
不一致的是 modkern_cflags
:
modkern_cflags = \
$(if $(part-of-module), \
$(KBUILD_CFLAGS_MODULE) $(CFLAGS_MODULE), \
$(KBUILD_CFLAGS_KERNEL) $(CFLAGS_KERNEL) $(modfile_flags))
Todo
曾经遇到 .mod.o
受 EXTRA_CFLAGS
影响而构建阶段不受的情况,待研究。
modules.order
和 modules_install
¶
modules.order
用于记录模块的构建顺序和依赖关系,它将遍历子目录,如果子目录有 modules.order
文件,则将其内容追加到 modules.order
文件中,否则附加目标名。
cmd_modules_order = { $(foreach m, $(real-prereqs), \
$(if $(filter %/modules.order, $m), cat $m, echo $m);) :; } \
> $@
$(obj)/modules.order: $(obj-m) FORCE
$(call if_changed,modules_order)
modules_install
目标见 scripts/Makefile.modinst
,将读取 modules.order
文件并将其中的模块逐一安装。