RFC-0136:Fxfs | |
---|---|
状态 | 已接受 |
领域 |
|
说明 | 新 Fuchsia 文件系统的简要概览。 |
Gerrit 更改 | |
作者 | |
审核人 | |
提交日期(年-月-日) | 2021-09-30 |
审核日期(年-月-日) | 2021-10-13 |
总结
Fxfs 是存储团队正在开发的一种新的 Fuchsia 文件系统。该提案概述了 Fxfs 的动机和更高层次的设计决策。
设计初衷
Fuchsia 现有的可变文件系统 Minfs 存在一些限制,这意味着以后将很难添加我们要添加的功能并实现我们的性能目标。我们正在开发 Fxfs,作为替代 Minfs 以实现这些目标的候选对象。
其中几乎肯定会用到某些功能,例如:
- 完全读/写 mmap 支持
- 支持扩展属性
- 严格的配额强制执行
- 文件系统通知(具体而言,支持
inotify
) - 文件锁定(具体而言,支持
flock
)
我们最终可能还需要其他一些功能:
- 内置加密 - 按文件加密
- 快照支持
- 文件克隆
- 多卷支持
- 压缩支持
我们还有一个目标就是实现与其他操作系统相当的文件系统性能。在所有考虑因素中,这可能是最困难实现的,并且需要花费最多精力。
所有文件系统都需要考虑一些基本的文件系统目标:
- 需要低写入放大率,以最大限度减少闪存磨损。
- 高效利用空间 - 例如,最大限度地减少小文件的浪费。
- 迁移的难易程度。
实现这些目标所需的大部分工作都将由存储堆栈和内核中的其他位置工作涵盖,此处不作介绍;此 RFC 仅重点介绍文件系统实现 Fxfs。
虽然 Fxfs 非常适合在闪存设备上运行,但具体设计不会妨碍它在旋转存储设备上运行,我们预计 Fxfs 也可以在旋转存储设备上运行良好。
利益相关方
教员:
审核者:abarth@google.com、brettw@google.com、palmer@google.com
咨询人员:
咨询了 Fuchsia 的安全团队,咨询了加密相关问题。
社交:
Local Storage 团队对 Fxfs 进行了详细的设计审核。
设计
日志结构化合并树
如需查看日志结构化合并 (LSM) 树的概览,请参阅维基百科。Fxfs 使用 LSM 树提供在 Fxfs 中使用的永久性键值对数据结构。
LSM 树具有一些具有吸引力的属性:
- 写入设备的层是不可变的,这意味着:
- 访问这些图层时无需锁定。
- 限制某些类别的 bug。如果不修改图层,破坏它们的可能性就会更小。
- 可能更易于调试问题 - 如果层未更改,移动部分就会减少。
- 更容易支持压缩,并且可以在后台应用更积极的压缩以节省空间。
- 变更是分批按顺序(而非逐条)写出的,这是闪存更有利的写入模式,可减少写入放大并避免不必要的清空周期。这种写入模式碰巧也适合旋转的存储设备。
- 快照支持会比其他情况更容易。
- 允许将写入操作所需的工作推迟到空闲时间(许多设备具有很长的空闲时间)。在实践中,这意味着可以在设备未使用时进行压缩,在最方便的时候。
- 压缩可以在后台完成,从而省出时间来处理更重要的任务。
- 重写元数据是一项成熟的集成文件系统操作,可以使格式迁移变得更加轻松。在压缩过程中,可以写出完全不同的层格式。
不过,也有一些缺点:
- 更难管理空间 - 所有操作都需要元数据空间,甚至删除,因此需要谨慎预留空间。请注意,此问题会在支持快照的其他文件系统中出现,因此它不是 LSM 树所独有的。
- 存在大量层时,读取速度会减慢。我们可以采用多种缓解措施,例如泛光过滤器,而压缩可以通过合并层来降低成本。
值得注意的是,即使选择 LSM 树,如果我们发现混合方法合理,则仍然可以选择使用可变的持久性层格式。
Fxfs 的 LSM 树实现需要一个合并函数,用于指示如何合并记录。
Fxfs 的内存中层由高效、支持并发的数据结构(当前是一个跳过列表)表示。
对象存储
Fxfs 由对象存储层次结构组成。对象存储提供一个由对象 ID(64 位无符号整数)进行键控的类文件 API。系统支持简单的命名空间功能(例如目录支持或类似功能),但并非强制性要求;可以使用对象存储,然后仅使用对象 ID 来引用对象。对象存储将其元数据保留在永久性键值对数据结构(LSM 树)中。元数据包括典型的文件信息,例如对象大小和映射到设备偏移量的范围。
Fxfs 的 LSM 树使用对象存储来存储其呈现循环依赖关系的持久性层:对象存储使用 LSM 树来存储其元数据,然后 LSM 树使用对象存储来存储其持久性层。要解决此问题,请按层次结构排列对象存储:
根父存储区
根父对象存储由仅在内存中的 LSM 树提供支持,因此不依赖于对象存储。
根父存储区仅包含根存储区和日志文件(稍后介绍)的图层文件和日志文件。请注意,LSM 树仅包含元数据(例如范围信息),并且只有该信息永久驻留在内存中。
根存储区
根存储区使用根父存储区来存储其持久性层文件。
根存储区包含支持文件系统的所有其他对象,例如分配器和超级块使用的对象。
子商店
子存储区使用根存储区来存储其持久性图层文件。它们存储用户数据。可以有多个子存储区,但它们只能将根存储区用作其父存储区,这会将层次结构限制为三个级别。请注意,如果需要,我们可以支持对象存储的更深层次结构。
物体
对象由 64 位无符号整数标识,该整数在存储的存储区中具有唯一性,因此要在文件系统中唯一标识对象,您需要指定存储区。
零是保留的,用于表示无效的对象 ID。
对象支持多个属性,该属性由 64 位属性 ID 编入索引,该属性在对象中具有唯一性。属性具有类似于文件的 API,可以在属性中以任意偏移量读取和写入任意量。最初,Fxfs 只会公开对对象(即文件的内容)单个属性的访问权限。之后,可能会使用其他属性来支持扩展属性。特性可以稀疏。
有两种可写入对象的模式:写入时复制模式(与 Minfs 相同)和覆盖模式。在写入时复制模式下,对已分配的块执行的任何写入都将涉及写入新分配的块,更新元数据以指向新块,然后释放旧块。覆盖模式将覆盖现有块,因此不需要更新相同的元数据。大多数对象将使用写入时复制模式写入,并且此模式是最初向外部用户公开的唯一模式。
日志
Fxfs 的日志有以下两种用途:
- 它支持事务:以原子方式将变更应用于多个文件系统对象。
- 在断电时快速持久保留更改。如果不执行此操作,在内存层刷新之前不会保留任何更改,但出于性能和写入放大的原因,最好尽可能延迟(具体取决于内存限制)。
对内存中数据结构的所有更改都必须通过日志进行。与 Minfs 的日志不同,日志日志是一种逻辑日志,而不是物理日志。这样可以大幅减少日志占用的空间 - 插入记录可以由几个字节表示,而对 Minfs 中的数据结构的更改可能会涉及多个块。
请注意,Fxfs 的日志(例如 Minfs)不包含数据。不过,Fxfs 的日志确实包含对以 COW 模式写入的数据的校验和,并且 Fxfs 会在日志重放期间针对自上次已知设备刷新以来写入的数据验证这些校验和,这意味着 Fxfs 可以检测到已撕裂的 COW 写入,并将文件内容还原为其上次完全写入的版本。Minfs 没有此数据校验和功能。
日志作为单个不断增长的文件存在。变更会流式传输到文件。由于树较为紧凑,因此日记文件开头的部分可以释放,这意味着日志将具有稀疏的前缀。所有对象的大小均为 64 位,并且我们需要支持的写入速率意味着,在我们有生之时,封装便不再是问题。为抵消设备可以随意对写入进行重新排序这一事实,系统会在每 4 KiB 分块的末尾放置一个校验和。重放时,重放将在日志中具有校验和不匹配的第一个分块结束。一个分块的校验和用作下一个分块的种子,这应该防止无意中接受旨在其他偏移的块或来自先前 Fxfs 实例的残留块。
重放日志时,很难了解超级块未涵盖的日志文件的范围。为解决此问题,该日志将使用覆盖操作模式,并预分配范围。在重放期间,日志流中的任何区段都将用于读取日志文件中以后的偏移量。
超级方块
Fxfs 的超级块不仅仅是块,但它们的用途与传统超级块类似,因此使用相同的名称。超级块以对象的形式存在于根对象存储区中。与其他对象不同,它们的第一个范围位于设备上的固定位置(所有其他对象对位置没有限制)。超级块的第一部分包含一个序列化结构,其中包含与其他文件系统上的类似信息(例如块大小、重要对象的对象编号等)。之后,超级块包含(写入超级块时)根父对象存储中包含的所有记录。超级块的写入方式与写入日志的方式相同:每个 4 KiB 块的末尾都有一个校验和。
有两个超级块。在装载期间,选择具有最新序列号的超级块。
装载文件系统包括:
- 读取具有最新序列号的超级块。
- 将日志中的所有变更应用到内存层。
- 读取层信息并初始化 LSM 树持久层。
分配器
分配器使用 LSM 树来存储引用计数区段(这可以有效地视为永久存储 <device-range> => <reference-count>
)。最初,Fxfs 仅支持 1 的引用计数,但 Fxfs 将来有可能支持文件克隆或范围共享,这会导致引用计数不止 1。
目录
对象通过按以下格式存储记录来支持目录存储:<object-id><child-name> => <child-object-id><child-type>
。此信息记录在用于其他对象元数据的同一树中。
压缩
最初,压缩操作需要释放日志中的空间。我们最初会选择合理的政策,并会根据需要对其进行改进;无论我们采取什么措施都与格式无关,因此可以轻松地进行更改。
文件名编码
Fxfs 用于文件名的具体编码目前尚未确定,但请注意,编码选择与 UTF-8(即用于 FIDL 字符串和 Rust 字符串的编码)最低兼容。Fxfs 区分大小写,并且不执行标准化。如果出现兼容性问题,则可能需要更改此配置。
文件系统限制
- 文件大小不得超过
2^63
字节(64 位有符号整数的最大值)。在内部,Fxfs 可以表示最大2^64
字节的文件,但现有文件系统 API 对此有限制(例如,带签名的off_t
类型)。 - 目录大小没有特定的限制,但实际上受磁盘大小和 inode 计数的限制。
- 每个卷可以有大约 2^64 个 inode。在实践中,这更有可能受磁盘大小限制。
- Fxfs 可以支持大约 2^64 个卷。在实践中,这更有可能受磁盘大小和其他资源(例如加载卷时的内存)的限制。
- Fxfs 支持纳秒级时间戳(
2^64
位秒和2^32
位纳秒),它们可以表示很久以前的 UNIX 纪元时间戳。
Fsck
Fxfs 具有 fsck 的基本实现,用于验证分配和对象引用计数。在不久的将来,我们会做更多工作来扩大 fsck 的覆盖范围。
小精灵
Minfs 的一些限制可以指导我们在 Fxfs 设计中选择:
- 多线程支持。Minfs 是单线程的。追溯添加多线程支持比在开始时实现多线程支持要困难得多。Fxfs 是多线程的。
- Minfs 使用物理日志,这很简单,但会导致写入放大。Fxfs 使用逻辑日志。
- Minfs 对目录大小和 inode 数量有硬性限制。Fxfs 的限制主要取决于磁盘的大小。
- 改进 Minfs 的格式非常困难,因为所有更改都必须向后兼容,并且必须仔细考虑迁移。Fxfs 仍然需要迁移,但由于压缩会触发元数据的完全重写,因此某些格式更改会更简单。
实现
Fxfs 的原型设计工作已经开展一段时间了,其树内已存在 (//src/storage/fxfs),并且存在使用它来代替 Minfs 的选项。Minfs 目前仍是默认的可变文件系统。更改默认设置的决定不在本 RFC 的讨论范围之内。
语言
Fxfs 使用 Rust。我们认为 Rust 提供了一些极具吸引力的优势:
- 内存安全 - 崩溃更少,安全问题更少,这在涉及多个线程时特别有用。
- 良好的异步支持 - 文件系统可以受益于高并行性,而 Rust 的异步支持会比其他语言更容易实现这一点。
性能
采用 Fxfs 的一个动机原因是提高可变文件系统的性能。我们用于评估性能的基准是独立开发的。这些基准的精确性不在本 RFC 的讨论范围内。
向后兼容性
Fxfs 最初将作为 Minfs 的替代项存在,但在从 Minfs 迁移时获得有限支持(不适用于生产环境)。
我们可能会考虑在将来支持更强大的迁移。
安全注意事项
最初,Fxfs 可以与 Zxcrypt 搭配使用来进行卷级加密(就像 Minfs 一样),因此在这方面应该不会有任何变化。Fxfs 是用 Rust 开发的,在理论上应该可以降低一些安全风险。
Fxfs 内置加密需要全面考虑安全因素,这不在此 RFC 的讨论范围之内。
隐私注意事项
不适用
测试
Fxfs 使用的综合文件系统测试套件与 Minfs 使用的全套文件系统相同。在适当的情况下,还将存在其他特定于 Fxfs 的单元测试和集成测试。
文档
Fxfs 应该可与 Minfs 互换,因此无需其他外部文档;我们的 API 不会发生变化。此 RFC 本身是记录 Fxfs 概要设计的主要工件。
缺点、替代方案和未知情况
开发新的文件系统是一项重大工作。虽然我们已成功实现了一个在功能和性能方面接近 Minfs 的原型,但在 Fxfs 投入生产环境之前还有一些时间。
在开始此项目之前,我们考虑过其他策略,例如移植现有的开源文件系统,或使用从现有文件系统的设计。以下是一些注意事项:
- Fuchsia 的文件系统接口与其他操作系统截然不同,因此移植非常重要。
- 某些现有的开源文件系统存在许可障碍。
- 这些代码库与大部分 Fuchsia 代码库不一致,而且将来会一直不一致(C++ 不常用,测试做法也不同)。
- 选择现有设计会对格式演变施加一些限制;通过更改格式来支持新功能要求,可能需要创建格式分支,并且/或者通过将更改推送到上游来产生开销。
- 我们需要做的很多性能工作实际上都不是在文件系统实现内完成,因此可以说,开发新文件系统不是实现我们的性能目标的关键路径。
- 现有文件系统具有演变史。重新实现现有设计通常需要选择我们想要支持的功能集,这会导致兼容性有限。
- 考虑到我们最有可能支持迁移,对 Minfs 进行增量更改以到达需要的位置可能需要更长时间。
尽管如此,我们还是选择支持对 F2fs 移植由外部贡献,目的是让 Fuchsia 能够很好地支持多个不同的文件系统。
早期技术和参考资料
LSM 树并不新鲜,广泛用于各种应用,但其在较小规模文件系统中的使用示例有限。
您可以在其他文件系统中找到 Fxfs 设计的其他方面(例如逻辑日志格式和多卷支持)。