RFC-0257:storage-host:对上层存储驱动程序进行组件化 | |
---|---|
状态 | 已接受 |
区域 |
|
说明 | 将上层存储驱动程序 (GPT、FVM 等) 移至新的存储主机组件。 |
问题 | |
Gerrit 更改 | |
作者 | |
审核人 | |
提交日期(年-月-日) | 2024-07-08 |
审核日期(年-月-日) | 2024-08-13 |
摘要
目前,块设备是在驱动程序框架中以分层方式实现的。实际与硬件交互的低级驱动程序(例如 sdmmc、virtio)会实现内部 fuchsia.hardware.block.driver
协议,而高级驱动程序(例如 GPT、FVM、zxcrypt)会与此协议交互,并在这些驱动程序之上提供更高级别的功能(包括公共 fuchsia.hardware.block
和 fuchsia.hardware.block.partition
协议)。我们建议将所有上层块驱动程序移至新的组件 storage-host,只在驱动程序框架中保留低级驱动程序。
设计初衷
此项更改的主要目标是将存储堆栈与 Devfs 和驱动程序框架解耦,以支持驱动程序框架团队移除 Devfs(更广泛地说,是将驱动程序堆栈迁移到驱动程序框架 V2)。
目前,存储堆栈在发现块设备和对其进行拓扑访问时,在很大程度上依赖于 Devfs。移除此依赖项需要对存储堆栈进行大量重构,以使用 Devfs 的替代机制(在撰写本文档时,此机制尚未设计完毕)。由于需要创建新的 API 并更改存储堆栈才能使用它们,因此我们自然也需要重新考虑这些 API 的实际实现位置。
可以说,GPT 等上层块设备不是真正意义上的设备驱动程序;它们不会与硬件交互,而是与下层块设备交互。换句话说,驱动程序框架针对与这些上层块设备不同的用例进行了优化。存储堆栈一直是驱动程序框架的异常客户端(例如,我们在 Devfs 中大量使用拓扑路径),而且维持这种不匹配对驱动程序框架或存储团队似乎没有任何好处。
除了加快驱动程序框架团队废弃 Devfs 的工作之外,将这些上层块设备迁移到存储主机还有许多其他好处:
- 通过将技术栈简化并整合为熟悉的语言 (Rust) 和框架(常规的非驱动程序组件),提高存储团队的工程速度
- 启用需要跨堆栈工作(例如可能将文件系统与上层块驱动程序共享位置,或进行 API 更改,例如 I/O 优先级所需的更改)的性能优化
- 允许存储团队控制哪些组件可以访问块设备,这应由存储政策(而非对 /dev/class/block 的访问权限)进行中介。
- 我们无需将这些驱动程序移植到驱动程序框架 V2。
利益相关方
教员:
- hjfreyer@google.com
Reviewers:
- garratt@google.comm
- csuter@google.com
- curtisgalloway@google.com
- surajmalhotra@google.com
咨询了:
- 我们咨询了驱动程序框架团队。
社交:
在发布之前,我们已与驱动程序框架团队分享了此 RFC。
要求
这项更改必须对 Fuchsia 的其余部分公开;我们预计不会在此过程中进行任何功能更改。
此更改不应导致性能、内存或存储用量出现任何重大回归。
设计
storage-host 组件将管理物理块设备的分区和嵌套块设备。它将采用 Rust 实现。
为方便说明,本文将介绍采用 GPT 格式的块设备的系统的启动流程。Storage-host 将能够以类似的方式处理其他分区方案(例如 MBR 和 FVM)。
Fshost 目前会监听来自驱动程序框架的块设备,检测其格式,并确定要绑定的块设备驱动程序。例如,当检测到采用 GPT 格式的设备时,fshost 将启动 gpt
设备驱动程序。fshost 中的更改非常简单:fshost 将不再启动 gpt
设备驱动程序,而是将设备交接给存储主机。
storage-host 将从 GPT 解析分区表,并为每个分区导出 fuchsia.hardware.block.partition.Partition
服务。这些服务将由 storage-host 导出的 partitions
目录提供。此目录可替代 Devfs 中的 dev-topological 路径,支持对嵌套分区进行分层发现和访问。
Fshost 会监控发布到 partitions
的设备,并执行常规匹配,其中可能包括启动文件系统。嵌套分区有效(例如 GPT 中的 FVM),在这种情况下,fshost 只会请求 storage-host 挂载并取消嵌套给定分区。
在此过程中,文件系统和其他客户端用于执行块 I/O 的协议无需更改,因为存储主机将实现与驱动程序相同的协议。不过,我们可能会出于其他原因(例如提升性能)选择更改这些协议。
目前使用 Devfs 发现和连接到块设备的客户端需要移植到使用 partitions
。许多用法已由 fshost 或 paver 中介,因此将大量客户端迁移到通过 fshost 使用中介访问可能很有意义,这样我们就可以更严格地控制对块设备的使用。例如,我们可以在 fshost 中公开一个 API 来访问特定分区,而不是向所有块设备授予全面访问权限。partitions
目录仅在树内使用,并且应该只有一组有限的客户端,因此我们可以根据需要灵活地对其进行更改。
fshost 和 storage-host 之间的来回关系(fshost 将块设备传递给 storage-host,storage-host 绑定到分区,并将嵌套的块设备传递回 fshost)可能看起来不寻常,但有两个好处。从实际角度来看,由于 fshost 中需要进行的更改非常少,因此它可以加快存储主机的实现速度。它还保持了职责分离:fshost 实现存储政策(要绑定的文件系统、要挂载的块设备等),而 storage-host 通过托管嵌套的块设备来帮助执行该政策。如果有必要,可以稍后重构循环关系。
请注意,早期启动(即从 ZBI 加载 bootfs)不会发生任何变化。引导序列的这一部分在启动 fshost 之前,因此不会因此提案而发生变化。
多个存储主机实例
虽然目前没有必要,但我们将避免假定系统中只有一个存储主机实例。例如,我们可能希望为嵌入式存储设备和可插拔 USB 设备提供单独的存储主机实例,以便在两个存储堆栈之间保持一定程度的隔离,从而增强安全性。或者,我们可能希望让运行不同格式(例如 GPT 和 FVM)的存储主机实例分开,以增强隔离性。
图 1:当前架构。
图 2:提议的架构。
实现
随着我们迁移各种驱动程序,此更改可以逐渐进行,并按开发板进行。
例如,vim3 配置仅需要迁移 GPT 驱动程序,因此我们可以先实现该驱动程序,然后在 GPT 驱动程序移植完毕后在 vim3 上启用 storage-host。智能产品需要 FVM 和 zxcrypt;这些功能将在稍后实现。
我们需要确保客户端可以从 devfs 或 storage-host 临时访问块设备,这很简单,因为我们计划使用类似的以目录为导向的接口进行发现。
我们将限制 fuchsia.fshost.StorageHost
配置功能对 storage-host 的使用,并在特定产品/开发板配置准备好切换时启用它。
所有用例都已切换并被视为稳定后,我们就可以移除过渡逻辑以完成迁移。
storage-host 将作为单体 Rust 二进制文件实现。如果有必要(例如,为了减少二进制文件膨胀),我们可以将功能(例如 zxcrypt)拆分到库中,并根据需要按产品动态关联这些库,但为简单起见,我们将从单体二进制文件开始。
请注意,由于存储主机与基于驱动程序框架的等效实现之间没有功能差异,因此如果出现问题,可以安全地还原这些更改。
请注意,FVM 和 GPT 还配有各种主机工具。我们不打算将这些库移植到 Rust,因为它们稳定且独立于驱动程序,目前没有更改它们的动机。
性能
我们认为,对于不依赖于 FVM 和 zxcrypt 的配置,此更改对性能没有影响。虽然现在文件系统和块设备驱动程序之间有一个新组件,但通过一些简单的 API 更改,就可以移除此组件(例如,存储主机可以将底层块设备的窗口化视图代理给文件系统,然后文件系统可以直接与底层块设备驱动程序通信)。
对于 FVM 和 zxcrypt,存储主机需要拦截每个请求并在传输过程中对其进行修改,这会导致延迟时间增加,与当前的驱动程序框架实现相比。对于 FVM,必须执行此操作才能将虚拟偏移量映射到物理偏移量;对于 zxcrypt,必须执行此操作才能进行加密。
延迟时间增加的原因是,存储主机需要将请求从块 FIFO 中拉出,并重新加入底层 FIFO 队列。请注意,驱动程序框架实现仍需要拦截、修改和重新加入队列每个请求,但驱动程序之间的通信是通过 Banjo 协议 (fuchsia.hardware.block.driver.Block) 进行的,该协议会在两个驱动程序共处一处时使用更高效的传输机制。换句话说,延迟时间是由于通过存储主机进行额外进程跳转而产生的。
我们认为可以通过以下几种方式解决此问题(这些方式在使用存储空间主机时会更简单或更可行):
- 在 Rust 中引入并发更容易,因此我们或许能够通过流水线或并行处理工作来提高吞吐量。
- Blobfs 使用外部解压缩程序来提高安全性,这会增加文件读取的往返次数。这可以移至存储空间主机,并与 I/O 请求一起执行,从而取消存储空间主机引入的额外跳转。(如果是这种情况,从安全角度来看,Blobfs 不会受到影响,因为 Blobfs 不会假定块设备本身是可信的;从安全角度来看,主要目标是避免进程内解压缩,因为这可能会破坏 Blobfs 进程。其他文件系统(例如 Minfs)没有 Blobfs 那样强大的数据验证能力,因此如果存储主机遭到入侵,数据可能会被篡改;我们必须权衡此优化带来的性能和安全性权衡)。
今后,我们有望能够更轻松地在 storage-host 中进行更改,这将有助于我们在未来进行性能优化工作。例如,我们最终可能需要引入 I/O 优先级,这需要对整个堆栈进行更改。借助采用我们可以更快地进行工作且使用我们熟悉的语言和环境的整合型代码库,存储团队可以更轻松地实现这一点。
向后兼容性
由于我们只修改了树内代码和 API,因此此更改不会带来向后兼容性问题。
安全注意事项
由于 zxcrypt 是一项对安全性敏感的服务,因此重新实现 zxcrypt 需要进行安全审核。
此项变更还将通过以下几种方式增强安全性:
- 驱动程序将采用 Rust 而非 C++ 实现,从而减少内存损坏漏洞。
- 我们将有机会审核并减少对通过 /dev/class/block 或 dev-topological 访问块设备的权限,因为所有这些用法都需要迁移。
需要注意的是,此提案将涉及对现有安全领域进行一些更改。我们可以将托管在同一进程中的组件视为位于单个安全域中;如果其中一个组件遭到入侵,则意味着另一个组件也遭到入侵。通过 FIDL 或其他 IPC 机制相互通信的组件之间具有更高程度的隔离性(这并不意味着它们能免受攻击)。此提案会增加隔离(将存储设备管理与其他共处的驱动程序分开),但我们认为,如果出于性能考虑需要将软件部署在同一位置,则灵活确定适当的隔离边界非常有用。请参阅“设计”部分中的图表。
隐私注意事项
不适用。
测试
现有的 fshost 测试套件为我们验证这些更改提供了坚实的基础,因为这些测试会演练检测和绑定到屏蔽设备的端到端流程。
我们还应借此机会开发更稳健的块堆栈测试,以便验证这两种实现之间的行为是否完全相同。
文档
存储空间的较低层级一直以来都缺少文档,这是通过一些高级架构文档改进这一点的好机会。存储空间对最终开发者来说通常是不可见的,但对系统开发者和与存储空间集成的其他 Fuchsia 团队来说却很重要。
缺点、替代方案和未知情况
缺点
在此更改中,我们无法将这些上层驱动程序与最低级别的存储驱动程序共存,因为最低级别的驱动程序将保留在驱动程序框架中。尽管如此,我们认为这不是一个问题,因为我们只移动了存储堆栈在驱动程序框架中的边界,而该边界不太可能包含整个存储堆栈(包括文件系统),因此存储堆栈位于 DF 中的部分与位于 DF 之外的部分之间始终会存在某种边界。
这项变更会使 OOT 存储驱动程序的实现变得更加困难,但我们已经不打算支持这种做法;我们预计,在可预见的未来,所有与存储相关的代码都将保留在树中。
我们最初将使用所有可用格式静态编译 storage-host,而在旧版驱动程序框架中,驱动程序是动态加载的,并且仅包含产品使用的驱动程序。如上所述,我们认为这不是一个问题,如果需要,我们可以稍后通过动态链接来解决。
替代方案
显而易见的替代方案是根本不进行此更改,将上层驱动程序保留在驱动程序框架中。不过,请注意,这并不意味着我们没有工作要做。我们仍需要:
- 将上层分块驱动程序迁移到 DFv2。
- 与驱动程序框架团队合作,设计用于发现和连接到块设备的新接口。
- 将所有客户端都移植到使用此新接口(而不是 dev-topological)。
换句话说,这两种情况下的集成工作具有相同的广度和范围。此方案需要执行的额外工作是在 Rust 中重新实现驱动程序,而不是将其移植到 DFv2。这些驱动程序都不特别复杂,我们大约一周就能够创建出可用的 GPT 原型,因此这笔额外的费用并不高,而且还能带来上述好处。
另一种方法是等待在驱动程序框架中引入基于 Rust 的驱动程序,然后将现有驱动程序移植到 Rust,并将其保留在驱动程序框架中。不过,时间表不允许这样做。Devfs 的弃用目标日期为 2024 年底,并且没有针对稳定支持 Rust 驱动程序的承诺时间表,因此无论如何,我们都必须处理 Devfs 弃用问题;我们不妨同时获得上述存储主机的其他好处。
在先技术和参考文档
不适用。