(轉)讓32位應用程序不再為2G內存限制苦惱


轉載自: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位平台。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM