本文正在参加「金石计划 . 瓜分6万现金大奖」
在 Windows 内存管理知识总结一篇中,我提到了
- 在 Windows 中 32 位进程只能使用 2GB 的虚拟内存
- Windows 为我们提供了扩展虚拟内存到 3GB 的方式
本篇会继续介绍
- 扩展虚拟内存具体如何操作
- 对于线上产品我们应该如何落地此优化
操作过程介绍
BCDEdit \set increaseuserva 3072
- 在 XP 以上系统,需要用管理员模式打开 cmd,输入
BCDEdit \set increaseuserva 3072
,会设置系统参数 increaseuserva 3072,其中 3072 表示 3GB - 在 XP 系统,需要修改 boot.ini
- 详细内容可参考 learn.microsoft.com/zh-cn/windo…
检验设置是否生效
- 再次打开 cmd,输入
bcdedit
- 打开我们的应用,使用 vmmap 查看内存情况
图中可以发现,Total + Free 并没有达到 3GB,仍然为 2GB,这是怎么回事呢? 我们除了设置系统参数外,还需要修改应用的链接器参数,下面看看具体要怎么做
设置连接器参数 /LARGEADDRESSAWARE
- 我的 Visual Studio 版本比较古老,新版本应该在相似的位置
- dumpbin.exe 在
Microsoft Visual Studio xx\VC\bin\
目录下 - 打开 cmd,输入
dumpbin /HEADERS {your exe path}
,可以看到Application can handle large (>2GB) address
,证明连接器参数设置成功
再次检查
- 可以看到,Total + Free 达到了 3GB
各位仔细算一下~ Total + Free 其实等于 4GB ,这又是怎么回事?
是因为我的 windows 操作系统为 64 位,在 64 位系统上运行 LargeAddress 模式的 32 位程序,虚拟内存会被扩展到 4GB,为什么会有这样的结果,后文会具体介绍。
64 位 Windows 操作系统运行 win32 程序
在 64 位系统上除了上面提到的虚拟内存会被扩展到 4GB,还有额外一点要注意: 不需要像 32 位系统一样设置 bcdedit,只需设置连接器参数 /LARGEADDRESSAWARE
即可。
32位进程虚拟内存地址空间不是一共只有 4GB,为什么在 64 位操作系统下能扩展到 4GB?这不是占用了内核空间么?
实际上,并没有占用内核空间地址。 原因是 32 位进程跑在 64 位系统时,实际是运行的 64 位进程,所以虚拟内存地址空间为 2^64
,低地址 4GB 远达不到内核空间的位置。下面附上一张 64 位进程的虚拟地址空间图
如何落地 LargeAddress 优化?
前面提到过,32 位系统下,需要通过 cmd 设置系统参数,我们总不能要求用户去设置吧?那么有没有能让用户花最小代价设置的方案呢?
我们能想到最直接的方案是,在代码中运行 bcdedit
命令,代码如下
ShellExecute(0, L"runas", L"BCDEdit.exe", L"/set increaseuserva 3072", NULL, SW_SHOWNORMAL);
复制代码
遗憾的是,这样做并不能成功,会报找不到 BCDEdit.exe
的错误,但直接在 cmd 中却能设置成功,这又是为什么呢?
因为我测试代码还是运行在 64 位系统上,在 64 位跑 32 位进程时,由于文件系统重定向机制(File System Redirection)程序无法访问 SysWOW64
文件夹,所以需要我们做些额外的操作
BOOL bWow64 = false;
IsWow64Process(GetCurrentProcess(), &bWow64);
if (bWow64)
{
PVOID OldValue = NULL;
if (Wow64DisableWow64FsRedirection(&OldValue))
{
ShellExecute(0, L"runas", L"BCDEdit.exe", L"/set increaseuserva 3072", NULL, SW_SHOWNORMAL);
Wow64RevertWow64FsRedirection(&OldValue);
}
}
复制代码
使用上述代码后,可以成功在 64 位系统上设置 bcdedit 系统参数了。更详细可以参考微软社区大神回答我的内容 learn.microsoft.com/en-us/answe…
但是,等等!我们刚刚不是说过 64 位系统不需要设置 bcdedit
么? 好吧,我这步确实走了弯路,不过在解决问题的过程中也学到了更多知识,另外微软社区的大神也希望提问者多了解 Windows 系统的一些机制
还要提到的是,通过代码执行 cmd 时,仍会弹出让用户选择是否允许以管理员模式运行 cmd 的界面,只有用户同意打开,系统参数才能设置成功。
LARGEADRRESSAWARE 的风险及如何避免
前面我们详细介绍了如何设置 LargeAddress 模式,那么这么做会有什么风险么?
结论是 不兼容 LargeAddress 模式的代码分配超过 2GB 的高地址时,可能造成指针截断,或者新分配的地址为 0
具体例子我没有调研,猜测是某些程序会分配固定的地址,在地址超过 2GB 时可能取到的地址就变为 0 了,还有可能是某特定类型的指针,只能寻址到 2GB,超过 2GB 的部分会被截断。
那么怎么才能化解风险呢?
需要我们使用 Top Down 内存分配模式去进行全面测试,正常情况下内存地址分配是从低到高的,如果改为从高到低分配,我们就能优先使用 2GB 之上的地址,就可以验证程序中是否存在指针接截断的问题了。
那么,要怎么做呢?
需要修改注册表 [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management]
新增 AllocationPreference=00100000
,具体看下图提示
设置成功后,一定要重启计算机后才能生效!