内核虚拟地址布局

本文档介绍了 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。

posted @ 2024-02-27 17:56  CharyGao  阅读(81)  评论(0编辑  收藏  举报