在应用程序中使用虚拟内存——Windows核心编程学习手札之十五

在应用程序中使用虚拟内存

——Windows核心编程学习手札之十五

Windows提供了3种进行内存管理的方法:

1)  虚拟内存,最适合用来管理大量对象或结构数组;

2)  内存映射文件,最适合用来管理大型数据流(通常来自文件)以及在单个计算机上运行的多个进程之间共享数据;

3)  内存堆栈,最适合用来管理大量的小对象;

用于管理虚拟内存的函数可以用来直接保留一个地址空间区域,将物理存储器(来自页文件)提交给该区域,并且可以设置你自己的保护属性。

通过调用VirtualAlloc函数,可以在进程的地址空间中保留一个区域:

PVOID VirtualAlloc(

                 PVOID pvAddress,

                 SIZE_T dwSize,

                 DWORD fdwAllocationType,

                 DWORD fdwProtect);

第一个参数pvAddress包含一个内存地址,用于设定想让系统将地址空间保留在什么地址,多数情况下,该参数传递NULL,用于告诉VirtualAlloc,保存一个空间地址区域的记录的系统应该将区域保留在它认为合适的任何地方。系统可以从进程的地址空间的任何位置来保留一个区域,因为不能保证系统可以从地址空间的底部向上或从上面向底部来分配各个区域,使用MEM_TOP_DOWN标志来说明该分配方式。

分配内存时,操作系统寻找一个大小满足需要的内存块,并返回内存块的地址,由于每个进程有自己的地址空间,可以设定一个基本内存地址,在这个地址上让操作系统保留地址空间区域。例如,将一个从50MB开始的区域保留在进程的地址空间中,传递pvAddress52428800(50*1024*1024),如果该内存地址有一个足够大的空闲区域满足你的要求,那系统就保留这个区域并返回地址,如果在特定的地址上不存在空闲区域,或者空闲区域不够大,那系统就不满足需求,VirtualAlloc函数返回NULL。注意,为pvAddress参数传递的任何地址必须始终位于进程的用户方式分区中,否则对VirtualAlloc函数的调用就会失败,导致其返回NULL。地址空间区域总是按照分配粒度的边界来保留(迄今为止所有的Windows环境下都是64KB),因此,如果试图在进程地址空间中保留一个从19668992(300*65356+8192)这个地址开始的区域,系统就会将这个地址保留为64KB的倍数,即19660800(300*65356)开始的区域。如果VirtualAlloc函数满足了需求,就返回保留区域的基地址,如果传递一个指定的地址作为VirtualAllocpvAddress参数,那么该返回值与传递给VirtualAlloc的值相同,并取为64KB的整数倍。

VirtualAlloc函数的第二个参数是dwSize,用于设定想保留的区域大小(以字节为单位),系统保留的区域必须是CPU页面大小的倍数,如试图保留一个跨越62KB的区域,结果就会在使用4KB/8KB16KB页面的计算机上产生一个跨越64KB的区域。

VirtualAlloc函数的第三个参数是fdwAllocationType,告诉系统想保留一个区域还是提交物理存储器(VirtualAlloc函数也可以用来提交物理存储器),若要保留一个地址空间区域,传递MEM_RESERVE标识符作为fdwAllocationType参数的值。如果保留区域预计在很长时间内不会被释放,那可以在尽可能高的内存地址上保留该区域,这样,该区域就不会从进程地址空间的中间位置上进行保留,因此这个位置可能导致区域分成碎片,如果想让系统在最高内存地址上保留一个区域,需为pvAddress参数和fdwAllocationType参数传递NULL,还必须逐位使用ORMEM_TOP_DOWN标志和MEM_RESERVE标志连接起来。

VirtualAlloca最后一个参数是fdwProtect,用于指明应该赋予该地址空间区域的保护属性。与该区域相关联的保护属性对映射到该区域的已提交内存没有影响,无论赋予区域的保护属性是社呢,如果没有提交任何物理存储器,那访问该范围中的内存地址的任何企图都将导致该线程引发一个访问违规。

当保留一个区域后,必须将物理存储器提交给该区域,然后才能访问该区域中包含的内存地址,系统从它的页文件中将已提交的物理存储器分配给一个区域,物理存储器总是按页面边界和页面大小的块来提交的。若要提交物理存储器,须再次调用VirtualAlloc函数,设置参数fdwAllocationTypeMEM_COMMIT,传递的页面保护属性一般与调用VirtualAlloc来保留区域时使用的保护属性相同(大多数情况下是PAGE_READWRITE)。在已保留的区域中,须告诉VirtualAlloc函数,要将物理存储器提交到那里以及提交多大物理存储空间,实现这一点,需要在pvAddress参数中设定需要的内存地址,并在dwSize参数上设定物理存储器的大小。

提交物理存储器的例子:应用程序在X86CPU上运行,保留了一个从地址5242880开始的512KB区域,现在将物理存储器提交给已保留区域的6KB部分,从2KB的地方开始,直到已保留区域的地址空间。可调用带有MEM_COMMIT标志的VirtualAlloc函数:

VirtualAlloc((PVOID)(5242880+(2*1024)),6*1024,MEM_COMMIT,PAGE_READWRITE);

例子中,系统必须提交8KB的物理存储器,地址范围从52428805251071(5242880+8KB-1),这两个提交的页面都拥有PAGE_READWRITE保护属性,保护属性在整个页面单位内生效,同一个内存页面的不同部分不能使用不同的保护属性,但在不同区域中的一个页面可以使用两种以上保护属性。

若要回收映射到一个区域的物理存储器,或者释放这个地址空间区域,可调用VirtualFree函数:

BOOL VirtualFree(

               LPVOID pvAddress,

               SIZE_T dwSize,

               DWORD fdwFreeType);

当进程不再访问区域中的物理存储器,可以释放整个保留的区域和所有提交给该区域的物理存储器,方法是一次调用VirtualFree函数。pvAddress是释放区域的基地址,与该区域被保留时VirtualAlloc函数返回的地址相同,系统知道在特定内存地址上的区域大小,因此dwSize参数可以为零,实际,该参数必须传递零,否则调用VirtualFree失败,对最后一个参数fdwFreeType,必须传递MEM_RELEASE,以告诉系统将所有映射的物理存储器提给该区域并释放该区域,当释放一个区域时,必须释放该区域保留的所有地址空间。如不想保留128KB的区域,不能只释放64KB。当想要从一个区域回收某些物理存储器,但是却不释放该区域,设置参数fdwFreeTypeMEM_DECOMMIT标志。回收时也按照页面的分配粒度来进行,设定一个页面中间的一个内存地址就可以回收整个页面,如果pvAddress+dwSize的值位于一个页面的中间,那包含该地址的整个页面将被回收,因此,位于pvAddresspvAddress+dwSize范围内的所有页面均被回收。

 

你可能感兴趣的:(Windows)