轉載:linux
1、術語解釋
髒頁:linux內核中的概念,由於硬盤的讀寫速度遠趕不上內存的速度,系統就把讀寫比較頻繁的數據事先放到內存中,以提升讀寫速度,這就叫高速緩存,linux是以頁做爲高速緩存的單位,當進程修改了高速緩存裏的數據時,該頁就被內核標記爲髒頁,內核將會在合適的時間把髒頁的數據寫到磁盤中去,以保持高速緩存中的數據和磁盤中的數據是一致的。數據庫
內存映射:內存映射文件,是由一個文件到一塊內存的映射。Win32提供了容許應用程序把文件映射到一個進程的函數 (CreateFileMapping)。內存映射文件與虛擬內存有些相似,經過內存映射文件能夠保留一個地址空間的區域,同時將物理存儲器提交給此區域,內存文件映射的物理存儲器來自一個已經存在於磁盤上的文件,並且在對該文件進行操做以前必須首先對文件進行映射。使用內存映射文件處理存儲於磁盤上的文件時,將沒必要再對文件執行I/O操做,使得內存映射文件在處理大數據量的文件時能起到至關重要的做用。編程
//摘錄自百度百科緩存
延遲寫(delayed write): 傳統的UNIX實如今內核中設有緩衝區高速緩存或頁面高速緩存,大多數磁盤I/O都經過緩衝進行。 當將數據寫入文件時,內核一般先將該數據複製到其中一個緩衝區中,若是該緩衝區還沒有寫滿,則 並不將其排入輸出隊列,而是等待其寫滿或者當內核須要重用該緩衝區以便存放其餘磁盤塊數據時, 再將該緩衝排入到輸出隊列,而後待其到達隊首時,才進行實際的I/O操做。這種輸出方式就被稱爲延遲寫。安全
//摘錄自《UNIX環境高級編程第三版》P65
2、正文數據結構
延遲寫減小了磁盤讀寫次數,可是卻下降了文件內容的更新速度,使得欲寫到文件中的數據在一段時間內並無寫到磁盤上。當系統發生故障時,這種延遲可能形成文件更新內容的丟失。爲了保證磁盤上實際文件系統與緩衝區高速緩存中內容的一致性,UNIX系統提供了sync、fsync和fdatasync三個函數。app
一、sync函數less
sync函數只是將全部修改過的塊緩衝區排入寫隊列,而後就返回,它並不等待實際寫磁盤操做結束。
一般稱爲update的系統守護進程會週期性地(通常每隔30秒)調用sync函數。這就保證了按期沖洗內核的塊緩衝區。命令sync(1)也調用sync函數。
二、fsync函數
fsync函數只對由文件描述符filedes指定的單一文件起做用,而且等待寫磁盤操做結束,而後返回。
fsync可用於數據庫這樣的應用程序,這種應用程序須要確保將修改過的塊當即寫到磁盤上。
三、fdatasync函數
fdatasync函數相似於fsync,但它隻影響文件的數據部分。而除數據外,fsync還會同步更新文件的屬性。
對於提供事務支持的數據庫,在事務提交時,都要確保事務日誌(包含該事務全部的修改操做以及一個提交記錄)徹底寫到硬盤上,才認定事務提交成功並返回給應用層。異步
四、fflush:標準IO函數(如fread,fwrite等)會在內存中創建緩衝,該函數刷新內存緩衝,將內容寫入內核緩衝,要想將其真正寫入磁盤,還須要調用fsync。(即先調用fflush而後再調用fsync,不然不會起做用)。fflush以指定的文件流描述符爲參數(對應以fopen等函數打開的文件流),僅僅是把上層緩衝區中的數據刷新到內核緩衝區就返回,async
所以相對於fsync而言不是很安全,還須要再調用一下fsync來把數據真正寫入硬盤。使用函數
把文件流描述符(fp)轉換爲文件描述符(fd),以方便fsync的調用,那麼,在linux操做系統上,怎樣才能保證數據被正確地寫入外部永久存儲介質?
1. write不能知足要求,須要fsync
對於write函數,咱們認爲該函數一旦返回,數據便已經寫到了文件中。可是這種概念只是宏觀上的,通常狀況下,對硬盤(或者其餘持久存儲設備)文件的write操做,更新的只是內存中的頁緩存(page cache),而髒頁不會當即更新到硬盤中,而是由操做系通通一調度,如flusher內核線程在知足必定條件時(必定時間間隔、內存中
的髒頁達到必定比例)將髒頁面同步到硬盤上(放入設備的IO請求隊列)。由於write調用不會等到硬盤IO完成以後才返回,設想若是操做系統在write調用以後、硬盤同步以前崩潰,則數據可能丟失。雖然這樣的時間窗口很小,可是對於須要保證事務的持久化(durability)和一致性(consistency)的數據庫程序來講,write()所提供的「鬆散的異步語義」是不夠的,一般須要操做系統提供的同步IO(synchronized-IO)原語來保證:
函數原型:
fsync的功能是確保文件fd全部已修改的內容已經正確同步到硬盤上,該調用會阻塞等待直到設備報告IO完成。
PS:若是採用內存映射文件的方式進行文件IO(使用mmap,將文件的page cache直接映射到進程的地址空間,經過寫內存的方式修改文件),也有相似的系統調用來確保修改的內容徹底同步到硬盤之上:
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,放寬了同步的語義以提升性能:
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的開銷
3、總結
一、若是是對全部的緩衝區發出寫硬盤的命令,應該使用sync函數,但應該注意該函數僅僅只是把該命令放入隊列就返回了,在編程時須要注意。
二、若是是要把一個已經打開的文件所作的修改提交到硬盤,應調用fsync函數,該函數會在數據實際寫入硬盤後才返回,所以是最安全最可靠的方式。
三、若是是針對一個已經打開的文件流操做,則應該首先調用fsync函數把修改同步到內核緩衝區,而後再調用fsync把修改真正的同步到硬盤。
4、附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、unix在內核中設有緩衝區、高速緩衝或頁面高速緩衝,大多數磁盤I/O都經過緩衝進行,採用延遲寫技術。sync:將全部修改過的快緩存區排入寫隊列,而後返回,並不等待實際寫磁盤操做結束;fsync:只對有文件描述符制定的單一文件起做用,而且等待些磁盤操做結束,而後返回;fdatasync:相似fsync,但它隻影響文件的數據部分。fsync還會同步更新文件的屬性;fflush:標準I/O函數(如:fread,fwrite)會在內存創建緩衝,該函數刷新內存緩衝,將內容寫入內核緩衝,要想將其寫入磁盤,還須要調用fsync。(先調用fflush後調用fsync,不然不起做用)。