x64内核内存空间结构

0x00 前言

本文主要是讨论Windows 7 x64下的内核虚拟地址空间的结构,可以利用WiinDBG调试的扩展命令"!CMKD.kvas"来显示x64下的内核虚拟地址空间的整体布局。了解内核的地址布局在某些情况下是很有的,比如说在研究New Blue Pill的源码和虚拟化的时候。

0x01 基本结构

X64的CPU的地址为64位,但实际上只支持48位的虚拟地址空间供软件使用。虚拟地址的高16位在用户模式下总是被设置为0000,而在内核模式下全置为FFFF。

因此用户模式的地址空间范围为0x00000000~00000000——0x0000FFFF~ffffffff,内核模式的地址空间范围为0xFFFF0000~00000000——0xFFFFffff~ffffffff,所以对操作系统可见的内核虚拟地址空间的大小为256TB。Windows操作系统将整个内核地址空间划分为若干个有特定用途的大小固定的虚拟地址空间。下表是关于Windows对于虚拟地址空间具体的划分:


起始地址


结束地址


内存大小


用途


FFFF0800`00000000


FFFFF67F`FFFFFFFF


238TB


未使用


FFFFF680`00000000


FFFFF6FF`FFFFFFFF


512GB


PTE内存空间


FFFFF700`00000000


FFFFF77F`FFFFFFFF


512GB


Hyper内存空间


FFFFF780`00000000


FFFFF780`00000FFF


4KB


系统共享空间


FFFFF780`00001000


FFFFF7FF`FFFFFFFF


512GB-4K


系统cache工作集


FFFFF800`00000000


FFFFF87F`FFFFFFFF


512GB


初始化映射区


FFFFF880`00000000


FFFFF89F`FFFFFFFF


128GB


系统PTE区域


FFFFF8a0`00000000


FFFFF8bF`FFFFFFFF


128GB


分页池区域


FFFFF900`00000000


FFFFF97F`FFFFFFFF


512GB


会话空间


FFFFF980`00000000


FFFFFa70`FFFFFFFF


1TB


内核动态虚拟空间


FFFFFa80`00000000


*nt!MmNonPagedPoolStart-1


6TB Max


PFN 数据


*nt!MmNonPagedPoolStart


*nt!MmNonPagedPoolEnd


512GB Max


不分页内存池


FFFFFFFF`FFc00000


FFFFFFFF`FFFFFFFF


4MB


HAL和加载器映射区

Windows操作系统用了一些特定的数据结构,比如说Push Locks,Ex Fast Referenced Pointers和Interlocked Slists,对这些数据结构的操作都需要CPU对同一个虚拟地址的数字执行两遍原子操作。因此虽然64位处理器的虚拟地址是64位,却必须要有128位长的CMPXCHG指令。但是在早期的64位处理器中是没有这样的指令的,在使用上述的数据结构的时候就会引发故障。64位CPU已经将虚拟地址有效位限制为48位,而Windows操作系统则进一步将虚拟地址有效位限制为44位,实际上可以用来存储上述数据结构的虚拟地址空间大小就是2^44,即64位虚拟地址空间的高8TB的空间,也就是0xFFFFF80000000000 - 0xFFFFFFFFFFFFFFFF。例如之前的”未使用空间”,“PTE内存空间”,“ Hyper内存空间”和“系统cache工作集”都超出了44位虚拟地址的限制,都无法存储这些特定的数据结构。这些限制也会影响到用户空间,将用户空间可用虚拟内存大小限制到了8TB,即0x00000000`00000000 - 0x000007FF`FFFFFFFF,内核空间可用虚拟虚拟内存大小也为8TB,即0xFFFFF000`00000000 - 0xFFFFFFFF`FFFFFFFF。需要说明的一点就是,由Windows操作系统使用的不在FFFF0800`00000000 - FFFFF7FF`FFFFFFFF范围内的虚拟内存,也并不是都会分配和保存上述的特定数据结构。

64位处理器物理页大小是4KB,CPU使用PTEs(Page Table Entry页表项)来完成从虚拟地址到物理地址的映射,因此每个PTE映射4K大小的物理页。64位处理器下的PTE占64位,也就是8个字节为了兼容更大的物理地址和PFNs(Page Frame Number,页帧号)。因此单个页表的物理页可以容纳512个PTE,所有的PTE可以映射2MB(512*4KB)的虚拟地址。同样的,因为PDEs(Page Directory Entries,页目录项)指向页表的物理页,所以单个的PDE可以映射2MB的虚拟地址空间。

0x02 内核虚拟空间组成

下面说明内核地址空间的具体组成部分,及其作用。

未使用的 (Unused System Space)

由nt!MmSystemRangeStart开始,这部分在Windows 7 X64下并未使用

PTE空间  (PTE Space)

这部分包含了x64下用户空间和内核空间的虚拟地址映射的4级页表。X64下不同页表页的映射范围如下:

PTE Pages FFFFF680`00000000

PDE Pages FFFFF6FB`40000000

PPE Pages FFFFF6FB`7DA00000

PXE Pages FFFFF6FB`7DBED000

Hyper空间 (HyperSpace)

映射进程的工作集。对每一个进程的EPROCESS.Vm.VmWorkingSetList 中包含的地址

0xFFFFF700`01080000就会映射到这片空间。这片空间包括MMWSL(Memory Manager Working Set List)结构和MMWSLE(Memory Manager Working Set List Entry)的数组结构,包括进程工作集的每个物理页。

需要注意的是虽然函数MiMapPageInHyperSpaceWorker()支持映射物理页到Hyper空间的虚拟地址,但实际上是将物理页映射到了PTE空间,而不是真正的Hyper空间。

共享系统页 (Shared System Page)

这4K大小的页是由用户空间和内核空间共享的,主要是用来在用户层和内核层之前快速的传递信息,共享数据的数据结构就是nt!_KUSER_SHARED_DATA。

系统cache工作集(System Cache Working Set)

包含系统cache的虚拟地址的工作集(Working Set)和工作集链表项(Working Set List Entries)。

内核变量nt!MmSystemCacheWs指向系统cache工作集的数据结构(即nt!_MMSUPPORT)。想要显示系统cache的工作集链表项可以使用WinDBG命令

"!wsle 1 @@(((nt!_MMSUPPORT *) @@(nt!MmSystemCacheWs))->VmWorkingSetList)"。而这些项会被用来修剪(trim)系统cache的虚拟内存的物理页。

初始化加载映射区 (Initial Loader Mappings)

Ntoskrnl,HAL和内核调试DLL(KDCOM,KD1394,KDUSB)都会被加载到这片区域。除此之外,这片空间包含idle线程的线程栈,DPC的栈,KPCR和idle线程的数据结构。

分页池区域 (Paged Pool Area)

分页池的结束地址保存在变量nt!MmPagedPoolEnd中。而分页池的大小保存在变量nt!MmSizeOfPagedPoolInBytes。当调用MiVaPagePool()时,MiObtainSystemVa()函数就会从这片区域分配内存,分页池的内存分配方式由变量nt!MiPagedPoolVaBitMap按位(bit)决定。

PFN数据库(PFN Database)

对于系统的每个物理页在PFN中都有对应的项(nt!MmHighestPossiblePhysicalPage+1)。可以在WinDBG中输入命令‘? poi(nt!MmNonPagedPoolStart) - poi(nt!MmPfnDatabase)‘来获得”PFN Database”的大小。也可以使用命令

‘?(poi(nt!MmNonPagedPoolStart) - poi(nt!MmPfnDatabase))/ @@(sizeof(nt!_MMPFN))‘来获得PFN中项的总数。而这片区域的起始地址保存在nt!MmPfnDatabase中。

不分页内存池(Non-Paged Pool)

不分页内存池的区域直接跟在PFN Database后面。不分页内存池的起始地址保存在nt!MmNonPagedPoolStart中。当调用MiVaNonPagedPool()时,MiObtainSystemVa()就会在这片区域分配内存。内存的分配方式由变量nt!MiNonPagePoolVaBitmap按位决定。

硬件抽象层和加载映射区(HAL and Loader Mappings)

内核全局变量nt!MiLowHalVa包含这片区域的起始地址,即0xFFFFFFFFFFC00000。结束地址和X64内核虚拟地址空间结束地址一致,为0xFFFFFFFFFFFFFFFF。这片区域仅用于系统启动时,也就是在MmInitSystem()函数中,这片区域中的内存在启动初始化完毕以后就不可以再被使用了。

在系统初始化函数MmInitSystem()的结尾处调用函数MiAddHalIoMappings()来扫描这片虚拟地址空间来判断是否有I/O映射到了这片空间,如果有,将会调用函数MiInsertIoSpaceMap()加入到由系统维护的I/O队列中。而对于每一个I/O映射区域,MiInsertIoSpaceMap()都会用池标签”Io space mapping trackers“创建一个tracker项,然后将其加入到头为nt!MmIoHeader的双向链表中,其中的每一项都表示的虚拟内存块都已经映射了物理地址,而tracker项中的一些字段也包含关于物理内存和虚拟地址映射的信息。

struct _IO_SPACE_MAPPING_TRACKER {
    LIST_ENTRY Link;
    PHYSICAL_ADDRESS  Pfn;
    ULONGLONG  Pages;
    PVOID Va;
    . . .
}

会话空间  (Session Space)

关于会话(session)的数据结构,会话池和会话映像都会加载到这片区域。

会话映像包括驱动映像比如Win32k.sys(Windows Manager),CDD.dll(Canonical Display Driver),TSDDD.dll(Frame Buffer Display Driver),DXG.sys(DirectX Graphics Driver)等等。

对于任意一个进程,其EPROCESS->Session指向的MM_SESSION_SPACE就是其所属的会话结构,而会话池的范围由MM_SESSION_SPACE->PagesPoolStart 和MM_SESSION_SPACE->PagesPoolEnd指定。

系统PTE (Sys PTEs)

这片区域包括映射的View,MDL,adapter内存,驱动程序的映像和内核栈。当使用MiVaSystemPtes()时,就会调用函数MiObtainSystemVa()在这片区域分配内存。

内核动态虚拟空间(Dynamic Kernel VA Space)

这片区域由系统cache的view,特定的分页内存池和特定不分页内存池组成。nt!MiSystemAvailableVa保存动态内核虚拟空间可用的2MB的区域数量。

调用MiObtainSystemVa()的参数是MiVaSystemCache,MiVaSpecialPoolPaged或MiVaSpecialPoolNonpaged时,将会从这片区域分配内存。

0x03  内核虚拟内存的分配

内存管理器使用函数MiObtainSystemVa()来动态的从不同的内核虚拟地址空间分配不同的2MB的内存。当调用MiObtainSystemVa()函数时,调用者需要指定分配的PDE项的总数和系统虚拟内存的分配类型(nt!_MI_SYSTEM_VA_TYPE),而对于此函数有效的类型为MiVaPagedPool,MiVaNonPagedPool,MiVaSystemPtes,MiVaSystemCache,MiVaSpecialPoolPaged,MiVaSpecialPoolNonPaged 。

MiObtainSystemVa()可以满足不同的内核虚拟空间的分配请求。例如,MiVaPagedPool要求分配分页池区域(Paged Pool region),MiVaNonPagedPool要求分配不分页池区域(non-paged pool region),MiVaSystemPtes则分配系统PTE区域(System PTE region),而其他类型的分配请求则是直接分配系统动态虚拟内存(Dynamic System VA region)。内存的释放则是由函数MiReturnSystemVa()完成。

一个动态内存分配的例子就是MiExpandSystemCache()调用MiObtainSystemVa()来获取系统cache的view。MiExpandSystemCache()调用MiObtainSystemVa(MiVaSystemCache)来申请存放Cache Manager VACB(Virtual Address Control Block)数据结构的虚拟内存

0x04 系统PTE管理 (SysPTE Management)

由MiObtainSystemVa()从SysPTE区域分配的内存会由MiReservePtes()按照分配要求(nt!MiKernelStackPteInfo和nt!MiSystemPteInfo)进一步的划分为两类,其目的就是为了防止虚拟内存的碎片化。因为内核栈内存(尤其是system和服务进程的线程)生命期是很长的,而其他的类型分配,例如MDL的生命周期相对短很多。

两种结构类型nt!MiKernelStackPteInfo和nt!MiSystempteInfo都是属于nt!_MI_SYSTEM_PTE_TYPE,而这些结构体都是由函数MiInitializeSystemPtes()产生,他们的每一位包含的信息可以影响SysPTE区域的128GB的空间。而函数MiReservePtes()在被调用时需要这些结构体其中一个作为参数来申请SysPTE区域以外的内存,申请的内存由MiReleasePtes()进行释放。

当虚拟内存地址被nt!MiKernelStackPteInfo和nt!MiSystemPteInfo覆盖时,则已经耗尽了通过调用MiExpandPtes()(实际调用MiObtainSystemVa(MiVaSystemPtes))扩展的内存区域。

函数MmAllocateMappingAddress()和MmCreateKernelStack()都是申请nt!MiKernelStackPteInfo类型的内存,而函数MiVaildateLamgePfn()和MiCreateImageFileMap(),MiRelocateImagePfn(),MiRelocateImageAgain()申请nt!MiSystemPteInfo类型的内存。

时间: 2024-08-09 02:20:19

x64内核内存空间结构的相关文章

clients(PV操作共享内核内存进行输入输出分屏) - server(进程间通信)模型实现

1.拓扑结构 2.PV操作共享内核内存进行输入输出分屏 (1) 1 int semop(int semid,struct sembuf *sops,size_t nsops): 功能描述 操作一个或一组信号. semid: 信号集的识别码,可通过semget获取. sops: 指向存储信号操作结构的数组指针,信号操作结构的原型如下 1 struct sembuf 2 { 3 unsigned short sem_num; /* semaphore number */ 4 short sem_op

linux内核内存分配(三、虚拟内存管理)

在分析虚拟内存管理前要先看下linux内核内存的详细分配我开始就是困在这个地方,对内核内存的分类不是很清晰.我摘录其中的一段: 内核内存地址 =========================================================================================================== 在linux的内存管理中,用户使用0-3GB的地址空间,而内核只是用了3GB-4GB区间的地址空间,共1GB:非连 续空间的物理映射就位于3G

Linux内核——内存管理

内存管理 页 内核把物理页作为内存管理的基本单位:内存管理单元(MMU,管理内存并把虚拟地址转换为物理地址)通常以页为单位进行处理.MMU以页大小为单位来管理系统中的页表.从虚拟内存的角度看,页就是最小单位. 32位系统:页大小4KB 64位系统:页大小8KB 在支持4KB页大小并有1GB物理内存的机器上,物理内存会被划分为262144个页.内核用 struct page 结构表示系统中的每个物理页. struct page { page_flags_t flags;   /* 表示页的状态,每

linux内核内存分配(二、struct slab和struct kmem_cache)

前一篇bloglinux内核内存分配(一.基本概念)主要是分析linux内核内存的分配和物理页分配函数接口.但是在实际的操作中,不一定所有内存申请都需要一个物理页,很多只是需要分配几K大小的内存就可以.所以就需要更小的内存分配函数.刚开始看这个有点不懂,不过懂了就很简单了.哈哈. slab思想 摘抄<深入linux设备驱动程序内核机制>的一段话:slab分配器的基本思想是,先利用页面分配器分配出单个或者一组连续的物理页面,然后在此基础上将整块页面分割成多个相等的小内存单元,以满足小内存空间分配

linux内核内存分配

实验要求: 1.编写一个内核模块,在模块中分配内存并访问 2.理解并验证kmalloc.vmalloc等函数的区别. 背景知识: 1.Linux内存页管理 Linux内核管理物理内存是通过分页机制实现的,它将整个内存划分成4K大小页,作为使分配和回收内存的基本单位.在分配内存时尽量分配连续内存,避免TLB的刷新率过高.故此Linux采用了“伙伴“关系来管理空闲页框.因此空闲页面分配时也需要遵循伙伴关系.最小单位是2的幂倍页面大小.内核中分配空闲页框的基本函数是get_free_page/get_

linux内核内存分配(一、基本概念)

内存分配是linux比较复杂也是比较重要的部分,这个和ssd驱动很类似:物理地址和虚拟地址的映射关系.下面总结下最近看到的有关内存分配的内容和自己的理解: 1.一致内存访问和非一致内存访问 上图来自<深入linux设备驱动程序内核机制> 简单的说明下,UMA(一致内存访问 uniform memory access)可以很好的看到所有cpu访问内存的距离都是一样的(其实就是通过总线到内存的速度和距离都是一样的)所以就叫一致内存访问: 很显然右边的NUMA就是非一致内存访问,内存节点0是CPU0

Linux-0.11内核内存管理get_free_page()函数分析

/* *Author : DavidLin*Date : 2014-11-11pm*Email : [email protected] or [email protected]*world : the city of SZ, in China*Ver : 000.000.001*history : editor time do 1)LinPeng 2014-11-11 created this file! 2)*/Linux-0.11内存管理模块是源代码中比较难以理解的部分,现在把笔者个人的理解

Linux内核学习笔记——内核内存管理方式

一 页 内核把物理页作为内存管理的基本单位:内存管理单元(MMU)把虚拟地址转换为物理 地址,通常以页为单位进行处理.MMU以页大小为单位来管理系统中的也表. 32位系统:页大小4KB 64位系统:页大小8KB 内核用相应的数据结构表示系统中的每个物理页: <linux/mm_types.h> struct page {} 内核通过这样的数据结构管理系统中所有的页,因此内核判断一个页是否空闲,谁有拥有这个页 ,拥有者可能是:用户空间进程.动态分配的内核数据.静态内核代码.页高速缓存…… 系统中

(笔记)Linux内核学习(九)之内核内存管理方式

一 页 内核把物理页作为内存管理的基本单位:内存管理单元(MMU)把虚拟地址转换为物理 地址,通常以页为单位进行处理.MMU以页大小为单位来管理系统中的也表. 32位系统:页大小4KB 64位系统:页大小8KB 内核用相应的数据结构表示系统中的每个物理页: <linux/mm_types.h> struct page {} 内核通过这样的数据结构管理系统中所有的页,因此内核判断一个页是否空闲,谁有拥有这个页 ,拥有者可能是:用户空间进程.动态分配的内核数据.静态内核代码.页高速缓存-- 系统中