BlobFS

BlobFS 是一个内容可寻址文件系统,针对一次写入、经常读取的文件(例如二进制文件和库)进行了优化。在 Fuchsia 上,BlobFS 是适用于所有软件包的存储系统。

装载后,BlobFS 会显示一个包含所有文件(也称为 blob)的逻辑目录:

blob/
 ├── 00aeb9b5652a4adbf630d04a6ca22668f9c8469746f3f175687b3c0ff6699a49
 ├── 01289d3e1d2cdbc7d1b4977210877c5bbdffdbad463d992badc149152962a205
 ├── 018951bcf92091fd5d294cbd1f3a48d6ca59be7759587f28077b2eb754b437c0
 └── 01bad8536a7aee498ffd323f53e06232b8a81edd507ac2a95bd0e819c4983138

BlobFS 中的文件包括:

  • 不可变:blob 一经创建便无法修改(移除除外)。
  • Content-Addressable:Blob 名称确定性地从其内容派生。
  • 已验证:加密校验和用于确保 blob 数据的完整性。

blob 的这些属性使得 BlobfS 成为 Fuchsia 安全状况的关键组件,可确保软件包的内容在执行之前进行验证。

BlobFS 的设计和实现

磁盘格式

BlobFS 将每个 blob 存储在非相邻范围(连续的数据块范围)的链接列表中。每个 blob 都有一个关联的 Inode,用于描述块的数据在磁盘上开始存储的位置以及有关 blob 的其他元数据。

BlobFS 将一个磁盘(或其分区)划分为五个区块:

  • 用于存储文件系统级元数据的超级块
  • 块映射,用于跟踪可用数据块和已分配数据块的位图,
  • 节点映射:Inode(对 blob 的数据在磁盘上开始的位置的引用)或 ExtentContainers(对包含 blob 某些数据的若干区的引用)。
  • Journal:一个文件系统操作日志,即使在操作期间设备重新启动或断电,也能确保文件系统的完整性;
  • 数据块:blob 内容及其验证元数据存储在一系列区中。

BlobFS 磁盘布局

图 1:BlobFS 磁盘布局

超级方块

超块是 BlobFS 格式的分区中的第一个块。它描述了文件系统中其他区块的位置和大小,以及其他文件系统级元数据。

装载 BlobFS 格式的文件系统时,此块会映射到内存中并解析,以确定文件系统的其余部分位于何处。每当创建新的 blob 时,以及当 BlobFS 文件系统的大小缩小或增加时(对于 FVM 管理的 BlobFS 实例),该分块都会修改。

BlobFS 超级块

图 2:BlobFS 超级块

当 BlobFS 由 FVM 管理时,超级块包含一些额外的元数据,用于描述包含 BlobFS 文件系统的 FVM 切片。对于非 FVM、固定大小的 BlobFS 映像,这些字段(上图中的黄色)会被忽略。

屏蔽地图

块映射是一个简单的位图,可将每个数据块标记为“已分配”或“未分配”。在块分配期间,此映射用于查找连续的块范围(称为范围),用于存储 blob 内容。

块映射示例

图 3:包含几个不同大小的自由区的块图示例。

装载 BlobFS 映像时,块映射会映射到内存中,可供块分配器读取。每当分配(在 blob 创建期间)或取消分配(在 blob 删除期间)块时,块映射都会写回磁盘。

节点映射

节点映射是文件系统上所有节点的数组,可能有两种变体:

  • Inode:描述文件系统上的单个 blob,或者
  • ExtentContainers,指向包含部分 blob 数据的程度。

这两种类型的节点一起存储在一个平面数组中。每个节点都有一个公共标头,用于说明节点的类型以及是否已分配该节点。这两种节点类型的大小相同,因此不存在数组的内部碎片。

节点 (Inode)

文件系统中的每个 blob 都有一个对应的 Inode,用于描述 blob 的数据开始位置以及关于 blob 的一些其他元数据。

BlobFS Inode 的布局

图 4:BlobFS Inode 的布局。

对于小型 blob,Inode 可能是描述 blob 在磁盘上位置所必需的唯一节点。在这种情况下,extent_count 为 1,不得使用 next_node,而 inline_extent 描述 blob 的单个范围。

较大的 blob 可能会占用多个区段,尤其是在碎片化 BlobFS 映像上。在这种情况下,blob 的第一个范围存储在 inline_extent 中,所有后续范围存储在从 next_node. 开始的 ExtentContainers 链接列表中

范围的格式

图 5:范围的格式(占用 64 位)。Inode 和 ExtentContainers 都会使用这种格式。

请注意,范围的这种表示法意味着一个范围最多只能包含 2**16 个块(范围大小的最大值)。

ExtentContainers

ExtentContainer 保存对多个(最多 6 个)范围的引用,这些范围用于存储 blob 的一些内容。

ExtentContainer 中的区段在逻辑上是连续的(即,extents[0] 中存储的 blob 的逻辑可寻址分块位于 extents[1] 之前),并且按顺序填充。如果设置了 next_node,则 ExtentContainer 必须已满。

BlobFS ExtentContainer 的布局

图 6:BlobFS ExtentContainer 的布局。

节点链接列表的属性

blob 的范围保存在单个 Inode(包含第一个范围)和零个或多个 ExtentContainer(每个范围最多包含 6 个范围)的链接列表中。

此关联列表具有以下属性。违反其中任何属性都会导致 blobfs 将 blob 视为损坏。

  • 区间在逻辑上是连续的:
    • 如果节点 A 在列表中的节点 B 之前,则节点 A 中的所有范围在 blob 的内容中具有较低的逻辑偏移量。
    • 在指定的 ExtentContainer 中,对于范围 x 和 y,如果 x < y,则范围 x 到 blob 内容的逻辑偏移量低于范围 y。
  • 在关联新节点之前,系统会先打包节点。也就是说,如果某个节点的 next_node 为非 null,则该节点必须完整地包含区间(*对于 Inodes,范围为 6 个,ExtentContainers 的范围为 6 个)。
  • 链接列表中的范围总数必须等于 Inode 的 extent_count
  • 链接列表中所有范围的大小总和必须等于 Inode 的 block_count
  • 列表末尾根据满足 Inode 中的 extent_count 确定。不应使用最后一个节点中的 next_node
节点布局示例

本部分包含一些示例,介绍了 blob 节点的格式设置方式。

示例:单范围 blob

示例:单范围 blob

图 7:存储在单个区段中的 blob 的节点布局

示例:多范围 blob

示例:多范围 blob

图 8:存储在多个区段中的 blob 的节点布局。请注意,blob 的范围可能分散在整个磁盘中。

Blob 碎片化

新创建的 BlobFS 映像中的所有数据块均已释放。可以轻松找到任意大小的范围,而且 blob 往往存储在单个较大范围(或几个大范围)中。

随着时间的推移,随着 blob 的分配和取消分配,块映射将碎片成许多更小的区段。新创建的 blob 必须存储在多个较小的区域中。

碎片化的块地图

图 9:碎片化的块映射。虽然有大量可用块,但可用的块却很少。

由于以下几种原因,碎片化是不可取的:

  • 读取速度更慢:读取碎片化 blob 需要跟踪节点映射中的指针。这会影响顺序读取和随机访问读取
  • 创建和删除速度较慢:创建 blob 需要为其找到可用范围;如果必须找到许多小范围,此过程需要更长的时间。同样,删除碎片化 blob 需要追踪并释放许多空间。
  • 元数据开销:存储碎片化 blob 需要更多节点。节点映射中的节点数量有限,可能会耗尽,从而阻止创建 blob。

目前,BlobFS 不执行碎片整理。

日志

TODO

数据块

最后,blob 的实际内容必须存储在某个地方。BlobFS 映像中的其余存储块就是用于此目的。

每个 blob 都会分配足够的区段来包含其所有数据,以及预留的数据块,用于存储 blob 的验证元数据。这些元数据始终存储在 blob 的第一个块中。元数据将填充,以便实际数据始终从与块对齐的地址开始。

此验证元数据称为 Merkle 树,一种使用加密哈希来保证 blob 内容的完整性的数据结构。

Merkle 树

blob 的 Merkle 树结构如下(如需了解详情,请参阅 Fuchsia Merkle Roots):

  • 每个叶节点都是单个块数据的 sha256 哈希值。
  • 每个非叶节点都是一个 sha256 哈希,它会将其子节点的哈希组合起来。
  • 树会在具有单个 sha256 哈希的级别终止。

最顶层节点的哈希值称为 blob 的 Merkle 根。 此值用作 blob 的名称。

简化示例 Merkle 树

图 10:一个简化的示例 Merkle 树。请注意,在实践中,每个哈希值中都包含更多信息(例如块偏移量和长度),并且每个非叶节点会更宽(具体来说,每个非叶节点最多可以包含 8192 / 32 == 256 个子节点)。

BlobFS 的实现

与其他 Fuchsia 文件系统一样,BlobFS 是作为用户空间进程实现的,该进程通过 FIDL 接口提供客户端。