如何处理 Linux 内核 Driver 中的设备删除?

How to handle device removal in Linux Kernel Driver?

你已经做了一千次了:你拔掉了一些 USB 设备,然后 driver 删除了与该 USB 设备关联的任何设备。任何使用以前打开的文件句柄的程序都会出错。大多数 Linux driver 都以某种方式解决了这个问题。

我目前正努力在一个简单的 driver 中实现相同的功能。我的 driver 创建了一个字符设备。当设备打开时,我将 struct fileprivate_data 成员设置为每个字符设备存在一次的一些管理数据的地址。该管理数据还包括一个互斥锁,我用它来同步 readwriteioctl.

等操作

当USB设备被拔掉时,问题就出现了。我不能释放管理数据所在的内存。首先,任何当前 运行 readwriteioctl 都应该完成。任何这样的 运行 调用也可能会锁定互斥锁并尝试解锁它。因此将访问互斥量所在的内存。

任何 readwriteioctl 拔出设备后的调用都应该失败,所以每个这样的调用都必须读取一些变量来判断 USB 设备是否仍然插入或不。同样,只要有打开的文件句柄,该变量必须存在于某个地方并且它所在的内存必须保持分配状态。

长话短说,在我看来我必须对引用计数进行排序:管理数据必须保持分配状态,直到所有文件句柄都已关闭。我可以自己实现,但我觉得我会重新发明轮子。这种事情肯定已经存在了,我不是第一个遇到这个问题的人。

Linux 是否在内部跟踪打开文件句柄的数量?我可以定义一个在所有文件句柄关闭时调用的回调吗?这甚至是一件可行的事情吗?从系统中删除字符设备的正确方法是什么?

不能避免使用全局变量,因为可以连接任意数量的 USB 设备。

根据 0andriy 的建议,我使用引用计数来跟踪打开文件句柄的数量。对于每个字符设备,我添加了一个包含 mutexkrefcdev 结构和指向更多数据的指针的新结构。打开新文件句柄时,我使用 kref_get 来增加引用计数器。因此,我在释放文件句柄或拔下 USB 设备时使用 kref_put

由于所有 I/O 操作(readwriteioctl 等)都使用互斥锁,我可以安全地将指针更改为 NULL 拔下 USB 设备时。 I/O 操作然后开始报告错误。

当 USB 设备被拔出时,kref 引用计数器仅 returns 归零 并且 所有文件句柄都已关闭。那么我也可以释放新结构的内存。

这似乎有效。令人惊讶的是 cdev 结构被文件句柄引用。好像只要有打开的文件句柄就需要。

但我仍然很惊讶我不得不走这条路。在每个驱动程序中实现它似乎是多余的。

更新: 事实证明,自己进行引用计数是危险的。特别是计算打开文件句柄的数量是错误的。 Linux中有一个完整的基于引用计数的垃圾收集系统,你需要使用它。

例如,Linux 在进程打开字符设备时调用 cdev_get,在进程存在且文件句柄仍处于打开状态时调用 cdev_put。不幸的是,对 cdev_put 的调用将在文件句柄的释放函数之后发生。正如我们在释放最后一个文件句柄后释放内存一样,我们最终会在调用 cdev_put 之前释放 cdevmutex 的底层内存。

解决方案是通过 cdev_set_parentcdev 分配父级。每当调用 cdev_get 时,父 kobject 的引用计数器将增加,而在调用 cdev_put 后将减少。然后,kobject 可以拥有自己的释放函数,释放 cdev 所需的任何内存。基本上我用 kobject 替换了结构中的 kref。

经验教训:不要自己进行引用计数。使用现有的 kobjects 层次结构,这是内核进行垃圾收集的方式。

UPDATE2: 事实证明,我们又重新发明了轮子。 device 结构包含一个释放钩子,当内部 kobject 的引用计数达到零时调用它。这是内核中通常用于释放与设备关联的资源的机制 - 包括设备结构本身。

我正在寻找 cdev 结构中的释放挂钩。有 none。但事实证明,我应该在层次结构中向上查找一个这样的钩子。

长话短说:将 cdev_device_adddevice 释放挂钩结合使用。 cdev_device_add 将在内部调用 cdev_set_parent,因此 device 成为 cdev 的父级。这些是其他内核驱动程序(例如 evdev)用来释放资源的机制。