Linux 外部内核模块:Kbuild、内核构建目录与符号版本

Linux 外部内核模块:Kbuild、内核构建目录与符号版本

类型:Linux 内核官方文档 Building External Modules 的中文解读。保留原文构建语义和版本条件;下面的 hello_lab 模块与 Python 编排是补充学习示例,不是原文完整翻译。没有在博客服务器加载模块或编译内核。

为什么不能把驱动当普通 C 程序编译

内核外部模块是放在内核源码树之外开发的模块,通常生成 .ko 文件。它仍依赖目标内核的配置、头文件、构建参数与导出符号。官方要求通过 Kbuild 构建,以取得内核所需的编译参数,并跟随构建基础设施的变化。[2]

因此,“机器上有 gcc 和 /usr/include/linux”不等于具备模块构建环境。用户空间头文件与目标内核的模块构建资料不能互相替代。

一个最小的验收链条是:确认目标内核 → 找到对应构建目录 → 编译模块 → 检查产物 → 在隔离环境加载验证。编译成功与能正确驱动硬件是不同的检查点。

构建目录必须对应目标内核

原文针对正在运行的内核使用 /lib/modules/<kernel-release>/build。对于发行版内核,需要安装与目标内核对应的开发或头文件包;对于自行构建的内核,需要可用的配置与相关构建产物,并且内核启用了模块支持。[2]

下面的 Python 检查只针对本机运行内核。交叉编译或给另一台设备构建时,不能拿本机 platform.release() 代替目标版本。

from pathlib import Path
import platform

release = platform.release()
build_dir = Path("/lib/modules") / release / "build"
print("运行内核:", release)
print("构建目录:", build_dir)
if not build_dir.exists():
    raise SystemExit("缺少对应内核的构建目录,请先准备匹配的开发包")
print("实际路径:", build_dir.resolve())
if not (build_dir / "Makefile").is_file():
    raise SystemExit("构建目录中没有 Makefile")

不要为了解决找不到目录的问题,把 build 链接到任意新版源码树。路径存在只能证明文件存在,不能证明 ABI、配置或架构相匹配。

用一个小模块观察构建过程

在独立目录保存 hello_lab.c。它只用于观察模块初始化和退出,不访问实际硬件。内核代码即使很短也应在可恢复的虚拟机或专用实验设备上测试。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

static int __init hello_lab_init(void)
{
    pr_info("hello_lab: module initialized\n");
    return 0;
}

static void __exit hello_lab_exit(void)
{
    pr_info("hello_lab: module exited\n");
}

module_init(hello_lab_init);
module_exit(hello_lab_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Minimal out-of-tree module for a learning lab");

同目录保存 Kbuild,内容只有一行:

obj-m := hello_lab.o

obj-m 告诉 Kbuild 生成可加载模块。Kbuild 将对应 C 文件编译为对象文件,再完成模块链接,得到 hello_lab.ko。文件名与目标名必须一致。[2]

下面用 Python 表达原文的 make -C <kernel-dir> M=<module-dir> 调用关系,不需要自己拼接一套 gcc 参数。

from pathlib import Path
import platform
import subprocess

module_dir = (Path.home() / "kernel-module-lab").resolve()
kernel_dir = Path("/lib/modules") / platform.release() / "build"
for filename in ("Kbuild", "hello_lab.c"):
    if not (module_dir / filename).is_file():
        raise FileNotFoundError(module_dir / filename)

subprocess.run(
    ["make", "-C", str(kernel_dir), f"M={module_dir}", "modules"],
    check=True,
)
artifact = module_dir / "hello_lab.ko"
if not artifact.is_file():
    raise RuntimeError("构建结束但没有预期模块产物")
subprocess.run(["modinfo", str(artifact)], check=True)

-C 指向内核构建目录;M 指向外部模块自身的绝对路径。不要交换这两个目录。modinfo 能帮助检查模块元信息,但不代表模块已加载或工作正常。

多个源文件怎样组成一个模块

原文的多文件例子使用 8123.ko,由 8123_if.c 与 8123_pci.c 组成。这里保留文件命名,避免在解释规则时改变原例的对应关系。[2]

obj-m := 8123.o
8123-y := 8123_if.o 8123_pci.o

第一行定义最终模块,第二行列出组成该模块的对象。8123-y 不是再生成一个独立模块。如果多个模块彼此使用导出符号,原文推荐通过顶层 Kbuild 一起构建;不方便统一时,再考虑 KBUILD_EXTRA_SYMBOLS。[2]

自定义头文件目录也应该交给 Kbuild:

ccflags-y := -I$(src)/include

$(src) 指向当前 Kbuild 文件所在目录。构建过程中工作目录可能位于内核树,所以不能假设相对 include/ 总能指向模块自己的目录。

modules_prepare 并不等于完整准备所有版本信息

官方明确指出:modules_prepare 可准备外部模块构建所需的一部分信息,但即使设置了 CONFIG_MODVERSIONS,它也不会生成 Module.symvers。要正确使用模块版本校验,需要相应完整内核构建产生的符号信息。[2]

Module.symvers 记录导出符号;启用 CONFIG_MODVERSIONS 时,还涉及符号原型的 CRC。加载时内核会比较相应信息,校验不一致可能拒绝模块。[2]

因此,不应把未知符号错误一律归因于“少了头文件”,也不要把另一个不匹配内核的 Module.symvers 随意复制进来。应分别检查符号是否导出、依赖模块是否参与构建以及目标构建资料是否匹配。

安装与加载是后续独立操作

modules_install 是安装构建产物,不是执行模块功能。当前所读取官方文档给出的外部模块默认目录是目标版本下的 updates/,并允许用 INSTALL_MOD_PATH 和 INSTALL_MOD_DIR 调整;发行版或厂商构建可能另有约定。[2]

学习初期可以只构建与查看元信息,不执行系统级安装。需要加载时,先确认有控制台、恢复手段和实验环境,记录内核日志;不要在承担网站或数据库的生产主机上测试未知模块。

本篇刻意没有提供自动加载脚本,以免将“构建练习”误操作为“向正在运行的业务内核注入代码”。

版本边界与嵌入式延伸

原文指出,Linux 6.13 起支持另一种 -f 调用方式及直接包含内核 Makefile 的形式;较旧目标不应照搬。本文示例选用传统 -C 方式,避免把新功能当成所有内核都支持。[2]

给开发板编译时还要明确目标架构、交叉工具链、厂商内核树、配置和内核输出目录。这里没有给出某块板子的通用参数:ARM64 架构相同并不意味着两套厂商内核可以互换模块。

排错顺序

  1. 找不到 build:核对目标 release 与对应开发包,不先改源码。
  2. 编译参数或内核接口错误:核对目标源码和驱动所支持的内核版本。
  3. MODPOST 未定义符号:核对符号导出、依赖模块与 Module.symvers。
  4. 产物加载被拒绝:读取完整内核错误,继续检查版本、架构与签名策略,不能只看 .ko 文件已生成。
  5. 能加载但硬件没工作:转入总线匹配、probe、资源与硬件协议排查。这已经超出 Kbuild 文档的主题。

原文解决的是“如何正确构建外部模块”,不是“如何保证任意驱动支持任意硬件”。保留这个边界,才能避免把构建成功当作驱动完成。

来源

  1. Linux Kernel Documentation:Building External Modules

本文为中文技术解读,未替代 Linux 内核官方文档。


Linux 外部内核模块:Kbuild、内核构建目录与符号版本
https://blog.529808.xyz//archives/linux-external-kernel-modules-kbuild
作者
念头通达
发布于
2026年10月01日
更新于
2026年10月01日
许可协议