概述
本文主要闡述內核(linux-3.12)的文件系統預讀設計和實現。
所謂預讀,是指文件系統為應用程序一次讀出比預期更多的文件內容并緩存在page cache中,這樣下一次讀請求到來時部分頁面直接從page cache讀取即可。當然,這個細節對應用程序透明,應用程序可能的感覺就是下次讀的速度會更快,當然這是好事。文中我們會通過設置幾個情境(順序讀、隨機讀、多線程交織讀)來分析預讀的邏輯。
情境1:順序讀
//?事例代碼 { ????... ????f???=?open("file",?....); ????ret?=?read(f,?buf,?4096); ????ret?=?read(f,?buf,?2?*?4096); ????ret?=?read(f,?buf,?4?*?4096); ????... }
該場景非常簡單:打開文件,共進行三次讀(且是順序讀),那讓我們看看操作系統是如何對文件進行預讀的。
Read 1
第一次進入內核讀處理流程時,在page cache中查找該offset對應的頁面是否緩存,因為首次讀,緩存未命中,觸發一次同步預讀:
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: ????????//?如果沒有找到,啟動同步預讀 ????????page?=?find_get_page(mapping,?index); ????????if?(!page)?{ ????????????page_cache_sync_readahead( ????????????????mapping,?ra,?filp, ?????????????????index,? ?????????????????last_index?-?index ?????????????);
?
?
該同步預讀邏輯最終進入如下預讀邏輯:
?
?
//?注意:?這里offset?和req_size其實是頁面數量 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); ????//?第一次讀文件,直接初始化預讀窗口即可 ????if?(!offset) ????????goto?initial_readahead; ????????...... initial_readahead: ????ra->start?=?offset; ????ra->size?=?get_init_ra_size(req_size,?max); ????//?ra->size?一定是>=?req_size的,這個由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); }
讀邏輯會為該文件初始化一個預讀窗口:
(ra->start, ra->size, ra->async_size)
本例中的預讀窗口為(0,4,3),初始化該預讀窗口后調用ra_submit提交本次讀請求。形成的讀窗口如下圖所示:
圖中看到,應用程序申請訪問PAGE 0,內核一共讀出PAGE0 ~PAGE3,后三個屬于預讀頁面,而且PAGE_1被標記為PAGE_READAHEAD,當觸發到該頁面讀時,操作系統會進行一次異步預讀,這在后面我們會仔細描述。
等這四個頁面被讀出時,第一次讀的頁面已經在pagecache中,應用程序從該page中拷貝出內容即可。
Read 2
接下來應用程序進行第二次讀:offset=4096, size=8192。內核將其轉化為以page為單位計量,offset=1,size=2。即讀上面的PAGE1和PAGE2。
感謝第一次的預讀,PAGE1和PAGE2目前已經在內存中了,但由于PAGE1被打上了PAGE_AHEAD標記,讀到該頁面時會觸發一次異步預讀:
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,包含預讀量)?-? ?????*??上次預讀大小(ra->async_size)) ?????*?2.?offset?==?(ra->start?+?ra->size)??? ?????*/ ????if?((offset?==?(ra->start?+?ra->size?-?ra->async_size)?||? ????????offset?==?(ra->start?+?ra->size)))?{ ????????//?設置本次讀的offset,以page為單位 ????????ra->start?+=?ra->size;? ????????ra->size?=?get_next_ra_size(ra,?max); ????????ra->async_size?=?ra->size; ????????goto?readit; ????}
經歷了第一次預讀,文件的預讀窗口狀態為
(ra->start,ra->size, ra->async_size)=(0, 4, 3)
本次的請求為(offset,size)=(1, 2),上面代碼的判斷條件成立,因此我們會向前推進預讀窗口,此時預讀窗口變為
(ra->start,ra->size, ra->async_size) = (4, 8, 8)
由于本次是異步預讀,應用程序可以不等預讀完成即可返回,只要后臺慢慢讀頁面即可。本次預讀窗口的起始以及大小以及預讀大小可根據前一次的預讀窗口計算得到,又由于本次是異步預讀,因此,預讀大小就是本次讀的頁面數量,因此將本次預讀的第一個頁面(PAGE 4)添加預讀標記。
由于上面的兩次順序讀,截至目前,該文件在操作系統中的page cache狀態如下:
Read 3
接下來應用程序進行第三次讀,順序讀,范圍是[page3, page6],上面的預讀其實已經將這些頁面讀入page cache了,但是由于page4被打上了?PAGE_READAHEAD?標記,因此,訪問到該頁面時會觸發一次異步預讀,預讀的過程與上面的步驟一致,當前預讀窗口為?(4,8,8)?,滿足順序性訪問特征,根據特定算法計算本次預讀大小,更新預讀窗口為?(12,16,16)?,新的預讀窗口如下:
對該情境簡單總結下,由于三次的順序讀加上內核的預讀行為,文件的page cache中的狀態當前如下圖所示:
審核編輯:黃飛
?
評論
查看更多