Windows内存管理分析(三)

Windows内存管理分析(三)

三、虚拟内存的映射

虚拟内存的映射使用MmCreateVirtualMapping函数 参数是进程,虚拟地址,保护类型,页面编号,需要映射的页面数量。

Windows内存管理分析(三)

首先判断物理页面是否可以被映射,通过后使用MmCreateVirtualMappingUnsafe完成真正的映射。

Windows内存管理分析(三)

首先是安全性的检查,如果给定进程空指针,但是映射的地址却低于MmSystemRangeStart的话就返回严重错误,如果是用户进程想映射内核空间,同样返回严重错误,页面数量映射过多也是返回错误

Windows内存管理分析(三)

根据参数来设置安全属性

Windows内存管理分析(三)

Windows内存管理分析(三)

这个FOR循环遍历所有页面完成映射,首先是判断页面是否存在,否则返回严重错误。然后获取上一个映射的页面和本次映射的页表项的偏移是否相同,如果相同就加1到下一个页表项,代表着当前页面已经在上一次循环中被映射,如果不同代表是2个不同的页表项,取出对应的页面项内容PT,用InterlockedExchangePte交换2个属性,完成页表项的设置,也就是完成了这一页的映射。然后刷新TLB

细心的读者可能会想到问题,我们说开启保护模式后,访问的内存都是虚拟内存,这些虚拟地址必须要进行MMU的地址转译,但是进程的KPROCESS结构里面页表基址记录的却是真实物理页表的地址,这时候我们将新的页表项存进这个真实物理页表地址的时候,MMU是将它当成虚拟地址来访问,这时候不就起冲突了吗???这里就可以引出超空间(Hyperspace)的使用,超空间是为了临时访问某一个物理页面进行临时的映射,实际上我们看源代码的时候它并没有直接访问这个真实物理地址,而是通过超空间进行的访问,实际上也就是访问虚拟地址,然而这个虚拟地址指向了这个页表。具体的代码就在MmCreateVirtualMappingUnsafe中MmGetPageTableForProcess和MmUnmapPageTable的使用,MmGetPageTableForProcess临时将物理页面映射,然后返回具体PT,MmUnmapPageTable取消这个超空间的映射过程。关于超空间将于下一节分析。

我们再来分析一个解除映射的过程,有了前面映射的过程,解除估计就很好理解。

Windows内存管理分析(三)

Windows内存管理分析(三)

Windows内存管理分析(三)

这里容易理解,是映射的完全逆过程,不再叙述

四、Hyperspace

Hyperspace前文提到是做物理页面的临时映射,然后进行使用。用处例如就在上文提到的页表的基址实际上是真正的物理地址,不能直接访问,要先映射后再进行访问。创建超空间映射的函数是MmCreateHyperspaceMapping,返回值为虚拟地址的起始地址

Windows内存管理分析(三)

此函数是一个接口函数,真正实现映射的为MiMapPageInHyperSpace

Windows内存管理分析(三)

Windows内存管理分析(三)

MmFirstReservedMappingPte是系统保留的当前可用PTE指针,类似栈的原理,是从高地址往低地址,并且当前记录是没有使用的,个数一共有256个,也就是支持1M。

Windows内存管理分析(三)

系统初始化的时候会把MmFirstReservedMappingPte设置成Hyperspace的基地址对应的PTE项

我们看代码首先取出了对应的PFN项,如果PFN项指向的物理页号为0,就应该为MI_HYPERSPACE_PTES(宏定义是255)代表超空间的最高页面,然后对MmFirstReservedMappingPte记录的物理页号进行了减一处理。然后将这个物理页号和临时的内存属性结合成PTE,然后写入页表,完成映射。

再看删除映射

Windows内存管理分析(三)

同样的也是个接口,真正实现的为MiUnmapPageInHyperSpace

Windows内存管理分析(三)

实现的原理非常简单,取出地址对应页表项,设置为0,就取消了映射、

五、系统空间映射

我们知道每个进程拥有4GB空间,分为用户空间和内核空间,一般情况下低2G进程拥有,高2G内核拥有,实际上内核空间所有进程共享,除了Hyperspace和PAGETABLE_MAP。Hyperspace不支持共享的原因就是因为它是特定进程的,例如前文提到页表临时映射时,必须是基于某个进程进行的映射,所以是不支持所有进程共享。PAGETABLE_MAP页表也不支持共享,因为同样每个进程的页表都是不同的。

接下来分析PspCreateProcess关于内存部分的代码

Windows内存管理分析(三)

再创建进程空间的时候,如果有父进程,调用MmCreateProcessAddressSpace函数

分析一下这个函数

Windows内存管理分析(三)

不讨论PAE的情况,则会申请2页的物理页面

Windows内存管理分析(三)

接下来创建一个超空间,将内核空间的页表MmGlobalKernelPageDirectory进行全部的拷贝,但是内核空间只有2种内存需要修改,第一个就是页表,第二个就是超空间,设置EProcess的DirectoryTableBase结构体第一项为页表的物理地址,第二项为超空间的物理地址。这样就完成了进程虚拟地址的创建

如果这个进程没有父进程,就认为是idle进程

Windows内存管理分析(三)

那么就和idle共享所有虚拟内存

MmGlobalKernelPageDirectory内核页表会在系统初始化的时候会由MmInitGlobalKernelPageDirectory进行初始化

Windows内存管理分析(三)

主要是通过复制页表(虚拟地址)而来

Windows内存管理分析(三)

我们来分析NtAllocateVirtualMemory的实现

我们略过前面的参数检查,直接分析核心代码

Windows内存管理分析(三)

这段代码获取目标进程结构体,如果不是当前进程是通过句柄来引用,然后Attach到目标进程中

Windows内存管理分析(三)

创建一个VAD描述符然后插入到进程的VadRoot中,表明申请了内存

Windows内存管理分析(三)

如果之前是Attach的就可以Detach目标进程了

最核心的就是这些代码,接下来同样是进行一系列检查。

六、共享映射区Section

共享映射区指的是不同进程共享同一段物理内存的情景,功能之一是可以进行进程间的通信,第二个功能因为这个物理页面可以被倒换到磁盘中,进行Section的操作相当于间接的进行了文件IO,很是方便。

创建Section的方法Ring3下可以使用CreateFileMapping,实际上也是调用NtCreateSection,NtCreateSection会通过MmCreateSection创建Section。

来分析MmCreateSection

如果是要映射文件的情况

Windows内存管理分析(三)

首先制作内存属性掩码

Windows内存管理分析(三)

Windows内存管理分析(三)

这段代码获取具体的文件对象,如果没有提供文件句柄或者对象,然而却勾选了SEC_IMAGE属性就返回错误

Windows内存管理分析(三)

Windows内存管理分析(三)

这里进行的读写文件主要是注释说明主要是建立缓冲机制

Windows内存管理分析(三)

Windows内存管理分析(三)

然后判断参数需要的类型,调用不同的类型对应的函数来完成Section的创建。映像文件中可能有很多段,比如数据段代码段,所以是单独一种情况考虑。如果有文件,如果NEWCC宏进行了定义就执行MmCreateDataFileSection,否则是MmCreateCacheSection。如果没指定文件就用换页的文件作为目标文件。

我们分析这些个函数的实现,先分析MmCreateDataFileSection

Windows内存管理分析(三)

首先创建了这个对象,对象类型为

Windows内存管理分析(三)

FileObject执行对应的文件对象,下面的联合体因为要么就是映像Image Section,要么就是认为是Segment Section

Windows内存管理分析(三)

这些就是一个Section 区的各项属性,例如是否支持写时复制,区的保护类型,对应文件的FileOffset,特征字,这些段还用链表串在了一起。PageTable是页表,其类型为

Windows内存管理分析(三)

通过TableRoot结点可以看出实际上是一颗伸展树(这个优化对程序来说非常好,因为程序有局部性访问的原理,所以伸展树某段时间伸展完毕后,应该可以保证一段时间不进行变形,如果稳定的话甚至时间复杂度可以保持在O(1),并且对空间的节省相对于数组来说也是很不错的)。PageTable实际上是二级的结构,通过文件的偏移来找到第二级。第二级的结构是一个CACHE_SECTION_PAGE_TABLE

Windows内存管理分析(三)

文件的偏移的对齐值为CACHE_SECTION_PAGE_TABLE里面的页表项数组PageEntries的个数ENTRIES_PER_ELEMENT乘以页面大小,这个宏一般为256。同时,FileOffset还记录了它对应这个页面具体位移,Segment执行了它是哪个区,RefCount为引用计数。

返回代码

Windows内存管理分析(三)

接下来对这个新申请的Section对象进行初始化

Windows内存管理分析(三)

查询文件信息

Windows内存管理分析(三)

如果指定Section最大长度就用这个长度,否则就用文件的长度

Windows内存管理分析(三)

如果文件的大小没有指定的Section大小大,就对文件进行扩充。锁住文件

Windows内存管理分析(三)

Windows内存管理分析(三)

如果文件之前没有进行映射过,申请一个映射区SECTION_SEGMENT,进行初始化,对于Imge.VirtualAddress因为是数据文件所以直接是0,分析MiInitializeSectionPageTable对页表的初始化。

对于内核GenericTable的使用必须提供比较,申请,释放函数。这里伸展树初始化完毕后为空树。

Windows内存管理分析(三)

如果文件之前映射过判断文件之前的映射区的大小,如果没有指定要求的大就要做扩容处理

Windows内存管理分析(三)

最后完成Section的创建 并返回

分析MmCreateImageSection的结果起始和MmCreateDataFileSection其实大部分相同

Windows内存管理分析(三)

观察IMAGE_SECTION和普通的SEGMENT_SECTION就可以知道区别大体在哪,一般来说镜像文件会有多个segment,nrsegments说明了区数量,而Segments就是一个数组,对于每一个段都必须进行初始化赋值,具体为每一个段赋值在ExeFmtpCreateImageSection实现

创建了Section之后,并没有进行映射,映射通过NtMapViewOfSection来完成,然后会调用MmMapViewOfSection完成映射

由于映像文件和普通文件大体上一样,区别就是映像要为多个段进行映射,我们先分析普通文件的映射

计算出实际大小Viewsize后

Windows内存管理分析(三)

调用MmMapViewOfSegment进行单一段映射,MmMapViewOfSegment这个函数也可用于映像文件多个段的映射

Windows内存管理分析(三)

建立映射的过程也就只是把创建一个Area,将联合体Data的Section字段进行设置,这样就完成了映射。这里非常奇怪,没有建立映射到物理页的过程,那究竟是怎么样进行访问的呢?答案就是在页面异常的缺页异常进行处理。而取消映射的过程和映射的过程相反,就是把Area从VadRoot中进行删除

明天继续第四部分~


分享到:


相關文章: