什么可以解释这种奇怪的稀疏文件处理/在 tmpfs 中?

hum*_*ace 15 ext4 sparse-files tmpfs

在我的ext4文件系统分区上,我可以运行以下代码:

fs="/mnt/ext4"

#create sparse 100M file on ${fs}
dd if=/dev/zero \
   of=${fs}/sparse100M conv=sparse seek=$((100*2*1024-1)) count=1 2> /dev/null

#show its actual used size before
echo "Before:"
ls ${fs}/sparse100M -s

#setting the sparse file up as loopback and run md5sum on loopback
losetup /dev/loop0 ${fs}/sparse100M 
md5sum /dev/loop0

#show its actual used size afterwards
echo "After:"
ls ${fs}/sparse100M -s

#release loopback and remove file
losetup -d /dev/loop0
rm ${fs}/sparse100M
Run Code Online (Sandbox Code Playgroud)

这产生

Before:
0 sparse100M
2f282b84e7e608d5852449ed940bfc51  /dev/loop0
After:
0 sparse100M
Run Code Online (Sandbox Code Playgroud)

在 tmpfs 上做同样的事情:

fs="/tmp"
Run Code Online (Sandbox Code Playgroud)

产量

Before:
0 /tmp/sparse100M
2f282b84e7e608d5852449ed940bfc51  /dev/loop0
After:
102400 /tmp/sparse100M
Run Code Online (Sandbox Code Playgroud)

这基本上意味着我希望仅仅读取数据的东西导致稀疏文件“像气球一样爆炸”?

我希望这是因为文件tmpfs系统中对稀疏文件的支持不太完美,特别是因为缺少 FIEMAP ioctl,但我不确定是什么导致了这种行为?你能告诉我吗?

Bri*_*ard 5

首先,您并不是唯一一个对这些问题感到困惑的人。

这不仅限于NFSv4tmpfs而且一直是NFSv4引用的一个问题 。

如果应用程序读取稀疏文件中的“空洞”,文件系统会将空块转换为填充零的“真实”块,并将它们返回给应用程序。

md5sum尝试扫描文件时,它明确选择按顺序执行此操作, 根据 md5sum 尝试执行的操作,这很有意义。

由于文件中基本上存在“漏洞”,因此这种顺序读取将(在某些情况下)导致类似写入操作的副本以填充文件。然后,这将进入一个更深层次的问题fallocate(),即文件系统中实现的是否支持FALLOC_FL_PUNCH_HOLE.

幸运的是,不仅tmpfs支持这一点,而且有一种机制可以“挖”出漏洞。

使用 CLI 实用程序,fallocate我们可以成功检测并重新挖掘这些漏洞。

根据man 1 fallocate

-d, --dig-holes
      Detect and dig holes.  This makes the file sparse in-place, without
      using extra disk space.  The minimum size of the hole depends on
      filesystem I/O  block size (usually 4096 bytes).  Also, when using
      this option, --keep-size is implied.  If no range is specified by
      --offset and --length, then the entire file is analyzed for holes.

      You can think of this option as doing a "cp --sparse" and then
      renaming the destination file to the original, without the need for
      extra disk space.

      See --punch-hole for a list of supported filesystems.
Run Code Online (Sandbox Code Playgroud)

fallocate虽然在文件级别上运行md5sum ,但是当您针对块设备运行(请求顺序读取)时,您会发现fallocate()系统调用应该如何运行之间的确切差距。我们可以看到这一点:

在行动中,使用您的示例,我们看到以下内容:

$ fs=$(mktemp -d)
$ echo ${fs}
/tmp/tmp.ONTGAS8L06
$ dd if=/dev/zero of=${fs}/sparse100M conv=sparse seek=$((100*2*1024-1)) count=1 2>/dev/null
$ echo "Before:" "$(ls ${fs}/sparse100M -s)"
Before: 0 /tmp/tmp.ONTGAS8L06/sparse100M
$ sudo losetup /dev/loop0 ${fs}/sparse100M
$ sudo md5sum /dev/loop0
2f282b84e7e608d5852449ed940bfc51  /dev/loop0
$ echo "After:" "$(ls ${fs}/sparse100M -s)"
After: 102400 /tmp/tmp.ONTGAS8L06/sparse100M
$ fallocate -d ${fs}/sparse100M
$ echo "After:" "$(ls ${fs}/sparse100M -s)"
After: 0 /tmp/tmp.ONTGAS8L06/sparse100M
Run Code Online (Sandbox Code Playgroud)

现在......回答了你的基本问题。我的座右铭是“变得奇怪”,所以我进一步挖掘......

$ fs=$(mktemp -d)
$ echo ${fs}
/tmp/tmp.ZcAxvW32GY
$ dd if=/dev/zero of=${fs}/sparse100M conv=sparse seek=$((100*2*1024-1)) count=1 2>/dev/null
$ echo "Before:" "$(ls ${fs}/sparse100M -s)"
Before: 0 /tmp/tmp.ZcAxvW32GY/sparse100M
$ sudo losetup /dev/loop0 ${fs}/sparse100M
$ echo "After:" "$(ls ${fs}/sparse100M -s)"
After: 1036 /tmp/tmp.ZcAxvW32GY/sparse100M
$ sudo md5sum ${fs}/sparse100M
2f282b84e7e608d5852449ed940bfc51  /tmp/tmp.ZcAxvW32GY/sparse100M
$ echo "After:" "$(ls ${fs}/sparse100M -s)"
After: 1036 /tmp/tmp.ZcAxvW32GY/sparse100M
$ fallocate -d ${fs}/sparse100M
$ echo "After:" "$(ls ${fs}/sparse100M -s)"
After: 520 /tmp/tmp.ZcAxvW32GY/sparse100M
$ sudo md5sum ${fs}/sparse100M
2f282b84e7e608d5852449ed940bfc51  /tmp/tmp.ZcAxvW32GY/sparse100M
$ echo "After:" "$(ls ${fs}/sparse100M -s)"
After: 520 /tmp/tmp.ZcAxvW32GY/sparse100M
$ fallocate -d ${fs}/sparse100M
$ echo "After:" "$(ls ${fs}/sparse100M -s)"
After: 516 /tmp/tmp.ZcAxvW32GY/sparse100M
$ fallocate -d ${fs}/sparse100M
$ sudo md5sum ${fs}/sparse100M
2f282b84e7e608d5852449ed940bfc51  /tmp/tmp.ZcAxvW32GY/sparse100M
$ echo "After:" "$(ls ${fs}/sparse100M -s)"
After: 512 /tmp/tmp.ZcAxvW32GY/sparse100M
$ fallocate -d ${fs}/sparse100M
$ echo "After:" "$(ls ${fs}/sparse100M -s)"
After: 0 /tmp/tmp.ZcAxvW32GY/sparse100M
$ sudo md5sum ${fs}/sparse100M
2f282b84e7e608d5852449ed940bfc51  /tmp/tmp.ZcAxvW32GY/sparse100M
$ echo "After:" "$(ls ${fs}/sparse100M -s)"
After: 0 /tmp/tmp.ZcAxvW32GY/sparse100M
Run Code Online (Sandbox Code Playgroud)

您会看到,仅执行这些操作losetup就会更改稀疏文件的大小。因此,这成为 where tmpfs、HOLE_PUNCH 机制fallocate、 和块设备相交的有趣组合。

  • 感谢您的回答。我知道`tmpfs` 支持稀疏文件和punch_hole。这就是让它如此混乱的原因 - `tmpfs` _supports_ 这个,那么为什么在通过循环设备阅读时去填补稀疏的洞呢?`losetup` 不会改变文件大小,但它会创建一个块设备,然后在大多数系统上扫描它的内容,例如:是否有分区表?是否有带有 UUID 的文件系统?那么我应该创建一个 /dev/disk/by-uuid/ 符号链接吗?并且这些读取已经导致稀疏文件的一部分被分配,因为出于某种*神秘的原因*,tmpfs 填充了(某些)读取的漏洞。 (2认同)
  • 您能否澄清“_顺序读取(在某些情况下)会导致像操作那样写入时复制_”,好吗?我很想知道读操作如何触发写操作的副本。谢谢! (2认同)