
日志文档系统怎样缩短系统重启时间
假如发生系统崩溃,JFS 提供了快速文档系统重启。通过使用数据库日志技术,JFS 能在几秒或几分钟之内把文档系统恢复到一致状态,而非日志文档系统却要花上几小时甚至几天才能完成。本白皮书对 JFS 体系结构作了概述,并且描述了可在 developerWorks 网站上找到的 JFS 技术的设计特性、潜在限制连同管理实用程式。
日志文档系统 (JFS) 提供了基于日志的字节级文档系统,该文档系统是为面向事务的高性能系统而研发的。他具备可伸缩性和健壮性,和非日志文档系统相比,他的长处是其快速重启能力:JFS 能够在几秒或几分钟内就把文档系统恢复到一致状态。
虽然 JFS 主要是为满足服务器(从单处理器系统到高级多处理器和群集系统)的高吞吐量和可靠性需求而设计的,JFS 还可用于想得到高性能和可靠性的客户机配置。
体系结构和设计
JFS 体系结构可从磁盘布局特性的角度进行说明。
逻辑卷
任何文档系统讨论的基础是某种类型的逻辑卷。这能够是个物理磁盘,或物理磁盘空间的某个子集,例如:一个 FDISK 分区。逻辑卷也称为磁盘分区。
聚集和文档集
文档系统创建实用程式 mkfs,创建了完全包含在分区内的聚集。聚集是包含一种特定格式的磁盘块阵列,其格式包括终极块和分配映射表。终极块将分区标识成 JFS 聚集,而分配映射表描述聚集内每个数据块的分配状态。格式还包括描述他所必需的初始文档集和控制结构。文档集是可安装的实体。
文档、目录、inode 和寻址结构
文档集包含文档和目录。文档和目录由 inode 持续表示;每个 inode 描述文档或目录的属性,并作为查找磁盘上文档或目录数据的起始点。JFS 还使用 inode 来表示其他文档系统对象,如描述文档集中每个 inode 的分配状态和磁盘位置的映射表。
目录将用户特定的名称映射到为文档和目录所分配的 inode 上,并且形成传统的命名层次。文档包含用户数据,用户数据中没有隐含任何限制或格式。也就是说,JFS 将用户数据看成是未解释的字节流。根植于 inode 基于盘区的寻址结构用来将文档数据映射到磁盘。聚集终极块和磁盘分配映射表、文档描述符和 inode 映射表、inode、目录连同寻址结构一起表示了 JFS 控制结构或元数据。
日志
在每个聚集中维护 JFS 日志,并且用来记录元数据的操作信息。日志有一种同样由文档系统创建实用程式配置的格式。聚集内多个安装的文档集能够同时使用一个日志。
设计特性
JFS 从一开始就设计成完全整合了日志记录,而不是在现有文档系统上添加日志记录。JFS 的许多特性使之区分于其他文档系统。
日志处理
JFS 提供了改进的结构化一致性和可恢复性,连同比非日志文档系统(例如:HPFS、ext2 和传统 UNIX 文档系统)快得多的系统重启时间。发生系统故障时非日志文档系统容易崩溃,是由于一个逻辑写文档操作通常占用多个媒体 I/O 来完成,且在任何给定时间,可能没有完全反映在媒体上。这些文档系统依靠重启实用程式(也就是 fsck),fsck 检查文档系统的任何元数据(例如:目录和磁盘寻址结构)以检测和修复结构完整性问题。这是个耗时并且容易出错的过程,在最糟糕的情况下,他还可能丢失或放错数据。
相反,JFS 使用原来为数据库研发的技术,记录了文档系统元数据上执行的操作(即原子事务)信息。假如发生系统故障,可通过重放日志并对适当的事务应用日志记录,来使文档系统恢复到一致状态。由于重放实用程式只需检查文档系统最近活动所产生的运行记录,而不是检查任何文档系统的元数据,因此,和这种基于日志的方法相关的文档系统恢复时间要快得多。
基于日志恢复的其他几个方面也值得注意。首先,JFS 只记录元数据上的操作,因此,重放这些日志只能恢复文档系统中结构关系和资源分配状态的一致性。他没有记录文档数据,也没有将这些数据恢复到一致状态。因此,恢复后某些文档数据可能丢失或失效,对数据一致性有关键性需求的用户应该使用同步 I/O。
面对媒体出错,日志记录不是特别有效。特别地,在将日志或元数据写入磁盘的期间发生的 I/O 错误,意味着在系统崩溃后,要将文档系统恢复到一致状态,需要耗时并且有可能强加的全面完整性检查。这暗示着,坏块重定位是任何驻留在 JFS 下的存储管理器或设备的一个关键特性。
JFS 日志记录的语义如下:当涉及元数据更改的文档系统操作--例如,unlink()--返回成功执行的返回码时,操作的结果已提交到文档系统,即使系统崩溃了也能够发现。例如,一旦成功删除了文档,即使系统崩溃然后重启,他仍然是删除的并且不会再重新出现。
日志记录风格将同步写入日志磁盘引入每个修改元数据的 inode 或 vfs 操作。(对数据库专家而言,这是一种使用非剥夺缓冲区策略的仅重做的、物理残留映象、提前写的日志记录协议。)在性能方面,和依赖(多个)谨慎的同步元数据写操作以获得一致性的许多非日志文档系统相比,这种方法较好。但是,和其他日志文档系统相比,他在性能上处于劣势。其他日志文档系统,如 Veritas VxFS 和 Transarc Episode,使用不同的日志风格并且缓慢地将日志数据写入磁盘。在执行多个并行操作的服务器环境中,通过将多个同步写操作组合成单一写操作的组提交来减少这种性能损失。JFS 日志记录风格随着时间推移而得到不断改进,现在提供了异步日志记录,异步日志记录提高了文档系统的性能。
基于盘区的寻址结构
JFS 使用基于盘区的寻址结构,连同主动的块分配策略,产生紧凑、高效、可伸缩的结构,以将文档中的逻辑偏移量映射成磁盘上的物理地址。盘区是象一个单元那样分配给文档的相连块序列,可用一个由 <逻辑偏移量,长度,物理地址> 组成的三元组来描述。寻址结构是一棵 B+ 树,该树由盘区描述符(上面提到的三元组)填充,根在 inode 中,键为文档中的逻辑偏移量。
可变的块尺寸
按文档系统分,JFS 支持 512、1024、2048 和 4096 字节的块尺寸,以允许用户根据应用环境优化空间利用率。较小的块尺寸减少了文档和目录中内部存储碎片的数量,空间利用率更高。但是,小块可能会增加路径长度,和使用大的块尺寸相比,小块的块分配活动可能更频繁发生。因为服务器系统通常主要考虑的是性能,而不是空间利用率,所以缺省块尺寸为 4096 字节。
动态磁盘 inode 分配
JFS 按需为磁盘 inode 动态地分配空间,同时释放不再需要的空间。这一支持避开了在文档系统创建期间,为磁盘 inode 保留固定数量空间的传统方法,因此用户不再需要估计文档系统包含的文档和目录最大数目。另外,这一支持使磁盘 inode 和固定磁盘位置分离。
目录组织
JFS 提供两种不同的目录组织。第一种组织用于小目录,并且在目录的 inode 内存储目录内容。这就不再需要不同的目录块 I/O,同时也不再需要分配不同的存储器。最多可有 8 个项可直接存储在 inode 中,这些项不包括自己(.)和父(..)目录项,这两个项存储在 inode 中不同的区域内。
第二种组织用于较大的目录,用按名字键控的 B+ 树表示每个目录。和传统无序的目录组织比较,他提供更快的目录查找、插入和删除能力。
稀疏和密集文档
按文档系统分,JFS 既支持稀疏文档也支持密集文档。
稀疏文档允许把数据写到一个文档的任意位置,而不要将以前未写的中间文档块实例化。所报告的文档大小是已写入的最高块位处,但是,在文档中任何给定块的实际分配,只有在该块进行写操作时才发生。例如,假设在一个指定为稀疏文档的文档系统中创建一个新文档。应用程式将数据块写到文档中第 100 块。尽管磁盘空间只分配了 1 块给他,JFS 将报告该文档的大小为 100 块。假如应用程式下一步读取文档的第 50 块,JFS 将返回填充了 0 的一个字节块。假设应用程式然后将一块数据写到该文档的第 50 块,JFS 仍然报告文档的大小为 100 块,而现在已为他分配了两块磁盘空间。稀疏文档适合需要大的逻辑空间但只使用这个空间的一个(少量)子集的应用程式。
对于密集文档,将分配相当于文档大小的磁盘资源。在上例中,第一个写操作(将一块数据写到文档的第 100 块)将导致把 100 个块的磁盘空间分配给该文档。在任何已隐式写入的块上进行读操作,JFS 将返回填充了 0 的字节块,正如稀疏文档的情况相同。
JFS 内部(潜在)限制
JFS 是完全 64 位的文档系统。任何 JFS 文档系统结构化字段都是 64 位大小。这允许 JFS 同时支持大文档和大分区。
文档系统大小
JFS 支持的最小文档系统是 16M 字节。最大文档系统的大小是文档系统块尺寸和文档系统元数据结构支持的最大块数两者的乘积。JFS 将支持最大文档长度是 512 万亿字节(TB)(块尺寸是 512 字节)到 4 千万亿字节(PB)(块尺寸是 4K 字节)
文档长度
最大文档长度是主机支持的虚拟文档系统最大文档长度。例如:假如主机只支持 32 位,则这就限制了文档长度。
可移动媒体
JFS 不支持把软盘作为基本文档系统设备。
标准管理实用程式
JFS 提供创建和维护文档系统的标准管理实用程式。
创建文档系统
这个实用程式提供 mkfs 命令的 JFS 特定部分,用来在指定的驱动器上初始化 JFS 文档系统。该实用程式在较低级别上操作,并假设文档系统所存在的任何卷的创建/初始化由更高级别的另一个实用程式处理。
检查/修复文档系统
这个实用程式提供 fsck 命令的 JFS 特定部分。该命令检查文档系统的一致性,修复发现的问题。他也重放日志,把提交的改变应用到文档系统元数据,假如由于日志重放而声明文档系统是干净的,就不会再采取进一步操作。假如文档系统不认为是干净的,这意味着由于某种原因没有完整和正确地重放日志,或文档系统不能单靠重放日志来恢复到一致状态,那么,就对文档系统执行一遍完整检查。
当执行全部完整性检查时,检查/修复实用程式首要目的是要达到可靠的文档系统状态,以防止将来文档系统崩溃或故障,第二个目的就是面对崩溃时保存数据。这意味着为了达到文档系统的一致性,实用程式可能丢弃数据。具体而言,当实用程式在不做假设的情况下,无法获得所需信息以将结构上不一致的文档或目录恢复到一致状态时,就会废弃数据。当碰到不一致的文档或目录时,就废弃整个文档或目录,而不再试图保存任何部分。任何由删除受损目录所孤立起来的文档或子目录,都放在文档系统根下的 lost+found 目录中。
文档系统检查/修复实用程式重点考虑的因素之一是所需虚存数量。通常,这些实用程式所需的虚存数量由文档系统的大小决定,这是由于所需虚存主要用于跟踪文档系统中个别块的分配状态。随着文档系统增大,块的数量增多,用来跟踪这些块所需的虚存数量也随之增加。
JFS 检查/修复实用程式的设计区分在于其虚存需求由文档系统中文档和目录的数量(而不是由块的数量)所决定。对 JFS 检查/修复实用程式而言,每个文档或目录的虚存大约为每个文档或目录 32 字节,或对于包含百万个文档和目录的文档系统而言,不论其文档系统大小,虚存需求都是大约 32 兆字节。如同任何其他的文档系统,JFS 实用程式需要跟踪块分配状态,但避免使用虚存方法,而是使用位于实际文档系统中的一小块保留工作区来实现。
日志文档系统怎样处理磁盘布局
本文描述磁盘日志文档系统(JFS)布局,连同使用磁盘布局结构来实现可扩展性、可靠性和性能的机制。还会了解用来操作这些结构的策略和算法,连同 JFS 是在哪里使用遍布文档系统的 B+ 树来提高文档系统操作性能。
JFS 体系结构可通过其磁盘布局特性的上下文进行说明。磁盘布局是 JFS 用来控制文档系统的格式。本文讨论盘区的文档几何构造、目录格式、块分配映射表格式、inode 和布局结构的其他特性。本文还提供了文档布局使用的 B+ 树数据结构的细节和示例。选择 B+ 树是为了提高读写盘区的性能,这是 JFS 执行的最普通操作。
分区、聚集、分配组、文档集
分区
JFS 文档系统建立在分区上,分区是由 FDISK 导出到 JFS 的抽象。
分区有:
固定分区块尺寸,其合法值为 512、1024、2048 或 4096 字节。分区块尺寸定义了分区上支持的最小 I/O 单元。这对应于组成分区的物理设备的基本磁盘扇区大小,最普遍的尺寸是 512 字节。
大小为:PART_NBlocks,是分区磁盘块数。
分区磁盘块的抽象地址空间 [ 0.. PART_NBlocks - 1 ]。
聚集
为了支持 DCE DFS(分布式计算环境分布式文档系统),JFS 将磁盘空间分配池(称为聚集)的概念, 和可安装的文档系统子树(称为文档集)的概念分开。本文中聚集和文档集的术语和其 DFS 用法一致。每个分区刚好只有一个聚集;每个聚集可能有多个文档集。在第一个发行版中,JFS 仅支持每个聚集一个文档集;但是,任何元数据都已设计成适用于任何情况。
聚集有:
在此聚集的开始部分有 32K 保留区域。
固定的聚集块尺寸,其合法值为 512、1024、2048 或 4096 字节,但不小于分区块尺寸。聚集块尺寸定义了聚集上支持的最小空间分配单元。不要把他和分区块尺寸混淆起来,后者定义的是 I/O 的最小单元。
主聚集终极块和辅助聚集终极块。终极块包含聚集方面的信息,例如:聚集的大小、分配组的大小、聚集块的尺寸等等,辅助聚集终极块是主聚集终极块的直接副本。假如主聚集终极块损坏,则使用辅助聚集终极块。这些终极块位于固定位置。这使得 JFS 不依赖任何其他信息,就能够找到他们。终极块结构在 jfs_superblock.h 的 struct jfs_superblock 中定义。
聚集 inode 表,包含描述聚集范围的控制结构的 inode 。聚集 inode 表逻辑上包含一个 inode 数组。聚集无目录结构;在聚集或文档集名字空间中,任何地方都没有聚集 inode 。
辅助聚集 inode 表,包含从聚集 inode 表复制的 inode 。由于对任何文档系统信息的查找而言,聚集 inode 表中的 inode 都是至关重要的,所以他们每一个在辅助聚集 inode 表中都有备份。当然,不会复制 inode 的实际数据,而只是复制可用来查找数据和 inode 本身的寻址结构。
聚集 inode 映射表,描述聚集 inode 表。聚集 inode 分配映射表包含聚集 inode 上及其磁盘位置上的分配状态信息。
辅助聚集 inode 映射表,描述辅助聚集 inode 表。由于必须复制聚集 inode 表本身,辅助聚集 inode 映射表实际上是和聚集 inode 分配映射表分开的映射结构。
块分配映射表,描述在聚集内分配和释放聚集磁盘块的控制结构。块分配映射表在聚集磁盘块内进行一对一映射。
fsck 工作区,他为 fsck 提供用来跟踪聚集块分配的空间。因为 JFS 支持超大聚集,所以这一区域是必需的;当 fsck 运行时,可能没有足够的内存用来跟踪内存中的这些信息。终极块描述了这一区域。每个聚集块需要一位。 fsck 工作区总是存在于聚集的末端。
内嵌日志为记录聚集中的元数据更改提供了空间。终极块描述了这一区域。内嵌日志总是紧跟 fsck 工作空间后。
初始情况下,在聚集创建时分配了第一个 inode 盘区。按需要动态分配和释放其他 inode 盘区。每个聚集 inode 描述聚集本身的某些方面,如下:
保留聚集 inode 0。
聚集 inode 1,即自身 inode ,描述包括聚集 inode 映射表的聚集磁盘块。这是一种循环表示法,因为聚集 inode 1 本身也在自己所描述的文档中。可通过强制规定至少第一个聚集 inode 盘区要在众所周知的位置,即主聚集终极块后面 4K 的位置,来处理以上显而易见的循环表示法问题。因此,JFS 能轻而易举地找到聚集 inode 1,从聚集 inode 1,通过跟随 inode 1 中的 B+ 树,能找到聚集 inode 表的余下 inode 。
要复制聚集 inode 表,JFS 还需要找到聚集 inode 1 的副本,以查找所复制表的其余部分。终极块会包含一个盘区描述符,该描述符描述辅助聚集 inode 表的第一个 inode 盘区的位置。JFS 能够从中找到辅助聚集 inode 1,连同辅助聚集 inode 表的余下部分。
聚集 inode 2 描述块分配映射表。
聚集 inode 3 描述安装时的内嵌日志。虽然分配了 inode ,但无数据存入磁盘。
聚集 inode 4 描述在聚集格式化期间发现的坏块。在块映射表中这些标记成已分配。该 inode 是数据为坏块的普通文档。 保留聚集 inode 5 到 15 以备将来扩展。
从聚集 inode 16 开始,每个文档集有一个 inode ,即文档集分配映射表 inode 。这个 inode 描述了表示文档集的控制结构。当更多文档集添加到聚集中时,为了容纳更多的文档集 inode ,聚集 inode 表本身可能必须增大。
分配组
分配组(AG)把聚集中的空间分成大块,并且允许 JFS 资源分配策略使用众所周知的方法,来实现更好的 JFS I/O 性能。首先,分配策略尝试将相关数据的磁盘块和磁盘 inode 集群起来,使磁盘实现好的局域性。文档通常是顺序地读写,而目录中的文档通常一起访问。其次,为了容纳局域性,分配策略尝试在整个聚集中分配不相关数据。聚集内的分配组用从 0 开始的 AG(分配组)索引。即用 AG 标识。
必须选择分配组大小,以使 AG 足够大以不断提供连续资源分配。为了将聚集扩充或缩小时所需进行的更新数最小化,分配组必须限制最大组数 128。此外,JFS 将对 8192 个聚集块的分配组大小规定其最小值。分配组大小必须总是 1 个 dmap 页(1、2、4、8、 ...dmap 页)描述的块数的 2 的幂次方。分配组大小在聚集终极块中存储。
大小不是分配组大小倍数的聚集将包含部分分配组;磁盘块没有完全覆盖聚集的最后一个分配组。除了JFS 将标记在块分配映射表中分配的却不存在的磁盘块之外,该部分分配组将被当作完整的分配组。
文档集
文档集是文档和目录的集合,这些文档和目录形成了可单独安装的子树。文档集完全包含在一个聚集中。请注意,一个聚集中可能有多个文档集;在那种情况下,任何文档集共享由聚集控制结构定义的空闲聚集磁盘块公共池。
文档集有:
文档集 inode 表,包含描述文档集范围的控制结构的 inode 。文档集 inode 表逻辑上包含一个 inode 数组。
文档集 inode 分配映射表,描述文档集 inode 表。文档集 inode 分配映射表包含文档集 inode 上及其磁盘位置上的分配状态信息。描述文档集分配映射表和其他文档集信息的终极 inode ,驻留前面所描述的聚集 inode 表中。由于复制了聚集 inode 表,因此这个 inode 存在第二个版本,他指向同样的数据。终极 inode 本身是个文档。当文档集一开始创建时,分配第一个 inode 盘区按需要动态分配和释放其他 inode 盘区。
文档集中 inode 的分配如下所示:
保留文档集 inode 0。
文档集 inode 1 包含附加的文档集信息,他们无法放入聚集 inode 表中的文档集分配映射表 inode 。
文档集 inode 2 是文档集的根目录 inode 。注意,JFS 保留了 inode 2 是文档系统的根这一公共 Unix 约定。
文档集 inode 3 是文档集的 ACL 文档。
从文档集 inode 4 开始,文档集 inode 用于一般文档集对象、用户文档、目录和符号链接。
盘区、inode 、B+ 树
盘区是当作单元分配给 JFS 对象的连续聚集块序列。盘区完全包含在一个聚集(并且因此也是在一个分区)中;但是,大盘区可能跨多个分配组。
每个 JFS 对象可用一个 inode 来表示。inode 包含预期的对象特定信息,例如:时间戳和文档类型。他们还包含记录盘区分配的 B+ 树。注意,任何 JFS 元数据结构(除终极块之外)都以文档表示。通过重用这种数据的 inode 结构,数据格式(即磁盘布局) 自然是可扩展的。
盘区、B+ 树、inode 在以下章节中周详描述。
盘区
文档是按盘区顺序分配的。盘区是当作一个单元分配的聚集块的连续变长序列。盘区的尺寸范围是 1 到 2(24)-1 个聚集块。盘区可能跨越多个分配组(AG)。为了在插入新盘区、定位特定盘区等操作方面有更优性能,这些盘区是按 B+ 树索引的。
定义一个盘区需要两个值,即其长度和其地址。长度以聚集块尺寸为单位计算。JFS 使用 24 位值来表示盘区的长度,因此盘区的范围大小是 1 到 2(24)-1 个聚集块。
对于 512 字节的聚集块尺寸 (所允许的最小值),最大盘区是512*(2(24)-1)字节,(比 8G 稍小)。对于 4096 字节的聚集块尺寸(所允许的最大值),盘区的最大长度是 4096*(2(24)-1)字节,(比 64G 稍小)。这些限制仅适用于一个的盘区;对整体文档大小没有限制作用。地址指的是盘区中第一个块的地址。地址同样以聚集块为单位:他从聚集的开始处计算块偏移量。
结合了用户特定聚集块尺寸的基于盘区的文档系统,允许 JFS 无需单独支持内部存储碎片。可配置聚集使用小的聚集块尺寸(例如,512 字节),以使大量小尺寸文档的聚集内部存储碎片最小化。
通常,JFS 分配尝试通过分配最小数量的盘区策略,而使每个盘区尽可能大。这就允许大的 I/O 传送,结果使得性能提高。然而,对于特别情况,不一定总有这种结果。例如,一个段的写入时复制会造成连续盘区被分割成更小的连续盘区系列。另一种情况是盘区大小的限制。例如:由于 JFS 必须把整个盘区读入内存,然后进行解压缩,所以压缩文档盘区大小是有限的。由于 JFS 的可用内存数量有限,因此他必须确保有足够的空间用于解压缩盘区。
提供了一个碎片整理实用程式,以减少动态分配/释放可变长盘区时出现的外部存储碎片。这种分配和释放可能导致不相连的变长空闲盘区遍及整个聚集。碎片整理实用程式会把多个小的空闲盘区合并成一个较大的盘区。
inode
JFS 磁盘 inode 是 512 字节。一个 JFS 磁盘 inode 包含 4 组基本信息。第一组描述 JFS 对象的 POSIX 属性。第二组描述 JFS 对象的其他属性;这些属性包括支持 VFS 必需的信息、环境特定的信息、连同 B+ 树的头部。第三组不是包含 B+ 树根节点的盘区分配描述符就是包含内嵌数据。第四组包含扩展属性、更多内嵌数据或附加的盘区分配描述符。在 jfs_dinode.h 的 struct dinode 中定义磁盘 inode 结构。
JFS 动态分配 inode 提供的好处如下:
inode 磁盘块可放在任何磁盘地址,这使得 inode 号和位置分开。这种分离简化了支持聚集和文档集重组,能够使聚集缩小。能够移动 inode ,移动后号码仍然相同。这允许 JFS 不必需要查找目录结构就能够更新 inode 号。对于支持 DFS 文档集复制而言,这种分离也是必需的。当复制文档集时,仅复制 inode 。既然 JFS 能把新的 inode 放在磁盘的任意位置,新 inode 将有和从他们复制的 inode 相同的号码。这允许 JFS 不需复制目录结构并且更新 inode 号。
不再需要分配实际所需十倍的 inode 。这对于 JFS 中较大的 inode 尺寸(大于 512 字节)而言,尤为重要。
大文档的文档分配可能消耗多个分配组且仍是连续的,而静态分配造成间隔(由于每个分配组中初始分配的 inode )。
另一方面,动态 inode 分配造成大量问题,包括:
对于静态分配,文档系统的几何构造隐含描述了磁盘上 inode 的布局;对于动态分配,必需有单独的映射结构。
对 JFS 完整性而言,这些映射结构是至关重要的。由于复制这些结构的系统开销,JFS 决定接受丢失这些映射表的风险。但是,JFS 将复制 B+ 树结构,该结构允许 JFS 查找映射表。
通过只分配磁盘上 inode 连续大块的 inode 盘区,动态分配了 inode 。根据定义,一个 JFS inode 盘区包含 32 个 inode 。对于 512 字节的 inode 尺寸,因此磁盘上一个 inode 盘区的大小是 16KB。
当分配新的 inode 盘区时,并不初始化盘区。然而,要使 fsck 能够检查是否 inode 在使用中,JFS 需要 inode 的一些信息。一旦盘区中的 inode 标记成在使用中,就必须初始化他的文档集号、inode 号、inode 戳连同 inode 分配组块地址。因此,链接字段就足以确定 inode 当前是否正在使用。
注意,动态 inode 分配意味着在 inode 号和 inode 的磁盘地址之间没有直接关系。因此,JFS 必须有查找磁盘上 inode 的方法。inode 分配映射表提供了这一功能。
inode 生成号只是每当重用 inode 时值就增加的计数器。
存储每个 inode 生成计数器这一静态 inode 分配常用方法在动态 inode 分配中不起作用,因为当 inode 空闲时,其磁盘空间可能确实由不是 inode 的数据所重用,(换句话说,空间可能被收回,以存储普通文档数据)。因此,在 JFS 中,只有一个 inode 生成计数器,他在每一个 inode 分配时增加其值,即在重用 inode 时,相应的计数器增加其值,而不是每个 inode 有一个计数器。 B+ 树
这一节描述文档布局使用的 B+ 树数据结构。选择 B+ 树是为了提高读写盘区的性能,这是 JFS 必须进行的最普通操作。B+ 树为读取文档的特定盘区提供快速搜索。他还提供有效方法将盘区添加或插入文档中。较为少见的情形是:当删除文档时,JFS 需要遍历整个 B+ 树。为了确保 JFS 会删除 B+ 树使用的块连同文档数据,对于遍历 B+ 树效率也很高。
盘区分配描述符(xad 结构)描述盘区并且又添加了表示文档所需的两个字段:描述盘区表示的逻辑字节地址的偏移量和标志字段。盘区分配描述符结构在 jfs_xtree.h, struct xad 中定义。
xad 结构为:
struct xad {
unsignedflag:8;
unsignedrsvrd:16;
unsignedoff1:8;
uint32 off2;
unsignedlen:24;
unsignedaddr1:8;
uint32 addr2;
} xad_t;
其中:
flag 是包含各种标志的 8 位字段。这些标志能够表示写入时复制、是否分配了盘区但没有记录他、压缩信息等等。
rsvrd是保留供将来使用的 16 位字段。他总为零。
off1,off2 是 40 位字段,包含盘区中第一个块的逻辑偏移量。逻辑偏移量是以聚集块尺寸为单位表示;也就是说,要取得一个字节,偏移量必须乘以聚集块尺寸。
len 是 24 位字段,包含盘区的长度。长度以聚集块尺寸为单位表示。
addr1,addr2 是 40 位字段,包含盘区的地址。地址以聚集块尺寸为单位表示。
xad 结构描述了两个抽象范围:
磁盘上磁盘块的物理范围。他以聚集块号 xad_address 开始,并且延伸为 xad_length 聚集块。
文档内字节的逻辑范围。他以字节号 xad_offset * AGBS(聚集块尺寸)开始,并且延伸为 xad_length*AGBS 字节。
当然,物理范围和逻辑范围有相同长度的字节。请注意, xad_offset 以聚集块尺寸为单位存储(例如,在 xad_offset 中值 "3" 意味着 3 个聚集块,而不是 3 个字节)。他遵循文档内盘区总是以聚集块尺寸为边界。
JFS 中的任何索引对象(除目录外),有一个类属 B+ 树索引结构。索引的数据将取决于对象。B+ 树以由树描述的数据的 xad 偏移量为键。项按 xad 结构的偏移量排序。xad 结构是 B+ 树节点中的项。
磁盘 inode 第二扇区底部包含数据描述符,他描述在 inode 的后半部分内存储的内容。对于足够小的文档,后半部分可能包含内嵌数据。假如文档数据不适合 inode 的内嵌数据空间,他将包含在盘区中,inode 将包含 B+ 树的根节点。头部指出在使用的 xad 个数,可用的 xad 个数。通常,inode 将包含 8 个 xad 结构 B+ 树的根。假如文档有 8 个或更少盘区,那么这 8 个 xad 结构也是 B+ 树的叶节点。他们将描述盘区。否则,inode 中的 8 个 xad 结构将指向 B+ 树的叶节点或内部节点。
一旦 inode 中的 8 个 xad 结构均已填充,为了有更多的 xad 结构,就会尝试使用 inode 的最后四分之一。假如 INLINEEA 位在 inode 的 di_mode 字段中配置,那么 inode 的最后四分之一可用。
一旦 inode 中任何可用的 xad 结构都被使用,就必须拆分 B+ 树。JFS 将把 4K 的磁盘空间分配给 B+ 树的叶节点。叶节点逻辑上是带头的 xad 项的数组。头部指向节点中第一个空闲的 xad 项,没有分配紧跟其后的任何 xad 项。8 个 xad 项从 inode 复制到叶节点,初始化头部以指向第 9 个项作为第一个空闲项。然后 JFS 将把 B+ 树的根更新为 inode 的第一个 xad 结构;该 xad 结构将指向最新分配的叶节点。这个新的 xad 结构的偏移量是叶节点中第一个项的偏移量。将更新 inode 中的头部以表示当前 B+ 树只使用 1 个 xad。还需要更新 inode 头部以表示当前 inode 包含 B+ 树的纯根。
当把新盘区添加到文档时,将以必需的次序,继续把他们添加到相同的叶节点。这将持续直到节点填满为止。一旦节点填满了,将为 B+ 树的另一个叶节点分配新的 4K 磁盘空间。将把该 inode 的第二个 xad 结构配置成指向新分配的节点。
这将持续直到填满 inode 的任何 8 个 xad 结构为止,这时,将再次拆分 B+ 树。这种拆分将创建 B+ 树的内部 inode ,他们是纯粹用来记录树的搜索路径。JFS 将为 B+ 树的内部 inode 分配 4K 磁盘空间。内部节点看起来如同叶节点。从 inode 复制 8 个 xad 项到内部节点,初始化头部以指向第 9 个项作为第一个空闲项。然后,通过使 inode 的第一个 xad 结构指向新分配的内部节点,JFS 更新 B+ 树的根。将更新 inode 中的头部以表示当前 B+ 树只使用 1 个 xad。
文档 jfs_xtree.h 在 struct xtpage_t 中描述 B+ 树根的头部。文档 jfs_btree.h 是在 struct btpage_t 中的内部节点或叶节点的头部。
例子
下列例子进一步分析了盘区描述符和 xad 结构的用法:
连续分配的 1041377 字节文档。
相同的 1041377 字节文档,但在磁盘上拆分成三段。
1041377 字节的文档,但里面有一个"洞"(稀疏文档)。
连续分配的 16GB 文档。
在任何这些例子中,聚集块尺寸都是 1KB。
连续分配的 1041377 字节尺寸文档: 该文档需要 1017 个 1KB 聚集块,(在最后一个聚集块中,有 31 个字节丢失成为内部存储碎片)。要描述这个连续文档只需要一个 xad 结构:
flag这里不讨论
offset 0 /* the beginning of the file */
length 1017/* 1017 1KB aggregate blocks */
address xxxxx /* aggregate block # */
相同的 xad 结构能够表示任何长度为 1040385 (1016 * 1024 + 1)到 1041408 (1017 * 1024)的连续文档,因为盘区描述符只表示小于聚集块大小粒度的尺寸。只有 inode 的 di_size 字段记录字节粒度。
在 1041377 字节文档分三段: 假设相同的文档拆分成磁盘上三个不同盘区:一个为 495 个聚集块长,一个为 22,一个为 500。需要三个 xad 结构来表示该文档,每个物理盘区需要一个:
xad #0 :
flag这里不讨论
offset 0 /* the beginning of the file */
length 495 /* 495 1KB aggregate blocks */
address xxxxx /* aggregate block # */
xad #1:
flag这里不讨论
offset 495 /* the beginning of the file */
length 22 /* 22 1KB aggregate blocks */
address yyyyy /* aggregate block # */
xad #2:
flag这里不讨论
offset 517 /* the beginning of the file */
length 500 /* 500 1KB aggregate blocks */
address zzzzz /* aggregate block # */
该例中,0 号 xad 描述文档开始的 495 个物理聚集块。 xad_offset 字段包含 0,因为该 xad 描述以逻辑偏移量 0 开始的字节。第二个 xad,1 号 xad,描述文档接下来的 22 个物理聚集块。 xad_offset 字段包含 495,因为该 xad 描述以逻辑偏移量 506880 (495 * 1024) 开始的字节;前面的字节由 xad 0 描述。最后一个 xad 描述文档的最后 500 块。这里, xad_offset 字段是 517。请注意,对于非稀疏文档,给定 xad 的 xad_offset 字段等于任何以前 xad 结构长度和(在本例中,517 = 495 + 22)。假如这一关系总是成立的,那么 xad_offset 字段就是冗余的,能够消除。然而,下一个例子显示,对于稀疏文档, xad_offset 字段不是冗余的。
1041377 字节的稀疏文档: 考虑经由以下 POSIX 风格的操作而创建的文档:
fd = create ("newfile", blah blah blah);
write (fd, "hi", 2);
lseek (fd, 1041374, 0);
write (fd, " bye" , 3);
该文档有以逻辑字节偏移量 0 开始的两字节数据("hi"),更有以逻辑字节偏移量 1,041,374 开始的三字节数据 ("bye"),并且在这两者之间全为 0(稀疏的)。文档的长度为 1041377 字节。
通常,JFS 不分配物理磁盘空间以保存从不写入文档的字节范围。因此,将占用两个 xad 结构来表示该文档:一个为包含 "hi" 数据的盘区,一个为包含 "bye" 数据的盘区:
xad #0 :
flag这里不讨论
offset 0 /* the beginning of the file */
length 1 /* 1 1KB aggregate blocks */
address xxxxx /* aggregate block # */
xad #1:
flag这里不讨论
offset 1016/* the beginning of the file */
length 1 /* 1 1KB aggregate blocks */
address yyyyy /* aggregate block*/
在该例中,第一个盘区(xad 0)包含字节 "hi",紧接着是 1022 字节 0。最后一个盘区(xad 1)包含 990 字节 0,紧接着是 3 字节 "bye"。1KB 盘区中剩余的 31 字节不是文档的组成部分。(他们和第一个例子中丢失成为内部存储碎片的 31 个字节相同)。
请注意,该例中, xad_offset 字段是必需的;这是知道 xad 1 表示文档内在无法预料的逻辑偏移量字节序列的唯一方法(也即,xad 1 的偏移量不等于 xad 0 的偏移量加长度)。这是表示稀疏文档的方法。
inode 的 di_size 字段包含写入的最后一个字节偏移值加 1。
连续分配的 16GB 的文档: xad 结构中的长度字段仅有 24 位长:因此,他能包含的最大值是 2(24)-1。假如聚集块大小是 1KB(例如),那么一个 xad 能够表示的最大盘区是(2(24)-1)*2(10)=1KB,小于 16G。暗示这也是 xad 结构能够表示的最大盘区。因此,假如文档够大的话,就算他在磁盘上是相连的,也需要多个 xad 结构来表示他。本例中显示了这样一个连续分配的文档:一个 16G 文档,他从聚集块号 12345 开始连续分配,获取 16777216 个 1KB 的聚集块(16G)。
xad #0 :
flag这里不讨论
offset 0 /* the beginning of the file */
length 16777215/* 1 1KB aggregate blocks */
address 12345 /* aggregate block*/
xad #1:
flag这里不讨论
offset 16777215/* the beginning of the file */
length 1 /* 1 1KB aggregate blocks */
address 16789560/* aggregate block # */
在该例中,不论文档在磁盘上是否相连,要表示他至少需要两个 xad 结构,这是由于单个盘区的长度限制。
AG 空闲 inode 盘区列表 AG 空闲 inode 盘区列表有助于解决反向查找问题连同空闲 inode 号查找问题。这使得 JFS 能找到下一个空闲盘区所在的 IAG 号和 AG 号。(实际是给出了空闲 inode 号。)每个文档集的每个 AG 都有一个AG 空闲 inode 盘区列表。为减少扩展和缩减聚集的系统开销,JFS 设定每个聚集允许的最大 AG 数。所以,AG 空闲 inode 盘区列表头的个数是固定的。列表头在 inode 分配映射表的控制页中。表的第 i 项是个双向列表的头,该双向列表是第 i 个 AG 中任何包含空闲 inode 的 inode 分配映射表项(IAG)的集合。IAG 号作为列表索引。-1 表示列表尾。每个 IAG 控制区都包含指向该列表的正向和反向指针。
当盘区中任何的 inode 都已删除,则释放该 inode 盘区的磁盘块。当 IAG 的一个 inode 盘区被删除时,该 IAG 插至所属的 AG 空闲 inode 盘区列表的表头。当创建新的 IAG,并分配一个 inode 盘区时,该 IAG 号插至 AG 空闲 inode 盘区列表的表头。当 IAG 的任何 inode 盘区分配完时,从列表中删除该 IAG。当释放 IAG 的任何 inode 盘区时,从列表中删除该 IAG 同时加到IAG 空闲列表中。当 AG 需要分配 inode 盘区时, 则使用 AG 空闲列表头上的第一项。
此表没有记日志;但能够在恢复时由 logredo 恢复,或由 fsck 重建。
表的结构定义见 jfs_imap.h, struct dinomap_t .
IAG 空闲列表 IAG 空闲列表有助于查找空闲 inode 号。这使得 JFS 不用查看相应分配的 inode 盘区就可找到 IAG。(实际时给出了空闲 inode 号)。聚集和其每个文档集都有自己的链表。该列表的每个项指向一个 IAG 链表。IAG 号作为列表索引。-1 表示列表尾。当删除盘区的任何 inode 时,则释放该 inode 盘区的磁盘块。假如某个 IAG 的任何 inode 都为空闲,则该 IAG 号插入 IAG 空闲列表头。当需要分配新的 inode 盘区,而该 AG 中又没有包含空闲盘区的 IAG,则使用 IAG 空闲列表头的第一项(即从表中删除)。inode 盘区分配描述符一经分配就不再删除。inode 盘区的地址设为 0x0。
对于聚集 IAG 空闲列表头是聚集自用 inode 的一个字段。对于每个文档集 IAG 空闲列表头是文档集分配映射表 inode 的一个字段。该列表没记日志;但可在恢复时由 logredo 修复,或由 fsck 重建。
IAG 空闲列表的结构定义 struct inomap_t 在文档 jfs_dinode.h 中。
下一个空闲 IAG 下一个空闲 IAG 计数器有助于查找空闲 inode 号。使得 JFS 能找到下一个能够分配的 IAG 的 iag号。(实际是让 JFS 找到空闲 inode 号)。聚集和其每个文档集都有自己的计数器。计数器在 inode 分配映射表的控制页中。IAG 一经分配就不再删除。
文档集分配 inode 文档集 inode 表中的文档集分配映射表 inode 是特别类型的 inode 。既然这些节点表示文档集,则能够说是文档集的“父 inode ”。这些节点包含文档集特定信息,而不是一般的 inode 数据。同时也记录文档集 inode 分配映射表在 B+ 树中的位置。结构定义 struct dinode 见文档 jfs_dinode.h
文档 文档由包含一个 B+ 树根的 inode 表示,B+树描述包含用户数据的盘区。B+ 树以盘区的偏移量作为索引。
符号链接 符号链接由一个 inode 表示,该 inode 的 di_mode 字段配置为符号链接模式 (S_IFLNK)。假如 inode 内有空间,则链接文档的整个路径直接存储在 inode 中。否则,将作为 inode 的数据存于盘区中(通过该 inode 的 B+ 树索引)。
目录 目录是 JFS 中日志化的元数据文档。目录由目录项组成,目录项表示目录中包含的对象。目录项将名字和 inode 号连接在一起。特定的 inode 描述特定名字的对象。为提高目录项定位的性能,B+ 树采用按名排序。
目录 inode 的 di_size 字段仅表示目录 B+ 树的叶子页。假如 inode 中包含目录的叶节点,则 di_size 字段为256。
目录中没有特定项表示自身 (".") 和父目录 ("..")。而在 inode 中表示。自身就是目录自己的 inode 号。父目录是 inode 中的特别字段, idotdot,struct dtroot_t ,见文档 jfs_dtree.h。
目录 inode 包含 B+ 树的根,处理方法和一般文档类似。只是目录 B+ 树以名为键。目录 B+ 树的叶节点包含目录项,且以目录项的全名作为键值。目录 B+ 树最下层内部节点使用后缀压缩。其他内部节点采用相同的压缩后缀。后缀压缩将名字缩至最短,正好足以区分当前目录项和前一目录项。
由于 B+ 树项的大小是可变的,JFS 需要处理这些项的方案。JFS 想要避免在删除一项时引起的项移动,平均一项有2K的数据。
B+ 树节点的内容:
固定个数的目录槽,个数取决于节点的大小。这些槽用于存储目录槽数组和目录项或路由项。目录槽的大小总是 32 字节。固定大小的目录槽使得 JFS 在删除目录项不必移动,从而还避免了内部碎片。 一个目录 B+ 树的头,描述 B+ 树 inode 。此部分包含一个标志,标记节点是内部节点或是叶节点, 及是不是 B+ 树的根节点。还包含自身的块地址。 nextindex 字段记录目录槽数组中的最后一项。 stblindex 字段记录目录槽数组的开始位置。 freelist 字段指向该节点中空闲槽列表头。 一个目录槽数组,他是正使用的目录槽索引的有序数组。使用该数组减少了目录项增删时所需的移动次数。数组比项本身小很多,所以移动的只是数组而不是整个项。在数组中,能够用二分法搜索某个目录项。 一个目录 B+ 树槽空闲列表,使得内部碎片最小化。目录 B+ 树的头包含列表表头,每个空闲目录槽指向列表中的下一个空闲槽。假如有一系列相连的空闲槽,则在第一个槽中设立一个计数值,说明该系列的长度。这有利于在新建目录 B+ 树节点时,进行快速初始化。 一个目录项,将名字链接到一个 inode 号。目录项包含在叶节点的目录槽中。假如需要存储整个目录名,目录项能够有附加槽。目录项的 next 字段表明该项是否有后继项。大多数目录项只有单个槽。 一个路由项,用于记录目录 B+ 树的搜索路径。路由项包含在内部节点的目录槽中。路由项将按后缀压缩的路由键映射到盘区,此盘区包含下一层目录 B+ 树的内部节点或叶节点。假如路由项需要记录整个的路由键,则能够有附加槽。路由项的 next 字段表示该项是否有后继项。大多数路由项只有单个槽。 目录 B+ 树中的内部节点或叶节点是 4K 大小的页。由于许多目录都不是很大,所以这种方式对大多数目录来说是很浪费磁盘空间的。所以目录的初始叶节点采用以下分配方案:
初始目录项存储在目录嵌入数据区中。 当目录 inode 的嵌入数据区填满时,JFS 分配一个叶节点,大小和聚集块的尺寸相同。 当初始叶节被占满,而大小又不到 4k,则倍增节点大小。首先在当前盘区中扩增;假如没有足够空间,则需分配新的盘区,然后将旧盘区的数据复制到新盘区。目录槽数组仅够存放页未扩时的槽,所以必须创建新的槽数组。从新分配的数组起始处使用槽,并将旧的数组数据复制到新的位置。更新指向该数组的头指针,并将旧数组中的槽添加到空闲列表中。 假如叶节点再次填满,而大小仍不足 4K,重复步骤 3。一旦叶节点达到 4K 则分配新叶节点。初始节点后的每个叶节点,一开始就分配 4K。 当叶子页的任何项都释放,则从 B+ 树中删除该页。仅当目录中任何目录项都已删除,目录又缩回 inode 。
访问控制列表 (ACL) JFS 的每个 inode 都有不同的访问控制列表 (ACL)。ACL 能够表示不同的项,例如许可权、用户标识符、或组标识符。聚集 inode 的 ACL 字段是没有用的。
虽然在磁盘上和内存中 ACL 的表示方式没有规定,但从 DFS 外部所看到的“外部”表示是固定的。ACL 大小的唯一限制是其外部表示必须适合 8192 字节大小的 dfs_acl 结构。
任意 JFS 对象都可有一个管理该对象存取的 ACL;这种 ACL 称为常规 ACL。目录对象在创建时可能用到两个关联的可选 ACL;初始目录 ACL和初始文档 ACL。初始 ACL 的作用范围是目录中的任何文档。
ACL 体系结构未指定 ACL 的存储方式,但建议 ACL 有字段标识或命名其辅助对象,这样通过简单的等同性检查就能够检测到文档集中的共享关系。因此,JFS 在每个文档集中用一个文档(ACL 文档)存储文档集的 ACL;文档集 inode 1 就是 ACL 文档。文档集中的每个 inode 在 ACL 文档中存放一个索引。
ACL 文档需要一个存储 ACL 空闲区域的位图。ACL 文档有一个 4K 大小的位图,标识 8M 的 ACL 项,如有必要可扩增。位图中的一位代表 256 字节连续磁盘空间;位图不描述自身的状态。
ACL 文档的数据未日志化。
扩展属性(EA) 扩展属性是附加到 JFS 对象适用存储和存取的机制。EA 连续存储在扩展属性空间 (EAS) 中,空格存储 EAS 由 JFS 对象 inode 的 EA 描述符定义。EA 描述符只是个盘区描述符,定义见 jfs_types.h, struct dxd_t 。
EA 能够存放在 inode 内,或存放在单独盘区内。EA 描述符的标志字段指示存储的方式。由于此空间也可用于存放文档 xtree 附加的 xad 项,所以 inode 的 di_mode 字段指明该空间是否可用。假如该字段值为 INLINEEA,则表明空间可用。
假如 EA 存于 inode 内,则忽略 EA 描述符的 offset 和 length 字段。EA 描述符的大小表示数据的字节数。
假如 EA 存于盘区内,EA 描述符将描述该盘区。JFS 不希望 EA 数据太大,所以 JFS 不支持每个 inode 有多于一个盘区的 EA 数据。
EA 项包括 EA 名称和其值。要访问某个 EA,JFS 只是线性搜索 EA 数据。
EA 数据未日志化,但他是写同步的(即数据不是旧数据,就是新数据,但绝不可能是部分更新的数据)。JFS 在日志中记录 EA 数据的位置。嵌入 EA 数据是日志化的。
流 流用于将数据连接到一个文档或目录。这种附加数据和目录数据相似,都可按名引用。在第一版中不支持流,在这里讨论仅为元数据结构的完整性。
磁盘 inode 的四部分的第二部分有一个字段描述流描述符。由于附加到一个对象的流数目是可变的,所以流描述符是个 inode 号,以允许流增加或缩减。流描述符 inode 指向的数据称为流列表。
流没有关联的扩展属性,所以从不使用流的 inode 四部分的最后一个部分-扩展属性。实际上该部分用于附加的流项。B+ 树的数据如同目录项。每个流都有自己的 inode ,他们依次记录流数据存放的数据块地址。
结束语
JFS 小组最重要的目标是创建可靠的,高性能的文档系统。本文讨论了 JFS 磁盘布局结构,连同实现可伸缩性、可靠性和高性能的机制。同时周详探讨了 JFS 怎样在整个文档系统中使用 B+ 树提高文档系统操作。
|