32位应用程序如何使用大于2GB的内存?
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的内存?相关推荐
- 64位系统下一个32位的程序究竟可以申请到多少内存?
64位系统下一个32位的程序究竟可以申请到多少内存? cpu的位是指一次性可处理的数据量是多少,1字节=8位,32位处理器可以一次性处理4个字节的数据量,依次类推.32位操作系统针对的32位的CPU设 ...
- 64位系统下,一个32位的程序究竟可以申请到多少内存,4GB还是更多
前言: cpu的位是指一次性可处理的数据量是多少,1字节=8位,32位处理器可以一次性处理4个字节的数据量,依次类推.32位操作系统针对的32位的CPU设计.64位操作系统针对的64位的CPU设计.操 ...
- 64位系统下,一个32位的程序究竟可以申请到多少内存,4GB还是更多?
前言: cpu的位是指一次性可处理的数据量是多少,1字节=8位,32位处理器可以一次性处理4个字节的数据量,依次类推.32位操作系统针对的32位的CPU设计.64位操作系统针对的64位的CPU设计.操 ...
- 32位系统利用ReadyFor4GB扩大大于4G的内存
本文是通过网上的文章进行修改整理的,感谢他们的努力! 首先我不评论这个软件的好坏,也不妄断有没有用,只是在这里告诉大家的使用方法.但是个人还是建议: 1,如果你的cpu不是64位的还是不要用了 2,电 ...
- 让32位应用程序不再为2G内存限制苦恼
让32位应用程序不再为2G内存限制苦恼 分类: VC++ 2009-09-21 17:57 8303人阅读 评论(6) 收藏 举报 windows winapi token server null a ...
- Zynq UltraScale + MPSoC示例设计 - 在64位Linux上执行32位应用程序
目录 官方说明连接:https://china.xilinx.com/support/answers/66636.html 描述 解决方案 官方说明连接:https://china.xilinx.co ...
- 您没有足够的全新为该计算机所有用户安装,很抱歉,无法安装Office(64位),因为您的计算机上已经安装了这些32位Office程序解决办法...
64位与32位版本的Office程序不兼容,因此您一次只能安装一种类型,请尝试改为安装32位版本的Office ,或卸载其他32位Office 程序,然后再次尝试此安装. 在安装Office 2016 ...
- 32位应用程序单个进程最大占用内存是4GB
32位应用程序单个进程最大占用内存是4GB左右,这个问题是因为需要测试大数据里才能体现出来的问题,所以反反复复花了一周多的时间来搞定它,希望大家在对它有一个印象.64位的应用程序的时候就不会有这个限制 ...
- linux更改cxxflags环境变量,在64位的ubuntu 14.04 上开展32位Qt 程序开发环境配置(pro文件中增加 QMAKE_CXXFLAGS += -m32 命令)...
为了能中一个系统上开发64或32位C++程序,费了些周折,现在终于能够开始干过了.在此记录此时针对Q5.4版本的32位开发环境配置过程. 1. 下载Qt 5.4 的32位版本,进行安装,安装过程中会发 ...
最新文章
- Linux磁盘分区管理(转载)
- Spring 原理初探——IoC、AOP
- 【温故知新】CSS学习笔记(盒子边框介绍)
- Python 实现批量从不同的Linux服务器下载文件
- 容器化的 DevOps 工作流
- cobertura_Cobertura和Sonar 5.1的问题
- js html异步加载的属性,异步加载JS的五种方式
- 挖槽!堪称神级的Java技术手册火了???(文末送书活动)
- Visual C# 2008+SQL Server 2005 数据库与网络开发――3.2.4 匿名类型
- Criteo公司在上海新开通一个数据中心
- php操作excel表格的导入和导出
- 华为毕昇JDK8的改进,效果很一般
- 游戏脚本代码大全_如何用5行Python代码写出刷分游戏脚本!Python真牛!
- DLL load failed while importing rdBase
- 高德地图php接口,简单使用高德地图开放平台API
- NeRF 源码分析解读(二)
- vue中的Actions
- 某星级酒店警卫队定岗定编项目纪实 ——完善定岗定编,转向人性化管理
- 1033 旧键盘打字 (20分)
- 如何让spyder中途暂停(之前运行出来的结果仍然显示)
热门文章
- shmget出现Invalid argument错误
- 快速向表中插入大量数据Oracle中append与Nologgin的作用
- ubuntu如何删除刚添加的源?
- Codeforces Round #879 (Div. 2) C. Short Program
- DZNEmptyDataSet,优秀的空白页或者出错页封装
- 微信支付,判断是否安装了微信
- BZOJ 1801: [Ahoi2009]chess 中国象棋( dp )
- WPF MVVM设计模式的ViewModelBase和CommandBase代码
- SQL server 使用自定义函数以及游标
- 聊聊Elasticsearch的CachedSupplier