如何处理 Linux 内核 Driver 中的设备删除?
How to handle device removal in Linux Kernel Driver?
你已经做了一千次了:你拔掉了一些 USB 设备,然后 driver 删除了与该 USB 设备关联的任何设备。任何使用以前打开的文件句柄的程序都会出错。大多数 Linux driver 都以某种方式解决了这个问题。
我目前正努力在一个简单的 driver 中实现相同的功能。我的 driver 创建了一个字符设备。当设备打开时,我将 struct file
的 private_data
成员设置为每个字符设备存在一次的一些管理数据的地址。该管理数据还包括一个互斥锁,我用它来同步 read
、write
和 ioctl
.
等操作
当USB设备被拔掉时,问题就出现了。我不能释放管理数据所在的内存。首先,任何当前 运行 read
、write
或 ioctl
都应该完成。任何这样的 运行 调用也可能会锁定互斥锁并尝试解锁它。因此将访问互斥量所在的内存。
任何 read
、write
或 ioctl
拔出设备后的调用都应该失败,所以每个这样的调用都必须读取一些变量来判断 USB 设备是否仍然插入或不。同样,只要有打开的文件句柄,该变量必须存在于某个地方并且它所在的内存必须保持分配状态。
长话短说,在我看来我必须对引用计数进行排序:管理数据必须保持分配状态,直到所有文件句柄都已关闭。我可以自己实现,但我觉得我会重新发明轮子。这种事情肯定已经存在了,我不是第一个遇到这个问题的人。
Linux 是否在内部跟踪打开文件句柄的数量?我可以定义一个在所有文件句柄关闭时调用的回调吗?这甚至是一件可行的事情吗?从系统中删除字符设备的正确方法是什么?
不能避免使用全局变量,因为可以连接任意数量的 USB 设备。
根据 0andriy 的建议,我使用引用计数来跟踪打开文件句柄的数量。对于每个字符设备,我添加了一个包含 mutex
、kref
、cdev
结构和指向更多数据的指针的新结构。打开新文件句柄时,我使用 kref_get
来增加引用计数器。因此,我在释放文件句柄或拔下 USB 设备时使用 kref_put
。
由于所有 I/O 操作(read
、write
、ioctl
等)都使用互斥锁,我可以安全地将指针更改为 NULL
拔下 USB 设备时。 I/O 操作然后开始报告错误。
当 USB 设备被拔出时,kref
引用计数器仅 returns 归零 并且 所有文件句柄都已关闭。那么我也可以释放新结构的内存。
这似乎有效。令人惊讶的是 cdev
结构被文件句柄引用。好像只要有打开的文件句柄就需要。
但我仍然很惊讶我不得不走这条路。在每个驱动程序中实现它似乎是多余的。
更新: 事实证明,自己进行引用计数是危险的。特别是计算打开文件句柄的数量是错误的。 Linux中有一个完整的基于引用计数的垃圾收集系统,你需要使用它。
例如,Linux 在进程打开字符设备时调用 cdev_get
,在进程存在且文件句柄仍处于打开状态时调用 cdev_put
。不幸的是,对 cdev_put
的调用将在文件句柄的释放函数之后发生。正如我们在释放最后一个文件句柄后释放内存一样,我们最终会在调用 cdev_put
之前释放 cdev
和 mutex
的底层内存。
解决方案是通过 cdev_set_parent
为 cdev
分配父级。每当调用 cdev_get
时,父 kobject
的引用计数器将增加,而在调用 cdev_put
后将减少。然后,kobject 可以拥有自己的释放函数,释放 cdev 所需的任何内存。基本上我用 kobject 替换了结构中的 kref。
经验教训:不要自己进行引用计数。使用现有的 kobject
s 层次结构,这是内核进行垃圾收集的方式。
UPDATE2: 事实证明,我们又重新发明了轮子。 device
结构包含一个释放钩子,当内部 kobject 的引用计数达到零时调用它。这是内核中通常用于释放与设备关联的资源的机制 - 包括设备结构本身。
我正在寻找 cdev
结构中的释放挂钩。有 none。但事实证明,我应该在层次结构中向上查找一个这样的钩子。
长话短说:将 cdev_device_add
与 device
释放挂钩结合使用。 cdev_device_add
将在内部调用 cdev_set_parent
,因此 device
成为 cdev
的父级。这些是其他内核驱动程序(例如 evdev)用来释放资源的机制。
你已经做了一千次了:你拔掉了一些 USB 设备,然后 driver 删除了与该 USB 设备关联的任何设备。任何使用以前打开的文件句柄的程序都会出错。大多数 Linux driver 都以某种方式解决了这个问题。
我目前正努力在一个简单的 driver 中实现相同的功能。我的 driver 创建了一个字符设备。当设备打开时,我将 struct file
的 private_data
成员设置为每个字符设备存在一次的一些管理数据的地址。该管理数据还包括一个互斥锁,我用它来同步 read
、write
和 ioctl
.
当USB设备被拔掉时,问题就出现了。我不能释放管理数据所在的内存。首先,任何当前 运行 read
、write
或 ioctl
都应该完成。任何这样的 运行 调用也可能会锁定互斥锁并尝试解锁它。因此将访问互斥量所在的内存。
任何 read
、write
或 ioctl
拔出设备后的调用都应该失败,所以每个这样的调用都必须读取一些变量来判断 USB 设备是否仍然插入或不。同样,只要有打开的文件句柄,该变量必须存在于某个地方并且它所在的内存必须保持分配状态。
长话短说,在我看来我必须对引用计数进行排序:管理数据必须保持分配状态,直到所有文件句柄都已关闭。我可以自己实现,但我觉得我会重新发明轮子。这种事情肯定已经存在了,我不是第一个遇到这个问题的人。
Linux 是否在内部跟踪打开文件句柄的数量?我可以定义一个在所有文件句柄关闭时调用的回调吗?这甚至是一件可行的事情吗?从系统中删除字符设备的正确方法是什么?
不能避免使用全局变量,因为可以连接任意数量的 USB 设备。
根据 0andriy 的建议,我使用引用计数来跟踪打开文件句柄的数量。对于每个字符设备,我添加了一个包含 mutex
、kref
、cdev
结构和指向更多数据的指针的新结构。打开新文件句柄时,我使用 kref_get
来增加引用计数器。因此,我在释放文件句柄或拔下 USB 设备时使用 kref_put
。
由于所有 I/O 操作(read
、write
、ioctl
等)都使用互斥锁,我可以安全地将指针更改为 NULL
拔下 USB 设备时。 I/O 操作然后开始报告错误。
当 USB 设备被拔出时,kref
引用计数器仅 returns 归零 并且 所有文件句柄都已关闭。那么我也可以释放新结构的内存。
这似乎有效。令人惊讶的是 cdev
结构被文件句柄引用。好像只要有打开的文件句柄就需要。
但我仍然很惊讶我不得不走这条路。在每个驱动程序中实现它似乎是多余的。
更新: 事实证明,自己进行引用计数是危险的。特别是计算打开文件句柄的数量是错误的。 Linux中有一个完整的基于引用计数的垃圾收集系统,你需要使用它。
例如,Linux 在进程打开字符设备时调用 cdev_get
,在进程存在且文件句柄仍处于打开状态时调用 cdev_put
。不幸的是,对 cdev_put
的调用将在文件句柄的释放函数之后发生。正如我们在释放最后一个文件句柄后释放内存一样,我们最终会在调用 cdev_put
之前释放 cdev
和 mutex
的底层内存。
解决方案是通过 cdev_set_parent
为 cdev
分配父级。每当调用 cdev_get
时,父 kobject
的引用计数器将增加,而在调用 cdev_put
后将减少。然后,kobject 可以拥有自己的释放函数,释放 cdev 所需的任何内存。基本上我用 kobject 替换了结构中的 kref。
经验教训:不要自己进行引用计数。使用现有的 kobject
s 层次结构,这是内核进行垃圾收集的方式。
UPDATE2: 事实证明,我们又重新发明了轮子。 device
结构包含一个释放钩子,当内部 kobject 的引用计数达到零时调用它。这是内核中通常用于释放与设备关联的资源的机制 - 包括设备结构本身。
我正在寻找 cdev
结构中的释放挂钩。有 none。但事实证明,我应该在层次结构中向上查找一个这样的钩子。
长话短说:将 cdev_device_add
与 device
释放挂钩结合使用。 cdev_device_add
将在内部调用 cdev_set_parent
,因此 device
成为 cdev
的父级。这些是其他内核驱动程序(例如 evdev)用来释放资源的机制。