Win32提供三种方式控制内存:
(1)虚拟内存:最适合于管理大的对象和结构
一、在地址空间中保留区域:通过调用VirtualAloc函数实现在进程地址空间中保留一个区域。
[@more@]LPVOID VirtualAlloc(LPVOID lpAddress,DWORD cbSize,DWORD fdwAllocationType,DWORD fdwProtect);
下面对各个参数进程说明:
lpAddress:是一个地址,系统将在这里保留地址空间。大多数时候这个值是NULL,为NULL的意思是“系统可以在它认为合适的地方保留区域”,此时系统可能在进程地址空间的任意处保留区域。在过去分配内存时,操作系统会找到一块满足要求的内存块,分配该块,然后返回它的地址。但是因为每一个Win32进程都有它自己的地址空间,因此就可以指定想要操作系统保留的区域的基本内存地址了。举个例子:假设想要从进程的地址空间为50MB偏移量开始保留一个区域,这时要把lpAddress参数设置为52428800(50*1024*1024),如果该地址空间处有一块满足请求的空闲区域,系统就会保留所要的区间,然后返回。如果该处没有空闲区域,或者说空闲区域不够大,系统就不能满足请求,VirtualAlloc就返回NULL。还有一点需要说明的是:“区域总是保留在一个分配单元的边界上”,这是什么意思呢?就是说如果lpAddress的值不是边界值的整数倍,那么系统就会将lpAddress的值舍入到“边界值”的整数倍。(边界值:对于现在的所有Win32系统来讲都是64KB)
cbSize:指定想要保留区域的字节大小。(注:系统保留的区域字节大小必须是页面大小的整数倍,一般来讲页面的大小为4KB或8KB)
fdwAllocationType:说明函数VirtualAlloc是执行“保留区域”还是“提交存储”。
要保留区域的话,该参数必须为:MEM_RESERVE。另注一个知识点:如果要想使保留的区域保留的时间很长,那么可以将本参数设置为NULL,还必须把标志MEM_RESERVE和MEM_TOP_DOWN按比特相或。
fdwProtect:应赋给该区域的保护属性。注:该区域的“提交物理存储的保护属性”应当与“保留区域的保护属性相同”。这样做会使系统内部记录的维护更有效。下面说明各种保护属性:PAGE_NOACCESS、PAGE_READWRITE、PAGE_READONLY、PAGE_EXECUTE、PAGE_EXECUTE_READ、PAGE_EXECUTE_READWRITE。还有几个只能在提交存储时使用:PAGE_GUARD、PAGE_NOCHANGE。保留区域不能使用的有:PAGE_WRITECOPY、PAGE_EXECUTE_WRITECOPY,否则VirtualAlloc将返回NULL。
二、在保留区域中提交存储:在保留区域后,如果要使用其中的内存,还要向其中提交物理存储。系统从硬盘上的页面文件分配要提交的物理存储。物理存储以页为单位提交到页面边界上。要提交物理存储还要使用VirtualAlloc函数,这时要把它的参数fdwAllocationType设置为MEM_COMMIT,此外还必须告诉VirtualAlloc从何处开始提交物理存储和要提交多少,注意:不必一次为整个区域提交物理存储。下面举例说明:
假设在一台Intel x86 CPU上执行应用程序,并且已经从地址5242880开始保留了一个512KB的区域。现在要做的是:“提交一个该已经保留区域中偏移量为2KB,大小为6KB的存储”,实现如下:
VirtualAlloc((PVOID)(5242880+(2*1024)),6*1024,MEM_COMMIT,PAGE_READWRITE);
这时系统肯定会提交8KB的物理存储,因为提交的物理存储必须是页的整数倍。
三、何时提交物理存储:最好的方法是使用“结构化异常处理(SHE,Structured Except:on Handing)”,简单说:就是先建立一个有异常处理的应用程序,然后,当试图访问没有提交的内存时,系统就会把问题提交给应用程序。应用程序然后提交内存,再让系统重试引起异常的指令。
四、释放区域和物理存储:使用函数VirtualFree函数来释放区域和物理存储。
BOOL VirtualFree(LPVOID lpAddress,DWORD cbSize,DWORD dwFreeType);
使用该函数需要注意两点:
1、释放区域及物理存储的大小都是页面大小的整数倍。
2、如果在执行此函数时lpAddress的值为区域的基本地址且dwFreeType为MEM_RESERVE并且cbSize为0,那么该区域将全部被释放。如果想释放区域中的部分区域时,可以将lpAddress设置为要被释放的第一页的地址,dwFreeType为MEM_DECOMMIT。
五、修改保护属性:为什么要修改保护属性呢?这里有一个例子:假设你开发了一段代码来管理一个链表,链表中的结点被保存在一个保留区域中。你可设计一些处理链表的函数,在每一个函数的开始把链表的保护属性设置为PAGE_READWRITE,在函数返回之前把属性设置为PAGE_NOACCESS。这样做就可以保护链表不被程序中的其它隐藏的错误破坏,如果此时程序中的其它代码试图访问链表,就会产生访问错误。
使用VirtualProtect函数来改变内存中的页面的保护属性。
BOOL VirtualProtect(LPVOID lpAddress,DWORD dwSize,DWORD flNewProtect,PDWORD lpflOldProtect);
lpAddress:指向内存的基本地址。
dwSize:表示待修改的保护属性的字节数。
flNewProtect:可以为除了PAGE_WRITECOPY和PAGE_EXECUTE_WRITECOPY以外的其它PAGE_*标识。
lpflOldProtect:是一个DWORD类型的地址。
下面举例子说明:
DWORD someOldProtect;
VirtualProtect(lpRgnBase + (3*1024),2*1024,PAGE_NOACCESS,&someOldProtect);
六、在RAM中锁定物理存储:
重点提示:“提交物理存储”的实际意思就是:从系统的页面文件中分配空间。
另外还有一个要说明的是:当程序访问数据时,系统必须在页面文件中定位程序的物理存储,然后将其装入RAM。(目前为止我理解的“页面交换”就是:将页面文件中的程序装入到RAM这一过程。页面交换好像有好多种)
两个函数VirtualLock和VirtualUnLock。这两个Win32函数是用来锁定和解锁RAM中的线程的。
当进程中有线程在运行时,页面被锁定在RAM中。当进程中没有线程运行时,系统就可以把页面解锁,(注意:如果RAM中的空间足够的话,系统不会立即)将RAM中这些线程换回页面文件中的物理存储。当系统准备再次调用进程中的线程时,它再把想要锁定的页面装入RAM中。装完后系统就又可能让线程运行了。这样做的目的是“使进程的性能更好”。最好还是让系统来控制线程的加锁解锁,因为只有系统能更加明白,应用程序运行时进程是怎么工作的。
BOOL VirtualLock(LPVOID lpMem,DWORD cbMem);该函数锁定从lpMem开始,大小为cbMem字节的RAM。
使用该函数需要注意的几点:
1.要锁定的所有页必须是已经提交的物理存储。
2.VirtualLock不能锁定保护属性为PAGE_NOACCESS的分配的内存。
3.系统不允许一个进程锁定超过30页的存储。(这样做是为了避免一个进程比较大的影响系统的性能)
BOOL VirtualUnLock(LPVOID lpMem,DWORD cbMem);解锁从lpMem开始,大小为cbMem字节的内存。
在Windows95中这两个函数未完全实现,调也没有用。在Windows NT里可以直接使用VirtualUnLock来解锁内存。例如:知道内存中的某个函数或数据在一段时间内不使用的话,就可以使用VirtualUnLock来解锁这些函数和数据。
七、重设物理存储中的内容:看一个例子来说明物理存储中内容是如何改变的:
const char szAppName[] = “测试应用”;
const char szTestData[] = “测试数据”;
PVOID pvData;
MEMORYSTATUS mst;
MEMORY_BASIC_INOFRMATION mbi;
//首先提交物理存储
pvData = VirtualAlloc(NULL,1024,MEM_RESERVE|MEM_COMMIT,PAGE_READWRITE);
strcpy(pvData,szTestData);//修改新提交的物理存储的页面内容
VirtualQuery(pvData,&mbi,sizeof(mbi));
VirtualAlloc(pvData,mbi.RegionSize,MEM_RESET,PAGE_READWRITE);//把修改的物理存储提交。
……
八、线程的栈:
缺省时系统保留1MB的区域,提交2页的物理存储。(注:这两个缺省值可以在链接程序时,通过向链接器指定/STACK选项来改变,/STACK:reserve ,commit)。注:线程的栈顶和栈底都被保留,不会被提交,这样做的目的是防止栈下溢和上溢。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/31514103/viewspace-2149320/ ,如需转载,请注明出处,否则将追究法律责任。