@窅默

NOVA文件系统

非易失性内存是一种保证在掉电不丢数据的同时能够快速按照字节寻址访问的存储。为了利用好这个新型存储,我们需要某种树形结构来存储数据。一般情况下我们会想到用文件系统,理由么很简单,因为它天生就是干这个的。不过悲剧的是传统文件系统并不能完美的匹配这些非易失性内存,所以很自然的就有人想针对这玩意设计一种新的文件系统,而NOVA则是其中一个尝试。

让我们先来看看通用文件系统在设计时一系列特定的假设,比如存储的访问速度会很慢,所以我们要尽量通过消耗一定的CPU和内存来减少对底层设备的访问。再比如一般的硬盘在随机操作时性能很差,所以我们要尽量让数据布局比较连续。还有就是针对扇区做IO是原子操作,要么整个扇区被写入,要么保持不变。所有这些假定对大多数文件系统的设计影响很深,不过很抱歉这些对非易失性内存设备来说基本都是不正确的。所以尽管诸如XFS或ext4之类的文件系统可以在这种设备上得到明显的加速,但从头开始针对非易失性内存设计一个新的文件系统将会更加明智。 于是乎,NOVA出现了,它的革命很彻底,因为它已经不是一个常规的块设备,而且根本无法使用内核的块层。它直接将存储映射到内核的地址空间,这样既节省了块层带来的开销,又降低了CPU的开销。当然避开块层的另一个效果是放弃掉目前很多块层的功能,比如请求合并,队列管理,请求优先级等等,下面让我们仔细分析看看。

NOVA文件系统结构

像大多数文件系统一样,NOVA以一个超级块开始(这一点上还是类似的,善哉善哉),它描述了文件系统的顶层数据结构同时也提供了其他数据结构的位置信息。以inode为例(inode是文件系统中文件或目录的内部表现形式,大家都懂得哈),NOVA的inode表被设置为以PER CPU来组织的数组,这样任何CPU在分配新的inode的时候就不再需要对其他CPU上锁。

NVM的可用空间同样被打散到不同的CPU中,每个CPU都用了一棵红黑树进行管理,这样可以加速空闲区域的合并。与inode表不同的是空闲列表维护在普通RAM而不是非易失性内存中。它们在文件系统卸载(umount)的时候被写回NVM,如果文件系统未能正确卸载,那么空闲列表将只能通过扫描整个文件系统进行重建。

下面我们来看看NOVA最有趣的地方,也就是inode的存储方式。在像ext4这样的文件系统上,磁盘inode是包含许多文件元数据的明确定义的结构。而为了达到更快的性能,NOVA则采取了基于日志的做法,结果是inode表中的一个inode只是一对指向数据结构的指针,看起来像是这样的。

File:Nova-inode.png

每个有意义的inode元数据包含对该文件所做修改的日志,而inode结构中的所有信息就是一对指针,分别指示第一个和最后一个有效的日志条目。这些条目在非易失性内存中以至少4KB的chunk存在,以链表形式组织起来。每个日志条目指示一个事件,比如:

文件属性被修改 —— 例如,权限位修改。

    目录中添加一个条目(显然是针对目录inode)。
    文件的链接被添加。
        数据被写回到文件中。 

	下面重点讲讲写数据的场景。如果一个进程写入了一个空文件,NOVA从每CPU空闲列表中分配所需的内存,拷贝数据到该空间。然后将追加一个条目到inode日志中指示文件新长度,同时指向数组中数据写入的位置。最后,inode尾部指针原子更新并使得这个操作全局可见。That's it! 再来看看覆盖写的场景,事情将会有些不同。由于NOVA是写时复制文件系统,因此第一步是再次为新数据分配新的非易失性内存。如果需要,数据会从旧页拷贝到新页,然后添加新的数据。最后指向新页的条目将被添加日志中,尾部指针被更新,并且这些页的旧日志条目被无效。到这里操作就完成了,旧的数据页以后可以被释放以重新使用。

	因此,NOVA中的磁盘inode其实并不是真正意义上对一个文件的元数据的描述,而可以认为是一组对inode的操作集合,当我们按照顺序(跳过无效的)执行这些操作将产生文件元数据的一个完整描述。使用这种存储方式的优点在是当文件修改时只需最小的锁就可以达到最快的更新,同时显而易见访问文件时我们的速度会慢一些。针对这个慢的问题,NOVA通过在文件打开时在内存中生成并保存一份文件元数据来缓解,这个动作其实也不慢,因为整个操作都是可以由CPU直接寻址来操作,这也算是NVM的一个特点吧。所以如果我们将这类结构存储在旋转磁盘上,或者普通块的SSD上,都将会是灾难。

	这样的日志结构同时还具备另外一个有趣的特性,那就是日志中的每个条目都包含了创建条目时设置的“纪元编号”(epoch number)。这样我们创建快照将非常简单,我们只需要增加全局纪元编号并将先前的编号与指向快照的指针关联起来就可以了。当快照挂载时,可简单忽略掉纪元编号大于快照编号的任何日志条目,这样就可以获得打快照时文件存在的视图。当然这里还有一些细节需要打磨,不过这仍然是该问题一个非常优雅的解决方案。

	DAX及其以后

	读者可能很想知道NOVA如何与内核的DAX接口交互,该接口允许应用程序将非易失性内存中的文件直接映射到它们的地址空间中,从而在将来的访问时完全绕过内核。从上面的分析我们可以看出,对于一个COW文件系统我们很难利用DAX。在2016年介绍NOVA的论文中PDF,作者也说他们没有尝试过DAX,而是支持一种被称为“原子mmap”的替代机制,其将数据拷贝到“副本页面”并反过来映射它们。某种意义上,原子mmap部分实现了页面缓存的功能。

	能直接访问非易失性内存一直是NVM最引入瞩目的特性之一,所以没有DAX大家会很失望的。貌似作者也懂的,所以这次提交给社区的补丁已经声称支持DAX了。从这次提交的文档和代码来看,NOVA通过对已经映射到进程地址空间的文件部分禁用COW来实现这个功能,这样对文件的修改就可以在原地展开了。当然这样做的一个重要的缺陷是对于这些已经映射到进程地址空间的文件页将无法通过write()写入(原因前面说过啦,因为write需要用到COW这个机制啦)。解决这个问题可能需要一些比较复杂的逻辑,在另一篇论文中有详细的描述PDF。

	NOVA内置了一些自我保护措施,比如数据和元数据的checksuming,然而似乎这个特性引起了不小的争议。当你把整个存储阵列映射到内核地址空间后写入一个杂乱的指针就有可能直接破坏持久化的数据。这在bug-free的内核将不是一个问题,但显然现实是残酷的。。。为了防止无意的数据覆盖,NOVA可将整个阵列映射为只读。当必须进行修改时,处理器的写保护位将暂时清除,允许内核绕过内存权限来操作。禁止写保护这个动作在过去一直被认为非常危险,现在也依然不受欢迎。所以看起来NOVA要想想别的办法了。

	在NOVA被认真考虑合并到上游前,还有其他一些事情需要解决。例如,它仅适用x86-64架构,且由于使用了PER CPU的inode表结构,当两台机器的CPU数量不同时将无法将NOVA文件系统从一个系统移动到另外一个。另外NOVA还不支持ACL或者磁盘配额,也没有文件系统的检查工具等等。开发者已经知道了这些问题并且后面会处理它们。

	文件系统开发者注意到这些细节并且努力使他的文件系统合并至内核的行为是令人鼓舞的,尤其当这个情况发生在学术界的时候(确切的说是加州大学圣地亚哥分校)。因为一般情况下学术工作在论文出版和捐赠资金用完后就会停下来,所以通常情况下自由软件从大学获取的源码远远低于人们的期望。但愿NOVA可以引领一个新的潮流。

	写到这里就差不多了,这个文件系统的很多其他方面还请参阅上面提到两篇论文以及补丁自身的文档。NOVA将会是一个值得关注的项目。如果进展顺利它将让我们能够从巨大、快速的非易失性内存阵列中获得卓越的性能,希望这一天快点到来。