最近在做个程序,虽然是小型程序,但是使用的内存量却很大,动辄达到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,系统只能认出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. WINBASEAPI
  6. BOOL
  7. WINAPI
  8. AllocateUserPhysicalPages(
  9. __in    HANDLE hProcess,
  10. __inout PULONG_PTR NumberOfPages,
  11. __out_ecount_part(*NumberOfPages, *NumberOfPages) PULONG_PTR PageArray
  12. );
  13. WINBASEAPI
  14. BOOL
  15. WINAPI
  16. FreeUserPhysicalPages(
  17. __in    HANDLE hProcess,
  18. __inout PULONG_PTR NumberOfPages,
  19. __in_ecount(*NumberOfPages) PULONG_PTR PageArray
  20. );
  21. WINBASEAPI
  22. BOOL
  23. WINAPI
  24. MapUserPhysicalPages(
  25. __in PVOID VirtualAddress,
  26. __in ULONG_PTR NumberOfPages,
  27. __in_ecount_opt(NumberOfPages) PULONG_PTR PageArray
  28. );
  29. //...
  30. #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. BOOL WINAPI FreeUserPhysicalPages(  //释放AllocateUserPhysicalPages函数分配的内存
  7. __in     HANDLE hProcess,     //释放此进程虚拟地址空间中的分配的内存页
  8. __inout  PULONG_PTR NumberOfPages,    //要释放的内存页数
  9. __in     PULONG_PTR UserPfnArray  //指向存储内存页帧成员的数组的指针
  10. );
  11. BOOL WINAPI MapUserPhysicalPages(   //将分配好的内存页映射到指定的地址
  12. __in  PVOID lpAddress,        //指向要重映射的内存区域的指针
  13. __in  ULONG_PTR NumberOfPages,    //要映射的内存页数
  14. __in  PULONG_PTR UserPfnArray     //指向要映射的内存页的指针
  15. );

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

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

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

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

程序运行结果如下:

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

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

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

让32位应用程序不再为2G内存限制苦恼的更多相关文章

  1. C++内存管理1-64位系统运行32位软件会占用更多的内存吗?

    随着大容量内存成为电脑平台常规化的配置,在配置组装机时很多的用户都会选择8GB甚至是16GB的容量规格内存使用在自己的机器上,如果要将这8GB甚至是16GB的内容在系统使用时能充分利用起来的话,你平台 ...

  2. Ubuntu不会放弃32位应用程序

    Ubuntu 开发人员澄清,人们以为 Ubuntu 将在 Ubuntu 19.10 和后续版本中放弃对运行 32 位应用程序的支持,但“根本不是这种情况”.那么这究竟是怎么一回事呢?前几天 Ubunt ...

  3. 64位系统运行32位Oracle程序解决方案

    Attempt to load Oracle client libraries threw BadImageFormatException. This problem will occur when ...

  4. 关于oracle 11g 64位与 32位的 plsql、及其他32位应用程序共存的问题

    因为 plsql 不支持 64位 oracle 客户端,所以plsql 必须使用 oracle 的 32位 instanclient 包.  解压缩后放一个目录,例如: D:\Oracle\insta ...

  5. 使 IIS 6.0 可以在 64 位 Windows 上运行 32 位应用程序 试图加载格式不正确的程序。

    原文 使 IIS 6.0 可以在 64 位 Windows 上运行 32 位应用程序 试图加载格式不正确的程序. win7 64位操作系统上边运行IIS网站应用的时候,提示错误"试图加载格式 ...

  6. 解决windows server2003 64位操作系统上不能加载32位应用程序dll 的问题

    [FileLoadException: Could not load file or assembly 'sapnco_utils, Version=3.0.0.42, Culture=neutral ...

  7. 64位系统下,一个32位的程序究竟可以申请到多少内存,4GB还是更多?(一)

    前言: cpu的位是指一次性可处理的数据量是多少,1字节=8位,32位处理器可以一次性处理4个字节的数据量,依次类推.32位操作系统针对的32位的CPU设计.64位操作系统针对的64位的CPU设计.操 ...

  8. IIS启用32位应用程序兼容

    针对服务器出现html和jsp页面都可以应用,但唯独asp页面打不开的一种情况 win7的IIS运行在32状态下,原因是ASP程序必须在32位下才能使用ACCESS 设置办法: 打开IIS管理器,点应 ...

  9. 在64位的ubuntu 14.04 上开展32位Qt 程序开发环境配置(pro文件中增加 QMAKE_CXXFLAGS += -m32 命令)

    为了能中一个系统上开发64或32位C++程序,费了些周折,现在终于能够开始干过了.在此记录此时针对Q5.4版本的32位开发环境配置过程. 1. 下载Qt 5.4 的32位版本,进行安装,安装过程中会发 ...

随机推荐

  1. [RK3288][Android6.0] 调试笔记 --- user版本默认显示开发者选项【转】

    本文转载自:https://blog.csdn.net/kris_fei/article/details/70157137 Platform: ROCKCHIPOS: Android 6.0Kerne ...

  2. JQuery 操作 iframe

    JQuery访问iframe内的元素 $("iframe#Main", top.document).contents().find("#id"); JQuery ...

  3. google Json

    gradle仓库地址: // https://mvnrepository.com/artifact/com.google.code.gson/gsoncompile group: 'com.googl ...

  4. springmvc-restful

    1.restful概述 REST 仅仅是一种架构的风格,并不是真正的架构,也不是一个软件,而是一种思想. 我们可以基于现有的HTTP.URI.XML.等现有技术来实现REST的风格.而不用去学习任何新 ...

  5. jsonp 实现跨域例子

    直接上代码: js: <html> <head> <title>JSONP</title> </head> <script src = ...

  6. js获取css样式方法

    一.CSS样式共有三种:内联样式(行间样式).内部样式.外部样式(链接式和导入式) <div id="a" style="width: 100px;height: ...

  7. 【转】服务器.htaccess 详解以及 .htaccess 参数说明

    htaccess文件(或者”分布式配置文件”)提供了针对目录改变配置的方法, 即,在一个特定的文档目录中放置一个包含一个或多个指令的文件, 以作用于此目录及其所有子目录.作为用户,所能使用的命令受到限 ...

  8. PHP 学习(一)——课程介绍

    一.课程路线介绍 教程的学习路线按照:初级——>中级——>高级——>项目实做 初级: 中级: 高级: 项目实做: 整体: Php体系了解:

  9. XE8_TPaintBox画

    1. procedure TForm1.PaintBox1Paint(Sender: TObject; Canvas: TCanvas); var bitmap :TBitMap; pt1, pt2 ...

  10. 新建Maven项目Index.jsp文件报错解决方法

    The superclass "javax.servlet.http.HttpServlet" was not found on the Java Build Path    in ...