zoukankan      html  css  js  c++  java
  • 让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文件示例:

    [xhtml] view plain copy
     
    1. [boot loader]  
    2. timeout=30  
    3. default=multi(0)disk(0)rdisk(0)partition(1)WINDOWS  
    4. [operating systems]  
    5. 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中。

    [cpp] view plain copy
     
    1. #if (_WIN32_WINNT >= 0x0500)  
    2. //  
    3. // Very Large Memory API Subset  
    4. //  
    5.   
    6. WINBASEAPI  
    7. BOOL  
    8. WINAPI  
    9. AllocateUserPhysicalPages(  
    10.     __in    HANDLE hProcess,  
    11.     __inout PULONG_PTR NumberOfPages,  
    12.     __out_ecount_part(*NumberOfPages, *NumberOfPages) PULONG_PTR PageArray  
    13.     );  
    14.   
    15. WINBASEAPI  
    16. BOOL  
    17. WINAPI  
    18. FreeUserPhysicalPages(  
    19.     __in    HANDLE hProcess,  
    20.     __inout PULONG_PTR NumberOfPages,  
    21.     __in_ecount(*NumberOfPages) PULONG_PTR PageArray  
    22.     );  
    23.   
    24. WINBASEAPI  
    25. BOOL  
    26. WINAPI  
    27. MapUserPhysicalPages(  
    28.     __in PVOID VirtualAddress,  
    29.     __in ULONG_PTR NumberOfPages,  
    30.     __in_ecount_opt(NumberOfPages) PULONG_PTR PageArray  
    31.     );  
    32. //...  
    33. #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的代码出错,可以在程序头文件中加入下面的代码。

    [cpp] view plain copy
     
    1. #ifndef _WIN32_WINNT  
    2. #define _WIN32_WINNT 0x0501  
    3. #endif    

        下面简要介绍一下每个API的功能。

    [cpp] view plain copy
     
    1. BOOL WINAPI AllocateUserPhysicalPages(  //分配物理内存页,用于后面AWE的内存映射  
    2.   __in     HANDLE hProcess,     //指定可以使用此函数分配的内存页的进程  
    3.   __inout  PULONG_PTR NumberOfPages,    //分配的内存页数,页的大小由系统决定  
    4.   __out    PULONG_PTR UserPfnArray  //指向存储分配内存页帧成员的数组的指针  
    5. );  
    6.   
    7. BOOL WINAPI FreeUserPhysicalPages(  //释放AllocateUserPhysicalPages函数分配的内存  
    8.   __in     HANDLE hProcess,     //释放此进程虚拟地址空间中的分配的内存页  
    9.   __inout  PULONG_PTR NumberOfPages,    //要释放的内存页数  
    10.   __in     PULONG_PTR UserPfnArray  //指向存储内存页帧成员的数组的指针  
    11. );  
    12.   
    13. BOOL WINAPI MapUserPhysicalPages(   //将分配好的内存页映射到指定的地址  
    14.   __in  PVOID lpAddress,        //指向要重映射的内存区域的指针  
    15.   __in  ULONG_PTR NumberOfPages,    //要映射的内存页数  
    16.   __in  PULONG_PTR UserPfnArray     //指向要映射的内存页的指针  
    17. );  

        在看实例程序前还有一些设置需要做,需要对系统的本地安全策略进行设置。在win7中,打开“控制面板->系统和安全->管理工具->本地安全策略”,给“锁定内存页”添加当前用户,然后退出,重启(不重启一般无法生效!)。

        经过前面的准备(再啰嗦一次:确认自己的电脑装有4G或4G以上的内存;开启PAE,使系统认出4G或以上的内存;设置好本地安全策略),我们就可以通过下面的代码来做个实验了。

        代码是从MSDN中AWE的一个Example修改而来的,具体流程见代码中的注释,如果对该Example的源代码有兴趣可以参考MSDN。

    [cpp] view plain copy
     
    1. #include "AWE_TEST.h"  
    2. #include <windows.h>  
    3. #include <stdio.h>  
    4.   
    5. #define MEMORY_REQUESTED ((2*1024+512)*1024*1024) //申请2.5G内存,测试机上只有4G内存,而且系统是window7,比较占内存.申请3G容易失败.  
    6. #define MEMORY_VIRTUAL 1024*1024*512        //申请长度0.5G的虚拟内存,即AWE窗口.  
    7.   
    8. //检测"锁定内存页"权限的函数  
    9. BOOL LoggedSetLockPagesPrivilege ( HANDLE hProcess, BOOL bEnable);  
    10.   
    11. void _cdecl main()  
    12. {  
    13.     BOOL bResult;                   // 通用bool变量  
    14.     ULONG_PTR NumberOfPages;        // 申请的内存页数  
    15.     ULONG_PTR NumberOfPagesInitial; // 初始的要申请的内存页数  
    16.     ULONG_PTR *aPFNs;               // 页信息,存储获取的内存页成员  
    17.     PVOID lpMemReserved;            // AWE窗口  
    18.     SYSTEM_INFO sSysInfo;           // 系统信息  
    19.     INT PFNArraySize;               // PFN队列所占的内存长度  
    20.   
    21.     GetSystemInfo(&sSysInfo);  // 获取系统信息  
    22.   
    23.     printf("This computer has page size %d./n", sSysInfo.dwPageSize);  
    24.   
    25.     //计算要申请的内存页数.  
    26.   
    27.     NumberOfPages = MEMORY_REQUESTED/sSysInfo.dwPageSize;  
    28.     printf ("Requesting %d pages of memory./n", NumberOfPages);  
    29.   
    30.     // 计算PFN队列所占的内存长度  
    31.   
    32.     PFNArraySize = NumberOfPages * sizeof (ULONG_PTR);  
    33.   
    34.     printf ("Requesting a PFN array of %d bytes./n", PFNArraySize);  
    35.   
    36.     aPFNs = (ULONG_PTR *) HeapAlloc(GetProcessHeap(), 0, PFNArraySize);  
    37.   
    38.     if (aPFNs == NULL)   
    39.     {  
    40.         printf ("Failed to allocate on heap./n");  
    41.         return;  
    42.     }  
    43.   
    44.     // 开启"锁定内存页"权限  
    45.   
    46.     if( ! LoggedSetLockPagesPrivilege( GetCurrentProcess(), TRUE ) )   
    47.     {  
    48.         return;  
    49.     }  
    50.   
    51.     // 分配物理内存,长度2.5GB  
    52.   
    53.     NumberOfPagesInitial = NumberOfPages;  
    54.     bResult = AllocateUserPhysicalPages( GetCurrentProcess(),  
    55.         &NumberOfPages,  
    56.         aPFNs );  
    57.   
    58.     if( bResult != TRUE )   
    59.     {  
    60.         printf("Cannot allocate physical pages (%u)/n", GetLastError() );  
    61.         return;  
    62.     }  
    63.   
    64.     if( NumberOfPagesInitial != NumberOfPages )   
    65.     {  
    66.         printf("Allocated only %p pages./n", NumberOfPages );  
    67.         return;  
    68.     }  
    69.   
    70.     // 保留长度0.5GB的虚拟内存块(这个内存块即AWE窗口)的地址  
    71.   
    72.     lpMemReserved = VirtualAlloc( NULL,  
    73.         MEMORY_VIRTUAL,  
    74.         MEM_RESERVE | MEM_PHYSICAL,  
    75.         PAGE_READWRITE );  
    76.   
    77.     if( lpMemReserved == NULL )   
    78.     {  
    79.         printf("Cannot reserve memory./n");  
    80.         return;  
    81.     }  
    82.   
    83.     char *strTemp;  
    84.     for (int i=0;i<5;i++)  
    85.     {  
    86.         // 把物理内存映射到窗口中来  
    87.         // 分5次映射,每次映射0.5G物理内存到窗口中来.  
    88.         // 注意,在整个过程中,lpMenReserved的值都是不变的  
    89.         // 但是映射的实际物理内存却是不同的  
    90.         // 这段代码将申请的2.5G物理内存分5段依次映射到窗口中来  
    91.         // 并在每段的开头写入一串字符串.  
    92.   
    93.         bResult = MapUserPhysicalPages( lpMemReserved,  
    94.             NumberOfPages/5,  
    95.             aPFNs+NumberOfPages/5*i);  
    96.   
    97.         if( bResult != TRUE )   
    98.         {  
    99.             printf("MapUserPhysicalPages failed (%u)/n", GetLastError() );  
    100.             return;  
    101.         }  
    102.   
    103.         // 写入字符串,虽然是写入同一个虚存地址,  
    104.         // 但是窗口映射的实际内存不同,所以是写入了不同的内存块中  
    105.         strTemp=(char*)lpMemReserved;  
    106.         sprintf(strTemp,"This is the %dth section!",i+1);  
    107.   
    108.         // 解除映射  
    109.   
    110.         bResult = MapUserPhysicalPages( lpMemReserved,  
    111.             NumberOfPages/5,  
    112.             NULL );  
    113.   
    114.         if( bResult != TRUE )   
    115.         {  
    116.             printf("MapUserPhysicalPages failed (%u)/n", GetLastError() );  
    117.             return;  
    118.         }  
    119.     }  
    120.   
    121.     // 现在再从5段内存中读出刚才写入的字符串  
    122.     for (int i=0;i<5;i++)  
    123.     {  
    124.         // 把物理内存映射到窗口中来  
    125.   
    126.         bResult = MapUserPhysicalPages( lpMemReserved,  
    127.             NumberOfPages/5,  
    128.             aPFNs+NumberOfPages/5*i);  
    129.   
    130.         if( bResult != TRUE )   
    131.         {  
    132.             printf("MapUserPhysicalPages failed (%u)/n", GetLastError() );  
    133.             return;  
    134.         }  
    135.   
    136.         // 将映射到窗口中的不同内存块的字符串在屏幕中打印出来  
    137.         strTemp=(char*)lpMemReserved;  
    138.         printf("%s/n",strTemp);  
    139.   
    140.         // 解除映射  
    141.   
    142.         bResult = MapUserPhysicalPages( lpMemReserved,  
    143.             NumberOfPages/5,  
    144.             NULL );  
    145.   
    146.         if( bResult != TRUE )   
    147.         {  
    148.             printf("MapUserPhysicalPages failed (%u)/n", GetLastError() );  
    149.             return;  
    150.         }  
    151.     }  
    152.       
    153.   
    154.     // 释放物理内存空间  
    155.   
    156.     bResult = FreeUserPhysicalPages( GetCurrentProcess(),  
    157.         &NumberOfPages,  
    158.         aPFNs );  
    159.   
    160.     if( bResult != TRUE )   
    161.     {  
    162.         printf("Cannot free physical pages, error %u./n", GetLastError());  
    163.         return;  
    164.     }  
    165.   
    166.     // 释放虚拟内存地址  
    167.   
    168.     bResult = VirtualFree( lpMemReserved,  
    169.         0,  
    170.         MEM_RELEASE );  
    171.   
    172.     // 释放PFN队列空间  
    173.   
    174.     bResult = HeapFree(GetProcessHeap(), 0, aPFNs);  
    175.   
    176.     if( bResult != TRUE )  
    177.     {  
    178.         printf("Call to HeapFree has failed (%u)/n", GetLastError() );  
    179.     }  
    180.   
    181. }  
    182.   
    183. /***************************************************************** 
    184.  
    185. 输入: 
    186.  
    187. HANDLE hProcess: 需要获得权限的进程的句柄 
    188.  
    189. BOOL bEnable: 启用权限 (TRUE) 或 取消权限 (FALSE)? 
    190.  
    191. 返回值: TRUE 表示权限操作成功, FALSE 失败. 
    192.  
    193. *****************************************************************/  
    194. BOOL  
    195. LoggedSetLockPagesPrivilege ( HANDLE hProcess,  
    196.                              BOOL bEnable)  
    197. {  
    198.     struct {  
    199.         DWORD Count;  
    200.         LUID_AND_ATTRIBUTES Privilege [1];  
    201.     } Info;  
    202.   
    203.     HANDLE Token;  
    204.     BOOL Result;  
    205.   
    206.     // 打开进程的安全信息  
    207.   
    208.     Result = OpenProcessToken ( hProcess,  
    209.         TOKEN_ADJUST_PRIVILEGES,  
    210.         & Token);  
    211.   
    212.     if( Result != TRUE )   
    213.     {  
    214.         printf( "Cannot open process token./n" );  
    215.         return FALSE;  
    216.     }  
    217.   
    218.     // 开启 或 取消?  
    219.   
    220.     Info.Count = 1;  
    221.     if( bEnable )   
    222.     {  
    223.         Info.Privilege[0].Attributes = SE_PRIVILEGE_ENABLED;  
    224.     }   
    225.     else   
    226.     {  
    227.         Info.Privilege[0].Attributes = 0;  
    228.     }  
    229.   
    230.     // 获得LUID  
    231.   
    232.     Result = LookupPrivilegeValue ( NULL,  
    233.         SE_LOCK_MEMORY_NAME,  
    234.         &(Info.Privilege[0].Luid));  
    235.   
    236.     if( Result != TRUE )   
    237.     {  
    238.         printf( "Cannot get privilege for %s./n", SE_LOCK_MEMORY_NAME );  
    239.         return FALSE;  
    240.     }  
    241.   
    242.     // 修改权限  
    243.   
    244.     Result = AdjustTokenPrivileges ( Token, FALSE,  
    245.         (PTOKEN_PRIVILEGES) &Info,  
    246.         0, NULL, NULL);  
    247.   
    248.     // 检查修改结果  
    249.   
    250.     if( Result != TRUE )   
    251.     {  
    252.         printf ("Cannot adjust token privileges (%u)/n", GetLastError() );  
    253.         return FALSE;  
    254.     }   
    255.     else   
    256.     {  
    257.         if( GetLastError() != ERROR_SUCCESS )   
    258.         {  
    259.             printf ("Cannot enable the SE_LOCK_MEMORY_NAME privilege; ");  
    260.             printf ("please check the local policy./n");  
    261.             return FALSE;  
    262.         }  
    263.     }  
    264.   
    265.     CloseHandle( Token );  
    266.   
    267.     return TRUE;  
    268. }  

    程序运行结果如下:

        可以看出系统分页的大小为4K,总共申请了655360个分页,也就是2.5G。每个分页成员占4字节,总共2621440字节。2.5G内存分成5段512M的块,成功写入了字符串并成功读取。

        在调试过程中,在执行了AllocateUserPhysicalPages函数后设置断点,查看任务管理器,可以看出成功分配了物理内存后,实际物理内存被占用了2.5G,从而验证了AWE的效果。

        通过上述示例,我们成功的在32位系统中识别出了4G的内存,并且在32位程序中成功使用了超过2G的内存。借助PAE和AWE,即使在32位系统上,我们也能够顺利开发对内存消耗较大的应用程序,而不需要依赖于64位平台。

  • 相关阅读:
    IE 插件 Trixie 介绍
    .net开发沉淀之:HttpHandler(一)
    在Eclipse中导入已经存在的jar包
    浏览器的GreaseMonkey和Trixie插件
    为什么export>runnable jar file的launch configuration没有东西可以选择?
    SQL2008:如何解决“阻止保存要求重新创建表的更改”
    在IIS6下部署MVC2.0的注意事项
    ASP.NET 4.0中使用FreeTextBox和FCKeditor遇到安全问题警告的解决办法
    MyEclipse 设置JDK指向目录错误信息
    RHEL5.5 安装 oracle 11g
  • 原文地址:https://www.cnblogs.com/lidabo/p/8426011.html
Copyright © 2011-2022 走看看