一:背景
1. 講故事
最近接連遇到了幾起 2G 虛擬地址緊張 導致的程式崩潰,基本上 90% 都集中在醫療行業,真的很無語,他們用的都是一些上古的 XP,Windows7 x86,我也知道技術人很難也基本無法推動硬體系統和裝置的升級,這裡蘊含了巨大的人情世故。
寫這一篇的目的是想系統化的整理一下如何配置 3G 開關讓程式吃到更多的記憶體,讓程式崩潰的不那麼頻繁一些,以及如何驗證是否成功開啟!
二:32位作業系統
1. 測試程式碼
首先大家要有一個理念:就是 32bit系統上跑的程式,預設只能吃到 2G 記憶體,因為這涉及到公平,使用者態吃2G,核心態吃2G,為了方便演示,向一個 List 塞入 5000w 的 string,大概佔用 2G 記憶體,然後把程式跑在 Windows7 32bit 作業系統上。
static void Main(string[] args)
{
var list = new List<string>();
for (int i = 0; i < 50000000; i++)
{
list.Add(i.ToString());
if (i % 10000 == 0) { Console.WriteLine($"i={i}"); }
}
Console.WriteLine("ok");
Console.ReadLine();
}
從圖中可以清楚的看到當記憶體到了631M
的時候就扛不住了,可能有些朋友好奇,為什麼才這麼點就不行了,這是因為 List 的底層是 2倍 擴容,所以記憶體大概會漲到 0.63G + 1.2G = 1.83G
。
有些朋友可能會問,這不是還沒到2G嗎?一般來說記憶體到了 1.2G+ 的時候崩潰風險就會劇增,這個要謹記!
2. 如何解決
剛才也說了,醫療行業現狀如此,只能透過人情世故去推動,那這 2G 資料真的無處安放嗎? 這時候就只能啟動 3G 開關,那如何啟動呢?
- 開啟程式級的 Large Address Aware
這個 Large Address Aware 欄位俗稱大地址,途徑就是在 PE 頭裡開啟一個開關,讓Windows載入器決定是否給程式開啟 3G 的綠色通道。
當然看 PE頭 的工具有很多,對於.NET程式個人感覺最好的就是用 DnSpy,它把 File Header 中的 Characteristics 欄位具化了,我們選中 Large Address Aware 核取方塊然後儲存,截圖如下:
- 開啟機器級別 3G 開關
在32bit作業系統上讓使用者態程式吃到 3G 記憶體這對作業系統來說是非常謹慎的,畢竟這對核心態是非常不公平的,言外之意就是讓出自己的 1G 給使用者態,這騷操作可能就會把自己坑慘,謹慎起見需要人工開啟機器級別的 3G 開關,命令如下:
bcdedit /set IncreaseUserVa 3072
做了這兩步之後,繼續讓程式跑起來,截圖如下:
從圖中可以清晰的看到,終於有出息了。
3. 如何驗證是否開啟了 3G
這確實是一個好問題,最簡單的方式就是用!address
觀察下地址空間。
0:000> !address
BaseAddr EndAddr+1 RgnSize Type State Protect Usage
-----------------------------------------------------------------------------------------------
...
+ bffde000 bffdf000 1000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE TEB [~0; aa4.fb8]
+ bffdf000 bffe0000 1000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE PEB [aa4]
+ bffe0000 bfff0000 10000 MEM_PRIVATE MEM_RESERVE PAGE_NOACCESS <unknown>
0:000> ? bfff0000/0x100000
Evaluate expression: 3071 = 00000bff
上面卦中的 bfff0000
轉換過來就是 3G,如果你看到的是這個值,那就恭喜你啦!
如果有朋友想問如何驗證 dump程式是否開啟了大地址,這個可以用windbg提供的 !dh 命令。
0:000> lm
start end module name
001e0000 001e8000 ConsoleApp4 C (pdb symbols) D:\code\MyApplication\ConsoleApp4\obj\x86\Debug\ConsoleApp4.pdb
66dd0000 678c8000 mscorlib_ni (deferred)
678d0000 67e61000 mscorwks (deferred)
6c7a0000 6c83b000 msvcr80 (deferred)
...
0:000> !dh ConsoleApp4
File Type: EXECUTABLE IMAGE
FILE HEADER VALUES
14C machine (i386)
3 number of sections
EDB20AC7 time date stamp
0 file pointer to symbol table
0 number of symbols
E0 size of optional header
122 characteristics
Executable
App can handle >2gb addresses
32 bit word machine
如果看到上面卦中的 App can handle >2gb addresses
字樣就表示你開啟成功啦!
三:64位作業系統
1. 如何吃更多記憶體
在 x64系統上就方便多了, 只需要做第一步開啟 Large Address Aware
即可,畢竟 x64系統 的虛擬地址空間不要太充足,在 48根地址匯流排上就是2的48次方,所以開啟大地址後,會給 x32 程式4G的定址空間,即 2 的 32 次方。
接下來直接把剛才的 ConsoleApp4.exe 程式從 Windows7 x86 搬遷到 Windows 10 x64 系統上,然後用 windbg 附加執行, 跑完後使用 !address
檢視。
0:007> !address
BaseAddr EndAddr+1 RgnSize Type State Protect Usage
-----------------------------------------------------------------------------------------------
+ 0 c60000 c60000 MEM_FREE PAGE_NOACCESS Free
...
+ ff671000 ff680000 f000 MEM_FREE PAGE_NOACCESS Free
+ ff680000 ff6b3000 33000 MEM_MAPPED MEM_COMMIT PAGE_READONLY Other [NLS Tables]
+ ff6b3000 ffff0000 93d000 MEM_FREE PAGE_NOACCESS Free
0:007> ? ffff0000 /0x100000
Evaluate expression: 4095 = 00000fff
如果在你的卦中也看到了上面的 ffff0000
,那就恭喜你,你程式的記憶體定址空間擴充套件到了 4G 。
三:總結
本篇說了這麼多,其實都是一些不得已而為之的事情,很心酸,這世上很多東西不是靠技術就能解決的,更需要靠人情事故!