對 .NET程式2G虛擬地址緊張崩潰 的最後一次反思

2023-11-24 18:00:57

一:背景

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 開關,那如何啟動呢?

  1. 開啟程式級的 Large Address Aware

這個 Large Address Aware 欄位俗稱大地址,途徑就是在 PE 頭裡開啟一個開關,讓Windows載入器決定是否給程式開啟 3G 的綠色通道。

當然看 PE頭 的工具有很多,對於.NET程式個人感覺最好的就是用 DnSpy,它把 File Header 中的 Characteristics 欄位具化了,我們選中 Large Address Aware 核取方塊然後儲存,截圖如下:

  1. 開啟機器級別 3G 開關

在32bit作業系統上讓使用者態程式吃到 3G 記憶體這對作業系統來說是非常謹慎的,畢竟這對核心態是非常不公平的,言外之意就是讓出自己的 1G 給使用者態,這騷操作可能就會把自己坑慘,謹慎起見需要人工開啟機器級別的 3G 開關,命令如下:


bcdedit /set IncreaseUserVa 3072

做了這兩步之後,繼續讓程式跑起來,截圖如下:

從圖中可以清晰的看到,終於有出息了。

更多作業系統設定,可參考這篇文章:https://www.autodesk.com.cn/support/technical/article/caas/sfdcarticles/sfdcarticles/CHS/How-to-enable-a-3GB-switch-on-Windows-Vista-Windows-7-or-Windows-XP-s.html?v=2018

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 。

三:總結

本篇說了這麼多,其實都是一些不得已而為之的事情,很心酸,這世上很多東西不是靠技術就能解決的,更需要靠人情事故!

圖片名稱