此页面旨在让用户了解与 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。< /p>
ZFS 提供了一种创建“仿真卷”的方式。这些卷利用存储池中的存储进行备份,但在 /dev
下显示为一个普通设备。这不是典型的用例,不过有一些用例充分说明了这种功能的用处。有少量应用程序直接与这些设备交互,但大多数普通使用者是位于设备层之上的内核文件系统或目标驱动程序。
Solaris 在内部版本 28 中提供了基于 Web 的 ZFS GUI。虽然还不是 OpenSolaris 的组成部分,但它是位于 JNI 层之上基于 Java 的 GUI 的一个示例。< /p>
这些应用程序是处理 ZFS 文件系统或存储池(包括检查属性和数据集分层结构)的应用程序。在出现一些分散的异常(zoneadm、zoneadmd、fstyp)时,两个主要的应用程序是 zpool(1M) 和 zfs(1M)。
此命令负责创建和管理 ZFS 存储池。其主要目的是分析命令行输入,将其转换为 libzfs 调用,并顺带处理出现的任何错误。此命令的源代码可在 usr/src/cmd/zpool 中找到。它包含以下文件:
zpool_main.c | 批量命令,负责处理所有的参数和子命令 |
zpool_vdev.c | 代码,负责将一系列 vdev 转换成 libzfs 的 nvlist 表示 |
zpool_iter.c | 代码,用于在系统中方便地对一些或全部存储池进行迭代 |
zpool_util.c | 其他实用程序功能 |
此命令负责创建和管理 ZFS 文件系统。与 zpool(1M) 类似,其目的实际上只是分析命令行参数并将处理结果传递到 libzfs。此命令的源代码可在 usr/src/cmd/zfs 中找到。它包含以下文件:
zfs_main.c | 批量命令,负责处理所有的参数和子命令 |
zfs_iter.c | 代码,用于在系统中对一些或全部数据集进行迭代 |
此库提供了一个连接 libzfs 的 Java 接口。它当前是一个专用接口,专门为 GUI 定制。因此,它主要用于观察,因为 GUI 通过 CLI 执行大多数操作。此库的源代码可在 usr/src/lib/libzfs_jni 中找到。
它是管理应用程序与 ZFS 内核模块交互时所用的主要接口。此库为访问和处理存储池和文件系统提供了一种统一的、基于对象的机制。用于与内核通信的基础机制是通过 /dev/zfs 调用 ioctl(2)。此库的源代码可以在 usr/src/lib/libzfs 中找到。它包含以下文件:
libzfs_dataset.c | 用于处理数据集的主要接口 |
libzfs_pool.c | 处理存储池的主要接口 |
libzfs_changelist.c | 在子级中传播属性更改的实用程序例程 |
libzfs_config.c | 读取和处理存储池配置信息 |
libzfs_graph.c | 构造数据集相关列表 |
libzfs_import.c | 搜索和导入存储池 |
libzfs_mount.c | 挂载、取消挂载和共享数据集。 |
libzfs_status.c | 根据存储池状态链接到 FMA 知识库文章 |
libzfs_util.c | 其他例程 |
ZPL 是作为文件系统与 ZFS 交互的主要接口。它是一个薄层(相对来说),位于 DMU 顶部,并提供文件和目录的文件系统抽象。它负责连接 OpenSolaris VFS 接口和基础 DMU 接口。它还负责强制执行 ACL(Access Control List,访问控制列表)规则和同步 (O_DSYNC) 语义。
zfs_vfsops.c | OpenSolaris VFS 接口 |
zfs_vnops.c | OpenSolaris vnode 接口 |
zfs_znode.c | 每个 vnode 对应一个基础 znode |
zfs_dir.c | 目录操作 |
zfs_acl.c | ACL 实现 |
zfs_ctldir.c | 伪文件系统实现。zfs/snapshot |
zfs_log.c | 用于记录意图日志条目的 ZPL 接口 |
zfs_replay.c | 重放意图日志条目的 ZPL 接口 |
zfs_byteswap.c | 用于 ZPL 数据结构的字节交换例程 |
ZFS 具有提供原始设备的能力,这些原始设备利用存储池中的空间进行备份。这在源代码中称作 'zvol',在 ZFS 源代码中通过单个文件实现。
zvol.c | 卷仿真接口 |
此设备是 libzfs 的主要控制点。当使用者可以直接使用 ioctl(2) 接口时,它与 libzfs 紧密结合,并且它不是公共接口(libzfs 也不是公共接口)。它由单个文件组成,该文件会对 ioctl() 参数进行某些验证,然后将请求转发到 ZFS 中的适当位置。
zfs_ioctl.c | /dev/zfs ioctl() 接口 |
zfs.h | 内核和用户空间之间共享的接口 |
DMU 负责提供事务对象模型,这些模型构建在 SPA 提供的单层地址空间之上。使用者可通过对象集、对象和事务与 DMU 交互。对象集是对象的集合,其中的每个对象都是 SPA 中的任意一个存储单元。每个事务是必须以组的形式向磁盘提交的一系列操作;其主要作用是为 ZFS 提供盘上一致性。
dmu.c | 主要的外部 DMU 接口 |
dmu_objset.c | 对象集打开/关闭/处理外部接口 |
dmu_object.c | 对象分配/释放接口 |
txg.c | 事务模型控制线程 |
dmu_tx.c | 事务创建/处理接口 |
dnode.c | 打开上下文对象级处理函数 |
dnode_sync.c | 同步上下文对象级处理函数 |
dbuf.c | 缓冲区管理函数 |
dmu_zfetch.c | 数据流预取逻辑 |
refcount.c | 通用引用计数接口 |
dmu_send.c | 发送和接收快照函数 |
通过继承的属性以及配额和预留执行,DSL 可将 DMU 对象集聚合到一个分层的名称空间中。它还负责管理对象集的快照和克隆。
有关如何实现快照的更多信息,请参见 Matt 的博客文章。
dsl_dir.c | 名称空间管理函数 |
dsl_dataset.c | 快照/回滚/克隆支持接口 |
dsl_pool.c | 存储池级支持接口 |
dsl_prop.c | 属性处理函数 |
unique.c | 用于唯一对象集 ID 的支持函数 |
ZAP 构建在 DMU 之上,它使用可伸缩哈希算法在对象集内创建任意(名称、对象)关联。它最常用于在 ZPL 内实现目录,但也广泛用于整个 DSL 内,它还是对存储池范围的属性进行存储的方法。有两种不同的 ZAP 算法,专为不同类型的目录而设计。当条目数相对较小且每个条目比较短时,使用 "micro zap"。对于较大目录或具有极长名称的目录,则使用 "fat zap"。
zap.c | Fat ZAP 接口 |
zap_leaf.c | 低级别支持函数 |
zap_micro.c | Micro ZAP 接口 |
虽然 ZFS 在磁盘上提供始终一致的数据,但它遵循传统文件系统语义,根据这些语义,大部分数据不会立即写入磁盘,否则性能将下降。但是,有些应用程序要求更严格的语义,以保证在 read(2) 或 write(2) 调用返回时数据已在磁盘上。对于要求此行为(用 O_DSYNC 指定)的应用程序,ZIL 使用有效的、基于每个数据集的事务日志提供必要的语义,该事务日志可在出现崩溃时重放。
有关 ZIL 实现的更多详细信息,请参见 Neil 的 博客文章。
zil.c | 意图日志 |
遍历为在活动存储池中浏览所有数据提供了一种安全、有效、可重新开始的方法。它是重新同步和清理操作的基础。它可以在所有元数据中查找在某一期间内修改过的块。由于使用 ZFS 的写复制功能,它具有快速排除在故障期间未访问过的较大子树的优点。它本质上是一个 SPA 工具,但有某些 DMU 结构的锁定功能,以便能够处理快照、克隆以及盘上格式的某些其他特性。
dmu_traverse.c | 遍历代码 |
ZFS 使用自适应替换缓存的修改版本来满足其主要缓存需求。此缓存在 DMU 和 SPA 之间分层,因而是在虚拟块级别上进行操作。这样,文件系统可与其快照和克隆共享其缓存数据。
arc.c | 自适应替换缓存实现 |
虽然整个存储池层经常被称作 SPA(Storage Pool Allocator,存储池分配器),但配置部分实际上是公共接口。它负责将 ZIO 和 vdev 层结合成一个一致的存储池对象。它包含一些例程,这些例程可根据其配置信息创建和销毁存储池,并定期将数据与 vdev 同步。
spa.c | 打开、导入、导出和销毁存储池的例程 |
spa_misc.c | 其他 SPA 例程,包括锁定 |
spa_config.c | 分析和更新存储池配置数据 |
spa_errlog.c | 持久性存储池范围的数据错误的日志。 |
spa_history.c | 修改存储池状态的命令成功运行的持久性环形缓冲区。 |
zfs_fm.c | 发布 ereport 以占用 FMA 的例程。 |
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 | 完成 |
zio.c | ZIO 主要阶段,包括组块转换 |
zio_checksum.c | 通用校验和接口 |
fletcher.c | Fletcher2 和 Fletcher4 校验和算法 |
sha256.c | SHA256 校验和算法 |
zio_compress.c | 通用压缩接口 |
lzjb.c | LZJB 压缩算法 |
uberblock.c | 基本超级块(根块)例程 |
bplist.c | 跟踪块指针列表 |
metaslab.c | DVA 批量转换 |
space_map.c | 跟踪 DVA 转换期间使用的可用空间 |
szio_inject.c | 用于注入有关数据和设备的持久性错误的框架 |
虚拟设备子系统提供了排列和访问设备的统一方法。虚拟设备可形成一个树结构,其中包含一个根 vdev 以及多个内部(镜像和 RAID-Z)vdev 和叶(磁盘和文件)vdev。每个 vdev 负责表示可用空间,并在物理磁盘上排列块。
vdev.c | 通用 vdev 例程 |
vdev_disk.c | 磁盘虚拟设备 |
vdev_file.c | 文件虚拟设备 |
vdev_mirror.c | N 向镜像 |
vdev_raidz.c | RAID-Z 分组(请参见 Jeff 的博客)。 |
vdev_root.c | 顶级伪 vdev |
vdev_missing.c | 用于导入的特殊设备 |
vdev_label.c | 用于读取和写入标识标签的例程 |
vdev_cache.c | 用于读取的简单设备级缓存 |
vdev_queue.c | 用于 vdev 的 I/O 调度算法 |
在栈底部,ZFS 通过 LDI(即分层驱动程序接口)与基础物理设备交互;处理文件时,还与 VFS 接口交互。