檔案系統與儲存

用更名做原子替換(rename-for-atomic-replace)

假設你想用新內容覆寫一個重要的設定檔。如果你開啟它、把新位元組寫在舊的之上,而電源在半途斷掉,你便落得一個既非舊版本、也非新版本的檔案——一個損壞的混合物,可能被截斷、可能只寫了一半。有一個標準的把戲,能讓替換一個檔案變成全有或全無,使讀者永遠看到的不是完整的舊檔、就是完整的新檔,絕不是撕裂的中間態。它就是「寫到暫存檔再更名」的模式,是一個普通應用所擁有最重要的原子性工具。

這個模式有一份精確的食譜。(1) 把整份新內容寫到「同一個目錄」裡一個全新的暫存檔(同一目錄很要緊,因為 rename 只在單一檔案系統內才原子)。(2) 對那個暫存檔 fsync(),使它的資料持久地落在磁碟——略過這步,當機可能讓更名後的檔案存在卻是空的或只有一部分。(3) 把暫存檔 rename() 到最終的名字上。POSIX 的 rename() 呼叫是原子的:在每一瞬間,目標名字指的不是舊 inode、就是新 inode,絕不會指向一個寫了一半的檔案,而且它還在同一個原子步驟裡解除舊目標的連結。(4) 為了名字變更本身的完整持久性,事後對父目錄 fsync(),使新的目錄項挺過當機。一個在任何時刻開啟該檔案的讀者,都得到一個前後一致的版本;不存在任何名字指向垃圾的窗口。

為何重要:這正是編輯器、套件管理員與設定工具安全替換檔案的方式,也是為何你常在一次儲存中看到一個臨時暫存檔出現又消失。誠實之點很鋒利。rename() 只在單一檔案系統內才原子——跨掛載邊界的 rename 根本不是 rename,而是一次複製加刪除,那並不原子。在 rename 前略過對暫存檔的 fsync 是一個真實而常見的錯誤:更名可能在檔案的資料持久之前就先持久,使當機留下一個名字正確卻空白的檔案。而原子替換給新檔一個新的 inode,這會破壞任何指向舊檔的硬連結、並改變 inode 號——對監看該檔的程式有時是個意外。

int fd = open("config.tmp", O_WRONLY|O_CREAT|O_TRUNC, 0644); write(fd, new_data, n); fsync(fd); // 1. 暫存檔的資料持久 close(fd); rename("config.tmp", "config"); // 2. 原子交換:讀者看到舊或新,絕不撕裂 fsync(dirfd); // 3. 使名字變更本身持久

對暫存檔 fsync、再原子地 rename 蓋過目標、再對目錄 fsync——一次「完整或維持舊版」的替換。

rename() 只在「單一」檔案系統內才原子;跨掛載點時它退化成非原子的複製加刪除。經典的錯誤是在對暫存檔的資料 fsync() 之前就更名,這可能在當機後留下一個名字正確卻空白的檔案。也要注意被替換的檔案會得到一個新的 inode,破壞指向舊檔的硬連結。

又称
atomic file replacewrite-temp-then-renameatomic save pattern原子檔案替換寫暫存再更名