轉載自:http://blog.csdn.net/jerjupiter/article/details/4577083
最近在做個程序,雖然是小型程序,但是使用的內存量卻很大,動輒達到10G。在64位系統上可以輕松實現,無奈我是基於32位的系統進行開發,程序還沒跑起來就已經被終止了。
試過很多辦法,包括文件內存映射等,效率不高,而且由於32位應用程序的限制,可用的內存地址最高只能到0x7FFFFFFF,能調用的內存到2G就是極限了。最后好不容易找到了AWE(Address Windowing Extensions)。
AWE是Windows的內存管理功能的一組擴展,它允許應用程序獲取物理內存,然后將非分頁內存的視圖動態映射到32位地址空間。雖然32位地址空間限制為4GB,但是非分頁內存卻可以遠遠大於4GB。這使需要大量內存的應用程序(如大型數據庫系統)能使用的內存量遠遠大於32位地址空間所支持的內存量。
與AWE有關的函數在后面介紹。
為了使用大容量內存,除了要用到AWE外,還有一樣東西不能少,那就是PAE(Physical Address Extension)。PAE是基於x86的服務器的一種功能,它使運行Windows Server 2003,Enterprise Edition 和Windows Server 2003,Datacenter Edition 的計算機可以支持 4 GB 以上物理內存。物理地址擴展(PAE)允許將最多64 GB的物理內存用作常規的4 KB頁面,並擴展內核能使用的位數以將物理內存地址從 32擴展到36。
一般情況下,windows系統的PAE沒有生效,只有開啟了PAE后windows系統才可以識別出4G以上的內存。在使用boot.int的系統中,要啟動PAE必須在boot.ini中加入/PAE選項。在Windows Vista和Windows7中則必須修改內核文件,同時設置BCD啟動項。針對Vista系統和Win7系統可以使用Ready For 4GB這個軟件直接完成這一操作,具體方法見Ready For 4GB的軟件說明。以下就是一個開啟了/PAE選項的boot.ini文件示例:
[boot loader] timeout=30 default=multi(0)disk(0)rdisk(0)partition(1)WINDOWS [operating systems] multi(0)disk(0)rdisk(0)partition(1)WINDOWS="Windows Server 2003, Enterprise" /fastdetect /PAE
本文將以Windows 7旗艦版為例介紹如何在打開PAE的情況下使用AWE在程序中達到使用2G以上內存的目的。
如果沒有打開PAE,系統只能認出3G的內存,最多可以再多0.5G不到,這樣即使使用AWE,由於系統和其他應用程序已經占去了一部分內存,剩下的內存或許也只有2G多一點了,沒什么太大提高。只有當系統認出了4G以上的內存,AWE才能發揮它真正的作用。
下面我們看看windows中給出的有關AWE的API函數,它們都定義在winbase.h中。
#if (_WIN32_WINNT >= 0x0500)
//
// Very Large Memory API Subset
//
WINBASEAPI
BOOL
WINAPI
AllocateUserPhysicalPages(
__in HANDLE hProcess,
__inout PULONG_PTR NumberOfPages,
__out_ecount_part(*NumberOfPages, *NumberOfPages) PULONG_PTR PageArray
);
WINBASEAPI
BOOL
WINAPI
FreeUserPhysicalPages(
__in HANDLE hProcess,
__inout PULONG_PTR NumberOfPages,
__in_ecount(*NumberOfPages) PULONG_PTR PageArray
);
WINBASEAPI
BOOL
WINAPI
MapUserPhysicalPages(
__in PVOID VirtualAddress,
__in ULONG_PTR NumberOfPages,
__in_ecount_opt(NumberOfPages) PULONG_PTR PageArray
);
//...
#endif
從winbase.h中的定義可以看出,只有當你的系統版本大於或等於0x0500時,才能夠使用AWE。各個版本的_WIN32_WINNT值見下表,Windows 2000以下的版本不能使用AWE。
| Minimum system required |
Minimum value for _WIN32_WINNT and WINVER |
| Windows 7 |
0x0601 |
| Windows Server 2008 |
0x0600 |
| Windows Vista |
0x0600 |
| Windows Server 2003 with SP1, Windows XP with SP2 |
0x0502 |
| Windows Server 2003, Windows XP |
0x0501 |
| Windows 2000 |
0x0500 |
如果你的系統版本符合要求,但是編譯器在編譯加入了AWE API的代碼出錯,可以在程序頭文件中加入下面的代碼
#ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0501 #endif
下面簡要介紹一下每個API的功能。
BOOL WINAPI AllocateUserPhysicalPages( //分配物理內存頁,用於后面AWE的內存映射 __in HANDLE hProcess, //指定可以使用此函數分配的內存頁的進程 __inout PULONG_PTR NumberOfPages, //分配的內存頁數,頁的大小由系統決定 __out PULONG_PTR UserPfnArray //指向存儲分配內存頁幀成員的數組的指針 ); BOOL WINAPI FreeUserPhysicalPages( //釋放AllocateUserPhysicalPages函數分配的內存 __in HANDLE hProcess, //釋放此進程虛擬地址空間中的分配的內存頁 __inout PULONG_PTR NumberOfPages, //要釋放的內存頁數 __in PULONG_PTR UserPfnArray //指向存儲內存頁幀成員的數組的指針 ); BOOL WINAPI MapUserPhysicalPages( //將分配好的內存頁映射到指定的地址 __in PVOID lpAddress, //指向要重映射的內存區域的指針 __in ULONG_PTR NumberOfPages, //要映射的內存頁數 __in PULONG_PTR UserPfnArray //指向要映射的內存頁的指針 );
在看實例程序前還有一些設置需要做,需要對系統的本地安全策略進行設置。在win7中,打開“控制面板->系統和安全->管理工具->本地安全策略”,給“鎖定內存頁”添加當前用戶,然后退出,重啟(不重啟一般無法生效!)。
經過前面的准備(再啰嗦一次:確認自己的電腦裝有4G或4G以上的內存;開啟PAE,使系統認出4G或以上的內存;設置好本地安全策略),我們就可以通過下面的代碼來做個實驗了。
代碼是從MSDN中AWE的一個Example修改而來的,具體流程見代碼中的注釋,如果對該Example的源代碼有興趣可以參考MSDN。
#include "AWE_TEST.h"
#include <windows.h>
#include <stdio.h>
#define MEMORY_REQUESTED ((2*1024+512)*1024*1024) //申請2.5G內存,測試機上只有4G內存,而且系統是window7,比較占內存.申請3G容易失敗.
#define MEMORY_VIRTUAL 1024*1024*512 //申請長度0.5G的虛擬內存,即AWE窗口.
//檢測"鎖定內存頁"權限的函數
BOOL LoggedSetLockPagesPrivilege ( HANDLE hProcess, BOOL bEnable);
void _cdecl main()
{
BOOL bResult; // 通用bool變量
ULONG_PTR NumberOfPages; // 申請的內存頁數
ULONG_PTR NumberOfPagesInitial; // 初始的要申請的內存頁數
ULONG_PTR *aPFNs; // 頁信息,存儲獲取的內存頁成員
PVOID lpMemReserved; // AWE窗口
SYSTEM_INFO sSysInfo; // 系統信息
INT PFNArraySize; // PFN隊列所占的內存長度
GetSystemInfo(&sSysInfo); // 獲取系統信息
printf("This computer has page size %d./n", sSysInfo.dwPageSize);
//計算要申請的內存頁數.
NumberOfPages = MEMORY_REQUESTED/sSysInfo.dwPageSize;
printf ("Requesting %d pages of memory./n", NumberOfPages);
// 計算PFN隊列所占的內存長度
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;
}
// 開啟"鎖定內存頁"權限
if( ! LoggedSetLockPagesPrivilege( GetCurrentProcess(), TRUE ) )
{
return;
}
// 分配物理內存,長度2.5GB
NumberOfPagesInitial = NumberOfPages;
bResult = AllocateUserPhysicalPages( GetCurrentProcess(),
&NumberOfPages,
aPFNs );
if( bResult != TRUE )
{
printf("Cannot allocate physical pages (%u)/n", GetLastError() );
return;
}
if( NumberOfPagesInitial != NumberOfPages )
{
printf("Allocated only %p pages./n", NumberOfPages );
return;
}
// 保留長度0.5GB的虛擬內存塊(這個內存塊即AWE窗口)的地址
lpMemReserved = VirtualAlloc( NULL,
MEMORY_VIRTUAL,
MEM_RESERVE | MEM_PHYSICAL,
PAGE_READWRITE );
if( lpMemReserved == NULL )
{
printf("Cannot reserve memory./n");
return;
}
char *strTemp;
for (int i=0;i<5;i++)
{
// 把物理內存映射到窗口中來
// 分5次映射,每次映射0.5G物理內存到窗口中來.
// 注意,在整個過程中,lpMenReserved的值都是不變的
// 但是映射的實際物理內存卻是不同的
// 這段代碼將申請的2.5G物理內存分5段依次映射到窗口中來
// 並在每段的開頭寫入一串字符串.
bResult = MapUserPhysicalPages( lpMemReserved,
NumberOfPages/5,
aPFNs+NumberOfPages/5*i);
if( bResult != TRUE )
{
printf("MapUserPhysicalPages failed (%u)/n", GetLastError() );
return;
}
// 寫入字符串,雖然是寫入同一個虛存地址,
// 但是窗口映射的實際內存不同,所以是寫入了不同的內存塊中
strTemp=(char*)lpMemReserved;
sprintf(strTemp,"This is the %dth section!",i+1);
// 解除映射
bResult = MapUserPhysicalPages( lpMemReserved,
NumberOfPages/5,
NULL );
if( bResult != TRUE )
{
printf("MapUserPhysicalPages failed (%u)/n", GetLastError() );
return;
}
}
// 現在再從5段內存中讀出剛才寫入的字符串
for (int i=0;i<5;i++)
{
// 把物理內存映射到窗口中來
bResult = MapUserPhysicalPages( lpMemReserved,
NumberOfPages/5,
aPFNs+NumberOfPages/5*i);
if( bResult != TRUE )
{
printf("MapUserPhysicalPages failed (%u)/n", GetLastError() );
return;
}
// 將映射到窗口中的不同內存塊的字符串在屏幕中打印出來
strTemp=(char*)lpMemReserved;
printf("%s/n",strTemp);
// 解除映射
bResult = MapUserPhysicalPages( lpMemReserved,
NumberOfPages/5,
NULL );
if( bResult != TRUE )
{
printf("MapUserPhysicalPages failed (%u)/n", GetLastError() );
return;
}
}
// 釋放物理內存空間
bResult = FreeUserPhysicalPages( GetCurrentProcess(),
&NumberOfPages,
aPFNs );
if( bResult != TRUE )
{
printf("Cannot free physical pages, error %u./n", GetLastError());
return;
}
// 釋放虛擬內存地址
bResult = VirtualFree( lpMemReserved,
0,
MEM_RELEASE );
// 釋放PFN隊列空間
bResult = HeapFree(GetProcessHeap(), 0, aPFNs);
if( bResult != TRUE )
{
printf("Call to HeapFree has failed (%u)/n", GetLastError() );
}
}
/*****************************************************************
輸入:
HANDLE hProcess: 需要獲得權限的進程的句柄
BOOL bEnable: 啟用權限 (TRUE) 或 取消權限 (FALSE)?
返回值: TRUE 表示權限操作成功, FALSE 失敗.
*****************************************************************/
BOOL
LoggedSetLockPagesPrivilege ( HANDLE hProcess,
BOOL bEnable)
{
struct {
DWORD Count;
LUID_AND_ATTRIBUTES Privilege [1];
} Info;
HANDLE Token;
BOOL Result;
// 打開進程的安全信息
Result = OpenProcessToken ( hProcess,
TOKEN_ADJUST_PRIVILEGES,
& Token);
if( Result != TRUE )
{
printf( "Cannot open process token./n" );
return FALSE;
}
// 開啟 或 取消?
Info.Count = 1;
if( bEnable )
{
Info.Privilege[0].Attributes = SE_PRIVILEGE_ENABLED;
}
else
{
Info.Privilege[0].Attributes = 0;
}
// 獲得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;
}
// 修改權限
Result = AdjustTokenPrivileges ( Token, FALSE,
(PTOKEN_PRIVILEGES) &Info,
0, NULL, NULL);
// 檢查修改結果
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;
}
程序運行后,可以看出系統分頁的大小為4K,總共申請了655360個分頁,也就是2.5G。每個分頁成員占4字節,總共2621440字節。2.5G內存分成5段512M的塊,成功寫入了字符串並成功讀取。
在調試過程中,在執行了AllocateUserPhysicalPages函數后設置斷點,查看任務管理器,可以看出成功分配了物理內存后,實際物理內存被占用了2.5G,從而驗證了AWE的效果。
通過上述示例,我們成功的在32位系統中識別出了4G的內存,並且在32位程序中成功使用了超過2G的內存。借助PAE和AWE,即使在32位系統上,我們也能夠順利開發對內存消耗較大的應用程序,而不需要依賴於64位平台。
