
你是否好奇过,为什么 Linux 系统可以在不重启的情况下支持新硬件?为什么修改一个驱动程序不需要重新编译整个内核?这一切都离不开 Linux 的 "模块化魔法"—— 内核模块(Kernel Module)。作为 Linux 内核最灵活的特性之一,内核模块让开发者可以动态扩展内核功能,今天就来揭开这个神秘组件的面纱。
如果把 Linux 内核比作一台电脑主机,那么内核模块就是可以随时插拔的外设:
内核模块是可以在内核运行时动态加载 / 卸载的独立代码单元,具备以下特点:
特性 | 内核模块 | 普通用户程序 |
|---|---|---|
运行空间 | 内核态(Ring 0) | 用户态(Ring 3) |
内存管理 | 直接操作物理内存 | 通过虚拟内存机制 |
权限 | 完全访问硬件资源 | 受限访问(需系统调用) |
加载方式 | insmod/rmmod 动态加载 | execve 执行二进制文件 |
依赖关系 | 需要内核符号表支持 | 依赖用户空间库文件 |
(1)加载过程(insmod 命令背后的故事)

(2)卸载过程(rmmod 命令做了什么)
内核模块可以通过EXPORT_SYMBOL宏导出函数 / 变量,供其他模块使用:
// 导出一个全局函数供其他模块调用
int my_kernel_function(int param);
EXPORT_SYMBOL(my_kernel_function);
// 其他模块使用时需声明外部符号
extern int my_kernel_function(int param);注意:内核内置的符号(如printk)会自动导出,无需额外声明。
#include <linux/init.h>
#include <linux/module.h>
// 模块初始化函数(加载时执行)
static int __init hello_init(void) {
printk(KERN_INFO "Hello, Kernel World!\n"); // 内核日志输出
return 0; // 0表示初始化成功,非0表示错误码
}
// 模块退出函数(卸载时执行)
static void __exit hello_exit(void) {
printk(KERN_INFO "Goodbye, Kernel World!\n");
}
// 声明模块入口/出口函数
module_init(hello_init);
module_exit(hello_exit);
// 模块许可证(必须声明,否则编译会报警告)
MODULE_LICENSE("GPL");
// 可选:模块作者、描述等信息
MODULE_AUTHOR("byte轻骑兵");
MODULE_DESCRIPTION("A simple hello world kernel module");obj-m += hello_module.o # 目标模块名(生成hello_module.ko)
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean# 1. 编译模块
make all
# 2. 加载模块(需要root权限)
sudo insmod hello_module.ko
# 3. 查看内核日志(验证初始化函数执行)
dmesg | tail
# 4. 卸载模块
sudo rmmod hello_module.ko
# 5. 再次查看日志(验证退出函数执行)
dmesg | tail#include <linux/ai_module.h> // 2025新增头文件
struct ai_engine my_ai = {
.model = "TensorRT-8.6",
.max_batch = 32,
};
static int __init ai_module_init(void) {
register_ai_engine(&my_ai);
printk(KERN_INFO "AI Module Loaded: %s\n", my_ai.model);
return 0;
}
module_ai_init(ai_module_init); // 2025新宏
MODULE_AI_COMPATIBLE("NVIDIA AI-MOD v1.2");通过module_param宏可以在加载模块时传递参数:
#include <linux/moduleparam.h>
static int debug_level = 1; // 默认调试级别
static char *device_name = "my_device"; // 默认设备名
// 声明参数(类型、权限、描述)
module_param(debug_level, int, S_IRUGO); // 可读权限
module_param(device_name, charp, S_IRUSR); // 字符串类型,用户可读
MODULE_PARM_DESC(debug_level, "Debug level (0-3)");
MODULE_PARM_DESC(device_name, "Name of the target device");加载时使用:
sudo insmod hello_module.ko debug_level=2 device_name=usb_devicestatic int __init complex_init(void) {
int ret;
// 分配内存
buffer = kmalloc(BUFFER_SIZE, GFP_KERNEL);
if (!buffer) {
printk(KERN_ERR "Memory allocation failed\n");
return -ENOMEM; // 返回标准错误码
}
// 注册设备驱动
ret = register_chrdev(MAJOR_NUMBER, DEVICE_NAME, &fops);
if (ret < 0) {
kfree(buffer); // 释放已分配的内存
return ret;
}
return 0;
}最佳实践:遵循 "资源获取即初始化" 原则,反向释放已申请的资源
适合场景 | 不适合场景 |
|---|---|
设备驱动开发 | 内核核心功能修改(如调度器) |
实验性功能测试 | 性能敏感的核心路径 |
硬件厂商特定功能实现 | 需严格实时性的场景 |
旧内核功能扩展 | 安全要求极高的环境 |
①动态内核组件加载(DKLM)
// 动态替换调度器示例
struct dklm_hooks scheduler_hooks = {
.replace = cfs_scheduler_replace,
.rollback = cfs_scheduler_rollback,
};
MODULE_DKLM(scheduler_hooks);②安全增强特性
原因:有其他模块依赖当前模块,或模块资源未正确释放
解决步骤:
内核模块是 Linux 内核最灵活的特性之一,它让系统具备了 "动态进化" 的能力:
用户空间工具
命令 | 作用 | 示例 | |
|---|---|---|---|
insmod | 加载模块 | insmod hello.ko | |
rmmod | 卸载模块 | rmmod hello | |
modprobe | 智能加载(处理依赖) | modprobe nvidia | |
lsmod | 列出已加载模块 | `lsmod grep nvme' | |
modinfo | 查看模块信息 | modinfo virtio_net |