Windows采用了页式内存管理方案,在Intel x86处理器上,Windows不使用段来管理虚拟内存,但是,Intel x86处理器在访问内存时必须要通过段描述符,这意味着Windows将所有的段描述符都构造成了从基地址0开始,且段的大小设置为0x80000000、0xc0000000或0xffffffff,具体取决于段的用途和系统设置。所以,Windows系统中的代码,包括操作系统本身的代码和应用程序代码,所面对的地址空间都是线性地址空间。这种做法相当于屏蔽了处理器中的逻辑地址概念,段只被用于访问控制和内存保护。
Windows使用了两种特权级别:0和3,其中特权级0称为内核模式,特权级3称为用户模式。当处理器执行内核模式代码时,它们处于系统地址空间,位于0x80000000~0xffffffff,所有进程共享此地址;当处理器执行用户模式代码时,它们处于进程地址空间,位于0x00000000~0x7fffffff,这部分空间是进程私有的。用户模式代码只能访问进程自身的数据,而内核模式代码不仅可以访问当前进程的数据,也可以访问系统地址空间中的数据。所有的进程,一旦进入到内核模式,则共享同样的系统地址空间。
其次,在Windows的每个地址空间中,虚拟地址空间的分配和回收都必须按照严格的规则进行。Windows规定,应用程序在使用内存以前必须先申请,所以,操作系统内部可以根据引用程序的申请和释放操作来维护好整个虚拟地址空间的内存分配情况。而且,Windows也采用了按需分配的策略。也就是说,只有当一段虚拟内存正真被使用的时候,系统才会为它分配页面和物理页面。每个进程的虚拟地址空间的分配情况通过一组虚拟地址描述符(VAD,Virtual Address Descriptor)记录下来,这些描述符构成了一棵平衡二叉树,以便于快速地定位到一个指定虚拟地址的描述符上。
在Windows内存管理器中,除了虚拟地址空间的管理,另一个重要职责是管理物理页面,以及实现进程页面的换入和换出过程。在Windows中,页帧编号数据库(Page Frame Number Database,简称PFN数据库)描述了物理内存各个页面的状态。PFN数据库实际上是一个结构数组,每个页面对应有一个PFN项,记录了该页面的使用情况,包括它的状态、对应页表项的地址等信息。此外,操作系统还维护了一组链表,分别将相同类型的页面链接起来,比如,所有空闲的页面都插入到一个空间链表中,所以,操作系统可以快速地从该链表中获得一个空闲的物理页面。页面也会随着其状态的变化,以及被进程的使用情况,而在不通的链表中流动,或者不属于任何一个链表。Windows的页面替换算法也正是在这些数据结构的基础上实现的。
虽然Intel x86定义了虚拟地址与物理地址之间的转换方式,但是页目录和页表需要操作系统来维护,Windows定义了PDE和PTE,并且小心地维护好这些数据结构,以便处理器能够正确地转译虚拟地址。操作系统在创建每个进程时,都需要为这个进程建立一套页目录和页表数据结构,从而建立起该进程的地址空间。
地址空间建立起来以后,并不等于所有的页面都分配好了,Windows采用的时按需换页的策略,当进程或系统使用某个尚未得到物理页面的虚拟地址时,处理器会触发页面错误(page fault)异常,所以,操作系统可以在页面错误的异常处理例程中为其分配页面,并设置好页表项和页面之间的逻辑关系。这一异常的发生对于使用此虚拟地址的代码而言是不可见的,一旦异常处理例程完成,则原来的代码指令继续执行,就好像此换页过程什么也没有发生一样。
什么时候页面被换出呢?当系统认为内存紧缺,或者一个进程由于工作集的限制而不允许拥有更多的物理页面时,Windows会替换工作集中的页面。这项工作是由一个称为工作集管理器(working set manager)的组件来完成的,它运行在一个称为平衡集管理器(balance set manager)的系统线程中。工作集缩减的过程称为修剪(trim)。
Windows内存管理中一些比较重要的组件:
执行体层提供了一组内存管理服务,用户分配、释放和管理虚拟地址。执行体也包括一个堆管理器(heap manager),提供动态小内存块的管理。
页面错误异常处理器。负责分配物理页面,或者把磁盘上的数据读入到页面中。
一组系统线程,负责维护操作系统的内存,包括:
平衡集管理器,它包括工作集管理器,每1s被调用一次,工作集管理器负责实施一些全局性的内存管理策略,比如工作集修剪。
进程/栈交换器。当系统需要执行换入或换出操作时,通知线程完成这些任务。
修改页面写出器。它负责将脏页面写回到映射文件或页面文件中。在WRK中,写到映射文件和写到页面文件是分别由两个线程来完成的。
零页面线程。它运行的优先级为0,负责将空闲链表上的页面清零(即内存零化),以便当系统需要零页面时可以满足其要求。
系统地址空间包含了全局的系统代码和数据结构,并且对于所有的进程都是可见的,操作系统在初始化过程中首先建立起系统地址空间。因此,我们接下来先从系统地址空间的内存管理开始介绍,暂不考虑3GB进程地址空间选项,以及对PAE和大页面(页面大小为4MB)的支持