32 位应用程序如何使用超过 2GB 的内存?
无论是在 32 位 Windows 还是 64 位 Windows 中,32 位应用程序最多只能使用 2GB 的内存,这是我们习惯的常见设置。但实际上,Windows 为我们提供了一些方法来打破这种设置,让程序使用超过 2GB 的内存。
为什么 32 位程序最多只能使用 2GB 内存?
32 位寻址空间大小只有 4GB,因此 32 位应用程序(进程)最多只能使用 4GB 内存。但是,除了应用程序本身使用的内存外,操作系统内核还需要使用它。应用程序使用的内存空间分为用户空间和内核空间。每个32位程序的用户空间可以独占前2GB空间(指针值为正数),内核空间由所有进程共享2GB空间(指针值为负数)。因此,32位应用程序实际可以访问的内存地址空间最多只有2GB。
让 32 位程序使用超过 2GB 内存的两种方法
编辑器
这是Visual Studio 2017采用的方法,我们需要用到两个工具——editbin和dumpbin。前者用于编辑我们编译和生成的程序,以便头信息声明它支持超过2GB的内存,后者用于检查程序的头信息以验证我们是否已对其进行更改。
编辑程序以声明它支持超过 2GB 内存的命令是:
editbin /largeaddressaware xxx.exe其中xxx.exe就是我们要修改的程序。您可以使用相对路径或绝对路径(如果路径中有空格,请记住加上引号)。
验证这个程序是否被改变的命令是:
dumpbin /headers xxx.exe | more同理,xxx.exe就是我们刚才修改要检查的程序。您可以使用相对路径或绝对路径。
使用dumpbin查看我们编辑editbin前后的程序头信息,得到如下两张图:


请注意,FILE HEADER VALUES 块的倒数第二行有更多的应用程序可以处理大 (>2GB) 地址。
如果没找到,那肯定是你的命令执行出错了。检查!最常见的错误是执行后发现没有这样的命令。是的,editbin 命令从何而来?您可以在开始菜单的 Visual Studio 文件夹中找到 VS 2017 的开发人员命令提示符。运行此启动的命令行包含 editbin 和 dumpbin。

如果希望在编译 Visual Studio 时能够自动调用此工具,请参考:LargeAddressAware Visual Studio 2015 C#。
编译到 AnyCPU(首选 32 位)
这是本文比较推荐的方法,也是最简单的方法。方法是打开入口程序集的属性页,选择“Target Platform”为“AnyCPU”,然后勾选“Prefer 32-bit”。应该注意的是,这种生成方法仅在.Net Framework 4. 5及更高版本中可用。
关于 AnyCPU (Prefer 32-bit) 和 x86 生成方式的区别,请参见:Visual Studio 2012 中“Prefer 32-bit”设置的目的是什么,它实际上是如何工作的?。
语句支持2GB以上内存后可以使用多少内存?
对于 32 位操作系统,程序仍然只能使用 2GB 内存,除非打开 /3GB 开关,请参阅:/3GB 了解详情。打开后,应用程序的用户态可以使用3GB内存,而内核态只能使用1GB内存。微软认为,是否开启/3GB开关是电脑设备开发者需要做的事情,开发者也需要在开启后测试驱动的性能和稳定性。
对于64位操作系统,Windows将慷慨地为此类程序贡献所有4GB的空间,因为系统本身已经具有更多可用的内存寻址空间,因此无需使用32位应用程序抢占寻址空间。 .
AWE(地址窗口扩展)
AWE 是对 Windows 内存管理功能的一组扩展。它允许应用程序获取物理内存,然后将非分页内存的视图动态映射到 32 位地址空间。尽管 32 位地址空间限制为 4GB,但非分页内存可以远大于 4GB。这允许需要大量内存的应用程序(例如大型系统)使用比 32 位地址空间支持的内存量大得多的内存量。
为了使用大容量内存,除了使用AWE之外,还有一件不能少的东西,那就是PAE(Physical Address Extension)。 PAE 是基于 x86 的服务器的一项功能,它使运行 Windows Server 2003 企业版和 Windows Server 2003 数据中心版的计算机能够支持超过 4 GB 的物理内存。物理地址扩展 (PAE) 允许最多 64 GB 的物理内存用作常规 4 KB 页面,并将内核可用于扩展物理内存地址的位数从 32 扩展到 36。
一般情况下,Windows系统的PAE不会生效。只有开启PAE,windows系统才能识别4G以上的内存。在使用boot.int的系统中,要启动PAE,必须将/ PAE选项添加到boot.ini中。在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 Ultimate为例,介绍如何使用AWE来实现在打开PAE时在程序中使用超过2G内存的目的。下图显示了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中的定义可以看出,AWE只能在你的系统版本大于等于0x0500时使用。
各版本的_WIN32_WINNT值如下表所示。 AWE 不能用于 Windows 2000 以下的版本。
最低系统要求
_WIN32_WINNT 和WINVER 的最小值
Windows7
0x0601
Windows Server2008
0x0600
Windows Vista
0x0600
Windows Server2003 SP1,WindowsXP SP2
0x0502
Windows Server2003、WindowsXP
0x0501
Windows2000
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以上内存;开启PAE让系统识别4G以上内存;设置本地安全策略),我们就可以通过按照代码做一个实验。
代码是从 MSDN 中的 AWE 示例修改而来的。具体过程见代码中的注释。如果您对本示例的源代码感兴趣,可以参考 MSDN。
#include "AWE_TEST.h"
#include
#include
#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;
}
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/shoujiruanjian/article-378997-1.html
1依然没有躲过淘汰的命运
加油