Intel x86体系结构既支持段式内存管理,也支持页式内存管理,然而,windows没有使用段式内存管理方案,而只是简单地将32位虚拟内存空间按照0~4GB的线性地址空间来看待。任何一个进程都定义了它自己的完整4GB地址空间,但是,其中的2GB~4GB之间的部分是所有进程共享的,称为系统地址空间;0~2GB部分是进程私有的,称为进程地址空间。但是对于一些对内存大小有特殊要求的应用程序,如数据库,对私有空间要求越大越好,因此需要在windows的引导选项”/3GB"对进程空间进行设置以获得3GB的进程地址空间,而系统地址空间只留有1GB。
为了有效的管理2GB的系统地址空间,系统在初始化时将这2GB划分成了一些固定区域,各个区域有专门的用途。而且,在内核中使用了一组变量来记录了每个区域的边界,所以,系统地址空间的初始化实际上是对这些全局变量的初始化,并相应的初始化每个区域。WINDOWS的一些引导选项以及系统配置(位于注册表)可能会影响到某些区域的位置和大小。系统空间中的的主要区域包括:内核模块映像、PFN数据库、换页内存池、非换页内存池、会话空间、系统缓存区、系统PTE区域、系统视图以及页表等。
windows使用Intel x86的二级或多级页表机制来访问虚拟内存。处理器在执行内存访问指令时,将虚拟地址翻译成物理地址,翻译过程涉及页查询页目录和页表,一旦页表项页表项指示一个页面未在物理内存中,则触发页面错误(page fault)异常。虚拟内存管理器通过页面错误异常将已被换出到磁盘上的数据或代码重新带入物理内存,供当前活跃的程序访问。另一方面当物理内存紧张时,它会将不常用的页面换出到磁盘上。windows利用Intel x86页表机制,实现了灵活的页面交换算法,可以支持一个或多个页面交换文件;同时也实现了内存页面的写时复制(copy-on-write)特性。
在系统地址空间中,不同的区域使用不完全相同的内存页面管理算法,典型的方法有以下三种:
- 非换页内存池:这部分的内存区域在初始化时已经被映射到物理页面,所以系统利用空闲链表的做法,按照不同的粒度(1,2,3,>=4个页面大小)将空闲页面链接起来,空闲页面本身即是链表的节点,因而这些链表无须额外的内存空间(除了头节点)。申请和释放页面的操作实际上是针对空闲链表来进行的。
- 换页内存池:在换页内存池区域,空闲的页面并没有被映射好物理页面,windows使用位图来管理页面的分配,分配连续的多个页面,即从位图找到连续的零位。
- 系统PTE区域:这部分内存区域存放的并非PTE,而只是表示这部分地址范围是以PTE的形式来管理的,即把PTE当做资源来管理,当内核需要一段虚拟地址来映射物理页面时,它可以使用系统PTE区域中的地址。
以上这些内存区域按照页面粒度来管理其分配情况,windows执行体在这些系统内存区域管理的基础上,还提供了一组更小粒度的(8B的倍数、最小为8B)的内存池管理,包括执行体换页内存池核执行体非换页内存池,这些内存池通过空闲链表记录下每个已申请的页面中的空闲内存块;当释放内存时,自动与相邻的空闲内存块合并以构成更大的空闲内存块。内核其他组件或驱动程序通过执行体暴露的API函数(ExAllocatePoolWithTag和ExFreePoolWithTag)来使用这些内存池。
进程地址空间是随着进程一起被创建的,每个进程有他自己的页目录页面,其中有一半页目录项(PDE)是共享的,即系统地址空间部分,余下一半初始化为零。随着进程中的映像文件(包括exe和dll文件)被加载进来,以及各个模块的初始化代码被执行,进程的地址空间将被建立起来。
进程地址空间按照其虚拟地址被分配或保留来进行管理,用户模式代码通过windows api函数vitualAllocated(ex)和VirtualFree(ex)来申请或释放地址范围,而内核中的虚拟内存管理器则通过一颗平衡二叉搜索树来管理进程地址空间被使用的情况,树中的每个节点为VAD(虚拟地址描述符,Virtual Address Descriptor),描述了一段连续的地址范围;但是在整个地址空间中,被使用或保留的地址范围往往是不连续的,之所以选择平衡二叉搜索树,是因为这样可以使查找、插入和删除VAD节点都可以快速的完成。
在VAD树中,有一种重要的节点类型为内存区对象(section object)它是windows平台上两个或多个进程之间共享内存的一种常用方式。内存区对象可以被映射到系统页面文件,可执行映像文件或者其他数据文件中,也可能被映射到物理内存中,总之,内存区对象代表了一种物理存储资源。
除了对系统地址空间和进程地址空间的管理,内存管理器另一个重要的任务是管理有限的物理内存,在windows的系统地址空间中,专门保留了一个称为PFN数据库(Page Frame Number Database,页面编号数据库)的区域。每一个物理页面都对应于PFN数据库的一项,此PFN项描述了该页面的状态。windows支持八种状态:活动、备用standby、已修改、已修改但不写出、转移、空闲、零化和坏状态。这里的活动状态时是指正在被某个进程和系统空间所使用,有一个对应的PTE指向该页面。坏状态说明一个物理页面已被检测到硬件错误,其他状态说明了一个物理页面已经不再被原来的PTE所指,但是其内容可能还有效,或者需要被写到磁盘上,或者内容已经不再需要了,或者页面内容已被零化等多种可能。内存管理利用PFN数据库,按照页面状态来管理物理内存,并且负责页面的状态转移。
当系统中的进程需要使用大量的内存时,内存管理器会设法将有限的物理内存分页分给那些需要使用的内存,这里就要引入工作集管理器来解决。工作集(working set)是指一个进程当前正在使用的物理页面的集合,在windows中,有三种工作集:
- 进程工作集:进程当前正在使用的物理页面的集合
- 系统工作集:系统空间中动态映射的页面集合
- 会话工作集:会话空间中的代码和数据区
每个进程都有一个工作链表,其中的每一项不仅记录物理页面的编号,还记录了其他的属性,包括他的年龄(2位),因此,工作集管理器可以根据一些策略来选择要修剪的进程,然后针对要修剪的进程,选择哪些页面被换出到磁盘中,从而将物理页面腾出来。工作集管理器运行在一个称为平衡集管理器(balance set manager)的线程中,每隔1s被触发一次,当可用内存太低时也会被触发,平衡集管理器除了触发工作集管理器以外,也定期触发进程/栈交换器(process/stack swapper),进程/栈交换器是另一个单独的线程,一旦被唤醒,就会将满足特定条件的进程和栈换入内存或换出内存。
windows内存管理综合运用了多种数据结构的算法,很复杂,需要逐步深入,多层级的渐进学习才能熟悉和掌握。