ITPub博客

首页 > 应用开发 > IT综合 > Windows内核编程学习笔记之六“在应用程序中使用虚拟内存”

Windows内核编程学习笔记之六“在应用程序中使用虚拟内存”

IT综合 作者:CPlusPlusArjun 时间:2017-12-26 16:41:08 0 删除 编辑

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/ ,如需转载,请注明出处,否则将追究法律责任。

上一篇: 没有了~
下一篇: 没有了~
请登录后发表评论 登录
全部评论

注册时间:2017-12-26

  • 博文量
    1
  • 访问量
    1005