为什么关闭文件在覆盖文件时会等待同步,而在创建文件时则不会?

为什么关闭文件在覆盖文件时会等待同步,而在创建文件时则不会?

运行此脚本时:

#!/usr/bin/env python3
f = open("foo", "w")
f.write("1"*10000000000)
f.close()
print("closed")

我可以在我的 Ubuntu 机器上观察到以下过程:

内存充满10GB。页面缓存充满了 10GB 的脏页。 (/proc/meminfo) 打印“close”并且脚本终止。一段时间后,脏页就会减少。

但是,如果文件“foo”已经存在,则 close() 会阻塞,直到所有脏页都被写回为止。

这种行为的原因是什么?

如果文件不存在,这是 strace:

openat(AT_FDCWD, "foo", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 3
fstat(3, {st_mode=S_IFREG|0664, st_size=0, ...}) = 0
ioctl(3, TCGETS, 0x7ffd50dc76f0)        = -1 ENOTTY (Inappropriate ioctl for device)
lseek(3, 0, SEEK_CUR)                   = 0
ioctl(3, TCGETS, 0x7ffd50dc76c0)        = -1 ENOTTY (Inappropriate ioctl for device)
lseek(3, 0, SEEK_CUR)                   = 0
lseek(3, 0, SEEK_CUR)                   = 0
mmap(NULL, 10000003072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fcd9892e000
mmap(NULL, 10000003072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fcb4486f000
write(3, "11111111111111111111111111111111"..., 10000000000) = 2147479552
write(3, "11111111111111111111111111111111"..., 7852520448) = 2147479552
write(3, "11111111111111111111111111111111"..., 5705040896) = 2147479552
write(3, "11111111111111111111111111111111"..., 3557561344) = 2147479552
write(3, "11111111111111111111111111111111"..., 1410081792) = 1410081792
munmap(0x7fcb4486f000, 10000003072)     = 0
munmap(0x7fcd9892e000, 10000003072)     = 0
close(3)                                = 0
write(1, "closed\n", 7closed
)                 = 7
rt_sigaction(SIGINT, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fcfedd5cf20}, {sa_handler=0x62ffc0, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fcfedd5cf20}, 8) = 0
sigaltstack(NULL, {ss_sp=0x2941be0, ss_flags=0, ss_size=8192}) = 0
sigaltstack({ss_sp=NULL, ss_flags=SS_DISABLE, ss_size=0}, NULL) = 0
exit_group(0)                           = ?
+++ exited with 0 +++

这是 strace(如果存在):

openat(AT_FDCWD, "foo", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 3
fstat(3, {st_mode=S_IFREG|0664, st_size=0, ...}) = 0
ioctl(3, TCGETS, 0x7fffa00b4fe0)        = -1 ENOTTY (Inappropriate ioctl for device)
lseek(3, 0, SEEK_CUR)                   = 0
ioctl(3, TCGETS, 0x7fffa00b4fb0)        = -1 ENOTTY (Inappropriate ioctl for device)
lseek(3, 0, SEEK_CUR)                   = 0
lseek(3, 0, SEEK_CUR)                   = 0
mmap(NULL, 10000003072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f71de68b000
mmap(NULL, 10000003072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f6f8a5cc000
write(3, "11111111111111111111111111111111"..., 10000000000) = 2147479552
write(3, "11111111111111111111111111111111"..., 7852520448) = 2147479552
write(3, "11111111111111111111111111111111"..., 5705040896) = 2147479552
write(3, "11111111111111111111111111111111"..., 3557561344) = 2147479552
write(3, "11111111111111111111111111111111"..., 1410081792) = 1410081792
munmap(0x7f6f8a5cc000, 10000003072)     = 0
munmap(0x7f71de68b000, 10000003072)     = 0
close(3#### strace will block exactly here until write-back is completed ####)                                = 0 
write(1, "closed\n", 7closed
)                 = 7
rt_sigaction(SIGINT, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f7433ab9f20}, {sa_handler=0x62ffc0, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f7433ab9f20}, 8) = 0
sigaltstack(NULL, {ss_sp=0x1c68be0, ss_flags=0, ss_size=8192}) = 0
sigaltstack({ss_sp=NULL, ss_flags=SS_DISABLE, ss_size=0}, NULL) = 0
exit_group(0)                           = ?
+++ exited with 0 +++

当简单地打印并通过管道传输到文件而不是使用 python file-io 时,以及使用小型等效 C++ 程序打印到 cout 执行相同操作时,可以观察到相同的行为。似乎是实际的系统调用阻塞了。

答案1

这听起来像是在提醒O_PONIES我们最近刚刚度过了 11 岁生日的那场惨败。

在 ext4 出现之前,ext3 已经获得了在断电时保持稳定的声誉。它很少损坏,也很少丢失文件中的数据。然后,ext4添加了数据块的延迟分配,这意味着它甚至不会尝试立即将文件数据写入磁盘。通常,只要数据在某个时刻到达那里,这就不成问题,而对于临时文件,可能根本不需要将数据写入磁盘。

但ext4确实写了元数据更改,并记录文件发生了某些变化。现在,如果系统崩溃,文件会被标记为已截断,但此后的写入不会存储在磁盘上(因为没有为它们分配块)。因此,在 ext4 上,您经常会看到最近修改的文件在崩溃后被截断为零长度。

当然,这并不完全是大多数用户想要的,但有人认为,如此关心其数据的应用程序应该调用fsync(),并且如果他们确实关心重命名,它们也应该fsync()(或至少fdatasync())包含目录。不过,几乎没有人这样做,部分原因是在 ext3 上,同步fsync()了整个磁盘,可能包括大量不相关的数据。 (或者尽可能接近整个磁盘,差异无论如何都无关紧要。)

现在,一方面,ext3 的性能较差,fsync()另一方面,ext4 要求fsync()不丢失文件。考虑到大多数应用程序都愿意实现特定于文件系统的行为,甚至比fsync()在正确的时刻进行调用的僵化舞蹈更不关心,这不是一个好的情况。显然,要弄清楚文件系统是否存在并不容易曾是首先安装为 ext3 或 ext4。

最后,ext4 开发人员对最常见的看似危急的情况进行了一些更改

  • 在另一个文件之上重命名一个文件。在正在运行的系统上,这是一个原子更新,通常用于放置文件的新版本。
  • 覆盖现有文件(您的情况)。这在运行的系统上不是原子的,但通常意味着应用程序希望替换文件,而不是截断文件。如果覆盖失败,您将丢失旧版文件的内容,因此这与创建一个全新的文件有点不同,后者断电只会丢失最新的数据。

据我所知,XFS 在崩溃后甚至在 ext4 之前也表现出类似的零长度文件。不过,我从来没有遵循这一点,所以我不知道他们会做什么样的修复。

请参阅 LWN 上的这篇文章,其中提到了修复:ext4 和数据丢失(2009 年 3 月)

当然,当时还有其他关于此的文章,但我不确定链接到它们是否有用,因为这主要是一个相互指责的问题。

答案2

这不是关于 Linux 本身,而是关于 ext4。 btrfs 不会出现这种效果。

ext4令我惊讶的是,使用mount 选项也会出现这种情况data=writeback

相关内容