Soft Updates: 用于快速文件系统(FFS)的一项消除大多数同步写操作的技术

摘要

传统上,维持掉电或系统崩溃后的文件系统一致性维护主要采用两种方法:其一是对存在依赖顺序的元数据(metadata)进行同步写入,其二是用写通式日志来将原子操作组织在一起。Soft Updates,一种不同于它们的方法,是一种通过保证元数据按依赖顺序更新来确保磁盘上文件系统的总保持一致的实现机制。使用SoftUpdates避免了对于独立日志或大量同步写操作的需求。同时,它还能将很多以前独立且同步进行的操作合并,从而在文件操作密集的环境(例如程序开发、邮件服务器等)中减少40%-70%的写操作。在提高性能的同时,SoftUpdates还可以更好地维持文件系统的一致性。通过保证不一致性仅存在于未声明的块或i-节点,SoftUpdate能够消除对于系统崩溃后运行文件系统检查程序的依赖。这样,在重启后文件系统立即处于可用状态。另外,可以通过一个后台任务在处于运行状态的文件系统中回收丢失的块和i-节点。

这篇论文描述了集成到4.4BSD快速文件系统(Fast Filesystem)中的一个SoftUpdates实现。它详细介绍了建立一个成品级质量的系统时,对研究原型以及BSD系统做的修改。同时,它也讨论了在将SoftUpdates从研究转入现实中的经验、难处,以及从中得到的教训; 那些非常规的文件系统操作(例如,fsck和‘fsync’),需要进行的重新考虑和增加的代码。最终实现的系统得到的体验证明了早先的研究成果:SoftUpdates很好地融合进了现有的文件系统,保证了元数据关联性,并基本达到了最佳的性能。

第1节 研究背景与导言

元数据(如文件目录,i-节点,以及空闲块映射表)指明原始数据存储(raw storage)的结构。元数据提供指针和描述符,通过它们将磁盘上的扇区联结成文件,并区分它们。为了长期维持可靠存储,文件系统必须在遭遇非预期的系统崩溃,如断电和操作系统故障时,保证元数据的完整性。由于类似的崩溃通常导致保存于易失性主存中的全部信息的丢失,保存在非易失性存储器(例如,磁盘)中的信息必须总具有足够的一致性以便确
定性地重建文件系统的一致。特别地,文件系统在磁盘上的镜像绝不能包含悬挂指针,也不能有足以导致二义性的资源所有权指针,或未引用的活动资源。维持这些原则通常需要顺序地(或按原子操作分组)更新小的元数据对象。

过去,BSD快速文件系统(FFS)及其派生系统采用同步写入来保证稳定的存储器写入次序。例如,在BSD系统中创建一个文件,首先需要分配和初始化新的i-节点并填充一个新的目录指向项。由于采用同步写入,文件系统将强制创建文件的应用程序等待这些初始化操作的完成,其结果是,在这些系统中类似创建、删除文件这样的操作将以磁盘,而不是CPU/内存的速度进行。由于磁盘操作相对于其他部件来说更慢,同步写入会降低系统性能。元数据更新问题也可以在采用NVRAM技术的前提下通过其他机制解决,例如,可以使用不间断电源(UPS)或Flash。此时只需要保证NVRAM的一致性,而更新可以按任何方便的方式复制到磁盘上。还有一种是将操作分组为包含某种写进式日志的原子操作或使用 shadowpaging。总而言之,这些方法通过在磁盘上增加可以在系统故障或介质损坏之后用来重建提交的元数据附加信息来达到目的。很多现代文件系统成功地使用了写通式日志来获得比同步写入更好的性能。在[Ganger & Patt, 1994]中建议了另一种方式,SoftUpdates,并在研究模型中进行了评估。使用SoftUpdates,文件系统延迟写入(如回写式缓存)元数据的修改,追踪更新的依赖关系,并在回写时保持它们之间的一一依赖关系。因为很多元数据块包含大量指针,如果依赖关系仅以块级记录时循环依赖会经常发生,因此SoftUpdates追踪以指针为单位的依赖关系,这使得块能够以任意顺序写入。非独立的更新在其他写入前将回滚(rolled-back),并在写入完成后恢复,循环依赖问题从而被消除。使用SoftUpdates时,应用程序总是看到最新的元数据块副本,而磁盘上的数据总是和其上的其他内容一致。在这篇论文中,我们描述了在NetBSD, OpenBSD, FreeBSD, BSDI操作系统中采用的4.4BSD FFS中集成SoftUpdates的过程。同时,我们讨论了其中的经验、教训,并描述了文件系统中一些比较复杂的难
题,使用核心内存追踪依赖关系,完整的“fsync”调用实现,一些系统调用的语义等等。在fsck中正确地检测和处理丢失的资源,干净并正确地完成一个unmount系统调用需要进行的额外考虑,以及相应地增加代码的复杂程度。尽管存在这些困难,我们的性能试验证实了早先研究的结论。特别地,在BSD FFS中使用SoftUpdates消除了绝大多数同步写入,并且,和理论最佳情况(完全异步更新的FFS)的差异不到5%。同时,SoftUpdate使BSD FFS语义更明确、完整性更强健,并提供更好的安全保证。此外,它还能在崩溃之后立即恢复(不再需要先行执行fsck)。这篇论文的其余部分包括:第2节,描述BSD FFS操作中的更新依赖关系;第3节描述BSD SoftUpdates实现如何处理它们,包括关键的数据结构,如何使用这些结构,以及集成到4.4BSD操作系统中的过程;第4节讨论了我们将原型转化为生产环境实现中得到的经验和教训;第5节简要总结了在
4.4BSD系统中引入SoftUpdates后的性能改善;第6节讨论了新增的文件系统快照支持,以及这一特性如何在一个活动的文件系统中被用于后台执行的局部fsck;第7节概要描述了BSD SoftUpdates代码的状态和可用性。

第2节 BSD快速文件系统中的更新依赖关系

很多重要的文件系统操作由一系列相关的对分散的元数据更新组成。为了保证在出现了非预期的故障后能够恢复,这些修改通常必须以一个特定的顺序复制到可靠存储。例如创建一个新文件时,文件系统首先分配一个i-节点,对其进行初始化并创建一个指向它的目录项。如果系统在新目录项已被写入,而对应i-节点尚未写入时崩溃,则完整性将被破坏,因为磁盘上的i-节点状态未知。为了确保元数据的一致性,初始化过的i-节点
必须先于新的目录项到达可靠存储器。我们称这一需求为更新依赖关系——安全地写入目录项依赖于首先写入i-节点。更新顺序可以用三条简单的规则描述:

1. 绝不在一个结构被初始化之前指向它(例如,i-节点必须在目录项引用它之前初始化)
2. 绝不在所有指向某一资源的指针都被清零之前重用这一资源(例如,指向数据块的i-节点指针必须在那个数据块分配给其他i-节点之前全部清零)
3. 绝不在指向一个活资源的新指针设置成功之前对旧指针实施复位操作(例如,对一个文件进行更名时,在写入新名字之前不应移去这个i-节点的旧名字)。

本节将描述BSD FFS中的更新依赖关系问题,限于篇幅,我们假定读者对[McKusick etal, 1996]描述的BSD FFS有初步的了解。

总共有8个BSD FFS操作需要顺序地进行更新以保证崩溃后的恢复:创建文件,删除文件,创建目录,移除目录,文件/目录更名,块分配,间接块维护,以及空闲映射表管理。

i-节点和数据块是BSD FFS管理的两种主要资源。为了管理这些资源,使用了两个位映射表来管理这些资源。对于文件系统的每一个i-节点,i-节点位映射表中都有一个对应位,当该位置1表示此i-节点在用,而置0则表示此i-节点空闲。同样,对于每个数据块,在数据块位映射表中也有一个对应位表示它是空闲还是在用。FFS文件系统可以被分割为以柱面组(cylinder groups)为单位的固定大小的单元。每个柱面组都有一个包
括在当前柱面组中的i-节点和数据块的位映射表的块。对于一个大的文件系统,这一组织结构使核心内存(kernel memory)中能够只存放这种小的文件系统单元。活动的柱面组都被存放在单独的I/O缓冲区中,并可以进行独立于其他柱面组的写操作。

创建文件时,在不同的独立块中的三组元数据都将被修改。首先是一个新的、初始化过的i-节点,其内容包括文件类型、被设置为1的连接计数(这表示它是活动的,如被某一目录项引用)、它的权限以及其他信息;随后是i-节点位映射表,以反映i-节点被分配这一状况;最后是新目录项,它将包括新的文件名,以及一个指向i-节点的指针。为确保位映射表总反映全部已经分配资源,位映射表必须在i-节点或目录项之前写入。由于
初始化好的i-节点在写盘之前处于未知状态,规则1要求所有关于它的相关更新必须它写入后进行。尽管未被明确地要求,绝大多数FFS实现在创建文件这一系统调用返回之前,也写入目录块。这个附加的同步写入确保了当应用程序在其后进行“fsync”系统调用时文件名已经被保存到可靠的存储器中。如果没这么做,那么“fsync”调用将必须查找所有未写入的包含该文件名字的目录块,并写入磁盘。类似的更新依赖关系也存在于为一个i-节点指定另一个名字时(也称hard link,硬连接),因为增加第二个名字需要文件系统增加i-节点的连接计数,并在写入目录项之前重写这个i-节点。

删除文件时,将修改目录块,i-节点,以及若干柱面组位映射表。在目录块中,相关的目录项将被“移除”,这一操作将i-节点指针清零。在i-节点块中,相关i-节点的类型字段、连接计数,以及数据块指针将被清零。被删文件的数据块以及i-节点随后被相应的数据块/i-节点映射表反映。规则2规定了目录项和i-节点之间,以及i-节点和所有修改了的映射表位之间的更新依赖关系。为了保持连接计数适当地大(这也简化实现),在移除一个文件别名(硬连接)时也存在类似的依赖关系。

创建和删除目录大体和前述的对普通文件的操作相同。不过,因为“..”是子目录指向父目录的连接,这引发了更多的更新依赖。特别地,在创建时,父目录的连接计数必须在新目录的“..”指针之前增加并同步到磁盘。同样,在删除时,父目录的连接计数必须在子目录的“..”指针清零之后减少(当然,这一清零过程在删除子目录时暗含,不真正执行)。

当新的块被分配时,它在位映射表对应的位置将被更新以反映它在用,而块的内容将以新写入的数据或0来初始化。此外,指向新数据块的指针将被加入到i-节点或间接块中(后文会详细说明)。为确保磁盘上的位映射表总反映已分配的资源,位映射表必须在指针之前写盘。同样,由于新分配的磁盘位置未知,规则1规定了新块及指向它的指针之间的更新依赖。因为通过采用同步写入方式保持这一更新依赖关系的方式必将降低数据吞吐量,很多实现中对常规数据块忽略这一过程。这一设计会削弱完整性和安全性,因为新分配的块往往包含已删除的文件数据。有别于此,SoftUpdates将在几乎不损失性能的前提下对块分配实施保护。

维护间接块并不导致本质上不同的更新依赖,但它们在这里讨论很有意义。间接块以及指向间接块的块指针的分配和前面类似,而文件删除,以及明确地释放,对它则更具研究意义。

考虑到i-节点上的引用是确定块是否直接或间接地连在其上唯一方法,清空指向间接块的i-节点指针对于清除所有已声明(said)块的可恢复指针来说已经足够了。一旦指针清零被写盘,则它指向的所有块都可以被释放;只有对文件的截断才会产生间接块指针和块的更新依赖关系。某些FFS实现不包含这一差别,尽管这可能会在删除大文件时极大地延长操作时间。

文件改名时将影响两个目录项。将创建一个新的目录项(包含新的名字)指向相应的i-节点,之后原目录项被移除。规则3确定,新目录项应该在旧目录项移除之前创建,以保证在崩溃后对文件的引用不丢失。如果算上引用计数本身,那么改名操作需要按如下的次序进行四次磁盘更新:增加i-节点的连接计数,创建新的目录项,移除旧的项,减少i-节点的连接计数;如果同名目录项存在,那么它应该先被删除。

更名是POSIX文件操作,换言之希望在多用户环境下这一操作是原子的。有趣的是,POSIX并未要求上述的语义,且多数实现都不能提供它。

在活动的文件系统中位映射表经常发生变化。因此,核心内存中的位映射表副本与磁盘上的经常会不同。如果系统在没把这些差异写盘之前停机,那么某些刚分配的i-节点和块数据就不能在磁盘上反映。为了维持一致性,在系统崩溃之后的启动通常必须运行文件系统检查程序fsck检查文件系统中所有的i-节点,以确定在用的i-节点,并让位映射表处于一致的状态。SoftUpdates的一个额外的好处是,它能够追踪对于位映射表的写
入操作,并利用这一信息确保新分配的i-节点或到新分配的数据块的指针一定会在引用它们的位映射表项之后写入磁盘。这保证了绝不会出现已分配的i-节点或数据块没有在位映射表上标记的情形,从而,不再需要在系统崩溃之后运行fsck。这一特性将在第6节说明。

你可能感兴趣的:(Soft Updates: 用于快速文件系统(FFS)的一项消除大多数同步写操作的技术)