Microsoft Windows 提供了以下三种机制来对内存进行操控:
本篇只讨论第一种方式 虚拟内存。
15.1 预订地址空间区域
可以使用VirtualAlloc函数来预订进程中的地址空间区域
LPVOID WINAPI VirtualAlloc( __in_opt LPVOID lpAddress, __in SIZE_T dwSize, __in DWORD flAllocationType, __in DWORD flProtect );
lpAddress :想要预订地址空间的哪一块,如果要在进程地址空间第50MB的地方分配区域。我们要传52 428 800(50*1024*1024)给pvAddress参数。系统始终按分配粒度的整数倍来分配的因此如果我们想要在19668992(300*65536+8192)的地方预订区域,那么系统会向下取整到64整数倍,也就是19660800(300*65536)然后取整后的地址预订区域。如果VirtualAlloc能满足我们的要求,那么它会预订一块区域并返回该区域的基地址。如果我们还给pvAddesss指定了参数,那么,它的返回值就等于我们传给pvASddress参数值向下取整到64KB的整数倍dwSize: 指定我们想要分析区域的大小,以字节为单位。由于系统始终都根据cpu页面大小整数倍来预订区域,因此如果我们的页面大小为4KB,8KB或16KB的机器上预订62 KB的区域的话,那么最终得到的区域大小为64KBflAllocationType:是预订区域还是调拨物理存储器,如果是预订就要传 MEM_RESERVE为参数如果打算预订一块区域,并且用很长时间,那我们可能会希望系统从尽可能高的内存地址来预订。这样可以防止进程地址空间的中间预订区域,从而避免可能会引起内存碎片。fdwProtect:给区域指定保护属性。如果应用程序在非统一内存访问的机器上运行,我们可以调用VirtualAllocExNuma函数来强制系统在某个节点的物理内存中分配一部分虚拟内存
15.2 给区域调拨物理存储器、
在预了区域之后,我们还需要给给区域调拨物理存储器,这样才能访问其中的内存地址。系统会从页交换文件中来调拨物理存储器给区域。在调拨物理存储器时,起始地址始终都是页面大小的整数倍,整个大小也是页面大小的整数倍调拨物理存储器,我们必须再次调用VirtualAlloc。但这次我们会传 MEM_COMMIT来作为第二个参数fdwAllocationType的值在已预订的区域中,我们“必须”告诉VirtualAlloc要调拨多少物理存储器给哪里。这是通过pvAddress和dwSize来指定的。前者表示想要调拨物理存储器给哪个内存地址,后来表示物理存储器的数量。以字节为单位。
15.3 同时预订和调拨物理存储器
PVOID pvMem = VirtualAlloc(NULL,99*1024,MEM_RESERVE | MEM_COMMIT,PAGE_READWRITE)
15.4 何时调拨物理存储器
何时调拨物理存储器是一个问题,例如我们正在实现一个电子表格的程序,它支持200行,256。如果一个单元格是一个CELLDATA结构的话,如果这一个结构頕128字节,那么这个二维数组需要 200 * 256 * 128 的字节的物理存储器。那么如果一打开程序就去调拨,明显会頕用大量的物理存储器。尤其是因为用户经常只用一两个单元格的时候,那么这种调拨方法明显是浪费虚拟内存技术为我们提供了方案:1.预订一块足够大的区域来容纳CELLDATA数组。只预订根本不会消耗物理存储器
2.当用户某个单元格输入数据时,首先确定CELLDATA的内存地址。当然没有调拨,会引发访问违规
3.给第二步中的内存地址调拨足够的物理存储器。
4.设置CELLDATA结构成员的内容
虚拟内存技术存在一个问题是:我们必须确定什么时候需要调拨物理存储器。有以下四种方法来确定是否需要给区域中的某一部分调拨物理存储器
1.总是尝试调拨,这种该当的缺点是每次对CELLDATA进行修改时,要多调用一次 VirtualAlloc。
2.使用VirtualQuery来判断是否给CELLDATA调拨过了。这种方法其实比第一种方法还糟糕:由于额外调用了VirtuaQuery函数,它不但增加了程序的大小,而且还降低了程序的性能
3.记录哪些被调拨过。这样来实时调拨
4.使用结构化异常处理------最佳方案。让程序底图方法内存异常的时候,调拨。
15.5 撤销缺氧物理存储器及释放区域
要撤销调拨给区域的物理存储器,或是释放地址空间中的一整块区域,可以调用VirtualFree函数。如果进程不再需要访问区域中的物理存储器,那么我们只需要调用VirtaulFree一次,就能够释放整个区域以及调拨给该区域的物理存储。我们必须传MEM_RELEASE给fdwFreeType来告诉系统撤销调拨给该区域的所有物理存储器,并释放区域。如果想要撤销区域的一部分,我们只需要给pvAddress传地址,然后dwSize传大小,fdwFreeType传MEM_DECOMMIT就行
15.5.1何时撤销调拨物理存储器
15.6 改变保护属性
15.7 改变物理存储器的内容
当我们修改物理存储页中的内容时,系统会尽量把改动保持在内存中。但是,当应用程序在运行的时候,系统可能需要从exe文件,或dll文件或页交换文件中载入新的页面到内存里。为了满足最近的载入请求,系统会在内存查找可用的页面,如果找到的页面已经被修改过,那么系统还必须将它们换出到页交换文件中。
Windows还提供了一种特性,一路使得应用程序能够提高中自身的性能-----这项特性就是重置物理存储器。重置物理存储器的意思是,我们告诉系统一个或几个物理存储页中的数据没有被修改过。如果系统正在查找一页闲置内存并且找到了一个修改过的页面,那么系统必须把该内存写入到页交换文件中。这个操作比较慢,会影响性能。对大多数应用程序来说,我们都希望系统把修改后的页面保存到页交换文件中。
但是,有些应用程序只需要在一小段时间内使用存储器,之后也不需要保留存储器中的内容。为了提高性能,应用程序可以告诉系统不要在页交换文件中保存指定存储器。这基本上是应用程序用来告诉系统一个页面未被修改过的一种方法。因此,如果系统决定将一个内存页挪作他用,好么它不会将页面内容保存到页交换文件中,这样提高了性能。为了重置存储器,应用程序应该调用VirtualAlloc函数,并在第三个参数中传MEM_RESET标志。
15.8 地址窗口扩展
地址窗口扩展特性:
1.允许应用程序以一种特殊的方式分配内存,操作系统保证不会将以这种方式分配的内存换出到磁盘上
2.允许应用程序访问比进程地址空间还要多的内存。
基本上 AWE提供了一种方式,可以让应用程序分配一块或多块内存。当一开始分配时,在进程的地址空间中是看不见这些内存块的应用程序然后预订地址空间区域,这就是地址窗口。应用程序然后调用一个函数,每调用一次把一块内存指定到该地址窗口。把内存块指定到地址窗口是非常快的。
//1.预订1MB地址空间 ULONG_PTR ulRAMBytes = 1024 * 1024; PVOID pvWindow = VirtualAlloc(NULL,ulRAMBytes, MEM_RESERVE | MEM_PHYSICAL ,PAGE_READWRITE); //2.得到当前平台的一个页面的大小 SYSTEM_INFO sinf; GetSystemInfo(&sinf); //3.计算需要多少内存页 ULONG_PTR ulRAMPages = (ulRAMBytes + sinf.dwPageSize - 1) / sinf.dwPageSize; //4.分配内存页数组、 ULONG_PTR * aRAMPages = (ULONG_PTR * ) new ULONG_PTR[ulRAMPages]; //5.分配物理存储器 AllocateUserPhysicalPages(GetCurrentProcess(), &ulRAMPages, aRAMPages); //6. 指定内存块指定给地址窗口 MapUserPhysicalPages(pvWindow, ulRAMPages, aRAMPages); //7.使用内存... //8.释放内存页块 FreeUserPhysicalPages(GetCurrentProcess(), &ulRAMPages, aRAMPages); VirtualFree(pvWindow,0,MEM_RELEASE); delete [] aRAMPages;