3. 第一个内核模块

内核模块是 Linux 内核可动态加载和卸载的代码片段,它运行在内核空间,拥有最高特权级。编写第一个内核模块“Hello World”是理解模块加载/卸载机制、模块许可证声明以及 module_init / module_exit 宏的最佳起点。

3.1 模块代码结构

一个完整的内核模块至少包含以下四个部分:

  • 头文件包含:#include <linux/module.h> 和 #include <linux/kernel.h>,提供模块 API 和内核打印函数。
  • 入口函数:使用 module_init() 宏注册的初始化函数,模块加载时执行。
  • 出口函数:使用 module_exit() 宏注册的清理函数,模块卸载时执行。
  • 许可证声明:使用 MODULE_LICENSE() 宏声明模块使用的许可证,通常为 "GPL"。
关键点: 内核模块不能使用标准 C 库(如 glibc),只能使用内核导出的 API。打印输出使用 printk(),而非 printf()。

3.2 Hello World 模块完整代码

#include <linux/module.h>   // 所有模块都需要
#include <linux/kernel.h>   // 提供 printk() 和 KERN_INFO

static int __init hello_init(void)
{
    printk(KERN_INFO "Hello World from Kernel Module!\n");
    return 0;   // 返回 0 表示初始化成功
}

static void __exit hello_exit(void)
{
    printk(KERN_INFO "Goodbye, Kernel Module unloaded.\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple Hello World kernel module");

3.3 module_init 与 module_exit 宏

这两个宏是模块与内核框架之间的“注册契约”。它们将开发者定义的函数指针注册到内核的模块管理表中。

宏 作用 函数签名 返回值含义
module_init(x) 指定模块加载时调用的初始化函数 int __init func(void) 0 成功;负数表示失败,模块加载被中止
module_exit(x) 指定模块卸载时调用的清理函数 void __exit func(void) 无返回值
__init 和 __exit 标记: 这两个标记是内核的优化提示。__init 告诉内核该函数仅在初始化阶段使用,执行完毕后可以释放占用的内存;__exit 标记的函数在模块被编译进内核(非模块方式)时会被忽略,从而节省空间。

3.4 模块许可证声明

内核模块必须声明许可证,否则内核会认为该模块是“污染”的,加载时会打印警告信息,并且某些 GPL-only 的符号将无法使用。

宏 常用值 说明
MODULE_LICENSE() "GPL" 最常用,表示模块遵循 GNU General Public License v2
"GPL v2" 明确指定 GPL 版本 2
"Dual BSD/GPL" 双许可证,BSD 或 GPL 均可
"Proprietary" 专有许可证,内核会标记为“污染”
重要: 如果模块使用了 EXPORT_SYMBOL_GPL() 导出的内核符号,则必须声明 MODULE_LICENSE("GPL"),否则链接会失败。这是内核社区保护 GPL 代码不被专有模块使用的机制。

3.5 模块加载与卸载流程

下图展示了从用户空间执行 insmod 和 rmmod 命令到内核模块函数被调用的完整流程。

内核模块加载与卸载流程 insmod hello.ko rmmod hello init_module() delete_module() 内核模块管理器 查找 module_init / module_exit 注册表 hello_init() hello_exit()

3.6 编译与测试步骤

  1. 创建 Makefile:利用内核构建系统自动编译模块。
  2. 执行编译:运行 make,生成 hello.ko 文件。
  3. 加载模块:sudo insmod hello.ko,观察 dmesg 输出。
  4. 查看模块信息:lsmod | grep hello 或 modinfo hello.ko。
  5. 卸载模块:sudo rmmod hello,再次查看 dmesg。
Makefile 示例:
obj-m += hello.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD  := $(shell pwd)
all:
    $(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
    $(MAKE) -C $(KDIR) M=$(PWD) clean

3.7 常见错误与排查

错误现象 可能原因 解决方案
insmod: ERROR: could not insert module 内核版本不匹配或模块格式错误 确认 KDIR 指向当前运行内核的构建目录
modprobe: FATAL: Module not found 模块未安装到标准路径 使用 insmod 加绝对路径,或执行 depmod
printk 无输出 日志级别被控制台过滤 使用 dmesg 查看;或调整 /proc/sys/kernel/printk
内核报 tainted 警告 缺少 MODULE_LICENSE 或声明为专有 添加 MODULE_LICENSE("GPL")