(十四)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 高爾基

說明:

  1. Kernel版本:4.14
  2. ARM64處理器,Contex-A53,雙核
  3. 使用工具: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在取指令或者訪問數據時,需要把虛擬地址轉換成物理地址,這個過程需要進行幾種檢查,在不滿足的情況下都能造成異常:

  1. 地址的合法性,比如以39有效位地址爲例,內核地址的高25位爲全1,用戶進程地址的高25位爲全0;
  2. 地址的權限檢查,這裏邊的權限位都位於頁表條目中;

從上圖中可以看到,最後都會調到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函數用於處理文件頁異常,包括以下三種情況:

  1. 讀文件頁錯誤;
  2. 寫私有文件頁錯誤;
  3. 寫共享文件頁錯誤;

3.2 do_anonymous_page

匿名頁的缺頁異常處理調用本函數,在以下情況下會觸發:

  1. malloc/mmap分配了進程地址空間區域,但是沒有進行映射處理,在首次訪問時觸發;
  2. 用戶棧不夠的情況下,進行棧區的擴大處理;

3.3 do_swap_page

如果訪問Swap頁面出錯(頁面不在內存中),則從Swap cacheSwap文件中讀取該頁面。
由於在4.14內核版本中,do_swap_page調用的很多函數都是空函數,無法進一步的瞭解,大體的流程如下圖:

3.4 do_wp_page

do_wp_page函數用於處理寫時複製(copy on write),會在以下兩種情況處理:

  1. 創建子進程時,父子進程會以只讀方式共享私有的匿名頁和文件頁,當試圖寫的時候,觸發頁錯誤異常,從而複製物理頁,並創建映射;
  2. 進程創建私有文件映射,讀訪問後觸發異常,將文件頁讀入到page cache中,並以只讀模式創建映射,之後發生寫訪問後,觸發COW

關鍵的複製工作是由wp_page_copy完成的:

作者:LoyenWang
出處:https://www.cnblogs.com/LoyenWang/
公衆號:LoyenWang
版權:本文版權歸作者和博客園共有
轉載:歡迎轉載,但未經作者同意,必須保留此段聲明;必須在文章中給出原文連接;否則必究法律責任
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章