Linux文件讀寫機(jī)制及優(yōu)化方式

本文只討論Linux下文件的讀寫機(jī)制羡疗,不涉及不同讀取方式如read,fread,cin等的對(duì)比,這些讀取方式本質(zhì)上都是調(diào)用系統(tǒng)api read汹族,只是做了不同封裝锚国。以下所有測(cè)試均使用open, read, write這一套系統(tǒng)api

緩存

緩存是用來減少高速設(shè)備訪問低速設(shè)備所需平均時(shí)間的組件,文件讀寫涉及到計(jì)算機(jī)內(nèi)存和磁盤俊卤,內(nèi)存操作速度遠(yuǎn)遠(yuǎn)大于磁盤嫩挤,如果每次調(diào)用read,write都去直接操作磁盤,一方面速度會(huì)被限制消恍,一方面也會(huì)降低磁盤使用壽命岂昭,因此不管是對(duì)磁盤的讀操作還是寫操作,操作系統(tǒng)都會(huì)將數(shù)據(jù)緩存起來

Page Cache

頁緩存(Page Cache)是位于內(nèi)存和文件之間的緩沖區(qū)狠怨,它實(shí)際上也是一塊內(nèi)存區(qū)域约啊,所有的文件IO(包括網(wǎng)絡(luò)文件)都是直接和頁緩存交互,操作系統(tǒng)通過一系列的數(shù)據(jù)結(jié)構(gòu)佣赖,比如inode, address_space, struct page恰矩,實(shí)現(xiàn)將一個(gè)文件映射到頁的級(jí)別,這些具體數(shù)據(jù)結(jié)構(gòu)及之間的關(guān)系我們暫且不討論憎蛤,只需知道頁緩存的存在以及它在文件IO中扮演著重要角色外傅,很大一部分程度上纪吮,文件讀寫的優(yōu)化就是對(duì)頁緩存使用的優(yōu)化

Dirty Page

頁緩存對(duì)應(yīng)文件中的一塊區(qū)域,如果頁緩存和對(duì)應(yīng)的文件區(qū)域內(nèi)容不一致萎胰,則該頁緩存叫做臟頁(Dirty Page)碾盟。對(duì)頁緩存進(jìn)行修改或者新建頁緩存,只要沒有刷磁盤技竟,都會(huì)產(chǎn)生臟頁

查看頁緩存大小

linux上有兩種方式查看頁緩存大小冰肴,一種是free命令

 $ free
             total       used       free     shared    buffers     cached
Mem:      20470840    1973416   18497424        164     270208    1202864
-/+ buffers/cache:     500344   19970496
Swap:            0          0          0

cached那一列就是頁緩存大小,單位Byte

另一種是直接查看/proc/meminfo榔组,這里我們只關(guān)注兩個(gè)字段

Cached:          1202872 kB
Dirty:                52 kB

Cached是頁緩存大小嚼沿,Dirty是臟頁大小

臟頁回寫參數(shù)

Linux有一些參數(shù)可以改變操作系統(tǒng)對(duì)臟頁的回寫行為

 $ sysctl -a 2>/dev/null | grep dirty
vm.dirty_background_ratio = 10
vm.dirty_background_bytes = 0
vm.dirty_ratio = 20
vm.dirty_bytes = 0
vm.dirty_writeback_centisecs = 500
vm.dirty_expire_centisecs = 3000

vm.dirty_background_ratio是內(nèi)存可以填充臟頁的百分比,當(dāng)臟頁總大小達(dá)到這個(gè)比例后瓷患,系統(tǒng)后臺(tái)進(jìn)程就會(huì)開始將臟頁刷磁盤(vm.dirty_background_bytes類似骡尽,只不過是通過字節(jié)數(shù)來設(shè)置)

vm.dirty_ratio是絕對(duì)的臟數(shù)據(jù)限制,內(nèi)存里的臟數(shù)據(jù)百分比不能超過這個(gè)值擅编。如果臟數(shù)據(jù)超過這個(gè)數(shù)量攀细,新的IO請(qǐng)求將會(huì)被阻擋,直到臟數(shù)據(jù)被寫進(jìn)磁盤

vm.dirty_writeback_centisecs指定多長(zhǎng)時(shí)間做一次臟數(shù)據(jù)寫回操作爱态,單位為百分之一秒

vm.dirty_expire_centisecs指定臟數(shù)據(jù)能存活的時(shí)間谭贪,單位為百分之一秒,比如這里設(shè)置為30秒锦担,在操作系統(tǒng)進(jìn)行寫回操作時(shí)俭识,如果臟數(shù)據(jù)在內(nèi)存中超過30秒時(shí),就會(huì)被寫回磁盤

這些參數(shù)可以通過sudo sysctl -w vm.dirty_background_ratio=5這樣的命令來修改洞渔,需要root權(quán)限套媚,也可以在root用戶下執(zhí)行echo 5 > /proc/sys/vm/dirty_background_ratio來修改

文件讀寫流程

在有了頁緩存和臟頁的概念后,我們?cè)賮砜次募淖x寫流程

讀文件

  • 用戶發(fā)起read操作
  • 操作系統(tǒng)查找頁緩存
    • 若未命中磁椒,則產(chǎn)生缺頁異常堤瘤,然后創(chuàng)建頁緩存,并從磁盤讀取相應(yīng)頁填充頁緩存
    • 若命中浆熔,則直接從頁緩存返回要讀取的內(nèi)容
  • 用戶read調(diào)用完成

寫文件

  • 用戶發(fā)起write操作
  • 操作系統(tǒng)查找頁緩存
    • 若未命中本辐,則產(chǎn)生缺頁異常,然后創(chuàng)建頁緩存医增,將用戶傳入的內(nèi)容寫入頁緩存
    • 若命中慎皱,則直接將用戶傳入的內(nèi)容寫入頁緩存
  • 用戶write調(diào)用完成
  • 頁被修改后成為臟頁,操作系統(tǒng)有兩種機(jī)制將臟頁寫回磁盤
    • 用戶手動(dòng)調(diào)用fsync()
    • 由pdflush進(jìn)程定時(shí)將臟頁寫回磁盤

頁緩存和磁盤文件是有對(duì)應(yīng)關(guān)系的叶骨,這種關(guān)系由操作系統(tǒng)維護(hù)茫多,對(duì)頁緩存的讀寫操作是在內(nèi)核態(tài)完成,對(duì)用戶來說是透明的

文件讀寫的優(yōu)化思路

不同的優(yōu)化方案適應(yīng)于不同的使用場(chǎng)景邓萨,比如文件大小地梨,讀寫頻次等,這里我們不考慮修改系統(tǒng)參數(shù)的方案缔恳,修改系統(tǒng)參數(shù)總是有得有失宝剖,需要選擇一個(gè)平衡點(diǎn),這和業(yè)務(wù)相關(guān)度太高歉甚,比如是否要求數(shù)據(jù)的強(qiáng)一致性万细,是否容忍數(shù)據(jù)丟失等等。優(yōu)化的思路有以下兩個(gè)考慮點(diǎn)

  1. 最大化利用頁緩存
  2. 減少系統(tǒng)api調(diào)用次數(shù)

第一點(diǎn)很容易理解纸泄,盡量讓每次IO操作都命中頁緩存赖钞,這比操作磁盤會(huì)快很多,第二點(diǎn)提到的系統(tǒng)api主要是read和write聘裁,由于系統(tǒng)調(diào)用會(huì)從用戶態(tài)進(jìn)入內(nèi)核態(tài)雪营,并且有些還伴隨這內(nèi)存數(shù)據(jù)的拷貝,因此在有些場(chǎng)景下減少系統(tǒng)調(diào)用也會(huì)提高性能

readahead

readahead是一種非阻塞的系統(tǒng)調(diào)用衡便,它會(huì)觸發(fā)操作系統(tǒng)將文件內(nèi)容預(yù)讀到頁緩存中献起,并且立馬返回,函數(shù)原型如下

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

在通常情況下镣陕,調(diào)用readahead后立馬調(diào)用read并不會(huì)提高讀取速度谴餐,我們通常在批量讀取或在讀取之前一段時(shí)間調(diào)用readahead,假設(shè)如下場(chǎng)景呆抑,我們需要連續(xù)讀取1000個(gè)1M的文件岂嗓,有如下兩個(gè)方案,偽代碼如下

直接調(diào)用read函數(shù)

char* buf = (char*)malloc(10*1024*1024);
for (int i = 0; i < 1000; ++i)
{
    int fd = open_file();
    int size = stat_file_size();
    read(fd, buf, size);
    // do something with buf
    close(fd);
}

先批量調(diào)用readahead再調(diào)用read

int* fds = (int*)malloc(sizeof(int)*1000);
int* fd_size = (int*)malloc(sizeof(int)*1000);
for (int i = 0; i < 1000; ++i)
{
    int fd = open_file();
    int size = stat_file_size();
    readahead(fd, 0, size);
    fds[i] = fd;
    fd_size[i] = size;
}
char* buf = (char*)malloc(10*1024*1024);
for (int i = 0; i < 1000; ++i)
{
    read(fds[i], buf, fd_size[i]);
    // do something with buf
    close(fds[i]);
}

感興趣的可以寫代碼實(shí)際測(cè)試一下鹊碍,需要注意的是在測(cè)試前必須先回寫臟頁和清空頁緩存厌殉,執(zhí)行如下命令

sync && sudo sysctl -w vm.drop_caches=3

可通過查看/proc/meminfo中的Cached及Dirty項(xiàng)確認(rèn)是否生效

通過測(cè)試發(fā)現(xiàn),第二種方法比第一種讀取速度大約提高10%-20%侈咕,這種場(chǎng)景下是批量執(zhí)行readahead后立馬執(zhí)行read年枕,優(yōu)化空間有限,如果有一種場(chǎng)景可以在read之前一段時(shí)間調(diào)用readahead乎完,那將大大提高read本身的讀取速度

這種方案實(shí)際上是利用了操作系統(tǒng)的頁緩存熏兄,即提前觸發(fā)操作系統(tǒng)將文件讀取到頁緩存,并且操作系統(tǒng)對(duì)缺頁處理树姨、緩存命中摩桶、緩存淘汰都由一套完善的機(jī)制,雖然用戶也可以針對(duì)自己的數(shù)據(jù)做緩存管理帽揪,但和直接使用頁緩存比并沒有多大差別硝清,而且會(huì)增加維護(hù)代價(jià)

mmap

mmap是一種內(nèi)存映射文件的方法,即將一個(gè)文件或者其它對(duì)象映射到進(jìn)程的地址空間转晰,實(shí)現(xiàn)文件磁盤地址和進(jìn)程虛擬地址空間中一段虛擬地址的一一對(duì)映關(guān)系芦拿,函數(shù)原型如下

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

實(shí)現(xiàn)這樣的映射關(guān)系后士飒,進(jìn)程就可以采用指針的方式讀寫操作這一段內(nèi)存,而系統(tǒng)會(huì)自動(dòng)回寫臟頁面到對(duì)應(yīng)的文件磁盤上蔗崎,即完成了對(duì)文件的操作而不必再調(diào)用read,write等系統(tǒng)調(diào)用函數(shù)酵幕。如下圖所示

mmap除了可以減少read,write等系統(tǒng)調(diào)用以外,還可以減少內(nèi)存的拷貝次數(shù)缓苛,比如在read調(diào)用時(shí)芳撒,一個(gè)完整的流程是操作系統(tǒng)讀磁盤文件到頁緩存,再?gòu)捻摼彺鎸?shù)據(jù)拷貝到read傳遞的buffer里未桥,而如果使用mmap之后笔刹,操作系統(tǒng)只需要將磁盤讀到頁緩存,然后用戶就可以直接通過指針的方式操作mmap映射的內(nèi)存冬耿,減少了從內(nèi)核態(tài)到用戶態(tài)的數(shù)據(jù)拷貝

mmap適合于對(duì)同一塊區(qū)域頻繁讀寫的情況舌菜,比如一個(gè)64M的文件存儲(chǔ)了一些索引信息,我們需要頻繁修改并持久化到磁盤亦镶,這樣可以將文件通過mmap映射到用戶虛擬內(nèi)存酷师,然后通過指針的方式修改內(nèi)存區(qū)域,由操作系統(tǒng)自動(dòng)將修改的部分刷回磁盤染乌,也可以自己調(diào)用msync手動(dòng)刷磁盤

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末山孔,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子荷憋,更是在濱河造成了極大的恐慌台颠,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件勒庄,死亡現(xiàn)場(chǎng)離奇詭異串前,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)实蔽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門荡碾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人局装,你說我怎么就攤上這事坛吁。” “怎么了铐尚?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵拨脉,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我宣增,道長(zhǎng)玫膀,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任爹脾,我火速辦了婚禮帖旨,結(jié)果婚禮上箕昭,老公的妹妹穿的比我還像新娘。我一直安慰自己解阅,他們只是感情好落竹,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著瓮钥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪烹吵。 梳的紋絲不亂的頭發(fā)上碉熄,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音肋拔,去河邊找鬼锈津。 笑死,一個(gè)胖子當(dāng)著我的面吹牛凉蜂,可吹牛的內(nèi)容都是我干的琼梆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼窿吩,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼茎杂!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起纫雁,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤煌往,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后轧邪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刽脖,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年忌愚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了曲管。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡硕糊,死狀恐怖院水,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情简十,我是刑警寧澤衙耕,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站勺远,受9級(jí)特大地震影響橙喘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜胶逢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一厅瞎、第九天 我趴在偏房一處隱蔽的房頂上張望饰潜。 院中可真熱鬧,春花似錦和簸、人聲如沸彭雾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽薯酝。三九已至,卻和暖如春爽柒,著一層夾襖步出監(jiān)牢的瞬間吴菠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工浩村, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留做葵,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓心墅,卻偏偏與公主長(zhǎng)得像酿矢,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子怎燥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容