FIDEDUPERANGE ioctl 在 btrfs 上的行为不符合预期

FIDEDUPERANGE ioctl 在 btrfs 上的行为不符合预期

根据ioctl_fideduperange,

最大尺寸为src_length取决于文件系统,通常为 16 MiB。

然而,我已经能够src_length通过一次调用成功使用> 1 Gib ioctl。关于 16 MiB 的警告是否完全是夸大其词,至少对于btrfs

另外,根据VFS 文档,

实现必须处理传入 len == 0 的调用者;这意味着“重新映射到源文件的末尾”。

但是,当我尝试设置src_length为时0ioctl呼叫成功但没有执行任何操作。

我是否误读了这两句话,或者btrfs实现根本不符合(很好)文档?我正在使用 kernel 的 Linux Mint 20 上进行测试5.4.0-62-generic。我用来filefrag -sv FILE1 FILE2检查文件的块级分配,看看它们是否重复。我正在使用下面的程序来删除重复的文件。有问题的文件位于btrfs使用sudo mkfs.btrfs -mraid1 -draid1 /dev/mapper/sda1_crypt /dev/mapper/sdb1_crypt.

设想:

$ cp -f file1 file2
$ filefrag -sv file1 file2   # see that files use different extents (are not deduplicated)
$ myprog file1 file2
$ filefrag -sv file1 file2   # see that files use the same extents (have been deduplicated)

删除两个文件重复的程序:

// deduplicate srcfile and targetfile if contents are identical
// usage:  myprog srcfile targetfile
// compile with:  gcc myprog.c -o myprog

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <linux/fs.h>

int main(int argc, char**argv)
{
   struct stat st;
   long size;
   __u64 buf[2048];        /* __u64 for proper field alignment */
   struct file_dedupe_range *range = (struct file_dedupe_range *)buf;

   memset(range, 0, sizeof(struct file_dedupe_range));
   memset(&range->info, 0, sizeof(struct file_dedupe_range_info));

   long srcfd = open(argv[1], O_RDONLY);
   if (srcfd < 0) { perror("open-src"); exit(1); }
   if (fstat(srcfd, &st) < 0) { perror("stat-src"); exit(1); }
   size = st.st_size;

   long tgtfd = open(argv[2], O_RDWR);
   if (tgtfd < 0) { perror("open-tgt"); exit(1); }
   if (fstat(tgtfd, &st) < 0) { perror("stat-tgt"); exit(1); }
   if (size != st.st_size) {
      fprintf(stderr, "SIZE DIFF\n");
      exit(1);
   }

   range->src_offset = 0;
   range->src_length = size;
// range->src_length = 0;                    // I expected this to work
   range->dest_count = 1;
   range->info[0].dest_fd = tgtfd;
   range->info[0].dest_offset = 0;

   while (range->src_length > 0) {
      if (ioctl(srcfd, FIDEDUPERANGE, range) < 0) { perror("ioctl"); exit(1); }

      fprintf(stderr, "bytes_deduped: %llu\n", range->info[0].bytes_deduped);
      fprintf(stderr, "status: %d\n", range->info[0].status);
      if (range->info[0].status == FILE_DEDUPE_RANGE_DIFFERS) {
         fprintf(stderr, "DIFFERS\n");
         break;
      } else if (range->info[0].status == FILE_DEDUPE_RANGE_SAME) {
         fprintf(stderr, "SAME\n");
      } else {
         fprintf(stderr, "ERROR\n");
         break;
      }

      if (range->info[0].bytes_deduped >= range->src_length) { break; }
      range->src_length -= range->info[0].bytes_deduped;
      range->src_offset += range->info[0].bytes_deduped;
      range->info[0].dest_offset += range->info[0].bytes_deduped;
   }
   exit(0);
}

答案1

旧版本btrfs(例如 4.15)的每次FIDEDUPERANGE调用有 16 MiB 的限制,并且会悄悄地将超大请求减少到 16 MiB。我忘记了更改发生的确切时间,但当前版本btrfs(即 5.16)以 16 MiB 块循环。不过,我认为 linux(btrfs现在不是)仍然默默地减少了超过 1 GiB 的请求。如果您希望使用FIDEDUPERANGE旧版本的btrfs,您绝对应该遵守 16 MiB 的限制。此外,其他文件系统可能也有类似的限制。

至于src_length = 0,您确实应该查阅各个 s 的文档ioctl以获取有关如何使用它们的说明。您正确引用的man页面意味着不会删除任何内容。FIDEDUPERANGEsrc_length = 0

关于你引用的VFS页面,事情很复杂。 处理改编自最初设计和单独实现的多个sremap_file_range()的功能。在clone s中,表示克隆到文件末尾。在 dedup 中,表示不进行任何重复数据删除。我忘记了具体时间,但有人努力统一克隆和重复数据删除功能。然而,改变当前支持的.在版本 5.16 中,有一个奇怪的 hack,涉及将参数转换为取决于是克隆还是重复数据删除调用。人们很容易说 VFS 文档是错误的,因为我认为这里的行为没有改变。但是,我不确定其他文件系统是否已经实现了这种含义,所以它很复杂。ioctlbtrfsbtrfsioctlsrc_length == 0ioctlsrc_length == 0ioctlbtrfsbtrfs_remap_file_range_prep()lenremap_file_range()ioctlbtrfsremap_file_range()len == 0

相关内容