Files

為什麼在覆蓋文件時關閉文件等待同步,但在創建時不等待?

  • May 26, 2020

執行此腳本時:

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

我可以在我的 Ubuntu 機器上觀察以下過程:

記憶體填滿 10GB。頁面記憶體填充了 10GB 的髒頁。(/proc/meminfo) 列印“關閉”並且腳本終止。一段時間後,臟頁減少。

但是,如果文件“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 執行相同操作時,可以觀察到相同的行為。這似乎是阻塞的實際系統呼叫。

這聽起來像是對O_PONIES最近剛剛過 11 歲生日的慘敗的提醒。

在 ext4 出現之前,ext3 已經獲得了某種聲譽,因為它在面對電力損失時保持穩定。它很少損壞,很少失去文件中的數據。然後,ext4 增加了數據塊的延遲分配,這意味著它甚至沒有嘗試立即將文件數據寫入磁碟。通常,只要數據在某個時候到達那裡就不是問題,而對於臨時文件,可能根本不需要將數據寫入磁碟。

但是 ext4 確實寫入了元數據更改,並記錄了文件發生了一些變化。現在,如果系統崩潰,文件被標記為被截斷,但之後的寫入不會儲存在磁碟上(因為沒有為它們分配塊)。因此,在 ext4 上,您經常會看到最近修改的文件在崩潰後被截斷為零長度。

這當然不是大多數使用者想要的,但有人認為,非常關心他們的數據的應用程序應該呼叫fsync(),如果他們真的關心renames,他們應該fsync()(或至少fdatasync())包含目錄也。然而,幾乎沒有人這樣做,部分原因是在 ext3 上,fsync()同步了整個磁碟,可能包括大量不相關的數據。(或者盡可能接近整個磁碟,無論如何差異都無關緊要。)

現在,一方面,您的 ext3 性能不佳,fsync()另一方面,ext4 要求fsync()不失去文件。這不是一個好的情況,考慮到大多數應用程序會關心實現特定於文件系統的行為,甚至比fsync()在正確的時刻呼叫的僵化舞蹈還要少。顯然,首先要弄清楚文件系統安裝為 ext3 還是 ext4 甚至都不容易。

最後,ext4 開發人員對最常見的看似關鍵的情況進行了一些更改

  • 在另一個文件之上重命名文件。在正在執行的系統上,這是一種原子更新,通常用於放置文件的新版本。
  • 覆蓋現有文件(您的情況)。這在正在執行的系統上不是原子的,但通常意味著應用程序希望文件被替換,而不是被截斷。如果覆蓋失敗,您也會失去文件的舊版本,所以這與創建一個全新的文件有點不同,斷電只會失去最新的數據。

據我所知,XFS 在崩潰後甚至在 ext4 之前也展示了類似的零長度文件。不過,我從來沒有遵循過,所以我不知道他們會做什麼樣的修復。

參見,例如這篇關於 LWN 的文章,其中提到了修復:ext4 和數據失去(2009 年 3 月)

當然,當時還有其他關於這方面的著作,但我不確定連結到它們是否有用,因為這主要是指指點點的問題。

引用自:https://unix.stackexchange.com/questions/588586