Windows核心编程(七)

十五、在应用程序中使用虚拟内存

Microsoft Windows提花了以下三种机制来对内存进行操控。

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

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

3)堆 最适合用来管理大量的小型对象。

Windows提供了一些用来操控虚拟内存的函数,我们可以通过这些函数直接预订地址空间区域,给区域调拨(来自页交换文件的)物理存储器,以及根据自己的需要来设置页面的保护属性。

调用VirtualAlloc函数来预订进程中的地址空间区域:

LPVOID WINAPI VirtualAlloc(
  __in          LPVOID lpAddress, // 从哪儿开始预订,通常传NULL让系统自动找一块闲置区域。会被向上取整到分配粒度的整数倍。
  __in          SIZE_T dwSize,    // 想要预订的区域大小,以字节为单位,会被向上取整到页面大小的整数倍。
  __in          DWORD flAllocationType,  //是预订还是调拨物理存储器
  __in          DWORD flProtect          //保护属性。
);
在预订了区域之后,需要给区域调拨物理存储器,这样才能访问其中的内存地址。系统会从页交换文件中调拨物理存储器给区域。在调拨物理存储器时,起始地址始终都是页面大小的整数倍,整个大小也是页面大小的整数倍。

为了调拨物理存储器,必须再次调用VirtualAlloc,但fdwAllocationType值应该为MEM_COMMIT。如果想同时预订和调拨物理存储器,这个参数可以使用EME_RESERVE | MEM_COMMIT。

Windows还提供了大页面支持,可以在处理大块内存的时候提升性能。这种情况下,系统在分配内存时,不再使用GetSystemInfo函数返回的SYSTEM_INFO结构中的dwPageSize字段作为分配粒度,而是使用下面的函数返回的大页面分配粒度:

SIZE_T GetLargePageMinimum();

如果CPU不支持大页面分配,那么该函数会返回0。如果要分配的内存大于GetLargePageMinimum的返回值,就可以使用大页面支持。在调用VirtualAlloc时将MEM_LARGE_PAGE标志与fdwAllocationType按位或起来,同时必须满足以下三个条件。

1)要分配的内存块的大小(即dwSize的值)必须是GetLargePageMinimum函数的返回值的整数倍 。

2)在调用VirtualAlloc时,必须加入MEM_RESERVE | MEM_COMMIT标志按位或起来,即必须同时预订和调拨内存。

3)在用VitualAlloc分配内存时必须传PAGE_READWRITE保护发生给fdwProtect参数。

Windows认为用MEM_LARGE_PAGE标志分配得到的内存是不可换页的(unpagable):必须驻留在内存中。因此需要调用方具有内存中锁定页面(Lock Pages In Memory)的用户权限,否则函数调用会失败。默认情况下,并不是任何用户或用户组都有这个权限。需要在管理工具的本地安全策略中选择“内存中锁定页面”属性,加入用户或用户组中。

要撤销调拨给区域的物理存储器,或是释放地址空间中的一整块区域,可以调用VirtualFree函数。

要改变一个内存页面的保护属性,调用VirtualProtect函数。


十六、线程栈

当系统创建线程时,会为线程预订一块地址空间区域(每个线程都有自己的栈),并给区域调拨一些物理存储器。默认情况下,系统会预订1MB的地址空间并调拨两个页面的存储器。可以通过修改编译链接选项来改变默认值。

在构建应用程序时,链接器会把想要的栈的大小写入到.exe或.dll文件的PE文件头中。当系统创建线程栈时,会根据PE文件头的大小来预订地址空间区域。但在调用CreateThread或_beginthreadex函数时,开发人员也可以指定需要在一开始就调拨的存储器数量(这两个函数都有一个参数用来指定一开始要调拨给线程栈的地址空间区域的存储器的大小)。下图为一个页面大小为4KB的机器上线程线的地址空间区域(基地址为0x08000000)。


在预订地址空间区域后,系统会给区域顶部(即地址最高)的两个页面调拨物理存储器。在让线程开始执行之前,系统会把线程栈的指针指向区域顶部的那个页面的末尾(接近于0x08100000),这个页面是线程开始使用栈的地方。区域顶部往下的第二个页面被称为防护页面(guard page)。随着线程需要越来越多的空间,线程试图访问防护页面中的内存,系统会得到通知。这时系统会先给防护页面的下面的那个页面调拨存储器,接着去除当前防护页面的PAGE_GUARD保护发生标志,然后给刚调拨的存储页指定PAGE_GUARD保护属性标志。该项技术使得系统能够在线程需要的时候才增大栈存储器的大小。

系统 永远不会给区域底部的那个页面调拨存储器,否则,系统无法捕捉到线程对栈外区域的访问。如要栈的增长越过了所预订的区域,那么线程就会覆盖进程地址空间中的其他数据。

当系统去除地址为0x08002000的页面的PAGE_GUARD保护属性标志,然后给0x08001000调拨物理存储器的时候,不会给0x08001000指定防护属性。这意味着栈的地址空间区域已经放满了它所能容纳得下的所有存储器。它系统给地址0x08001000的页面调拨物理存储器的时候,会执行一个额外的操作——抛出EXCEPTION_STACK_OVERFLOW异常,通过使用结构化异常处理,程序能够从这一异常情况下恢复。但如果在引发栈溢出异常后继续使用栈,那么它会用尽0x08001000的页面中的内存,并试图访问地址为0x08000000的页面的内存,但它是未调拨的,系统会抛出违规异常,会提出结束进程的对话框。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值