最近在做个程序,虽然是小型程序,但是使用的内存量却很大,动辄达到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. 重用UITableViewCell对象的概念

    重用UITableViewCell对象 UITableView控件十分常见,基本上我们随意打开一款App都能见到,它被用来列表展示数据,而其中的每一行内容都是一个cell对象 我们知道手机设备上的内存 ...

  2. SQL Server 利用WITH AS递归获取层级关系数据

    WITH AS短语,也叫做子查询部分(subquery factoring),在SQL Server 2005中提供了一种解决方案,这就是公用表表达式(CTE),使用CTE,可以使SQL语句的可维护性 ...

  3. Complex social network Partition for Balanced Subnetworks---Hao Lan Zhang,Jiming Liu,Chunyu Feng,Chaoyi Pang,Tongliang Li,Jing He阅读

    摘要:Abstract—Complex social network analysis methods have been applied extensively in various domains ...

  4. 【BZOJ2733】永无乡[HNOI2012](splay启发式合并or线段树合并)

    题目大意:给你一些点,修改是在在两个点之间连一条无向边,查询时求某个点能走到的点中重要度第k大的点.题目中给定的是每个节点的排名,所以实际上是求第k小:题目求的是编号,不是重要度的排名.我一开始差点被 ...

  5. ML 感知机(Perceptrons)

    感知机 Perceptrons 学习Hinton神经网络公开课的学习笔记 https://class.coursera.org/neuralnets-2012-001 1 感知机历史 在19世纪60年 ...

  6. OpenStack with Opendaylight Part 1: Intro to Pipeline

    Using Vagrant to create vm nodes; devstack to start openstack using Opendaylight as ML2. Openstack w ...

  7. Centos7+httpd+fastcgi安装提示错误

    搭建的环境: centos7 Apache/2.4.6 fastcgi2.4.6 rails4 在安装fastcgi的时候遇到了问题: 问题: ...... In file included from ...

  8. 使用log4j的邮件功能

    Log4j的邮件功能能够为我们做这样的事情----当程序运行完的时候,或者正在运行也是可以的,它将程序的日志通过邮件的方式发到你的邮箱上. 这样,对于程序运行的控制就不用每次都跑到机器上去看日志文件这 ...

  9. php 5.6以上可以采用new PDD连接数据库的方法。

    <?php// @mysqli_connect($db=localhost,// $_cont['root'],// $_cont['root'],// $_cont['demo'],// $_ ...

  10. LeetCode OJ:Binary Tree Level Order Traversal(二叉树的层序遍历)

    Given a binary tree, return the level order traversal of its nodes' values. (ie, from left to right, ...