【问题标题】:How to handle device removal in Linux Kernel Driver?如何在 Linux 内核驱动程序中处理设备删除?
【发布时间】:2023-03-28 05:30:03
【问题描述】:

你已经做了一千次了:你拔掉了一些 USB 设备,与该 USB 设备关联的任何设备都被驱动程序删除了。任何使用以前打开的文件句柄的程序都会出错。不知何故,大多数 Linux 驱动程序都会处理这个问题。

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

现在拔下 USB 设备时会出现问题。我不能释放管理数据所在的内存。首先,任何当前运行的readwriteioctl 都应该完成。任何此类正在运行的调用也可能会持有互斥锁并尝试解锁它。所以会访问互斥体所在的内存。

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

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

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

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

【问题讨论】:

  • 你需要做一些引用计数。一是防止驱动程序在使用时消失。另一个是防止设备在打开时消失。也许你需要更多,但没有代码就很难说。
  • 对于 USB,如果设备在文件操作处理程序中断开连接,您可能只需检查(锁定互斥锁),并在断开连接处理程序中杀死运行中的 URB,如通过“drivers/usb/usb-skeleton.c”中的原型“usb-skeleton”驱动程序代码。

标签: linux-kernel usb linux-device-driver


【解决方案1】:

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

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

kref 引用计数器仅在拔下 USB 设备并且所有文件句柄都关闭时返回为零。那么我也可以释放新结构的内存。

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

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

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

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

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

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

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

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

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

【讨论】:

  • 用基于 kobject 层次结构的最终解决方案更新了答案。
  • 你有没有问过自己,为什么在数百个内核中只有几个驱动程序会这样做? I.o.w.是什么让你的代码如此特别?再说一次,你没有显示代码来看看你在做什么。
  • 是的,我有。不过这个问题我没有答案。
  • 我在最初的问题中提到我没有做任何特别的事情。建议使用引用计数 - 但引用计数的唯一安全方法是通过 cdev 也是 kobject 的事实。也许我还没有找到更好的方法。
猜你喜欢
  • 1970-01-01
  • 2016-08-19
  • 2017-03-02
  • 1970-01-01
  • 2013-10-29
  • 1970-01-01
  • 1970-01-01
  • 2012-12-13
  • 2017-04-28
相关资源
最近更新 更多