內核虛擬地址佈局

本文檔介紹了 Windows 7 和 Server 2008 R2 X64 版本上內核虛擬地址空間的詳細信息。調試器擴展命令!CMKD.kvas應用這一理論來顯示 X64 虛擬地址空間並將給定地址映射到其中一個地址範圍。

內核虛擬地址佈局

X64 CPU 僅支持 CPU 上運行的軟件使用的 64 位虛擬地址中的 48 位。對於用戶模式地址,虛擬地址的高 16 位始終設置爲 0x0;對於內核模式地址,虛擬地址的高 16 位始終設置爲 0xF。這有效地將 X64 地址空間劃分爲用戶模式地址範圍 0x00000000`00000000 - 0x0000FFFF`FFFFFFFF 和內核模式地址範圍 0xFFFF0000`00000000 - 0xFFFFFFFF`FFFFFFFF。此內核虛擬地址範圍總計爲 Windows 可訪問的總內核虛擬地址空間 256TB。Windows 靜態地將虛擬地址空間劃分爲多個固定大小的 VA 區域,每個區域分配特定的用途。每個區域的開始和結束大部分是靜態的,如下表所示。

開始 結尾 尺寸 描述
FFFF0800`00000000 FFFFF67F`FFFFFFFF 238TB 未使用的系統空間
FFFFF680`00000000 FFFFF6FF`FFFFFFFF 512GB PTE空間
FFFFF700`00000000 FFFFF77F`FFFFFFFF 512GB 超空間
FFFFF780`00000000 FFFFF780`00000FFF 4K 共享系統頁面
FFFFF780`00001000 FFFFF7FF`FFFFFFFF 512GB-4K 系統緩存工作集
FFFFF800`00000000 FFFFF87F`FFFFFFFF 512GB 初始加載器映射
FFFFF880`00000000 FFFFF89F`FFFFFFFF 128GB 系統 PTE
FFFFF8a0`00000000 FFFFF8bF`FFFFFFFF 128GB 分頁池區
FFFFF900`00000000 FFFFF97F`FFFFFFFF 512GB 會議空間
FFFFF980`00000000 FFFFF70`FFFFFFFF 1TB 動態內核VA空間
FFFFFa80`00000000 *nt!MmNonPagedPoolStart-1 最大 6TB PFN數據庫
*nt!MmNonPagedPoolStart *nt!MmNonPagedPoolEnd 最大 512GB 非分頁池
FFFFFFFF`FFc00000 FFFFFFFF`FFFFFFFF 4MB HAL 和加載器映射

Windows 使用某些數據結構,例如推鎖、Ex 快速引用指針和互鎖 SList,這些數據結構需要 CPU 指令能夠對虛擬地址中位數兩倍的數字執行原子操作。因此在虛擬地址爲64位的X64 CPU上,需要128位原子CMPXCHG指令。早期版本的 X64 CPU 沒有這樣的指令,在實現上述數據結構時造成了障礙。X64 CPU 已經將虛擬地址中的可用位數限制爲 48 位,Windows 對虛擬地址進行了進一步限制,將其減少爲 44 位。因此,可以存儲此類結構的虛擬地址範圍被限制爲 2^44,即 X64 虛擬地址空間的上限 8TB,即 0xFFFFF80000000000 - 0xFFFFFFFFFFFFFFFF。因此,“未使用的系統空間”、“PTE 空間”、“超空間”和“系統緩存工作集”等超出 44 位範圍限制的虛擬地址區域無法存儲這些數據結構。此限制也擴展到用戶模式,有效地將 Windows 在用戶模式下使用的虛擬地址量限制爲 8TB,即 0x00000000`00000000 - 0x000007FF`FFFFFFFF,在內核模式下使用 8TB,即 0xFFFFF000`00000000 - 0xFFFFFFFF`FFFFFFFF。請注意,Windows 使用此範圍之外的內核虛擬地址區域,即 FFFF0800`00000000 - FFFFF7FF`FFFFFFFF,但不用於如上所述的通用分配和數據結構存儲。

X64 CPU 上的頁面大小爲 4K。CPU 使用頁表條目 (PTE) 將虛擬頁映射到物理頁,每個 PTE 映射一個 4K 頁面。在 X64 CPU 上,PTE 的大小爲 64 位(8 字節),以便能夠容納較大的物理地址或頁幀號 (PFN)。因此單個頁表頁(4K)只能容納 512 個 PTE。存儲在這樣一個頁面中的所有 PTE 映射 2MB (512*4K) 的虛擬地址空間。此外,由於頁目錄條目 (PDE) 指向頁表頁面,因此單個 PDE 條目映射 2MB 的虛擬地址空間。此 2MB 地址範圍是上表中列出的大多數虛擬地址區域內的分配粒度。這些區域中的大多數都有與其關聯的分配位圖,用於在這些區域內以 2MB 塊的倍數執行內存分配。此任務由內存管理器內部函數 MiObtainSystemVa() 執行,該函數採用枚舉類型 nt!_MI_SYSTEM_VA_TYPE 定義的值作爲從中分配內存的區域標識符。

內核虛擬地址組件

以下部分描述了內核虛擬地址空間中的每個虛擬地址區域。

未使用的系統空間

起始地址位於 nt!MmSystemRangeStart 中。此空間在 Windows 7 X64 上未使用。

PTE空間

該區域包含用於用戶模式和內核模式虛擬地址空間映射的X64 4級頁表頁面。各種類型的 X64 頁表頁都映射在此範圍內的以下指定地址處:

PTE頁面FFFFF680`00000000

偏微分方程頁 FFFFF6FB`40000000

個人防護裝備頁 FFFFF6FB`7DA00000

PXE 頁面 FFFFF6FB`7DBED000

超空間

流程工作集列表條目映射到此處。對於每個進程,EPROCESS.Vm.VmWorkingSetList 包含映射到該區域的地址 0xFFFFF700`01080000。該區域包含 MMWSL(內存管理器工作集列表)結構和一組 MMWSLE(內存管理器工作集列表條目)結構,每個結構對應進程工作集中的每一頁。請注意,函數 MiMapPageInHyperSpaceWorker() 應該將物理頁映射到 HyperSpace VA,即實際上將它們映射到系統 PTE 區域而不是此(HyperSpace)區域。

共享系統頁面

此 4K 頁面由 UVAS 和 KVAS 共享。它是在用戶模式和內核模式之間傳遞信息的快速方法。共享數據結構是nt!_KUSER_SHARED_DATA。

系統緩存工作集

系統緩存 VA 的工作集和工作集列表條目。

內核變量nt!MmSystemCacheWs指向系統緩存的工作集數據結構(即nt!_MMSUPPORT)。要顯示系統緩存的工作集列表條目,請使用命令“!wsle 1 @@(((nt!_MMSUPPORT *) @@(nt!MmSystemCacheWs))->VmWorkingSetList)”。工作集修剪器使用這些條目從系統緩存虛擬地址中修剪物理頁。

初始加載器映射

NTOSKRNL、HAL 和內核調試器 DLL(KDCOM、KD1394、KDUSB)在此區域加載。該區域還包括空閒線程的堆棧、DPC 堆棧、KPCR 和空閒線程結構。

分頁池區

最後一個分頁池地址位於變量 nt!MmPagedPoolEnd 中。分頁池的大小以 nt!MmSizeOfPagedPoolInBytes 爲單位。當使用 MiVaPagedPool 調用時,MiObtainSystemVa() 從此區域進行分配。分頁池的分配由位圖 nt!MiPagedPoolVaBitMap 控制,分配提示存儲在 nt!MiPagedPoolVaBitMapHint 中。

PFN數據庫

PFN 數據對於系統中的每個物理頁都有一個條目 (nt!MmHighestPossiblePhysicalPage +1) 以及用於容納熱插拔內存的 PFN 條目。要查找 PFN 數據庫的大小,表達式 '? poi(nt!MmNonPagedPoolStart) - poi(nt!MmPfnDatabase)' 可以在調試器中使用。要查找 PFN 數據庫中的條目總數,可以使用表達式“?(poi(nt!MmNonPagedPoolStart) - poi(nt!MmPfnDatabase))/ @@(sizeof(nt!_MMPFN))”。nt!MmPfnDatabase 定義該區域的開始。

非分頁池

非分頁池區域在 PFN 數據庫之後立即啓動。非分頁池的開始存儲在 nt!MmNonPagedPoolStart 中。當使用 MiVaNonPagedPool 調用時,MiObtainSystemVa() 從此區域進行分配。該區域中的分配由 nt!MiNonPagePoolVaBitmap 控制,分配提示存儲在 nt!MiNonPagedPoolVaBitMapHint 中。

HAL 和加載器映射

內核全局nt!MiLowHalVa包含該範圍的起始地址,即0xFFFFFFFFFFC00000。VA 範圍結束於 X64 內核虛擬地址空間的末尾(0xFFFFFFFFFFFFFFFF)。

該區域僅在系統啓動期間使用,即在函數 MmInitSystem() 內使用。初始化階段後,系統無法使用此地址範圍內的內存。

在系統初始化結束時,MmInitSystem() 調用函數 MiAddHalIoMappings(),該函數掃描此 VA 範圍並確定是否有任何 I/O 映射必須添加到系統維護的 I/O 映射列表中,如果有的話,調用 MiInsertIoSpaceMap()。對於每個 I/O 映射,MiInsertIoSpaceMap() 創建一個帶有池標記 MmIo“IO 空間映射跟蹤器”的跟蹤器條目,並將該條目添加到頭位於 nt!MmIoHeader 的雙鏈表中。每個這樣的條目代表一個已映射到 SysPTE 區域的物理內存塊。這些跟蹤器條目的前幾個字段包含一些有趣的信息,描述物理內存及其 VA 映射。MmMapIoSpace 還調用函數 MiInsertIoSpaceMap() 來跟蹤系統中的所有適配器內存映射。

結構_IO_SPACE_MAPPING_TRACKER {
    LIST_ENTRY 鏈接;
    物理地址 Pfn;
    烏龍龍頁面;
    PVOID Va;
    。。。
}

會議空間

會話數據結構、會話池和會話圖像加載在此區域中。

會話映像空間包含驅動程序映像,例如 Win32K.sys(窗口管理器)、CDD.DLL(規範顯示驅動程序)、TSDDD.dll(幀緩衝區顯示驅動程序)、DXG.sys(DirectX 圖形驅動程序)等。

對於屬於會話的任何進程,字段 EPROCESS->Session 指向該會話的 MM_SESSION_SPACE 結構。會話分頁池限制由 MM_SESSION_SPACE->PagesPoolStart 和 MM_SESSION_SPACE->PagesPoolEnd 指向。

系統 PTE

該區域包含映射視圖、MDL、適配器內存映射、驅動程序映像和內核堆棧。該區域由位圖 nt!MiSystemPteBitmap 描述,分配提示存儲在 nt!MiSystemPteBitMapHint 中。當用 MiVaSystemPtes 調用時,MiObtainSystemVa() 從此區域分配。

動態內核VA空間

該區域由系統緩存視圖、分頁特殊池和非分頁特殊池組成。nt!MiSystemAvailableVa 包含動態內核 VA 空間中可用的 2MB 區域的數量。當使用 MiVaSystemCache、MiVaSpecialPoolPaged 和 MiVaSpecialPoolNonPaged 調用時,MiObtainSystemVa() 從此區域進行分配。該區域由位圖 MiSystemVaBitmap 描述,分配提示存儲在 nt!MiSystemVaBitMapHint 中。

內核虛擬地址空間分配

內存管理器函數 MiObtainSystemVa() 用於從各個內核 VA 區域以 2MB 的倍數動態分配內存。當調用 MiObtainSystemVa() 時,調用者指定要分配的 PDE 條目數和要分配的系統 VA 類型(即 nt!_MI_SYSTEM_VA_TYPE 中的值之一)。可通過此函數分配的有效 VA 類型爲 MiVaPagedPool、MiVaNonPagedPool、MiVaSystemPtes。MiVaSystemCache、MiVaSpecialPoolPaged、MiVaSpecialPoolNonPaged。

MiObtainSystemVa()滿足來自不同內核VA區域的VA分配請求。例如,對 MiVaPagedPool 的請求定向到分頁池區域,MiVaNonPagedPool 定向到非分頁池區域,MiVaSystemPtes 定向到系統 PTE 區域,所有其他分配都定向到動態系統 VA 區域。

MiReturnSystemVa() 釋放 MiObtainSystemVa() 分配的內存。函數 MiInitializeDynamicBitmap() 初始化 MiObtainSystemVa() 和 MiReturnSystemVa() 使用的所有位圖來分配和釋放內核 VA。

動態內存分配的一個示例是 MiExpandSystemCache() 調用 MiObtainSystemVa() 來獲取系統緩存視圖。MiExpandSystemCache() 調用 MiObtainSystemVa(MiVaSystemCache) 來分配緩存管理器 VACB(虛擬地址控制塊)數據結構使用的虛擬地址空間。

系統PTE管理

MiObtainSystemVa() 從 SysPTE 區域分配的內存由 MiReservePtes() 基於分配位圖 nt!MiKernelStackPteInfo 和 nt!MiSystemPteInfo 進行子分配。

將 SysPTE 分配分爲 2 個不同類別的基本原理是爲了防止 VA 碎片。由於內核堆棧(尤其是系統和服務進程線程)是長期分配,而其他分配(例如 MDL 和映射視圖)的分配時間相對較短。

2 個結構體 nt!MiKernelStackPteInfo 和 nt!MiSystemPteInfo 的類型爲 nt!_MI_SYSTEM_PTE_TYPE。這些結構由函數 MiInitializeSystemPtes() 設置。這些結構中的位圖覆蓋了整個 128GB 的​​ SysPTE 區域。使用這些結構之一調用函數 MiReservePtes() 來從這些區域中分配 VA。隨後使用 MiReleasePtes() 釋放該內存。當 nt!MiKernelStackPteInfo 和 nt!MiSystemPteInfo 覆蓋的 VA 範圍耗盡時,通過調用 MiExpandPtes() 來擴展 VA 範圍,而 MiExpandPtes() 又調用 MiObtainSystemVa(MiVaSystemPtes)。

MmAllocateMappingAddress() 和 MmCreateKernelStack() 等函數從 nt!MiKernelStackPteInfo 分配 SysPTE VA。

MiValidateIamgePfn() 和 MiCreateImageFileMap()、MiRelocateImagePfn()、MiRelocateImageAgain() 等函數從 nt!MiSystemPteInfo 分配 SysPTE VA。

將 PFN 映射到超空間

HyperSpace VA 實際上是從內核虛擬地址空間的 Sys PTE 區域分配的。MiMapPageInHyperSpaceWorker() 將 PFN 映射到內核 VA 並返回分配給該映射的 VA。MiZeroPhysicalPage()、MiWaitForInPageComplete()、MiCopyHeaderIfResident()、MiRestoreTransitionPte()等函數調用MiMapPageInHyperSpaceWorker()來臨時獲取映射到物理地址的VA。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章