fsync 語意(持久性保證)
/ fsync = EFF-sink /
你已經學到:一次成功的 write() 只抵達 RAM 裡的頁面快取,而非磁碟。那麼一個真正在乎的程式——一個資料庫、一個正在存你文件的文字編輯器——如何確保它的位元組真的、實體地落在能挺過斷電的穩定儲存上?它呼叫 fsync()。fsync(fd) 阻塞,直到那個檔案所有被修改的資料與後設資料都從頁面快取被強制貫穿到持久儲存,然後才回報成功。它是那唯一一個把「已寫」轉成「持久」的呼叫。
有兩個相關的呼叫。fsync(fd) 沖刷檔案的資料與它的後設資料(大小、時間戳記)並等待。fdatasync(fd) 沖刷資料、外加僅讀回那份資料嚴格所需的後設資料(所以它可以略過只改時間戳記的更新),略為便宜一些。關鍵是,fsync 還必須確保位元組已離開磁碟機本身上任何揮發性的快取——一個現代的 fsync 對裝置發出一個快取沖刷指令(或使用 FUA,強制單元存取,寫入),使資料不只是坐在磁碟機自己的 RAM 裡。關於目錄還有一個微妙之處:建立一個新檔案之後,檔案的資料只在對該檔案 fsync 之後才持久,但命名它的目錄項只在對其父目錄 fsync 之後才持久——漏掉這點,當機可能讓資料留著、卻沒有任何名字指向它。
為何重要:fsync 是每個可靠儲存系統所建立其上的持久性原語,而把它弄錯是無聲資料遺失的經典來源。對它的限制與代價要誠實。它很昂貴——它逼磁碟真正提交,這可能花上幾毫秒、並使呼叫的執行緒停頓——所以每次微小寫入後都呼叫它會摧毀效能。它可能「失敗」,而它的失敗意味你的資料「不」持久;忽略 fsync 的回傳值是一個真正的錯誤。而且歷史上有些系統把失敗的 fsync 處理得如此糟糕(清掉髒旗標,使重試假性地成功),以致對 fsync 失敗的安全回應往往是把資料當成已遺失、而非重試。fsync 不是形式——它正是持久性保證實際存活或死亡之處。
安全儲存一個新檔案: write(fd, data, n); if (fsync(fd) != 0) { /* 不持久——當成資料遺失處理 */ } /* 也要持久地記下名字: */ fsync(dirfd); // 對父目錄 fsync,使新的目錄項挺過當機
持久性需要對檔案「和」對其父目錄都 fsync;回傳值必須被檢查。
fsync 可能失敗,而失敗的 fsync 意味資料不持久——絕不要忽略它的回傳。某些核心在回寫失敗時還會清掉髒旗標,使日後的 fsync 可能假性回報成功;對 fsync 失敗的穩健回應往往是把資料當成已遺失、而非重試。此外,對檔案 fsync 不會使它的目錄項持久——也要對父目錄 fsync。