背景
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完成的:


轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/144536.html
標籤:Linux


