Fiemap Ioctl

fiemap ioctl 是一种用户空间获取文件范围映射的有效方法。与逐块映射(例如 bmap)不同,fiemap 返回一个范围列表。

请求基础

fiemap 请求在 struct fiemap 中编码

struct fiemap {
      __u64   fm_start;        /* logical offset (inclusive) at
                                * which to start mapping (in) */
      __u64   fm_length;       /* logical length of mapping which
                                * userspace cares about (in) */
      __u32   fm_flags;        /* FIEMAP_FLAG_* flags for request (in/out) */
      __u32   fm_mapped_extents; /* number of extents that were
                                  * mapped (out) */
      __u32   fm_extent_count; /* size of fm_extents array (in) */
      __u32   fm_reserved;
      struct fiemap_extent fm_extents[0]; /* array of mapped extents (out) */
};

fm_start 和 fm_length 指定进程希望获取映射的文件内的逻辑范围。返回的范围与磁盘上的范围相对应 - 也就是说,第一个返回的范围的逻辑偏移量可能在 fm_start 之前开始,并且最后一个返回的范围所覆盖的范围可能在 fm_length 之后结束。所有偏移量和长度都以字节为单位。

可以在 fm_flags 中设置某些标志来修改查找映射的方式。如果内核不理解某些特定标志,它将返回 EBADR 并且 fm_flags 的内容将包含导致错误的标志集。如果内核与传递的所有标志兼容,则 fm_flags 的内容将保持不变。用户空间需要确定拒绝某个特定标志是否对其操作至关重要。此方案旨在允许 fiemap 接口在未来扩展,但不会丢失与旧软件的兼容性。

fm_extent_count 指定 fm_extents[] 数组中可用于返回范围的元素数量。如果 fm_extent_count 为零,则忽略 fm_extents[] 数组(不会返回任何范围),并且 fm_mapped_extents 计数将保存 fm_extents[] 中保存文件的当前映射所需的范围数。请注意,没有任何措施可以阻止文件在调用 FIEMAP 之间发生更改。

可以在 fm_flags 中设置以下标志

FIEMAP_FLAG_SYNC

如果设置了此标志,内核将在映射范围之前同步文件。

FIEMAP_FLAG_XATTR

如果设置了此标志,则返回的范围将描述 inode 的扩展属性查找树,而不是其数据树。

范围映射

范围信息在嵌入的 fm_extents 数组中返回,用户空间必须与 fiemap 结构一起分配该数组。fiemap_extents[] 数组中的元素数量应通过 fm_extent_count 传递。内核映射的范围数量将通过 fm_mapped_extents 返回。如果分配的 fiemap_extents 数量少于映射请求范围所需的数量,则将在 fm_extent[] 数组中返回可映射的最大范围数量,并且 fm_mapped_extents 将等于 fm_extent_count。在这种情况下,数组中的最后一个范围不会完成请求的范围,并且不会设置 FIEMAP_EXTENT_LAST 标志(请参阅下一节有关范围标志的内容)。

每个范围都由单个 fiemap_extent 结构描述,如 fm_extents 中返回的

struct fiemap_extent {
        __u64       fe_logical;  /* logical offset in bytes for the start of
                            * the extent */
        __u64       fe_physical; /* physical offset in bytes for the start
                            * of the extent */
        __u64       fe_length;   /* length in bytes for the extent */
        __u64       fe_reserved64[2];
        __u32       fe_flags;    /* FIEMAP_EXTENT_* flags for this extent */
        __u32       fe_reserved[3];
};

所有偏移量和长度都以字节为单位,并与磁盘上的偏移量和长度相对应。范围的逻辑偏移量在请求之前开始或其逻辑长度超出请求范围是有效的。除非返回 FIEMAP_EXTENT_NOT_ALIGNED,否则 fe_logical、fe_physical 和 fe_length 将与文件系统的块大小对齐。除了标记为 FIEMAP_EXTENT_MERGED 的范围之外,相邻的范围不会被合并。

fe_flags 字段包含描述返回范围的标志。文件中的最后一个范围始终会设置一个特殊的标志 FIEMAP_EXTENT_LAST,以便进行 fiemap 调用的进程可以确定何时没有更多可用的范围,而无需再次调用 ioctl。

某些标志有意含糊不清,并且在存在其他更具体的标志时始终会设置。这样,查找通用属性的程序不必知道所有现有和未来暗示该属性的标志。

例如,如果设置了 FIEMAP_EXTENT_DATA_INLINE 或 FIEMAP_EXTENT_DATA_TAIL,则也会设置 FIEMAP_EXTENT_NOT_ALIGNED。查找内联或尾部打包数据的程序可以使用特定标志。但是,只是不想尝试操作未对齐范围的软件可以使用 FIEMAP_EXTENT_NOT_ALIGNED,而不必担心所有现在和未来可能暗示未对齐数据的标志。请注意,反之则不然 - FIEMAP_EXTENT_NOT_ALIGNED 单独出现是有效的。

FIEMAP_EXTENT_LAST

这通常是文件中的最后一个范围。尝试映射超过此范围可能会返回空值。某些实现设置此标志来指示此范围是用户(通过 fiemap->fm_length)查询的范围中的最后一个范围。

FIEMAP_EXTENT_UNKNOWN

此范围的位置当前未知。这可能表明数据存储在无法访问的卷上,或者尚未为该文件分配任何存储空间。

FIEMAP_EXTENT_DELALLOC

这也会设置 FIEMAP_EXTENT_UNKNOWN。

延迟分配 - 虽然此范围有数据,但其物理位置尚未分配。

FIEMAP_EXTENT_ENCODED

此范围不包含普通文件系统块,而是经过编码(例如,加密或压缩)的。通过 I/O 读取块设备中此范围的数据将导致未定义的结果。

请注意,在没有文件系统协助的情况下,尝试通过写入指定位置来更新数据,或在使用 FIEMAP 接口返回的信息访问数据,同时文件系统处于挂载状态,这始终是未定义的。换句话说,用户应用程序只能在文件系统卸载时通过 I/O 读取块设备中的范围数据,并且只有在 FIEMAP_EXTENT_ENCODED 标志未设置时才允许这样做;在任何其他情况下,用户应用程序都不得尝试通过块设备读取或写入文件系统。

FIEMAP_EXTENT_DATA_ENCRYPTED

这也会设置 FIEMAP_EXTENT_ENCODED 此范围中的数据已由文件系统加密。

FIEMAP_EXTENT_NOT_ALIGNED

不保证范围偏移量和长度与块对齐。

FIEMAP_EXTENT_DATA_INLINE

这也会设置 FIEMAP_EXTENT_NOT_ALIGNED 数据位于元数据块中。

FIEMAP_EXTENT_DATA_TAIL

这也会设置 FIEMAP_EXTENT_NOT_ALIGNED 数据与来自其他文件的数据打包到一个块中。

FIEMAP_EXTENT_UNWRITTEN

未写入的范围 - 该范围已分配,但其数据尚未初始化。这表示如果通过文件系统读取,该范围的数据将全部为零,但如果直接从设备读取,则内容未定义。

FIEMAP_EXTENT_MERGED

当文件不支持范围时,将设置此项,即它使用基于块的寻址方案。由于将每个块的范围返回到用户空间效率低下,因此内核会尝试将大多数相邻的块合并为“范围”。

VFS -> 文件系统实现

希望支持 fiemap 的文件系统必须在其 inode_operations 结构上实现 ->fiemap 回调。fs ->fiemap 调用负责定义其支持的 fiemap 标志集,并调用每个发现的范围的帮助程序函数

struct inode_operations {
     ...

     int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start,
                   u64 len);

->fiemap 传递 struct fiemap_extent_info,该结构描述 fiemap 请求

struct fiemap_extent_info {
      unsigned int fi_flags;          /* Flags as passed from user */
      unsigned int fi_extents_mapped; /* Number of mapped extents */
      unsigned int fi_extents_max;    /* Size of fiemap_extent array */
      struct fiemap_extent *fi_extents_start; /* Start of fiemap_extent array */
};

文件系统不应该直接访问此结构的任何部分。文件系统处理程序应能容忍信号,并在收到致命信号后返回 EINTR。

应通过 fiemap_prep() 帮助程序在 ->fiemap 回调的开头执行标志检查

int fiemap_prep(struct inode *inode, struct fiemap_extent_info *fieinfo,
                u64 start, u64 *len, u32 supported_flags);

struct fieinfo 应按从 ioctl_fiemap() 接收到的方式传递。文件系统理解的 fiemap 标志集应通过 fs_flags 传递。如果 fiemap_prep 发现无效的用户标志,它将把错误值放入 fieinfo->fi_flags 中,并返回 -EBADR。如果文件系统从 fiemap_prep() 收到 -EBADR,它应立即退出,并将该错误返回给 ioctl_fiemap()。此外,该范围还会根据支持的最大文件大小进行验证。

对于请求范围内的每个范围,文件系统应调用帮助程序函数 fiemap_fill_next_extent()

int fiemap_fill_next_extent(struct fiemap_extent_info *info, u64 logical,
                            u64 phys, u64 len, u32 flags, u32 dev);

fiemap_fill_next_extent() 将使用传递的值来填充 fm_extents 数组中的下一个可用范围。将代表调用的文件系统自动从特定标志设置“常规”范围标志,以便用户空间 API 不会被破坏。

fiemap_fill_next_extent() 成功时返回 0,当用户提供的 fm_extents 数组已满时返回 1。如果在将范围复制到用户内存时遇到错误,将返回 -EFAULT。