4. 模块加载与卸载:insmod、rmmod、modprobe 命令详解与模块依赖管理

内核模块编译完成后(.ko 文件),需要动态加载到运行中的内核才能生效。Linux 提供了 insmod、rmmod、modprobe 三组核心命令,分别对应模块的加载、卸载与依赖解析。本章深入讲解每条命令的工作原理、使用场景以及模块依赖管理机制。

4.1 模块加载命令:insmod 与 modprobe

insmod 是最直接的模块加载命令,它通过 init_module() 系统调用将模块二进制内容插入内核空间。但 insmod 不处理依赖关系,如果模块引用了其他模块导出的符号,必须先手动加载依赖模块。

核心区别: insmod 加载指定路径的 .ko 文件,不解析依赖;modprobe 根据模块名查找并自动加载所有依赖模块。

4.1.1 insmod 命令用法

# 加载当前目录下的模块
sudo insmod ./hello.ko

# 加载带参数的模块
sudo insmod ./gpio_led.ko gpio_pin=17 blink_rate=500

# 查看加载是否成功(无输出表示成功)
lsmod | grep hello
参数/选项 说明
./module.ko 模块文件路径(必须指定完整或相对路径)
param=value 向模块传递参数(需模块内用 module_param 声明)
-f (force) 强制加载,即使内核版本不匹配或模块签名验证失败
⚠ 生产环境禁止使用 -f 强制加载: 强制加载会绕过版本校验和签名验证,可能导致内核崩溃(kernel panic)或数据损坏。仅用于开发调试。

4.1.2 modprobe 命令与依赖解析

modprobe 是更智能的模块加载工具,它依赖 /lib/modules/$(uname -r)/ 目录下的模块树和 modules.dep 依赖文件。加载模块时自动递归解析所有依赖。

# 按模块名加载(自动查找 .ko 文件并处理依赖)
sudo modprobe usb_storage

# 查看 modprobe 实际加载了哪些模块
sudo modprobe -v usb_storage

# 显示模块依赖关系(不实际加载)
sudo modprobe --show-depends usb_storage
💡 最佳实践: 开发阶段使用 insmod 加载自己编译的模块;生产环境或加载系统自带模块时使用 modprobe。

4.2 模块卸载命令:rmmod 与 modprobe -r

卸载模块时,内核会检查模块的引用计数(refcnt)。只有当引用计数为 0 时,模块才能被安全卸载。rmmod 和 modprobe -r 都调用 delete_module() 系统调用,但行为略有不同。

4.2.1 rmmod 命令

# 卸载指定模块
sudo rmmod hello

# 强制卸载(即使引用计数不为 0)
sudo rmmod -f hello

# 卸载并打印详细信息
sudo rmmod -v hello
场景 命令 说明
正常卸载 sudo rmmod <module> 引用计数必须为 0
强制卸载 sudo rmmod -f <module> 危险操作,可能造成内核不稳定
卸载并清理依赖 sudo modprobe -r <module> 递归卸载不再被使用的依赖模块

4.2.2 卸载失败常见原因

  • 模块正被使用: 其他模块或进程持有该模块的引用,lsmod 可查看引用计数。
  • 模块是内置模块: 编译进内核(y)的模块无法卸载。
  • 模块有设备文件打开: 用户空间程序打开了模块注册的设备节点。
调试技巧: 使用 lsmod | grep <module> 查看模块当前引用计数;使用 cat /proc/modules 获取更详细的模块状态信息。

4.3 模块依赖管理

模块依赖关系由符号导出(EXPORT_SYMBOL)和符号引用决定。内核在模块加载时通过 符号表解析 建立依赖图。

4.3.1 依赖关系生成流程

  1. 编译阶段: 每个 .ko 文件包含 .modinfo 段,记录依赖的其他模块名。
  2. 安装阶段: depmod 工具扫描 /lib/modules/$(uname -r)/ 下所有模块,生成 modules.dep 文件。
  3. 加载阶段: modprobe 读取 modules.dep,按拓扑顺序加载依赖模块。
# 手动更新模块依赖(安装新模块后必须执行)
sudo depmod -a

# 查看模块依赖关系
cat /lib/modules/$(uname -r)/modules.dep | grep usb_storage

4.3.2 依赖关系可视化

内核模块依赖关系示意图 模块依赖关系与加载顺序 依赖模块 A EXPORT_SYMBOL(func_a) 依赖模块 B EXPORT_SYMBOL(func_b) 主模块 (main_driver) 引用 func_a() 和 func_b() 符号解析 符号解析 加载顺序要求 1. 先加载依赖模块 A 2. 再加载依赖模块 B 3. 最后加载主模块 卸载顺序相反:先卸载主模块

4.4 模块信息查看与调试

加载/卸载前后,使用以下命令验证模块状态:

命令 查看内容 示例输出
lsmod 当前加载的所有模块、大小、引用计数、被谁使用 hello 16384 0 - Live 0xffffffffc0000000
modinfo <module> 模块作者、描述、许可证、依赖、参数、别名 depends: i2c-core, gpiolib
cat /proc/modules 与 lsmod 相同,但格式更原始 hello 16384 0 - Live 0xffffffffc0000000
dmesg | tail 内核日志,查看模块加载/卸载时的 printk 输出 [12345.678] Hello, world from kernel module!

4.5 模块加载/卸载完整流程总结

模块加载与卸载完整流程图 模块加载与卸载完整流程 加载流程 (insmod/modprobe) 用户态命令 init_module() 系统调用 符号解析 module_init() 执行 卸载流程 (rmmod/modprobe -r) 用户态命令 delete_module() 系统调用 检查引用计数 module_exit() 执行 关键注意事项 🔹 加载顺序:依赖模块 → 主模块(modprobe 自动处理) 🔹 卸载顺序:主模块 → 依赖模块(引用计数为 0 才能卸载) 🔹 版本匹配:模块必须与当前内核版本一致(vermagic 校验) 🔹 参数传递:insmod 支持命令行传参,modprobe 通过 /etc/modprobe.d/ 配置 🔹 调试手段:dmesg 查看内核日志,lsmod 查看模块状态

4.6 实战案例:依赖模块的加载与卸载

假设有两个模块:gpio_core(提供 GPIO 操作函数)和 led_driver(依赖 gpio_core)。

# 1. 查看模块依赖关系
modinfo led_driver
# 输出: depends: gpio_core

# 2. 错误方式:直接加载 led_driver(insmod 会失败)
sudo insmod ./led_driver.ko
# 输出: insmod: ERROR: could not insert module led_driver.ko: Unknown symbol in module

# 3. 正确方式:先加载依赖模块
sudo insmod ./gpio_core.ko
sudo insmod ./led_driver.ko

# 4. 使用 modprobe 自动处理依赖
sudo modprobe led_driver  # 自动加载 gpio_core

# 5. 卸载(必须先卸载 led_driver)
sudo rmmod led_driver
sudo rmmod gpio_core

# 6. 使用 modprobe -r 递归卸载
sudo modprobe -r led_driver  # 同时卸载 gpio_core(如果不再被使用)
💡 开发建议: 在模块开发阶段,建议在 Makefile 中添加 depmod 自动更新依赖,然后使用 modprobe 加载测试,避免手动处理依赖顺序的麻烦。