每个进程都被赋予它自己的虚拟地址空间。对于32位进程来说,这个地址空间是4GB,因为32位指针可以拥有从0x00000000至0xFFFFFFFF之间的任何一个值。当你因为拥有如此大的地址空间可以用于应用程序而兴高采烈之前,记住,这是个虚拟地址空间,不是物理地址空间。该地址空间只是内存地址的一个范围。在你能够成功地访问数据而不会出现违规访问之前,必须赋予物理存储器,或者将物理存储器映射到各个部分的地址空间。
当进程被创建并被赋予它的地址空间时,该可用地址空间的主体是空闲的,即未分配的。若要使用该地址空间的各个部分,必须通过调用VirtualAlloc函数(第15章介绍)来分配它里边的各个区域。对一个地址空间的区域进行分配的操作称为保留。每当你保留地址空间的一个区域时,系统要确保该区域从一个分配粒度的边界开始。对于不同的CPU平台来说,分配粒度是各不相同的。但是,截止到撰写本书时,所有的CPU平台都使用64KB这个相同的分配粒度。当你保留地址空间的一个区域时,系统还要确保该区域的大小是系统的页面大小的倍数。页面是系统在管理内存时使用的一个内存单位。与分配粒度一样,不同的CPU,其页面大小也是不同的,x86使用的页面大小是4KB。注意有时系统能够代表你的进程来保留地址空间的区域。例如,系统可以分配一个地址空间区域,以便存放进程环境块(FEB)。FEB是由系统创建、操作和撤消的一个小型数据结构。当创建一个进程时,系统就为FEB分配一个地址空间区域。系统也需要创建一个线程环境块(TEB),以便管理进程中当前存在的所有线程。用于这些TEB的区域将根据进程中的线程被创建和撤消等情况而保留和释放。当你的程序算法不再需要访问已经保留的地址空间区域时,该区域应该被释放。这个过程称为释放地址空间的区域,它是通过调用VirtualFree函数来完成的。
若要使用已保留的地址空间区域,必须分配物理存储器,然后将该物理存储器映射到已保留的地址空间区域。这个过程称为提交物理存储器。物理存储器总是以页面的形式来提交的。若要将物理存储器提交给一个已保留的地址空间区域,也要调用VirtualAlloc函数。当将物理存储器提交给地址空间区域时,不必将物理存储器提交给整个区域。例如,可以保留一个64KB的区域,然后将物理存储器提交给该区域中的第二和第四个页面。当你的程序算法不再需要访问保留的地址空间区域中已提交的物理存储器时,该物理存储器应该被释放。这个过程称为回收物理存储器,它是通过VirtualFree函数来完成的。
今天的操作系统能够使得磁盘空间看上去就像内存一样。磁盘上的文件通常称为页文件,它包含了可供所有进程使用的虚拟内存。当然,若要使虚拟内存能够运行,需要得到CPU本身的大量帮助。当一个线程试图访问一个字节的内存时,CPU必须知道这个字节是在RAM中还是在磁盘上。现在,当你的进程中的一个线程试图访问进程的地址空间中的一个数据块时,将会发生两种情况之一,参见图中的流程图。
当启动一个应用程序的时候,系统将打开该应用程序的.exe文件,确定该应用程序的代码和数据的大小。然后系统要保留一个地址空间的区域,并指明与该区域相关联的物理存储器是在.exe文件本身中。即系统并不是从页文件中分配地址空间,而是将.exe文件的实际内容即映像用作程序的保留地址空间区域。当然,这使应用程序的加载非常迅速,并使页文件能够保持得非常小。当硬盘上的一个程序的文件映像(这是个.exe文件或DLL文件)用作地址空间的区域的物理存储器时,它称为内存映射文件。当一个.exe文件或DLL文件被加载时,系统将自动保留一个地址空间的区域,并将该文件映像映射到该区域中。但是,系统也提供了一组函数,使你能够将数据文件映射到一个地址空间的区域中。关于内存映射文件的详细说明,将在第17章中介绍。
已经分配的物理存储器的各个页面可以被赋予不同的保护属性。其属性有:PAGE_NOACCESS,PAGE_READONLY,PAGE_READWRITE,PAGE_EXECUTE,PAGE_EXECUTE_READ,PAGE_EXECUTE_READWRITE,PAGE_WRITECOPY,PAGE_EXECUTE_WRITECOPY。最后两个保护属性需要作一些说明,这两个属性的作用是为了节省RAM的使用量和页文件的空间。Windows支持一种机制,使得两个或多个进程能够共享单个内存块。因此,如果10个Notepad实例正在运行,那么所有实例可以共享应用程序的代码和数据页面。让所有实例共享同样的内存页面将能够大大提高系统的性能,但是这要求所有实例都将该内存视为只读或只执行的内存。当使用VirtualAlloc函数来保留地址空间或者提交物理存储器时,不应该传递PAGE_WRITECOPY或PAGE_EXECUTE_WRITECOPY。如果传递的话,将会导致VirtualAlloc调用的失败。当操作系统映射.exe或DLL文件映像时,这两个属性将被操作系统使用,操作系统给共享内存块赋予了Copy-On-Write保护属性。当一个进程中的线程试图将数据写入一个共享内存块时,系统就会进行干预。