本文根据 https://lwn.net/Articles/712467/ 翻译改编而来。
随着3DX POINT相关技术的逐渐普及和推广,我们可以预见很快持久化内存(Persistent Memory)将会被使用得越来越多,同时也催生了Linux内核很多方面的革新,Matthew Wilcox的DAX就是其中非常著名的一个,这个东东可以让用户对于基于持久化内存的文件系统跳过页面缓存(Page Cache)直接访问文件系统,而Dave Chinner甚至预测有了DAX,我们将不再需要页面缓存了。这个从1995年开始存在于Linux内核并造福无数苍生的玩意难道要寿终正寝了么?作为DAX的始作俑者,Matthew Wilcox又是怎么认为的?让我们一起去听听他在今年linux.conf.au上的演讲。
缓存的重要性
首先计算机就是缓存的世界,君不见体系结构领域里面关于cache相关的paper一直是层出不穷,此起彼伏。Wilcox甚至回过头查阅了1975年发行的Unix第六版,并在那里找到了使用缓冲区缓存的例子。同时Wilcox举例说只要缓存都命中,他的新电脑每秒可以执行100亿条指令。但是内存每秒只能跑5亿3千万条cache line,因此缓存未命中就会严重影响性能。如果数据没有缓存到主存,需要从存储设备读, 即使是快速的SSD,也会变得很慢。 PDP-11会因缓存未命中而显著变慢,几十年过去了,这个问题反而恶化了。因为CPU的发展速度比内存快,而同时内存的发展速度相比存储也更快,所以缓存未命中的带来的性能损失也会越来越严重。
什么是Linux的页面缓存
如前文所述,很久以来,Unix系统都有缓冲区缓存(Buffer Cache),位于文件系统与磁盘之间,目的是为了缓存磁盘块到内存中。Linux从一开始就有个缓冲区缓存。在1995年发行的1.3.50版本中,Linus Torvald做了一个重大创新——页面缓存。页面缓存与缓冲区缓存的区别在于,它是位于虚拟文件系统(VFS)与文件系统本身之间。有了页面缓存,如果所需的页面已经存在,则根本无需调用文件系统的代码。起初,页面缓存和缓冲区缓存是完全独立的,在1999年,Ingo Molnar统一了它们。现在,缓冲区缓存仍然存在,但是内容是指向页面缓存。
页面缓存有许多非常重要的功能,比如可以通过给定的索引查找页面。如果页面不存在,则创建并从磁盘填充相应的内容。脏页可以刷回磁盘,页面可以被锁定,解锁,以及从缓存中删除。线程可以等待页面状态的变化,也可以通过接口对给定状态的页面进行搜索,页面缓存还能够追踪与外部存储相关的错误等等。
另外页面缓存还需要有一套手段来对未来的使用进行预测。当缓存增长太大时,各种启发算法开始决策哪些页面应当被移除。仅使用了一次的页面很可能不会被再使用,因此它们将保留在“不活跃”链表(inactive list)并相对较快的换出。第二次使用将会把页面从不活跃链表转移到活跃链表(active list),活跃链表的页也会因为超时而被移到不活跃列表。 有一个例外,“影子”条目用于追踪已脱离不活跃链表并已回收的页面,这些条目可以延长相对遥远的过去使用过的页的生命周期。
随着内存变大,大页和透明大页也开始普及起来。不过透明大页(THP)特性最初只能用于匿名(非文件后端)内存,把大页在页面缓存中使用同样也会有很多优点。不过由于radix tree的限制,将大页作为页面缓存是Linux内核的一个挑战。最早的一个尝试是简单地往页面缓存中增加了大量单页条目,以对应于单个大页。Wilcox认为这种方法是“愚蠢的”,他增强了用于追踪页面缓存中页面的radix tree代码,使得页面缓存可以使用单个条目来表示对应的大页,从而使radix tree能够直接处理大页的条目。
是否仍然需要页面缓存?
介绍完页面缓存光辉的历史和强大的功能以后,我们回到开头的问题,现在Linux内核是否还需要页面缓存呢?由于Wilcox的DAX实现,Dave Chinner预测我们将不再需要页面缓存。当然同样也有其他人不同意Chinner,Linus Torvalds也是其中之一。Torvalds在一个单独的论坛中指出页面缓存很重要,因为在数据访问的关键路径上,好的东西从来不是出自低层的文件系统代码(译者作为一个文件系统开发者,表示很受伤,附上文中Torvalds的原话:the page cache is important because good things don’t come from having low-level filesystem code in the critical path for data access)。
对于是否需要页面缓存,DAX的作者Wilcox是咋想的呢?他说“没有什么比你的同事怀疑你的整个动机更糟糕的了”,所以貌似他也不是想干掉页面缓存。但是等等,为啥Dave Chinner会得出与之相反的结论呢?
原来Wilcox在设计他的DAX代码的时候,其实真的没有使用页面缓存。当一个应用使用类似于read()的系统调用从存储在持久化内存中的文件中读取数据时,DAX会介入。由于请求的数据不存在于页面缓存中,VFS层调用文件系统特定的read_iter()函数。这反过来调用到DAX代码,它将回调到文件系统将文件偏移转换为块号,然后查询块层以获取持久化内存块的位置(如果需要,将其映射到内核地址空间),最终使得块内容可以被拷贝回应用。
但是现在Wilcox觉得当初他的这个设计是错误的,read()应该以另外一种方式工作,初始的步骤是相同的,因为read_iter()函数仍将被调用,同时它将调用到DAX代码。但是,DAX不是回调到文件系统,而是应当调用到页面缓存以获取文件中所需偏移关联的物理地址,然后数据从该地址拷贝到用户空间。这样的逻辑就和现在页面缓存的工作原理完全一致了,而且如果信息已经存在页面缓存中的情况下,低层文件系统的代码完全无需介入。
Wilcox在这里又再次引用了Torvalds关于页面缓存的帖子:
从锁的角度来看,这也是一个重大的灾难:相信我,如果你认为你的文件系统可以进行细粒度的锁,当诸如并发路径查找的事情到来时,你会生活在一个梦幻世界。
Torvalds先生的话是“如此正确”,Wilcox说。DAX中的锁实现的确是灾难性的。他最初认为可能用相对简单的锁来解决,但复杂性在每个新发现的边缘场景蔓延。DAX的锁实现现在“实在丑陋”,他很抱歉他犯了一个错误,认为可以绕过页面缓存。现在,他说他必须去弥补这个错误。
未来的工作
Wilcox同时总结了围绕DAX和页面缓存的一系列增强。前文提到的大页支持优化是其中之一,这已经在mm树中,应该很快就可以完成。使用页框数而不是页面也已经讨论了一段时间,因为让内核为这些大型持久化内存保持大量的页面结构体(struct page)是完全没有必要的。
另外他想重新考虑文件系统块尺寸大于系统页面尺寸的想法,这是人们多年想要的东西。现在既然页面缓存可以处理多个页面大小,这个想法应该已经有希望了。不过他自己没时间折腾这个,所以他正在找寻其他感兴趣的开发人员一起来做这个项目。
交换大页也是一个不错的领域。我们已经在内存中使用了大页,但当这些大页被换出时,他们又被分解成普通尺寸的页面。针对这一问题,目前已经有一些提升交换性能的工作在进行,但是这个解法从根上来看可能就是不对的,正确的解法应该是让交换出去的大页保持在一起。相应的,这同样也有助于我们将内存交换到持久化内存。因为使用持久化内存的交换空间中的数据仍然可以被访问,因此将其留在那可能是有意义的,尤其是当数据没有被大量修改的时候。
最后附上Wilcox的演讲视频,视频中还包含了页面缓存锁的相关分析和讨论。
结论
所以貌似即使我们有了持久化内存,页面缓存还会继续存在下去。但是译者觉得随着持久化内存速度越来越快,价格越来越便宜,也许内核社区会有一些新的想法也说不定,让我们拭目以待吧。