RFC-0257: storage-host: 将上层存储驱动程序组件化

RFC-0257:storage-host:将上层存储驱动程序组件化
状态已接受
区域
  • 存储
说明

将上层存储驱动程序(GPT、FVM 等)移至新的存储主机组件。

问题
Gerrit 更改
作者
审核人
提交日期(年-月-日)2024-07-08
审核日期(年-月-日)2024-08-13

摘要

目前,块存储设备是在驱动程序框架中以分层方式实现的。实际与硬件(例如 sdmmc、virtio)交互的低级驱动程序实现内部 fuchsia.hardware.block.driver 协议,而上层驱动程序(例如 GPT、FVM、zxcrypt)与此协议交互,并在这些驱动程序(包括公共 fuchsia.hardware.blockfuchsia.hardware.block.partition 协议)之上提供更高级别的功能。我们建议将所有上层块驱动程序移至新组件 storage-host 中,仅在驱动程序框架中保留低级驱动程序。

设计初衷

此项更改的主要目标是将存储堆栈与 Devfs 和驱动程序框架分离,从而支持驱动程序框架团队移除 Devfs(更广泛地说,是将驱动程序堆栈迁移到驱动程序框架 V2)。

目前,存储堆栈在很大程度上依赖于 Devfs 来发现块设备并以拓扑方式访问块设备。如需移除此依赖项,需要对存储堆栈进行大量重构,以使用替代 Devfs 的机制(在撰写本文档时,该机制尚未设计)。由于需要创建新的 API,并且需要更改存储堆栈才能使用这些 API,因此现在正是重新考虑这些 API 的实际实现位置的好时机。

从某种意义上说,GPT 等上层块设备并不是真正的设备驱动程序;它们不与硬件交互,而是与下层块设备交互。换句话说,驱动程序框架针对的应用场景与这些上层块设备不同。存储堆栈一直以来都是驱动程序框架的一个不寻常的客户端(例如,我们在 Devfs 中大量使用拓扑路径),并且无论是驱动程序框架还是存储团队,似乎都没有任何理由来维持这种不匹配的情况。

除了加快驱动程序框架团队弃用 Devfs 的工作之外,将这些上层块设备迁移到存储主机还带来了许多其他好处:

  • 通过将技术堆栈简化并整合为熟悉的语言 (Rust) 和框架(常规非驱动程序组件),提高存储团队的工程速度,
  • 启用需要跨堆栈工作的性能优化(例如,可能将文件系统与上层块驱动程序并置,或进行 API 更改,例如 I/O 优先级所需的更改),
  • 允许存储团队保持对哪些组件访问块设备的控制,这应由存储政策(而不是对 /dev/class/block 的访问权限)来协调。
  • 我们节省了将这些驱动程序移植到驱动程序框架 V2 的工作。

利益相关方

辅导员

  • hjfreyer@google.com

审核者

  • garratt@google.comm
  • csuter@google.com
  • curtisgalloway@google.com
  • surajmalhotra@google.com

已咨询

  • 我们咨询了驱动程序框架团队。

共同化

此 RFC 在发布之前已与驱动程序框架团队进行过沟通。

要求

此更改必须对 Fuchsia 的其余部分透明;我们预计此工作不会带来任何功能性更改。

此更改不应导致性能、内存或存储空间用量出现任何显著衰退。

设计

存储主机组件将管理物理块设备的分区和嵌套块设备。它将以 Rust 实现。

为便于说明,本文档将介绍具有 GPT 格式化块设备的系统的启动流程。存储主机将能够以类似的方式处理其他分区方案,例如 MBR 和 FVM。

Fshost 目前会监听来自驱动程序框架的块设备,检测它们的格式,并确定要绑定哪个块设备驱动程序。例如,当检测到采用 GPT 格式的设备时,fshost 将启动 gpt 设备驱动程序。fshost 中的更改非常简单:fshost 不再启动 gpt 设备驱动程序,而是将设备交给 storage-host。

storage-host 将从 GPT 中解析出分区表,并为每个分区导出 fuchsia.hardware.block.partition.Partition 服务。这些服务将位于由存储主机导出的 partitions 目录中。此目录可替代 Devfs 中的开发拓扑路径,从而实现对嵌套分区的分层发现和访问。

Fshost 将监控发布到 partitions 中的设备,并执行其常规匹配,这可能包括启动文件系统。嵌套分区是有效的(例如 GPT 中的 FVM),在这种情况下,fshost 只需请求存储宿主装载并取消嵌套指定分区。

文件系统和其他客户端用于执行块 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 上启用存储主机。智能产品需要 FVM 和 zxcrypt;这些将在稍后实现。

我们需要确保客户端可以从 devfs 或 storage-host 临时访问块设备,这很简单,因为我们计划使用类似的面向目录的接口进行发现。

我们将通过 fuchsia.fshost.StorageHost 配置功能来限制存储主机的使用,并在特定产品/主板配置准备好切换时启用它。

所有使用情况都切换完毕并被视为稳定后,我们就可以移除过渡逻辑,完成迁移。

storage-host 将实现为单体式 Rust 二进制文件。如果需要(例如为了减少二进制文件膨胀),我们可以将功能(例如 zxcrypt)分离到库中,并根据需要在每个产品的基础上动态链接它们,但为了简单起见,我们将从单体二进制文件开始。

请注意,由于 storage-host 与基于等效驱动程序框架的实现之间没有功能差异,因此如果出现问题,可以安全地还原这些更改。

请注意,FVM 和 GPT 还附带了各种主机工具。我们不打算将这些内容移植到 Rust,因为它们稳定且独立于驱动程序,目前没有更改它们的动机。

性能

我们认为,对于不依赖 FVM 和 zxcrypt 的配置,此更改对性能的影响为零。虽然文件系统和块设备驱动程序之间现在有一个新组件,但通过一些简单的 API 更改,此组件可以不再发挥作用(例如,存储主机可以向文件系统中介底层块设备的窗口化视图,然后文件系统可以直接与底层块驱动程序通信)。

对于 FVM 和 zxcrypt,storage-host 需要拦截每个请求并在传输过程中对其进行修改,与当前的驱动程序框架实现相比,这会增加延迟时间。对于 FVM,这是将虚拟偏移量映射到物理偏移量所必需的;对于 zxcrypt,这是加密所必需的。

延迟时间之所以会增加,是因为存储主机需要从块 FIFO 中拉取请求,然后在底层 FIFO 中重新排队。请注意,驱动程序框架实现仍需要拦截、修改和重新排队每个请求,但驱动程序到驱动程序的通信通过 Banjo 协议 (fuchsia.hardware.block.driver.Block) 进行,当两个驱动程序位于同一位置时,该协议使用更高效的传输机制。换句话说,延迟是由于通过存储主机进行额外的进程跳转而产生的。

我们认为可以通过以下几种方式解决此问题(这些方式在存储主机上更容易实现或可行):

  • 在 Rust 中引入并发性更容易,因此我们或许可以通过流水线化或并行化工作来提高吞吐量。
  • Blobfs 使用外部解压缩器来提高安全性,这会增加文件读取的往返时间。这可以移至存储主机,并与 I/O 请求同时完成,从而抵消存储主机引入的额外跳数。(如果真是这样,从安全角度来看,Blobfs 也不会更糟,因为 Blobfs 不会假设块设备本身是可信的;从安全角度来看,主要目标是避免进程内解压缩,这可能会危及 Blobfs 进程。其他文件系统(例如 Minfs)不具备 Blobfs 的数据验证能力,因此如果存储主机遭到入侵,可能会被篡改;我们必须权衡此优化的性能和安全性权衡。

展望未来,我们希望能够更轻松地在存储主机中进行更改,这将有助于我们开展未来的性能优化工作。例如,我们可能最终需要引入 I/O 优先级,这将需要更改整个堆栈。通过使用一种语言和环境中的整合代码库,存储团队可以更快地完成这项工作。

向后兼容性

由于我们仅修改了树内代码和 API,因此此变更不存在向后兼容性问题。

安全注意事项

由于 zxcrypt 是一项对安全性要求较高的服务,因此重新实现该服务需要进行安全审核。

除此之外,这项变更还将通过以下几种方式来增强安全性:

  • 驱动程序将使用 Rust 而不是 C++ 实现,从而缓解内存损坏漏洞。
  • 我们将有机会通过 /dev/class/block 或 dev-topological 审核并减少对块设备的访问,因为所有这些用法都需要迁移。

需要注意的一点是,此提案将涉及对当前存在的安全网域进行一些更改。我们可以将托管在同一进程中的组件视为位于同一安全网域中;如果一个组件遭到入侵,则意味着另一个组件也遭到入侵。通过 FIDL 或其他 IPC 机制相互通信的组件彼此之间的隔离程度更高(但这并不意味着它们不会受到攻击)。此提案可提高隔离程度(将存储设备管理与其他共置驱动程序分离),但我们认为,如果出于性能考虑需要共置部分软件,那么灵活地确定适当的隔离边界非常重要。请参阅“设计”部分中的图表。

隐私注意事项

不适用。

测试

现有的 fshost 测试套件为我们验证这些更改奠定了坚实的基础,因为这些测试会执行检测和绑定到块设备的端到端流程。

我们应借此机会开发更强大的块堆栈测试,以便验证两种实现之间的行为是否相同。

文档

存储的较低层级历来缺乏文档,这是一个通过一些高级别架构文档来改进这一情况的好机会。存储空间通常对最终开发者是不透明的,但对于与存储空间集成的系统开发者和其他 Fuchsia 团队来说,存储空间非常重要。

缺点、替代方案和未知因素

缺点

在此次更改中,我们失去了将这些上层驱动程序与最低层存储驱动程序共置的能力,因为最低层驱动程序将正确地保留在驱动程序框架中。不过,我们认为这不是问题,因为我们只是将存储堆栈在驱动程序框架中的边界移到了下方,而该边界不太可能包含整个存储堆栈(包括文件系统),因此在驱动程序框架中运行的存储堆栈部分与未在驱动程序框架中运行的存储堆栈部分之间始终会存在一些边界。

此项更改使得实现 OOT 存储驱动程序变得更加困难,但这本来就不是我们打算支持的功能;我们预计,在可预见的未来,所有与存储相关的代码都将保留在树内。

我们将最初静态编译存储主机与所有可用格式,而在旧的驱动程序框架中,驱动程序是动态加载的,并且仅包含产品使用的驱动程序。如上所述,我们认为这不是问题,如果需要,我们稍后可以通过动态链接来解决。

替代方案

显而易见的替代方案是不进行此更改,将上层驱动程序留在驱动程序框架中。不过请注意,这并不意味着没有工作要做。我们仍需执行以下操作:

  • 将上层块驱动程序迁移到 DFv2。
  • 与驱动程序框架团队合作,设计用于发现和连接到块设备的新界面。
  • 将所有客户端移植为使用此新接口,而不是使用 dev-topological。

换句话说,这两种情况下的集成工作具有相同的广度和范围。此提案所需的额外工作是以 Rust 重新实现驱动程序,而不是将其移植到 DFv2。这些驱动因素都不太复杂,我们在一周左右的时间内就创建了一个可用的 GPT 原型,因此这只是适度的额外成本,并且会带来上述好处。

另一种选择是等待将基于 Rust 的驱动程序引入驱动程序框架,然后将现有驱动程序移植到 Rust,并将其保留在驱动程序框架中。不过,时间表并不一致。Devfs 的弃用目标日期为 2024 年底,并且没有关于稳定支持 Rust 驱动程序的已提交时间表,因此无论如何我们都必须处理 Devfs 弃用问题;我们不妨同时获得上述存储主机带来的其他好处。

在先技术和参考资料

不适用。