Skip to main content

构建自己的 Linux 内核模块:从编译到排障

Created: April 24, 2026 Larry Qu 1 min read

为什么要学习内核模块

内核模块(.ko)让我们在不重编整个内核的情况下扩展内核能力。常见场景:

  1. 设备驱动。
  2. 文件系统扩展。
  3. 网络过滤与观测。
  4. 安全审计或低层性能采集。

对于运维和系统工程师,理解模块编译与加载流程能显著提升定位故障的效率。

最小可运行示例

目录结构:

simplemodule/
 Makefile
 simplemodule.c

Makefile:

obj-m := simplemodule.o

all:
 make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
 make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

simplemodule.c:

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

MODULE_LICENSE("GPL");
MODULE_AUTHOR("CalmOps");
MODULE_DESCRIPTION("A minimal Linux kernel module");

static int __init simple_init(void)
{
  pr_info("simplemodule: loaded\n");
  return 0;
}

static void __exit simple_exit(void)
{
  pr_info("simplemodule: unloaded\n");
}

module_init(simple_init);
module_exit(simple_exit);

建议使用 pr_info/pr_err 而不是裸 printk,语义更清晰。

编译步骤

make

编译后常见文件:

  1. simplemodule.ko:最终模块。
  2. Module.symvers:符号版本信息。
  3. modules.order:模块顺序。
  4. 若干 .cmd 中间文件。

加载与卸载

sudo insmod simplemodule.ko
lsmod | grep simplemodule

sudo rmmod simplemodule
lsmod | grep simplemodule

查看内核日志:

dmesg | tail -n 30

在 systemd 环境也可用:

journalctl -k -n 50

insmod vs modprobe

  1. insmod 只加载指定 .ko,不自动处理依赖。
  2. modprobe 会根据模块索引自动解析依赖,适合生产。

若模块已安装到标准路径,推荐:

sudo modprobe simplemodule
sudo modprobe -r simplemodule

典型报错与排障

1) invalid module format

原因通常是:

  1. 模块编译时内核版本与当前运行内核不一致。
  2. 架构不一致(x86_64/arm64)。

检查:

uname -r
modinfo simplemodule.ko
file simplemodule.ko

2) unknown symbol

说明模块引用了当前内核不可见符号。

处理:

  1. 确认依赖模块是否先加载。
  2. 检查内核配置是否启用对应功能。
  3. 检查导出符号策略(EXPORT_SYMBOL)。

3) 签名相关错误(Secure Boot)

在开启 Secure Boot 的系统上,未签名模块可能无法加载。

解决方向:

  1. 使用发行版签名流程。
  2. 企业环境统一签名和证书分发。
  3. 实验环境临时关闭 Secure Boot(谨慎)。

开发环境准备建议

  1. 安装匹配当前内核的 headers/build 包。
  2. 使用测试机或虚拟机,不要直接在生产内核实验。
  3. 保留串口/控制台访问手段,避免内核崩溃后失联。

生产实践:用 DKMS 管理模块

对于长期维护的第三方驱动,推荐使用 DKMS:

  1. 内核升级时自动重编模块。
  2. 降低版本漂移导致的人工维护成本。

适用场景:网卡、存储、虚拟化驱动等。

安全与稳定性提醒

内核模块运行在内核态,任何越界、空指针、并发缺陷都可能导致系统崩溃。建议:

  1. 优先使用内核提供的安全 API。
  2. 严格做错误处理与资源回收。
  3. 使用静态分析和代码审查。
  4. 先小流量灰度,再全量上线。

小结

内核模块编译并不复杂,难点在于兼容性、排障和长期维护。掌握 makeinsmod/modprobedmesg/journalctl 与版本匹配逻辑,就能解决大多数一线问题。

如果要进入生产环境,建议尽早引入自动化构建、签名与 DKMS 流程。

参考资料

Comments

Share this article

Scan to read on mobile