文件的直接访问

动机

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

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

用法

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

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

在 ext2 和 erofs 上启用 DAX

挂载文件系统时,在命令行中使用 -o dax 选项,或者将“dax”添加到 /etc/fstab 中的选项。 这适用于启用文件系统中所有文件的 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. 存在可以覆盖 FS_XFLAG_DAX 以设置 S_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 策略时,对现有常规文件的更改只有在文件被所有进程关闭后才会生效。

详细信息

每个文件有两个 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 标志。

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

块驱动程序编写者的实施技巧

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

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

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

以下块设备可用作灵感来源: - brd:RAM 支持的块设备驱动程序 - pmem:NVDIMM 持久内存驱动程序

文件系统编写者的实施技巧

文件系统支持包括

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

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

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

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

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

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

以下文件系统可用作灵感来源

另请参见

ext2:请参见 The Second Extended Filesystem

另请参见

xfs:请参见 The SGI XFS Filesystem

另请参见

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 文件 mmapped 的用户内存范围调用 get_user_pages() 将失败。 通过添加对驱动程序控制下的页面的可选 struct page 支持,已经在某些设备驱动程序中解决了此问题(有关如何执行此操作的示例,请参见 drivers/nvdimm 中的 CONFIG_NVDIMM_PFN)。 在非 struct page 情况下,从非 DAX 文件对这些内存范围执行 O_DIRECT 读取/写入将失败

注意

O_DIRECT 读取/写入 DAX 文件可以正常工作,关键是正在访问的内存。 在非 struct page 情况下无法工作的其他内容包括 RDMA、sendfile()splice()