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

轉載自:http://blog.csdn.net/jerjupiter/article/details/4577083數據庫

 

最近在作個程序,雖然是小型程序,可是使用的內存量卻很大,動輒達到10G。在64位系統上能夠輕鬆實現,無奈我是基於32位的系統進行開發,程序還沒跑起來就已經被終止了。  windows

試過不少辦法,包括文件內存映射等,效率不高,並且因爲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以上內存的目的。post

若是沒有打開PAE,系統只能認出3G的內存,最多能夠再多0.5G不到,這樣即便使用AWE,因爲系統和其餘應用程序已經佔去了一部份內存,剩下的內存或許也只有2G多一點了,沒什麼太大提升。只有當系統認出了4G以上的內存,AWE才能發揮它真正的做用。測試

下面咱們看看windows中給出的有關AWE的API函數,它們都定義在winbase.h中。ui

#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位平臺。

相關文章
相關標籤/搜索