是什么可以解释对tmpfs的这种奇怪的稀疏文件处理?


14

在我的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

产生

Before:
0 sparse100M
2f282b84e7e608d5852449ed940bfc51  /dev/loop0
After:
0 sparse100M

在tmpfs上执行与以下操作相同的操作:

fs="/tmp"

产量

Before:
0 /tmp/sparse100M
2f282b84e7e608d5852449ed940bfc51  /dev/loop0
After:
102400 /tmp/sparse100M

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

我希望这是因为对文件tmpfs系统中的稀疏文件的支持不够完善,尤其是由于缺少FIEMAP ioctl,但是我不确定是什么原因导致了这种现象?你能告诉我吗?


哼。有一个共享的(写时复制)零页,例如,当需要对稀疏页进行mmap()处理时,可以使用该共享零页。因此,我不确定为什么从稀疏的tmpfs文件中进行任何类型的读取都需要分配实际内存。lwn.net/Articles/517465。我想知道这是否是使用直接io进行循环转换的副作用,但是当您尝试在tmpfs上使用新型循环时,似乎应该没有任何区别。 spinics.net/lists/linux-fsdevel/msg60337.html
sourcejedi

也许如果这样的话可能会得到答案?只是一个想法

1
/ tmp的输出具有不同的文件之前/之后。那是错字吗?之前:0 / tmp / sparse100(末尾不带M)之后:102400 / tmp / sparse100M(后缀M)。
YoMismo

@YoMismo,是的,只有一点错字
humanityANDpeace

Answers:


4

首先,您并不孤单地对这些问题感到困惑。

这不仅限于NFSv4tmpfs而且已引起关注 。

如果应用程序读取稀疏文件中的“漏洞”,则文件系统会将空块转换为填充有零的“实际”块,然后将其返回给应用程序。

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.

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

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

$ 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

你看到的仅仅是行为进行losetup更改稀疏文件的大小。因此,这成为where tmpfs,HOLE_PUNCH机制fallocate和块设备相交的有趣组合。


2
感谢您的回答。我知道tmpfs支持稀疏文件和punch_hole。这就是让它如此混乱的原因- tmpfs 支持这一点,那么为什么在阅读循环设备时去填补稀疏的漏洞呢?losetup不会更改文件大小,但是会创建一个块设备,然后在大多数系统上扫描该块设备,以查找类似以下内容的内容:是否有分区表?是否存在带有UUID的文件系统?我应该创建一个/ dev / disk / by-uuid / symlink吗?这些读取已经导致稀疏文件的一部分被分配,因为出于某种神秘的原因,tmpfs填补了(某些)读取的漏洞。
frostschutz

1
您能否澄清“ 在某些情况下顺序读取会(在某些情况下)导致写时复制 ”?我很好奇理解读操作将如何触发写操作时的复制。谢谢!
roaima

这很奇怪。在我的系统上,我遵循相同的步骤,尽管是手动执行而不是在脚本中执行。首先,我像OP一样做了一个100M的文件。然后,我仅用10MB的文件重复了这些步骤。第一个结果:ls -s sparse100M是102400。但是10MB文件上的ls -s只有328个块。??
帕特里克·泰勒·泰勒

1
@PatrickTaylor〜328K是关于UUID扫描仪通过后使用的内容,但是您没有使用cat / md5sum循环设备进行完整读取。
frostschutz

1
我在浏览循环内核模块的源代码(中loop.c),发现有两个相关功能lo_read_simplelo_read_transfer。它们在进行低级内存分配的方式上有一些细微的差异…… lo_read_transfer实际上是在执行调用时从slab.hGFP_NOIO)请求非阻塞io alloc_page()lo_read_simple()另一方面表现不佳alloc_page()
Brian Redbeard
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.