1.进程虚拟地址空间布局:
每个进程都被赋予它自己的虚拟地址空间。对于3 2位进程来说,这个地址空间是4GB,Win2K在IA-32架构的CPU下面的进程地址空间分布如下表所示:
可见Win2K的内存布局非常简单,主要有4个部分,两个不能被存取的64K;然后剩下的部分就是分别是用户空间(2G–128K)和系统空间(2G)了。
进程虚拟地址空间布局说明:
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.如何分配地址空间:
对一个地址空间的区域进行分配的操作称为保留( reserving)。若要使用已保留的地址空间区域,必须分配物理存储器,然后将该物理存储器映射到已保留的地址空间区域。这个过程称为提交物理存储器。物理存储器总是以页面的形式来提交的。若要将物理存储器提交给一个已保留的地址空间区域,也要调用VirtualAlloc函数。
分配地址空间的两个规则:要确保保留区域从一个分配粒度的边界开始(系统自身保留地址空间时未必遵守这个约定)。x86使用64KB这个分配粒度;还要确保保留区域的大小是系统页面大小的倍数。x86使用的页面大小是4KB。
3.如何回收地址空间:
当你的程序不再需要访问已经保留的地址空间区域时,保留区域应该被释放。这个过程称为释放地址空间的区域,它是通过调用VirtualFree函数来完成的。
注意这节说的只是对地址空间的分配,而不是对内存的分配。已经被分配的地址空间没有和内存相对应;这时候对这块空间的读写会发生访问违规。
当你的进程中的一个线程试图访问进程的地址空间中的一个数据块时,将会发生两种情况之一,参见下图的流程图。
在第一种情况中,线程试图访问的数据是在RAM中。在这种情况下, CPU将数据的虚拟内存地址映射到内存的物理地址中,然后执行需要的访问。
在第二种情况中,线程试图访问的数据不在RAM中,而是存放在页文件中的某个地方。这时,试图访问就称为页面失效,CPU将把试图进行的访问通知操作系统。这时操作系统就寻找RAM中的一个内存空页。如果找不到空页,系统必须释放一个空页。如果一个页面尚未被修改,系统就可以释放该页面。但是,如果系统需要释放一个已经修改的页面,那么它必须首先将该页面从RAM拷贝到页交换文件中,然后系统进入该页文件,找出需要访问的数据块,并将数据加载到空闲的内存页面。然后,操作系统更新它的用于指明数据的虚拟内存地址现在已经映射到RAM中的相应的物理存储器地址中的表。这时CPU重新运行生成初始页面失效的指令,但是这次CPU能够将虚拟内存地址映射到一个物理RAM地址,并访问该数据块。
当启动一个应用程序的时候,系统将打开该应用程序的. exe文件,确定该应用程序的代码和数据的大小。然后系统要保留一个地址空间的区域,并指明与该区域相关联的物理存储器是在. exe文件本身中。即系统并不是从页文件中分配地址空间,而是将. exe文件的实际内容即映像用作程序的保留地址空间区域。当然,这使应用程序的加载非常迅速,并使页文件能够保持得非常小。
4.地址空间可以和什么相对应呢?
和地址空间相对应的只有两样东西,一个是页面文件,也就是通常所说的虚拟内存,一个是内存映射文件,内存映射文件也是磁盘上的文件,这包括exe,dll等,还包括用户自己创建的内存映射文件。或许你认为和地址空间相对应的应该是ram,不错,当我们真正对一个地址进行读写的时候,确实是对ram进行读写;但是ram只是相当于一个缓冲区;负责对内存映射文件和虚拟内存读写进行缓冲。
5.什么是Copy-On-Write保护属性?
在保留地址空间的同时可以指定其保护属性,这些属性无法就是读,写,执行,及其组合,这里重点说一下两个特殊的保护属性。一个是PAGE_WRITECOPY,另一个是PAGE_EXECUTE_WRITECOPY。这两个属性的作用是为了节省RAM的使用量和页文件的空间。Windows支持一种机制,使得两个或多个进程能够共享单个内存块。但是这要求所有实例都将该内存视为只读或只执行的内存。如果一个实例中的线程将数据写入内存修改它,那么其他实例看到的这个内存也将被修改,从而造成一片混乱。为了防止出现这种混乱,操作系统给共享内存块赋予了Copy-On-Write保护属性。当一个.exe或DLL模块被映射到一个内存地址时,系统将计算有多少页面是可以写的(通常包含代码的页面标为PAGE_EXECUTE_READ,而包含数据的页面则标为PAGE_READWRITE)。然后,系统依照计算结果从页文件中分配内存,以防备对这些可写页面写入时的需要。当然如果不对该模块的可写页面进行实际的写入操作,那么这些页文件内存就不会被使用。
6.Copy-On-Write保护是如何实现的?
当一个进程中的线程试图将数据写入一个共享内存块时,系统就会进行干预,并执行下列操作步骤:
1)系统查找RAM中的一个空闲内存页面。
2)系统将试图被修改的页面内容拷贝到第一步中找到的页面。并将其赋予PAGE_READWRITE或PAGE_EXECUTE_READWRITE保护属性。原始页面的保护属性和数据不发生任何变化。
3)然后系统更新进程的页面表,使得被访问的虚拟地址被转换成新的RAM页面。
当系统执行了这3个操作步骤之后,该进程就可以访问它自己的内存页面的私有实例。
7.数据对齐的重要性
当C P U访问正确对齐的数据时,它的运行效率最高。当数据大小的数据模数的内存地址是0时,数据是对齐的。例如, W O R D值应该总是从被2除尽的地址开始,而D W O R D值应该总是从被4除尽的地址开始,如此等等。当C P U试图读取的数据值没有正确对齐时, C P U可以执行两种操作之一。即它可以产生一个异常条件,也可以执行多次对齐的内存访问,以便读取完整的未对齐数据值。