文件直接访问

动机

页面缓存通常用于缓冲文件的读写操作。它还用于提供通过 mmap 调用映射到用户空间的页面。

对于类似内存的块设备,页面缓存页将是原始存储的不必要副本。DAX 代码通过直接对存储设备执行读写操作来消除额外的副本。对于文件映射,存储设备直接映射到用户空间。

用法

如果您有一个支持 DAX 的块设备,您可以像往常一样在上面创建文件系统。DAX 代码目前仅支持块大小等于内核 PAGE_SIZE 的文件,因此您可能需要在创建文件系统时指定块大小。

目前,有 5 个文件系统支持 DAX:ext2、ext4、xfs、virtiofs 和 erofs。在它们上启用 DAX 的方式有所不同。

在 ext2 和 erofs 上启用 DAX

挂载文件系统时,在命令行上使用 -o dax 选项,或者在 /etc/fstab 中的选项中添加“dax”。这可以启用文件系统中所有文件的 DAX。它等同于下面的 -o dax=always 行为。

在 xfs 和 ext4 上启用 DAX

总结

  1. 存在一个内核文件访问模式标志 S_DAX,它对应于 statx 标志 STATX_ATTR_DAX。有关此访问模式的详细信息,请参阅 statx(2) 的手册页。

  2. 存在一个持久性标志 FS_XFLAG_DAX,可以应用于常规文件和目录。此建议性标志可以随时设置或清除,但这样做不会立即影响 S_DAX 状态。

  3. 如果在一个目录上设置了持久性 FS_XFLAG_DAX 标志,则此标志将由随后在该目录中创建的所有常规文件和子目录继承。在父目录上设置或清除此标志时存在的目录和子目录不会因对父目录的此修改而修改。

  4. 存在可以覆盖 S_DAX 标志设置中的 FS_XFLAG_DAX 的 dax 挂载选项。给定支持 DAX 的底层存储,以下情况成立:

    -o dax=inode 表示“遵循 FS_XFLAG_DAX”,这是默认设置。

    -o dax=never 表示“永不设置 S_DAX,忽略 FS_XFLAG_DAX。”

    -o dax=always 表示“始终设置 S_DAX,忽略 FS_XFLAG_DAX。”

    -o dax 是一个旧选项,是 dax=always 的别名。

    警告

    选项 -o dax 将来可能会被删除,因此 -o dax=always 是指定此行为的首选方法。

    注意

    即使文件系统以 dax 选项挂载,对 FS_XFLAG_DAX 的修改及其继承行为也保持不变。但是,内核内部 inode 状态 (S_DAX) 将被覆盖,直到文件系统以 dax=inode 重新挂载,并且 inode 从内核内存中逐出。

  5. 可以通过以下方式更改 S_DAX 策略:

    1. 在创建文件之前,根据需要设置父目录的 FS_XFLAG_DAX

    2. 设置适当的 dax=”foo” 挂载选项

    3. 更改现有常规文件和目录上的 FS_XFLAG_DAX 标志。这具有运行时约束和限制,将在下面的 6) 中描述。

  6. 当通过切换持久性 FS_XFLAG_DAX 标志来更改 S_DAX 策略时,对现有常规文件的更改只有在所有进程关闭这些文件后才会生效。

详细信息

每个文件都有 2 个 dax 标志。一个是持久性 inode 设置 (FS_XFLAG_DAX),另一个是表示该功能的活动状态的易失性标志 (S_DAX)。

FS_XFLAG_DAX 保存在文件系统中。可以使用 FS_IOC_FS`[`GS]`ETXATTR` ioctl (参见 ioctl_xfs_fsgetxattr(2)) 或诸如 “xfs_io” 之类的实用程序来设置、清除和/或查询此持久性配置设置。

新文件和目录在创建时会自动从其父目录继承 FS_XFLAG_DAX。因此,可以在目录创建时设置 FS_XFLAG_DAX 来设置整个子树的默认行为。

为了阐明继承,这里有 3 个示例:

示例 A

mkdir -p a/b/c
xfs_io -c 'chattr +x' a
mkdir a/b/c/d
mkdir a/e

------[outcome]------

dax: a,e
no dax: b,c,d

示例 B

mkdir a
xfs_io -c 'chattr +x' a
mkdir -p a/b/c/d

------[outcome]------

dax: a,b,c,d
no dax:

示例 C

mkdir -p a/b/c
xfs_io -c 'chattr +x' c
mkdir a/b/c/d

------[outcome]------

dax: c,d
no dax: a,b

当文件 inode 在内核中实例化到内存中时,将设置当前启用状态 (S_DAX)。它根据底层介质支持、FS_XFLAG_DAX 的值和文件系统的 dax 挂载选项进行设置。

statx 可以用于查询 S_DAX

注意

只有常规文件才会设置 S_DAX,因此 statx 永远不会指示在目录上设置了 S_DAX

即使底层介质不支持 dax 和/或文件系统被挂载选项覆盖,也会发生设置 FS_XFLAG_DAX 标志(专门或通过继承)的情况。

在 virtiofs 上启用 DAX

virtiofs 上 DAX 的语义基本上与 ext4 和 xfs 上的语义相同,但指定了 “-o dax=inode” 时,virtiofs 客户端会通过 FUSE 协议从 virtiofs 服务器推断是否应启用 DAX,而不是持久性 FS_XFLAG_DAX 标志。也就是说,是否应启用 DAX 完全由 virtiofs 服务器决定,而 virtiofs 服务器本身可以部署各种算法来做出此决定,例如,具体取决于主机上的持久性 FS_XFLAG_DAX 标志。

仍然支持在客户机内部设置或清除持久性 FS_XFLAG_DAX 标志,但不能保证随后将为相应文件启用或禁用 DAX。客户机内部的用户仍然需要调用 statx(2) 并检查 statx 标志 STATX_ATTR_DAX 以查看是否为该文件启用了 DAX。

给块驱动程序编写者的实现提示

要在您的块驱动程序中支持 DAX,请实现 “direct_access” 块设备操作。它用于将扇区号(以 512 字节扇区为单位表示)转换为标识内存物理页的页帧号 (pfn)。它还返回一个内核虚拟地址,该地址可用于访问内存。

direct_access 方法采用一个 “size” 参数,该参数指示请求的字节数。该函数应返回可以在该偏移量处连续访问的字节数。如果发生错误,它也可能返回负的 errno。

为了支持此方法,CPU 必须始终可以字节访问存储。如果您的设备使用分页技术通过较小的窗口公开大量内存,则您无法实现 direct_access。同样,如果您的设备偶尔会使 CPU 停顿很长时间,您也不应尝试实现 direct_access。

这些块设备可用于获取灵感:- brd:RAM 支持的块设备驱动程序 - dcssblk:s390 dcss 块设备驱动程序 - pmem:NVDIMM 持久内存驱动程序

给文件系统编写者的实现提示

文件系统支持包括:

  • 通过在 i_flags 中设置 S_DAX 标志来添加支持以将 inode 标记为 DAX

  • 实现 ->read_iter 和 ->write_iter 操作,当 inode 设置了 S_DAX 标志时,使用 dax_iomap_rw()

  • DAX 文件实现 mmap 文件操作,该操作在 VMA 上设置 VM_MIXEDMAPVM_HUGEPAGE 标志,并将 vm_ops 设置为包括故障、pmd_fault、page_mkwrite 和 pfn_mkwrite 的处理程序。这些处理程序可能应调用 dax_iomap_fault(),并传递适当的故障大小和 iomap 操作。

  • 对于 DAX 文件,调用 iomap_zero_range() 并传递适当的 iomap 操作,而不是 block_truncate_page()

  • 确保读取、写入、截断和页面错误之间有足够的锁定

用于分配块的 iomap 处理程序必须确保在返回之前将分配的块归零并转换为写入的范围,以避免通过 mmap 暴露未初始化的数据。

这些文件系统可以用来获取灵感

另请参阅

ext2:请参阅 第二扩展文件系统

另请参阅

xfs:请参阅 SGI XFS 文件系统

另请参阅

ext4:请参阅 Documentation/filesystems/ext4/

处理介质错误

libnvdimm 子系统为每个 pmem 块设备(在 gendisk->badblocks 中)存储已知介质错误位置的记录。如果我们在这样的位置发生错误,或者在尚未发现的潜在错误位置发生错误,应用程序可能会收到 SIGBUS 信号。Libnvdimm 还允许通过简单地写入受影响的扇区来清除这些错误(通过 pmem 驱动程序,并且如果底层 NVDIMM 支持 ACPI 定义的 clear_poison DSM)。

由于 DAX IO 通常不通过 driver/bio 路径,应用程序或系统管理员可以选择以下方式从先前的 backup/inbuilt 冗余中恢复丢失的数据:

  1. 删除受影响的文件,并从备份中恢复(系统管理员方式):这将释放该文件正在使用的文件系统块,并且下次分配它们时,它们将首先被清零,这通过驱动程序完成,并且将清除坏扇区。

  2. 截断或挖空文件中包含坏块的部分(至少必须挖空整个对齐的扇区,但不一定必须挖空整个文件系统块)。

这是允许 DAX 文件系统在存在介质错误的情况下继续运行的两种基本方式。将来可以在此基础上构建更强大的错误恢复机制,例如,通过 DM 在块层提供冗余/镜像,或者在文件系统层提供冗余/镜像。这些机制必须依赖于上述两个原则,即可以通过发送 IO 通过驱动程序或清零(也通过驱动程序)来清除错误。

缺点

即使内核或其模块存储在支持 DAX 的文件系统上,该文件系统位于支持 DAX 的块设备上,它们仍将被复制到 RAM 中。

DAX 代码在具有虚拟映射缓存的体系结构(如 ARM、MIPS 和 SPARC)上无法正常工作。

当没有 'struct page' 来描述这些页面时,在从 DAX 文件 mmap 的用户内存范围内调用 get_user_pages() 将会失败。一些设备驱动程序通过为驱动程序控制下的页面添加可选的 struct page 支持来解决此问题(有关如何执行此操作的示例,请参阅 drivers/nvdimm 中的 CONFIG_NVDIMM_PFN)。在非 struct page 情况下,从非 DAX 文件对这些内存范围进行 O_DIRECT 读取/写入将会失败。

注意

O_DIRECT 读取/写入 DAX 文件确实有效,关键在于正在访问的内存)。在非 struct page 情况下,其他不起作用的操作包括 RDMA、sendfile()splice()