概述
本文主要闡述內(nèi)核(linux-3.12)的文件系統(tǒng)預(yù)讀設(shè)計(jì)和實(shí)現(xiàn)。
所謂預(yù)讀,是指文件系統(tǒng)為應(yīng)用程序一次讀出比預(yù)期更多的文件內(nèi)容并緩存在page cache中,這樣下一次讀請(qǐng)求到來(lái)時(shí)部分頁(yè)面直接從page cache讀取即可。當(dāng)然,這個(gè)細(xì)節(jié)對(duì)應(yīng)用程序透明,應(yīng)用程序可能的感覺(jué)就是下次讀的速度會(huì)更快,當(dāng)然這是好事。文中我們會(huì)通過(guò)設(shè)置幾個(gè)情境(順序讀、隨機(jī)讀、多線(xiàn)程交織讀)來(lái)分析預(yù)讀的邏輯。
情境1:順序讀
//?事例代碼 { ????... ????f???=?open("file",?....); ????ret?=?read(f,?buf,?4096); ????ret?=?read(f,?buf,?2?*?4096); ????ret?=?read(f,?buf,?4?*?4096); ????... }
該場(chǎng)景非常簡(jiǎn)單:打開(kāi)文件,共進(jìn)行三次讀(且是順序讀),那讓我們看看操作系統(tǒng)是如何對(duì)文件進(jìn)行預(yù)讀的。
Read 1
第一次進(jìn)入內(nèi)核讀處理流程時(shí),在page cache中查找該offset對(duì)應(yīng)的頁(yè)面是否緩存,因?yàn)槭状巫x,緩存未命中,觸發(fā)一次同步預(yù)讀:
static?void?do_generic_file_read(struct? ????file?*filp,?loff_t?*ppos, ????read_descriptor_t?*desc,? ????read_actor_t?actor)? { ????...... ????for?(;;)?{ ????????...... ????????cond_resched(); find_page: ????????//?如果沒(méi)有找到,啟動(dòng)同步預(yù)讀 ????????page?=?find_get_page(mapping,?index); ????????if?(!page)?{ ????????????page_cache_sync_readahead( ????????????????mapping,?ra,?filp, ?????????????????index,? ?????????????????last_index?-?index ?????????????);
?
?
該同步預(yù)讀邏輯最終進(jìn)入如下預(yù)讀邏輯:
?
?
//?注意:?這里offset?和req_size其實(shí)是頁(yè)面數(shù)量 static?unsigned?long?ondemand_readahead( ????struct?address_space?*mapping,? ????struct?file_ra_state?*ra,? ????struct?file?*filp,? ????bool?hit_readahead_marker, ????pgoff_t?offset, ????unsigned?long?req_size) { ????unsigned?long?max?=? ????????max_sane_readahead(ra->ra_pages); ????//?第一次讀文件,直接初始化預(yù)讀窗口即可 ????if?(!offset) ????????goto?initial_readahead; ????????...... initial_readahead: ????ra->start?=?offset; ????ra->size?=?get_init_ra_size(req_size,?max); ????//?ra->size?一定是>=?req_size的,這個(gè)由get_init_ra_size保證 ????//?如果req_size?>=?max,那么ra->async_size?=?ra_size ????ra->async_size?=?ra->size?>?req_size???ra->size?-?req_size?:?ra->size; readit: ????/* ?????*?Will?this?read?hit?the?readahead?marker?made?by?itself? ?????*?If?so,?trigger?the?readahead?marker?hit?now,?and?merge ?????*?the?resulted?next?readahead?window?into?the?current?one. ?????*/ ????if?(offset?==?ra->start?&& ????????ra->size?==?ra->async_size)?{ ????????ra->async_size?=?get_next_ra_size(ra,?max); ????????ra->size?+=?ra->async_size; ????} ????return?ra_submit(ra,?mapping,?filp); }
讀邏輯會(huì)為該文件初始化一個(gè)預(yù)讀窗口:
(ra->start, ra->size, ra->async_size)
本例中的預(yù)讀窗口為(0,4,3),初始化該預(yù)讀窗口后調(diào)用ra_submit提交本次讀請(qǐng)求。形成的讀窗口如下圖所示:
圖中看到,應(yīng)用程序申請(qǐng)?jiān)L問(wèn)PAGE 0,內(nèi)核一共讀出PAGE0 ~PAGE3,后三個(gè)屬于預(yù)讀頁(yè)面,而且PAGE_1被標(biāo)記為PAGE_READAHEAD,當(dāng)觸發(fā)到該頁(yè)面讀時(shí),操作系統(tǒng)會(huì)進(jìn)行一次異步預(yù)讀,這在后面我們會(huì)仔細(xì)描述。
等這四個(gè)頁(yè)面被讀出時(shí),第一次讀的頁(yè)面已經(jīng)在pagecache中,應(yīng)用程序從該page中拷貝出內(nèi)容即可。
Read 2
接下來(lái)應(yīng)用程序進(jìn)行第二次讀:offset=4096, size=8192。內(nèi)核將其轉(zhuǎn)化為以page為單位計(jì)量,offset=1,size=2。即讀上面的PAGE1和PAGE2。
感謝第一次的預(yù)讀,PAGE1和PAGE2目前已經(jīng)在內(nèi)存中了,但由于PAGE1被打上了PAGE_AHEAD標(biāo)記,讀到該頁(yè)面時(shí)會(huì)觸發(fā)一次異步預(yù)讀:
find_page: ????????...... ????????page?=?find_get_page(mapping,?index); ????????if?(!page)?{ ????????????page_cache_sync_readahead( ????????????????mapping,?ra,?filp, ????????????????index, ????????????????last_index?-?index); ????????????page?=?find_get_page(mapping,?index); ????????????if?(unlikely(page?==?NULL)) ????????????????goto?no_cached_page; ????????} ????????if?(PageReadahead(page))?{ ????????????page_cache_async_readahead( ????????????????mapping,?ra,?filp,? ?????????????????page,index,? ?????????????????last_index?-?index); ????????} static?unsigned?long ondemand_readahead( ????struct?address_space?*mapping,? ????struct?file_ra_state?*ra, ????struct?file?*filp, ????bool?hit_readahead_marker, ????pgoff_t?offset, ????unsigned?long?req_size) { ????unsigned?long?max?=? ????max_sane_readahead(ra->ra_pages); ????........ ????/*?如果: ?????*?1.?順序讀(本次讀偏移為上次讀偏移?(ra->start)?+?讀大小(ra->size,包含預(yù)讀量)?-? ?????*??上次預(yù)讀大小(ra->async_size)) ?????*?2.?offset?==?(ra->start?+?ra->size)??? ?????*/ ????if?((offset?==?(ra->start?+?ra->size?-?ra->async_size)?||? ????????offset?==?(ra->start?+?ra->size)))?{ ????????//?設(shè)置本次讀的offset,以page為單位 ????????ra->start?+=?ra->size;? ????????ra->size?=?get_next_ra_size(ra,?max); ????????ra->async_size?=?ra->size; ????????goto?readit; ????}
經(jīng)歷了第一次預(yù)讀,文件的預(yù)讀窗口狀態(tài)為
(ra->start,ra->size, ra->async_size)=(0, 4, 3)
本次的請(qǐng)求為(offset,size)=(1, 2),上面代碼的判斷條件成立,因此我們會(huì)向前推進(jìn)預(yù)讀窗口,此時(shí)預(yù)讀窗口變?yōu)?/p>
(ra->start,ra->size, ra->async_size) = (4, 8, 8)
由于本次是異步預(yù)讀,應(yīng)用程序可以不等預(yù)讀完成即可返回,只要后臺(tái)慢慢讀頁(yè)面即可。本次預(yù)讀窗口的起始以及大小以及預(yù)讀大小可根據(jù)前一次的預(yù)讀窗口計(jì)算得到,又由于本次是異步預(yù)讀,因此,預(yù)讀大小就是本次讀的頁(yè)面數(shù)量,因此將本次預(yù)讀的第一個(gè)頁(yè)面(PAGE 4)添加預(yù)讀標(biāo)記。
由于上面的兩次順序讀,截至目前,該文件在操作系統(tǒng)中的page cache狀態(tài)如下:
Read 3
接下來(lái)應(yīng)用程序進(jìn)行第三次讀,順序讀,范圍是[page3, page6],上面的預(yù)讀其實(shí)已經(jīng)將這些頁(yè)面讀入page cache了,但是由于page4被打上了?PAGE_READAHEAD?標(biāo)記,因此,訪(fǎng)問(wèn)到該頁(yè)面時(shí)會(huì)觸發(fā)一次異步預(yù)讀,預(yù)讀的過(guò)程與上面的步驟一致,當(dāng)前預(yù)讀窗口為?(4,8,8)?,滿(mǎn)足順序性訪(fǎng)問(wèn)特征,根據(jù)特定算法計(jì)算本次預(yù)讀大小,更新預(yù)讀窗口為?(12,16,16)?,新的預(yù)讀窗口如下:
對(duì)該情境簡(jiǎn)單總結(jié)下,由于三次的順序讀加上內(nèi)核的預(yù)讀行為,文件的page cache中的狀態(tài)當(dāng)前如下圖所示:
審核編輯:黃飛
?
評(píng)論
查看更多