https://lwn.net/Articles/731706/ 文卿

持久性内存在不久的将来将会为我们提供可以字节寻址的高速存储能力,最近几年内核社区一直在致力于完善对持久性内存的支持上。而这其中有一个问题仍然没有很好的解决,即应用程序直接将数据写入文件系统管理的持久性内存上。直到现在仍然没有一个方案合并到内核主线,但是内核开发者们仍然在不断尝试。目前就有2组补丁在尝试解决上述问题。

现在的文件系统会控制所有下发给存储介质的I/O请求,以此来确保文件系统结构的一致。即便将传统设备上的一个文件映射到进程虚拟地址空间时,文件系统仍然会管理脏页从缓存回写到持久存储设备上的操作。而直接跳过文件系统将持久性内存上的文件映射到进程地址空间后,会造成一系列潜在的问题,比如文件系统元数据不一致,数据的损坏和丢失。要解决这一问题又需要借助文件系统本身,而这与DAX(direct access)特性又是相违背的。

此前已经有一些解决方案尝试解决该问题,包括一个特殊的”我知道我在做什么”标志以及最近提出的通过daxctl系统调用冻结文件元数据以便数据可以原地修改。但上面的方案都不完善,因此内核开发者们又坐回键盘前开发新的解决方案了。 [编辑] 同步缺页中断

同步缺页中断是Jan Kara提交的解决方案。该方案继承了此前尝试的思路,即在文件系统元数据写操作完成前不允许对该文件数据的直接修改。而解决方案在mmap()系统调用中引入了一个新的标志MAP_SYNC来要求这一同步行为。

该标志提供了如下的语义:一个磁盘块被可写的映射到进程地址空间时,即便系统宕机后该位置的数据也依然可见。

换句话说文件系统不会偷偷移动磁盘块,这就意味着文件系统在系统宕机后依然可以确保元数据的一致性。要实现上è¿°语义就要确保所有元数据写操作都要在进程修改对应位置的内存页前完成。

当持久性内存区域通过MAP_SYNC标志映射到进程中时,内存管理代码会检查该段区域是否有未完成的元数据写操作。如果确实有未完成的元数据写操作,则这段区域会被操作系统映射为只读方式并带有一个特殊的记号。这样当进程第一次尝试向这段区域写入数据时就会触发缺页中断。处理缺页的时候就会将相关元数据同步的写入到持久内存中并设置对应的内存页为可写。这样就确保了进程可以安全的通过内存映射方式将数据写入持久性内存中。

这一方式相较于每次写数据前都调用fsync()系统调用自然要好很多。而副作用就是每次写操作都会带来额外的元数据刷新操作,进而引入额外的操作延迟。 [编辑] MAP_DIRECT

另一个解决方案来自Dan Williams的MAP_DIRECT方案。该方案以daxctl()方案为基础,与daxctl()方案的不同是不再需要单独的系统调用,而是在mmap()系统调用中引入MAP_DIRECT标志。为了避免Jan Kara方案中额外的写入延迟,Dan的方案在文件映射时设置文件到一个“密封(sealing)”状态。

当文件系统处理带有MAP_DIRECT标记的mmap()系统调用时,会确保相关元数据处于一致状态。一旦该文件被映射到进程内存地址空间中,文件系统就会拒绝任何对元数据造成影响的写操作。比如移动磁盘块,截断(truncate)文件,分配磁盘块等等操作都将失败。这意味着该文件一旦被映射就不能再分配新的磁盘块。因此应用程序需要确保相关磁盘块在映射前分配好。

使用MAP_DIRECT标志的应用程序需要内核提供一个明确的信息确保文件被“密封”好,但是mmap()系统调用恰恰是不能提供上述功能的系统调用之一。因为mmap()系统调用并不会去检查那些不能识别的标志。也就是说在旧版本内核上使用MAP_DIRECT标志应用程序将得不到任何错误提示。直观的解决方法是再定义一个新的mmap3()系统调用,该系统调用在遇到不能识别的标志时会返回错误。在实现该系统调用时,会在file_operations结构中添加一个mmap_supported_mask域以便底层实现指定可以处理的标志。让应用去修改代码使用新的系统调用并不是完美的解决方案,但是想不修改ABI而解决上述问题,似乎也没有更好的方法。

为了使用MAP_DIRECT标志的应用程序需要具备CAP_LINUX_IMMUTABLE能力。如果不做这一限制,恶意程序可能通过该标志将文件密封从而对系统进行拒绝服务攻击。因为其他进程可能需要对该文件进行修改。这样å°±限制了MAP_DIRECT标志的可用性。目前方案的解决办法是在fcntl()中添加F_MAP_DIRECT标志。特权进程通过fcntl()系统调用设置一个已经打开的进程描述符,并将该文件描述符传递给普通进程以便普通进程使用。

MAP_DIRECT方案的一个优势是除了提供应用程序高速访问持久性内存的能力外,内核可以借助密封机制提供文件级别的换入换出(swap)功能,以及为用户态驱动提供DMA I/O的能力。

经过几轮修改后,2个方案都已经得到大部分开发者的认可。接下来就需要讨论2个方案哪个更好,是否存在融合两种方案的可能等等问题。至少我们看到直接写入持久性内存的方案进入内核主线代码的时间不远了。