设备与驱动解绑机制问题:解绑未等待字符设备fops完成引发Oops
解决设备解绑时未等待字符设备操作完成的问题
核心思路
通过原子引用计数跟踪字符设备的打开状态,结合等待队列让remove函数阻塞,直到所有字符设备的文件句柄都被释放,确保字符设备的release日志先于设备移除日志输出,彻底避免ioctl执行中解绑导致的Oops。
具体实现步骤
1. 扩展设备私有结构体
在你的设备结构体中新增引用计数、等待队列和移除标记,用于同步状态:
struct my_device { struct device *dev; struct cdev cdev; atomic_t open_count; // 原子计数,跟踪当前打开的文件句柄数 wait_queue_head_t wait_q; // 等待队列,供remove函数阻塞等待 bool removing; // 标记设备是否处于移除流程中 // 其他设备相关成员变量 };
2. 初始化同步成员
在probe函数中完成引用计数和等待队列的初始化:
static int my_probe(struct platform_device *pdev) { struct my_device *my_dev = devm_kzalloc(&pdev->dev, sizeof(*my_dev), GFP_KERNEL); // ... 其他设备初始化逻辑 ... atomic_set(&my_dev->open_count, 0); init_waitqueue_head(&my_dev->wait_q); my_dev->removing = false; // 注册字符设备等后续操作 // ... return 0; }
3. 改造file_operations的open/release函数
- open函数:先检查设备是否正在移除,拒绝新的打开请求,同时增加引用计数:
static int my_open(struct inode *inode, struct file *file) { struct my_device *my_dev = container_of(inode->i_cdev, struct my_device, cdev); // 设备正在移除时,拒绝新的打开操作 if (my_dev->removing) { return -ENODEV; } atomic_inc(&my_dev->open_count); file->private_data = my_dev; // ... 其他open阶段逻辑 ... return 0; }
- release函数:减少引用计数,若设备正在移除且最后一个句柄被释放,唤醒等待队列中的remove函数:
static int my_release(struct inode *inode, struct file *file) { struct my_device *my_dev = file->private_data; atomic_dec(&my_dev->open_count); // 设备移除中且所有句柄已释放,唤醒阻塞的remove函数 if (my_dev->removing && atomic_read(&my_dev->open_count) == 0) { wake_up(&my_dev->wait_q); } // ... 其他release阶段逻辑 ... return 0; }
4. 修改remove函数,阻塞等待句柄释放
在remove函数中先标记设备状态,再等待所有打开句柄释放,最后执行资源清理:
static int my_remove(struct platform_device *pdev) { struct my_device *my_dev = platform_get_drvdata(pdev); // 标记设备进入移除流程,拒绝新的打开请求 my_dev->removing = true; // 等待所有文件句柄关闭,超时时间可根据业务需求调整(这里设为5秒) if (!wait_event_timeout(my_dev->wait_q, atomic_read(&my_dev->open_count) == 0, msecs_to_jiffies(5000))) { dev_warn(&pdev->dev, "Warning: Timeout waiting for file handles to close\n"); // 超时后可选择强制清理,但需确保不会触发新的Oops } // 此时所有句柄已释放,安全注销字符设备并清理资源 cdev_del(&my_dev->cdev); // ... 其他设备资源清理操作 ... dev_info(&pdev->dev, "Device removed successfully\n"); return 0; }
关键原理说明
- 原子变量
open_count确保多线程环境下计数的正确性,避免竞态问题。 removing标记阻断新的打开请求,防止解绑过程中出现新的ioctl操作。- 等待队列让
remove函数进入休眠状态,避免忙等占用CPU资源,直到所有句柄释放才继续执行。 - 超时机制防止因异常场景(如进程挂死)导致
remove函数永久阻塞。
额外注意事项
- 若你的ioctl包含长时间运行的任务,需在任务循环中检查
removing标记,一旦发现设备正在移除,立即终止任务并返回错误,避免等待超时。 - 所有访问设备资源的逻辑(如ioctl中)都要先检查
removing状态,防止访问已释放的内存。
内容的提问来源于stack exchange,提问作者akshay gupta
相关产品推荐
相关产品推荐

