logo
0
0
WeChat Login

一、 模块加载与卸载

1. 加载模块

使用 insmodmodprobe 命令加载内核模块。

1.1 insmod 命令

insmod 是最基础的模块加载命令,直接加载指定的 .ko 文件:

insmod /path/to/module.ko

常用参数:

  • 无参数:直接加载模块
  • 传递参数:insmod module.ko param1=value1 param2=value2

1.2 modprobe 命令

modprobeinsmod 更智能,会自动处理模块依赖关系:

modprobe module_name

常用选项:

  • -r--remove:卸载模块
  • -f--force:强制加载
  • -v--verbose:显示详细信息
  • --show-depends:显示模块依赖

1.3 加载示例

# 使用 insmod 加载模块 sudo insmod hello_world.ko # 使用 modprobe 加载模块(需要模块已安装到 /lib/modules/$(uname -r)/) sudo modprobe hello_world # 加载模块并传递参数 sudo insmod hello_world.ko count=5 name="test"

2. 卸载模块

使用 rmmodmodprobe -r 命令卸载内核模块。

2.1 rmmod 命令

rmmod 用于从内核中移除指定模块:

rmmod module_name

常用选项:

  • -f--force:强制卸载(可能不安全)
  • -w--wait:等待模块使用计数为 0 后再卸载
  • -v--verbose:显示详细信息

2.2 modprobe -r 命令

modprobe -r 会自动处理依赖关系,同时卸载依赖模块:

modprobe -r module_name

2.3 卸载示例

# 使用 rmmod 卸载模块 sudo rmmod hello_world # 使用 modprobe 卸载模块 sudo modprobe -r hello_world # 强制卸载(谨慎使用) sudo rmmod -f hello_world

3. 查看模块信息

使用 lsmodmodinfo 等命令查看模块状态和详细信息。

3.1 lsmod 命令

lsmod 显示当前已加载的所有内核模块:

lsmod

输出格式说明:

  • Module:模块名称
  • Size:模块大小(字节)
  • Used by:使用计数和依赖模块

3.2 modinfo 命令

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 字符分隔字段(便于脚本处理)

3.3 查看示例

# 查看所有已加载模块 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/*

3.4 dmesg 查看模块日志

模块加载/卸载时的 printk 输出可以通过 dmesg 查看:

# 查看最近的内核日志 dmesg | tail # 实时查看内核日志 dmesg -w # 过滤模块相关日志 dmesg | grep -i hello

4. modprobe 使用前提

modprobe 命令需要模块位于特定目录,并且要有对应的依赖文件才能正常工作。

4.1 模块安装目录

modprobe 默认从以下目录查找模块:

/lib/modules/$(uname -r)/

目录结构说明:

  • kernel/:内核自带的模块目录
  • extra/:额外安装的模块目录
  • modules.dep:模块依赖关系文件
  • modules.dep.bin:模块依赖关系二进制文件
  • modules.alias:模块别名文件
  • modules.symbols:模块符号文件

4.2 安装模块到系统目录

将编译好的 .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

4.3 更新模块依赖文件

安装新模块后,必须更新依赖文件:

# 生成模块依赖关系 sudo depmod -a # 仅更新指定内核版本的依赖 sudo depmod -a $(uname -r)

depmod 命令会扫描 /lib/modules/$(uname -r)/ 目录下的所有模块,生成以下文件:

  • modules.dep:模块依赖关系文本文件
  • modules.dep.bin:模块依赖关系二进制文件(modprobe 实际使用)
  • modules.alias:模块别名映射
  • modules.symbols:模块符号信息

4.4 自定义模块目录

除了使用默认的 /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 后缀)。

4.5 modprobe 与 insmod 的区别

特性insmodmodprobe
模块路径需要指定完整路径自动从 /lib/modules/ 查找
依赖处理不处理依赖自动加载依赖模块
配置文件不支持支持 /etc/modprobe.d/ 配置
使用前提直接加载 .ko 文件需要模块已安装并运行 depmod

使用场景建议:

  • 开发调试阶段:使用 insmod 直接加载本地 .ko 文件,方便快速测试
  • 生产环境部署:使用 modprobe,便于管理依赖和配置

二、 Hello World

本节介绍一个最简单的内核模块示例,包含源码文件和 Makefile。

1. 模块源码

创建 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()

2. Makefile 文件

创建 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:交叉编译工具链前缀

3. 编译与测试

编译模块:

# 进入示例目录 cd 01_helloworld/001_template # 编译模块 make # 清理编译产物 make clean

测试模块:

# 加载模块 sudo insmod hello_world.ko # 查看模块输出 dmesg | tail # 查看模块信息 modinfo hello_world.ko # 卸载模块 sudo rmmod hello_world

4. 内核污染说明

4.1 什么是内核污染

内核污染(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

4.2 污染标志含义

内核的污染标志是分类型的,通过不同比特位记录不同 "不纯洁" 的来源。/proc/sys/kernel/tainted 返回一个数值,每一位代表不同的污染原因。

关键区别:O 和 P 是两个独立的标志

与模块开发直接相关的有两个重要标志:

标志宏定义含义触发条件
OTAINT_OOT_MODULE664Out-of-tree 模块模块不在当前运行内核的官方源码树中编译
PTAINT_PROPRIETARY_MODULE01Proprietary 模块模块声明的许可证不被内核认为是 GPL 兼容

O 与 P 的关键区别

  • O 标志(Out-of-tree):只要模块不在内核官方源码树中编译,加载时就会设置这一位。这是 loading out-of-tree module taints kernel. 的直接原因。它表明 "代码不在主线树中",但并没有指控它 "闭源/专有"。
  • P 标志(Proprietary):当模块声明的许可证不被内核认为是 GPL 兼容时(例如 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

完整污染标志列表

标志含义
P01加载了非 GPL 许可的模块(Proprietary)
F12模块加载时未指定许可证
S24强制加载模块
R38内核 SMP 配置不匹配
M416模块被强制卸载
B532模块加载警告
O664加载了来自外部的模块(Out-of-tree)
I7128加载了测试模块
E8256发生了硬件问题
A9512发生了 ACPI 表覆盖
W101024内核警告
C112048发生了 OOPS
D124096使用了危险的模块
N138192发生了硬件错误
U1416384使用了过时的模块

解读污染值

# 查看当前污染值 cat /proc/sys/kernel/tainted # 例如返回 64,表示仅加载了 out-of-tree 模块 # 例如返回 65,表示 out-of-tree + proprietary 模块 # 例如返回 4160,表示 4096 + 64,即危险模块 + out-of-tree

4.3 避免内核污染

开发内核模块时,应遵循以下原则避免污染内核:

正确声明许可证

/* 必须声明 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");

4.4 污染的影响

内核污染后的影响:

  • 内核开发者可能拒绝处理问题报告
  • 某些内核调试功能可能受限
  • 系统稳定性可能降低
  • 无法获得官方内核社区支持

开发建议

  • 开发阶段:使用 GPL 许可证声明,避免污染
  • 测试阶段:如果出现污染,检查 MODULE_LICENSE() 声明
  • 生产环境:确保所有模块使用 GPL 或兼容许可证

本文档由 markdowncli 技能辅助生成

About

rk3568 驱动模块

760.00 KiB
0 forks0 stars1 branches0 TagREADMEMIT license
Language
Makefile45%
C39.6%
Shell15.3%
Others0.1%