主頁(yè) > 知識(shí)庫(kù) > 詳細(xì)介紹Linux IO

詳細(xì)介紹Linux IO

熱門(mén)標(biāo)簽:鐵路電話系統(tǒng) AI電銷 呼叫中心市場(chǎng)需求 百度競(jìng)價(jià)排名 地方門(mén)戶網(wǎng)站 網(wǎng)站排名優(yōu)化 Linux服務(wù)器 服務(wù)外包

1.IO概述

分析一下寫(xiě)操作:

char *buf = malloc(MAX_BUF_SIZE);

strncpy(buf, src, , MAX_BUF_SIZE);

fwrite(buf, MAX_BUF_SIZE, 1, fp);

fclose(fp);

以下圖為例:分析數(shù)據(jù)流寫(xiě)入硬盤(pán)的過(guò)程

malloc的buf對(duì)于圖層中的application buffer,即應(yīng)用程序的buffer;

調(diào)用fwrite后,把數(shù)據(jù)從application buffer 拷貝到了 CLib buffer,即C庫(kù)標(biāo)準(zhǔn)IObuffer。

fwrite返回后,數(shù)據(jù)還在CLib buffer,如果這時(shí)候進(jìn)程core掉。這些數(shù)據(jù)會(huì)丟失。沒(méi)有寫(xiě)到磁盤(pán)介質(zhì)上。當(dāng)調(diào)用fclose的時(shí)候,fclose調(diào)用會(huì)把這些數(shù)據(jù)刷新到磁盤(pán)介質(zhì)上。

除了fclose方法外,還有一個(gè)主動(dòng)刷新操作fflush函數(shù),不過(guò)fflush函數(shù)只是把數(shù)據(jù)從CLib buffer 拷貝到page cache 中,并沒(méi)有刷新到磁盤(pán)上,從page cache刷新到磁盤(pán)上可以通過(guò)調(diào)用fsync函數(shù)完成。

fwrite是系統(tǒng)提供的最上層接口,也是最常用的接口。它在用戶進(jìn)程空間開(kāi)辟一個(gè)buffer,將多次小數(shù)據(jù)量相鄰寫(xiě)操作先緩存起來(lái),合并,最終調(diào)用write函數(shù)一次性寫(xiě)入(或者將大塊數(shù)據(jù)分解多次write調(diào)用)。

Write函數(shù)通過(guò)調(diào)用系統(tǒng)調(diào)用接口,將數(shù)據(jù)從應(yīng)用層copy到內(nèi)核層,所以write會(huì)觸發(fā)內(nèi)核態(tài)/用戶態(tài)切換。當(dāng)數(shù)據(jù)到達(dá)page cache后,內(nèi)核并不會(huì)立即把數(shù)據(jù)往下傳遞。而是返回用戶空間。數(shù)據(jù)什么時(shí)候?qū)懭胗脖P(pán),有內(nèi)核IO調(diào)度決定,所以write是一個(gè)異步調(diào)用。這一點(diǎn)和read不同,read調(diào)用是先檢查page cache里面是否有數(shù)據(jù),如果有,就取出來(lái)返回用戶,如果沒(méi)有,就同步傳遞下去并等待有數(shù)據(jù),再返回用戶,所以read是一個(gè)同步過(guò)程。當(dāng)然你也可以把write的異步過(guò)程改成同步過(guò)程,就是在open文件的時(shí)候帶上O_SYNC標(biāo)記。

數(shù)據(jù)到了page cache后,內(nèi)核有pdflush線程在不停的檢測(cè)臟頁(yè),判斷是否要寫(xiě)回到磁盤(pán)中。把需要寫(xiě)回的頁(yè)提交到IO隊(duì)列——即IO調(diào)度隊(duì)列。IO調(diào)度隊(duì)列調(diào)度策略調(diào)度何時(shí)寫(xiě)回。

IO隊(duì)列有2個(gè)主要任務(wù)。一是合并相鄰扇區(qū)的,而是排序。合并相信很容易理解,排序就是盡量按照磁盤(pán)選擇方向和磁頭前進(jìn)方向排序。因?yàn)榇蓬^尋道時(shí)間是和昂貴的。

這里IO隊(duì)列和我們常用的分析工具IOStat關(guān)系密切。IOStat中rrqm/s wrqm/s表示讀寫(xiě)合并個(gè)數(shù)。avgqu-sz表示平均隊(duì)列長(zhǎng)度。

內(nèi)核中有多種IO調(diào)度算法。當(dāng)硬盤(pán)是SSD時(shí)候,沒(méi)有什么磁道磁頭,人家是隨機(jī)讀寫(xiě)的,加上這些調(diào)度算法反而畫(huà)蛇添足。OK,剛好有個(gè)調(diào)度算法叫noop調(diào)度算法,就是什么都不錯(cuò)(合并是做了)。剛好可以用來(lái)配置SSD硬盤(pán)的系統(tǒng)。

從IO隊(duì)列出來(lái)后,就到了驅(qū)動(dòng)層(當(dāng)然內(nèi)核中有更多的細(xì)分層,這里忽略掉),驅(qū)動(dòng)層通過(guò)DMA,將數(shù)據(jù)寫(xiě)入磁盤(pán)cache。

至于磁盤(pán)cache時(shí)候?qū)懭氪疟P(pán)介質(zhì),那是磁盤(pán)控制器自己的事情。如果想要睡個(gè)安慰覺(jué),確認(rèn)要寫(xiě)到磁盤(pán)介質(zhì)上。就調(diào)用fsync函數(shù)吧??梢源_定寫(xiě)到磁盤(pán)上了。

2.linux IO子系統(tǒng)和文件系統(tǒng)讀寫(xiě)流程

I/O子系統(tǒng)是個(gè)層次很深的系統(tǒng),數(shù)據(jù)請(qǐng)求從用戶空間最終到達(dá)磁盤(pán),經(jīng)過(guò)了復(fù)雜的數(shù)據(jù)流動(dòng)。

read系統(tǒng)調(diào)用的處理分為用戶空間和內(nèi)核空間處理兩部分。其中,用戶空間處理只是通過(guò)0x80中斷陷入內(nèi)核,接著調(diào)用其中斷服務(wù)例程,即sys_read以進(jìn)入內(nèi)核處理流程。

對(duì)于read系統(tǒng)調(diào)用在內(nèi)核的處理,如上圖所述,經(jīng)過(guò)了VFS、具體文件系統(tǒng),如ext2、頁(yè)高速緩沖存層、通用塊層、IO調(diào)度層、設(shè)備驅(qū)動(dòng)層、和設(shè)備層。其中,VFS主要是用來(lái)屏蔽下層具體文件系統(tǒng)操作的差異,對(duì)上提供一個(gè)統(tǒng)一接口,正是因?yàn)橛辛诉@個(gè)層次,所以可以把設(shè)備抽象成文件。具體文件系統(tǒng),則定義了自己的塊大小、操作集合等。引入cache層的目的,是為了提高IO效率。它緩存了磁盤(pán)上的部分?jǐn)?shù)據(jù),當(dāng)請(qǐng)求到達(dá)時(shí),如果在cache中存在該數(shù)據(jù)且是最新的,則直接將其傳遞給用戶程序,免除了對(duì)底層磁盤(pán)的操作。通用塊層的主要工作是,接收上層發(fā)出的磁盤(pán)請(qǐng)求,并最終發(fā)出IO請(qǐng)求(BIO)。IO調(diào)度層則試圖根據(jù)設(shè)置好的調(diào)度算法對(duì)通用塊層的bio請(qǐng)求合并和排序,回調(diào)驅(qū)動(dòng)層提供的請(qǐng)求處理函數(shù),以處理具體的IO請(qǐng)求。驅(qū)動(dòng)層的驅(qū)動(dòng)程序?qū)?yīng)具體的物理設(shè)備,它從上層取出IO請(qǐng)求,并根據(jù)該IO請(qǐng)求中指定的信息,通過(guò)向具體塊設(shè)備的設(shè)備控制器發(fā)送命令的方式,來(lái)操縱設(shè)備傳輸數(shù)據(jù)。設(shè)備層都是具體的物理設(shè)備。

VFS層

內(nèi)核函數(shù)sys_read是read系統(tǒng)調(diào)用在該層的入口點(diǎn)。它根據(jù)文件fd指定的索引,從當(dāng)前進(jìn)程描述符中取出相應(yīng)的file對(duì)象,并調(diào)用vfs_read執(zhí)行文件讀取操作。vfs_read會(huì)調(diào)用與具體文件相關(guān)的read函數(shù)執(zhí)行讀取操作,file->f_op.read。然后,VFS將控制權(quán)交給了ext2文件系統(tǒng)。(ext2在此作為示例,進(jìn)行解析)

Ext2文件系統(tǒng)層

通過(guò)ext2_file_operations結(jié)構(gòu)知道,上述函數(shù)最終會(huì)調(diào)用到do_sync_read函數(shù),它是系統(tǒng)通用的讀取函數(shù)。所以說(shuō),do_sync_read才是ext2層的真實(shí)入口。該層入口函數(shù) do_sync_read 調(diào)用函數(shù) generic_file_aio_read ,后者判斷本次讀請(qǐng)求的訪問(wèn)方式,如果是直接 io (filp->f_flags 被設(shè)置了 O_DIRECT 標(biāo)志,即不經(jīng)過(guò) cache)的方式,則調(diào)用 generic_file_direct_IO 函數(shù);如果是 page cache 的方式,則調(diào)用 do_generic_file_read 函數(shù)。它會(huì)判斷該頁(yè)是否在頁(yè)高速緩存,如果是,直接將數(shù)據(jù)拷貝到用戶空間。如果不在,則調(diào)用page_cache_sync_readahead函數(shù)執(zhí)行預(yù)讀(檢查是否可以預(yù)讀),它會(huì)調(diào)用mpage_readpages。如果仍然未能命中(可能不允許預(yù)讀或者其它原因),則直接跳轉(zhuǎn)readpage,執(zhí)行mpage_readpage,從磁盤(pán)讀取數(shù)據(jù)。在mpage_readpages(一次讀多個(gè)頁(yè))中,它會(huì)將連續(xù)的磁盤(pán)塊放入同一個(gè)BIO,并延緩BIO的提交,直到出現(xiàn)不連續(xù)的塊,則直接提交BIO,再繼續(xù)處理,以構(gòu)造另外的BIO。

page cache 結(jié)構(gòu)

圖5顯示了一個(gè)文件的 page cache 結(jié)構(gòu)。文件被分割為一個(gè)個(gè)以 page 大小為單元的數(shù)據(jù)塊,這些數(shù)據(jù)塊(頁(yè))被組織成一個(gè)多叉樹(shù)(稱為 radix 樹(shù))。樹(shù)中所有葉子節(jié)點(diǎn)為一個(gè)個(gè)頁(yè)幀結(jié)構(gòu)(struct page),表示了用于緩存該文件的每一個(gè)頁(yè)。在葉子層最左端的第一個(gè)頁(yè)保存著該文件的前4096個(gè)字節(jié)(如果頁(yè)的大小為4096字節(jié)),接下來(lái)的頁(yè)保存著文件第二個(gè)4096個(gè)字節(jié),依次類推。樹(shù)中的所有中間節(jié)點(diǎn)為組織節(jié)點(diǎn),指示某一地址上的數(shù)據(jù)所在的頁(yè)。此樹(shù)的層次可以從0層到6層,所支持的文件大小從0字節(jié)到16 T 個(gè)字節(jié)。樹(shù)的根節(jié)點(diǎn)指針可以從和文件相關(guān)的 address_space 對(duì)象(該對(duì)象保存在和文件關(guān)聯(lián)的 inode 對(duì)象中)中取得(更多關(guān)于 page cache 的結(jié)構(gòu)內(nèi)容請(qǐng)參見(jiàn)參考資料)。

mpage處理機(jī)制就是page cache層要處理的問(wèn)題。

通用塊層

在緩存層處理末尾,執(zhí)行mpage_submit_bio之后,會(huì)調(diào)用generic_make_request函數(shù)。這是通用塊層的入口函數(shù)。它將bio傳送到IO調(diào)度層進(jìn)行處理。

IO調(diào)度層

對(duì)bio進(jìn)行合并、排序,以提高IO效率。然后,調(diào)用設(shè)備驅(qū)動(dòng)層的回調(diào)函數(shù),request_fn,轉(zhuǎn)到設(shè)備驅(qū)動(dòng)層處理。

設(shè)備驅(qū)動(dòng)層

request函數(shù)對(duì)請(qǐng)求隊(duì)列中每個(gè)bio進(jìn)行分別處理,根據(jù)bio中的信息向磁盤(pán)控制器發(fā)送命令。處理完成后,調(diào)用完成函數(shù)end_bio以通知上層完成。

3.IO之流程與buffer概覽

一般情況下,進(jìn)程在io的時(shí)候,要依賴于內(nèi)核中的一個(gè)buffer模塊來(lái)和外存發(fā)生數(shù)據(jù)交換行為。另一個(gè)角度來(lái)說(shuō),數(shù)據(jù)從應(yīng)用進(jìn)程自己的buffer流動(dòng)到外存,中間要先拷貝到內(nèi)核的buffer中,然后再由內(nèi)核決定什么時(shí)候把這些載有數(shù)據(jù)的內(nèi)核buffer寫(xiě)出到外存。

“buffer cache”僅僅被內(nèi)核用于常規(guī)文件(磁盤(pán)文件)的I/O操作。

內(nèi)核中的buffer模塊-“buffer cache”(buffer,cache的功能兼?zhèn)?

一般情況下,read,write系統(tǒng)調(diào)用并不直接訪問(wèn)磁盤(pán)。這兩個(gè)系統(tǒng)調(diào)用僅僅是在用戶空間和內(nèi)核空間的buffer之間傳遞目標(biāo)數(shù)據(jù)。舉個(gè)例子,下面的write系統(tǒng)調(diào)用僅僅是把3個(gè)字節(jié)從用戶空間拷貝到內(nèi)核空間的buffer之后就直接返回了

write(fd,”abc”,3);

在以后的某個(gè)時(shí)間點(diǎn)上,內(nèi)核把裝著“abc”三個(gè)字節(jié)的buffer寫(xiě)入(flush)磁盤(pán)。如果另外的進(jìn)程在這個(gè)過(guò)程中想要讀剛才被打開(kāi)寫(xiě)的那個(gè)文件怎么辦?答案是:內(nèi)核會(huì)從剛才的buffer提供要讀取的數(shù)據(jù),而不是從磁盤(pán)讀。

當(dāng)前系統(tǒng)上第一次讀一個(gè)文件時(shí),read系統(tǒng)調(diào)用觸發(fā)內(nèi)核以blocrk為單位從磁盤(pán)讀取文件數(shù)據(jù),并把數(shù)據(jù)blocks存入內(nèi)核buffer,然后read不斷地從這個(gè)buffer取需要的數(shù)據(jù),直到buffer中的數(shù)據(jù)全部被讀完,接下來(lái),內(nèi)核從磁盤(pán)按順序把當(dāng)前文件后面的blocks再讀入內(nèi)核buffer,然后read重復(fù)之前的動(dòng)作…

一般的文件訪問(wèn),都是這種不斷的順序讀取的行為,為了加速應(yīng)用程序讀磁盤(pán),unix的設(shè)計(jì)者們?yōu)檫@種普遍的順序讀取行為,設(shè)計(jì)了這樣的機(jī)制—-預(yù)讀,來(lái)保證進(jìn)程在想讀后續(xù)數(shù)據(jù)的時(shí)候,這些后續(xù)數(shù)據(jù)已經(jīng)的由內(nèi)核預(yù)先從磁盤(pán)讀好并且放在buffer里了。這么做的原因是磁盤(pán)的io訪問(wèn)比內(nèi)存的io訪問(wèn)要慢很多,指數(shù)級(jí)的差別。

read,write從語(yǔ)義和概念上來(lái)說(shuō),本來(lái)是必須要直接和磁盤(pán)交互的,調(diào)用時(shí)間非常長(zhǎng),應(yīng)用每次在使用這兩個(gè)系統(tǒng)的時(shí)候,從表象上來(lái)說(shuō)都是被卡住。而有了這些buffer,這些系統(tǒng)調(diào)用就直接和buffer交互就可以了,大幅的加速了應(yīng)用執(zhí)行。

Linux內(nèi)核并沒(méi)有規(guī)定”buffer cache”的尺寸上線,原則上來(lái)說(shuō),除了系統(tǒng)正常運(yùn)行所必需和用戶進(jìn)程自身所必需的之外的內(nèi)存都可以被”buffer cache”使用。而系統(tǒng)和用戶進(jìn)程需要申請(qǐng)更多的內(nèi)存的時(shí)候,”buffer cache”的內(nèi)存釋放行為會(huì)被觸發(fā),一些長(zhǎng)久未被讀取,以及被寫(xiě)過(guò)的臟頁(yè)就會(huì)被釋放和寫(xiě)入磁盤(pán),騰出內(nèi)存,以便被需要的行為方使用。

”buffer cache”有五個(gè)flush的觸發(fā)點(diǎn):

1.pdflush(內(nèi)核線程)定期flush;

2.系統(tǒng)和其他進(jìn)程需要內(nèi)存的時(shí)候觸發(fā)它flush;

3.用戶手工sync,外部命令觸發(fā)它flush;

4.proc內(nèi)核接口觸發(fā)flush,”echo 3 >/proc/sys/vm/drop_caches;

5.應(yīng)用程序內(nèi)部控制flush。

這個(gè)”buffer cache”從概念上的理解就是這些了,實(shí)際上,更準(zhǔn)確的說(shuō),linux從2.4開(kāi)始就不再維護(hù)獨(dú)立的”buffer cache”模塊了,而是把它的功能并入了”page cache”這個(gè)內(nèi)存管理的子系統(tǒng)了,”buffer cache”現(xiàn)在已經(jīng)是一個(gè)unix系統(tǒng)族的普遍的歷史概念了

高性能寫(xiě)文件

寫(xiě)100MB的數(shù)據(jù)

場(chǎng)景1,1次寫(xiě)1個(gè)字節(jié),總共write 100M次;

場(chǎng)景2,1次寫(xiě)1K個(gè)字節(jié),總共write 100K次;

場(chǎng)景3,1次寫(xiě)4K個(gè)字節(jié),總共write 25K次;

場(chǎng)景4,1次寫(xiě)16k個(gè)字節(jié),總共write大約不到7K次。

以上4種寫(xiě)入方式,內(nèi)核寫(xiě)磁盤(pán)的次數(shù)基本相同,因?yàn)閷?xiě)磁盤(pán)的單位是block,而不是字節(jié)?,F(xiàn)在的系統(tǒng)默認(rèn)的block都是4k。

第1種性能非常差,user time和system time執(zhí)行時(shí)間都很長(zhǎng),既然寫(xiě)盤(pán)次數(shù)都差不多,那他慢在哪兒呢?答案是系統(tǒng)調(diào)用的次數(shù)太多

第2種,user time和system time都顯著降低,不過(guò)system time降低幅度更大

第2種以后,性能差別就不是很高了,第3種和第4種性能幾乎一樣

有興趣的朋友可以試一試,如果你的服務(wù)器很好,可以適當(dāng)放大測(cè)試樣本。

總而言之,得出的結(jié)論是以block的尺寸為write(fd, sizeof(buf),buf)的調(diào)用單位就可以了,再大對(duì)性能也沒(méi)什么太大的提高。

題外話:一個(gè)衡量涉及IO程序的好壞的粗略標(biāo)準(zhǔn)是“程序運(yùn)行應(yīng)該盡量集中在user time,避免大量的system time”以及“IO的時(shí)候肯定是需要一些應(yīng)用層buf的,比如上述4個(gè)場(chǎng)景,匹配就可以了(比如場(chǎng)景3,場(chǎng)景1和場(chǎng)景2會(huì)導(dǎo)致系統(tǒng)調(diào)用次數(shù)太多,場(chǎng)景4使用的buf尺寸過(guò)于浪費(fèi))”

每個(gè)系統(tǒng)調(diào)用在返回的時(shí)候,會(huì)有一個(gè)從內(nèi)核態(tài)向用戶態(tài)切換的間隙,每次在這個(gè)間隙里面,系統(tǒng)要干兩個(gè)事情—-遞送信號(hào)和進(jìn)程調(diào)度,其中進(jìn)程調(diào)度會(huì)重新計(jì)算全部RUN狀態(tài)進(jìn)程的優(yōu)先級(jí)。

系統(tǒng)調(diào)用太多的話,遞送信號(hào)和進(jìn)程調(diào)度引起的計(jì)算量是不容忽視的。

精確地flush “buffer cache”

在很多業(yè)務(wù)場(chǎng)景下,我們僅僅調(diào)用write()把需要寫(xiě)盤(pán)的數(shù)據(jù)推送至內(nèi)核的”buffer cache”中,這是很不負(fù)責(zé)任的。或許我們應(yīng)該不斷地頻繁地把”buffer cache”中的數(shù)據(jù)強(qiáng)制flush到磁盤(pán),盡最大可能保證我們的業(yè)務(wù)數(shù)據(jù)盡量不因斷電而丟失。

天下沒(méi)有免費(fèi)的午餐,既想要效率(寫(xiě)入內(nèi)核buffer),又想要安全性(數(shù)據(jù)必須flush到外存介質(zhì)中才安全),這似乎是很矛盾的。SUSv3(Single UNIX Specification Version 3)給了這種需求一個(gè)折中的解決方案,讓OS盡量滿足我們的苛刻的要求。介紹這個(gè)折中方案之前,有兩個(gè)SUSv3提案的規(guī)范很重要,說(shuō)明如下:

1.數(shù)據(jù)完整性同步(synchronized I/O data integrity)

一個(gè)常規(guī)文件所包含的信息有兩種:文件元數(shù)據(jù)和文件內(nèi)容數(shù)據(jù)。

文件元數(shù)據(jù)包括:文件所屬用戶、組、訪問(wèn)權(quán)限,文件尺寸,文件硬連接數(shù)目,最后訪問(wèn)時(shí)間戳,最后修改時(shí)間戳,最后文件元數(shù)據(jù)修改時(shí)間戳,文件數(shù)據(jù)塊指針。

對(duì)于文件內(nèi)容數(shù)據(jù),大家應(yīng)該都很清楚是什么東西。

對(duì)于寫(xiě)操作,這個(gè)規(guī)范規(guī)定了,寫(xiě)文件時(shí)保證文件內(nèi)容數(shù)據(jù)和必要的文件元數(shù)據(jù)保持完整性即可。粗糙地舉個(gè)例子來(lái)解釋這個(gè)規(guī)范,某次flush內(nèi)核中的數(shù)據(jù)到磁盤(pán)的時(shí)候,僅僅把文件內(nèi)容數(shù)據(jù)寫(xiě)入磁盤(pán)即可,但是如果這次寫(xiě)文件導(dǎo)致了文件尺寸的變化,那么這個(gè)文件尺寸作為文件的元數(shù)據(jù)也需要被寫(xiě)入磁盤(pán),必要信息保持同步。而其他的文件元數(shù)據(jù),例如修改時(shí)間,訪問(wèn)時(shí)間一概略去,不需要同步。

2.文件完整性同步(synchronized I/O file integrity)

相對(duì)于數(shù)據(jù)完整性同步而言,這個(gè)規(guī)范規(guī)定了,所有內(nèi)容數(shù)據(jù)以及元數(shù)據(jù)都要同步。

下面來(lái)介紹linux提供的幾種flush內(nèi)核緩沖數(shù)據(jù)的幾種方案,相信看完之后,大家應(yīng)該知道上述提及的折中方案是怎樣的:)

1.int fsync(int fd);

文件完整性同步;

2.int fdatasync(int fd);

數(shù)據(jù)完整性同步。

fdatasync相對(duì)于fsync的意義在于,fdatasync大致僅需要一次磁盤(pán)操作,而fsync需要兩次磁盤(pán)操作。舉例說(shuō)明一下,假如文件內(nèi)容改變了,但是文件尺寸并沒(méi)有發(fā)生變化,那調(diào)用fdatasync僅僅是把文件內(nèi)容數(shù)據(jù)flush到磁盤(pán),而fsync不僅僅把文件內(nèi)容flush刷入磁盤(pán),還要把文件的last modified time也同步到磁盤(pán)文件系統(tǒng)。last modified time屬于文件的元數(shù)據(jù),一般情況下文件的元數(shù)據(jù)和文件內(nèi)容數(shù)據(jù)在磁盤(pán)上不是連續(xù)存放的,寫(xiě)完內(nèi)容數(shù)據(jù)再寫(xiě)元數(shù)據(jù),必然涉及到磁盤(pán)的seek,而seek又是機(jī)械硬盤(pán)速度慢的根源。。。

在某些業(yè)務(wù)場(chǎng)景下,fdatasync和fsync的這點(diǎn)微小差別會(huì)導(dǎo)致應(yīng)用程序性能的大幅差異。

3.sync_file_range()

這個(gè)接口是linux從2.6.17之后實(shí)現(xiàn)的,是linux獨(dú)有的非標(biāo)準(zhǔn)接口。這個(gè)接口提供了比f(wàn)datasync更為精準(zhǔn)的flush數(shù)據(jù)的能力。詳細(xì)請(qǐng)參照man。

4.void sync(void);

強(qiáng)制”buffer cache”中的數(shù)據(jù)全部flush到磁盤(pán),并且要遵循文件完整性同步。

上面4種方式介紹完畢,open()系統(tǒng)調(diào)用的打開(kāi)文件的標(biāo)志位,比如O_DSYNC諸如此類的標(biāo)志,對(duì)flush數(shù)據(jù)的影響和上面幾個(gè)接口作用類似。

預(yù)讀

上面介紹了寫(xiě)buffer以及如何控制buffer的flush,下面來(lái)講一講如何控制讀cache的行為。

讀cache這一塊,基本上,我們可以控制的就是文件的預(yù)讀。

我們從POSIX規(guī)定的一個(gè)接口來(lái)論述一下如何控制文件的預(yù)讀以及控制它的意義。接口原型如下:

int posix_fadvise(int fd, off_t offset, off_t len, int advice);

fd:打開(kāi)文件的描述符其實(shí);

offset和len:指明文件區(qū)域;

advice:預(yù)讀的方式。預(yù)讀方式及其意義如下:

POSIX_FADV_NORMAL:內(nèi)核默認(rèn)的預(yù)讀方式;

POSIX_FADV_RANDOM:內(nèi)核禁用預(yù)讀。適合隨機(jī)讀文件的業(yè)務(wù),每次按業(yè)務(wù)要求的量讀取數(shù)據(jù),不多讀;

POSIX_FADV_SEQUENTIALP:內(nèi)核把默認(rèn)的預(yù)讀量(POSIX_FADV_NORMAL)擴(kuò)大一倍;

POSIX_FADV_WILLNEED:讀取出來(lái)的內(nèi)容會(huì)被應(yīng)用程序多次訪問(wèn)(就是應(yīng)用程序會(huì)不斷的調(diào)用read()對(duì)這些內(nèi)容不斷的讀);

POSIX_FADV_NOREUSE:讀取出來(lái)的內(nèi)容只會(huì)被應(yīng)用程序訪問(wèn)一次,訪問(wèn)一次之后就清理掉并且釋放內(nèi)存。cache服務(wù)器,比如memcache或者redis啟動(dòng)時(shí),把文件內(nèi)容加載到應(yīng)用層cache,就是這個(gè)參數(shù)存在的典型場(chǎng)景;

POSIX_FADV_DONTNEED:應(yīng)用程序后續(xù)不打算訪問(wèn)指定范圍中的文件內(nèi)容,內(nèi)核從”page cache(buffer cache)”中刪除指定范圍的文件內(nèi)容,釋放內(nèi)存。

對(duì)于POSIX_FADV_WILLNEED這種方式,linux自己有一個(gè)特定接口,原型如下:

ssize_t readahead(int fd, off64_t offset, size_t count);

linux的”buffer cache”默認(rèn)預(yù)讀128k。

實(shí)際上,OS全局控制”buffer cache”的操作接口不僅僅是上面提及的幾種,/proc/sys/vm/目錄下還有幾個(gè)參數(shù)可以從其他一些方面來(lái)控制”buffer cache”的行為,這部分內(nèi)容在之后我整理筆記之后會(huì)介紹。

IO之標(biāo)準(zhǔn)C庫(kù)buffer

在論述這個(gè)主題之前,先介紹一下標(biāo)準(zhǔn)C庫(kù)和linux系統(tǒng)調(diào)用以及windows API之間的關(guān)系。

拿寫(xiě)文件來(lái)舉個(gè)例子

linux下寫(xiě)文件用write()

windows下寫(xiě)文件用WriteFile()

這說(shuō)明不同操作系統(tǒng)實(shí)現(xiàn)同樣的系統(tǒng)功能的接口應(yīng)該是不一樣的。造成這種現(xiàn)狀是操作系統(tǒng)發(fā)展的歷史原因造成的,無(wú)法在操作系統(tǒng)的層面統(tǒng)一系統(tǒng)函數(shù)接口。同樣功能的程序在linux上寫(xiě)一套,windows上又得寫(xiě)另外一套,毫無(wú)移植性可言。如果要開(kāi)發(fā)一個(gè)既能在linux跑,又能在windows上跑的程序,開(kāi)發(fā)成本飆升!

為了解決這個(gè)移植性的問(wèn)題,標(biāo)準(zhǔn)C庫(kù)利用了封裝技術(shù),扮演了一個(gè)重要的角色,統(tǒng)一了部分基本功能接口。

標(biāo)準(zhǔn)C規(guī)定的寫(xiě)文件的函數(shù)是fwrite(),就是不管在linux還是在windows上,各自都有一個(gè)標(biāo)準(zhǔn)C庫(kù),庫(kù)函數(shù)封裝的下層細(xì)節(jié)不一樣,但是接口完全一樣,提供的功能完全一樣。

這是怎么做到的?猜一猜大致實(shí)現(xiàn)就知道了

在linux上,標(biāo)準(zhǔn)C接口fwrite()的實(shí)現(xiàn)偽代碼

size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream){ ... ... return write(stream->fd,buffer,count);}

在windows上,標(biāo)準(zhǔn)C接口fwrite()的實(shí)現(xiàn)偽代碼

size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream){#define OUT BOOL ret = false; OUT int optnum; ... ... ret = WriteFile(stream->filehandle, buffer, count, ;optnum,...); if( ret == true) return optnum; else return -1;}

內(nèi)部實(shí)現(xiàn)不一致,沒(méi)關(guān)系,接口一樣就可以,不管在linux還是windows上,寫(xiě)文件都用fwrite(),分別在各自平臺(tái)上編譯就可以了。

標(biāo)準(zhǔn)C就是這樣一個(gè)處于系統(tǒng)層面之上的應(yīng)用層標(biāo)準(zhǔn)函數(shù)庫(kù),為了統(tǒng)一各個(gè)操作系統(tǒng)上的函數(shù)接口而生。

回到我們的主題—-IO之應(yīng)用層buffer

什么是應(yīng)用層buffer?

回想一下我之前介紹的《IO之內(nèi)核buffer”buffer cache”》,既然write()能把需要寫(xiě)文件的數(shù)據(jù)推送到一個(gè)內(nèi)核buffer來(lái)偷工減料欺騙應(yīng)用層(為了加速I(mǎi)/O),說(shuō)“我已經(jīng)寫(xiě)完文件并返回了”。那應(yīng)用層的標(biāo)準(zhǔn)C庫(kù)的fwrite()按道理也可以為了加速,在真正調(diào)用write()之前,把數(shù)據(jù)放到(FILE*)stream->buffer中,等到多次調(diào)用fwrite(),直至(FILE*)stream->buffer中積攢的數(shù)據(jù)量達(dá)到(FILE*)stream->bufferlen這么多的時(shí)候,一次性的把這些數(shù)據(jù)全部送入write()接口,寫(xiě)入內(nèi)核,這是多么美妙啊。。。

實(shí)際上,標(biāo)準(zhǔn)C庫(kù)就是這么做的!

把fwrite()的linux實(shí)現(xiàn)再細(xì)致一下

過(guò)程其實(shí)仍然很粗糙,為了突出buffer的重點(diǎn),計(jì)算stream->buffer是否滿,拷貝多少,填充多少這樣的細(xì)節(jié)和主題無(wú)關(guān)的東西我略去了

size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream){
…

if( stream->buffer滿 ){
write(stream->fd,stream->buffer,stream->bufferlen);

} else{
拷貝buffer內(nèi)容至stream->buffer

}

…

return count;

//過(guò)程很粗糙,為了突出buffer的重點(diǎn),計(jì)算stream->buffer是否滿,拷貝多少,填充多少這樣的細(xì)節(jié)和主題無(wú)關(guān)的東西我略去了

}

fwrite()在windows平臺(tái)的實(shí)現(xiàn)也基本上是這樣的,也有buffer。

值得一說(shuō)的是,fread()也有一個(gè)讀cache來(lái)完成預(yù)讀。

setvbuf()和setbuf()都是控制這個(gè)標(biāo)準(zhǔn)C庫(kù)的buffer的。

還有fflush()是C庫(kù)用于flush數(shù)據(jù)的函數(shù)。

以上三個(gè)函數(shù),如果大家有興趣,可以去看看linux上對(duì)應(yīng)的man文檔。

重點(diǎn)是要知道不僅系統(tǒng)的內(nèi)核有buffer,應(yīng)用層的C庫(kù)同樣也有buffer。這些buffer的唯一作用就是為了加速應(yīng)用,不讓?xiě)?yīng)用老是卡在和磁盤(pán)交互上。

說(shuō)個(gè)題外話,實(shí)際上對(duì)于磁盤(pán)、RAID卡、盤(pán)陣這樣的外存介質(zhì)而言,他們各自在硬件上也都有一層前端的buffer,有時(shí)也叫cache,用來(lái)緩沖讀寫(xiě)加速。cache越多,價(jià)格越貴,性能越好。大型存儲(chǔ)設(shè)備一般擁有多層cache,用的是昂貴的SSD。 
需要分享的一點(diǎn)經(jīng)驗(yàn)是,不管是標(biāo)準(zhǔn)C庫(kù)的buffer也好,內(nèi)核的”buffer cache”也罷,我們終究對(duì)它們的控制力度是有限的。我們?cè)谧龇?wù)器程序的時(shí)候,如果業(yè)務(wù)上涉及太大的I/O量,需要做服務(wù)整體加速的時(shí)候,我們一般自己在業(yè)務(wù)層做一層自己的”buffer”,把業(yè)務(wù)數(shù)據(jù)buffer住,攢成以文件系統(tǒng)或者磁盤(pán)的block塊單位的大塊數(shù)據(jù),然后集中寫(xiě),然后集中寫(xiě)又有集中寫(xiě)的策略。。。 
再引申一點(diǎn)內(nèi)容,做高性能大流量的大站的架構(gòu),其中最重要幾個(gè)架構(gòu)角色之一就是cache。前端CDN、后端memcache、redis、mysql內(nèi)部cache等等,都是cache的應(yīng)用場(chǎng)景,可以說(shuō)”buffer cache”在服務(wù)器領(lǐng)域從軟件實(shí)現(xiàn)到硬件加速再到架構(gòu),真的是無(wú)處不在。

4.IO隊(duì)列和IO調(diào)度

IO調(diào)度和IO隊(duì)列

1.向塊設(shè)備寫(xiě)入數(shù)據(jù)塊或是從塊設(shè)備讀出數(shù)據(jù)塊時(shí),IO請(qǐng)求要先進(jìn)入IO隊(duì)列,等待調(diào)度。

2.這個(gè)IO隊(duì)列和調(diào)度的目標(biāo)是針對(duì)某個(gè)塊設(shè)備而言的,換句話說(shuō)就是每個(gè)塊設(shè)備都有一個(gè)獨(dú)立的IO隊(duì)列。 

3.本篇所涉及的所謂的塊設(shè)備就是iostat命令里面列出的形如sda,sdb這樣的塊設(shè)備,并不是指物理磁盤(pán)。假如一個(gè)盤(pán)被分成5個(gè)分區(qū),那么在這個(gè)主題下,5個(gè)分區(qū)代表5個(gè)塊設(shè)備,每個(gè)塊設(shè)備都有自己獨(dú)立的IO隊(duì)列。 

4.I/O 調(diào)度程序維護(hù)這些隊(duì)列,以便更有效地利用外存設(shè)備。簡(jiǎn)單來(lái)說(shuō),IO調(diào)度程序?qū)o(wú)序的IO操作變?yōu)榇笾掠行虻腎O請(qǐng)求。比如調(diào)度的時(shí)候調(diào)整幾個(gè)IO請(qǐng)求的順序,合并那些寫(xiě)盤(pán)區(qū)域相鄰的請(qǐng)求,或者按照寫(xiě)磁盤(pán)的位置排序這些請(qǐng)求,以降低磁頭在磁盤(pán)上來(lái)回seek的操作,繼而加速I(mǎi)O。 

5.每個(gè)隊(duì)列的每一次調(diào)度都會(huì)把整個(gè)隊(duì)列過(guò)一遍,類似于進(jìn)程調(diào)度的時(shí)候每次調(diào)度都要計(jì)算RUN隊(duì)列的全部進(jìn)程的優(yōu)先級(jí)。

 IO隊(duì)列深度

這個(gè)參數(shù)是iostat里面呈現(xiàn)的,字面意思顯而易見(jiàn),就是IO隊(duì)列的深度,這個(gè)參數(shù)有何意義呢? 
針對(duì)每個(gè)機(jī)械物理盤(pán),如果這個(gè)盤(pán)對(duì)應(yīng)的IO隊(duì)列深度超過(guò)3,那么基本上表示這個(gè)盤(pán)處理IO硬件請(qǐng)求有點(diǎn)吃緊,這個(gè)盤(pán)對(duì)應(yīng)的IO隊(duì)列深度怎么算呢? 
還拿上面一個(gè)盤(pán)被切成5個(gè)分區(qū)說(shuō)事兒,5個(gè)分區(qū)對(duì)應(yīng)5個(gè)塊設(shè)備,5個(gè)塊設(shè)備對(duì)應(yīng)5個(gè)IO隊(duì)列,這5個(gè)IO隊(duì)列的深度總和就是這個(gè)機(jī)械物理盤(pán)的IO隊(duì)列深度了。 
如何解決這個(gè)盤(pán)的IO請(qǐng)求吃緊呢,最簡(jiǎn)單的辦法硬件加速,把這個(gè)盤(pán)換成SSD盤(pán):) 
說(shuō)到這兒,我想提一提RAID卡。咱們使用RAID卡把幾個(gè)硬盤(pán)放在一起,讓系統(tǒng)只能看見(jiàn)一個(gè)塊設(shè)備。這個(gè)時(shí)候,假如有4個(gè)盤(pán)被放在RAID后面。那么這個(gè)RAID卡對(duì)應(yīng)的塊設(shè)備的IO隊(duì)列深度允許超過(guò)12(4個(gè)磁盤(pán),每個(gè)盤(pán)承受深度為3)。 
SSD盤(pán)可承受的IO隊(duì)列深度值很大,這個(gè)多少深度合適,我沒(méi)有注意具體觀察過(guò)。

 iostat另一個(gè)參數(shù)—-“%util”

實(shí)際生產(chǎn)系統(tǒng)上,我觀察IO設(shè)備是否吃緊,其實(shí)是看這個(gè)util的。這個(gè)值長(zhǎng)期高于60,咱們就得考慮物理磁盤(pán)IO吃不消了。 
如果是使用機(jī)械硬盤(pán)的服務(wù)器上這個(gè)值達(dá)到90以上,最簡(jiǎn)單的解決方案仍然是換SSD盤(pán),換完之后這個(gè)值會(huì)下降到20左右,非常有效。

 IO調(diào)度算法

IO調(diào)度算法存在的意義有兩個(gè):一是提高IO吞吐量,二是降低IO響應(yīng)時(shí)間。然而IO吞吐量和IO響應(yīng)時(shí)間往往是矛盾的,為了盡量平衡這兩者,IO調(diào)度器提供了多種調(diào)度算法來(lái)適應(yīng)不同的IO請(qǐng)求場(chǎng)景。 
以下幾個(gè)算法介紹是網(wǎng)上抄來(lái)的,說(shuō)的很詳細(xì),作者水平很高:) 

1、NOOP 

該算法實(shí)現(xiàn)了最簡(jiǎn)單的FIFO隊(duì)列,所有IO請(qǐng)求大致按照先來(lái)后到的順序進(jìn)行操作。之所以說(shuō)”大致”,原因是NOOP在FIFO的基礎(chǔ)上還做了相鄰IO請(qǐng)求的合并,并不是完完全全按照先進(jìn)先出的規(guī)則滿足IO請(qǐng)求。 
假設(shè)有如下的io請(qǐng)求序列: 
100,500,101,10,56,1000 
NOOP將會(huì)按照如下順序滿足: 
100(101),500,10,56,1000

2、CFQ 

CFQ算法的全寫(xiě)為Completely Fair Queuing。該算法的特點(diǎn)是按照IO請(qǐng)求的地址進(jìn)行排序,而不是按照先來(lái)后到的順序來(lái)進(jìn)行響應(yīng)。 
假設(shè)有如下的io請(qǐng)求序列: 
100,500,101,10,56,1000 
CFQ將會(huì)按照如下順序滿足: 
100,101,500,1000,10,56 
在傳統(tǒng)的SAS盤(pán)上,磁盤(pán)尋道花去了絕大多數(shù)的IO響應(yīng)時(shí)間。CFQ的出發(fā)點(diǎn)是對(duì)IO地址進(jìn)行排序,以盡量少的磁盤(pán)旋轉(zhuǎn)次數(shù)來(lái)滿足盡可能多的IO請(qǐng)求。在CFQ算法下,SAS盤(pán)的吞吐量大大提高了。但是相比于NOOP的缺點(diǎn)是,先來(lái)的IO請(qǐng)求并不一定能被滿足,可能會(huì)出現(xiàn)餓死的情況。

3、DEADLINE 

DEADLINE在CFQ的基礎(chǔ)上,解決了IO請(qǐng)求餓死的極端情況。除了CFQ本身具有的IO排序隊(duì)列之外,DEADLINE額外分別為讀IO和寫(xiě)IO提供了FIFO隊(duì)列。讀FIFO隊(duì)列的最大等待時(shí)間為500ms,寫(xiě)FIFO隊(duì)列的最大等待時(shí)間為5s。FIFO隊(duì)列內(nèi)的IO請(qǐng)求優(yōu)先級(jí)要比CFQ隊(duì)列中的高,而讀FIFO隊(duì)列的優(yōu)先級(jí)又比寫(xiě)FIFO隊(duì)列的優(yōu)先級(jí)高。優(yōu)先級(jí)可以表示如下: 
FIFO(Read) > FIFO(Write) > CFQ 
這個(gè)算法特別適合數(shù)據(jù)庫(kù)這種隨機(jī)讀寫(xiě)的場(chǎng)景。

4、ANTICIPATORY 

CFQ和DEADLINE考慮的焦點(diǎn)在于滿足離散IO請(qǐng)求上。對(duì)于連續(xù)的IO請(qǐng)求,比如順序讀,并沒(méi)有做優(yōu)化。為了滿足隨機(jī)IO和順序IO混合的場(chǎng)景,Linux還支持ANTICIPATORY調(diào)度算法。ANTICIPATORY的在DEADLINE的基礎(chǔ)上,為每個(gè)讀IO都設(shè)置了6ms的等待時(shí)間窗口。如果在這6ms內(nèi)OS收到了相鄰位置的讀IO請(qǐng)求,就可以立即滿足。

IO調(diào)度器算法的選擇,既取決于硬件特征,也取決于應(yīng)用場(chǎng)景。 
在傳統(tǒng)的SAS盤(pán)上,CFQ、DEADLINE、ANTICIPATORY都是不錯(cuò)的選擇;對(duì)于專屬的數(shù)據(jù)庫(kù)服務(wù)器,DEADLINE的吞吐量和響應(yīng)時(shí)間都表現(xiàn)良好。然而在新興的固態(tài)硬盤(pán)比如SSD、Fusion IO上,最簡(jiǎn)單的NOOP反而可能是最好的算法,因?yàn)槠渌齻€(gè)算法的優(yōu)化是基于縮短尋道時(shí)間的,而固態(tài)硬盤(pán)沒(méi)有所謂的尋道時(shí)間且IO響應(yīng)時(shí)間非常短。

 IO調(diào)度算法的查看和設(shè)置

查看和修改IO調(diào)度器的算法非常簡(jiǎn)單。假設(shè)我們要對(duì)sda進(jìn)行操作,如下所示: 
cat /sys/block/sda/queue/scheduler 
echo ‘cfq' >/sys/block/sda/queue/scheduler 
還有持久化設(shè)置,不一一列舉了。

以上就是Linux IO介紹的詳細(xì)內(nèi)容,更多關(guān)于Linux IO的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

您可能感興趣的文章:
  • Linux中的iostat命令使用教程
  • Linux IO多路復(fù)用之epoll網(wǎng)絡(luò)編程
  • Linux的Socket IO模型趣解
  • Linux 下的五種 IO 模型詳細(xì)介紹
  • 解析Linux高性能網(wǎng)絡(luò)IO和Reactor模型

標(biāo)簽:衡水 黃山 湖南 銅川 崇左 湘潭 蘭州 仙桃

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《詳細(xì)介紹Linux IO》,本文關(guān)鍵詞  ;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問(wèn)題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無(wú)關(guān)。
  • 相關(guān)文章
  • 收縮
    • 微信客服
    • 微信二維碼
    • 電話咨詢

    • 400-1100-266