此页面旨在让用户了解与 ZFS 文件系统相关的源代码的简要概述。它并不是对 ZFS 的介绍,而是假定您已初步熟悉常用术语和定义,并且对文件系统体系结构有了大致的了解。
通常,我们将 ZFS 描述为由三个组件组成:ZPL(ZFS POSIX Layer,ZFS POSIX 层)、DMU(Data Management Unit,数据管理单元)和 SPA(Storage Pool Allocator,存储池分配器)。虽然这是用作概念版的有用示例,但其实际层次还是比较复杂。以下图像给出了一个更详细的概述;单击其中一个区域将显示更详细的描述,并显示指向源代码的链接。
在此图片中,您仍可以看到三个基本层, 但每个层中有许多元素。另外,还显示 zvol 使用者以及管理路径,即 zfs(1M) 和 zpool(1M)。您可以在下文中找到所有这些子系统的简短描述。这并不是确切说明各子系统工作原理的详尽概述。最后一点需要注意的是,源代码是最终文档。我们希望它通俗易懂,注释详细。如有问题,随时欢迎在
ZFS 论坛中发表您的看法。
这些应用程序是基本的应用程序,可通过 POSIX 文件系统 API 单独与 ZFS 交互。实际上,每个应用程序都可归为此类别。系统调用通过通用 OpenSolaris VFS 层传递到 ZPL。
ZFS 提供了一种创建“仿真卷”的方式。这些卷利用存储池中的存储进行备份,但在 /dev 下显示为一个普通设备。这不是典型的用例,不过有一些用例充分说明了这种功能的用处。有少量应用程序直接与这些设备交互,但大多数普通使用者是位于设备层之上的内核文件系统或目标驱动程序。
Solaris 在内部版本 28 中提供了基于 Web 的 ZFS GUI。虽然还不是 OpenSolaris 的组成部分,但它是位于 JNI 层之上基于 Java 的 GUI 的一个示例。
这些应用程序是处理 ZFS 文件系统或存储池(包括检查属性和数据集分层结构)的应用程序。在出现一些分散的异常(zoneadm、zoneadmd、fstyp)时,两个主要的应用程序是 zpool(1M) 和 zfs(1M)。
zpool(1M)
此命令负责创建和管理 ZFS 存储池。其主要目的是分析命令行输入,将其转换为 libzfs 调用,并顺带处理出现的任何错误。此命令的源代码可在
usr/src/cmd/zpool 中找到。它包含以下文件:
zfs(1M)
此命令负责创建和管理 ZFS 文件系统。与 zpool(1M) 类似,其目的实际上只是分析命令行参数并将处理结果传递到 libzfs。此命令的源代码可在
usr/src/cmd/zfs 中找到。它包含以下文件:
此库提供了一个连接 libzfs 的 Java 接口。它当前是一个专用接口,专门为 GUI 定制。因此,它主要用于观察,因为 GUI 通过 CLI 执行大多数操作。此库的源代码可在
usr/src/lib/libzfs_jni 中找到。
它是管理应用程序与 ZFS 内核模块交互时所用的主要接口。此库为访问和处理存储池和文件系统提供了一种统一的、基于对象的机制。用于与内核通信的基础机制是通过 /dev/zfs 调用 ioctl(2)。此库的源代码可以在
usr/src/lib/libzfs 中找到。它包含以下文件:
ZPL 是作为文件系统与 ZFS 交互的主要接口。它是一个薄层(相对来说),位于 DMU 顶部,并提供文件和目录的文件系统抽象。它负责连接 OpenSolaris VFS 接口和基础 DMU 接口。它还负责强制执行 ACL(Access Control List,访问控制列表)规则和同步 (O_DSYNC) 语义。
ZFS 具有提供原始设备的能力,这些原始设备利用存储池中的空间进行备份。这在源代码中称作 'zvol',在 ZFS 源代码中通过单个文件实现。
此设备是 libzfs 的主要控制点。当使用者可以直接使用 ioctl(2) 接口时,它与 libzfs 紧密结合,并且它不是公共接口(libzfs 也不是公共接口)。它由单个文件组成,该文件会对 ioctl() 参数进行某些验证,然后将请求转发到 ZFS 中的适当位置。
DMU 负责提供事务对象模型,这些模型构建在 SPA 提供的单层地址空间之上。使用者可通过对象集、对象和事务与 DMU 交互。对象集是对象的集合,其中的每个对象都是 SPA 中的任意一个存储单元。每个事务是必须以组的形式向磁盘提交的一系列操作;其主要作用是为 ZFS 提供盘上一致性。
通过继承的属性以及配额和预留执行,DSL 可将 DMU 对象集聚合到一个分层的名称空间中。它还负责管理对象集的快照和克隆。
有关如何实现快照的更多信息,请参见
Matt 的博客文章。
ZAP 构建在 DMU 之上,它使用可伸缩哈希算法在对象集内创建任意(名称、对象)关联。它最常用于在 ZPL 内实现目录,但也广泛用于整个 DSL 内,它还是对存储池范围的属性进行存储的方法。有两种不同的 ZAP 算法,专为不同类型的目录而设计。当条目数相对较小且每个条目比较短时,使用 "micro zap"。对于较大目录或具有极长名称的目录,则使用 "fat zap"。
虽然 ZFS 在磁盘上提供始终一致的数据,但它遵循传统文件系统语义,根据这些语义,大部分数据不会立即写入磁盘,否则性能将下降。但是,有些应用程序要求更严格的语义,以保证在 read(2) 或 write(2) 调用返回时数据已在磁盘上。对于要求此行为(用 O_DSYNC 指定)的应用程序,ZIL 使用有效的、基于每个数据集的事务日志提供必要的语义,该事务日志可在出现崩溃时重放。
有关 ZIL 实现的更多详细信息,请参见 Neil 的博客文章。
遍历为在活动存储池中浏览所有数据提供了一种安全、有效、可重新开始的方法。它是重新同步和清理操作的基础。它可以在所有元数据中查找在某一期间内修改过的块。由于使用 ZFS 的写复制功能,它具有快速排除在故障期间未访问过的较大子树的优点。它本质上是一个 SPA 工具,但有某些 DMU 结构的锁定功能,以便能够处理快照、克隆以及盘上格式的某些其他特性。
ZFS 使用自适应替换缓存的修改版本来满足其主要缓存需求。此缓存在 DMU 和 SPA 之间分层,因而是在虚拟块级别上进行操作。这样,文件系统可与其快照和克隆共享其缓存数据。
虽然整个存储池层经常被称作 SPA(Storage Pool Allocator,存储池分配器),但配置部分实际上是公共接口。它负责将 ZIO 和 vdev 层结合成一个一致的存储池对象。它包含一些例程,这些例程可根据其配置信息创建和销毁存储池,并定期将数据与 vdev 同步。
ZIO 管道是读取或写入磁盘时传递所有数据必经的地方。它负责将 DVA(Device Virtual Address,设备虚拟地址)转换成 vdev 上的逻辑位置,并在必要时对数据进行校验和以及压缩。它以多阶段管道的方式实现,并使用位掩码控制对每个 I/O 执行哪个阶段。管道本身很复杂,但可以通过以下图表作概括说明:
| I/O 类型 |
ZIO 状态 |
压缩 |
校验和 |
组块
| DVA 管理 |
Vdev I/O |
| RWFCI | 打开 | | | | | |
| RWFCI | 等待 子项就绪 | | | | | |
| -W--- | | 写入压缩 | | | | |
| -W--- | | | 校验和生成 | | | |
| -WFC- | | | | 组管道 | | |
| -WFC- | | | | 获取组头 | | |
| -W--- | | | | 重写组 头 | | |
| --F-- | | | | 释放组 成员 | | |
| ---C- | | | | 声明组 成员 | | |
| -W--- | | | | | DVA 分配 | |
| --F-- | | | | | DVA 释放 | |
| ---C- | | | | | DVA 声明 | |
| -W--- | | | 组校验和 生成 | | | |
| RWFCI | 就绪 | | | | | |
| RW--I | | | | | | I/O 开始 |
| RW--I | | | | | | I/O 完成 |
| RW--I | | | | | | I/O 访问 |
| RWFCI | 等待 子项完成 | | | | | |
| R---- | | | 校验和验证 | | | |
| R---- | | | | 读取组 成员 | | |
| R---- | | 读取解压缩 | | | | |
| RWFCI | 完成 | | | | | |
- I/O 类型
- I/O 管道的每个阶段应用于特定类型的 I/O。字母代表的含义:(R) 表示读取、(W) 表示写入、(F) 表示释放、(C) 表示声明、(I) 表示 loctl。
- ZIO 状态
- 这些状态是用于同步 I/O 的内部状态。例如,具有子 I/O 的 I/O 必须在分配 BP 之前等待所有子项准备就绪,而且必须在返回之前等待所有子项完成。
- 压缩
- 此阶段应用任何压缩算法(如果适用)。
- 校验和
- 此阶段应用任何校验和算法(如果适用)。
- 组块
- 如果没有足够的连续空间可用来写入完整块,ZIO 管道会将 I/O 分成几个更小的“组块”,这些组块可以在以后进行透明汇编以显示为完整块。
- DVA 管理
- 必须为每个 I/O 分配一个 DVA(Device Virtual Address,设备虚拟地址),以与存储池中 vdev 的特定部分相对应。通过使用 metaslab 和空间映射,这些阶段对这些地址执行必要的分配。
- Vdev I/O
- 这些阶段是实际将 I/O 发出到包含在存储池中的 vdev 的阶段。
虚拟设备子系统提供了排列和访问设备的统一方法。虚拟设备可形成一个树结构,其中包含一个根 vdev 以及多个内部(镜像和 RAID-Z)vdev 和叶(磁盘和文件)vdev。每个 vdev 负责表示可用空间,并在物理磁盘上排列块。
在栈底部,ZFS 通过 LDI(即分层驱动程序接口)与基础物理设备交互;处理文件时,还与 VFS 接口交互。 |