web-dev-qa-db-ja.com

ファイルを上書きするときに、ファイルを閉じるときに同期が待機するのはなぜですか?

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

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

Ubuntuマシンで次のプロセスを確認できます。

メモリは10GBでいっぱいです。ページキャッシュは10GBのダーティページで一杯になります。 (/ proc/meminfo)「closed」が出力され、スクリプトが終了します。しばらくすると、ダーティページが減少します。

ただし、ファイル「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++プログラムで同じことを実行する場合も、同じ動作が見られます。ブロックする実際のシステムコールになる。

9
JMC

_O_PONIES_の大失敗を思い出させるようですが、つい最近11周年を迎えました。

Ext4が登場する前は、ext3は電力損失に直面しても安定しているという一種の評判を得ていました。それはめったに壊れません、それはめったにファイルからデータを失いませんでした。次に、ext4はデータブロックの遅延割り当てを追加しました。つまり、ファイルデータをディスクにすぐに書き込もうともしませんでした。通常、データがある時点でそこに到達する限り、これは問題ではありません。一時ファイルの場合、データをディスクに書き込む必要がまったくないことが判明する場合があります。

しかし、ext4はmetadata変更を書き込み、ファイルで何かが変更されたことを記録しました。システムがクラッシュした場合、ファイルは切り捨てられたとマークされますが、それ以降の書き込みはディスクに保存されませんでした(ブロックが割り当てられていないため)。したがって、ext4では、最近変更されたファイルがクラッシュ後に長さがゼロに切り捨てられることがよくあります。

もちろん、ほとんどのユーザーが望んでいたものではありませんでしたが、データを非常に重視するアプリケーションプログラムはfsync()を呼び出し、実際にrenames、それらは含んでいるディレクトリもfsync()(または少なくともfdatasync())する必要があります。しかし、ext3ではfsync()がディスク全体を同期したため、だれもそれをしていませんでした。おそらく、無関係な大量のデータが含まれている可能性があります。 (または、ディスク全体に限りなく、違いは関係ありません。)

さて、一方ではfsync()でのパフォーマンスが低いext3があり、もう一方ではfsync()がファイルを失わないようにする必要があるext4がありました。ほとんどのアプリケーションプログラムは、適切なタイミングでfsync()を呼び出す厳密なダンスよりも、ファイルシステム固有の動作の実装に関心があることを考えると、良い状況ではありません。どうやら、ファイルシステムが最初にext3またはext4としてマウントされたかどうかを判断するのは簡単ではなかったようです。

最後に、ext4開発者は、最も一般的なクリティカルシーミングケースにいくつかの変更を加えました

  • 別のファイルの名前を変更する。実行中のシステムでは、これはアトミック更新であり、通常、ファイルの新しいバージョンを配置するために使用されます。
  • 既存のファイルを上書きする(あなたの場合)。これは、実行中のシステムではアトミックではありませんが、通常、アプリケーションはファイルを切り捨てずに置換することを望んでいることを意味します。上書きが失敗した場合、ファイルの古いバージョンも失われるため、これは完全に新しいファイルを作成するのとは少し異なります。停電では最新のデータのみが失われます。

私が覚えている限りでは、XFSはext4の前でさえ、クラッシュ後に同様の長さゼロのファイルを示しました。しかし、私はそれを決してフォローしなかったので、彼らがどんな種類の修正をしたのかわかりません。

たとえば、 LWNに関するこの記事では、修正について説明しています: ext4 and data loss (2009年3月)

もちろん、その時点で他の文章もありましたが、それらをリンクすることが有用であるかどうかはわかりません。ほとんどの場合、人差し指の問題であるためです。

8
ilkkachu

これはLinux自体ではなく、ext4に関するものです。この影響は、btrfsでは発生しません。

驚いたことに、それはext4マウントオプションdata=writeback

1
Hauke Laging