Linux杂项设备卸载时misc_deregister触发通用保护错误排查
杂项字符驱动卸载时GPF错误排查与修复
问题背景
编写了一个简单的杂项字符驱动,在misc_init()中动态分配私有数据,misc_exit()中释放资源。执行sudo rmmod sepehr时偶尔触发内核通用保护错误(GPF),多次加载卸载后必然出现,驱动无法移除,只能重启虚拟机。
dmesg日志显示错误发生在misc_deregister流程中,RIP指向strlen(),调用链为kstrdup()->device_get_devnode()。测试环境为Fedora 6.3.12-100.fc37.x86_64内核的干净虚拟机,移除数据分配逻辑则无错误。需求是找到正确的动态私有数据初始化方式,且不使用全局静态非const变量。
错误原因分析
- 静态全局
misc_device的内存回收冲突:原代码使用静态全局的misc_device变量,模块卸载时该变量所在的模块内存会被内核回收。若misc_deregister执行过程中存在并发访问(如内核线程遍历设备链表),会访问到已释放的misc_device.name字段,触发野指针错误,导致strlen()崩溃。 - 私有数据操作不规范:直接赋值
this_device->driver_data虽能实现功能,但未使用内核标准的dev_set_drvdata/dev_get_drvdata接口,存在兼容性风险。
解决方案
核心修改点
- 动态分配
struct miscdevice实例,替代静态全局变量,避免模块卸载时的内存回收冲突。 - 使用内核标准接口
dev_set_drvdata设置私有数据,dev_get_drvdata获取私有数据,保证操作规范性与兼容性。 - 调整资源释放顺序:先注销misc设备,再依次释放私有数据、miscdevice实例。
最终修正代码
#include <linux/kernel.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/miscdevice.h> #include <linux/slab.h> #include <linux/device.h> // 私有数据结构体 struct sepehr_private { struct mutex mtx; char *message; size_t size; }; // 文件操作 - Open static int sepehr_open(struct inode *inode, struct file *file) { // 将私有数据关联到file结构体(可选,方便后续文件操作使用) struct sepehr_private *priv = dev_get_drvdata(inode->i_cdev->dev); file->private_data = priv; return 0; } // 文件操作 - Release static int sepehr_release(struct inode *inode, struct file *file) { return 0; } // 文件操作 - Read static ssize_t sepehr_read(struct file *file, char *buffer, size_t len, loff_t *offset) { return 0; } // 文件操作 - Write static ssize_t sepehr_write(struct file *file, const char *buffer, size_t len, loff_t *offset) { return len; } // 文件操作 - Ioctl static long sepehr_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { return 0; } // 文件操作 - Llseek static loff_t sepehr_llseek(struct file *file, loff_t offset, int whence) { return 0; } // 文件操作结构体(const全局静态,符合要求) static const struct file_operations fops = { .owner = THIS_MODULE, .open = sepehr_open, .release = sepehr_release, .read = sepehr_read, .write = sepehr_write, .unlocked_ioctl = sepehr_ioctl, .llseek = sepehr_llseek, }; // 模块初始化函数 static int __init misc_init(void) { int ret; struct sepehr_private *priv; struct miscdevice *misc_dev; // 1. 动态分配miscdevice实例 misc_dev = kzalloc(sizeof(*misc_dev), GFP_KERNEL); if (!misc_dev) { printk(KERN_ERR "sepehr: 分配miscdevice内存失败\n"); return -ENOMEM; } // 2. 动态分配私有数据 priv = kzalloc(sizeof(*priv), GFP_KERNEL); if (!priv) { printk(KERN_ERR "sepehr: 分配私有数据内存失败\n"); kfree(misc_dev); return -ENOMEM; } // 3. 初始化私有数据 mutex_init(&priv->mtx); priv->message = NULL; priv->size = 0; // 4. 初始化miscdevice字段 misc_dev->minor = MISC_DYNAMIC_MINOR; misc_dev->name = "sepehr"; misc_dev->fops = &fops; misc_dev->mode = 0666; // 5. 注册misc设备 ret = misc_register(misc_dev); if (ret) { printk(KERN_ERR "sepehr: 注册misc设备失败(错误码:%d)\n", ret); kfree(priv); kfree(misc_dev); return ret; } // 6. 使用标准接口关联私有数据与设备 dev_set_drvdata(misc_dev->this_device, priv); // 7. 将misc_dev指针存储为模块私有数据,方便exit时获取 set_module_driver_data(THIS_MODULE, misc_dev); printk(KERN_INFO "sepehr: misc设备注册成功\n"); return 0; } // 模块退出函数 static void __exit misc_exit(void) { struct sepehr_private *priv; struct miscdevice *misc_dev = get_module_driver_data(THIS_MODULE); if (!misc_dev) { printk(KERN_WARNING "sepehr: 未找到misc设备指针\n"); return; } // 获取私有数据 priv = dev_get_drvdata(misc_dev->this_device); // 1. 注销misc设备(必须先注销,再释放资源) misc_deregister(misc_dev); // 2. 释放私有数据资源 if (priv) { mutex_destroy(&priv->mtx); kfree(priv->message); kfree(priv); } // 3. 释放miscdevice实例 kfree(misc_dev); printk(KERN_INFO "sepehr: misc设备注销成功\n"); } module_init(misc_init); module_exit(misc_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Sepehr"); MODULE_DESCRIPTION("动态分配资源的测试杂项字符驱动"); MODULE_VERSION("0.1");
关键说明
- 动态分配miscdevice:使用
kzalloc动态创建struct miscdevice实例,避免静态全局变量带来的模块卸载内存回收冲突。 - 模块私有数据管理:通过
set_module_driver_data和get_module_driver_data在模块内部存储和获取miscdevice指针,确保exit时能正确定位要注销的设备。 - 标准私有数据接口:使用
dev_set_drvdata和dev_get_drvdata管理设备与私有数据的关联,符合内核规范,避免直接访问结构体字段的兼容性问题。 - 资源释放顺序:先注销misc设备,再释放私有数据和miscdevice实例,确保内核设备链表中已移除该设备后再释放内存,彻底避免野指针访问。
内容的提问来源于stack exchange,提问作者Emax
相关产品推荐
相关产品推荐

