Windows核心编程笔记(十五)应用程序中使用虚拟内存

本文介绍了Windows系统下内存管理的几种方式,包括虚拟内存、内存映射文件和堆,并详细讲解了如何使用VirtualAlloc等API来预定地址空间、调拨物理存储器、改变页面保护属性等高级操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

windows 提供了三种机制来对内存进行操作


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

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

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



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


LPVOID WINAPI VirtualAlloc(
  __in_opt  LPVOID lpAddress,
  __in      SIZE_T dwSize,
  __in      DWORD flAllocationType,
  __in      DWORD flProtect
);



1. 预订地址空间区域

virtualalloc  地址传入null,等于告诉系统自动找一块闲置区域但是不保证从上到下分配或从下到上分配

如果打算预订一块区域并且用很长时间,那么我们希望系统从尽可能高的内存分配地址,避免引起内存碎片

需要给flAllocationType 指定MEM_RESERVE | MEM_TOP_DOWN


在预定地址空间时最小的分配粒度是64KB,同时大小为系统定义的页面小大的倍数。

可以测试来验证


int main(int argc, char **agrv)
{
    LPVOID pAddr = VirtualAlloc(NULL,1024,MEM_RESERVE,PAGE_READWRITE);
    VirtualAlloc(pAddr,1024,MEM_COMMIT,PAGE_READWRITE);

    int *pValue = reinterpret_cast<int*> (pAddr+2048);
    *pValue = 100;

    if( !(reinterpret_cast<DWORD>(pAddr)%(1024*64)) )
    {
        cout<<"Allocation granularity is 64 KB"<<endl;
    }
    return 0;
}



2. 给区域调拨物理存储器

系统会从页交换文件或物理内存中调拨存储器给区域

需要给flAllocationType 指定MEM_COMMIT



3. 取或则同时预定和调拨

   给flAllocationType 指定MEM_COMMIT | MEM_RESERVE


4. 何时调拨物理存储器

虚拟内存,既能方便享受数组方法所带来的快速,又能节省存储器,就像使用链表一样

(1)预定足够大的空间,只预定不调拨不会消耗物理存储器

(2) 在对数组元素访问时使用SEH异常,在异常处理时调拨物理内存

(3)设置..结构成员


5. 撤销调拨物理存储器及释放

BOOL WINAPI VirtualFree(
  __in  LPVOID lpAddress,
  __in  SIZE_T dwSize,
  __in  DWORD dwFreeType

);


当释放整个区域时 dwFreeType 设置为MEM_RELEASE,lpAddress设置为区域的起始地址,dwSize设置为0,


当释放区域中已调拨的某部分页面时,dwFreeType设置为 MEM_DECOMMIT,撤销已调拨物理内存也是基于页面粒度的


6.改变保护属性

BOOL WINAPI VirtualProtect(
  __in   LPVOID lpAddress,
  __in   SIZE_T dwSize,
  __in   DWORD flNewProtect,
  __out  PDWORD lpflOldProtect
);
同样设置保护属性的时候也使用系统定义的页面粒度


7.重置物理存储器的内容


当我们对一些已经调拨物理存储的页面的内容不在需要的时候,可以通过给VirtualAlloc的flAllocationType指定为MEM_RESET,来将页面设置为重置,这样当系统需要空闲页面时候可以将这些页面调拨给需要的进程,而不将内容倒换进磁盘分页文件中,当重新写入或者读取这部分地址的时候,这些地址被映射成新的页面。


通过设置页面重置,可以有效提高应用程序的性能。


8. 地址窗口扩展


虽然Windows的32位版本可以支持多达128GB物理内存,但每个32位用户进程在默认情况下只有2GB虚拟地址空间(在Boot.ini中使用/3GB和/USERVA开关时,此限制值可配置为3GB)。

为了允许一个32位进程分配和访问更多的物理内存,突破这一受限地址空间所能表达的内存范围,Windows提供了一组函数,称为地址窗口扩展(AWE , Address  Windowing  Extensions)。例如,在一个具有8GB物理内存Windows  2000  Advanced  Server系统上,一个数据库服务器应用程序有可能使用AWE来分配和使用6GB内存作为数据库缓存。




AWE函数分配和使用内存:


1.分配所要使用的物理内存。
2.创建一个虚拟地址空间区域,把它当作一个窗口来映射物理内存的多个
3.将物理内存的视图映射到窗口中。


一个应用程序想要分配物理内存,可以调用Windows函数
AllocateUserPages(这个函数要求“lock pages in memory[将页面锁在内存中]”用户权限)。然后,应用程序使用VirtualAlloc函数以及MEM_PHYSICAL标志,在进程地址空间的私有部分创建一个窗口,该窗口被映射至原先分配
的物理内存中的全部或一部分。之后,AWE分配的内存几乎可以与所有的Windows API一起使用。

如果一个应用程序在它的地址空间中创建一个256MB的窗口,并且分配4GB的物理内存(当然系统物理内存要超过4GB),此应用程序就可以使用MapUserPhysicalPages或MapUserPhysicalPagesScatter函数,将4GB物理内存映射到256MB窗口中,从而访问此4GB物理内存中的任何部分。应用程序的虚拟地址空间大小决定了该应用程序通过一次映射可以访问的物理内存的数量。




Windows的所有版本都有AWE功能,而且不管一个系统有多少物理内存,AWE功能都可以使用。当然,在超过2GB物理内存的系统上,AWE才是最有用的。




另一种用途是出于安全性考虑:AWE内存永远不会被换出,AWE内存中的数据不会在页面文件中有拷贝,从而避免坏蛋将系统重新引导到另一个操作系统中,通过检查页面文件来获取数据。


对于通过AWE函数分配和映射的内存,有以下限制:

1.进程之间不能共享页面;
2.同样的物理页面不能被映射至同一进程的多个虚拟地址;
3.老版本的Windows中,对页面的保护仅限于读/写;在Windows Server 




2003 Service Pack1及以后的版本中,也支持不可访问和只读模式。


下面是MSDN提高的示例代码:



#define _WIN32_WINNT 0x0600
#include <windows.h>
#include <stdio.h>



#define MEMORY_REQUESTED 1024*1024 // request a megabyte

BOOL
LoggedSetLockPagesPrivilege ( HANDLE hProcess,
                              BOOL bEnable);

int _cdecl main()
{
  BOOL bResult;                   // generic Boolean value
  ULONG_PTR NumberOfPages;        // number of pages to request
  ULONG_PTR NumberOfPagesInitial; // initial number of pages requested
  ULONG_PTR *aPFNs;               // page info; holds opaque data
  PVOID lpMemReserved;            // AWE window
  SYSTEM_INFO sSysInfo;           // useful system information
  int PFNArraySize;               // memory to request for PFN array

  GetSystemInfo(&sSysInfo);  // fill the system information structure

  printf("This computer has page size %d.\n", sSysInfo.dwPageSize);

  // Calculate the number of pages of memory to request.

  NumberOfPages = MEMORY_REQUESTED/sSysInfo.dwPageSize;
  printf ("Requesting %d pages of memory.\n", NumberOfPages);

  // Calculate the size of the user PFN array.

  PFNArraySize = NumberOfPages * sizeof (ULONG_PTR);

  printf ("Requesting a PFN array of %d bytes.\n", PFNArraySize);

  aPFNs = (ULONG_PTR *) HeapAlloc(GetProcessHeap(), 0, PFNArraySize);

  if (aPFNs == NULL)
  {
    printf ("Failed to allocate on heap.\n");
    return 0;
  }

  // Enable the privilege.

  if( ! LoggedSetLockPagesPrivilege( GetCurrentProcess(), TRUE ) )
  {
    return 0;
  }

  // Allocate the physical memory.

  NumberOfPagesInitial = NumberOfPages;
  bResult = AllocateUserPhysicalPages( GetCurrentProcess(),
                                       &NumberOfPages,
                                       aPFNs );

  if( bResult != TRUE )
  {
    printf("Cannot allocate physical pages (%u)\n", GetLastError() );
    return 0;
  }

  if( NumberOfPagesInitial != NumberOfPages )
  {
    printf("Allocated only %p pages.\n", NumberOfPages );
    return 0;
  }

  // Reserve the virtual memory.

  lpMemReserved = VirtualAlloc( NULL,
                                MEMORY_REQUESTED,
                                MEM_RESERVE | MEM_PHYSICAL,
                                PAGE_READWRITE );

  if( lpMemReserved == NULL )
  {
    printf("Cannot reserve memory.\n");
    return 0;
  }

  // Map the physical memory into the window.

  bResult = MapUserPhysicalPages( lpMemReserved,
                                  NumberOfPages,
                                  aPFNs );

  if( bResult != TRUE )
  {
    printf("MapUserPhysicalPages failed (%u)\n", GetLastError() );
    return 0;
  }

  // unmap

  bResult = MapUserPhysicalPages( lpMemReserved,
                                  NumberOfPages,
                                  NULL );

  if( bResult != TRUE )
  {
    printf("MapUserPhysicalPages failed (%u)\n", GetLastError() );
    return 0;
  }

  // Free the physical pages.

  bResult = FreeUserPhysicalPages( GetCurrentProcess(),
                                   &NumberOfPages,
                                   aPFNs );

  if( bResult != TRUE )
  {
    printf("Cannot free physical pages, error %u.\n", GetLastError());
    return 0;
  }

  // Free virtual memory.

  bResult = VirtualFree( lpMemReserved,
                         0,
                         MEM_RELEASE );

  // Release the aPFNs array.

  bResult = HeapFree(GetProcessHeap(), 0, aPFNs);

  if( bResult != TRUE )
  {
      printf("Call to HeapFree has failed (%u)\n", GetLastError() );
  }

}

/*****************************************************************
   LoggedSetLockPagesPrivilege: a function to obtain or
   release the privilege of locking physical pages.

   Inputs:

       HANDLE hProcess: Handle for the process for which the
       privilege is needed

       BOOL bEnable: Enable (TRUE) or disable?

   Return value: TRUE indicates success, FALSE failure.

*****************************************************************/
BOOL
LoggedSetLockPagesPrivilege ( HANDLE hProcess,
                              BOOL bEnable)
{
  struct {
    DWORD Count;
    LUID_AND_ATTRIBUTES Privilege [1];
  } Info;

  HANDLE Token;
  BOOL Result;

  // Open the token.

  Result = OpenProcessToken ( hProcess,
                              TOKEN_ADJUST_PRIVILEGES,
                              & Token);

  if( Result != TRUE )
  {
    printf( "Cannot open process token.\n" );
    return FALSE;
  }

  // Enable or disable?

  Info.Count = 1;
  if( bEnable )
  {
    Info.Privilege[0].Attributes = SE_PRIVILEGE_ENABLED;
  }
  else
  {
    Info.Privilege[0].Attributes = 0;
  }

  // Get the LUID.

  Result = LookupPrivilegeValue ( NULL,
                                  SE_LOCK_MEMORY_NAME,
                                  &(Info.Privilege[0].Luid));

  if( Result != TRUE )
  {
    printf( "Cannot get privilege for %s.\n", SE_LOCK_MEMORY_NAME );
    return FALSE;
  }

  // Adjust the privilege.

  Result = AdjustTokenPrivileges ( Token, FALSE,
                                   (PTOKEN_PRIVILEGES) &Info,
                                   0, NULL, NULL);

  // Check the result.

  if( Result != TRUE )
  {
    printf ("Cannot adjust token privileges (%u)\n", GetLastError() );
    return FALSE;
  }
  else
  {
    if( GetLastError() != ERROR_SUCCESS )
    {
      printf ("Cannot enable the SE_LOCK_MEMORY_NAME privilege; ");
      printf ("please check the local policy.\n");
      return FALSE;
    }
  }

  CloseHandle( Token );

  return TRUE;
}






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值