系统挂起期间的 USB 设备持久性¶
- 作者:
Alan Stern <stern@rowland.harvard.edu>
- 日期:
2006 年 9 月 2 日(2008 年 2 月 25 日更新)
问题是什么?¶
根据 USB 规范,当 USB 总线挂起时,总线必须继续提供挂起电流(大约 1-5 mA)。这是为了让设备能够保持其内部状态,并且集线器能够检测连接更改事件(设备插入或拔出)。技术术语是“电源会话”。
如果 USB 设备的电源会话中断,则系统必须表现得好像设备已拔出。这是一种保守的方法;在没有挂起电流的情况下,计算机无法知道实际发生了什么。也许同一个设备仍然连接着,或者它被移除并且一个不同的设备插入了端口。系统必须假设最坏的情况。
默认情况下,Linux 的行为符合规范。如果 USB 主控制器在系统挂起期间失去电源,那么当系统唤醒时,连接到该控制器的所有设备都将被视为已断开连接。这始终是安全的,并且是“官方正确”的做法。
对于许多类型的设备来说,这种行为根本无关紧要。如果内核想要相信你的 USB 键盘在系统休眠时被拔掉,并且当系统唤醒时插入了一个新的键盘,谁在乎呢?当你敲击它时,它仍然会以相同的方式工作。
不幸的是,问题_确实_会发生,特别是在大容量存储设备上。其效果与系统挂起时设备真的被拔掉完全相同。如果你在设备上挂载了文件系统,那你就不走运了——该文件系统中的所有内容现在都无法访问。如果你的根文件系统位于该设备上,这尤其令人恼火,因为你的系统会立即崩溃。
电源丢失不是唯一需要担心的机制。任何中断电源会话的行为都会产生相同的效果。例如,即使在系统休眠期间可能保持了挂起电流,但在许多系统上,在唤醒的初始阶段,固件(即 BIOS)会重置主板的 USB 主控制器。结果:所有电源会话都被破坏,并且再次出现的情况就像你拔掉了所有 USB 设备一样。是的,这完全是 BIOS 的错,但除非你能说服 BIOS 供应商修复这个问题(祝你好运!),否则这对_你_没有任何好处。
在许多系统上,USB 主控制器会在挂起到 RAM 之后被重置。在几乎所有系统上,休眠期间(也称为 swsusp 或挂起到磁盘)都没有挂起电流可用。你可以在恢复后检查内核日志,看看是否发生了其中任何一种情况;查找说“根集线器失去电源或被重置”的行。
在实践中,人们被迫在挂起之前卸载 USB 设备上的任何文件系统。如果根文件系统位于 USB 设备上,则根本无法挂起系统。(好吧,_可以_挂起——但它会在唤醒时立即崩溃,这并没有好多少。)
解决方案是什么?¶
内核包含一个名为 USB-persist 的功能。它试图通过允许核心 USB 设备数据结构在电源会话中断时保持存在来解决这些问题。
它是这样工作的。如果内核看到 USB 主控制器在恢复期间未处于预期状态(即,如果控制器被重置或以其他方式失去电源),则它会对该控制器下方的每个 USB 设备应用持久性检查,其中设置了“persist”属性。它不尝试恢复设备;一旦电源会话消失,这是不可能的。相反,它会发出 USB 端口重置,然后重新枚举设备。(这与 USB 设备重置时发生的事情完全相同。)如果重新枚举显示现在连接到该端口的设备具有与以前相同的描述符,包括供应商 ID 和产品 ID,则内核会继续使用相同的设备结构。实际上,内核将设备视为仅仅被重置而不是被拔掉。
如果主控制器处于预期状态,但 USB 设备被拔出然后重新插入,或者如果 USB 设备未能执行正常的恢复,则会发生相同的事情。
如果现在没有设备连接到端口,或者描述符与内核记住的不同,则处理方式与你期望的一样。内核会销毁旧的设备结构,并表现得好像旧设备已被拔出并且插入了一个新设备。
最终结果是 USB 设备保持可用且可使用。文件系统挂载和内存映射不受影响,世界现在变得美好而快乐。
请注意,“USB-persist”功能仅适用于启用了该功能的设备。你可以通过执行(以 root 身份)启用该功能
echo 1 >/sys/bus/usb/devices/.../power/persist
其中“...”应该用设备的 ID 填写。通过写入 0 而不是 1 来禁用该功能。对于集线器,该功能会自动永久启用,并且 power/persist 文件甚至不存在,因此你只需要担心为真正重要的设备设置它。
这是最佳解决方案吗?¶
也许不是。可以说,跨设备断开连接跟踪已挂载的文件系统和内存映射应由集中的逻辑卷管理器处理。这样的解决方案将允许你插入 USB 闪存设备,创建与其关联的持久卷,拔出闪存设备,稍后重新插入,并且仍然有与该设备关联的相同持久卷。因此,它将比 USB-persist 更具深远意义。
另一方面,编写持久卷管理器将是一项艰巨的任务,并且使用它将需要用户的大量输入。这个解决方案要快得多,也容易得多——而且它现在已经存在,这绝对是一个巨大的优势!
此外,USB-persist 功能适用于_所有_ USB 设备,而不仅仅是大容量存储设备。它可能对其他设备类型(例如网络接口)同样有用。
警告:USB-persist 可能很危险!!¶
当恢复中断的电源会话时,内核会尽力确保 USB 设备没有被更改;也就是说,与之前相同的设备仍然插入到端口中。但检查不能保证 100% 准确。
如果你用另一个相同类型的 USB 设备(相同制造商、相同 ID 等)替换一个 USB 设备,则内核很可能不会检测到更改。将序列号字符串和其他描述符与内核存储的值进行比较,但这可能没有帮助,因为制造商经常完全省略其设备中的序列号。
此外,完全可以在更改其介质的同时保持 USB 设备完全相同。如果你在系统休眠时更换了 USB 读卡器中的闪存卡,则内核将无法知道你做了这件事。内核会假设没有发生任何事情,并将继续使用旧卡的分区表、inode 和内存映射。
如果内核以这种方式被愚弄,则几乎肯定会导致数据损坏并使你的系统崩溃。你将无从责怪,只能怪你自己。
对于那些设置了 avoid_reset_quirk 属性的设备,持久化可能会失败,因为它们可能会在重置后变形。
你已被警告!使用风险自负!
话虽如此,大多数时候都不会有任何麻烦。USB-persist 功能可能非常有用。充分利用它。