让32位应用程序不再为2G内存限制苦恼
最近在做个程序,虽然是小型程序,但是使用的内存量却很大,动辄达到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文件示例:
- [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.5GB
- NumberOfPagesInitial = 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;
- }
- // 获得LUID
- Result = 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内存限制苦恼的更多相关文章
- C++内存管理1-64位系统运行32位软件会占用更多的内存吗?
随着大容量内存成为电脑平台常规化的配置,在配置组装机时很多的用户都会选择8GB甚至是16GB的容量规格内存使用在自己的机器上,如果要将这8GB甚至是16GB的内容在系统使用时能充分利用起来的话,你平台 ...
- Ubuntu不会放弃32位应用程序
Ubuntu 开发人员澄清,人们以为 Ubuntu 将在 Ubuntu 19.10 和后续版本中放弃对运行 32 位应用程序的支持,但“根本不是这种情况”.那么这究竟是怎么一回事呢?前几天 Ubunt ...
- 64位系统运行32位Oracle程序解决方案
Attempt to load Oracle client libraries threw BadImageFormatException. This problem will occur when ...
- 关于oracle 11g 64位与 32位的 plsql、及其他32位应用程序共存的问题
因为 plsql 不支持 64位 oracle 客户端,所以plsql 必须使用 oracle 的 32位 instanclient 包. 解压缩后放一个目录,例如: D:\Oracle\insta ...
- 使 IIS 6.0 可以在 64 位 Windows 上运行 32 位应用程序 试图加载格式不正确的程序。
原文 使 IIS 6.0 可以在 64 位 Windows 上运行 32 位应用程序 试图加载格式不正确的程序. win7 64位操作系统上边运行IIS网站应用的时候,提示错误"试图加载格式 ...
- 解决windows server2003 64位操作系统上不能加载32位应用程序dll 的问题
[FileLoadException: Could not load file or assembly 'sapnco_utils, Version=3.0.0.42, Culture=neutral ...
- 64位系统下,一个32位的程序究竟可以申请到多少内存,4GB还是更多?(一)
前言: cpu的位是指一次性可处理的数据量是多少,1字节=8位,32位处理器可以一次性处理4个字节的数据量,依次类推.32位操作系统针对的32位的CPU设计.64位操作系统针对的64位的CPU设计.操 ...
- IIS启用32位应用程序兼容
针对服务器出现html和jsp页面都可以应用,但唯独asp页面打不开的一种情况 win7的IIS运行在32状态下,原因是ASP程序必须在32位下才能使用ACCESS 设置办法: 打开IIS管理器,点应 ...
- 在64位的ubuntu 14.04 上开展32位Qt 程序开发环境配置(pro文件中增加 QMAKE_CXXFLAGS += -m32 命令)
为了能中一个系统上开发64或32位C++程序,费了些周折,现在终于能够开始干过了.在此记录此时针对Q5.4版本的32位开发环境配置过程. 1. 下载Qt 5.4 的32位版本,进行安装,安装过程中会发 ...
随机推荐
- 递归实现N皇后问题
其实是看到一位名为“活在二次元的伪触”的博主昨天还是前天写了篇这个题材的笔记,觉得有点意思,于是想自己来写写. 其实我发现上述那位同学写N皇后问题写得还不错,文末也会给出这位同学用通过递归的方法实现N ...
- debian内核代码执行流程(二)
继续上一篇文章<debian内核代码执行流程(一)>未完成部分. acpi_bus_init调用acpi_initialize_objects,经过一系列复杂调用后输出下面信息: [ IN ...
- Docker 数据收集利器:cadvisor
gitHub地址:https://github.com/google/cadvisor cAdvisor cAdvisor (Container Advisor) provides container ...
- 淘宝分类常见---部分显示和全部显示的js效果
需求就是,点击“更多按钮”,显示全部的分类详情,再次点击,显示部分分类. 展开: 收起: 结构: <div class="SubBox" id="SubBox&qu ...
- POJ 2431 贪心+优先队列
题意:一辆卡车距离重点L,现有油量P,卡车每前行1米耗费油量1,途中有一些加油站,问最少在几个加油站加油可使卡车到达终点或到达不了终点. 思路:运用优先队列,将能走到的加油站的油量加入优先队列中, ...
- JDK各个版本的新特性jdk1.5-jdk8[转]
JDK各个版本的新特性 对于很多刚接触java语言的初学者来说,要了解一门语言,最好的方式就是要能从基础的版本进行了解,升级的过程,以及升级的新特性,这样才能循序渐进的学好一门语言.今天先为大家介绍一 ...
- 利用CXF框架开发webservice
开发服务端代码 1. web.xml文件中添加cxf的servlet 2. 定义接口 @WebService(targetNamespace="http://UserInfo.ws.com& ...
- C#反射第一天
[转]C#反射 反射(Reflection)是.NET中的重要机制,通过放射,可以在运行时获得.NET中每一个类型(包括类.结构.委托.接口和枚举等)的成员,包括方法.属性.事件,以及构造函数等. ...
- 好的SQL写法
DECLARE @beginTime VARCHAR(20)= '2017-12-20 00:00:00';DECLARE @endTime VARCHAR(20)= '2017-12-26 00:0 ...
- linux-RabbitMQ安装命令
一.RabbitMQ 1.安装配置epel源 $ rpm -ivh http://dl.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.no ...