使用 insmod 或 modprobe 命令加载内核模块。
insmod 是最基础的模块加载命令,直接加载指定的 .ko 文件:
insmod /path/to/module.ko
常用参数:
insmod module.ko param1=value1 param2=value2modprobe 比 insmod 更智能,会自动处理模块依赖关系:
modprobe module_name
常用选项:
-r 或 --remove:卸载模块-f 或 --force:强制加载-v 或 --verbose:显示详细信息--show-depends:显示模块依赖# 使用 insmod 加载模块
sudo insmod hello_world.ko
# 使用 modprobe 加载模块(需要模块已安装到 /lib/modules/$(uname -r)/)
sudo modprobe hello_world
# 加载模块并传递参数
sudo insmod hello_world.ko count=5 name="test"
使用 rmmod 或 modprobe -r 命令卸载内核模块。
rmmod 用于从内核中移除指定模块:
rmmod module_name
常用选项:
-f 或 --force:强制卸载(可能不安全)-w 或 --wait:等待模块使用计数为 0 后再卸载-v 或 --verbose:显示详细信息modprobe -r 会自动处理依赖关系,同时卸载依赖模块:
modprobe -r module_name
# 使用 rmmod 卸载模块
sudo rmmod hello_world
# 使用 modprobe 卸载模块
sudo modprobe -r hello_world
# 强制卸载(谨慎使用)
sudo rmmod -f hello_world
使用 lsmod、modinfo 等命令查看模块状态和详细信息。
lsmod 显示当前已加载的所有内核模块:
lsmod
输出格式说明:
Module:模块名称Size:模块大小(字节)Used by:使用计数和依赖模块modinfo 显示模块的详细信息:
# 使用模块名称(模块需已安装到 /lib/modules/$(uname -r)/ 目录)
modinfo module_name
# 或直接指定 .ko 文件路径(推荐用于本地开发的模块)
modinfo /path/to/module.ko
注意:
modinfo从/lib/modules/$(uname -r)/目录查找.ko文件,与模块是否已加载无关。
- 对于系统已安装的模块(位于
/lib/modules/$(uname -r)/),可直接使用模块名- 对于本地开发的模块(未安装到系统目录),必须指定
.ko文件路径# 本地开发的模块,必须使用 .ko 文件路径(即使模块已加载) modinfo ./hello_world.ko # 使用模块名查找(仅对已安装到系统目录的模块有效) modinfo hello_world # 模块未安装时报错:Module hello_world not found
常用选项:
-F field:只显示指定字段(如 author、description、license 等)-n 或 --filename:显示模块文件路径-0:使用 NUL 字符分隔字段(便于脚本处理)# 查看所有已加载模块
lsmod
# 过滤查看特定模块
lsmod | grep hello
# 查看模块详细信息
modinfo hello_world
# 只显示模块作者
modinfo -F author hello_world
# 只显示模块描述
modinfo -F description hello_world
# 查看 /sys/module 目录
ls /sys/module/
cat /sys/module/hello_world/parameters/*
模块加载/卸载时的 printk 输出可以通过 dmesg 查看:
# 查看最近的内核日志
dmesg | tail
# 实时查看内核日志
dmesg -w
# 过滤模块相关日志
dmesg | grep -i hello
modprobe 命令需要模块位于特定目录,并且要有对应的依赖文件才能正常工作。
modprobe 默认从以下目录查找模块:
/lib/modules/$(uname -r)/
目录结构说明:
kernel/:内核自带的模块目录extra/:额外安装的模块目录modules.dep:模块依赖关系文件modules.dep.bin:模块依赖关系二进制文件modules.alias:模块别名文件modules.symbols:模块符号文件将编译好的 .ko 文件安装到系统模块目录:
# 方法一:使用 make install(需要在 Makefile 中配置)
sudo make install
# 方法二:手动复制
sudo cp hello_world.ko /lib/modules/$(uname -r)/extra/
# 方法三:指定安装路径
sudo make INSTALL_MOD_PATH=/path/to/rootfs modules_install
安装新模块后,必须更新依赖文件:
# 生成模块依赖关系
sudo depmod -a
# 仅更新指定内核版本的依赖
sudo depmod -a $(uname -r)
depmod 命令会扫描 /lib/modules/$(uname -r)/ 目录下的所有模块,生成以下文件:
modules.dep:模块依赖关系文本文件modules.dep.bin:模块依赖关系二进制文件(modprobe 实际使用)modules.alias:模块别名映射modules.symbols:模块符号信息除了使用默认的 /lib/modules/$(uname -r)/ 目录外,还可以自定义模块搜索路径:
【方法一:创建自定义子目录】
# 在标准模块目录下创建自定义子目录
sudo mkdir -p /lib/modules/$(uname -r)/mydrivers/
# 复制模块到自定义目录
sudo cp hello_world.ko /lib/modules/$(uname -r)/mydrivers/
# 更新模块依赖(会自动扫描所有子目录)
sudo depmod -a
# 查看依赖是否生成成功
cat /lib/modules/$(uname -r)/modules.dep | grep "module_name"
# 现在可以使用 modprobe 加载
sudo modprobe hello_world
注意:自定义目录后需要运行
depmod -a更新依赖数据库,否则modprobe无法找到模块。modprobe使用的是 模块名称,而不是文件名。不要添加.ko后缀:# 正确 ✓ sudo modprobe hello_world # 错误 ✗ - 会报错 "Module hello_world.ko not found" sudo modprobe hello_world.ko
modules.dep中记录的是文件路径(如mydrivers/hello_world.ko:),但modprobe的参数是模块名称(不含.ko后缀)。
| 特性 | insmod | modprobe |
|---|---|---|
| 模块路径 | 需要指定完整路径 | 自动从 /lib/modules/ 查找 |
| 依赖处理 | 不处理依赖 | 自动加载依赖模块 |
| 配置文件 | 不支持 | 支持 /etc/modprobe.d/ 配置 |
| 使用前提 | 直接加载 .ko 文件 | 需要模块已安装并运行 depmod |
使用场景建议:
insmod 直接加载本地 .ko 文件,方便快速测试modprobe,便于管理依赖和配置本节介绍一个最简单的内核模块示例,包含源码文件和 Makefile。
创建 hello_world.c 文件:
#include <linux/init.h> /* module_init module_exit */
#include <linux/kernel.h>
#include <linux/module.h> /* MODULE_LICENSE */
/** @fn static int __init hello_world_demo_init(void)
* @brief 模块入口函数,模块加载时调用
* @return 返回 0 表示成功,负值表示失败
*/
static int __init hello_world_demo_init(void) {
printk("hello_world_demo module is running!\n");
return 0;
}
/** @fn static void __exit hello_world_demo_exit(void)
* @brief 模块出口函数,模块卸载时调用
* @return 无返回值
*/
static void __exit hello_world_demo_exit(void) {
printk("hello_world_demo will exit\n");
}
/* 将 __init 定义的函数指定为驱动的入口函数 */
module_init(hello_world_demo_init);
/* 将 __exit 定义的函数指定为驱动的出口函数 */
module_exit(hello_world_demo_exit);
/* 模块信息(通过 modinfo hello_world_demo 查看) */
MODULE_LICENSE("GPL"); /* 源码的许可证协议 */
MODULE_AUTHOR("sumu"); /* 字符串常量内容为模块作者说明 */
MODULE_DESCRIPTION("Description"); /* 字符串常量内容为模块功能说明 */
MODULE_ALIAS("module's other name"); /* 字符串常量内容为模块别名 */
关键点说明:
module_init():指定模块加载时执行的初始化函数module_exit():指定模块卸载时执行的清理函数__init 宏:标记初始化函数,加载后内存可被释放__exit 宏:标记退出函数,仅在卸载时调用MODULE_LICENSE():必须声明许可证,否则会警告printk():内核打印函数,类似用户空间的 printf()创建 Makefile 文件:
target := hello_world
# 模块名称
obj-m += $(target).o
# 内核源码路径
KERN_DIR := ../../../kernel-6.1
# 交叉编译工具链路径
CROSS_COMPILE := ../prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-
# 架构
ARCH := arm64
# 编译目标
all:
$(MAKE) -C $(KERN_DIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules
# 清理目标
clean:
$(MAKE) -C $(KERN_DIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) clean
Makefile 关键点说明:
obj-m:指定要编译为内核模块的目标文件KERN_DIR:内核源码目录路径M=$(PWD):告诉构建系统模块源码位置ARCH:目标平台架构(如 arm64、x86)CROSS_COMPILE:交叉编译工具链前缀编译模块:
# 进入示例目录
cd 01_helloworld/001_template
# 编译模块
make
# 清理编译产物
make clean
测试模块:
# 加载模块
sudo insmod hello_world.ko
# 查看模块输出
dmesg | tail
# 查看模块信息
modinfo hello_world.ko
# 卸载模块
sudo rmmod hello_world
内核污染(Kernel Tainting)是指加载非 GPL 许可的模块或某些特殊模块时,内核会标记自身处于 "被污染" 状态。这表示内核运行了非官方支持的代码,可能导致系统行为不可预测。
【典型污染打印信息】
加载 out-of-tree(非内核源码树内)模块时,dmesg 会显示:
hello_world: loading out-of-tree module taints kernel.
这表示模块不在内核源码树中编译,属于外部模块。即使模块声明了 GPL 许可证,只要是 out-of-tree 模块,内核仍会被污染。这是内核的一种机制,用于标记“有外部代码在运行”。
MODULE_LICENSE 使用 MIT 的时候,加载驱动会有下面的打印:
hello_world: module license 'MIT' taints kernel.
Disabling lock debugging due to kernel taint
查看内核污染状态:
# 查看污染标志
cat /proc/sys/kernel/tainted
# 查看污染原因详情
dmesg | grep -i tainted
内核的污染标志是分类型的,通过不同比特位记录不同 "不纯洁" 的来源。/proc/sys/kernel/tainted 返回一个数值,每一位代表不同的污染原因。
【关键区别:O 和 P 是两个独立的标志】
与模块开发直接相关的有两个重要标志:
| 标志 | 宏定义 | 位 | 值 | 含义 | 触发条件 |
|---|---|---|---|---|---|
| O | TAINT_OOT_MODULE | 6 | 64 | Out-of-tree 模块 | 模块不在当前运行内核的官方源码树中编译 |
| P | TAINT_PROPRIETARY_MODULE | 0 | 1 | Proprietary 模块 | 模块声明的许可证不被内核认为是 GPL 兼容 |
【O 与 P 的关键区别】
loading out-of-tree module taints kernel. 的直接原因。它表明 "代码不在主线树中",但并没有指控它 "闭源/专有"。MODULE_LICENSE("Proprietary")),才会设置这一位。【示例说明】
# GPL 许可证的 out-of-tree 模块
MODULE_LICENSE("GPL");
# 结果:只设置 O 标志(值为 64),不设置 P 标志
# 污染值:64
# Proprietary 许可证的模块
MODULE_LICENSE("Proprietary");
# 结果:同时设置 O 和 P 标志
# 污染值:65 (64 + 1)
# In-tree 的 GPL 模块
# 结果:不设置任何标志
# 污染值:0
【完整污染标志列表】
| 标志 | 位 | 值 | 含义 |
|---|---|---|---|
| P | 0 | 1 | 加载了非 GPL 许可的模块(Proprietary) |
| F | 1 | 2 | 模块加载时未指定许可证 |
| S | 2 | 4 | 强制加载模块 |
| R | 3 | 8 | 内核 SMP 配置不匹配 |
| M | 4 | 16 | 模块被强制卸载 |
| B | 5 | 32 | 模块加载警告 |
| O | 6 | 64 | 加载了来自外部的模块(Out-of-tree) |
| I | 7 | 128 | 加载了测试模块 |
| E | 8 | 256 | 发生了硬件问题 |
| A | 9 | 512 | 发生了 ACPI 表覆盖 |
| W | 10 | 1024 | 内核警告 |
| C | 11 | 2048 | 发生了 OOPS |
| D | 12 | 4096 | 使用了危险的模块 |
| N | 13 | 8192 | 发生了硬件错误 |
| U | 14 | 16384 | 使用了过时的模块 |
【解读污染值】
# 查看当前污染值
cat /proc/sys/kernel/tainted
# 例如返回 64,表示仅加载了 out-of-tree 模块
# 例如返回 65,表示 out-of-tree + proprietary 模块
# 例如返回 4160,表示 4096 + 64,即危险模块 + out-of-tree
开发内核模块时,应遵循以下原则避免污染内核:
【正确声明许可证】
/* 必须声明 GPL 或兼容许可证 */
MODULE_LICENSE("GPL"); /* 推荐:纯 GPL 许可 */
MODULE_LICENSE("GPL v2"); /* GPL 第二版 */
MODULE_LICENSE("GPL and additional rights"); /* GPL 及附加权利 */
MODULE_LICENSE("Dual BSD/GPL"); /* BSD/GPL 双许可 */
MODULE_LICENSE("Dual MIT/GPL"); /* MIT/GPL 双许可 */
MODULE_LICENSE("Dual MPL/GPL"); /* MPL/GPL 双许可 */
以下许可证会导致内核污染:
/* 以下许可证会导致污染(不推荐) */
MODULE_LICENSE("Proprietary"); /* 专有许可证,会污染内核 */
MODULE_LICENSE("BSD"); /* 单独 BSD 许可,会污染内核 */
MODULE_LICENSE("MIT"); /* 单独 MIT 许可,会污染内核 */
MODULE_LICENSE("MPL"); /* 单独 MPL 许可,会污染内核 */
【避免强制加载/卸载】
# 避免使用强制加载
sudo insmod -f module.ko # 不推荐
# 避免使用强制卸载
sudo rmmod -f module # 不推荐
【Out-of-tree】
最好的办法就是架构驱动编译进内核。或者也可以欺骗内核,内核判断 intree 的依据是模块 ELF 中是否包含 intree = Y 这个 modinfo 字段。所以可以直接添加:
MODULE_INFO(intree, "Y");
内核污染后的影响:
【开发建议】
MODULE_LICENSE() 声明本文档由 markdowncli 技能辅助生成