1.Windows的内存结构
Windows系统中的每个进程都被赋予它自己的虚拟地址空间。对于32位进程来说,这个地址空间是4GB,因为32位指针可以拥有从0x00000000至0xFFFFFFFF之间的任何一个值。对于64位进程来说,则这个空间是16EB。由于每个进程可以接收它自己的私有的地址空间,因此当进程中的一个线程正在运行时, 该线程也只能访问只属于它的进程的内存。属于所有其他进程的内存则隐藏着,并且不能被访问。
每个进程的虚 拟地址空间都要划分成各个分区,地址空间的分区时根据操作系统的基本实现来进行的,不同的windows内核,其分区也略有不同。下面以32位windows 2000 (x86和alpha处理器)
分 区 |
地 址范围 |
作 用 |
NULL指针分配的区域 |
0x00000000 |
为了帮助掌握NULL指针的分配情况,任何读写都将引发访问违规 |
0x0000FFFF |
||
用户方式分区 |
0x00010000 |
这是进程的私 有空间,该分区是维护进程的大部分数据的地方。 |
0x7FFEFFFF |
||
64k 禁止进入分区 |
0x7FFF0000 |
这个分区是禁 止进入的,任何访问都将是违规。保留此分区是为了更加容易地实现操作系统。怕用户内存越界到内核区。 |
0x7FFFFFFF |
||
内核方式 |
0x80000000 |
这个分区是存 放操作系统代码的地方。用于线程调度、内存管理、文件系统支持等以及设备驱动程序的代码全部在这个分区加载。 |
0xFFFFFFFF |
||
另外,如果用 户想把用户方式分区扩大到3GB,则在x86的windows 2000 advanced server版本和windows 2000 data center版本中可以加入/3GB开关到BOOT.INI文件中。使用/3GB开关后,将减少系统能够创建的线程、堆栈和其他资源的数量。此外,系统最多使用16GB的RAM,而通常情况下可以使用64GB的RAM。因为内核方式中没有足够的虚拟空间来管理更多的RAM。
2.地址空间中的区域
当进程被创建 并被赋予它的地址空间时,该可用空间的主体是空闲的,未分配的。若要使用该地址空间的各个部分,必须要调用virtualAlloc 函数来分配它里边的各个区域。对每一个地址空间的区域进行 分配的操作称为保留(reserve)
当你保留地址空间的一个区域时,该区域必须是系统的页面大小的倍数,而且分配边界必须从一个分配粒度开始。例如x86的页面大小为4KB,分配粒度为64KB。
若要使用已保留的地址空间区域,则必须分配物理存储器,然后将该物理存储器映射到已保 留的地址空间区域。这个过程叫提交物理存储器。也调用VirtualAlloc函数,但和前面保留的输入参数有所区别,可以自己查此函数。当然用户也可以在保留地址空 间的同时提交物理存储器。
在较老的操作系统中,物理存储器被视为计算机所有的RAM的容量。如果计算机有16M的RAM,则加载和运行的应用程序最多可以使用16M的RAM。今天的操作系统则使得磁盘空间看上去像内存一样。磁盘上的文件通常称为页文件,它包含 了可以供所有进程使用的虚拟内存。(用户可以在“我的电脑->属性->高级”里面查看虚拟内存的页文件信息)
这样,当一个 应用程序通过调用VirtrualAlloc函数,将物理存储器提交给地址空间的一个区域时,地址空间实际上是从硬盘上的一个文件中 进行分配的。
当用户进程中的一个线程试图访问进程的地址空间中的一个数据块的时候。一般会发生两种 情况:
1. 线程试图访问的数据是在RAM中,则cpu只需要将虚拟地址映射到内存的物理地址中,然后执行需要的访问。
2. 数据不在RAM中,而是放在页文件的某个地方。这时候,访问引起页面失效,cpu将通知操作系统,操作系统就从RAM中寻找一个空白页,如果找不到空白页,则必须释放一个页。如果该页面没有被修改过,则 可以直接释放,否则必须先把此页面从RAM拷贝到页面交换文件,然后系统进入该页文件,找出需要访问的数据,并将数据加载到空闲的内存页面。然后,操作系统更新它的用于指明数据的虚拟内存地址现在已经映射到RAM中的相应的物理存储器地址中的表。
3. Windows的内存管理方法
windows提供了3种方法来进行内存管理:
l 虚拟内存,最适合用来管理大型对象或者结构数组
l 内存映射文件,最适合用来管理大型数据流(通常来自文件)以及在单个计算机上运行多个进程之间共享数据。
l 内存堆栈,最适合用来管理大量的小对象。
3.1 虚拟内存
虚拟内存的使用主要有以下几个步骤:
1. 在地址空间保留一个区域,调用函数VirtualAlloc
2. 在保留区域中的提交物理存储器,当保留一个区域后,必须将物理存储器提交给该区域,然 后才能访问该区域中包含的内存地址。系统从它的页文件中将已提交的物理存储器分配给一个区域。仍旧调用函数VirtualAlloc具体参数设置可以见msdn,当然,用户也可以一次性地进行操作保留区域和提交物理存储器。
3. 回收虚拟内存和释放地址空间区域,调用VirtualFree函数,并且,如果要释放一个区域,必须释放该区域保留地所有地址空间。当然用户也可以只回收物理存储器而不释放区域,仍旧调用VirtualFree函数,但参数传入不同。
3.2 内存映射文件
与虚拟内存一样,内存映射文件可以用来保留一个地址空间的区域,并将物理存储器提交给该 区域。他们之间的区别是,物理存储器来自一个已经位于磁盘上的文件,而不是系统的页文件。一旦该文件被映射,就可以访问它,就像整个文件被加载到了内存一 样。
内存映射文件 一般用于3个不同 的目的:
1. 系统使用内存映射文件,以便加载和执行.exe和DLL文件。这可以大大节省页文件空间和应用程序启动运行所需的时间
2. 可以使用内存映射文件来访问磁盘上的数据文家爱女。这使你可以不比对文件执行i/o操作,并且可以不必对文件内容进行缓存
3. 可以使用内存映射文件,使同一台计算机上运行的多个进程能够相互之间共享数据。
若要使用内存映射文件,必须执行下列操作步骤:
1) 创建或打开一个文件内核对象,该对象用于标识磁盘上你想用作内存映射文件的文件 (CreateFile函数)
2) 创建一个文件映射内核对象,告诉系统该文件的大小和你打算如何访问该文件
(CreateFileMapping函数)
3) 让系统将文件映射对象的全部或一部分映射到你的地址空间
(MapViewOfFile函数,要求文件的位移是分配粒度的倍数)
当完成对内存映射文件的使用时,必须执行下面的这些步骤将它清除:
4)告诉系统从你的进程的地址空间中撤销文件映射内核对象的映象
(UnmapViewOfFile函数)
5)关闭文件映射内核对象
(CloseHandle函数,第2)步创建的对象)
6)关闭文件内核对象
(CloseHandle函数,第1)步创建的对象)
利用内存映射文件,还可以实现进程之间的数据共享。数据共享的方法是通过让两个或多个进程映射同一个文件映射对象的视图,这也意味着他们将共享物理存储器的同一个页面。另外,用户也可以创建由系统的页文件支持的内存映射文件,而不是由专用硬盘文件支持的内存映射文件。这样,就不需要调用CreateFile函数,只需要给CreateFileMapping的Hfile参数传递INVALID_HANDLE_VALUE,并传递一个以0结尾的字符串作为pszName参数。别的进程就可以用CreateFileMapping或者OpenFilemapping函数。
3.3 堆栈
堆栈可以用来分配许多较小的数据块,例如对链接表和链接树进行管理等。堆栈的优点是, 可以不考虑分配粒度和页面边界之类的问题。堆栈的缺点是,分配和释放内存块的速度比其他机制要慢,并且无法直接控制物理存储器的提交和回收。
当进程初始化时,系统在进程的地址空间中创建一个堆栈。该堆栈为进程的默认堆栈。按照默认设置,该堆栈的地址空间区域的大小是1MB。系统可以扩大进程的默认堆栈。由于进程的默认堆栈可以供许多windows函数调用,因此对默认堆栈的访问是按顺序进行的。也就是,系统必须保证在规定的时间内,每次只有一个线程能分配和释放默认堆栈中的内存块 。 当然,用户也可以在进程的地址空间中创建一些辅助堆栈。
堆栈的一些操作函数如下(具体可以查msdn):
1.创建堆栈 HeapCreate
2. 从堆栈中分配内存块 HeapAlloc
3. 改变内存块的大小 HeapReAlloc
4. 释放内存块 HeapFree
5. 撤销堆栈 HeapDestroy