32位应用程序如何使用大于2GB的内存?

不管是在 32 位 Windows 还是在 64 位 Windows,32 位应用程序都只能使用最大 2GB 的内存,这是我们司空见惯的一个设定。但其实 Windows 提供了一些方法让我们打破这样的设定,使程序使用大于 2GB 的内存。

为什么 32 位程序只能使用最大 2GB 内存?

32 位寻址空间只有 4GB 大小,于是 32 位应用程序(进程)最大只能用到 4GB 的内存。然而,除了应用程序本身要用内存,操作系统内核也需要使用。应用程序使用的内存空间分为用户空间和内核空间,每个 32 位程序的用户空间可独享前 2GB 空间(指针值为正数),而内核空间为所有进程共享 2GB 空间(指针值为负数)。所以,32 位应用程序实际能够访问的内存地址空间最多只有 2GB。

让 32 位程序使用大于 2GB 内存的两种方法

editbin

这是 Visual Studio 2017 采用的做法。我们需要使用到两个工具——editbin 和 dumpbin。前者用于编辑我们编译生成好的程序使之头信息中声明支持大于 2GB 内存,后者用于查看程序的头信息验证我们是否改好了。

编辑一个程序使之声明支持大于 2GB 内存的命令是:

editbin /largeaddressaware xxx.exe

其中,xxx.exe 是我们准备修改的程序,可以使用相对路径或绝对路径(如果路径中出现空格记得带引号)。

验证这个程序是否改好了的命令是:

dumpbin /headers xxx.exe | more

同样,xxx.exe 是我们刚刚改好准备检查的程序,可以使用相对路径或绝对路径。

editbin 改之前和改之后用 dumpbin 查看我们的程序头信息,得到下面两张图:

注意到 FILE HEADER VALUES 块的倒数第二行多出了 Application can handle large (>2GB) addresses

如果没发现,一定是你命令执行中发生了错误,检查一下吧!最容易出现的错误是执行后发现根本就没有这个命令。是的,editbin 命令从哪里来呢?可以在开始菜单中的 Visual Studio 文件夹中查找 Developer Command Prompt for VS 2017,运行这个启动的命令行中就带有 editbin 和 dumpbin。

如果希望能够在 Visual Studio 编译的时候自动调用这个工具,请参见:LargeAddressAware Visual Studio 2015 C#。

编译成 AnyCPU (Prefer 32-bit)

这是本文更推荐的做法,也是最简单的做法。方法是打开入口程序集的属性页,将“目标平台”选为“AnyCPU”,然后勾选“首选 32 位”。需要注意的是,这种生成方式是 .Net Framework 4.5 及以上版本才提供的。

至于 AnyCPU (Prefer 32-bit) 和 x86 两种生成方式的区别,请参见:What is the purpose of the “Prefer 32-bit” setting in Visual Studio 2012 and how does it actually work?。

声明支持大于 2GB 内存后,能使用多少内存?

对于 32 位操作系统,程序依然只能使用 2GB 内存,除非开启了 /3GB 开关,开启方法详见:/3GB。开启后,应用程序的用户态将可以使用 3GB 内存,但内核态将只能使用 1GB 内存。微软认为,是否打开 /3GB 开关是计算机设备开发商需要做的事情,开发商也需要自己测试开启后驱动程序的性能表现和稳定性。

对于 64 位操作系统,Windows 将很豪放地将 4GB 全部贡献给这样的程序,因为系统自己已经有更多的内存寻址空间可以使用了,没必要跟 32 位应用程序抢占寻址空间。

AWE(Address Windowing Extensions)

AWE是Windows的内存管理功能的一组扩展,它允许应用程序获取物理内存,然后将非分页内存的视图动态映射到32位地址空间。虽然32位地址空间限制为4GB,但是非分页内存却可以远远大于4GB。这使需要大量内存的应用程序(如大型数据库系统)能使用的内存量远远大于32位地址空间所支持的内存量。

为了使用大容量内存,除了要用到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以上内存的目的。下图分别为开启PAE和未开启PAE时系统识别出的内存容量区别。

开启PAE

关闭PAE

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

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

#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.5GBNumberOfPagesInitial = 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;}// 获得LUIDResult = 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位平台。

解决方案

默认情况下,32程序的内存地址空间只有2G,这2G内存除了存储程序数据外还需要存储程序本身,根据经验,程序一旦使用超过1G的内存就不太稳定。

方案一:进程拆分

将程序中耗内存的功能拆分成单独的进程。此方法在目前的各个项目开发中正在使用。但有时候会出现功能无法拆分(如数据浏览),或拆分后还出现主进程较长时间使用还是出现内存溢出的问题。所以此方案还不能完全解决这个内存溢出的问题。

方案二:突破2G限制

通过开启/largeaddressaware 的方式突破单进程2个G的限制(理论最大4G  虚拟地址空间)。

2.2.1  操作方法

具体操作方法:

方法1:在命令行依次执行以下几行命令

cd C:\Program Files(x86)\Microsoft Visual Studio 11.0\VC\bin

vcvars32.bat

editbin /largeaddressawrexx.exe

方法2:在VS (以VS2012为例)项目属性->生成事件->后期生成事件命令行中设置如下命令行:

call"%VS110COMNTOOLS%..\tools\vsvars32.bat"

editbin /largeaddressaware$(TargetPath)

说明:方法一在每次编译后需要执行相关命令;方法二在VS编译后自动完成相关设置操作,使用方便。

2.2.2  测试程序

以下为测试程序的编写示例:

测试程序:通过在exe中进行编写以下大量分配内存的程序进行测试,将错误信息输出到日志

      static void Main(string[] args){try{int count = 100000000;List<int[]> lst = newList<int[]>(count);for (int i = 0; i < count;i++){lst.Add(new int[1000000]);}}catch (Exception ex){LogHelper.Error.Append(ex);}}

1)未突破2G限制的日志信息如下:

19:33:07:849

System.OutOfMemoryException:引发类型为“System.OutOfMemoryException”的异常。

19:33:07:855

当前专用内大小:1.69 GB

当前虚拟内存大小:1.94 GB

最大虚拟内存量:1.94 GB

物理内存总大小:4 GB

可用物理内存大小:4 GB

虚拟内存总大小:2 GB

可用虚拟内存大小:62.31 MB

交换空间总大小:4 GB

可用交换空间大小:4 GB

2)突破4G限制的日志信息如下:

19:33:45:371

System.OutOfMemoryException:引发类型为“System.OutOfMemoryException”的异常。

19:33:45:377

当前专用内大小:3.59 GB

当前虚拟内存大小:3.9 GB

最大虚拟内存量:3.91 GB

物理内存总大小:4 GB

可用物理内存大小:4 GB

虚拟内存总大小:4 GB

可用虚拟内存大小:105.38 MB

交换空间总大小:4 GB

可用交换空间大小:4 GB

结论

综上,通过方案一和方案二相结合,能比较完美地解决32位程序内存溢出的问题(当然,如果这个程序使用过程中占用的内存达到3G以上的时候还是容易出问题,那就需要从程序本身去优化了)。

【转载】https://docs.microsoft.com/en-us/troubleshoot/windows-server/performance/desktop-heap-limitation-out-of-memory

32位应用程序如何使用大于2GB的内存?相关推荐

  1. 64位系统下一个32位的程序究竟可以申请到多少内存?

    64位系统下一个32位的程序究竟可以申请到多少内存? cpu的位是指一次性可处理的数据量是多少,1字节=8位,32位处理器可以一次性处理4个字节的数据量,依次类推.32位操作系统针对的32位的CPU设 ...

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

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

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

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

  4. 32位系统利用ReadyFor4GB扩大大于4G的内存

    本文是通过网上的文章进行修改整理的,感谢他们的努力! 首先我不评论这个软件的好坏,也不妄断有没有用,只是在这里告诉大家的使用方法.但是个人还是建议: 1,如果你的cpu不是64位的还是不要用了 2,电 ...

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

    让32位应用程序不再为2G内存限制苦恼 分类: VC++ 2009-09-21 17:57 8303人阅读 评论(6) 收藏 举报 windows winapi token server null a ...

  6. Zynq UltraScale + MPSoC示例设计 - 在64位Linux上执行32位应用程序

    目录 官方说明连接:https://china.xilinx.com/support/answers/66636.html 描述 解决方案 官方说明连接:https://china.xilinx.co ...

  7. 您没有足够的全新为该计算机所有用户安装,很抱歉,无法安装Office(64位),因为您的计算机上已经安装了这些32位Office程序解决办法...

    64位与32位版本的Office程序不兼容,因此您一次只能安装一种类型,请尝试改为安装32位版本的Office ,或卸载其他32位Office 程序,然后再次尝试此安装. 在安装Office 2016 ...

  8. 32位应用程序单个进程最大占用内存是4GB

    32位应用程序单个进程最大占用内存是4GB左右,这个问题是因为需要测试大数据里才能体现出来的问题,所以反反复复花了一周多的时间来搞定它,希望大家在对它有一个印象.64位的应用程序的时候就不会有这个限制 ...

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

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

最新文章

  1. Linux磁盘分区管理(转载)
  2. Spring 原理初探——IoC、AOP
  3. 【温故知新】CSS学习笔记(盒子边框介绍)
  4. Python 实现批量从不同的Linux服务器下载文件
  5. 容器化的 DevOps 工作流
  6. cobertura_Cobertura和Sonar 5.1的问题
  7. js html异步加载的属性,异步加载JS的五种方式
  8. 挖槽!堪称神级的Java技术手册火了???(文末送书活动)
  9. Visual C# 2008+SQL Server 2005 数据库与网络开发――3.2.4 匿名类型
  10. Criteo公司在上海新开通一个数据中心
  11. php操作excel表格的导入和导出
  12. 华为毕昇JDK8的改进,效果很一般
  13. 游戏脚本代码大全_如何用5行Python代码写出刷分游戏脚本!Python真牛!
  14. DLL load failed while importing rdBase
  15. 高德地图php接口,简单使用高德地图开放平台API
  16. NeRF 源码分析解读(二)
  17. vue中的Actions
  18. 某星级酒店警卫队定岗定编项目纪实 ——完善定岗定编,转向人性化管理
  19. 1033 旧键盘打字 (20分)
  20. 如何让spyder中途暂停(之前运行出来的结果仍然显示)

热门文章

  1. shmget出现Invalid argument错误
  2. 快速向表中插入大量数据Oracle中append与Nologgin的作用
  3. ubuntu如何删除刚添加的源?
  4. Codeforces Round #879 (Div. 2) C. Short Program
  5. DZNEmptyDataSet,优秀的空白页或者出错页封装
  6. 微信支付,判断是否安装了微信
  7. BZOJ 1801: [Ahoi2009]chess 中国象棋( dp )
  8. WPF MVVM设计模式的ViewModelBase和CommandBase代码
  9. SQL server 使用自定义函数以及游标
  10. 聊聊Elasticsearch的CachedSupplier