文件系统架构

本文档旨在概括介绍 Fuchsia 文件系统,包括其初始化、对标准文件系统操作(例如打开、读取、写入等)的讨论,以及实现用户空间文件系统的怪异行为。此外,本文档还介绍了在 VFS 级遍历命名空间,该命名空间可用于与非存储实体(例如系统服务)通信。

文件系统就是 Service

与更常见的单体式内核不同,Fuchsia 的文件系统完全位于用户空间内。它们不会链接到内核,也不会通过内核加载;它们只是用户空间进程,它们实现了可以显示为文件系统的服务器。因此,Fuchsia 的文件系统本身可以轻松地进行更改,修改不需要重新编译内核。

文件系统方框图

图 1:典型的文件系统进程块图。

与 Fuchsia 上的其他原生服务器一样,与文件系统服务器交互的主要模式是使用句柄原语(而非系统调用)实现的。内核对文件、目录或文件系统一无所知。因此,文件系统客户端无法直接请求内核进行“文件系统访问”。

此架构意味着与文件系统的交互仅限于以下接口:

  • 在与文件系统服务器建立的通信通道上发送的消息。这些通信通道可以是客户端文件系统的本地信道,也可以是远程信道。
  • 初始化例程(预计要针对每个文件系统进行大量配置;网络文件系统需要网络访问权限,永久性文件系统可能需要块设备访问,内存中的文件系统只需要一种机制来分配新的临时页面)。

此接口有一项优势,即为文件或目录实现预期的协议,任何可通过通道访问的资源都可以让自己看起来像文件系统。capabilities

文件生命周期

建立连接

为了打开文件,Fucsia 程序(客户端)会使用 FIDL 向文件系统服务器发送 RPC 请求。

FIDL 定义了在文件系统客户端和服务器之间传输消息和句柄的传输格式。Fuchsia 不会与内核实现的 VFS 层交互,而是将请求发送到为文件、目录和设备实现协议的文件系统服务。如需发送其中一个待处理的请求,Fuchsia 进程必须通过现有句柄将 RPC 消息传输到目录;如需详细了解此过程,请参阅打开文档的生命周期

命名空间

在 Fuchsia 上,命名空间是完全存在于客户端中的小型文件系统。从最基本的层面来讲,客户端将“/”保存为根并将句柄与其关联是一个非常原始的命名空间。您可以为 Fuchsia 进程提供任意目录句柄来表示“根”,而不是使用典型的单个“全局”文件系统命名空间,从而限制其命名空间的范围。为了限制此范围,Fuchsia 文件系统有意不允许通过 dotdot 访问父级目录

Fuchsia 进程可能还会将某些路径操作重定向到单独的文件系统服务器。当客户端引用“/bin”时,客户端可以选择将这些请求重定向到表示“/bin”目录的本地句柄,而不是直接将请求发送到“根目录”中的“bin”目录。与所有文件系统结构一样,命名空间在内核中是不可见的:它们是在客户端运行时(如 libfdio)中实现的,并插入在大多数客户端代码和远程文件系统的句柄之间。

由于命名空间基于句柄运行,并且大多数 Fuchsia 资源和服务都可以通过句柄访问,因此它们是一个非常强大的概念。文件系统对象(例如目录和文件)、服务、设备、软件包和环境(对特权进程可见)都可通过句柄使用,并且可以在子进程中任意组合。因此,命名空间支持在应用中自定义资源发现。一个进程在“/svc”中观察到的服务不一定与其他进程所看到的服务一致,并可根据应用启动政策加以限制或重定向。

如需详细了解用于限制进程功能的机制和政策,请参阅有关沙盒的文档。

传递数据

与文件、目录、设备或服务建立连接后,后续操作也会使用 RPC 消息传输。这些消息使用服务器验证和理解的有线格式,在一个或多个句柄上传输。

对于文件、目录、设备和服务,这些操作使用 FIDL 协议。

例如,如需在文件内搜寻,客户端将发送一条 Seek 消息,其中包含所需位置和 FIDL 消息中的“whence”,然后返回新的跳转位置。如需截断文件,可随所需的新文件系统发送 Truncate 消息,并返回状态消息。如需读取目录,可以发送 ReadDirents 消息并返回目录列表。如果这些请求被发送到无法处理它们的文件系统实体,则系统将发送错误,也不会执行相应操作(例如向文本文件发送 ReadDirents 消息)。

内存映射

对于能够支持内存映射文件的文件系统,内存映射文件稍微复杂一些。客户端要实际对文件的一部分进行“mmap”,发送一条“GetVmo”消息,并接收虚拟内存对象 (VMO) 作为响应。然后,此对象通常使用虚拟内存地址区域 (VMAR) 映射到客户端的地址空间。将文件内部“VMO”的有限视图传输回客户端需要中间消息传递层进行额外的工作,因此它们可以知道它们正在传回服务器供应商提供的对象句柄。

通过传回这些虚拟内存对象,客户端可以快速访问表示文件的内部字节,而不会实际承担往返 IPC 消息所产生的开销。对于尝试进行文件系统交互高吞吐量的客户端,此功能使得 mmap 成为一个有吸引力的选项。

可对路径执行的其他操作

除了“开放”操作之外,还有一些其他基于路径的操作值得探讨:“重命名”和“链接”。与“打开”不同,这些操作实际上会同时作用于多条路径,而不是单个位置。这会使它们的用法变得复杂:如果调用“rename(‘/foo/bar’,‘baz’)”,文件系统需要确定一种方法来:

  • 遍历两条路径,即使起点不同(此处就是如此;一条路径从根目录开始,另一条从 CWD 开始)
  • 打开这两个路径的父级目录
  • 同时对父目录和尾随路径名进行操作

为了满足这种行为,VFS 层采用了名为“Cookie”的 Zircon 概念。这些 Cookie 允许客户端操作使用句柄在服务器上存储打开状态,并在以后使用相同的句柄引用此行为。Fuchsia 文件系统使用此功能引用一个 Vnode,同时对另一个 Vnode 执行操作。

这些多路径操作执行以下操作:

  • 打开父源 vnode(对于“/foo/bar”,表示要打开“/foo”)
  • 打开目标父 vnode(对于“baz”,这意味着打开当前工作目录),并使用操作 GetToken(是文件系统 Cookie 的句柄)获取 vnode 令牌。
  • 向源父 vnode 发送“rename”请求、源路径和目标路径(“bar”和“baz”)以及之前获取的 vnode 令牌。这为文件系统提供了一种安全间接地引用目标 vnode 的机制;如果客户端提供的句柄无效,则内核将拒绝访问 Cookie 的请求,并且服务器可能会返回错误。

文件系统生命周期

安装式

Fshost 负责在系统上装载文件系统。在编写之时,正在进行更改以使文件系统作为组件运行(尽管 fshost 仍将控制这些文件系统的装载)。如果可能,将使用静态路由。请参阅 fuchsia.fs.startup/Startup 协议。

文件系统管理

有一系列的文件系统操作被视为与“管理”相关,其中包括“卸载当前文件系统”。这些操作由 admin.fidl 中的 fs.Admin 接口定义。文件系统会将此服务连同对文件系统根目录的访问权限一起导出。

当前文件系统

由于 Fuchsia 架构的模块化特性,因此向系统添加文件系统非常简单。目前,存在少数文件系统,旨在满足各种截然不同的需求。

MemFS:内存中文件系统

MemFS 用于实现对 /tmp 等临时文件系统的请求,其中文件完全存在于 RAM 中,并且不会传输到底层块设备。此文件系统目前还用于“bootfs”协议。在该协议中,表示文件和目录集合的大型只读 VMO 会在启动时解封装到用户可访问的 Vnode 中(这些文件可在 /boot 中访问)。

MinFS:永久性文件系统

MinFS 是能够永久存储文件的简单的传统文件系统。与 MemFS 一样,它广泛使用了前面提到的 VFS 层,但与 MemFS 不同,它需要一个块设备的额外句柄(在启动时会被传输到新的 MinFS 进程)。为了便于使用,MinFS 还提供了各种工具:“mkfs”用于格式化,“fsck”用于验证,“mount”和“umount”用于从命令行为命名空间增减 MinFS 文件系统。

Blobfs:一个不可变的完整性验证软件包存储文件系统

Blobfs 是一个针对“一次写入,然后只读”签名数据(例如软件包)进行了优化的简单平面文件系统。除了两个小前提条件(文件名是确定性、用于进行完整性验证的文件 Merkle 树根的内容可寻址哈希)和转发文件大小信息(在将 blob 写入到存储空间之前通过调用“ftruncate”识别到 Blobfs),Blobfs 看起来就像一个典型的文件系统。它可以装载和卸载,它似乎包含哈希的单个平面目录,并且可以通过“open”“read”“stat”和“mmap”等操作访问 blob。

前视同听

Fuchsia Volume Manager 是一个“逻辑音量管理器”,可在现有块设备之上提高灵活性。当前功能包括添加、移除、扩展和收缩虚拟分区的功能。为了实现这些功能,FVM 会在内部维护从(虚拟分区、块)到(切片、物理块)的物理到虚拟的映射。为了将维护开销保持在最低限度,它允许分区以称为切片的区块缩减/增大。切片是原生块大小的倍数。除元数据之外,设备其余部分则分成多个切片。每个切片要么是空闲的,要么属于且仅属于一个分区。 如果某个切片属于某个分区,FVM 会保留关于哪个分区正在使用该切片的元数据,以及该分区中该切片的虚拟地址。

FVM 的磁盘布局如下所示(此处声明)。

      +---------------------------------+ <- Physical block 0
      |           metadata              |
      | +-----------------------------+ |
      | |       metadata copy 1       | |
      | |  +------------------------+ | |
      | |  |    superblock          | | |
      | |  +------------------------+ | |
      | |  |    partition table     | | |
      | |  +------------------------+ | |
      | |  | slice allocation table | | |
      | |  +------------------------+ | |
      | +-----------------------------+ | <- Size of metadata is described by
      | |       metadata copy 2       | |    superblock
      | +-----------------------------+ |
      +---------------------------------+ <- Superblock describes start of
      |                                 |    slices
      |             Slice 1             |
      +---------------------------------+
      |                                 |
      |             Slice 2             |
      +---------------------------------+
      |                                 |
      |             Slice 3             |
      +---------------------------------+
      |                                 |

分区表由多个虚拟分区条目 (VPartitionEntry) 组成。除了包含名称和分区标识符之外,每个 vpart 条目还包含为此分区分配的切片的数量。

切片分配表由紧密打包的切片条目 (SliceEntry) 组成。每个条目都包含

  • 分配状态
  • 如果已分配,则
    • 它属于哪个分区
    • 该切片映射到分区中的哪个逻辑切片

可在此处找到 FVM 库。在铺装期间,系统会将某些分区从主机复制到目标。因此,分区和 FVM 文件本身可能会在主机上创建。为此,请点击此处使用主机端实用程序。可以使用 fvm-check 详细验证 FVM 设备/文件的完整性