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

如何实现平台驱动模块卸载控制?现有方案失效原因排查

问题背景

我开发了一个支持多线程访问的platform driver,通过IOCTL命令设置atomic_t标志,期望实现:

  • 当标志为0时,允许卸载模块;
  • 当标志为1时,调用rmmod my_module无法卸载模块。

尝试了两种方案都失败:

方案1:返回-EBUSY被系统忽略

代码实现:

int driver_remove(struct platform_device *_pdev)
{
        int reg = 0;

        reg = atomic_read(&_pdev->my_atomic_lock);
        if (reg) {
            pr_error("Cannot remove the module as it is still being used somewhere.");
            return -EBUSY;
        }

        // REST OF THE REMOVAL ....
        return 0;
}

static struct platform_driver my_driver = {
        .probe          = driver_probe,
        .remove         = driver_remove,
        .driver         = {
                .name           = MODULE_NAME,
                .owner          = THIS_MODULE,
                .of_match_table = match_types,
        },
};
module_platform_driver(my_driver);

系统提示返回非零值会被忽略。

方案2:自旋锁阻止卸载未生效

代码实现:

int driver_remove(struct platform_device *_pdev)
{
        // Be certain that no one has blocked the device (to prevent killing a running function)
        spin_lock(&_dev->_my_lock);

        // REST OF THE REMOVAL ....
        return 0;
}

static struct platform_driver my_driver = {
        .probe          = driver_probe,
        .remove         = driver_remove,
        .driver         = {
                .name           = MODULE_NAME,
                .owner          = THIS_MODULE,
                .of_match_table = match_types,
        },
};
module_platform_driver(my_driver);

已确认自旋锁处于锁定状态,但无法阻止卸载。


错误原因分析

方案1错误:platform_driver的remove返回值不影响模块卸载流程

Linux内核中,platform_driver的remove函数返回值仅用于设备与驱动解绑的内部逻辑,完全不作用于模块卸载判断。执行rmmod时,内核只检查模块的引用计数,只要计数为0,不管remove返回什么,模块都会被强制卸载,你看到的“返回非零值被忽略”就是这个原因。

方案2错误:自旋锁逻辑完全倒置

你在remove函数里加锁的行为,实际效果是让卸载流程等待锁被释放后继续执行,而不是阻止卸载。正确逻辑应该是:当设备被使用时(其他线程持有锁),模块引用计数保持大于0,让rmmod直接失败,而不是在卸载时去抢锁等待。


正确实现方案

要实现“设备忙时阻止模块卸载”,核心是利用内核的模块引用计数机制,结合atomic变量标记设备状态:

示例代码

// 标记设备忙状态的原子变量
static atomic_t device_busy = ATOMIC_INIT(0);

// IOCTL处理函数
long my_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
        switch(cmd) {
                case IOCTL_SET_BUSY:
                        // 设备转为忙状态,首次设置时增加模块引用计数
                        if (atomic_inc_return(&device_busy) == 1) {
                                if (!try_module_get(THIS_MODULE)) {
                                        atomic_dec(&device_busy);
                                        return -EINVAL;
                                }
                        }
                        return 0;
                case IOCTL_SET_IDLE:
                        // 设备转为空闲状态,最后一次释放时减少模块引用计数
                        if (atomic_dec_return(&device_busy) == 0) {
                                module_put(THIS_MODULE);
                        }
                        return 0;
                default:
                        return -ENOTTY;
        }
}

// remove函数仅负责清理设备资源
int driver_remove(struct platform_device *_pdev)
{
        // 可选:最后检查设备状态,记录错误日志
        if (atomic_read(&device_busy) != 0) {
                pr_error("Device is still busy during removal!\n");
                return -EBUSY;
        }

        // 清理设备硬件资源、内存等...
        return 0;
}

static struct platform_driver my_driver = {
        .probe          = driver_probe,
        .remove         = driver_remove,
        .driver         = {
                .name           = MODULE_NAME,
                .owner          = THIS_MODULE,
                .of_match_table = match_types,
        },
};
module_platform_driver(my_driver);

关键说明

  1. 模块引用计数是核心:rmmod会直接检查模块的refcount,只要计数>0,就会返回Resource temporarily unavailable,阻止卸载。
  2. 严格匹配引用操作:try_module_get和module_put的调用次数必须完全对应,避免引用计数泄露导致模块无法卸载。
  3. 字符设备场景优化:如果你的驱动是字符设备,也可以在open函数调用try_module_get,release函数调用module_put,这样只要设备文件被打开,模块就无法卸载,这是更通用的做法。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 21:37:03