讓32位應用程式不再為2G記憶體限制苦惱
最近在做個程式,雖然是小型程式,但是使用的記憶體量卻很大,動輒達到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和未開啟PAE時系統識別出的記憶體容量區別。
圖一.開啟PAE
圖二.關閉PAE
如果沒有開啟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位平臺。