Linux 內核 File cache 機制(中篇)
本篇則接上篇,主要介紹 Linux mmap 讀文件流程涉及缺頁中斷。
一、mmap 及缺頁中斷讀文件流程分析
上面篇幅,“什麼是 Filecache” 篇幅介紹了文件緩存框架,走讀了 read() 流程中涉及文件緩存的操作流程。對於用戶來說,讀過程一般是同步讀的過程(當然也有異步讀的接口,其思想跟文件緩存異步預讀機制類同,不再闡述),其執行完接口調用後所需數據就已經在內存中了,而操作的時候就通過 write() 等接口進行讀寫。
除了以上的接口,文件緩存的讀取和使用是否還有其他方法?答案是肯定的,例如 mmap() 等接口可以實現將文件映射到用戶地址空間,進程可以像訪問普通內存一樣對文件進行訪問。mmap 接口從內核的歸屬上是內存接口,Linux 系統對於用戶內存分配總是苛刻的,當用戶分配內存時,內核優先只給虛擬內存(MAP_POPULATE 等模式本文不作討論),只有使用時才分配物理內存,通過 mmap 接口進行文件映射屬於內核內存的管理框架內,自然也符合這個管理思想。
那麼系統怎麼及時地給用戶分配物理內存呢?這裏就引出缺頁中斷的概念:
-
當用戶訪問虛擬地址時發現地址沒有映射到具體的物理內存空間,則觸發中斷進入內核態,進行物理內存填充,本文只關注文件頁面的缺頁中斷。
-
對於文件緩存的缺頁中斷,另外一種觸發的場景也非常常見:文件緩存因爲內存緊張被系統回收了,因爲回收的過程會進行虛擬內存解映射,此時用戶再次訪問到之前分配的虛擬地址時,同樣也需要進入缺頁中斷流程進行數據的填充。關於文件緩存的回收,放到 “Filecache 回收流程分析” 章節,讀者有興趣可以進行查閱。
本篇幅通過分析 mmap/munmap 調用執行流程和文件緩存的缺頁中斷流程,瞭解該路徑下文件緩存的讀取過程。
1. mmap 函數生命週期
mmap 系統調用用於將用戶空間的一端內存區域映射到內核空間,根據傳遞參數的不同,其可以實現共享內存、單獨分配匿名內存以及映射文件頁面等。本文主要闡述映射文件緩存,普通文件被映射到進程地址空間後,進程可以像訪問普通內存一樣對文件進行訪問,而不需要調用 read()/write() 接口。
-
addr:是否指定映射的內存區域的起始地址,NULL 表示由內核指定;
-
length:要映射的內存區域大小;
-
prot:內存保護權限的標誌;
-
flags:執行映射對象的類型,例如是否共享、是否鎖定映射內存、是否匿名映射(不與文件關聯)等;
-
fd:文件描述符,內核通過該句柄來尋找具體的文件,如果要映射文件,必須正確地傳遞該參數。另外調用返回後,fd 可以關閉,但是映射依然有效;
-
offset:表示映射對象(文件)的內容的位置;
對於具體的參數使用可以通過 man mmap 進行查閱。對於文件映射,其 mmap 的生命週期如下:
mmap 系統調用最終調用到內核的 ksys_mmap_pgoff 接口,文件映射則通過文件描述符得到 file 結構體,然後最終調用到 do_mmap 函數,MAP_POPULATE 本文不闡述。
do_mmap 執行可以簡單歸納如下:
-
get_unmmaped_area:獲取虛擬地址;
-
mmap_region
-
vm_area_alloc:爲虛擬地址申請 vma 抽象結構體;
-
初始化 vma 結構體,包括其實虛擬地址、vm_flags(權限)、vm_file 賦值文件結構體指針等和文件映射相關的成員;
-
call_mmap:調用文件系統的 mmap 函數集;
get_unmmaped_area 用於在進程虛擬地址中找到一塊合適的空閒地址,內核中是通過紅黑樹進行管理和查找,對於虛擬內存的管理框架,如果讀者有興趣,可以查閱文章《進程內存管理初探》。(虛擬內存框架是相對抽象的子系統,包括 vma、vm_struct 等抽象結構體以及操作函數集合、地址管理邏輯等,如果讀者有興趣,可以在評論區留言出專篇)
下面走讀一下 mmap_region 函數,一探 mmap 的核心邏輯,函數定義於 mm/mmap.c 中:
函數的邏輯主幹很清晰,就不在細述。最後調用 call_mmap 函數通過 mapping->a_ops->mmap 回調文件系統操作函數集,以 ext4 文件系統爲例,定義在 fs/ext4/file.c 中:
至此 mmap 函數調用就結束了,可能讀者會很疑惑:mmap 構建了一個 vma 結構體後什麼都沒做?其實非 MAP_POPULATE 分支的 mmap 函數就是構建 vma 結構體,該結構抽象管理一段虛擬地址。如本章節篇頭提及,Linux 內核對用戶內存分配是很苛刻的,從內核的角度:因爲物理內存是公共資源且比較緊缺,用戶申請分配物理內存可能不用,避免共用資源浪費,所以就只先給虛擬內存,因爲虛擬內存是屬於進程內部獨立的,隻影響進程內部,對系統的影響僅僅是增加了 vma 等管理結構體的內存佔用開銷。
2. 文件緩存的缺頁中斷處理流程
用戶經過 mmap 接口後,得到一段虛擬內存,其沒有物理載體,即沒有承載任何用戶需要的數據,那麼當用戶訪問該虛擬內存後,怎麼獲取數據?答案就是 Linux 的缺頁中斷機制,簡單概括:
-
用戶訪問虛擬地址,MMU 發現虛擬地址沒有映射到物理內存;
-
觸發中斷進行缺頁處理流程;
-
根據 vma 結構體成員判斷是哪種缺頁的類型,進行物理頁面分配以及數據填充;
Linux 根據內存用途分成多種頁面,缺頁中斷也根據頁面類型會走不同的分支。本文只討論文件缺頁中斷,其他類型可根據相同思路走讀代碼。以 arm 處理器,其處理流程如下:
其核心處理階段包括:
-
平臺的缺頁中斷;
-
內存核心框架,銜接到文件系統的 fault 回調函數;
-
文件緩存管理框架進行文件頁面預讀;
-
通用塊設備層、IO 調度層和設備驅動層進行數據讀取;
本文走讀前三部分,最後一部分放在 IO 和文件系統篇幅,後續更新。
(1)缺頁中斷
缺頁中斷跟正常的中斷處理流程相似,硬件觸發經過彙編代碼後跳轉到 do_DataAbort 函數處理,定義在 arch/arm/mm/fault.c,arm 平臺有兩個寄存器用於記錄中斷的信息:
-
FSR:失效狀態寄存器
-
FAR:失效地址寄存器
內核使用 fsr_info 結構體抽象異常處理操作集合:
以二級頁表架構爲例子,定義於 arch/arm/mm/fsr-2level.h 中:
通過 do_page_fault() 調用到__do_page_fault 函數:通過當前進程的 mm_struct 和異常的地址得到 vma 結構體,然後調用 handle_mm_fault 函數。
handle_mm_fault 函數定義在 mm/memory.c 中,此時已經進入內存管理框架內,該函數主要分配頁表,然後通過 handle_pte_fault 函數,針對不同的頁面類型,進行分支處理:
處理流程也很清晰,根據不同的情況走不同的分支,do_fault 函數用於處理文件頁面的缺頁中斷,當然文件頁面的缺頁中斷也有不同的原因:
本文走讀 do_read_fault 流程,其他分支讀者可以自行分析,其邏輯大同小異。
__do_fault 函數通過 vma->vm_ops->fault() 回調到具體文件系統的操作函數集合,前文已知 vm_ops 函數操作集是在具體的文件系統賦值的,以 ext4 文件系統爲例,ext4_filemap_fault 通過 filemap_fault 函數進行文件頁面讀取。
(2)filemap_fault
filemap_fault 是文件缺頁中斷的核心處理函數,定義於 mm/memory.c 中,其邏輯作用:
-
判斷頁面是否在文件緩存中,如存在則判斷是否需要異步預讀;
-
如不存在則進行同步預讀;
其主要的邏輯還是集中在預讀窗口的構建。
函數主幹簡化後如上,邏輯很清晰就不再細述。關注一下此時的同步預讀和異步預讀策略跟 read 分析的預讀流程,是否存在不同。
- do_async_mmap_readahead 函數
首先是異步預讀 do_async_mmap_readahead 函數:
@1 中 PG_Readahead 標誌,表示該頁面上次是提前異步讀取的第一個頁面,在 “read 發起讀文件流程分析:4.1 章節” 的__do_page_cache_readahead 中有分析。當前如果訪問到這個文件表示,上次提前預讀的頁面被命中,那麼可能是順序讀,此時需繼續進行異步預讀,page_cache_async_readahead 已經在 “read 發起讀文件流程分析” 章節分析,這裏不再累述。
- do_sync_mmap_readahead 函數
ra_submit 通過__do_page_cache_readahead 函數進行預讀,該函數在 “read 發起讀文件流程分析” 章節都已經分析。
3. munmap 函數生命週期
munmap 系統調用邏輯非常簡單:
-
內核根據虛擬地址找到 vma 結構體;
-
unmap_region 進行解映射,並釋放頁表
-
remove_vma_list 將 vma 從用戶進程的 mm_struct 中剝離
這裏需要注意的是,調用 munmap 接口後,是否文件緩存就會一定馬上被回收?答案是否定的:
-
首先文件頁面可能被其他進程映射使用;
-
其次 Linux 系統對待文件緩存還是比較 “博愛” 的,除非用戶主動調用 drop_cache 等接口將文件緩存從內存刷出,不然內核會盡量將文件緩存保存在內存中,避免下次用戶使用時需要再次從磁盤中讀取。既然是“儘量”,那就得有限度,該限度就是內核空閒內存是否低於 LOW_WATERMARK 水位線?如果低於該值,就會進行內存回收,其中就包括文件緩存的回收。
二、File cache 回收流程分析
Linux File cache 的回收涉及的知識點很多,包括內存管理 LRU 機制、workingset 機制、內存回收 shrink 機制和髒頁管理機制等,此部分內容放置在《Linux 內核 File cache 機制(下篇)》中,後續發佈,有興趣的可以關注。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/Tf-J7vFrUsPW08Iy7hcsTA