1 进程的虚拟地址空间布局
1.1 进程虚拟地址空间布局:
每个进程都被赋予它自己的虚拟地址空间。对于3 2位进程来说,这个地址空间是4GB,Win2K在IA-32架构的CPU下面的进程地址空间分布如下表所示:
地址空间说明
地址空间
64K NULL指针分配区
0x00000000~0x0000FFFF (64K)
用户空间
0x00010000~0x7FFEFFFF (2G–128K)
64K禁入区
0x7FFF0000~0x7FFFFFFF (64K)
内核空间
0x80000000~0xFFFFFFFF (2G)
可见Win2K的内存布局非常简单,主要有4个部分,两个不能被存取的64K;然后剩下的部分就是分别是用户空间(2G–128K)和系统空间(2G)了。
1.2 进程虚拟地址空间布局说明:
1. 64K NULL指针分配区:
这个分区的设置是为了帮助程序员掌握NULL指针的分配情况。如果你的进程中的线程试图读取该分区的地址空间的数据,或者将数据写入该分区的地址空间,那么C P U就会引发一个访问违规。也就是说在Win2K中,NULL的宏定义不必一定是0;可以是64K之内的任何地址;比如在win2K下面定义一个指针变量pVar;令pVar取值在0~65535(64K)之间的时候都会发生存取异常;当令pVar = 65536就没有问题了。
2. 用户空间:
在Windows 2000中,所有的. exe和DLL模块均加载这个分区。每个进程可以将这些D L L加载到该分区的不同地址中(不过这种可能性很小)。系统还可以在这个分区中映射该进程可以访问的所有内存映射文件。
3. 64K禁入区:
64K禁入区的作用很明显是隔离了用户和内核空间;防止用户程序跨越到内核空间中。
4. 内核空间:
这个分区是存放操作系统代码的地方。用于线程调度、内存管理、文件系统支持、网络支持和所有设备驱动程序的代码全部在这个分区加载。驻留在这个分区中的一切均可被所有进程共享。在Windows 2000中,这些组件是完全受到保护的。
2 进程的虚拟地址空间分配
2.1 如何分配地址空间?
当进程被创建并被赋予它的地址空间时,该地址空间的主体是未分配的。若要使用该地址空间的各个部分,必须通过调用VirtualAlloc函数来分配它里边的各个区域。对一个地址空间的区域进行分配的操作称为保留( reserving)。
分配地址空间的两个规则:要确保保留区域从一个分配粒度的边界开始(系统自身保留地址空间时未必遵守这个约定)。x86使用64KB这个分配粒度;还要确保保留区域的大小是系统页面大小的倍数。x86使用的页面大小是4KB。
2.2 如何回收地址空间?
当你的程序不再需要访问已经保留的地址空间区域时,保留区域应该被释放。这个过程称为释放地址空间的区域,它是通过调用VirtualFree函数来完成的。
注意这节说的只是对地址空间的分配,而不是对内存的分配。已经被分配的地址空间没有和内存相对应;这时候对这块空间的读写会发生访问违规。
2.3 地址空间可以和什么相对应呢?
和地址空间相对应的只有两样东西,一个是页面文件,也就是通常所说的虚拟内存,一个是内存映射文件,内存映射文件也是磁盘上的文件,这包括exe,dll等,还包括用户自己创建的内存映射文件。或许你认为和地址空间相对应的应该是ram,不错,当我们真正对一个地址进行读写的时候,确实是对ram进行读写;但是ram只是相当于一个缓冲区;负责对内存映射文件和虚拟内存读写进行缓冲。
3 进程的虚拟地址空间的保护属性
3.1 什么是Copy-On-Write保护属性?
在保留地址空间的同时可以指定其保护属性,这些属性无法就是读,写,执行,及其组合,这里重点说一下两个特殊的保护属性。一个是PAGE_WRITECOPY,另一个是PAGE_EXECUTE_WRITECOPY。这两个属性的作用是为了节省RAM的使用量和页文件的空间。Windows支持一种机制,使得两个或多个进程能够共享单个内存块。但是这要求所有实例都将该内存视为只读或只执行的内存。如果一个实例中的线程将数据写入内存修改它,那么其他实例看到的这个内存也将被修改,从而造成一片混乱。为了防止出现这种混乱,操作系统给共享内存块赋予了Copy-On-Write保护属性。当一个.exe或DLL模块被映射到一个内存地址时,系统将计算有多少页面是可以写的(通常包含代码的页面标为PAGE_EXECUTE_READ,而包含数据的页面则标为PAGE_READWRITE)。然后,系统依照计算结果从页文件中分配内存,以防备对这些可写页面写入时的需要。当然如果不对该模块的可写页面进行实际的写入操作,那么这些页文件内存就不会被使用。
3.2 Copy-On-Write保护是如何实现的?
当一个进程中的线程试图将数据写入一个共享内存块时,系统就会进行干预,并执行下列操作步骤:
1)系统查找RAM中的一个空闲内存页面。
2)系统将试图被修改的页面内容拷贝到第一步中找到的页面。并将其赋予PAGE_READWRITE或PAGE_EXECUTE_READWRITE保护属性。原始页面的保护属性和数据不发生任何变化。
3)然后系统更新进程的页面表,使得被访问的虚拟地址被转换成新的RAM页面。
当系统执行了这3个操作步骤之后,该进程就可以访问它自己的内存页面的私有实例。