【Linux内核模块】模块的使用计数
- 当第一个读者借阅时,计数器加 1,表示有 1 人正在使用
- 第二个读者借阅时,计数器变为 2
- 当一个读者归还时,计数器减 1
- 只有当计数器归 0 时,这本书才会被放回仓库(相当于模块卸载)
1.2 内核模块的使用计数
内核模块的使用计数原理完全一样:
- 当有其他模块或设备使用当前模块时,计数加 1
- 使用结束后,计数减 1
- 只有当计数为 0 时,模块才能被卸载
这个机制确保了模块不会在被使用时被意外卸载,避免系统崩溃。
1.3 使用计数的本质
使用计数本质上是一个原子计数器(atomic_t类型),存放在模块结构体(struct module)中,内核通过操作这个计数器来控制模块的生命周期。
二、使用计数的核心操作:增、减、查
2.1 增加使用计数:try_module_get()
当模块 A 要使用模块 B 时,必须先调用:
代码语言:javascript
AI代码解释
if (!try_module_get(moduleB)) {
// 获取失败,模块B已卸载或不可用
return -ENODEV;
}
// 获取成功,现在可以安全使用模块B的导出符号
- 检查模块 B 是否存在且未标记为卸载
- 如果满足条件,将模块 B 的使用计数加 1
- 返回 true(非 0)表示获取成功
2.2 减少使用计数:module_put()
当模块 A 使用完模块 B 后,必须调用:
代码语言:javascript
AI代码解释
module_put(moduleB); // 使用计数减1
- 将模块 B 的使用计数减 1
- 检查计数是否变为 0
- 如果变为 0 且模块 B 已标记为卸载,则执行实际卸载操作
2.3 查看使用计数:lsmod命令
用户空间可以通过lsmod命令查看模块的使用计数:
代码语言:javascript
AI代码解释
$ lsmod | grep usbcore
usbcore 311296 14 usb_storage,usbhid,btusb,...
这里的14表示usbcore模块当前的使用计数为 14,即有 14 个其他模块正在使用它。
三、使用计数的典型应用场景
3.1 驱动模块的依赖管理
USB 核心驱动(usbcore)被众多 USB 设备驱动依赖:
- 当插入 U 盘时,
usb-storage驱动加载,usbcore计数 + 1 - 再插入 USB 鼠标,
usbhid驱动加载,usbcore计数 + 1 - 拔出 U 盘,
usb-storage卸载,usbcore计数 - 1 - 只有当所有 USB 设备都拔出后,
usbcore计数为 0,才能被卸载
3.2 模块导出符号的安全使用
当模块 A 使用模块 B 导出的符号时:
代码语言:javascript
AI代码解释
// 模块A使用模块B的导出函数前
if (!try_module_get(THIS_MODULE)) {
return -EFAULT;
}
// 使用模块B的函数...
result = moduleB_function();
// 使用完毕后
module_put(THIS_MODULE);
这样确保在模块 A 使用模块 B 期间,模块 B 不会被卸载。
3.3 设备文件操作中的计数
字符设备驱动常在内核态文件操作函数中维护计数:
代码语言:javascript
AI代码解释
static int my_device_open(struct inode *inode, struct file *file) {
if (!try_module_get(THIS_MODULE)) {
return -EBUSY;
}
// 设备初始化...
return 0;
}
static int my_device_release(struct inode *inode, struct file *file) {
// 设备清理...
module_put(THIS_MODULE);
return 0;
}
这样当有用户打开设备文件时,模块计数 + 1;关闭时计数 - 1。
四、使用计数的工作原理:内核源码揭秘
1. 模块结构体中的计数器
在include/linux/module.h中定义:
代码语言:javascript
AI代码解释
struct module {
// ...
atomic_t refcnt; // 使用计数
// ...
};
refcnt就是核心的使用计数器,初始值为 1(模块加载时)。
2. try_module_get()源码简化版
代码语言:javascript
AI代码解释
int try_module_get(struct module *mod) {
if (mod->state != MODULE_STATE_LIVE)
return 0; // 模块已卸载或正在卸载
if (atomic_inc_not_zero(&mod->refcnt))
return 1; // 计数成功加1
return 0; // 模块在检查后状态变化
}
3. module_put()源码简化版
代码语言:javascript
AI代码解释
void module_put(struct module *mod) {
if (atomic_dec_and_test(&mod->refcnt)) {
// 计数变为0,且模块已标记为卸载
synchronize_sched(); // 等待所有CPU上的任务完成
free_module(mod); // 释放模块内存
}
}
4. 使用计数的原子性保障
由于内核是多任务环境,可能有多个 CPU 同时操作计数,因此使用了原子操作:
atomic_inc_not_zero():原子性地增加计数,且检查结果是否非零atomic_dec_and_test():原子性地减少计数,并检查结果是否为零
这些原子操作确保了计数在并发环境下的正确性。
五、实战示例:手动控制模块使用计数
下面通过一个简单示例,演示如何在模块中手动管理使用计数。
1. 模块代码(count_demo.c)
代码语言:javascript
AI代码解释
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#define DEMO_MAJOR 240
#define DEMO_NAME "count_demo"
// 设备打开函数
static int demo_open(struct inode *inode, struct file *file) {
printk(KERN_INFO "设备打开,当前使用计数: %d\n",
module_refcount(THIS_MODULE));
// 增加使用计数
if (!try_module_get(THIS_MODULE)) {
printk(KERN_ERR "获取模块失败\n");
return -EBUSY;
}
printk(KERN_INFO "使用计数已增加: %d\n",
module_refcount(THIS_MODULE));
return 0;
}
// 设备关闭函数
static int demo_release(struct inode *inode, struct file *file) {
printk(KERN_INFO "设备关闭,当前使用计数: %d\n",
module_refcount(THIS_MODULE));
// 减少使用计数
module_put(THIS_MODULE);
printk(KERN_INFO "使用计数已减少: %d\n",
module_refcount(THIS_MODULE));
return 0;
}
// 文件操作结构体
static struct file_operations demo_fops = {
.owner = THIS_MODULE,
.open = demo_open,
.release = demo_release,
};
// 模块初始化
static int __init demo_init(void) {
int ret;
ret = register_chrdev(DEMO_MAJOR, DEMO_NAME, &demo_fops);
if (ret < 0) {
printk(KERN_ERR "注册字符设备失败\n");
return ret;
}
printk(KERN_INFO "模块加载成功,初始使用计数: %d\n",
module_refcount(THIS_MODULE));
return 0;
}
// 模块退出
static void __exit demo_exit(void) {
unregister_chrdev(DEMO_MAJOR, DEMO_NAME);
printk(KERN_INFO "模块卸载成功\n");
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("使用计数演示模块");
2. 测试步骤
代码语言:javascript
AI代码解释
# 编译并加载模块
make
sudo insmod count_demo.ko
# 查看初始计数(应该为1)
lsmod | grep count_demo
count_demo 16384 0
# 创建设备节点
sudo mknod /dev/count_demo c 240 0
# 打开设备(模拟用户程序)
cat /dev/count_demo & # 后台运行
# 查看计数(应该为2)
lsmod | grep count_demo
count_demo 16384 1
# 关闭设备
kill %1 # 终止刚才的cat命令
# 查看计数(应该恢复为1)
lsmod | grep count_demo
count_demo 16384 0
# 卸载模块
sudo rmmod count_demo
3. 查看内核日志
代码语言:javascript
AI代码解释
dmesg | tail
[ 1234.567890] 模块加载成功,初始使用计数: 1
[ 1234.678901] 设备打开,当前使用计数: 1
[ 1234.678910] 使用计数已增加: 2
[ 1234.789012] 设备关闭,当前使用计数: 2
[ 1234.789020] 使用计数已减少: 1
[ 1234.890123] 模块卸载成功
六、使用计数的常见问题与解决方案
6.1 模块无法卸载:"Resource busy"
原因:使用计数不为 0,可能有:
- 其他模块仍在使用当前模块
- 设备文件仍被打开(用户程序未关闭)
- 内核线程仍在运行
解决:
代码语言:javascript
AI代码解释
# 查看模块依赖
lsmod | grep 模块名
# 查看打开的文件
lsof | grep /dev/模块设备名
# 终止相关进程
kill -9 进程ID
6.2 使用计数异常:计数不减少
原因:
- 忘记调用
module_put() try_module_get()和module_put()不配对- 代码中出现异常导致
module_put()未执行
解决:
- 检查所有
try_module_get()调用处,确保都有对应的module_put() - 使用
goto语句确保异常处理路径也会减少计数:
6.3 卸载模块后系统崩溃
原因:
- 使用计数管理错误,模块卸载时仍有代码在执行
- 未正确同步,其他 CPU 上还有使用该模块的任务在运行
解决:
- 在关键代码处增加
try_module_get()/module_put()对 - 使用
synchronize_sched()确保所有 CPU 上的任务完成
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)