让32位应用程序不再为2G内存限制苦恼

分类: VC++ 2009-09-21 17:57 8303人阅读 评论(6) 收藏 举报

    最近在做个程序,虽然是小型程序,但是使用的内存量却很大,动辄达到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文件示例:

  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

关闭PAE


    如果没有打开PAE,系统只能认出3G的内存,最多可以再多0.5G不到,这样即使使用AWE,由于系统和其他应用程序已经占去了一部分内存,剩下的内存或许也只有2G多一点了,没什么太大提高。只有当系统认出了4G以上的内存,AWE才能发挥它真正的作用。

    下面我们看看windows中给出的有关AWE的API函数,它们都定义在winbase.h中。

 

  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的代码出错,可以在程序头文件中加入下面的代码。

  1. #ifndef _WIN32_WINNT   
  2. #define _WIN32_WINNT 0x0501   
  3. #endif    

 

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

 

  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。

 

  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位平台。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
现在易语言还不能开发64位程序,所以受32位地址空间限制;一般情况下32位程序最高内存地址是0x7FFFFFFF,能调的内存2G,也可以通过其他方法达到3G,但3G没有什么用处。 比如我们在开发服务端程序时候,会有很多客户端连接,假设每个活跃连接占用1M内存,那2000个连接,服务端内存就会不够用,应用场景还有很多。 有几个地方需要注意的: 1.2G内存已经满足不了你的程序,并且已经试过文件映射,分批处理等方式。 2.了解虚地址空间,了解内存读取操作。 3.首先你得有一台大内存服务器,比如64G,128G内存 4.有内存页锁定权限 组策略开启内存页锁定:控制面板->系统和安全->管理工具->本地安全策略->锁定内存页添加当前用户,重启电脑 这是好几年前写的,很久没看过源码了,但保证能用的,不要加我QQ,我很忙 实现流程: 1.申请一块扩展地址,比如64G大小 2.申请一块虚地址空间,把扩展地址映射到虚地址空间 3.通过虚地址空间进行读写 4.使用完了,释放扩展地址和虚地址空间 我的应用场景: 现在有8673客户端在线,假如突发情况下每个客户端给我发送1M数据,那需要8.4G内存,那我的程序肯定挂了。 我这是这么处理的: 服务端接收到数据以后,比如数据大于128k时候,就申请扩展地址,投递到完成端口队列中。 完成端口队列取出数据处理,返回给客户端。 当有突发情况的时候,就不用丢弃数据,又可以充分利用服务器内存
现在易语言还不能开发64位程序,所以受32位地址空间限制;一般情况下32位程序最高内存地址是 0x7FFFFFFF,能调的内存2G,也可以通过其他方法达到3G,但3G没有什么用处。 比如我们在开发服务端程序时候,会有很多客户端连接,假设每个活跃连接占用1M内存,那2000个连接,服务端内存就会不够用,应用场景还有很多。 有几个地方需要注意的: 1.2G内存已经满足不了你的程序,并且已经试过文件映射,分批处理等方式。 2.了解虚地址空间,了解内存读取操作。 3.首先你得有一台大内存服务器,比如64G,128G内存 4.有内存页锁定权限 组策略开启内存页锁定:控制面板->系统和安全->管理工具->本地安全策略->锁定内存页添加当前用户,重启电脑 这是好几年前写的,很久没看过源码了,但保证能用的,不要加我QQ,我很忙。 实现流程: 1.申请一块扩展地址,比如64G大小 2.申请一块虚地址空间,把扩展地址映射到虚地址空间 3.通过虚地址空间进行读写 4.使用完了,释放扩展地址和虚地址空间 现在有8673客户端在线,假如突发情况下每个客户端给我发送1M数据,那需要8.4G内存,那我的程序肯定挂了。 我这是这么处理的: 服务端接收到数据以后,比如数据大于128k时候,就申请扩展地址,投递到完成端口队列中。 完成端口队列取出数据处理,返回给客户端。 当有突发情况的时候,就不用丢弃数据,又可以充分利用服务器内存

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值