一、術語解釋
臟頁: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來把數據真正寫入硬盤。使用函數
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)原語來保證:
函數原型:
int fsync(int fd);
fsync的功能是確保文件fd所有已修改的內容已經正確同步到硬盤上,該調用會阻塞等待直到設備報告IO完成。
PS:如果采用內存映射文件的方式進行文件IO(使用mmap,將文件的page cache直接映射到進程的地址空間,通過寫內存的方式修改文件),也有類似的系統調用來確保修改的內容完全同步到硬盤之上:
#incude <sys/mman.h>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,放寬了同步的語義以提高性能:
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."
舉例來說,文件的尺寸(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優化日志同步
為了滿足事務要求,數據庫的日志文件是常常需要同步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、unix在內核中設有緩沖區、高速緩沖或頁面高速緩沖,大多數磁盤I/O都通過緩沖進行,采用延遲寫技術。
sync:將所有修改過的快緩存區排入寫隊列,然后返回,并不等待實際寫磁盤操作結束;
fsync:只對有文件描述符制定的單一文件起作用,并且等待些磁盤操作結束,然后返回;
fdatasync:類似fsync,但它只影響文件的數據部分。fsync還會同步更新文件的屬性;
fflush:標準I/O函數(如:fread,fwrite)會在內存建立緩沖,該函數刷新內存緩沖,將內容寫入內核緩沖,要想將其寫入磁盤,還需要調用fsync。(先調用fflush后調用fsync,否則不起作用)。
以上就是小編為大家帶來的函數sync、fsync與fdatasync的總結整理(必看篇)全部內容了,希望大家多多支持VEVB武林網~
新聞熱點
疑難解答
圖片精選