為什麼在覆蓋文件時關閉文件等待同步,但在創建時不等待?
執行此腳本時:
#!/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 月)
當然,當時還有其他關於這方面的著作,但我不確定連結到它們是否有用,因為這主要是指指點點的問題。