来自Windows核心编程 第十三章…
本章深入讨论 MS Windows 所使用的内存体系结构。
13.1 进程的虚拟地址空间
每个进程都有自己的虚拟地址空间。
对32位操作系统来说,因为寻址空间是4GB,所以这个虚拟地址空间的大小最大也只能是4GB
对64位操作系统来说,寻址空间是16EB,所以虚拟地址空间最大是可以到16EB的…
每个进程都有自己的虚拟地址空间。
进程A可以在自己的地址0x12345678存储一个数据结构A。
进程B也可以在自己的地址0x12345678存储一个数据结构B。
进程A访问这个地址访问的是数据结构A,进程B访问这个地址得到的是数据结构B。
进程A不能访问位于进程B地址空间中的数据结构,反之亦然。
虽然应用程序有这么大的地址空间可用,但是这只是 虚拟地址空间,不是物理存储器。
这个地址空间只是一个内存地址空间。为了能够正常 读/写 数据,还需要把物理存储器分配或映射到相应的地址空间,否则将导致违规访问。
13.2 虚拟地址空间的分区
每个进程的虚拟地址空间被划分为许多分区(partition)。
不同的Windows内核会有些不一样。
大致可以分为以下四类:
空指针赋值区域、用户模式分区、64KB禁入分区、内核模式分区
13.2.1 空指针赋值分区
在进程的地址分区,[0x00000000 , 0x00000FFFF]
如果进程中线程试图读取或写入这一分区的内存地址,就会引发违规访问。
举个例子:malloc函数,无法分配的时候,就会返回NULL值。
13.2.2 用户模式分区
这一分区是 进程地址空间 的驻地。
这里只讲x86 和 x64吧 IA-64已经过时了…
x86 一般状况下是 2G … 从 0x00010000 -> 0x7FFEFFFF
x86 有3G的情况 从 0x00010000 -> 0xBFFEFFFF
x64 是8192GB(8TB) 从 0x0000 0000 0001 0000 -> 0x0000 07FF FFFE FFFF
进程无法通过指针来读取、写入、或以任何方式 访问驻留在这一分区中其他进程的数据。
对所有应用程序来说,进程的大部分数据都保存在这一分区。
在 Windows 中,所有 .exe 和 动态链接库(DLL) 都载入在这一区域。
系统可能把该进程 可以访问的所有 内存映射文件 映射到 这一分区。
13.2.3 64KB禁入分区
…恩 禁入嘛
13.2.4 内核模式分区
这一分区是 操作系统 代码的驻地。与线程调度、内存管理、文件系统支持、网络支持以及设备驱动相关的代码都载入到这一分区。这一分区的任何东西都由所有进程所共有。
但是如果一个应用程序试图读取或写入位于这一分区中的内存地址,会引发访问违规。
13.3 地址空间中的区域
当系统创建一个进程并赋予它地址空间时,可用地址空间中的大部分都是闲置的或尚未分配的。
为了使用这部分地址空间,我们应该调用VirtualAlloc函数来分配其中区域。
分配区域操作被称为预定。
当应用程序预定地址空间区域时,系统会保证区域的起始地址正好是分配粒度的整数倍。
目前所有的CPU平台都是一样的大小,64KB。
预定一块区域的时候,系统会确保区域大小恰好是系统页面大小的整数倍。
页面是一个内存单元,系统用它来管理内存。
与分配粒度相似,根据不同的CPU会有些不同。
已知 x86 和 x64 是 4KB.
当不需要预定的地址空间区域的时候 ,可以用 VirtualFree 函数来释放它。
13.4 给区域调拨物理存储器
除了预定地址空间区域,还要分配物理存储器(真就是真正存储数据的时候了),并将存储器映射到所预定的区域。这个过程被称为调拨物理存储器。
物理存储器始终都以页面为单位来调拨。我们通过 VirtualAlloc 函数来将物理存储器调拨给所预定的区域。
当我们将物理存储器调拨给区域的时候,不需要给整个区域都调拨物理存储器。
同样的我么可以撤销调拨,也是用的前面说的 VirtualFree函数。
13.5 物理存储器和页交换文件
在老式的操作系统中,物理存储器一般会被认为是机器中内存的总量。
换句话说就是那时候,一台机器是16MB的内存,那应用程序最多只能用16MB内存。
而现在的操作系统除了内存条外,还能让磁盘空间看起来像内存一样。
磁盘上的文件被称为页交换文件,其中包含虚拟内存,可以供任何进程使用。
页交换文件以一种透明的方式增大了应用程序的可用内存总量。
下面讲一下线程试图访问所属进程的地址空间中的数据的时候可能出现的情况
情况1: 线程要访问的数据就在内存中,这种情况下CPU会把数据的 虚拟内存地址 映射到 物理内存地址 ,然后接下来,就是访问内存中的数据了。
情况2: 线程要访问的数据不在内存中,而是在页交换文件中的某处。这种情况下这次不成功的访问我们就称之为页面错误。此时,CPU会通知操作系统。操作系统在内存中找到一个闲置的页面,如果找不到,则释放一个已分配的页面,此时如果已分配的页面没有修改过,可以直接释放,如果修改过,那么必须先从内存复制到页交换文件,然后再释放。释放完后我们就在内存中有了一个页面,此时将页交换文件中的数据定位,然后载入到内存中的闲置页面。然后操作系统对自身内部表项进行更新,表示已经被映射到了物理内存地址。此时CPU再次运行那条引发页面错误的指令,就可以访问到数据了。
如下图所示:
系统需要在内存和页交换文件之间复制的频率越高,硬盘颠簸就越厉害,系统运行地越慢。
通过给计算机增加内存可以减少应用程序运行颠簸次数,极大提升性能。
不在页交换文件中维护的物理存储器
用户要求执行一个应用程序时,系统会打开该应用程序对应的.exe文件并计算出应用程序的代码和数据大小。然后系统会预定一块地址空间,并注明与该区域相关联的物理存储器就是.exe文件本身。
这样做的好处是载入非常快,且页交换文件也可以保持一个合理的大小。
当把一个程序位于硬盘上的文件映像(.exe 或 DLL) 用作地址空间区域对应的物理存储器时,我们称这个文件映像为内存映射文件。
13.6 页面保护属性
我们可以给每个已分配的物理存储页指定不同的页面保护属性。
大致就是 无权限、读、写、执行、和它们的组合。然后还有就是写时复制等…
13.7 实例分析
……
13.8 数据对齐的重要性
访问对齐的数据的时候,效率才更高…