Linux同步IO:sync、fsync和fdatasync

來源:互聯網
上載者:User

標籤:

轉自:http://blog.csdn.net/sishuiliunian0710/article/details/37739385

一、術語解釋
       髒頁:linux核心中的概念,因為硬碟的讀寫速度遠趕不上記憶體的速度,系統就把讀寫比較頻繁的資料事先放到記憶體中,以提高讀寫速度,這就叫快取,linux是以頁作為快取的單位,當進程修改了快取裡的資料時,該頁就被核心標記為髒頁,核心將會在合適的時間把髒頁的資料寫到磁碟中去,以保持快取中的資料和磁碟中的資料是一致的。

      記憶體映射:記憶體對應檔,是由一個檔案到一塊記憶體的映射。Win32提供了允許應用程式把檔案對應到一個進程的函數 (CreateFileMapping)。記憶體對應檔與虛擬記憶體有些類似,通過記憶體對應檔可以保留一個地址空間的地區,同時將實體儲存體器提交給此地區,記憶體檔案對應的實體儲存體器來自一個已經存在於磁碟上的檔案,而且在對該檔案進行操作之前必須首先對檔案進行映射。使用記憶體對應檔處理儲存於磁碟上的檔案時,將不必再對檔案執行I/O操作,使得記憶體對應檔在處理大資料量的檔案時能起到相當重要的作用。

                                                                                                      //摘錄自百度百科

      延遲寫(delayed write): 傳統的UNIX實現在核心中設有緩衝區快取或頁面快取,大多數磁碟I/O都通過緩衝進行。 當將資料寫入檔案時,核心通常先將該資料複製到其中一個緩衝區中,如果該緩衝區尚未寫滿,則 並不將其排入輸出隊列,而是等待其寫滿或者當核心需要重用該緩衝區以便存放其他磁碟塊資料時, 再將該緩衝排入到輸出隊列,然後待其到達隊首時,才進行實際的I/O操作。這種輸出方式就被稱為延遲寫。

                                                                                                    //摘錄自《UNIX環境進階編程第三版》P65
二、本文

       延遲寫減少了磁碟讀寫次數,但是卻降低了檔案內容的更新速度,使得欲寫到檔案中的資料在一段時間內並沒有寫到磁碟上。當系統發生故障時,這種延遲可能造成檔案更新內容的丟失。為了保證磁碟上實際檔案系統與緩衝區快取中內容的一致性,UNIX系統提供了sync、fsync和fdatasync三個函數。

1、sync函數

     sync函數只是將所有修改過的塊緩衝區排入寫隊列,然後就返回,它並不等待實際寫磁碟操作結束。
     通常稱為update的系統守護進程會周期性地(一般每隔30秒)調用sync函數。這就保證了定期沖洗核心的塊緩衝區。命令sync(1)也調用sync函數。
2、fsync函數
     fsync函數只對由檔案描述符filedes指定的單一檔案起作用,並且等待寫磁碟操作結束,然後返回。
     fsync可用於資料庫這樣的應用程式,這種應用程式需要確保將修改過的塊立即寫到磁碟上。
3、fdatasync函數
     fdatasync函數類似於fsync,但它隻影響檔案的資料部分。而除資料外,fsync還會同步更新檔案的屬性。
     對於提供事務支援的資料庫,在事務提交時,都要確保交易記錄(包含該事務所有的修改操作以及一個提交記錄)完全寫到硬碟上,才認定事務提交成功並返回給應用程式層。

4、fflush:標準IO函數(如fread,fwrite等)會在記憶體中建立緩衝,該函數重新整理記憶體緩衝,將內容寫入核心緩衝,要想將其真正寫入磁碟,還需要調用fsync。(即先調用fflush然後再調用fsync,否則不會起作用)。fflush以指定的檔案流描述符為參數(對應以fopen等函數開啟的檔案流),僅僅是把上層緩衝區中的資料重新整理到核心緩衝區就返回,

因此相對於fsync而言不是很安全,還需要再調用一下fsync來把資料真正寫入硬碟。使用函數

 

[cpp] view plaincopy 
  1. int fileno(FILE *stream);  

 

把檔案流描述符(fp)轉換為檔案描述符(fd),以方便fsync的調用,那麼,在linux作業系統上,怎樣才能保證資料被正確地寫入外部永久儲存介質?

1.  write不能滿足要求,需要fsync
    對於write函數,我們認為該函數一旦返回,資料便已經寫到了檔案中。但是這種概念只是宏觀上的,一般情況下,對硬碟(或者其他持久存放裝置)檔案的write操作,更新的只是記憶體中的頁緩衝(page cache),而髒頁不會立即更新到硬碟中,而是由作業系統統一調度,如flusher核心線程在滿足一定條件時(一定時間間隔、記憶體中
的髒頁達到一定比例)將髒頁面同步到硬碟上(放入裝置的IO請求隊列)。因為write調用不會等到硬碟IO完成之後才返回,設想如果作業系統在write調用之後、硬碟同步之前崩潰,則資料可能丟失。雖然這樣的時間視窗很小,但是對於需要保證事務的持久化(durability)和一致性(consistency)的資料庫程式來說,write()所提供的“鬆散的非同步語義”是不夠的,通常需要作業系統提供的同步IO(synchronized-IO)原語來保證:

函數原型:

 

[cpp] view plaincopy 
  1. int fsync(int fd);  

 

fsync的功能是確保檔案fd所有已修改的內容已經正確同步到硬碟上,該調用會阻塞等待直到裝置報告IO完成。

PS:如果採用記憶體對應檔的方式進行檔案IO(使用mmap,將檔案的page cache直接映射到進程的地址空間,通過寫記憶體的方式修改檔案),也有類似的系統調用來確保修改的內容完全同步到硬碟之上:

[cpp] view plaincopy 
  1. #incude <sys/mman.h>  
  2. int msync(void *addr, size_t length, int flags)  

msync需要指定同步的地址區間,如此細粒度的控制似乎比fsync更加高效(因為應用程式通常知道自己的髒頁位置),但實際上(Linux)kernel中有著十分高效的資料結構,能夠很快地找出檔案的髒頁,使得fsync只會同步檔案的修改內容。

2. fsync與fdatasync區別

除了同步檔案的修改內容(髒頁),fsync還會同步檔案的描述資訊(metadata,包括size、訪問時間等等),因為檔案的資料和metadata通常存在硬碟的不同地方,因此fsync至少需要兩次IO寫操作,多餘的一次IO操作,根據Wikipedia的資料,當前硬碟驅動的平均尋道時間(Average seek time)大約是3~15ms,7200RPM硬碟的平均旋轉延遲(Average rotational latency)大約為4ms,因此一次IO操作的耗時大約為10ms左右。Posix同樣定義了fdatasync,放寬了同步的語義以提高效能:

[cpp] view plaincopy 
  1. int fdatasync(int fd);  

fdatasync的功能與fsync類似,但是僅僅在必要的情況下才會同步,因此可以減少一次IO寫操作。

"fdatasync does not flush modified metadata unless that metadata is needed in order to allow a subsequent data retrieval to be corretly handled."

                                                                                                                                                                                         //摘錄自man手冊

舉例來說,檔案的尺寸(st_size)如果變化,是需要立即同步的,否則OS一旦崩潰,即使檔案的資料部分已同步,由於metadata沒有同步,依然讀不到修改的內容。而最後訪問時間(atime)/修改時間(mtime)是不需要每次都同步的,只要應用程式對這兩個時間戳記沒有苛刻的要求,基本沒有問題。

補充:函數open的參數O_SYNC/O_DSYNC有著和fsync/fdatasync類似的含義:使每次write都會阻塞等待硬碟IO完成。

O_SYNC 使每次write等待物理I/O操作完成,包括由write操作引起的檔案屬性更新所需的I/O。
O_DSYNC 使每次write等待物理I/O操作完成,但是如果該寫操作並不影響讀取剛寫入的資料,則不需等待檔案屬性被更新。
注意區別:
       O_DSYNC和O_SYNC標誌有微妙的區別:僅當檔案屬性需要更新以反映檔案資料變化(例如,更新檔案大小以反映檔案中包含了更多資料)時,O_DSYNC標誌才影響檔案屬性。而設定O_SYNC標誌後,資料和屬性總是同步更新。當檔案用O_DSYN標誌開啟,在重寫其現有的部分內容時,檔案時間屬性不會同步更新。於此相反,檔案如果是用O_SYNC標誌開啟的,那麼對於該檔案的每一次write都將在write返回前更新檔案時間,這與是否改寫現有位元組或追加檔案無關。相對於fsync/fdatasync,這樣的設定不夠靈活,應該很少使用。

3. 使用fdatasync最佳化日誌同步(來自http://blog.csdn.net/cywosp/article/details/8767327)
為了滿足事務要求,資料庫的記錄檔是常常需要同步IO的。由於需要同步等待硬碟IO完成,所以事務的提交操作常常十分耗時,成為效能的瓶頸。在Berkeley DB下,如果開啟了AUTO_COMMIT(所有獨立的寫操作自動具有事務語義)並使用預設的同步層級(日誌完全同步到硬碟才返回),寫一條記錄的耗時大約為5~10ms層級,基本和一次IO操作(10ms)的耗時相同。
 我們已經知道,在同步上fsync是低效的。但是如果需要使用fdatasync減少對metadata的更新,則需要確保檔案的尺寸在write前後沒有發生變化。記錄檔天生是追加型(append-only)的,總是在不斷增大,似乎很難利用好fdatasync。
 Berkeley DB是處理記錄檔的步驟:
1.每個log檔案固定為10MB大小,從1開始編號,名稱格式為“log.%010d"
2.每次log檔案建立時,先寫檔案的最後1個page,將log檔案擴充為10MB大小
3.向log檔案中追加記錄時,由於檔案的尺寸不發生變化,使用fdatasync可以大大最佳化寫log的效率
4.如果一個log檔案寫滿了,則建立一個log檔案,也只有一次同步metadata的開銷
 三、總結
1、如果是對所有的緩衝區發出寫硬碟的命令,應該使用sync函數,但應該注意該函數僅僅只是把該命令放入隊列就返回了,在編程時需要注意。
2、如果是要把一個已經開啟的檔案所做的修改提交到硬碟,應調用fsync函數,該函數會在資料實際寫入硬碟後才返回,因此是最安全最可靠的方式。

3、如果是針對一個已經開啟的檔案流操作,則應該首先調用fsync函數把修改同步到核心緩衝區,然後再調用fsync把修改真正的同步到硬碟。

四、附man手冊關於fsync,fdatasync部分

fsync()  transfers  ("flushes")  all  modified  in-core  data of (i.e., modified buffer cache pages for) the file referred to by the file
       descriptor fd to the disk device (or other permanent storage device) so that all changed information can be retrieved even after the sys‐
       tem  crashed  or  was  rebooted.   This  includes  writing through or flushing a disk cache if present.  The call blocks until the device
       reports that the transfer has completed.  It also flushes metadata information associated with the file (see stat(2)).
       Calling fsync() does not necessarily ensure that the entry in the directory containing the file has  also  reached  disk.   For  that  an
       explicit fsync() on a file descriptor for the directory is also needed.
       fdatasync()  is  similar  to  fsync(), but does not flush modified metadata unless that metadata is needed in order to allow a subsequent
       data retrieval to be correctly handled.  For example, changes to st_atime or st_mtime (respectively, time of last access and time of last
       modification; see stat(2)) do not require flushing because they are not necessary for a subsequent data read to be handled correctly.  On
       the other hand, a change to the file size (st_size, as made by say ftruncate(2)), would require a metadata flush.
       The aim of fdatasync() is to reduce disk activity for applications that do not require all metadata to be synchronized with the disk.

Linux同步IO:sync、fsync和fdatasync

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.