突破 Windows 的極限

Pushing the Limits of Windows: Physical Memory - Microsoft Community Hub

首次發佈於 2008 年 7 月 21 日在 TechNet 上

 

這是我將在接下來的幾個月中撰寫的系列博客文章中的第一篇,名爲“突破 Windows 的限制”,該系列文章描述了 Windows 和應用程序如何使用特定資源、資源的許可和實現衍生的限制、如何衡量資源的使用情況。用途以及如何診斷泄漏。爲了能夠有效地管理 Windows 系統,您需要了解 Windows 如何管理物理資源(例如 CPU 和內存)以及邏輯資源(例如虛擬內存、句柄和窗口管理器對象)。瞭解這些資源的限制以及如何跟蹤其使用情況,使您能夠將資源使用情況歸因於使用它們的應用程序,針對特定工作負載有效調整系統大小,並識別泄漏資源的應用程序。

這是整個“挑戰極限”系列的索引。雖然它們可以獨立存在,但它們假設您按順序閱讀它們。

突破 Windows 的極限:物理內存

突破 Windows 的極限:虛擬內存

突破 Windows 的極限:分頁池和非分頁池

突破 Windows 的極限:進程和線程

突破 Windows 的極限:句柄

突破 Windows 的極限:USER 和 GDI 對象 – 第 1 部分

突破 Windows 的極限:USER 和 GDI 對象 – 第 2 部分

物理內存

計算機上最基本的資源之一是物理內存。 Windows 的內存管理器負責用活動進程、設備驅動程序和操作系統本身的代碼和數據填充內存。由於大多數系統在運行時訪問的代碼和數據超出了物理內存的容量,因此物理內存本質上是瞭解隨着時間的推移使用的代碼和數據的窗口。因此,內存量會影響性能,因爲當進程或操作系統所需的數據或代碼不存在時,內存管理器必須從磁盤將其引入。

除了影響性能之外,物理內存量還會影響其他資源限制。例如,非分頁池、物理內存支持的操作系統緩衝區的數量顯然受到物理內存的限制。物理內存也會影響系統虛擬內存限制,該限制大約是物理內存大小加上任何頁面文件的最大配置大小的總和。物理內存還可以間接限制最大進程數,我將在以後有關進程和線程限制的文章中討論這一點。

Windows 服務器內存限制

Windows 對物理內存的支持取決於硬件限制、許可、操作系統數據結構和驅動程序兼容性。 MSDN 中 的 Windows 版本內存限制 頁面 按 SKU 記錄了不同 Windows 版本以及版本內的限制。

您可以看到所有 Windows 版本的服務器 SKU 之間的物理內存支持許可差異。例如,32位版本的Windows Server 2008 Standard僅支持4GB,而32位Windows Server 2008 Datacenter支持64GB。同樣,64位Windows Server 2008 Standard支持32GB,64位Windows Server 2008 Datacenter可以處理高達2TB的容量。市面上的 2TB 系統並不多,但 Windows Server 性能團隊知道有幾個,其中包括他們實驗室中曾經擁有的一個。這是該系統上運行的任務管理器的屏幕截圖:

標題爲“突破 Windows 的極限:物理內存”的博客文章的縮略圖 1

Windows Server 2003 Datacenter Edition 支持的最大 32 位限制爲 128GB,這是因爲內存管理器用於跟蹤物理內存的結構會在較大的系統上消耗過多的系統虛擬地址空間。內存管理器跟蹤稱爲 PFN 數據庫的數組中的每個內存頁,並且爲了提高性能,它將整個 PFN 數據庫映射到虛擬內存中。由於它用 28 字節的數據結構來表示內存的每個頁面,因此 128GB 系統上的 PFN 數據庫大約需要 980MB。 32位Windows有一個由硬件定義的4GB虛擬地址空間,默認情況下它在當前執行的用戶模式進程(例如記事本)和系統之間劃分。因此,980MB 幾乎消耗了可用的 2GB 系統虛擬地址空間的一半,只留下 1GB 用於映射內核、設備驅動程序、系統緩存和其他系統數據結構,這是一個合理的劃分:

標題爲“突破 Windows 的極限:物理內存”的博客文章的縮略圖 2

這也是爲什麼內存限制表列出了使用 4GB 調整啓動時相同 SKU 的下限(稱爲 4GT,並通過 Boot.ini 的 /3GB 或 /USERVA 以及 Bcdedit 的 /Set IncreaseUserVa 啓動選項啓用),因爲 4GT 將分割移動到給用戶模式3GB,只給系統留下1GB。爲了提高性能,Windows Server 2008 通過將其最大 32 位物理內存支持降低到 64GB 來保留更多的系統地址空間。

內存管理器可以通過根據需要將 PFN 數據庫的各個部分映射到系統地址來容納更多內存,但這會增加複雜性,並可能因映射和取消映射操作的額外開銷而降低性能。直到最近,系統才變得足夠大,可以考慮這一點,但由於系統地址空間不是在 64 位 Windows 上映射整個 PFN 數據庫的約束,因此對更多內存的支持留給了 64 位 Windows。

64 位 Windows Server 2008 Datacenter 的最大 2TB 限制並非來自任何實施或硬件限制,但 Microsoft 將僅支持他們可以測試的配置。截至 Windows Server 2008 發佈時,任何地方可用的最大系統爲 2TB,因此 Windows 限制了其物理內存的使用。

Windows 客戶端內存限制

64 位 Windows 客戶端 SKU 支持不同數量的內存作爲 SKU 差異化功能,低端 Windows XP Starter 爲 512MB,Vista Ultimate 爲 128GB,Windows 7 Ultimate 爲 192GB。然而,所有 32 位 Windows 客戶端 SKU(包括 Windows Vista、Windows XP 和 Windows 2000 Professional)均支持最大 4GB 物理內存。 4GB是標準x86內存管理模式下可訪問的最高物理地址。最初,甚至不需要考慮在客戶端上支持超過 4GB 的內存,因爲這種內存量很少見,即使在服務器上也是如此。

然而,當Windows XP SP2正在開發時,可以預見超過4GB的客戶端系統,因此Windows團隊開始在超過4GB內存的系統上廣泛測試Windows XP。 Windows XP SP2 還在實現不執行內存的硬件上默認啓用物理地址擴展 (PAE) 支持,因爲它是數據執行保護 (DEP) 所必需的,但這也支持超過 4GB 的內存。

他們發現,許多系統會崩潰、掛起或無法啓動,因爲某些設備驅動程序(通常是在客戶端而非服務器上找到的視頻和音頻設備驅動程序)未編程爲期望物理地址大於 4GB。結果,驅動程序截斷了這些地址,導致內存損壞和損壞副作用。服務器系統通常具有更通用的設備以及更簡單和更穩定的驅動程序,因此通常不會出現這些問題。有問題的客戶端驅動程序生態系統導致客戶端 SKU 決定忽略駐留在 4GB 以上的物理內存,儘管理論上它們可以解決該問題。

32 位客戶端有效內存限制

雖然 4GB 是 32 位客戶端 SKU 的許可限制,但有效限制實際上較低,並且取決於系統的芯片組和連接的設備。原因是物理地址映射不僅包括 RAM,還包括設備內存,x86 和 x64 系統映射 4GB 地址邊界以下的所有設備內存,以保持與不知道如何處理的 32 位操作系統兼容地址大於 4GB。如果系統具有 4GB RAM 和設備(例如視頻、音頻和網絡適配器),這些設備在其設備內存中實現了 Windows,總容量爲 500MB,則 4GB RAM 中的 500MB 將駐留在 4GB 地址邊界之上,如下所示:

標題爲“突破 Windows 的極限:物理內存”的博客文章的縮略圖 3

結果是,如果您的系統具有 3GB 或更多內存,並且運行的是 32 位 Windows 客戶端,則您可能無法充分利用所有 RAM。在 Windows 2000、Windows XP 和 Windows Vista RTM 上,您可以在“系統屬性”對話框、任務管理器的“性能”頁面中查看 Windows 可訪問的 RAM 量,在 Windows XP 和 Windows Vista(包括 SP1)上,可以在 Msinfo32 和溫弗公用事業。在 Window Vista SP1 上,其中一些位置已更改爲顯示已安裝的 RAM,而不是可用的 RAM,如本 知識庫文章 中所述 。

在我的 4GB 筆記本電腦上,當使用 32 位 Vista 啓動時,可用物理內存量爲 3.5GB,如 Msinfo32 實用程序中所示:

標題爲“突破 Windows 的極限:物理內存”的博客文章的縮略圖 4

您可以使用Alex Ionescu 的 Meminfo 工具 查看物理內存佈局 (他 爲我與 David Solomon 共同創作的 《Windows 內部結構 》第 5 版做出了貢獻 )。這是當我使用 -r 開關在該系統上運行 Meminfo 來轉儲物理內存範圍時的輸出:

標題爲“突破 Windows 的極限:物理內存”的博客文章的縮略圖 5

請注意內存地址範圍中的間隙是從頁 9F0000 到頁 100000,另一個間隙是從 DFE6D000 到 FFFFFFFF (4GB)。然而,當我使用 64 位 Vista 啓動該系統時,所有 4GB 都顯示爲可用,您可以看到 Windows 如何使用 4GB 邊界之上剩餘的 500MB RAM:

標題爲“突破 Windows 的極限:物理內存”的博客文章的縮略圖 6

4GB以下的空間被什麼佔據了?設備管理器可以回答這個問題。要進行檢查,請啓動“devmgmt.msc”,在“視圖”菜單中選擇“按連接劃分的資源”,然後展開“內存”節點。在我的筆記本電腦上,毫不奇怪,映射設備內存的主要消耗者是顯卡,它消耗 256MB 的內存,範圍爲 E0000000-EFFFFFFF:

標題爲“突破 Windows 的極限:物理內存”的博客文章的縮略圖 7

其他雜項設備佔其餘大部分,並且 PCI 總線爲設備保留了額外的範圍,作爲固件在引導期間使用的保守估計的一部分。

在配備大顯卡的高端遊戲系統上,低於 4GB 的內存地址消耗可能會非常嚴重。例如,我從一家精品遊戲設備公司購買了一臺配備 4GB RAM 和兩個 1GB 顯卡的設備。我沒有指定操作系統版本,並假設他們會安裝 64 位 Vista,但它附帶了 32 位版本,因此 Windows 只能訪問 2.2GB 內存。在我安裝 64 位 Windows 後,您可以在系統的 Meminfo 輸出中看到從 8FEF0000 到 FFFFFFFF 的巨大內存漏洞:

標題爲“突破 Windows 的極限:物理內存”的博客文章的縮略圖 8

設備管理器顯示超過 2GB 的空洞中有 512MB 用於顯卡(每個 256MB),而且固件似乎爲動態映射保留了更多空間,或者因爲它的估計比較保守:

標題爲“突破 Windows 的極限:物理內存”的博客文章的縮略圖 9

即使只有 2GB 的系統也可能無法在 32 位 Windows 下使用所有內存,因爲芯片組會積極爲設備保留內存區域。我們的共享家庭計算機是我們幾個月前從一家主要 OEM 購買的,報告顯示已安裝的 2GB 內存中只有 1.97GB 可用:

標題爲“突破 Windows 的極限:物理內存”的博客文章的縮略圖 10

從 7E700000 到 FFFFFFFF 的物理地址範圍由 PCI 總線和設備保留,這留下了理論上最大的 7E700000 字節(1.976GB)的物理地址空間,但甚至其中一些是爲設備內存保留的,這解釋了 Windows 報告的原因1.97GB。

標題爲“突破 Windows 的極限:物理內存”的博客文章的縮略圖 11

由於設備供應商現在必須向 Microsoft 的 Windows 硬件質量實驗室 (WHQL) 提交 32 位和 64 位驅動程序以獲得驅動程序簽名證書,因此當今大多數設備驅動程序可能可以處理 4GB 以上的物理地址。然而,32 位 Windows 將繼續忽略其上方的內存,因爲仍然存在一些難以衡量的風險,並且 OEM 正在(或至少應該)轉向 64 位 Windows,這不是問題。

最重要的是,無論數量多少,您都可以通過 64 位 Windows 充分利用系統內存(提高 SKU 的限制),如果您購買的是高端遊戲系統,您絕對應該要求 OEM 將 64 位設置爲 64 位。工廠裏就裝有窗戶。

你有足夠的內存嗎?

無論您的系統有多少內存,問題是,它足夠嗎?不幸的是,沒有硬性規則可以讓您確定地知道。您可以使用一個通用準則,該準則基於隨着時間的推移監控系統的“可用”內存,特別是當您運行內存密集型工作負載時。 Windows 將可用內存定義爲未分配給進程、內核或設備驅動程序的物理內存。顧名思義,如果需要,可用內存可分配給進程或系統。當然,內存管理器試圖通過將其用作文件緩存(備用列表)以及歸零內存(零頁列表)來充分利用該內存,並且 Vista 的 Superfetch 功能將數據和代碼預取到備用內存中列出並優先考慮可能在不久的將來使用的數據和代碼。

如果可用內存變得稀缺,則意味着進程或系統正在積極使用物理內存,如果它在很長一段時間內保持接近於零,您可能可以通過添加更多內存來受益。有多種方法可以跟蹤可用內存。在 Windows Vista 上,您可以通過查看任務管理器中的物理內存使用歷史記錄來間接跟蹤可用內存,並使其隨着時間的推移保持接近 100%。這是我的 8GB 桌面系統上任務管理器的屏幕截圖(嗯,我想我的內存可能太多了!):

標題爲“突破 Windows 的極限:物理內存”的博客文章的縮略圖 12

在所有版本的 Windows 上,您可以通過在內存性能計數器組中添加可用字節計數器,使用性能監視器繪製可用內存圖表:

標題爲“突破 Windows 的極限:物理內存”的博客文章的縮略圖 13

您可以在Process Explorer 的 “系統信息”對話框 中查看瞬時值 ,或者在 Vista 之前的 Windows 版本上,在“任務管理器”的“性能”頁面上查看瞬時值。

突破 Windows 的極限

在 CPU、內存和磁盤中,內存對於整體系統性能通常是最重要的。越多越好。 64 位 Windows 是確保您充分利用所有優勢的最佳選擇,並且 64 位 Windows 還具有其他性能優勢,我將在以後的突破極限博客文章中討論這些優勢關於虛擬內存限制。

突破 Windows 的極限:虛擬內存

在我的第一篇突破 Windows 限制的文章中,我討論了物理內存限制,包括許可、實現和驅動程序兼容性所施加的限制。這是整個“挑戰極限”系列的索引。雖然它們可以獨立存在,但它們假設您按順序閱讀它們。

突破 Windows 的極限:物理內存

突破 Windows 的極限:虛擬內存

突破 Windows 的極限:分頁池和非分頁池

突破 Windows 的極限:進程和線程

突破 Windows 的極限:句柄

突破 Windows 的極限:USER 和 GDI 對象 – 第 1 部分

突破 Windows 的極限:USER 和 GDI 對象 – 第 2 部分

這次我將注意力轉向另一個基本資源,虛擬內存。虛擬內存將程序的內存視圖與系統的物理內存分開,因此操作系統決定何時以及是否將程序的代碼和數據存儲在物理內存中,以及何時將其存儲在文件中。虛擬內存的主要優點是它允許比物理內存中容納的進程同時執行更多的進程。

雖然虛擬內存具有與物理內存限制相關的限制,但虛擬內存具有源自不同來源且因消費者而異的限制。例如,虛擬內存限制適用於運行應用程序的各個進程、操作系統以及整個系統。當您閱讀本文時,請務必記住,虛擬內存,顧名思義,與物理內存沒有直接聯繫。 Windows爲文件緩存分配一定數量的虛擬內存並不決定它在物理內存中實際緩存多少文件數據;它可以是任何數量,從無到大於可通過虛擬內存尋址的數量。

進程地址空間

每個進程都有自己的虛擬內存,稱爲地址空間,它將其執行的代碼以及代碼引用和操作的數據映射到其中。 32 位進程使用 32 位虛擬內存地址指針,這爲 32 位進程可以尋址的虛擬內存量創建了 4GB (2^32) 的絕對上限。然而,爲了使操作系統能夠在不改變地址空間的情況下引用自己的代碼和數據以及當前正在執行的進程的代碼和數據,操作系統使其虛擬內存在每個進程的地址空間中可見。默認情況下,32 位版本的 Windows 在系統和活動進程之間均勻劃分進程地址空間,爲每個進程創建 2GB 的限制:

 圖像

應用程序可能使用堆 API、.NET 垃圾收集器或 C 運行時 malloc 庫來分配虛擬內存,但在幕後所有這些都依賴於VirtualAlloc API。當應用程序用完地址空間時,VirtualAlloc 以及位於其之上的內存管理器將返回錯誤(由 NULL 地址表示)。 Testlimit 實用程序是我爲Windows Internals 第 4 版編寫的,用於演示各種 Windows 限制,它會重複調用 VirtualAlloc,直到在您指定 –r 開關時出現錯誤。因此,當您在 32 位 Windows 上運行 32 位版本的 Testlimit 時,它將消耗整個 2GB 的地址空間:

圖像

2010 MB 並不完全是 2GB,但 Testlimit 的其他代碼和數據(包括其可執行文件和系統 DLL)解釋了這一差異。您可以通過在Process Explorer中查看其虛擬大小來了解其消耗的地址空間總量:

圖像

某些應用程序(例如 SQL Server 和 Active Directory)管理大型數據結構,並且可以同時加載到其地址空間的數據越多,性能就越好。因此,Windows NT 4 SP3 引入了一個啓動選項/3GB,該選項通過將系統地址空間大小減少到 1GB 來爲進程提供 4GB 地址空間中的 3GB,而 Windows XP 和 Windows Server 2003 引入了 /userva 選項,該選項將分割 2GB 到 3GB 之間的任意位置:

 圖像

然而,要利用 2GB 行以上的地址空間,進程必須在其可執行映像中設置“大地址空間感知”標誌。對額外虛擬內存的訪問是可選的,因爲某些應用程序假設它們最多隻能獲得 2GB 的地址空間。由於引用低於 2GB 的地址的指針的高位始終爲零,因此它們會使用指針中的高位作爲自己數據的標誌,當然在引用數據之前將其清除。如果它們使用 3GB 地址空間運行,它們會無意中截斷值大於 2GB 的指針,從而導致程序錯誤,包括可能的數據損壞。

Windows 中的所有 Microsoft 服務器產品和數據密集型可執行文件都標記有大地址空間感知標誌,包括 Chkdsk.exe、Lsass.exe(在域控制器上託管 Active Directory 服務)、Smss.exe(會話管理器)和Esentutl.exe(Active Directory Jet 數據庫修復工具)。您可以使用 Visual Studio 附帶的 Dumpbin 實用程序查看圖像是否具有該標誌:

圖像

Testlimit 還被標記爲大地址感知,因此如果您在使用 3GB 用戶地址空間啓動時使用 –r 開關運行它,您將看到如下內容:

圖像

由於 64 位 Windows 上的地址空間遠大於 4GB(我稍後將對此進行描述),因此 Windows 可以爲 32 位進程提供最大 4GB 的尋址空間,並將其餘部分用於操作系統的虛擬內存。如果您在 64 位 Windows 上運行 Testlimit,您將看到它消耗了整個 32 位可尋址地址空間:

圖像

64 位進程使用 64 位指針,因此其理論最大地址空間爲 16 艾字節 (2^64)。然而,Windows 並沒有在活動進程和系統之間平均劃分地址空間,而是在地址空間中爲進程和其他各種系統內存資源定義一個區域,例如系統頁表條目 (PTE)、文件緩存、分頁池和非分頁池。

在 IA64 和 x64 版本的 Windows 上,進程地址空間的大小是不同的,其中通過平衡應用程序所需的內存成本和支持所需的開銷(頁表頁和轉換後備緩衝區 - TLB - 條目)來選擇大小。地址空間。在 x64 上,該大小爲 8192GB (8TB),在 IA64 上爲 7168GB(7TB - 與 x64 的 1TB 差異來自於 IA64 上的頂級頁目錄爲 Wow64 映射保留插槽)。在 Windows 的 IA64 和 x64 版本上,各種資源地址空間區域的大小均爲 128GB(例如,非分頁池分配了 128GB 的​​地址空間),但文件緩存除外,分配了 1TB。因此,64 位進程的地址空間看起來像這樣:

圖像

該圖並未按比例繪製,因爲即使是 8TB,更不用說 128GB,也只是一小部分。可以這麼說,就像我們的宇宙一樣,64 位進程的地址空間中有很多空白。

當您使用 –r 開關在 64 位 Windows 上運行 64 位版本的 Testlimit (Testlimit64) 時,您將看到它消耗 8TB,這是它可以管理的地址空間部分的大小:

圖像

圖像 

承諾內存

Testlimit 的 –r 開關讓它保留虛擬內存,但實際上並不提交它。保留的虛擬內存實際上不能存儲數據或代碼,但應用程序有時會使用保留來創建一大塊虛擬內存,然後根據需要提交它,以確保提交的內存在地址空間中是連續的。當進程提交虛擬內存區域時,操作系統保證它可以在物理內存或磁盤上維護該進程存儲在內存中的所有數據。這意味着進程可能會遇到另一個限制:提交限制

正如您從提交保證的描述中所期望的那樣,提交限制是物理內存和分頁文件大小的總和。實際上,並非所有物理內存都計入提交限制,因爲操作系統保留部分物理內存供自己使用。所有活動進程提交的虛擬內存量(稱爲當前提交費用)不能超過系統提交限制。當達到提交限制時,提交內存的虛擬分配將失敗。這意味着,即使是標準 32 位進程,在達到 2GB 地址空間限制之前也可能會出現虛擬內存分配失敗的情況。

當前的提交費用和提交限制由 Process Explorer 在“提交費用”部分的“系統信息”窗口以及“提交歷史記錄”條形圖和圖表中進行跟蹤:

圖像  圖像

Vista 和 Windows Server 2008 之前的任務管理器以類似方式顯示當前提交費用和限制,但在其圖表中將當前提交費用稱爲“PF 使用情況”:

圖像

在 Vista 和 Server 2008 上,任務管理器不顯示提交費用圖表,並使用“頁面文件”標記當前提交費用和限制值(儘管事實上,即使您沒有頁面文件,它們也將是非零值) :

圖像

您可以通過使用 -m 開關運行 Testlimit 來強調提交限制,這會指示它分配已提交的內存。 32 位版本的 Testlimit 在達到提交限制之前可能會也可能不會達到其地址空間限制,具體取決於物理內存的大小、分頁文件的大小以及運行時的當前提交費用。如果您運行的是 32 位 Windows,並且想要了解系統在達到提交限制時的行爲方式,只需運行 Testlimit 的多個實例,直到其中一個實例在耗盡其地址空間之前達到提交限制。

請注意,默認情況下,分頁文件配置爲增長,這意味着當提交費用接近提交限制時,提交限制將會增長。即使當分頁文件達到最大大小時,Windows 也會保留一些內存,其內部調整以及緩存數據的應用程序可能會釋放更多內存。 Testlimit 預見到了這一點,當它達到提交限制時,它會休眠幾秒鐘,然後嘗試分配更多內存,無限期地重複此操作,直到您終止它。

如果您運行 64 位版本的 Testlimit,它幾乎肯定會在耗盡其地址空間之前達到提交限制,除非物理內存和分頁文件總和超過 8TB(如前所述,這是 64 位版本的大小)位應用程序可訪問的地址空間。以下是在我的 8GB 系統上運行的 64 位 Testlimit 的部分輸出(我指定了 100MB 的分配大小以使其泄漏更快):

 圖像

這是提交歷史記錄圖,其中包含 Testlimit 暫停以允許分頁文件增長時的步驟:

圖像

當系統虛擬內存不足時,應用程序可能會失敗,並且您在嘗試常規操作時可能會收到奇怪的錯誤消息。不過,在大多數情況下,Windows 將能夠向您顯示低內存分辨率對話框,就像我運行此測試時所做的那樣:

圖像

退出 Testlimit 後,當內存管理器截斷爲適應 Testlimit 的極端提交請求而創建的分頁文件的尾部時,提交限制可能會再次下降。此處,Process Explorer 顯示當前限制遠低於 Testlimit 運行時達到的峯值:

圖像

進程提交內存

由於提交限制是一種全局資源,其消耗可能導致性能不佳、應用程序故障甚至系統故障,因此一個自然的問題是“進程貢獻了多少提交費用”?要準確回答這個問題,您需要了解應用程序可以分配的不同類型的虛擬內存。

並非進程分配的所有虛擬內存都計入提交限制。正如您所看到的,保留的虛擬內存則不然。表示磁盤上文件的虛擬內存(稱爲文件映射視圖)也不會計入限制,除非應用程序要求寫時複製語義,因爲 Windows 可以丟棄與物理內存中的視圖關聯的任何數據,然後從文件中檢索它。因此,映射其可執行文件和系統 DLL 映像的 Testlimit 地址空間中的虛擬內存不計入提交限制。有兩種類型的進程虛擬內存確實計入提交限制:私有內存和頁面文件支持的虛擬內存。

私有虛擬內存是垃圾收集器堆、本機堆和語言分配器的基礎。它被稱爲私有的,因爲根據定義它不能在進程之間共享。因此,很容易歸因於某個進程,並且 Windows 通過專用字節性能計數器跟蹤其使用情況。 Process Explorer 在進程屬性對話框的性能頁面的虛擬內存部分的專用字節列中顯示進程專用字節使用情況,並在進程屬性對話框的性能圖表頁面上以圖形形式顯示它。這是 Testlimit64 達到提交限制時的樣子:

圖像

圖像

頁面文件支持的虛擬內存更難歸屬,因爲它可以在進程之間共享。事實上,沒有特定於進程的計數器可以查看進程已分配或正在引用的數量。當您使用 -s 開關運行 Testlimit 時,它會分配頁面文件支持的虛擬內存,直到達到提交限制,但即使在消耗了超過 29GB 的提交之後,該進程的虛擬內存統計信息也沒有提供任何指示表明它是唯一的虛擬內存。負責任的:

圖像

因此,我不久前將 -l 開關添加到了 Handle 中。進程必須打開一個頁面文件支持的虛擬內存對象(稱爲節),以便在其地址空間中創建頁面文件支持的虛擬內存的映射。雖然 Windows 會保留現有的虛擬內存,即使應用程序關閉了其所在部分的句柄,但大多數應用程序仍保持句柄打開。 -l 開關打印進程已打開的頁面文件支持的部分的分配大小。以下是 Testlimit 在使用 -s 開關運行後打開的句柄的部分輸出:

圖像

您可以看到 Testlimit 以 1MB 塊的形式分配頁面文件支持的內存,如果您將其打開的所有部分的大小相加,您會發現它至少是貢獻大量提交費用的進程之一。

我應該將分頁文件設置爲多大?

也許與虛擬內存相關的最常見問題之一是,我應該將分頁文件設置爲多大?網絡上和報亭雜誌上有關 Windows 的荒謬建議層出不窮,甚至 Microsoft 也發佈了誤導性建議。幾乎所有建議都是基於將 RAM 大小乘以某個因子,常見值爲 1.2、1.5 和 2。 現在您已經瞭解了分頁文件在定義系統提交限制中所扮演的角色以及進程如何貢獻提交費用,你已經很清楚這些公式到底有多麼無用了。

由於提交限制設置了運行進程可以同時分配多少私有和頁面文件支持的虛擬內存的上限,因此合理調整分頁文件大小的唯一方法是瞭解您想要的程序的最大總提交費用同時運行。如果提交限制小於該數字,您的程序將無法分配所需的虛擬內存,並且將無法正常運行。

那麼您如何知道您的工作負載需要多少提交費用?您可能已經在屏幕截圖中注意到 Windows 跟蹤該數字並且 Process Explorer 顯示它:Peak Commit Charge。爲了優化分頁文件的大小,您應該同時啓動所有運行的應用程序,加載典型的數據集,然後記下提交費用峯值(或者在您知道達到最大負載的一段時間後查看該值) 。將頁面文件最小值設置爲該值減去系統中的 RAM 量(如果該值爲負數,請選擇一個最小大小以允許您配置的故障轉儲類型)。如果您希望爲潛在的大量提交需求留出一些喘息空間,請將最大值設置爲該數字的兩倍。

有些人認爲沒有分頁文件會帶來更好的性能,但一般來說,有分頁文件意味着 Windows 可以將修改列表上的頁面(代表未被主動訪問但尚未保存到磁盤的頁面)寫入到分頁文件,從而使該內存可用於更有用的目的(進程或文件緩存)。因此,雖然某些工作負載在沒有分頁文件的情況下可能會表現得更好,但一般來說,擁有分頁文件意味着系統可以使用更多的可用內存(不用擔心,如果沒有大的分頁文件,Windows 將無法寫入內核故障轉儲)足以容納它們)。

分頁文件配置位於系統屬性中,您可以通過在“運行”對話框中鍵入“sysdm.cpl”,單擊“高級”選項卡,單擊“性能選項”按鈕,單擊“高級”選項卡(這確實很高級)來訪問該配置,然後單擊“更改”按鈕:

圖像

您會注意到,Windows 的默認配置是自動管理頁面文件大小。在 Windows XP 和 Server 2003 上設置該選項後,Windows 將創建一個頁面文件,如果 RAM 小於 1GB,則該文件的最小大小爲 RAM 的 1.5 倍;如果 RAM 大於 1GB,則該文件的最小大小爲 RAM 的 1.5 倍,最大大小爲 RAM 的三倍。在 Windows Vista 和 Server 2008 上,最小值應足夠大以容納內核內存故障轉儲,並且是 RAM 加 300MB 或 1GB(以較大者爲準)。最大值爲 RAM 大小的三倍或 4GB,以較大者爲準。這就解釋了爲什麼我的 8GB 64 位系統上的峯值提交(在其中一張屏幕截圖中可見)是 32GB。我想無論誰編寫了該代碼,都從我提到的一本雜誌中得到了指導!

與虛擬內存相關的幾個最終限制是 Windows 支持的頁面文件的最大大小和數量。 32 位 Windows 的最大頁面文件大小爲 16TB(如果出於某種原因在非 PAE 模式下運行,則爲 4GB),64 位 Windows 的頁面文件大小在 x64 上最大爲 16TB,在 IA64 上最大爲 32TB。 Windows 8 ARM 的最大分頁文件大小爲 4GB。對於所有版本,Windows 最多支持 16 個分頁文件,其中每個分頁文件必須位於單獨的捲上。

版本

不帶 PAE 的 x86 限制

x86 w/PAE 的限制

ARM 的限制

x64 限制

IA64 的限制

Windows 7的

4GB

16TB

 

16TB

 

視窗8

 

16TB

4GB

16TB

 

Windows Server 2008 R2

     

16TB

32TB

Windows 服務器 2012

     

16TB

突破 Windows 的極限:分頁池和非分頁池

在之前的《突破極限》文章中,我描述了兩種最基本的系統資源:物理內存虛擬內存。這次我將描述兩種基本的內核資源:分頁池和非分頁池,它們基於這些資源,並且直接負責許多其他系統資源限制,包括最大進程數、同步對象和句柄。

這是整個“挑戰極限”系列的索引。雖然它們可以獨立存在,但它們假設您按順序閱讀它們。

突破 Windows 的極限:物理內存

突破 Windows 的極限:虛擬內存

突破 Windows 的極限:分頁池和非分頁池

突破 Windows 的極限:進程和線程

突破 Windows 的極限:句柄

突破 Windows 的極限:USER 和 GDI 對象 – 第 1 部分

突破 Windows 的極限:USER 和 GDI 對象 – 第 2 部分

分頁池和非分頁池充當操作系統和設備驅動程序用來存儲其數據結構的內存資源。池管理器在內核模式下運行,使用系統虛擬地址空間的區域(在推動虛擬內存的限制帖子中描述)來分配內存。內核的池管理器的運行方式與在用戶模式進程中執行的 C 運行時和 Windows 堆管理器類似。由於最小虛擬內存分配大小是系統頁面大小的倍數(x86 和 x64 上爲 4KB),因此這些輔助內存管理器將較大的分配劃分爲較小的分配,以便不會浪費內存。

例如,如果應用程序想要一個 512 字節的緩衝區來存儲一些數據,堆管理器會獲取它已分配的區域之一,並記錄前 512 字節正在使用中,返回指向該內存的指針,並將剩餘的區域放入內存中。它用於跟蹤空閒堆區域的列表上的內存。堆管理器使用空閒區域中的內存來滿足後續分配,空閒區域從分配的 512 字節區域開始。

非分頁池

內核和設備驅動程序使用非分頁池來存儲系統無法處理頁面錯誤時可能訪問的數據。內核在執行中斷服務例程(ISR)和延遲過程調用(DPC)時進入這種狀態,這些都是與硬件中斷相關的功能。當內核或設備驅動程序獲取自旋鎖時,頁錯誤也是非法的,因爲自旋鎖是唯一可以在 ISR 和 DPC 內使用的鎖類型,因此必須使用它來保護從 ISR 或 DPC 內訪問的數據結構。 DPC 和其他 ISR 或 DPC 或在內核線程上執行的代碼。驅動程序未能遵守這些規則會導致最常見的崩潰代碼IRQL_NOT_LESS_OR_EQUAL

因此,非分頁池始終保留在物理內存中,並且非分頁池虛擬內存分配給物理內存。存儲在非分頁池中的常見系統數據結構包括表示進程和線程的內核和對象、互斥體、信號量和事件等同步對象、表示爲文件對象的文件引用以及表示爲文件對象的 I/O 請求數據包 (IRP)。代表 I/O 操作。

分頁池

另一方面,分頁池之所以得名,是因爲 Windows 可以將其存儲的數據寫入分頁文件,從而允許其佔用的物理內存被重新利用。就像用戶模式虛擬內存一樣,當驅動程序或系統引用分頁文件中的分頁池內存時,會發生稱爲頁面錯誤的操作,並且內存管理器將數據讀回到物理內存中。分頁池的最大使用者(至少在 Windows Vista 及更高版本上)通常是註冊表,因爲對註冊表項和其他註冊表數據結構的引用存儲在分頁池中。表示內存映射文件的數據結構(內部稱爲)也存儲在分頁池中。

設備驅動程序使用ExAllocatePoolWithTag API 來分配非分頁和分頁池,並將所需的池類型指定爲參數之一。另一個參數是 4 字節Tag,驅動程序應該使用它來唯一標識它們分配的內存,這對於跟蹤泄漏池的驅動程序來說是一個有用的鍵,正如我稍後將展示的那樣。

查看分頁和非分頁池使用情況

有三個性能計數器指示池使用情況:

  • 池非分頁字節
  • 池分頁字節(分頁池的虛擬大小 - 有些可能會被分頁)
  • 池分頁駐留字節(分頁池的物理大小)

但是,沒有針對這些池的最大大小的性能計數器。可以使用內核調試器 !vm 命令查看它們,但對於 Windows Vista 及更高版本,要在本地內核調試模式下使用內核調試器,您必須在調試模式下啓動系統,這會禁用 MPEG2 播放。

因此,請使用 Process Explorer 查看當前分配的池大小以及最大值。要查看最大值,您需要將 Process Explorer 配置爲使用操作系統的符號文件。首先,安裝最新的Windows 調試工具包。然後運行 ​​Process Explorer 並在“選項”菜單中打開“符號配置”對話框,並將其指向“Windows 調試工具”安裝目錄中的 dbghelp.dll,並將符號路徑設置爲指向 Microsoft 的符號服務器:

圖像

配置完符號後,打開“系統信息”對話框(單擊“視圖”菜單中的“系統信息”或按 Ctrl+I)以查看“內核內存”部分中的池信息。這是在 2GB Windows XP 系統上的樣子:

圖像

    2GB 32 位 Windows XP

非分頁池限制

正如我在上一篇文章中提到的,在 32 位 Windows 上,系統地址空間默認爲 2GB。這本質上將非分頁池(或任何類型的系統虛擬內存)的上限限制爲 2GB,但它必須與其他類型的資源共享該空間,例如內核本身、設備驅動程序、系統頁表條目 (PTE)、和緩存的文件視圖。

在 Vista 之前,32 位 Windows 上的內存管理器會計算在啓動時爲每種類型分配多少地址空間。其公式考慮了各種因素,其中主要因素是系統上的物理內存量。它分配給非分頁池的容量在具有 512MB 的系統上從 128MB 開始,在具有略高於 1GB 或更多的系統上高達 256MB。在使用 /3GB 選項啓動的系統上(以犧牲內核地址空間爲代價將用戶模式地址空間擴展到 3GB),最大非分頁池爲 128MB。前面顯示的 Process Explorer 屏幕截圖報告了在不使用 /3GB 開關啓動的 2GB Windows XP 系統上的最大容量爲 256MB。

32 位 Windows Vista 及更高版本(包括 Server 2008 和 Windows 7)中的內存管理器(沒有 32 位版本的 Windows Server 2008 R2)不會靜態地劃分系統地址;相反,它根據不斷變化的需求動態地將範圍分配給不同類型的內存。但是,它仍然根據物理內存量設置非分頁池的最大值,略高於物理內存的 75% 或 2GB,以較小者爲準。以下是 2GB Windows Server 2008 系統上的最大值:

圖像

    2GB 32 位 Windows Server 2008

64位Windows系統具有更大的地址空間,因此內存管理器可以靜態地劃分它,而不必擔心不同類型可能沒有足夠的空間。 64 位 Windows XP 和 Windows Server 2003 將最大非分頁池設置爲略高於每 MB RAM 400K 或 128GB(以較小者爲準)。下面是 2GB 64 位 Windows XP 系統的屏幕截圖:

圖像 

    2GB 64 位 Windows XP

64 位 Windows Vista、Windows Server 2008、Windows 7 和 Windows Server 2008 R2 內存管理器通過設置最大值來匹配其 32 位對應項(如果適用 - 如前所述,沒有 32 位版本的 Windows Server 2008 R2)大約 75% 的 RAM,但最大上限爲 128GB,而不是 2GB。下面是 2GB 64 位 Windows Vista 系統的屏幕截圖,該系統的非分頁池限制與前面顯示的 32 位 Windows Server 2008 系統類似。

圖像 

    2GB 32 位 Windows Server 2008

最後,這是 8GB 64 位 Windows 7 系統的限制:

圖像 

    8GB 64 位 Windows 7

下面的表格總結了不同 Windows 版本的非分頁池限制:

  32位 64位
XP、服務器 2003 高達 1.2GB 內存:32-256 MB > 1.2GB 內存:256MB 分鐘(~400K/MB 內存,128GB)
Vista、服務器 2008、Windows 7、服務器 2008 R2 分鐘(約 RAM 的 75%,2GB) 最小(~RAM 的 75%,128GB)
Windows 8、服務器 2012 分鐘(約 RAM 的 75%,2GB) 最小(2x RAM,128GB)

分頁池限制

內核和設備驅動程序使用分頁池來存儲永遠不會從 DPC 或 ISR 內部或在持有自旋鎖時訪問的任何數據結構。這是因爲分頁池的內容可以存在於物理內存中,或者如果內存管理器的工作集算法決定重新利用物理內存,則可以將其發送到分頁文件,並在再次引用時按需故障返回到物理內存中。因此,分頁池限制主要由內存管理器分配給分頁池的系統地址空間量以及系統提交限制決定。

在 32 位 Windows XP 上,該限制是根據分配給其他資源(最顯着的是系統 PTE)的地址空間大小來計算的,上限爲 491MB。前面顯示的 2GB Windows XP 系統的限制爲 360MB,例如:

圖像

   2GB 32 位 Windows XP

32位Windows Server 2003爲頁面緩衝池保留了更多的空間,因此其上限爲650MB。

由於 32 位 Windows Vista 及更高版本具有動態內核地址空間,因此它們只是將限制設置爲 2GB。因此,當系統地址空間已滿或達到系統提交限制時,分頁池將耗盡。

64 位 Windows XP 和 Windows Server 2003 將其最大值設置爲非分頁池限制的四倍或 128GB,以較小者爲準。這裏還是 64 位 Windows XP 系統的屏幕截圖,它顯示分頁池限制恰好是非分頁池的四倍:

圖像 

     2GB 64 位 Windows XP

最後,64 位版本的 Windows Vista、Windows Server 2008、Windows 7 和 Windows Server 2008 R2 只需將最大值設置爲 128GB,允許分頁池的限制跟蹤系統提交限制。下面再次是64位Windows 7系統的截圖:

圖像 

    8GB 64 位 Windows 7

以下是跨操作系統的分頁池限制的摘要:

  32位 64位
XP、服務器 2003 XP:最多 491MB Server 2003:最多 650MB 分鐘(4 * 非分頁池限制,128GB)
Vista、服務器 2008、Windows 7、服務器 2008 R2 min(系統提交限制,2GB) 分鐘(系統提交限制,128GB)
Windows 8、服務器 2012 min(系統提交限制,2GB) 分鐘(系統提交限制,384GB)

測試池限制

由於幾乎每個內核操作都會使用內核池,因此耗盡它們可能會導致不可預測的結果。如果您想親眼見證池運行不足時系統的行爲方式,請使用Notmyfault工具。它具有一些選項,導致它以您指定的增量泄漏非分頁池或分頁池。如果您想更改泄漏率,則可以在泄漏時更改泄漏大小,並且 Notmyfault 在退出時會釋放所有泄漏的內存:

圖像

除非您已做好可能丟失數據的準備,否則請勿在系統上運行此操作,因爲當池耗盡時,應用程序和 I/O 操作將開始失敗。如果驅動程序沒有正確處理內存不足的情況(這被認爲是驅動程序中的錯誤),您甚至可能會出現藍屏。 Windows 硬件質量實驗室 (WHQL) 強調使用驅動程序驗證程序(Windows 內置的工具)的驅動程序,以確保它們能夠容忍池外情況而不會崩潰,但您可能有尚未消失的第三方驅動程序通過此類測試或存在 WHQL 測試期間未發現的錯誤。

我在虛擬機中的各種測試系統上運行了 Notmyfault,看看它們的行爲如何,沒有遇到任何系統崩潰,但確實看到了不穩定的行爲。例如,在 64 位 Windows XP 系統上的非分頁池用完後,嘗試啓動命令提示符會導致出現以下對話框:

圖像

在我已經運行命令提示符的 32 位 Windows Server 2008 系統上,即使是更改當前目錄和目錄列表等簡單操作,在非分頁池耗盡後也會開始失敗:

圖像

在一個測試系統上,我最終看到了這條錯誤消息,表明數據可能已丟失。我希望您永遠不會在真實系統上看到此對話框!

圖像

用完分頁池會導致類似的錯誤。以下是在分頁池耗盡後嘗試在 32 位 Windows XP 系統上從命令提示符啓動記事本的結果。請注意 Windows 如何未能重繪窗口的標題欄以及每次嘗試時遇到的不同錯誤:

圖像

以下是開始菜單的附件文件夾無法在分頁池外的 64 位 Windows Server 2008 系統上填充的情況:

圖像

在這裏,您可以看到系統提交級別(也顯示在 Process Explorer 的“系統信息”對話框中)隨着 Notmyfault 泄漏大塊分頁池並在 2GB 32 位 Windows Server 2008 系統上達到 2GB 最大值而快速上升:

圖像

當池耗盡時,即使系統不可用,Windows 也不會簡單地崩潰,因爲池耗盡可能是由極端工作負載峯值引起的臨時情況,之後池被釋放,系統恢復正常運行。然而,當驅動程序(或內核)泄漏池時,這種情況是永久性的,並且識別泄漏的原因變得很重要。這就是帖子開頭描述的池標籤發揮作用的地方。

跟蹤水池泄漏

當您懷疑池泄漏並且系統仍然能夠啓動其他應用程序時,Poolmon(Windows 驅動程序工具包中的一個工具)會按池類型和傳遞到 ExAllocatePoolWithTag 調用中的標記顯示分配數量和未完成的分配字節數。各種熱鍵導致Poolmon按不同的列排序;要查找泄漏分配類型,請使用“b”按字節排序,或使用“d”按分配數和釋放數之間的差異排序。下面是在一個系統上運行的 Poolmon,其中 Notmyfault 泄漏了 14 個分配,每個分配約 100MB:

圖像

識別出左欄中的有罪標籤(在本例中爲“泄漏”)後,下一步是找到使用它的驅動程序。由於標籤存儲在驅動程序映像中,因此您可以通過掃描驅動程序映像來查找相關標籤來完成此操作。 Sysinternals 中的字符串實用程序將可打印字符串轉儲到您指定的文件中(默認情況下長度至少爲三個字符),並且由於大多數設備驅動程序映像位於 %Systemroot%\System32\Drivers 目錄中,因此您可以打開命令提示符下,切換到該目錄並執行“strings * | findstr <標籤>”。找到匹配項後,您可以使用 Sysinternals Sigcheck實用程序轉儲驅動程序的版本信息。以下是使用“Leak”查找驅動程序時的過程:

圖像

如果系統崩潰並且您懷疑是由於池耗盡所致,請將崩潰轉儲文件加載到 Windbg 調試器(包含在 Windows 調試工具包中),然後使用 !vm 命令進行確認。以下是 Notmyfault 已耗盡非分頁池的系統上 !vm 的輸出:

圖像

確認泄漏後,使用 !poolused 命令按與 Poolmon 類似的標籤查看池使用情況。 !poolused 默認顯示未排序的摘要信息,因此指定 1 作爲按分頁池使用情況排序的選項,指定 2 按非分頁池使用情況排序:

圖像 

在轉儲來源的系統上使用字符串,使用您發現導致問題的標籤來搜索驅動程序。

到目前爲止,在本博客系列中,我已經介紹了 Windows 中最基本的限制,包括物理內存、虛擬內存、分頁和非分頁池。下次我將討論 Windows 支持的進程和線程數量的限制,這些限制是源自這些限制。

突破 Windows 的極限:進程和線程

這是我的“突破 Windows 極限”系列文章中的第四篇文章,該系列探討了 Windows 中基本資源的邊界。這次,我將討論 Windows 支持的最大線程和進程數的限制。我將簡要描述線程和進程之間的區別,調查線程限制,然後調查進程限制。我首先介紹線程限制,因爲每個活動進程至少有一個線程(一個已終止的進程,但被另一個進程擁有的句柄引用,不會有任何線程),因此進程的限制直接受到以下上限的影響:限制線程。

與某些 UNIX 變體不同,Windows 中的大多數資源沒有編譯到操作系統中的固定上限,而是根據我已經介紹過的基本操作系統資源得出其限制。例如,進程和線程需要物理內存、虛擬內存和池內存,因此在給定的 Windows 系統上可以創建的進程或線程的數量最終由這些資源之一決定,具體取決於進程的運行方式或線程被創建以及首先遇到哪個約束。因此,如果您還沒有閱讀過,我建議您閱讀前面的文章,因爲我將提到保留內存、提交內存、系統提交限制以及我介紹過的其他概念。這是整個“挑戰極限”系列的索引。雖然它們可以獨立存在,但它們假設您按順序閱讀它們。

突破 Windows 的極限:物理內存

突破 Windows 的極限:虛擬內存

突破 Windows 的極限:分頁池和非分頁池

突破 Windows 的極限:進程和線程

突破 Windows 的極限:句柄

突破 Windows 的極限:USER 和 GDI 對象 – 第 1 部分

突破 Windows 的極限:USER 和 GDI 對象 – 第 2 部分

進程和線程

Windows 進程本質上是託管可執行映像文件執行的容器。它用內核進程對象表示,Windows 使用進程對象及其關聯的數據結構來存儲和跟蹤有關映像執行的信息。例如,進程具有虛擬地址空間,該空間保存進程的私有和共享數據,並且可執行映像及其關聯的 DLL 被映射到其中。 Windows 記錄進程對資源的使用情況,以便通過診斷工具進行統計和查詢,並在進程的句柄表中註冊進程對操作系統對象的引用。進程使用稱爲令牌的安全上下文進行操作,它標識分配給進程的用戶帳戶、帳戶組和權限。

最後,進程包括一個或多個實際執行進程中代碼的線程(從技術上講,進程不運行,線程運行),並且用內核線程對象表示。除了默認的初始線程之外,應用程序還創建線程的原因有多種:具有用戶界面的進程通常會創建線程來執行工作,以便主線程保持對用戶輸入和窗口命令的響應;想要利用多個處理器實現可擴展性或者想要在線程被佔用等待同步 I/O 操作完成時繼續執行的應用程序也可以從多個線程中受益。

線程限制

除了有關線程的基本信息(包括其 CPU 寄存器狀態、調度優先級和資源使用情況統計)之外,每個線程還分配有一部分進程地址空間(稱爲堆棧),線程在執行時可以將其用作臨時存儲傳遞函數參數、維護局部變量和保存函數返回地址的程序代碼。爲了避免不必要地浪費系統的虛擬內存,最初只分配或提交堆棧的一部分,而其餘部分則簡單地保留。由於堆棧在內存中向下增長,系統會將保護頁放置在堆棧的已提交部分之外,從而在訪問時觸發額外內存的自動提交(稱爲堆棧擴展)。下圖顯示了當堆棧擴展時堆棧的提交區域如何向下增長以及保護頁如何移動,以 32 位地址空間爲例(未按比例繪製):

圖像

可執行映像的可移植可執行文件 (PE) 結構指定爲線程堆棧保留和最初提交的地址空間量。鏈接器默認保留 1MB 並提交一頁 (4K),但開發人員可以通過在鏈接程序時更改 PE 值或在調用CreateThread時更改單個線程的 PE 值來覆蓋這些值。您可以使用 Visual Studio 附帶的Dumpbin等工具來查看可執行文件的設置。以下是新 Visual Studio 項目生成的可執行文件的 Dumpbin 輸出,其中包含 /headers 選項:

圖像

將數字從十六進制轉換,您可以看到堆棧保留大小爲 1MB,初始提交爲 4K,使用新的 Sysinternals VMMap工具附加到該進程並查看其地址空間,您可以清楚地看到線程堆棧的初始提交頁面,一個保護頁,以及剩餘的保留堆棧內存:

圖像

由於每個線程都消耗進程地址空間的一部分,因此進程對其可以創建的線程數量有一個基本限制,該限制是由其地址空間大小除以線程堆棧大小決定的。

32 位線程限制

即使線程沒有代碼或數據並且整個地址空間都可以用於堆棧,具有默認 2GB 地址空間的 32 位進程最多也可以創建 2,048 個線程。以下是在 32 位 Windows 上運行的Testlimit工具的輸出,並使用 –t 開關(創建線程)確認該限制:

圖像

同樣,由於部分地址空間已被代碼和初始堆使用,因此並非所有 2GB 都可用於線程堆棧,因此創建的線程總數無法完全達到理論限制 2,048。

我將 Testlimit 可執行文件與大地址空間感知選項鍊接起來,這意味着如果它提供了超過 2GB 的地址空間(例如在使用 /3GB 或 /USERVA Boot.ini 選項或其等效 BCD 啓動的 32 位系統上) Vista 上的選項以及後來增加的 userva),它將使用它。 32位進程在64位Windows上運行時被賦予4GB的地址空間,那麼32位Testlimit在64位Windows上運行時可以創建多少個線程?根據我們到目前爲止所討論的內容,答案應該大約是 4096(4GB 除以 1MB),但實際上這個數字要小得多。以下是在 64 位 Windows XP 上運行的 32 位 Testlimit:

圖像

造成這種差異的原因在於,當您在 64 位 Windows 上運行 32 位應用程序時,它實際上是一個代表 32 位線程執行 64 位代碼的 64 位進程,因此存在是一個64位線程棧和一個爲每個線程保留的32位線程棧區域。 64 位堆棧的保留量爲 256K(除了 Vista 之前的系統,初始線程的 64 位堆棧爲 1MB)。由於每個 32 位線程都以 64 位模式開始其生命週期,並且啓動時使用的堆棧空間超過一個頁面,因此您通常會看到至少 16KB 的 64 位堆棧已提交。下面是 32 位線程的 64 位和 32 位堆棧的示例(標記爲“Wow64”的是 32 位堆棧):

圖像

32 位 Testlimit 能夠在 64 位 Windows 上創建 3,204 個線程,其中每個線程使用 1MB+256K 堆棧地址空間(同樣,除了 Vista 之前的 Windows 版本上的第一個線程,它使用 1MB+1MB) ,正是您所期望的。然而,當我在 64 位 Windows 7 上運行 32 位 Testlimit 時,得到了不同的結果:

圖像

Windows XP 結果與 Windows 7 結果之間的差異是由 Windows Vista 中引入的地址空間佈局(地址空間加載隨機化 (ASLR))更加隨機的性質造成的,這會導致一些碎片。 DLL 加載、線程堆棧和堆放置的隨機化有助於防禦惡意代碼注入。從 VMMap 輸出中可以看到,仍有 357MB 的地址空間可用,但最大的空閒塊大小僅爲 128K,小於 32 位堆棧所需的 1MB:

圖像

正如我所提到的,開發人員可以覆蓋默認的堆棧保留。這樣做的原因之一是當線程的堆棧使用量始終顯着小於默認的 1MB 時,避免浪費地址空間。 Testlimit 將其 PE 映像中的默認堆棧保留設置爲 64K,並且當您將 –n 開關與 –t 開關一起包含時,Testlimit 將創建具有 64K 堆棧的線程。以下是具有 256MB RAM 的 32 位 Windows XP 系統上的輸出(我在小型系統上進行了此實驗以突出顯示此特定限制):

圖像

請注意不同的錯誤,這意味着地址空間不是這裏的問題。事實上,64K 堆棧應該允許大約 32,000 個線程 (2GB/64K = 32,768)。在這種情況下,達到的極限是多少?查看可能的候選者,包括提交和池,沒有給出任何線索,因爲它們都低於極限:

圖像

只需查看內核調試器中的其他內存信息即可揭示所達到的閾值,即已耗盡的常駐可用內存:

圖像

常駐可用內存是可分配給必須保存在 RAM 中的數據或代碼的物理內存。例如,非分頁池和非分頁驅動程序會對其進行計數,鎖定在 RAM 中用於設備 I/O 操作的內存也是如此。每個線程都有一個用戶模式堆棧(這就是我一直在討論的內容),但它們也有一個內核模式堆棧,在內核模式下運行時使用,例如在執行系統調用時。當線程處於活動狀態時,其內核堆棧會被鎖定在內存中,以便線程可以在內核中執行無法出現頁面錯誤的代碼。

基本內核堆棧在 32 位 Windows 上爲 12K,在 64 位 Windows 上爲 24K。 14,225 個線程需要大約 170MB 的常駐可用內存,這正好對應於 Testlimit 未運行時系統上的可用內存量:

圖像

一旦達到常駐可用內存限制,許多基本操作就會開始失敗。例如,當我雙擊桌面的 Internet Explorer 快捷方式時,出現以下錯誤:

圖像

正如預期的那樣,當在具有 256MB RAM 的 64 位 Windows 上運行時,Testlimit 只能創建 6,600 個線程,大約是在具有 256MB RAM 的 32 位 Windows 上創建的線程的一半,然後就會耗盡常駐可用內存:

圖像

我之前提到“基本”內核堆棧的原因是,執行圖形或窗口函數的線程在執行第一個調用時會獲得一個“大”堆棧,在 32 位 Windows 上爲 20K,在 64 位 Windows 上爲 48K。 Testlimit 的線程不調用任何此類 API,因此它們具有基本的內核堆棧。

64 位線程限制

與 32 位線程一樣,64 位線程也默認爲堆棧保留 1MB,但 64 位進程具有更大的用戶模式地址空間 (8TB),因此地址空間不應該成爲問題創建大量線程。不過,駐留可用內存顯然仍然是一個潛在的限制因素。 64 位版本的 Testlimit (Testlimit64.exe) 能夠在 256MB 64 位 Windows XP 系統上使用或不使用 –n 開關創建大約 6,600 個線程,與 32 位版本創建的數量相同,因爲它還達到常駐可用內存限制。然而,在具有 2GB RAM 的系統上,Testlimit64 只能創建 55,000 個線程,遠低於以駐留可用內存爲限制器時應能創建的數量 (2GB/24K = 89,000):

圖像

在這種情況下,初始線程堆棧提交導致系統耗盡虛擬內存並出現“分頁文件太小”錯誤。一旦提交級別達到 RAM 的大小,線程創建的速度就會減慢,因爲系統開始抖動,調出之前創建的線程堆棧,爲新線程堆棧騰出空間,並且分頁文件必須擴展。當指定 –n 開關時,結果是相同的,因爲線程具有相同的初始堆棧承諾。

過程限制

Windows支持的進程數顯然必須小於線程數,因爲每個進程都有一個線程,而進程本身會導致額外的資源使用。在 2GB 64 位 Windows XP 系統上運行的 32 位 Testlimit 創建了大約 8,400 個進程:

圖像

查看內核調試器表明它達到了常駐可用內存限制:

圖像

如果進程相對於駐留可用內存的唯一成本是內核模式線程堆棧,則 Testlimit 將能夠在 2GB 系統上創建遠遠超過 8,400 個線程。當 Testlimit 未運行時,該系統上的常駐可用內存量爲 1.9GB:

圖像

將 Testlimit 使用的常駐內存量 (1.9GB) 除以它創建的進程數 (8,400),得出每個進程 230K 常駐內存。由於 64 位內核堆棧爲 24K,因此還剩下大約 206K 未計算在內。剩下的費用從哪裏來?創建進程時,Windows 會保留足夠的物理內存來容納進程的最小工作集大小。這可以保證進程無論如何都有足夠的物理內存來保存足夠的數據來滿足其最小工作集。默認工作集大小恰好爲 200KB,當您將“最小工作集”列添加到Process Explorer 的顯示中時,這一事實就很明顯:

圖像

剩餘的大約 6K 是常駐可用內存,用於分配用於表示進程的額外不可分頁內存。 32 位 Windows 上的進程將使用稍少的常駐內存,因爲其內核模式線程堆棧較小。

與用戶模式線程堆棧一樣,進程可以使用SetProcessWorkingSetSize函數覆蓋其默認工作集大小。 Testlimit 支持 –n 開關,當與 –p 結合使用時,會導致主 Testlimit 進程的子進程將其工作集設置爲可能的最小值,即 80K。由於子進程必須運行以縮小其工作集,因此 Testlimit 在無法創建更多進程後會休眠,然後再次嘗試爲其子進程提供執行的機會。在具有 4GB RAM 的 Windows 7 系統上使用 –n 開關執行的 Testlimit 達到了常駐可用內存以外的限制:系統提交限制:

圖像

在這裏,您可以看到內核調試器不僅報告已達到系統提交限制,而且在耗盡提交限制(系統提交限制爲實際上,當分頁文件被填充然後增長以提高限制時會命中幾次):

圖像

Testlimit 運行之前的基線承諾約爲 1.5GB,因此線程消耗了約 8GB 的​​承諾內存。因此,每個進程大約消耗 8GB/6,600,即 1.2MB。內核調試器的 !vm 命令的輸出顯示了每個活動進程分配的私有內存,證實了該計算:

圖像

前面描述的初始線程堆棧承諾的影響可以忽略不計,其餘部分來自進程地址空間數據結構、頁表條目、句柄表、進程和線程對象以及進程創建時創建的私有數據所需的內存。初始化。

多少個線程和進程就足夠了?

那麼問題的答案就是“Windows 支持多少線程?”以及“Windows 上可以同時運行多少個進程?”依靠。除了線程指定其堆棧大小和進程指定其最小工作集的方式的細微差別之外,確定任何特定系統上的答案的兩個主要因素包括物理內存量和系統提交限制。無論如何,創建足夠線程或進程以接近這些限制的應用程序應該重新考慮其設計,因爲幾乎總是有替代方法可以以合理的數量實現相同的目標。例如,可擴展應用程序的總體目標是保持運行的線程數量等於 CPU 數量(NUMA 更改此設置以考慮每個節點的 CPU),實現這一目標的一種方法是從使用同步 I/O 切換到使用異步 I/O 並依靠 I/O 完成端口來幫助將正在運行的線程數與 CPU 數相匹配。

突破 Windows 的極限:句柄

這是我的“突破 Windows 極限”系列文章的第五篇,其中我探討了 Windows 管理的資源(例如物理內存、虛擬內存、進程和線程)數量和大小的上限。這是整個“挑戰極限”系列的索引。雖然它們可以獨立存在,但它們假設您按順序閱讀它們。

突破 Windows 的極限:物理內存

突破 Windows 的極限:虛擬內存

突破 Windows 的極限:分頁池和非分頁池

突破 Windows 的極限:進程和線程

突破 Windows 的極限:句柄

突破 Windows 的極限:USER 和 GDI 對象 – 第 1 部分

突破 Windows 的極限:USER 和 GDI 對象 – 第 2 部分

這次我將深入瞭解句柄的實現,以查找並解釋它們的限制。句柄是表示應用程序與之交互的基本操作系統對象的打開實例的數據結構,例如文件、註冊表項、同步原語和共享內存。有兩個與進程可以創建的句柄數量相關的限制:系統爲進程設置的最大句柄數量以及可用於存儲句柄和應用程序通過其句柄引用的對象的內存量。

在大多數情況下,句柄的限制遠遠超出了典型應用程序或系統曾經使用的限制。然而,應用程序在設計時沒有考慮到這些限制,可能會以開發人員意想不到的方式推動它們。出現一類更常見的問題是因爲這些資源的生命週期必須由應用程序管理,就像虛擬內存一樣,即使對於最優秀的開發人員來說,資源生命週期管理也充滿挑戰。未能釋放不需要的資源的應用程序會導致資源泄漏,最終導致達到限制,從而導致該應用程序、其他應用程序或整個系統出現奇怪且難以診斷的行爲。 

與往常一樣,我建議您閱讀之前的文章,因爲它們解釋了本文引用的一些概念,例如分頁池。

句柄和對象

Windows 的內核模式核心在%SystemRoot%\System32\Ntoskrnl.exe 映像中實現,由內存管理器、進程管理器、I/O 管理器、配置管理器(註冊表)等各種子系統組成,它們分別是行政機關的所有部分。這些子系統中的每一個都使用對象管理器定義一種或多種類型來表示它們嚮應用程序公開的資源。例如,配置管理器定義密鑰對象來表示打開的註冊表項;內存管理器定義共享內存的Section對象; Executive 定義了Semaphore、M utant(互斥體的內部名稱)和  Event同步對象(這些對象包裝了操作系統內核子系統定義的基本數據結構); I/O 管理器定義File對象來表示設備驅動程序資源的打開實例,其中包括文件系統文件;進程管理器創建了我在上一篇《突破極限》文章中討論過的線程進程對象。每個版本的 Windows 都會引入新的對象類型,其中 Windows 7 總共定義了 42 個對象類型。您可以通過使用管理權限運行 Sysinternals Winobj實用程序並導航到對象管理器命名空間中的 ObjectTypes 目錄來查看定義的對象:

圖像

當應用程序想要管理這些資源之一時,它首先必須調用適當的 API 來創建或打開該資源。例如,CreateFile函數打開或創建文件,RegOpenKeyEx函數打開註冊表項,CreateSemaphoreEx函數打開或創建信號量。如果函數成功,Windows 將在應用程序進程的句柄表中分配一個句柄並返回句柄值,應用程序將其視爲不透明,但實際上它是句柄表中返回句柄的索引。

有了句柄,應用程序就可以通過將句柄值傳遞給後續 API 函數(例如ReadFileSetEventSetThreadPriorityMapViewOfFile )來查詢或操作該對象。系統可以通過索引句柄表來查找句柄所引用的對象,以找到相應的句柄條目,其中包含指向該對象的指針。句柄條目還存儲進程在打開對象時被授予的訪問權限,這使系統能夠確保不允許進程對它未請求權限的對象執行操作。例如,如果進程成功打開一個文件進行讀訪問,則句柄條目將如下所示:

圖像

如果進程嘗試寫入文件,該函數將失敗,因爲尚未授予訪問權限,並且緩存的讀訪問權限意味着系統不必再次執行更昂貴的訪問檢查。

最大句柄數

您可以使用我在本系列中使用的 Testlimit 工具來探索第一個極限,以憑經驗探索極限。可以在此處的Windows Internals 圖書頁面上下載它。爲了測試進程可以創建的句柄數量,Testlimit 實現了 –h 開關,指示進程創建儘可能多的句柄。它通過使用CreateEvent創建事件對象,然後使用DuplicateHandle重複複製系統返回的句柄來實現此目的。通過複製句柄,Testlimit 避免創建新事件,它消耗的唯一資源是句柄表條目的資源。以下是在 64 位系統上使用 –h 選項進行 Testlimit 的結果:

圖像

然而,結果並不代表進程可以創建的句柄總數,因爲系統 DLL 在進程初始化期間打開各種對象。您可以通過將句柄計數列添加到任務管理器或 Process Explorer 來查看進程的總句柄計數。本例中 Testlimit 顯示的總數爲 16,711,680:

圖像

當您在 32 位系統上運行 Testlimit 時,它可以創建的句柄數量略有不同:

圖像

其總句柄數也不同,爲 16,744,448:

圖像

差異從何而來?答案在於負責管理句柄表的執行程序設置每個進程句柄限制以及句柄表條目的大小的方式。在 Windows 對資源設置硬編碼上限的罕見情況之一中,執行器將 16,777,216 (16*1024*1024) 定義爲進程可以分配的最大句柄數。任何在任何給定時間點打開超過一萬個句柄的進程都可能設計不當或存在句柄泄漏,因此 1600 萬個限制本質上是無限的,並且可以簡單地幫助防止存在泄漏的進程影響進程。系統的其餘部分。要了解爲什麼任務管理器顯示的數字不等於硬編碼的最大值,需要查看執行人員組織句柄表的方式。

句柄表條目必須足夠大以存儲授予訪問掩碼和對象指針。訪問掩碼是 32 位,但指針大小顯然取決於它是 32 位還是 64 位系統。因此,句柄條目在 32 位 Windows 上爲 8 字節,在 64 位 Windows 上爲 12 字節。 64 位 Windows 在 64 位邊界上對齊句柄條目數據結構,因此 64 位句柄條目實際上消耗 16 個字節。以下是 64 位 Windows 上句柄條目的定義,如使用 dt(轉儲類型)命令的內核調試器中所示:

圖像

輸出顯示該結構實際​​上是一個聯合,有時可以存儲除對象指針和訪問掩碼之外的信息,但這兩個字段已突出顯示。

執行程序根據需要在頁面大小的塊中分配句柄表,並將這些塊劃分爲句柄表條目。這意味着一個頁面(在 x86 和 x64 上都是 4096 字節)可以在 32 位 Windows 上存儲 512 個條目,在 64 位 Windows 上可以存儲 256 個條目。執行程序通過將硬編碼最大值 16,777,216 除以頁面中的句柄條目數來確定爲句柄條目分配的最大頁面數,結果在 32 位 Windows 上爲 32,768,在 64 位 Windows 上爲 65,536 。因爲執行程序使用每個頁面的第一個條目作爲自己的跟蹤信息,所以進程可用的句柄數實際上是 16,777,216 減去這些數字,這解釋了 Testlimit 獲得的結果:16,777,216-65,536 是 16,711,680 和 16,777,216-65,536-32,768是 16,744,448。

句柄和分頁池

影響句柄的第二個限制是存儲句柄表所需的內存量,執行程序從分頁池中分配這些內存。執行程序使用三級方案,類似於處理器內存管理單元 (MMU) 管理虛擬到物理地址轉換的方式,來跟蹤它分配的句柄表頁。我們已經看到了最低層和中間層的組織,它們存儲實際的句柄表條目。頂層用作指向中層表的指針,並且在 32 位 Windows 上每頁包含 1024 個條目。因此,對於 32 位 Windows,可以計算出存儲最大句柄數所需的頁面總數爲 16,777,216/512*4096,即 128MB。這與任務管理器中顯示的 Testlimit 的分頁池使用情況一致:

圖像

在 64 位 Windows 上,頂級指針頁中有 256 個指針。這意味着完整句柄表的分頁池總使用量爲 16,777,216/256*4096,即 256MB。查看 Testlimit 在 64 位 Windows 上的分頁池使用情況證實了計算結果:

圖像

分頁池通常足夠大,足以容納這些大小,但正如我之前所說,創建這麼多句柄的進程幾乎肯定會耗盡其他資源,如果它達到每個進程的句柄限制,它可能會自行失敗因爲它無法打開任何其他對象。

處理泄漏

句柄泄漏者的句柄計數會隨着時間的推移而增加。句柄泄漏之所以如此陰險,是因爲與 Testlimit 創建的句柄不同,這些句柄都指向同一個對象,泄漏句柄的進程也可能泄漏對象。例如,如果進程創建事件但無法關閉它們,則它將泄漏句柄條目和事件對象。事件對象消耗非分頁池,因此除了分頁池之外,泄漏還會影響非分頁池。

您可以使用 Process Explorer 的句柄視圖以圖形方式發現進程正在泄漏的對象,因爲它以綠色突出顯示新句柄,以紅色突出顯示已關閉的句柄;如果您看到大量綠色和很少見的紅色,那麼您可能會看到泄漏。您可以通過打開命令提示符進程、在 Process Explorer 中選擇進程、打開句柄視圖下部窗格,然後在命令提示符中更改目錄來觀看 Process Explorer 的句柄突出顯示操作。舊工作目錄的句柄將以紅色突出顯示,新工作目錄的句柄將以綠色突出顯示:

圖像 

默認情況下,Process Explorer 僅顯示引用具有名稱的對象的句柄,這意味着您不會看到進程正在使用的所有句柄,除非您從“視圖”菜單中選擇“顯示未命名的句柄和映射” 。以下是命令提示符句柄表中的一些未命名句柄:

圖像

就像大多數錯誤一樣,只有泄漏代碼的開發人員才能修復它。如果您在可以託管多個組件或擴展的進程(例如 Explorer、服務主機或 Internet Explorer)中發現泄漏,那麼問題是哪個組件對泄漏負責。弄清楚這一點可能會讓您通過禁用或卸載有問題的擴展來避免問題,通過檢查更新來解決問題,或者向供應商報告錯誤。

幸運的是,Windows 包含一個句柄跟蹤工具,您可以使用它來幫助識別泄漏和負責的軟件。它是在每個進程的基礎上啓用的,當激活時,執行程序會在創建或關閉每個句柄時記錄堆棧跟蹤。您可以通過使用應用程序驗證器(可從 Microsoft 免費下載)或使用Windows 調試器(Windbg)來啓用它。如果您希望系統從進程啓動時開始跟蹤進程的句柄活動,您應該使用應用程序驗證器。無論哪種情況,您都需要使用調試器和!htrace調試器命令來查看跟蹤信息。

爲了演示實際跟蹤,我啓動了 Windbg 並附加到我之前打開的命令提示符。然後,我使用 -enable 開關執行 !htrace 命令打開句柄跟蹤:

圖像

我讓進程繼續執行並再次更改目錄。然後我切換回 Windbg,停止進程的執行,並在不帶任何選項的情況下執行 htrace,其中列出了自上一個 !htrace 快照(使用–snapshot選項創建)或從 when 句柄開始執行的所有打開和關閉操作跟蹤已啓用。以下是同一會話的命令的輸出:

圖像

事件從最近操作到最少操作打印,因此從底部讀取,命令提示符打開句柄 0xb8,然後關閉它,接下來打開句柄 0x22c,最後關閉句柄 0xec。如果在目錄更改後刷新,Process Explorer 將以綠色顯示句柄 0x22c,以紅色顯示 0xec,但可能不會看到 0xb8,除非它碰巧在該句柄的打開和關閉之間刷新。 0x22c 的堆棧顯示,這是命令提示符 (cmd.exe) 執行其 ChangeDirectory 函數的結果。將句柄值列添加到 Process Explorer 可確認新句柄爲 0x22c:

圖像

如果您只是尋找泄漏,則應該將 !htrace 與–diff開關一起使用,該開關僅顯示自上次快照或跟蹤開始以來的新句柄。執行該命令僅顯示句柄 0x22c,如預期:

圖像

最後,第 9 頻道對 Microsoft 升級工程師 Jeff Dailey 的採訪,提供了有關調試句柄泄漏的更多技巧的精彩視頻:https://channel9.msdn.com/posts/jeff_dailey/Understanding-handle -泄漏以及如何使用 htrace 來查找它們/

下次我將研究其他一些基於句柄的資源(GDI 對象和 USER 對象)的限制。這些資源的句柄由 Windows 子系統而不是執行體管理,因此使用不同的資源並具有不同的限制。

突破 Windows 的極限:USER 和 GDI 對象 – 第 1 部分

到目前爲止,在“突破 Windows 的極限”系列中,我主要關注由 Windows 操作系統內核管理的資源,包括物理和虛擬內存、分頁和非分頁池、進程、線程和句柄。然而,在這篇文章和下一篇文章中,我將探討由 Windows 窗口管理器管理的兩種資源,即 USER 和 GDI 對象,它們代表窗口元素(如窗口和菜單)和圖形結構(如筆、畫筆和繪圖表面)。就像我在之前的文章中討論的其他資源一樣,耗盡各種 USER 和 GDI 資源限制可能會導致不可預測的行爲,包括應用程序故障和無法使用的系統。

與往常一樣,我建議您在閱讀這篇文章之前先閱讀之前的文章,因爲與 USER 和 GDI 資源相關的一些限制是基於我所介紹的限制。以下是我的其他《突破 Windows 極限》帖子的完整索引:

突破 Windows 的極限:物理內存

突破 Windows 的極限:虛擬內存

突破 Windows 的極限:分頁池和非分頁池

突破 Windows 的極限:進程和線程

突破 Windows 的極限:句柄

會話、窗口站和桌面

有幾個概念使 USER 對象、GDI 對象和系統之間的關係更加清晰。首先是會議。會話代表交互式用戶登錄,具有自己的鍵盤、鼠標和顯示器,並代表安全和資源邊界。

會話概念首先是在 Windows NT 4 終端服務器版中的終端服務(現在稱爲遠程桌面服務)中引入的,其中物理顯示器、鍵盤和鼠標概念針對每個遠程交互登錄到系統的用戶進行了虛擬化,而核心終端服務該功能內置於 Windows 2000 Server 中。在 Windows XP 中,利用會話來創建快速用戶切換 (FUS) 功能,該功能允許您在同一物理顯示器、鍵盤和鼠標上的多個交互式登錄之間進行切換。

因此,會話可以與連接到系統的物理顯示和輸入設備連接,也可以與邏輯顯示和輸入設備(例如遠程桌面客戶端應用程序提供的設備)連接,或者處於斷開連接狀態,例如當您從具有快速用戶切換的會話或終止遠程桌面客戶端連接而不註銷會話。

每個進程都與特定會話唯一關聯,當您將會話列添加到 Sysinternals Process Explorer時可以看到該會話。在這個屏幕截圖中,我摺疊了進程樹以僅顯示沒有父進程的進程,該屏幕截圖來自具有四個活動會話的遠程桌面服務(RDS - 以前的終端服務器服務)系統:會話 0 是專用會話,其中系統進程在 Windows Vista 及更高版本上執行;第 1 場會議是我寫這篇文章的會議;會話 2 是我同時從另一個系統登錄的另一個用戶帳戶的會話;最後,會話 3 是遠程桌面服務主動創建的會話,爲下一次交互式登錄做好準備:

圖像

由於每個進程都與特定會話相關聯,並且操作系統通常只需要訪問當前進程會話的會話特定數據,因此 Windows 在進程的虛擬地址空間中定義了進程會話數據的視圖。這樣,當系統在不同進程的線程之間切換時,它也會切換地址空間,從而切換當前會話視圖。例如,當Session 0的Csrss.exe進程是當前進程時,地址空間映射包括系統地址空間(包含在每個進程的地址空間中)、Csrss的地址空間和Session 0地址空間。映射會話數據的內存區域稱爲會話視圖空間或會話空間。當系統從 Session 1 的 Explorer 進程切換到線程時,映射會相應更改,而當系統從 Notepad 切換到線程時,Session 1 會話空間仍保持映射狀態:

圖像

請注意,對於 32 位 Windows Vista 及更高版本,該數字並不完全正確,因爲動態系統地址空間意味着會話空間不一定是連續的,並且可以根據這些系統的需要增長和縮小。

下一個概念是桌面,它是由窗口管理器定義的對象,用於表示虛擬顯示,其中包括與桌面關聯的窗口(請注意,這與資源管理器對桌面的定義不同,桌面是包含快捷方式和其他對象的用戶目錄用戶放置在那裏)。默認桌面被命名爲“Default”,但應用程序可以創建其他桌面並將連接切換到邏輯顯示器,Sysinternals Desktops實用程序使用它來創建最多四個用戶可以在之間切換的虛擬桌面。

最後,爲了支持與同一窗口管理器實例關聯的多個虛擬顯示器,窗口管理器定義了窗口站對象。一個窗口站與一個特定的會話相關聯,一個會話可以有多個窗口站,但每個會話只有一個交互式窗口站,稱爲Winsta0,它可以連接物理或邏輯顯示器、鍵盤和鼠標;其他窗口站本質上是“無頭”的,對它們的支持只是爲了隔離需要窗口管理器服務的進程,但事實並非如此。例如,系統爲每個服務帳戶創建非交互式窗口站,並將其與帳戶中運行的進程關聯起來,因爲 Windows 服務不應顯示用戶界面。

您可以通過使用Sysinternals Winobj工具查看 \Windows 目錄下的對象管理器命名空間來查看與會話 0 關聯的窗口站(查看該目錄需要以管理權限提升運行權限)。在這裏您可以看到 Microsoft Windows 搜索服務創建的一個窗口站,用於在其中運行搜索過濾器、三個內置服務帳戶(系統、網絡服務和本地服務)中每一個的窗口站,以及會話 0 的交互式窗口站:

圖像

您可以在對象管理器命名空間的 Sessions 目錄中看到與其他會話關聯的窗口站。這是與我的登錄會話關聯的唯一窗口站,交互式 WinSta0 窗口站:

圖像

該圖顯示了一個系統的會話、窗口站和桌面之間的關係,該系統有一個用戶在物理控制檯上登錄到會話 1,另一個用戶通過遠程桌面連接登錄到會話 2,其中用戶運行了虛擬桌面實用程序並切換了會話。顯示到Desktop1。

圖像

除了與特定會話關聯之外,進程還與特定窗口站和桌面關聯,儘管進程可以在兩者之間切換,線程也可以在桌面之間切換。因此,每個進程的關聯都可以用這樣的分層路徑表示:“Session 1\WinSta0\Default”。在大多數情況下,您可以通過在 Process Explorer 的句柄視圖中查看進程的句柄表來查看其打開的對象的名稱,從而間接確定進程連接到哪個窗口站和桌面。 Explorer 進程的句柄表的屏幕截圖顯示它已連接到會話 1 的 WinSta0 上的默認桌面:

圖像

用戶對象

掌握了基本概念後,讓我們首先將注意力轉向 USER 對象。USER 對象之所以得名,是因爲它們代表用戶界面元素,如桌面、窗口、菜單、光標、圖標和加速表(菜單鍵盤快捷鍵)。儘管 USER 對象與特定桌面相關聯,但它們必須可從會話的所有桌面訪問,例如允許一個桌面上的進程註冊可在其中任何一個桌面上輸入的熱鍵。因此,窗口管理器分配作用域爲窗口站的 USER 對象標識符。 

窗口管理器強加的一個基本限制是任何進程都不能創建超過 10,000 個 USER 對象。該限制試圖防止單個進程耗盡與 USER 對象相關的資源,因爲它使用可以創建過多對象的算法進行編程,或者因爲它通過分配對象而不是在使用對象時刪除它們而泄漏對象。您可以通過使用 –u 開關運行 Sysinternals Testlimit實用程序來輕鬆驗證此限制,該實用程序指示 Testlimit 創建儘可能多的 USER 對象:

圖像

窗口管理器會跟蹤進程分配的 USER 對象數量,當您將 USER Objects 列添加到 Process Explorer 的顯示中時,您可以看到這些信息,以便您可以密切關注進程分配的對象數量。此屏幕截圖顯示,正如預期的那樣,Windows 系統進程,包括 Lsass.exe(本地安全機構子系統)和 Svchost 等服務進程,不會分配 USER 對象,因爲它們沒有用戶界面:

圖像

Process Explorer 在進程的進程屬性對話框的性能頁面上顯示進程已分配的 USER 對象的數量:

圖像

USER 對象數量的一個基本限制來自於這樣一個事實:在 Windows 的第一個版本中,它們的標識符是 16 位值,而 Windows 是 16 位的。當在更高版本中添加 32 位支持時,USER 標識符必須保留爲 16 位值,以便 16 位進程可以與窗口和 32 位進程創建的其他 USER 對象進行交互。因此,65,535 (2^16) 是一個會話上可以創建的 USER 對象總數的限制(並且由於歷史原因,窗口必須具有偶數標識符,因此每個會話最多可以有 32,768 個窗口) )。您可以通過使用 –u 開關運行 Testlimit 的多個副本來驗證此限制,直到無法再創建爲止。假設您已經運行的進程沒有使用過多的對象,您應該能夠運行 7 個副本,其中前 6 個分配 10,000 個對象,最後一個分配已分配的對象數量與 65,535 之間的差值:

圖像

執行此操作時,請確保準備好硬關閉系統電源,因爲桌面可能會變得無法使用。許多操作,例如打開開始菜單的關閉菜單,都需要 USER 對象,當無法分配更多對象時,系統將以奇怪的方式運行。在 USER 對象耗盡後,我什至無法通過單擊其關閉菜單按鈕來終止正在運行的記事本進程。

到目前爲止,我只討論了與進程或窗口站可以分配的 USER 對象的絕對數量相關的限制,但還有由 USER 對象本身使用的存儲引起的其他限制。每個桌面都有自己的內存區域,稱爲桌面堆,在桌面上創建的大多數 USER 對象都是從該區域分配的。由於桌面堆存儲在會話空間中,並且 32 位地址空間限制了內核模式地址空間的數量,因此桌面堆的大小被限制在一個相對適中的數量。它們的大小也有所不同,具體取決於它們所適用的桌面類型以及系統是 32 位還是 64 位系統。

NT 調試博客中的Matthew Justice 的桌面堆概述桌面堆第 2 部分  文章出色地記錄了 Windows Vista SP1 中的桌面堆大小。下表總結了從 Windows Server 2008 R2 開始的各個 Windows 版本的大小:

  互動桌面 非交互式桌面 Winlogon桌面 斷開桌面連接
Windows XP 32 位 3MB 512 KB 128KB 64KB
Windows Server 2003 32 位 3MB 512 KB 128KB 64KB
Windows Server 2003 64 位 20MB 768 KB 192 KB 96KB
Windows Vista/Windows Server 2008 32 位 12MB 512 KB 128KB 64KB
Windows Vista/Windows Server 2008 64 位 20MB 768 KB 192 KB 96KB
Windows 7 32 位 12MB 512 KB 128KB 64KB
Windows 7/Windows Server 2008 R2 64 位 20MB 768 KB 192 KB 96KB

值得注意的是,Windows Vista 32 位的原始版本的交互式堆大小與以前的 32 位版本的 Windows 相同,爲 3 MB。發佈後,我們的遙測顯示,一些用戶偶爾會耗盡堆,可能是因爲他們在具有更多內存的系統上運行更多應用程序,因此 SP1 將大小提高到 12 MB。還可以使用 Matthew 的文章中描述的註冊表設置覆蓋默認桌面堆大小。

在 Windows Vista 之前的 Windows 版本上,您可以使用 Microsoft桌面堆監視器工具來查看桌面堆的大小以及每個桌面堆的使用量。以下是該工具在 32 位 Windows XP 系統上的輸出,顯示交互式桌面(默認)上僅消耗了 5.6% 的堆 (172 KB):

圖像

該工具尚未針對 Windows Vista 進行更新,因爲較新版本的 Windows 上的桌面堆大小較大,這意味着在達到其他 USER 對象限制之前桌面堆很少會耗盡。但是,您可以將 Testlimit 與 –u 和 –i 開關一起使用,以查看發生交互式桌面堆耗盡時系統的行爲。開關組合讓 Testlimit 創建具有 4 KB 額外類存儲的窗口類數據結構,直到失敗。這是我捕獲上述桌面堆監視器輸出後立即運行的 Testlimit 的輸出。 2823 KB 加上桌面堆監視器表示已分配的 172 KB 大約等於 3 MB:

圖像

儘管無法確定較新系統上使用了多少堆,但當堆耗盡時,窗口管理器會向系統事件日誌寫入一個事件,這有助於解決窗口管理器問題:

圖像

這涵蓋了 USER 對象限制。請繼續關注第 2 部分,我將在其中討論與窗口管理器 GDI 對象相關的限制。

 

突破 Windows 的極限:USER 和 GDI 對象 – 第 2 部分

上次,我介紹了兩個關鍵窗口管理器資源之一(USER 對象)的限制以及如何測量其使用情況。這次,我將介紹其他關鍵資源,GDI 對象。與往常一樣,我建議您在閱讀這篇文章之前先閱讀之前的文章,因爲與 USER 和 GDI 資源相關的一些限制是基於我所介紹的限制。以下是我的其他《突破 Windows 極限》帖子的完整索引:

突破 Windows 的極限:物理內存

突破 Windows 的極限:虛擬內存

突破 Windows 的極限:分頁池和非分頁池

突破 Windows 的極限:進程和線程

突破 Windows 的極限:句柄

突破 Windows 的極限:USER 和 GDI 對象 – 第 1 部分

GDI 對象

GDI 對象表示圖形設備接口資源,如字體、位圖、畫筆、筆和設備上下文(繪圖表面)。與對待 USER 對象一樣,窗口管理器將進程限制爲最多 10,000 個 GDI 對象,您可以使用 –g 開關通過 Testlimit 進行驗證:

圖像

您可以在 Process Explorer 進程屬性對話框的 Performance 頁面上查看單個進程的 GDI 對象使用情況,並將 GDI Objects 列添加到 Process Explorer 以觀察跨進程的 GDI 對象使用情況:

圖像

與 USER 對象一樣,16 位互操作性意味着 USER 對象具有 16 位標識符,每個會話將其限制爲 65,535 個。這是當 Testlimit 在 Windows Vista 64 位系統上達到該限制時出現的桌面:

圖像

請注意左下角的“開始”按鈕所屬的位置,但任務欄的其餘部分位於屏幕頂部。桌面變黑,側邊欄失去了大部分顏色。您的情況可能會有所不同,但您可以看到奇怪的事情開始發生,可能導致無法以可靠的方式與桌面交互。以下是我按下“開始”按鈕時顯示屏切換到的內容:

圖像

與 USER 對象不同,GDI 對象不是從桌面堆分配的;而是從桌面堆中分配的。相反,在未安裝終端服務的 Windows XP 和 Windows Server 2003 系統上,它們從通用分頁池中分配;在所有其他系統上,它們從每個會話的會話池中分配。 

內核調試器的“!vm 4”命令轉儲一般虛擬內存信息,包括輸出末尾的會話信息。在 Windows XP 系統上,它顯示會話分頁池未使用:

圖像

在沒有終端服務的 Windows Server 2003 系統上,輸出類似:

圖像

因此,這些系統上的 GDI 對象內存限制是分頁池限制,如我之前的文章《推動 Windows 的限制:分頁和非分頁池》中所述。但是,當終端服務安裝在同一個 Windows Server 2003 系統上時,從非零會話池使用情況可以看出 GDI 對象來自會話池:

圖像

上述輸出中的 !vm 4 命令還顯示會話分頁池最大值和會話池大小,但會話分頁池最大值和會話空間大小在 Windows Vista 及更高版本上不顯示,因爲它們是可變的。這些系統上的會話分頁池使用量受到其可以增長到的地址空間量或系統提交限制(以較小者爲準)的限制。以下是 Windows 7 系統上命令的輸出,顯示了當前會話分頁池的使用情況(按會話):

圖像

正如您所期望的,主要的交互式會話(會話 1)正在消耗最多的會話分頁池。

您可以使用帶有“-g 0”開關的 Testlimit 工具來查看當用於 GDI 對象的存儲空間耗盡時會發生什麼情況。 -g 之後指定的數字是 Testlimit 分配的 GDI 位圖對象的大小,但大小爲 0 時 Testlimit 只是嘗試分配儘可能大的對象。以下是 32 位 Windows XP 系統上的結果:

圖像

在未安裝終端服務的 Windows XP 或 Windows Server 2003 上,您可以使用 Windows 驅動程序工具包 (WDK) 中的 Poolmon 實用程序來按池標記查看 GDI 對象分配。當 Testlimit 在 WIndows XP 系統上耗盡分頁池時,Poolmon 的輸出看起來像這樣,當按分配的字節排序時(在 Poolmon 顯示中鍵入“b”以按分配的字節排序),通過推斷表明 Gh05 是位圖的標記Windows Server 2003 上的對象:

圖像

在安裝了終端服務的 Windows Server 2003 系統以及 Windows Vista 和更高版本上,您必須使用帶有 /s 開關的 Poolmon 來指定要查看的會話。下面是在安裝了終端服務的 Windows Server 2003 系統上執行的 Testlimit:

圖像

命令“poolmon /s1”顯示對會話 1 貢獻最大的分配的標籤。您可以在頂部看到 Gh15 標籤,表明位圖分配使用了不同的池標籤:

圖像

請注意 Testlimit 如何能夠在 Windows XP 系統上分配大約 58 MB 的位圖數據(該數字不考慮位圖對象的 GDI 內部開銷),但在 Windows Server 2003 系統上只能分配 10MB。較小的數字是因爲 Windows Server 2003 終端服務器系統上的會話池只有 32 MB,這大約是 Poolmon 顯示的歸因於 Gh15 標記的內存量。 “!vm 4”的輸出確認 Session1 的會話池已被消耗,並且後續從會話池分配 GDI 對象的嘗試失敗:

圖像

您還可以使用 !poolused 內核調試器命令來查看會話池的使用情況。首先,使用帶有 /p 開關的 .process 命令以及連接到該會話的進程對象的地址來切換到正確的會話。要查看特定會話中正在運行哪些進程,請使用 !sprocess 命令。以下是同一 Windows Server 2003 系統上 !poolmon 的輸出,其中 !poolused 的“c”選項按分配的字節對輸出進行排序:

圖像

不幸的是,窗口管理器的堆標記和它們所代表的對象之間沒有公共映射,但內核調試器的 !poolused 命令使用調試器安裝目錄中的 triage.ini 文件來打印有關標記的更多描述性信息。該命令報告 Gh15 是 GDITAG_HMGR_SPRITE_TYPE,這只是稍微有用一點,但其他的更清楚。

幸運的是,大多數 GDI 和 USER 對象問題僅限於達到每個進程 10,000 個對象限制的特定進程,因此不需要進行更高級的調查來找出哪個進程負責耗盡會話池或將 GDI 對象分配給耗盡分頁池。

下次我將看一下系統頁表條目(系統 PTE),這是另一個可能受到限制的關鍵系統資源,特別是在 Windows Server 2003 系統上的遠程桌面會話上。

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章