虛擬內存機制已經成為了現代操作系統所不可缺少的一部分, 不僅可以為每個程序提供獨立的地址空間保證安全性,更可以通過和磁盤的內存交換來提高內存的使用效率。虛擬內存管理作為linux 上的重要組成部分代碼非常龐大。這次並不是探明 linux 源碼級的內存映射,而是通過實例來驗證 x86-32 下的虛擬內存轉換流程。
x86-32 模式下的內存映射分為2部分, 分段和分頁。之所以使用 2 步映射更多的是歷史兼容原因。
編譯出的匯編代碼裡使用的是邏輯地址,表示形式為 [段標識符:段內偏移量], 在默認情形下可以省略段標識符,直接給出段內偏移即可。段標識符共有 6 個,分別是(CS, DS, SS, ES, FS, GS), 每個都有自己的含義。
邏輯地址經過 "分段" 會轉換為 線性地址,從我之前的文章可以看出,分段機制現在已經不實際使用了,在linux 中使用的分段模式為 “扁平模式”, 即邏輯地址和線性地址是一樣的。
從線性地址轉換為實際物理地址的過程稱為 "分頁"。分頁才是實際的虛擬地址轉換。分頁過程中使用了可迭代的頁表機制,操作系統為每個程序維護獨立的一組頁表來保證程序之間的互不干涉。
因為本文是反向驗證的流程,所以並不會仔細介紹整個映射流程,需要對虛擬內存機制有一定的了解。
本文整個流程參考了網上的另一篇文章,我會在文章末尾列出鏈接。
因為每個程序的相關資源都是獨立的,必須要保證程序的運行不能終止,且要在程序中輸出自己的相關寄存器狀態。大部分寄存器的訪問特權只支持在內核獲取,不能在用戶程序中獲取,我們要編寫相關模塊運行於內核,通過linux 的 /proc 文件系統將參數傳遞到用戶程序。
同時也要驗證我們通過手動映射流程獲得的物理地址是否就是程序內地址,要有工具能夠直接查看指定物理地址的數據。我們編寫一個字符設備來處理應用程序的請求,通過 kmap 函數將指定物理地址頁臨時映射獲取其數據。
最終需要程序為 4 個,如下所示。
編譯文件,加載 sys_reg.ko, phy_mem.ko 模塊
運行後可以得到以下輸出:
可以看到變量 a, 這就是我們要尋找物理地址的變量,其數據和地址都以及輸出了。
通過 CR0.PG ,可以看出系統已經打開了分頁機制。 變量 a 定在了數據段, 通過 ds 段寄存器的值可以看出使用的是 GDT ,entry 是 15. GDTR 的基址是 0xF7386000, 注意這裡是線性地址, linux 內核的地址映射偏移量是 0xC0000000, 然後獲得 使用的 GDT entry地址如下。
0xF7386000 - 0xC0000000 + 15 * 8 = 0x37386078
通過獲得的 GDT entry值和 gdt entry 格式,可以知道該分段的參數:
可以看出段基址是 0x00000000, G和limit決定了該段是 4G 大小。所以從邏輯地址獲得變量 a 的線性地址如下:
0x0804A044 + 0x00000000 = 0x0804A044;
因為 CR4.PAE = 1,說明系統開啟了 PAE(物理地址擴展). PAE 模式下的分頁有如下 2 種。
寄存器及 entry 格式如下:
此時 CR3 中的基址就是 PDPTE 的基址 0x1EF49000, 變量 a 線性地址的bit 31-30 代表了 PDPTE 的序號。 我們可以算出 使用的 PDPTE 地址:
0x1EF49000 + 0 * 8 = 0x1EF49000
可以看到 page directory 的基址為 0x1ec9f000, 使用線性地址中的 bits 29~ 21 來確定偏移為 0x40, 所以使用的 PDE 地址為
0x1EC9F000 + 0x40 * 8 = 0x1EC9F200
PDE 為 0x0000000020A36067, bit7 = 0,說明指向的是 page table, page table 地址為0x20A36000, 使用線性地址的 bits 20~12 作為偏移為 0x4A, 使用的 PTE 地址為
0x20A36000 + 0x4A * 8 = 0x20A36250
PTE 為 0x000000000B628067, 得到了最終的 4K page frame 基址為 0x0B628000, 使用線性地址的 bits 11~0 作為偏移為 0x44, 我們計算出的 變量 a 的物理地址為
0x0B628000 + 0x44 = 0x0B628044
我們看到了數據 0x013579BB, 說明我們正確找到了 a 的物理地址,反向驗證了 linux 在 x86-32 模式下開啟了 PAE 後的線性地址映射。
原文:http://www.cnblogs.com/ziyang3721/p/4269132.html