BlobFS是针对一次写入、经常读取的文件优化的内容可寻址的文件系统,例如二进制和库文件。在Fuchsia的文件系统中,BlobFS 是一种应用于所有软件包的存储系统。
装载后,BlobFS显示包含所有文件的单个逻辑目录(a.k.a., blobs):
blob/
├── 00aeb9b5652a4adbf630d04a6ca22668f9c8469746f3f175687b3c0ff6699a49
├── 01289d3e1d2cdbc7d1b4977210877c5bbdffdbad463d992badc149152962a205
├── 018951bcf92091fd5d294cbd1f3a48d6ca59be7759587f28077b2eb754b437c0
└── 01bad8536a7aee498ffd323f53e06232b8a81edd507ac2a95bd0e819c4983138
BlobFS 中的文件:
- Immutable:创建后,Blob不可以被修改(除了移动)
- Content-Addressable:Blob命名来源于内容
- Verified:加密校验和确保数据的一致性
BlobFS中文件的这些特性是Fuchsia安全性的一个关键组成部分,它能确保程序在启动后软件包不被篡改。
BlobFS的设计与实现
磁盘格式化
BlobFS将每个Blob存储在非相邻盘区(一个连续的数据块区间)的链表中。每一个blob都有一个关联的Inode,Inode描述数据块在磁盘的开始位置和blob的一些其他元数据。
BlobFS将一个磁盘(或其分区)划分为五个区块:
- Superblock 存储整个文件系统的元数据区块
- Block Map 存放用于寻址和分配数据块的位图的区块
- Node Map 存放Inode(引用BLOB数据在磁盘上的起始位置)或ExtentContainers(引用包含某些BLOB数据的多个分区)的二维数组的区块
- Journal 文件系统操作日志区块,即使设备在操作期间重新启动或断电,也可确保文件系统的完整性
- Data Blocks 存储连续BLOB数据的和校验的元数据的区块。
1:BlobFS的磁盘布局
Superblock
超级块是BlobFS格式化分区中的第一个块。它描述了文件系统的其他块的位置和大小,以及其他文件系统级元数据。
当BlobFS格式化的文件系统被挂载,该块将会映射到内存中然后进行分析以确定文件系统其余部分的位置。当一个新的blob被被创建时和BlobFS文件系统的大小缩小或增大时,该块会被更新。
2:BlobFS 超级块
当BlobFS由FVM管理时,超级数据块包含一些描述包含BlobFS文件系统的FVM切片的附加元数据。对于非FVM、固定大小的BlobFS映像,这些字段(上图中为黄色)将被忽略。
Block map
Block map 是一个简单的位图,它标明每一个数据块是否分配。此映射在块分配期间用于查找连续的块范围(称为extents),以在其中存储BLOB内容
3:具有多个大小不同的空闲分区的位图示例
挂载BlobFS映像时,位图会映射到内存中,块分配器可以在内存中读取它。每当分配块(在BLOB创建期间)或释放块(在BLOB删除期间)时,位图都会写回磁盘。
Node map
Node map 是文件系统上所有节点的数组,它可以分成两个部分:
- Inodes 它们描述了文件系统上的单个BLOB
- ExtentContainers 它指向包含BLOB数据部分的范围
节点这两种类型一起存储在一个二维数组。每一个节点都有一个共同的头它描述了节点的类型,以及节点是否被分配。两种类型的节点都是相同的大小,因此没有内部碎片。
Inodes
文件系统中的每个BLOB都有一个相应的Inode,它描述了BLOB的数据起始位置以及关于该BLOB的其他一些元数据。
4:BlobFS的Inode的布局
对于较小的BLOB,Inode可能是描述Blob在磁盘上的位置所必需的唯一节点。当extout_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分区在逻辑上是连续的并按顺序填写(即,存储在盘区[0]中的BLOB的逻辑可寻址块在盘区[1]之前)。如果next_node
被设置了,那么ExtentContainer一定就是空的。
6.BlobFS的ExtentContainer布局
节点链表的属性
BLOB的范围保存在单个Inode(其中包含第一个分区)的链表中和零个或多个ExtentContainer(每个最多可容纳6个数据区)
该链表具有以下特性,违反这些属性中的任何一个都会导致BlobFS将该BLOB视为已损坏
- 扩展区在逻辑上是连续的:
- 如果列表中节点A在节点B之前,则节点A中的所有区段在Blob内容中具有较低的逻辑偏移量
- 在ExtentContainer中,对于扩展区𝑥和𝑦,如果𝑥 < 𝑦,x中的所有区段在Blob内容中相对于y具有较低的逻辑偏移量
- 在新节点被链接之前先打包。那是因为,如果一个节点是非空的
next_node
,那么它一定占用了整个分区(ExtentContainers的分区是Inodes的分区的6倍) - 链表上的所有分区的总大小必须和Inode的
block_count
相等 - 链表的末尾是根据满足的inode中的
EXTEND_COUNt
确定的。next_node
在最后一个未使用的节点中
节点布局例子
本节包含设置BLOB节点格式的不同方式的一些示例
Example: 单分区 blob {: #example-single-extent-blob }
7:blob存储在单分区的节点布局
Example: 多分区 blob {: #example-multiple-extent-blob }
8:blob存储在多分区的节点布局,请注意,BLOB的范围可能分散在整个磁盘上。
Blob 分段存储
新创建的BlobFS映像的所有数据块都是空闲的。可以很容易地找到任意大小的段,并且BLOB往往存储在单个较大的段(或几个较大的段)中。
随着时间的推移,随着Blob的分配和释放,块映射将变得碎片化,即许多较小的段。新创建的BLOB必须存储在多个较小的范围中。
9:分散的块,虽然存在大量的空闲块,但是可用的大块区却很少
不采用分段,原因有几个:
- 读速度慢: 读取分段的blob需要在节点映射中跟踪指针. 这会影响顺序读取和随机访问读取
- 创建和删除速度慢: 创建一个blob需要找到空闲的分区; 如果大量的小分区需要被访问将花费大量时间
相同的, 删除分散的blob需要遍历和释放许多分区 - 元数据冗余: 存储分散的blob需要很多节点,但是节点的数量时有限的,当他被耗尽时将会无法创建blob
目前BlobFS不支持分段
Journal
TODO
Data blocks
BLOB的实际内容必须存储在某个地方。BlobFS映像中的其余存储块指定用于此目的。
每个BLOB都分配了足够的区段来包含其所有数据,以及保留用于存储BLOB的验证元数据的多个数据块。此元数据始终存储在BLOB的第一个块中。元数据会被填充,因此实际数据始终从块对齐的地址开始。
该验证元数据称为Merkle Tree,一种数据结构,它使用加密哈希来保证Blob内容的完整性。
Merkle tree
blob的Merkle tree构造如下,见Fuchsia Merkle Roots:
- 每个叶子节点都是单个数据块的sha256散列.
- 每个非叶节点都是一个sha256散列,它组合了它的子节点的散列.
- 该树终止于存在单个sha256散列的级别.
最顶端节点的哈希值称为BLOB的Merkle Root。
该值用于对blob进行命名。
10:一个简化的Merkle tree示例。请注意,在实践中,每个散列值中包含了更多信息(如块偏移量和长度),并且每个非叶节点都要宽得多(特别是,每个非叶节点最多可以包含8192/32==256个子节点)。
BlobFS的实现
与Fuchsia其他文件系统一样,BlobFS通过实现FIDL接口的用户空间进程为客户端提供服务。
Comments