Kbuild
定义
Kbuild指Linux内核源码树中用于把源代码转换为可运行内核镜像的那套构建规则与支撑脚本。它并不是一个独立发布的软件包,而是随内核源码一起分发的一组Makefile、配置文件和脚本的统称[1]。
按照内核文档的划分,这套体系包含五个部分:源码树根的顶层Makefile、记录配置结果的.config文件、位于arch/$(ARCH)/目录下的架构Makefile、提供通用规则的scripts/Makefile.*,以及分散在各子目录、数量约500个的kbuild Makefile[1]。分散的子目录构建文件通常命名为Makefile,也可以命名为Kbuild;两者同时存在时以Kbuild文件为准[3]。
原理
顶层Makefile是构建的入口。它读取配置过程产出的.config,据此确定要访问哪些子目录,并以递归下降的方式逐级调用下层Makefile[1]。顶层Makefile还以文本方式引入名为arch/$(ARCH)/Makefile的架构文件,后者向顶层提供与具体处理器架构有关的构建信息[1]。
子目录构建文件的核心是目标定义。把目标文件写入obj-y表示将其编入内核,写入obj-m表示构建为可加载模块;写成obj-$(CONFIG_FOO)的形式时,配置符号取值为y则编入内核,取值为m则编译为模块,取值既非y也非m则该文件既不编译也不链接[3]。一个模块由多个源文件组成时,通过<模块名>-y或<模块名>-objs列出组成它的目标文件[3]。
目标文件在目录内先被归档合并:编入内核的部分合为该目录的built-in.a,随后由链接脚本统一链接成vmlinux[1]。obj-y中文件的排列顺序会影响链接顺序,而module_init()等初始化函数按链接顺序在启动时被调用,因此调整顺序可能改变设备被探测的次序[3]。
依赖跟踪由Kbuild自行维护,跟踪对象包括全部前置的C与汇编源文件、这些文件里用到的配置选项,以及编译目标时使用的命令行;只要其中任一项变化,相关文件就会被重新编译[3]。对于内核基础设施未覆盖的场合,可以在构建文件中按GNU Make的常规写法自定义规则,此时用$(src)指向源文件所在目录、用$(obj)指向生成文件所在目录[1]。
配置系统与构建系统相互配合:内核通过make menuconfig等目标生成.config,配置语言的规格由Kconfig文件描述,而构建文件则读取配置结果来组织文件列表[4]。
发展历程
这套构建体系的形成早于本世纪第一个十年。早期文档由Michael Elizabeth Chastain编写,此后Kai Germaschewski、Sam Ravnborg等人陆续更新,文档末尾列出了这些维护者的署名[1]。
在3.2版内核的文档目录中,kbuild相关说明被拆成kbuild.txt、kconfig.txt、kconfig-language.txt、makefiles.txt和modules.txt等纯文本文件,并由00-INDEX列出索引[5]。3.19版内核的文档目录里增加了headers_install.txt,用于说明如何导出供用户空间使用的头文件[6]。
2019年6月,kbuild相关文档由纯文本转换为reStructuredText格式,文件名后缀改为.rst,并补充了段落与表格标记[7]。同年,用$(AR) rcSTP合并目标文件生成built-in.a的做法取代了旧的built-in.o,链接工作交由scripts/link-vmlinux.sh完成[3]。
2020年4月,Kbuild增加了LLVM=1开关,可一次性把默认工具链从GCC与GNU binutils切换为Clang与LLVM工具集[7]。2022年9月,随Rust语言支持进入内核,文档中补充了Rust相关说明,Kbuild随后也支持用Rust编写主机程序[7]。2024年9月,文档进一步澄清了构建外部模块时-C选项应指向内核构建输出目录而非源码目录[7]。
应用
最直接的用途是编译内核本身。用户或发行版打包者通过make menuconfig一类命令完成配置,再执行make得到vmlinux与模块,make install与make modules_install分别把内核镜像和模块安装到目标位置[4]。安装模块的默认路径为/lib/modules/$(KERNELRELEASE)/kernel/,外部模块默认安装到同级的extra/目录,可用INSTALL_MOD_PATH加上路径前缀[8]。
Kbuild也用于构建内核以外的模块。外部模块的典型做法是在自己的目录中给出一个Makefile,通过make -C <内核目录> M=<模块目录>调用内核的构建规则;模块名与组成文件写在名为Kbuild的文件里,构建时由内核侧的make再次调用[8]。这种方式使独立发布的驱动可以针对已构建好的内核树编译[8]。
输出物不止内核镜像。构建过程会生成modules.order,记录模块在Makefile中出现的次序,供modprobe在多个模块匹配同一别名时确定解析顺序;modules.builtin列出已编入内核的模块,使modprobe在遇到内建模块时不报错[9]。Module.symvers则记录本次构建导出的全部符号及其校验值,供后续编译外部模块时检查符号是否可用[8]。
此外,Kbuild可用于导出供用户空间使用的头文件,以及静态编译检查内核头文件是否自洽。设置KBUILD_OUTPUT或使用O=可以把构建产物输出到源码树以外的目录,便于同一份源码并行维护多套配置[9]。
局限
Kbuild建立在GNU Make之上,构建文件可以使用GNU Make的扩展特性,因而依赖GNU Make环境,其他make实现无法直接使用[1]。构建文件中处理目录与文件列表时大量依赖字符串拼接与递归展开,写法与一般项目的Makefile差别较大。
配置与构建分散在不同层级的文件中,出错时不易定位。文档明确指出,当Kbuild以m方式进入某目录而该目录的构建文件仍用obj-y列出目标时,这些目标会被孤立而不被链接,这通常意味着构建文件或Kconfig依赖存在缺陷[1]。
使用自定义规则时需要遵守若干隐含约束。以if_changed实现的规则必须把目标登记到targets变量中,否则命令行变化的检查会失效、目标每次都被重建;自定义命令还需分别给出quiet_cmd_<命令>与cmd_<命令>两个变量,才能在非啰嗦模式下正确显示缩写行[1]。
构建产物对工具链版本敏感。构建系统提供cc-option、as-option、ld-option等探测函数来判断当前编译器是否支持某个选项,这说明不同版本的编译器或链接器可能导致可用的编译选项不同[1]。模块加载还受符号校验约束,启用CONFIG_MODVERSIONS后,内核与模块中导出符号的校验值必须一致,否则内核拒绝加载该模块[8]。
参见
参考资料
- Linux Kernel Makefiles¶ [引用日期2026-10-14]
- llvm [引用日期2026-10-14]
- makefiles [引用日期2026-10-14]
- Kernel Build System¶ [引用日期2026-10-14]
- File: 00-INDEX [引用日期2026-10-14]
- softwareheritage.org 上的网页 [引用日期2026-10-14]
- Linux 6.x block layer and io_uring tree(s) - index : linux [引用日期2026-10-14]
- debian.org 上的网页 [引用日期2026-10-14]
- softwareheritage.org 上的网页 [引用日期2026-10-14]