You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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变量。

错误原因分析

  1. 静态全局misc_device的内存回收冲突:原代码使用静态全局的misc_device变量,模块卸载时该变量所在的模块内存会被内核回收。若misc_deregister执行过程中存在并发访问(如内核线程遍历设备链表),会访问到已释放的misc_device.name字段,触发野指针错误,导致strlen()崩溃。
  2. 私有数据操作不规范:直接赋值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");

关键说明

  1. 动态分配miscdevice:使用kzalloc动态创建struct miscdevice实例,避免静态全局变量带来的模块卸载内存回收冲突。
  2. 模块私有数据管理:通过set_module_driver_data和get_module_driver_data在模块内部存储和获取miscdevice指针,确保exit时能正确定位要注销的设备。
  3. 标准私有数据接口:使用dev_set_drvdata和dev_get_drvdata管理设备与私有数据的关联,符合内核规范,避免直接访问结构体字段的兼容性问题。
  4. 资源释放顺序:先注销misc设备,再释放私有数据和miscdevice实例,确保内核设备链表中已移除该设备后再释放内存,彻底避免野指针访问。

内容的提问来源于stack exchange,提问作者Emax

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 16:55:00