第13章 Windows的内存结构

3.1 进程的虚拟地址空间

由于每个进程可以接收它自己的私有的地址空间,因此当进程中的一个线程正在运行时,该线程可以访问只属于它的进程的内存。属于所有其他进程的内存则隐藏着,并且不能被正在运行的线程访问。

前面说过,每个进程有它自己的私有地址空间。进程A可能有一个存放在它的地址空间中的数据结构,地址是0 x 1 2 3 4 5 6 7 8,而进程B则有一个完全不同的数据结构存放在它的地址空间中,地址是0 x 1 2 3 4 5 6 7 8。当进程A中运行的线程访问地址为0 x 1 2 3 4 5 6 7 8的内存时,这些线程访问的是进程A的数据结构。当进程B中运行的线程访问地址为0 x 1 2 3 4 5 6 7 8的内存时,这些线程访问的是进程B的数据结构。进程A中运行的线程不能访问进程B的地址空间中的数据结构。反之亦然。

当你因为拥有如此大的地址空间可以用于应用程序而兴高采烈之前,记住,这是个虚拟地址空间,不是物理地址空间。该地址空间只是内存地址的一个范围。在你能够成功地访问数据而不会出现违规访问之前,必须赋予物理存储器,或者将物理存储器映射到各个部分的地址空间。

13.2 虚拟地址空间如何分区

windows内存

每个进程的虚拟地址空间都要划分成各个分区。地址空间的分区是根据操作系统的基本实现方法来进行的。不同的Wi n d o w s内核,其分区也略有不同。表1 3 - 1显示了每种平台是如何对进程的地址空间进行分区的。

13.2.1 NULL指针分配的分区—适用于Windows 2000和Windows 98

进程地址空间的这个分区的设置是为了帮助程序员掌握N U L L指针的分配情况。如果你的进程中的线程试图读取该分区的地址空间的数据,或者将数据写入该分区的地址空间,那么C P U就会引发一个访问违规。保护这个分区是极其有用的,它可以帮助你发现N U L L指针的分配情况。

C / C + +程序中常常不进行严格的错误检查。例如,下面这个代码就没有进行任何错误检查:

int* pnSomeInteger = (int*) malloc(sizeof(int));
*pnSomeInteger = 5;

如果m a l l o c不能找到足够的内存来满足需要,它就返回N U L L。但是,该代码并不检查这种可能性,它认为地址的分配已经取得成功,并且开始访问0 x 0 0 0 0 0 0 0 0地址的内存。由于这个分区的地址空间是禁止进入的,因此就会发生内存访问违规现象,同时该进程将终止运行。这个特性有助于编程员发现应用程序中的错误。

13.3 地址空间中的区域

当进程被创建并被赋予它的地址空间时,该可用地址空间的主体是空闲的,即未分配的。若要使用该地址空间的各个部分,必须通过调用Vi r t u a l A l l o c函数(第1 5章介绍)来分配它里边的各个区域。对一个地址空间的区域进行分配的操作称为保留( r e s e r v i n g )。

每当你保留地址空间的一个区域时,系统要确保该区域从一个分配粒度的边界开始。对于不同的C P U平台来说,分配粒度是各不相同的。但是,截止到撰写本书时,所有的C P U平台(x 8 6、3 2位A l p h a、6 4位A l p h a和I A - 6 4)都使用6 4 K B这个相同的分配粒度。

当你保留地址空间的一个区域时,系统还要确保该区域的大小是系统的页面大小的倍数。页面是系统在管理内存时使用的一个内存单位。与分配粒度一样,不同的C P U,其页面大小也是不同的。x 8 6使用的页面大小是4 KB,而A l p h a(当既能运行3 2位Windows 2000也能运行6 4位Windows 2000时)使用的页面大小则是8 KB。在撰写本书时, M i c r o s o f t预计I A - 6 4也使用8K B的页面。但是,如果测试显示使用更大的页面能够提高系统的总体性能,那么M i c r o s o f t可以切换到更大的页面(1 6 K B或更大)。

注意有时系统能够代表你的进程来保留地址空间的区域。例如,系统可以分配一个地址空间区域,以便存放进程环境块( F E B)。F E B是由系统创建、操作和撤消的一个小型数据结构。当创建一个进程时,系统就为F E B分配一个地址空间区域。

系统也需要创建一个线程环境块( T E B),以便管理进程中当前存在的所有线程。用于这些T E B的区域将根据进程中的线程被创建和撤消等情况而保留和释放。

虽然系统规定,要求保留的地址空间区域均从分配粒度边界(目前所有平台上均为6 4 K B)开始,但是系统本身并不受这个规定的限制。为你的进程的P E B和T E B保留的地址空间区域很可能不是从64 KB这个边界开始的。不过这些保留区域仍然必须是C P U的页面大小的倍数。

如果想保留一个10 KB的地址空间区域,系统将自动对你的请求进行四舍五入,使保留的地址空间区域的大小是页面大小的倍数。这意味着,在x 8 6平台上,系统将保留一个1 2 K B的区域,在A l p h a平台上,系统将保留一个1 6 K B的区域。

当你的程序算法不再需要访问已经保留的地址空间区域时,该区域应该被释放。这个过程称为释放地址空间的区域,它是通过调用Vi r t u a l F r e e函数来完成的。

13.4 提交地址空间区域中的物理存储器

若要使用已保留的地址空间区域,必须分配物理存储器,然后将该物理存储器映射到已保留的地址空间区域。这个过程称为提交物理存储器。物理存储器总是以页面的形式来提交的。若要将物理存储器提交给一个已保留的地址空间区域,也要调用Vi r t u a l A l l o c函数。

当将物理存储器提交给地址空间区域时,不必将物理存储器提交给整个区域。例如,可以保留一个6 4 K B的区域,然后将物理存储器提交给该区域中的第二和第四个页面。图1 3 - 1显示了进程的地址空间是个什么样子。注意,根据运行的C P U平台的不同,地址空间是各有差别的。左边的地址空间显示了x 8 6计算机(它的页面大小是4 KB)上的情况,而右边的地址空间则显示了A l p h a计算机(它的页面大小是8K B)上发生的情况。

当你的程序算法不再需要访问保留的地址空间区域中已提交的物理存储器时,该物理存储器应该被释放。这个过程称为回收物理存储器,它是通过Vi r t u a l F r e e函数来完成的。

13.5 物理存储器与页文件

在较老的操作系统中,物理存储器被视为计算机拥有的R A M的容量。换句话说,如果计算机拥有1 6 M B的R A M,那么加载和运行的应用程序最多可以使用1 6 M B的R A M。今天的操作系统能够使得磁盘空间看上去就像内存一样。磁盘上的文件通常称为页文件,它包含了可供所有进程使用的虚拟内存。

当然,若要使虚拟内存能够运行,需要得到C P U本身的大量帮助。当一个线程试图访问一个字节的内存时, C P U必须知道这个字节是在R A M中还是在磁盘上。

从应用程序的角度来看,页文件透明地增加了应用程序能够使用的R A M(即内存)的数量。如果计算机拥有6 4 M B的R A M,同时在硬盘上有一个100 MB的页文件,那么运行的应用程序就认为计算机总共拥有1 6 4 M B的R A M。

当然,实际上并不拥有1 6 4 M B的R A M。相反,操作系统与C P U相协调,共同将R A M的各个部分保存到页文件中,当运行的应用程序需要时,再将页文件的各个部分重新加载到R A M。由于页文件增加了应用程序可以使用的R A M的容量,因此页文件的使用是视情况而定的。如果没有页文件,那么系统就认为只有较少的R A M可供应用程序使用。但是,我们鼓励用户使用页文件,这样他们就能够运行更多的应用程序,并且这些应用程序能够对更大的数据集进行操作。最好将物理存储器视为存储在磁盘驱动器(通常是硬盘驱动器)上的页文件中的数据。这样,当一个应用程序通过调用Vi r t u a l A l l o c函数,将物理存储器提交给地址空间的一个区域时,地址空间实际上是从硬盘上的一个文件中进行分配的。系统的页文件的大小是确定有多少物理存储器可供应用程序使用时应该考虑的最重要的因素, R A M的容量则影响非常小。

现在,当你的进程中的一个线程试图访问进程的地址空间中的一个数据块时,将会发生两种情况之一,参见图1 3 - 2中的流程图。

在第一种情况中,线程试图访问的数据是在R A M中。在这种情况下, C P U将数据的虚拟内存地址映射到内存的物理地址中,然后执行需要的访问。

在第二种情况中,线程试图访问的数据不在R A M中,而是存放在页文件中的某个地方。这时,试图访问就称为页面失效, C P U将把试图进行的访问通知操作系统。这时操作系统就寻找R A M中的一个内存空页。如果找不到空页,系统必须释放一个空页。如果一个页面尚未被修改,系统就可以释放该页面。但是,如果系统需要释放一个已经修改的页面,那么它必须首先将该页面从R A M拷贝到页交换文件中,然后系统进入该页文件,找出需要访问的数据块,并将数据加载到空闲的内存页面。然后,操作系统更新它的用于指明数据的虚拟内存地址现在已经映射到R A M中的相应的物理存储器地址中的表。这时C P U重新运行生成初始页面失效的指令,但是这次C P U能够将虚拟内存地址映射到一个物理R A M地址,并访问该数据块。

系统需要将内存页面拷贝到页文件并反过来将页文件拷贝到内存页面的次数越多,你的硬盘倒腾的次数就越多,系统运行得越慢(倒腾意味着操作系统要花费更多的时间将页面从内存中转出转进,而不是将时间用于程序的运行)。因此,通过给你的计算机增加更多的R A M,就可以减少运行应用程序所需的倒腾次数,这就必然可以大大提高系统的运行速度。所以必须遵循一条基本原则,那就是要让你的计算机运行得更块,增加更多的R A M。实际上,在大多数情况下,若要提高系统的运行性能,增加R A M比提高C P U的速度所产生的效果更好。

windows页转换

 

13.6 保护属性

已经分配的物理存储器的各个页面可以被赋予不同的保护属性。表1 3 - 2显示了这些保护属性。

 

页面保护属性

 

13.6.1 Copy-On-Write访问

表1 3 - 2列出的保护属性都是非常容易理解的,不过最后两个属性需要作一些说明。一个是PA G E _ W R I T E C O P Y,另一个是PA G E _ E X E C U T E _ W R I T E C O P Y。这两个属性的作用是为了节省R A M的使用量和页文件的空间。Wi n d o w s支持一种机制,使得两个或多个进程能够共享单个内存块。因此,如果1 0个N o t e p a d实例正在运行,那么所有实例可以共享应用程序的代码和数据页面。让所有实例共享同样的内存页面将能够大大提高系统的性能,但是这要求所有实例都将该内存视为只读或只执行的内存。如果一个实例中的线程将数据写入内存修改它,那么其他实例看到的这个内存也将被修改,从而造成一片混乱。

为了防止出现这种混乱,操作系统给共享内存块赋予了C o p y - O n - Wr i t e保护属性。当一个. e x e或D L L模块被映射到一个内存地址时,系统将计算有多少页面是可以写入的(通常包含代码的页面标为PA G E _ E X E C U T E _ R E A D,而包含数据的页面则标为PA G E _ R E A D W R I T E)。然后,系统从页文件中分配内存,以适应这些可写入的页面的需要。除非该模块的可写入页面是实际的写入模块,否则这些页文件内存是不使用的。

当一个进程中的线程试图将数据写入一个共享内存块时,系统就会进行干预,并执行下列操作步骤:

1) 系统查找R A M中的一个空闲内存页面。注意,当该模块初次被映射到进程的地址空间时,该空闲页面将被页文件中已分配的页面之一所映射。当该模块初次被映射时,由于系统要分配所有可能需要的页文件,因此这一步不可能运行失败。

2) 系统将试图被修改的页面内容拷贝到第一步中找到的页面。该空闲页面将被赋予PA G E _ R E A D W R I T E或PA G E _ E X E C U T E _ R E A D W R I T E保护属性。原始页面的保护属性和数据不发生任何变化。

3) 然后系统更新进程的页面表,使得被访问的虚拟地址被转换成新的R A M页面。

当系统执行了这3个操作步骤之后,该进程就可以访问它自己的内存页面的私有实例。第1 7章还要详细地介绍共享内存和C o p y - O n - Wr i t e保护属性。

此外,当使用Vi r t u a l A l l o c函数来保留地址空间或者提交物理存储器时,不应该传递PA G E _ W R I T E C O P Y或PA G E _ E X E C U T E _ W R I T E C O P Y。如果传递的话,将会导致Vi r t u a l A l l o c调用的失败。对G e t L a s t E r r o r的调用将返回E R R O R _ I N VA L I D _ PA R A M E T E R。当操作系统映射. e x e或D L L文件映像时,这两个属性将被操作系统使用。

Windows 98不支持C o p y - O n - Wr i t e保护。当Windows 98发现需要C o p y _ O n _ Wr i t e保护时,它就立即进行数据的拷贝,而不是等待试图对内存进行写入操作。

13.8 数据对齐的重要性

数据对齐并不是操作系统的内存结构的一部分,而是C P U结构的一部分。

当C P U访问正确对齐的数据时,它的运行效率最高。当数据大小的数据模数的内存地址是0时,数据是对齐的。例如, W O R D值应该总是从被2除尽的地址开始,而D W O R D值应该总是从被4除尽的地址开始,如此等等。当C P U试图读取的数据值没有正确对齐时, C P U可以执行两种操作之一。即它可以产生一个异常条件,也可以执行多次对齐的内存访问,以便读取完整的未对齐数据值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值