linux 同步IO: sync msync、fsync、fdatasync与 fflush
最近閱讀leveldb源碼,作為一個(gè)保證可靠性的kv數(shù)據(jù)庫(kù)其數(shù)據(jù)與磁盤(pán)的交互可謂是極其關(guān)鍵,其中涉及到了不少內(nèi)存和磁盤(pán)同步的操作和策略。為了加深理解,從網(wǎng)上整理了linux池畔同步IO相關(guān)的函數(shù),這里做一個(gè)羅列和對(duì)比。大部分為copy,僅為記錄,請(qǐng)各位看官勿噴。
傳統(tǒng)的UNIX實(shí)現(xiàn)在內(nèi)核中設(shè)有緩沖區(qū)高速緩存或頁(yè)面高速緩存,大多數(shù)磁盤(pán)I/O都通過(guò)緩沖進(jìn)行。當(dāng)將數(shù)據(jù)寫(xiě)入文件時(shí),內(nèi)核通常先將該數(shù)據(jù)復(fù)制到其中一個(gè)緩沖區(qū)中,如果該緩沖區(qū)尚未寫(xiě)滿(mǎn),則并不將其排入輸出隊(duì)列,而是等待其寫(xiě)滿(mǎn)或者當(dāng)內(nèi)核需要重用該緩沖區(qū)以便存放其他磁盤(pán)塊數(shù)據(jù)時(shí),再將該緩沖排入輸出隊(duì)列,然后待其到達(dá)隊(duì)首時(shí),才進(jìn)行實(shí)際的I/O操作。這種輸出方式被稱(chēng)為延遲寫(xiě)(delayed write)(Bach [1986]第3章詳細(xì)討論了緩沖區(qū)高速緩存)。
延遲寫(xiě)減少了磁盤(pán)讀寫(xiě)次數(shù),但是卻降低了文件內(nèi)容的更新速度,使得欲寫(xiě)到文件中的數(shù)據(jù)在一段時(shí)間內(nèi)并沒(méi)有寫(xiě)到磁盤(pán)上。當(dāng)系統(tǒng)發(fā)生故障時(shí),這種延遲可能造成文件更新內(nèi)容的丟失。為了保證磁盤(pán)上實(shí)際文件系統(tǒng)與緩沖區(qū)高速緩存中內(nèi)容的一致性,UNIX系統(tǒng)提供了sync、fsync和fdatasync三個(gè)函數(shù)。
sync函數(shù)只是將所有修改過(guò)的塊緩沖區(qū)排入寫(xiě)隊(duì)列,然后就返回,它并不等待實(shí)際寫(xiě)磁盤(pán)操作結(jié)束。
通常稱(chēng)為update的系統(tǒng)守護(hù)進(jìn)程會(huì)周期性地(一般每隔30秒)調(diào)用sync函數(shù)。這就保證了定期沖洗內(nèi)核的塊緩沖區(qū)。命令sync(1)也調(diào)用sync函數(shù)。
fsync函數(shù)只對(duì)由文件描述符filedes指定的單一文件起作用,并且等待寫(xiě)磁盤(pán)操作結(jié)束,然后返回。fsync可用于數(shù)據(jù)庫(kù)這樣的應(yīng)用程序,這種應(yīng)用程序需要確保將修改過(guò)的塊立即寫(xiě)到磁盤(pán)上。
fdatasync函數(shù)類(lèi)似于fsync,但它只影響文件的數(shù)據(jù)部分。而除數(shù)據(jù)外,fsync還會(huì)同步更新文件的屬性。?
對(duì)于提供事務(wù)支持的數(shù)據(jù)庫(kù),在事務(wù)提交時(shí),都要確保事務(wù)日志(包含該事務(wù)所有的修改操作以及一個(gè)提交記錄)完全寫(xiě)到硬盤(pán)上,才認(rèn)定事務(wù)提交成功并返回給應(yīng)用層。?
一個(gè)簡(jiǎn)單的問(wèn)題:在*nix操作系統(tǒng)上,怎樣保證對(duì)文件的更新內(nèi)容成功持久化到硬盤(pán)??
1.? write不夠,需要fsync
一般情況下,對(duì)硬盤(pán)(或者其他持久存儲(chǔ)設(shè)備)文件的write操作,更新的只是內(nèi)存中的頁(yè)緩存(page cache),而臟頁(yè)面不會(huì)立即更新到硬盤(pán)中,而是由操作系統(tǒng)統(tǒng)一調(diào)度,如由專(zhuān)門(mén)的flusher內(nèi)核線(xiàn)程在滿(mǎn)足一定條件時(shí)(如一定時(shí)間間隔、內(nèi)存中的臟頁(yè)達(dá)到一定比例)內(nèi)將臟頁(yè)面同步到硬盤(pán)上(放入設(shè)備的IO請(qǐng)求隊(duì)列)。?
因?yàn)閣rite調(diào)用不會(huì)等到硬盤(pán)IO完成之后才返回,因此如果OS在write調(diào)用之后、硬盤(pán)同步之前崩潰,則數(shù)據(jù)可能丟失。雖然這樣的時(shí)間窗口很小,但是對(duì)于需要保證事務(wù)的持久化(durability)和一致性(consistency)的數(shù)據(jù)庫(kù)程序來(lái)說(shuō),write()所提供的“松散的異步語(yǔ)義”是不夠的,通常需要OS提供的同步IO(synchronized-IO)原語(yǔ)來(lái)保證:
int fsync(int fd);fsync的功能是確保文件fd所有已修改的內(nèi)容已經(jīng)正確同步到硬盤(pán)上,該調(diào)用會(huì)阻塞等待直到設(shè)備報(bào)告IO完成。
PS:如果采用內(nèi)存映射文件的方式進(jìn)行文件IO(使用mmap,將文件的page cache直接映射到進(jìn)程的地址空間,通過(guò)寫(xiě)內(nèi)存的方式修改文件),也有類(lèi)似的系統(tǒng)調(diào)用來(lái)確保修改的內(nèi)容完全同步到硬盤(pán)之上:
int msync(void *addr, size_t length, int flags)msync需要指定同步的地址區(qū)間,如此細(xì)粒度的控制似乎比f(wàn)sync更加高效(因?yàn)閼?yīng)用程序通常知道自己的臟頁(yè)位置),但實(shí)際上(Linux)kernel中有著十分高效的數(shù)據(jù)結(jié)構(gòu),能夠很快地找出文件的臟頁(yè),使得fsync只會(huì)同步文件的修改內(nèi)容。
2. fsync的性能問(wèn)題,與fdatasync
除了同步文件的修改內(nèi)容(臟頁(yè)),fsync還會(huì)同步文件的描述信息(metadata,包括size、訪(fǎng)問(wèn)時(shí)間st_atime & st_mtime等等),因?yàn)槲募臄?shù)據(jù)和metadata通常存在硬盤(pán)的不同地方,因此fsync至少需要兩次IO寫(xiě)操作,fsync的man page這樣說(shuō):
"Unfortunately fsync() will always initialize two write operations : one for the newly written data and another one in order to update the modification time stored in the inode. If the modification time is not a part of the transaction concept fdatasync() can be used to avoid unnecessary inode disk write operations." ?多余的一次IO操作,有多么昂貴呢?根據(jù)Wikipedia的數(shù)據(jù),當(dāng)前硬盤(pán)驅(qū)動(dòng)的平均尋道時(shí)間(Average seek time)大約是3~15ms,7200RPM硬盤(pán)的平均旋轉(zhuǎn)延遲(Average rotational latency)大約為4ms,因此一次IO操作的耗時(shí)大約為10ms左右。這個(gè)數(shù)字意味著什么?下文還會(huì)提到。
Posix同樣定義了fdatasync,放寬了同步的語(yǔ)義以提高性能:
int fdatasync(int fd); fdatasync的功能與fsync類(lèi)似,但是僅僅在必要的情況下才會(huì)同步metadata,因此可以減少一次IO寫(xiě)操作。那么,什么是“必要的情況”呢?根據(jù)man page中的解釋: "fdatasync does not flush modified metadata unless that metadata is needed in order to allow a subsequent data retrieval to be corretly handled."舉例來(lái)說(shuō),文件的尺寸(st_size)如果變化,是需要立即同步的,否則OS一旦崩潰,即使文件的數(shù)據(jù)部分已同步,由于metadata沒(méi)有同步,依然讀不到修改的內(nèi)容。而最后訪(fǎng)問(wèn)時(shí)間(atime)/修改時(shí)間(mtime)是不需要每次都同步的,只要應(yīng)用程序?qū)@兩個(gè)時(shí)間戳沒(méi)有苛刻的要求,基本無(wú)傷大雅。
PS:open時(shí)的參數(shù)O_SYNC/O_DSYNC有著和fsync/fdatasync類(lèi)似的語(yǔ)義:使每次write都會(huì)阻塞等待硬盤(pán)IO完成。(實(shí)際上,Linux對(duì)O_SYNC/O_DSYNC做了相同處理,沒(méi)有滿(mǎn)足Posix的要求,而是都實(shí)現(xiàn)了fdatasync的語(yǔ)義)相對(duì)于fsync/fdatasync,這樣的設(shè)置不夠靈活,應(yīng)該很少使用。
3. 使用fdatasync優(yōu)化日志同步
文章開(kāi)頭時(shí)已提到,為了滿(mǎn)足事務(wù)要求,數(shù)據(jù)庫(kù)的日志文件是常常需要同步IO的。由于需要同步等待硬盤(pán)IO完成,所以事務(wù)的提交操作常常十分耗時(shí),成為性能的瓶頸。
在Berkeley DB下,如果開(kāi)啟了AUTO_COMMIT(所有獨(dú)立的寫(xiě)操作自動(dòng)具有事務(wù)語(yǔ)義)并使用默認(rèn)的同步級(jí)別(日志完全同步到硬盤(pán)才返回),寫(xiě)一條記錄的耗時(shí)大約為5~10ms級(jí)別,基本和一次IO操作(10ms)的耗時(shí)相同。
我們已經(jīng)知道,在同步上fsync是低效的。但是如果需要使用fdatasync減少對(duì)metadata的更新,則需要確保文件的尺寸在write前后沒(méi)有發(fā)生變化。日志文件天生是追加型(append-only)的,總是在不斷增大,似乎很難利用好fdatasync。
且看Berkeley DB是怎樣處理日志文件的:
??????? 1.每個(gè)log文件固定為10MB大小,從1開(kāi)始編號(hào),名稱(chēng)格式為“l(fā)og.%010d"
??????? 2.每次log文件創(chuàng)建時(shí),先寫(xiě)文件的最后1個(gè)page,將log文件擴(kuò)展為10MB大小
??????? 3.向log文件中追加記錄時(shí),由于文件的尺寸不發(fā)生變化,使用fdatasync可以大大優(yōu)化寫(xiě)log的效率
??????? 4.如果一個(gè)log文件寫(xiě)滿(mǎn)了,則新建一個(gè)log文件,也只有一次同步metadata的開(kāi)銷(xiāo)
4. fflush
??? 標(biāo)準(zhǔn)IO函數(shù)(如fread,fwrite等)會(huì)在內(nèi)存中建立緩沖,該函數(shù)刷新內(nèi)存緩沖,將內(nèi)容寫(xiě)入內(nèi)核緩沖,而要想將其真正寫(xiě)入磁盤(pán),還需要調(diào)用fsync。(即先調(diào)用fflush然后再調(diào)用fsync,否則不會(huì)起作用)。fflush以指定的文件流描述符為參數(shù)(對(duì)應(yīng)以fopen等函數(shù)打開(kāi)的文件流),僅僅是把上層緩沖區(qū)中的數(shù)據(jù)刷新到內(nèi)核緩沖區(qū)就返回,因此相對(duì)于fsync而言不是很安全,還需要再調(diào)用一下fsync來(lái)把數(shù)據(jù)真正寫(xiě)入硬盤(pán)。為了實(shí)現(xiàn)以上功能,需要把文件流描述符(fp)轉(zhuǎn)換為文件描述符(fd),以方便fsync的調(diào)用,使用以下函數(shù):int fileno(FILE *fp); <- in stdio.
fflush是libc.a中提供的方法,
fsync是系統(tǒng)提供的系統(tǒng)調(diào)用。
2.原形
fflush接受一個(gè)參數(shù)FILE *.
fflush(FILE *);
fsync接受的時(shí)一個(gè)Int型的文件描述符。
fsync(int fd);
3.功能
fflush:是把C庫(kù)中的緩沖調(diào)用write函數(shù)寫(xiě)到磁盤(pán)[其實(shí)是寫(xiě)到內(nèi)核的緩沖區(qū)]。
fsync:是把內(nèi)核緩沖刷到磁盤(pán)上。
c庫(kù)緩沖-----fflush---------〉內(nèi)核緩沖--------fsync-----〉磁盤(pán)
總結(jié)
以上是生活随笔為你收集整理的linux 同步IO: sync msync、fsync、fdatasync与 fflush的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 在VI中删除行尾的换行符
- 下一篇: XGP怎么购买