(十四)Linux記憶體管理之page fault處理【轉】
轉自:https://www.cnblogs.com/LoyenWang/p/12116570.html
背景
Read the fucking source code!
--By 魯迅A picture is worth a thousand words.
--By 高爾基
說明:
- Kernel版本:4.14
- ARM64處理器,Contex-A53,雙核
- 使用工具:Source Insight 3.5, Visio
1. 概述
上篇文章分析到malloc/mmap
函式中,核心實現只是在程序的地址空間建立好了vma
區域,並沒有實際的虛擬地址到實體地址的對映操作。這部分就是在Page Fault
異常錯誤處理中實現的。
Linux核心中的Page Fault
異常處理很複雜,涉及的細節也很多,malloc/mmap
的實體記憶體對映只是它的一個子集功能,下圖大概涵蓋了出現Page Fault
的情況:
下邊就開始來啃啃硬骨頭吧。
2. Arm64處理
Page Fault
的異常處理,依賴於體系結構,因此有必要來介紹一下Arm64
的處理。
程式碼主要參考:arch/arm64/kernel/entry.S
。
Arm64在取指令或者訪問資料時,需要把虛擬地址轉換成實體地址,這個過程需要進行幾種檢查,在不滿足的情況下都能造成異常:
- 地址的合法性,比如以39有效位地址為例,核心地址的高25位為全1,使用者程序地址的高25位為全0;
- 地址的許可權檢查,這裡邊的許可權位都位於頁表條目中;
從上圖中可以看到,最後都會調到do_mem_abort
函式,這個函式比較簡單,直接看程式碼,位於arch/arm64/mm/fault.c
:
/*
* Dispatch a data abort to the relevant handler.
*/
asmlinkage void __exception do_mem_abort(unsigned long addr, unsigned int esr,
struct pt_regs *regs)
{
const struct fault_info *inf = esr_to_fault_info(esr);
struct siginfo info;
if (!inf->fn(addr, esr, regs))
return;
pr_alert("Unhandled fault: %s (0x%08x) at 0x%016lx\n",
inf->name, esr, addr);
mem_abort_decode(esr);
info.si_signo = inf->sig;
info.si_errno = 0;
info.si_code = inf->code;
info.si_addr = (void __user *)addr;
arm64_notify_die("", regs, &info, esr);
}
該函式中關鍵的處理:根據傳進來的esr
獲取fault_info
資訊,從而去呼叫函式。struct fault_info
用於錯誤狀態下對應的處理方法,而核心中也定義了全域性結構fault_info
,存放了所有的情況。
主要的錯誤狀態和處理函式對應如下:
static const struct fault_info fault_info[] = {
{ do_bad, SIGBUS, 0, "ttbr address size fault" },
{ do_bad, SIGBUS, 0, "level 1 address size fault" },
{ do_bad, SIGBUS, 0, "level 2 address size fault" },
{ do_bad, SIGBUS, 0, "level 3 address size fault" },
{ do_translation_fault, SIGSEGV, SEGV_MAPERR, "level 0 translation fault" },
{ do_translation_fault, SIGSEGV, SEGV_MAPERR, "level 1 translation fault" },
{ do_translation_fault, SIGSEGV, SEGV_MAPERR, "level 2 translation fault" },
{ do_translation_fault, SIGSEGV, SEGV_MAPERR, "level 3 translation fault" },
{ do_bad, SIGBUS, 0, "unknown 8" },
{ do_page_fault, SIGSEGV, SEGV_ACCERR, "level 1 access flag fault" },
{ do_page_fault, SIGSEGV, SEGV_ACCERR, "level 2 access flag fault" },
{ do_page_fault, SIGSEGV, SEGV_ACCERR, "level 3 access flag fault" },
{ do_bad, SIGBUS, 0, "unknown 12" },
{ do_page_fault, SIGSEGV, SEGV_ACCERR, "level 1 permission fault" },
{ do_page_fault, SIGSEGV, SEGV_ACCERR, "level 2 permission fault" },
{ do_page_fault, SIGSEGV, SEGV_ACCERR, "level 3 permission fault" },
...
};
從程式碼中可以看出:
- 出現0/1/2/3級頁錶轉換錯誤時,會呼叫
do_translation_fault
,實際中do_translation_fault
最終也會呼叫到do_page_fault
; - 出現1/2/3級頁表訪問許可權的時候,會呼叫
do_page_fault
; - 其他的錯誤則呼叫
do_bad
,其中未列出來的部分還包括do_sea
等操作函式;
do_translation_fault
do_page_fault
do_page_fault
函式為頁錯誤異常處理的核心函式,與體系結構相關,上圖中的handle_mm_fault
函式為通用函式,也就是不管哪種處理器結構,最終都會呼叫到該函式。
3. handle_mm_fault
handle_mm_fault
用於處理使用者空間的頁錯誤異常:
- 程序在使用者模式下訪問使用者虛擬地址,觸發頁錯誤異常;
- 程序在核心模式下訪問使用者虛擬地址,觸發頁錯誤異常;
從do_page_fault
函式的流程圖中也能看出來,當觸發異常的虛擬地址屬於某個vma
,並且擁有觸發頁錯誤異常的許可權時,會呼叫到handle_mm_fault
函式,而handle_mm_fault
函式的主要邏輯是通過__handle_mm_fault
來實現的。
流程如下圖:
3.1 do_fault
do_fault
函式用於處理檔案頁異常,包括以下三種情況:
- 讀檔案頁錯誤;
- 寫私有檔案頁錯誤;
- 寫共享檔案頁錯誤;
3.2 do_anonymous_page
匿名頁的缺頁異常處理呼叫本函式,在以下情況下會觸發:
- malloc/mmap分配了程序地址空間區域,但是沒有進行對映處理,在首次訪問時觸發;
- 使用者棧不夠的情況下,進行棧區的擴大處理;
3.3 do_swap_page
如果訪問Swap頁面
出錯(頁面不在記憶體中),則從Swap cache
或Swap檔案
中讀取該頁面。
由於在4.14核心
版本中,do_swap_page
呼叫的很多函式都是空函式,無法進一步的瞭解,大體的流程如下圖:
3.4 do_wp_page
do_wp_page
函式用於處理寫時複製(copy on write
),會在以下兩種情況處理:
- 建立子程序時,父子程序會以只讀方式共享私有的匿名頁和檔案頁,當試圖寫的時候,觸發頁錯誤異常,從而複製物理頁,並建立對映;
- 程序建立私有檔案對映,讀訪問後觸發異常,將檔案頁讀入到
page cache
中,並以只讀模式建立對映,之後發生寫訪問後,觸發COW
;
關鍵的複製工作是由wp_page_copy
完成的: