ファイルを上書きするときにファイルを閉じると同期を待ちますが、生成するときに同期を待たないのはなぜですか?

ファイルを上書きするときにファイルを閉じると同期を待ちますが、生成するときに同期を待たないのはなぜですか?

このスクリプトを実行するとき:

#!/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を使用する代わりに、単にファイルを印刷してパイピングするときと、coutに印刷する小さな同等のC ++プログラムを使用して同じ操作を実行すると、同じ動作が観察されることがあります。実際のシステムコールがブロックされているようです。

答え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驚くべきことに、この現象はマウントオプションでも発生しますdata=writeback

関連情報