頁故障異常處理流程,非法訪問頁故障處理,有效用戶頁故障處理,1.線性檔案映射頁的故障處理,2.非線性檔案映射頁的故障處理,3.匿名頁的故障處理,4.寫時複製頁的故障處理,有效核心頁故障處理,
頁故障異常處理流程
當頁故障異常發生時,處理器必須離開當前的工作,轉去處理異常。頁故障異常處理程式需要知道下列信息:
(1)異常發生時處理器所在的地址空間或特權級。
(2)引起頁故障異常的虛擬地址,可能是下一條指令的地址也可能是當前指令欲訪問的數據地址。
(3)引起頁故障異常的原因,如虛擬頁不在記憶體(缺頁)、虛擬頁不許寫等。由於引起頁故障異常的原因大多是缺頁,因而頁故障異常又稱為缺頁異常。
處理器為頁故障異常的處理提供了上述信息。當頁故障異常發生時,處理器會自動在當前進程的系統堆疊上壓入EFLAGS、CS、EIP和錯誤代碼,用於指示異常產生的環境和異常產生的原因,與此同時,處理器還在CR2暫存器中存入了引起頁故障異常的虛擬地址。
頁故障異常必須由作業系統核心處理。系統初始化程式已在IDT表中為頁故障異常創建了中斷門,入口程式是page_fault,真正的處理程式是do_page_fault。每當頁故障異常發生時,處理器都會離開當前的工作,轉去執行do_page_fault。
非法訪問頁故障處理
頁故障異常發生時,下列情況屬於非法記憶體訪問:
(1)故障地址對應的頁目錄或頁表項中的保留位被置1。正常情況下,頁目錄或頁表項中的保留位(如頁目錄項中的第6位、頁表項中的第7位)應保持為0,這些位為1表明當前進程的頁表已被破壞。
(2)故障地址在用戶空間、處理器在執行中斷處理程式。由於中斷的異步性,無法預知被中斷的當前進程,因而在中斷處理程式(包括硬處理和軟處理程式)中不應訪問進程的用戶虛擬地址。
(3)故障地址在用戶空間、處理器在執行核心執行緒。核心執行緒永遠運行在核心中,沒有自己的虛擬地址空間,它對用戶虛擬地址的訪問肯定是非法的。
(4)故障地址在核心空間、處理器在執行用戶程式。按照Linux的約定,用戶程式的特權級都是3,核心程式和數據的特權級都是0,用戶程式直接調用核心程式或訪問核心數據都是非法的。
(5)故障地址在用戶空間且是無效的。進程的有效虛擬地址都位於它的虛擬記憶體區域中,在所有區域之外的用戶虛擬地址都是無效的。
(6)進程對故障地址的訪問方式不符契約定。進程對各虛擬地址的訪問約定記錄在它的虛擬記憶體區域中,試圖向不可寫區域中寫數據的操作是非法的,試圖訪問不可讀、寫、執行區域的任何操作都是非法的。
對非法記憶體訪問的處理方法如下:
(1)如果頁故障發生時處理器正在用戶空間執行程式,則向當前進程傳送信號SIGSEGV,而後返回。在返回用戶態之前,進程會處理該信號。如果進程註冊了SIGSEGV信號的處理程式,該程式將先被執行;如果進程未註冊SIGSEGV信號的處理程式,核心將把它殺死。
(2)如果頁故障發生時處理器正在核心空間執行程式,則搜尋異常列表(在節_ex_table中),看有沒有為該異常預定處理程式(見4.2.2節):
①如有,則重置棧頂的ip,而後返回。返回操作會跳轉到預定的處理程式,由該程式處理此次的頁故障異常。
②如無,則顯示與故障相關的現場信息,如指令地址、故障地址、頁表項、堆疊等,而後終止進程。
有效用戶頁故障處理
如果引起頁故障異常的是用戶態的有效虛擬地址,且程式對它的訪問方式是合法的,則應該設法處理該異常,使虛擬地址生效,以便程式能夠正常執行下去。為此需要對該類合法頁故障異常做進一步地分析,以便明確原因,確定處理方法。
確定處理方法的依據是頁目錄/頁表項、錯誤代碼和虛擬記憶體區域(尤其是其操作集中的fault操作)。根據頁故障地址addr可以算出它所在的虛擬頁,查當前進程的頁目錄、頁表可以找到該虛擬頁對應的頁表項。如果在查找過程中發現頁表不存在,則要臨時為其創建一個新的頁表。事實上,進程的頁表都是動態創建的,新建頁表中的所有頁表項都是0。
1.線性檔案映射頁的故障處理
在為進程載入可執行程式或映射數據檔案時,已建立了虛擬記憶體區域與檔案區間之間的映射關係,但未將檔案的內容讀入物理記憶體,或者說還未建立起進程虛擬頁與物理記憶體頁之間的映射關係(頁表項為0)。當進程訪問這些虛擬頁時,自然會產生頁故障異常。對這類頁故障的處理思路十分明確:找到虛擬頁對應的檔案頁,將其讀入物理記憶體,而後修改頁表項,將虛擬頁映射到新讀入的檔案頁即可。此後,當進程再次訪問故障地址addr時,處理器便能順利地將其轉換成正確的物理地址,訪問到正確的檔案內容。
2.非線性檔案映射頁的故障處理
正常情況下,虛擬記憶體區域與檔案區間之間的映射關係是線性的,區域中的第i個虛擬頁對應區間中的第i個檔案頁。但從2.5版之後,Linux允許建立非線性映射關係(通過系統調用remap_file_pages),即允許將區域中的某些虛擬頁映射到檔案的其它位置(區間內的其它頁或區間外的某頁)。為了記錄這些非線性虛擬頁的映射位置,Linux對這些頁的頁表項進行了特殊設定:
(1)頁表項的P位被清0;
(2)頁表項的D位被置1(當P為0時,D是未用的,可將其復用為F標誌);
(3)檔案頁的頁號被記錄在頁表項的其餘各位中。
與線性檔案映射頁相似,當非線性檔案映射頁出現頁故障時,說明與之對應的檔案頁還未讀入記憶體,應該將其讀入並修改頁表項。
3.匿名頁的故障處理
匿名頁屬於匿名區域(如進程的用戶堆疊、堆等),沒有對應的映射檔案,其內容是動態生成的。匿名頁出現頁故障說明還未為其分配物理頁,處理的方法是為出現故障的匿名頁指派一個內容全部為0的物理頁。
4.寫時複製頁的故障處理
寫時複製(Copy on Write)頁是應該在進程創建時複製而沒有複製的頁,它們在物理記憶體中,包含它們的虛擬記憶體區域允許寫但頁表項表示它們不允許寫。寫時複製頁出現頁故障說明進程對其實施了寫操作,應該為其創建一個副本。
有效核心頁故障處理
如果引起頁故障異常的虛擬地址位於核心空間,而且程式對它的訪問是合法的,那么訪問者肯定是核心本身。正常情況下,核心程式對核心空間的訪問不應該出現頁故障,除非引起故障的地址位於VMALLOC_START和VMALLOC_END之間。
VMALLOC_START和VMALLOC_END之間的地址是為邏輯記憶體管理器預留的,大小為128MB。與kmap空間不同,在系統初始化時,Linux並未為預留的邏輯記憶體管理空間建立頁表。因而,初始情況下,在所有進程的頁目錄中,這塊地址空間所對應的頁目錄項都是空的。當核心申請邏輯頁塊時,邏輯記憶體管理器會根據需要動態地建立頁表,但僅會將新頁表插入到第0號進程的頁目錄(init_mm.pgd)中,並未修改其餘進程的頁目錄。當核心在這些進程的上下文中運行且訪問到邏輯頁塊時,自然會引起頁故障異常。
有效核心頁的故障處理方法十分簡單,同步一下當前進程的頁目錄即可。同步的方法是:找到故障地址在第0號進程中的頁目錄項,將其拷貝到當前進程的頁目錄中。
另外,當核心提升某個頁面的訪問許可權(如將唯讀頁改為可讀寫頁)時,由於未及時刷新其它處理器的TLB,也有可能出現頁故障異常。這類頁故障也是有效的,只要刷新處理器的TLB即可解決。