在《解析Windows 2000/XP物理內存管理》中我詳細的介紹了頁框數據庫(Page Frame Database)的概念,提到在物理內存的組織與管理方面對於每個頁面系統都在頁框數據庫中保存一個結構,用於跟蹤頁面狀態等。但頁框數據庫並不能真正協調物理內存的使用。我們知道,Windows是一個多任務的,而物理內存卻是一個相對貧乏的資源,為避免某個進程(或是系統)耗盡這一資源,引入了工作集(WorkingSet)的概念。WorkingSet是內存管理一個相當重要的術語,在Windows 2000/XP中通常分為兩種即進程工作集與系統工作集,分別用於跟蹤各個進程與系統的物理內存使用情況。由於終端服務的引入,另有一種工作集會話(Session)工作集,用於跟蹤各個Session使用物理內存的情況。本文從進程工作集的內部組織方式出發,簡要闡述工作集在Windows 2000/XP中的組織與管理。
EPROCESS是描述進程的結構,所以從EPROCESS入手,肯定也能找到進程工作集的表示方式。實際上位於EPROCESS中的子結構MMSUPPORT就是關於進程與內存子系統相關的一些關鍵內容,進程工作集自然也在此。對於早期的內核版本這些內容沒有集成至MMSUPPORT結構中,而且各版本間MMSUPPORT的定義是不相同的,底下列出MMSUPPORT在Windows XP Build 2600 SP0中的定義(本文中所有結構都可能只適用於這一版本):
typedef struct _MMSUPPORT {
LARGE_INTEGER LastTrimTime;
MMSUPPORT_FLAGS Flags;
ULONG PageFaultCount;
ULONG PeakWorkingSetSize;
ULONG WorkingSetSize;
ULONG MinimumWorkingSetSize;
ULONG MaximumWorkingSetSize;
PMMWSL VmWorkingSetList;
LIST_ENTRY WorkingSetExpansionLinks;
ULONG Claim;
ULONG NextEstimationSlot;
ULONG NextAgingSlot;
ULONG EstimatedAvailable;
ULONG GrowthSinceLastEstimate;
} MMSUPPORT, *PMMSUPPORT;
MMSUPPORT中PeakWorkingSetSize、WorkingSetSize、MinimumWorkingSetSize與MaximumWorkingSetSize分別表示此進程的工作集峰值、當然工作集大小、允許工作集的最大值與最小值。性能監視器(perfmon.msc)與任務管理器(taskmgr.exe)都可對這些數據進程跟蹤顯示。Win32 API GetProcessWorkingSetSize(Ex)和SetProcessWorkingSetSize(Ex)在具有相應PROCESS_QUERY_INFORMATION與PROCESS_SET_QUOTA權限後即能獲取或設置MinimumWorkingSetSize與MaximumWorkingSetSize等。
進程在建立時,進程工作集總為空的,CreateProcess等在建立進程過程中有責任初始化進程工作集。它會分配一個物理頁面,然後調用MiInitializeWorkingSetList初始化進程工作集。後者以剛建立的EPROCESS作為參數初始化我們上面提到的MMSUPPORT結構。這裡要提到一個很重要的成員VmWorkingSetList(結構MMWSL),定義如下:
+0x000 Quota : Uint4B
+0x004 FirstFree : Uint4B
+0x008 FirstDynamic : Uint4B
+0x00c LastEntry : Uint4B
+0x010 NextSlot : Uint4B
+0x014 Wsle : Ptr32 _MMWSLE
+0x018 LastInitializedWsle : Uint4B
+0x01c NonDirectCount : Uint4B
+0x020 HashTable : Ptr32 _MMWSLE_HASH
+0x024 HashTableSize : Uint4B
+0x028 NumberOfCommittedPageTables : Uint4B
+0x02c HashTableStart : Ptr32 Void
+0x030 HighestPermittedHashAddress : Ptr32 Void
+0x034 NumberOfImageWaiters : Uint4B
+0x038 VadBitMapHint : Uint4B
+0x03c UsedPageTableEntries : [768] Uint2B
+0x63c CommittedPageTables : [24] Uint4B
效率上考慮,Windows 2000/XP均將這一結構映射至一固定的虛擬內存地址中。由內核變量MmWorkingSetList指定,實際上MiInitializeWorkingSetList就是直接引用這個變量對MMSUPPORT結構的VmWorkingSetList成員進行操作的。MmWorkingSetList位於內核區域(在Windows XP Build 2600 Professional中為0xc0503000),通常內核區域均是由所有進程共享的,但顯然MmWorkingSetList指定的WorkingSet情況對於每個進程都有不同的映射,即具有不同的內容,這與進程頁目錄或是頁表一樣。後者我在《小議Windows NT/2000分頁機制》中詳細的做過測試。
因為進程WorkingSet是用於描述進程使用物理內存的情況,換句話說位於WorkingSet中的頁面均位於物理內存中(沒有被置換到pagefile.sys中等),所以訪問這些頁面均不會導致Page Fault。我們可以使用VirtualLock將頁面置入進程工作集中。反過來想,系統如何知道某一頁面(使用虛擬頁面地址),針對這一進程是否存在於工作集中呢?粗粗浏覽一下上面給出的MMWSL的定義,就知道Windows 2000/XP使用哈希表(HashTable)來組織這些頁面。HashTable具有快速檢索的特點,正好適合於WorkingSet頻繁訪問的特點。另一個例子是系統全局命名內核的組織,詳見《剖析Windows NT/2000內核對象組織》。與Windbg提供dump全局命令內核對象的!object命令一樣,Windbg提供!wsle用於dump進程工作集。例如:
kd> !wsle 7
Working Set @ c0503000
FirstFree: 469 FirstDynamic: 7
LastEntry 46c NextSlot: 4 LastInitialized 658
NonDirect 145 HashTable: c06f4000 HashTableSize: 400
Reading the WSLE data...
..
Virtual Address Age Locked ReferenceCount
c0300203 0 1 1
c0301203 0 1 1
c0502203 0 1 1
c0503203 0 1 1
c0504203 0 1 1
c06f4203 0 1 1
c06f5203 0 1 1
c0505203 0 1 1
c0506203 0 1 1
77c47029 0 0 1
.
.
.