在Windows系统中,每个进程都有自己的虚拟地址空间。对32位进程来说,这个地址空间的大小为4GB,这是因为32为指针可以表示从0x00000000到0xFFFFFFFF之间的任一值。他们覆盖了进程的4GB地址空间。那么Windows是如何实现这一机制的呢?
页交换文件的大小操作系统可以自动调节。
虚拟地址空间的分区(即虚拟地址空间布局)
进程的地址空间划分
分区 | x86(32位 Windows) | 3GB用户模式下的x86 (32位Windows) | X64 (64位Windows) | IA-64 (64位 Windows) |
---|---|---|---|---|
空指针赋值区 | 0x0000 0000 - 0x0000 FFFF | 0x0000 0000 - 0x0000 FFFF | 0x00000000 00000000 - 0x00000000 0000FFFF | 0x00000000 00000000 - 0x00000000 0000FFFF |
用户模式分区 | 0x0001 0000 - 0x7FFE FFFF | 0x0001 0000 - 0xBFFE FFFF | 0x00000000 00010000 - 0x000007FF FFFEFFFF | 0x00000000 00010000 - 0x000006FB FFFEFFFF |
64KB禁入分区 | 0x7FFF 0000 - 0x7FFF FFFF | 0xBFFF 0000 - 0xBFFF FFFF | 0x000007FF FFFF0000 - 0x000007FF FFFFFFFF | 0x000006FB FFFF0000 - 0x000006FB FFFFFFFF |
内核模式 | 0x8000 0000 - 0xFFFF FFFF | 0xC000 0000 - 0xFFFF FFFF | 0x00000800 00000000 - 0xFFFFFFFF FFFFFFFF | 0x000006FC 00000000 - 0xFFFFFFFF FFFFFFFF |
-
空指针赋值分区
①为帮助程序员捕获对空指针的赋值,当线程试图读取或写入这一分区的内存地址,就会引发访问违规
②没有任何办法可以让我们分配到位于这一地址区间的虚拟内存。 -
用户模式分区
①进程地址空间的驻地。对于应用程序来说,大部分数据都保存在这一分区。
②32位下,默认为2GB大小。打开/3GB开关时,可扩大到3GB空间,但同时内核空间缩小为1GB)
③为了让应用程序可以访问2GB以上的地址空间(特别地,早期的应用程序是不允许这样做的)。在链接时,可以打开/LARGEADDRESSAWARE链接开关 -
内核模式分区
操作系统代码的驻地。与线程调度、内存管理 、文件系统支持、网络支持以及设备驱动程序相关的代码都载入到这个分区中。该分区中的所有代码和数据都为所有进程共有,但这些代码和数据都是被保护起来的,如果试图在这分区的某个内存地址读取或写入数据时,会引发访问违规。
Windows内存安排(时间上的安排)
(1)每个应用程序都有自己的4GB寻址空间。该空间可存放操作系统、系统DLL和用户DLL代码,它们之中有各种函数供应用程序调用。再除去其他的一些空间,余下的是应用程序的代码、数据和可以分配的地址空间。
(2)不同应用程序的线性地址空间是隔离的。虽然它们在物理内存中同时存在,但在某个程序所属的时间片中,其他应用程序的代码和数据没有被映射到可寻址的线性地址中,所以是不可访问的。从编程的角度看,程序可供使用的4GB的寻址空间,而且这个空间是“私有的”
(3)DLL程序没有自己的“私有”的空间。它们总是被映射到其他应用程序的地址空间中,当做其他应用程序的一部分运行。原因很简单,如果它不和其他程序同属一个地址空间,应用程序就不能调用它。
(4)操作系统和系统DLL的代码需要供每个应用程序调用,所以在所有的时间片中都必须被映射;
(5)用户程序只在自己所属的时间片内被映射。用户DLL则有选择地被映射。如程序B和C都调用了xxx.dll,那么物理内存中xxx.dll(注意在内存中已经存在了!)的代码在图中的时间片2和n中被映射,其他时间片就不需要被映射。(当然物理内存中只需要一份xxx.dll的代码)。
地址空间中的区域(内存页面4K,物理地址和磁盘的页交换文件是以一个页面为单位的)
-
预定地址空间中的一块区域(预订:VirtualAlloc、释放:VirtualFree)
①起始地址:分配粒度(一般是64K)的整数倍。(注意:分配粒度与CPU平台有关,同时系统自己预订的区域的起始地址不一定非得是64KB的整数倍,如系统为进程环境块(PEB)和线程环境块(TEB)预定的区域地址就可能不是64KB的整数倍,但区域的大小仍是系统页面大小的整数倍。应用程序自己预订的区域,)
②预定空间的区域的大小:系统页面大小的整数倍(x86和x64的页面大小为4KB,I64系统使用的页面大小为8KB) -
将预订区域提交物理存储器
①提交时,可以只提交区域的一部分。如预订64KB空间大小,但可以只提交第2、第4两个页面(同样是调用VirtualAlloc函数,但传入的是MEM_COMMIT类型的参数)。
②撤消提交:VirtualFree,并传入MEM_DECOMMIT
物理存储器和页交换文件
- 虚拟内存的实现:当应用程序调用VirtualAlloc函数将预订的空间区域提交物理存储器(物理内存或页交换文件)时,该空间实际上仍然不是从物理内存而是页交换文件中分配得到的,以后当访问该空间时,会因数据并不存在于物理内存而发生访问“页面错误”,从而引发操作系统利用异常处理机制将虚拟地址空间真正映射到对应的物理内存中,如下图所示。
- 内存映射文件:把硬盘上的文件映像(如一个.exe或DLL文件)作为虚拟内存的一部分(注意是文件映射,而不是页交换文件)。当用户要执行一个可执行文件时,系统会打开应用程序对应的.exe文件并计算出应用程序的代码和数据的大小。然后预订一块地址空间,并注明与该区域相关的存储场所是.exe文件本身,而不是页交换文件。这样做可以将.exe的实际内容用作程序预订的地址空间区域,不仅载入程序速度快,而且可避免将为每个程序文件的代码和数据复制到页交换文件而造成页交换文件过于庞大和臃肿。
页面保护属性 (为了节省内存和安全,比如只读的页只需在内存中保存一份)
保护属性 | 描述 |
---|---|
PAGE_NOACCESS | 不可访问。试图读取、写入或执行页面中的数据(代码)时将引发访问违规。 |
PAGE_READONLY | 只读。试图写入页面或执行页面中的代码将引发访问违规 |
PAGE_READWRITE | 读写属性。试图执行页面中的代码将引发访问违规 。 |
PAGE_EXECUTE | 可执行属性。试图读取或写入页面将引发访问违规。 |
PAGE_EXECUTE_READ | 可读、可执行。读图写入页面将引发访问违规。 |
PAGE_EXECUTE_READWRITE | 可读可写可执行。对页面的任何操作都不会引发访问违规 |
PAGE_WRITECOPY | ①写时复制。试图执行页面中的代码将引发访问违规。②试图写入页面将使系统为进程单独创建一份该页面私有副本(以页交换文件为后备存储器) |
PAGE_EXECUTE_WRITECOPY | 对页面执行任何操作都不会引发访问违规。试图写入页面将使系统为进程单独创建一份该页面私有副本(以页交换文件为后备存储器) |