天天看點

《UNIXLinux程式設計教程》一3.9 fsync()和fdatasync()函數

前面介紹函數write()時,我們認為該函數一旦傳回,資料便已經寫到了檔案中。但是這種概念隻是宏觀上的。實際上,作業系統實作某些檔案i/o時(如磁盤檔案),為了保證i/o的效率,在核心通常會用到一片專門的區域(記憶體或獨立的i/o位址空間)作為i/o資料緩沖區。應用程式可以将這片核心區域看成是i/o資料的一個快速中轉站(圖3-5)。當調用write()函數寫出資料時,資料一旦寫到該緩沖區,函數便立即傳回。此時寫出的資料可以用read()讀回,也可以被其他程序讀到,但是并不意味着它們已經被寫到了外部永久存儲媒體上,即使調用close()關閉檔案後也可能如此。核心i/o資料緩沖區中的資料隻在适當的時候才由作業系統啟動外設進行傳輸,真正的傳輸動作由獨立于cpu的外設控制器或者外設本身(linux稱之為dma引擎)來完成。是以,從資料被實際寫到磁盤的角度來看,用write()寫出的檔案資料與外部儲存設備并不是完全同步的。在現代計算機系統中,這種不同步的時間間隔非常短,一般隻有幾秒或十幾秒,具體取決于寫出的資料量和i/o資料緩沖區的狀态。盡管不同步的時間間隔很短,但是如果在此期間發生掉電或者系統崩潰,則會導緻所寫資料來不及寫至磁盤而丢失的情況。

《UNIXLinux程式設計教程》一3.9 fsync()和fdatasync()函數

由于現代計算機通常都十分穩定可靠,出現掉電或系統崩潰的情況極少,是以多數應用在寫檔案時可以忽略這種瞬間不同步情況。但是,有些應用存在着這樣的一些同步點,在這些點上所寫的資料非常關鍵,或者必須及時保證檔案的一緻性。為了防備萬一,這些應用需要確定所有寫出的資料都已經傳送到了外部永久存儲媒體上。為此,unix提供了兩種手段來實作這一目的。其中一種方法是對檔案設定o_sync标志(表3-1),這樣可以保證每次寫資料都直接寫到磁盤。如果設定了這個标志,write()調用将直到資料已安全地寫到磁盤後(而不僅僅是系統的i/o緩沖區)才傳回。但是這樣每次寫資料都保持同步的效率比較低。

另一種方法是隻在需要時調用函數fsync()或者fdatasync()。

fsync()強制與描述字fildes相連檔案的所有修改過的資料(包括核内i/o緩沖區中的資料)傳送到外部永久媒體,即重新整理fildes給出的檔案的所有資訊。調用 fsync()的程序将阻塞直到裝置報告傳送已經完成。這裡“所有修改過的資料”包括使用者寫出的資料以及檔案本身的特征資料(4.1.1節和表4-1),如檔案的通路時間、修改時間、檔案的屬主等。

fdatasync()的功能與fsync()類似,隻是它隻強制傳送使用者已寫出的資料至實體儲存設備,不包括檔案本身的特征資料。這樣可以适當減少檔案重新整理時的資料傳送量。不過有的系統并不支援fdatasync(),在這種系統上,fdatasync()等價于fsync()。

一個程式在寫出資料之後,如果繼續進行後續處理之前要求確定所寫資料已寫到磁盤,則應當調用fsync()。例如,資料庫應用通常會在調用write()儲存關鍵交易資料的同時也調用fsync()。

我們在2.7節曾讨論了标準i/o流緩沖區的問題以及函數fflush()。那麼,這兩個緩沖區有何不同?回答是,核心i/o緩沖區是由作業系統管理的空間,而流緩沖區是由标準i/o庫管理的使用者空間。fflush()隻重新整理位于使用者空間中的流緩沖區。fflush()傳回後,隻保證資料已不在流緩沖區中,并不保證它們一定被寫到了磁盤。此時,從流緩沖區重新整理的資料可能已被寫至磁盤,也可能還待在核心i/o緩沖區中。要確定流i/o寫出的資料已寫至磁盤,那麼在調用fflush()後還應當調用fsync()。