在《解析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
.
.
.
wsle命令只是將VmWorkingSetList的Wsle成員(MMWSLE指標)指向的數組的每個元素dump出(每個元素32bit)。windbg的!wsle命令獲得的結果中Virtual Address列即Wsle的每一個32bit的內容。如下windbg命令所示:
kd> dd MmWorkingSetList l 1 //當前進程MMWSL結構所在的地址,如本文前頭描述。
805467d0 c0503000
kd> dd c0503000 l 10 //MMWSL內容
c0503000 000003b9 000003ba 00000007 000003b9
c0503010 00000004 c050369c 00000658 0000014c
--------
|_MMWSLE內容(如上給出的MMWSL定義,MMWSLE是一個指標)
c0503020 c06f4000 00000400 0000001a c06f4000
| |_HashTableSize(Uint4B)雜湊表大小
|_HashTable(MMWSLE_HASH)地址(底下將會用到這兩個數值)
c0503030 c0800000 00000000 0000005c 004d023a
kd> dd c050369c
//結果即上面wsle命令輸出的Virtual Address列(WorkingSet
//頻繁變動,如果有稍許不同可能是系統已經更改過了)。
c050369c c0300203 c0301203 c0502203 c0503203
c05036ac c0504203 c06f4203 c06f5203 c0505203
實際上這裡的每一個Virtual Address,就像上所示的如c0300203不僅僅是Virtual Address,因為WorkingSet是以頁面為單位的,所以這些32bit的內容中有12bit用於其它用途。實際上在Windows XP中這個32bit的內容定義為MMWSLENTRY,具體為:
Valid : Pos 0, 1 Bit
LockedInWs : Pos 1, 1 Bit
LockedInMemory : Pos 2, 1 Bit
Protection : Pos 3, 5 Bits
SameProtectAsProto : Pos 8, 1 Bit
Direct : Pos 9, 1 Bit
Age : Pos 10, 2 Bits
VirtualPageNumber : Pos 12, 20 Bits
wsle命令也即根據這低12bit輸出WSLE的一些屬性:如Age與Locked。ReferenceCount則位於PFN中,具體請參閱《解析Windows 2000/XP實體記憶體管理》。
整個結構至此已經比較明朗了,但是正像上面提到的WorkingSet訪問是非常頻繁的,在檢索指定虛擬位址的頁面是否在WorkingSet中還要依靠另一個重要的成員HashTable。既然通過HashTable,我們來給出HashFunction(有興趣想知道如何得到HashFunction的可像我一樣看看MiInsertWsle是如何?的)。
((PVA >>a) & 0x3ffffc) % (HashTableSize-1)
這裡,PVA指頁面虛擬位址,而HashTableSize指當前進程的WorkingSet雜湊表的大小。對於給定的一個頁面,如何在WSLE數組中快速的檢索到這個頁面的數組下標呢?有了雜湊表,當然通過Hash表了。這樣描述還是比較抽象,我們以一個具體的例子說明問題:從上面wsle命令輸出結果,我們知道虛擬位址77c47000(77c47029那一行),未於MMWSLE的第十項(數組下標為9,即index為9),而這個進程的工作集HashTableSize值為0x400(這個值可能系統會在需要時通常MiGrowWsleHash更改),所以:
((77c47029>>a)&0x3ffffc) % (0x400-1)
值為0x9a,所以位於HashTable的第0x9a個Bucket中(以0開始),通過上面得到的HashTable地址c06f4000,找到第0x9a個bucket。而每個Bucket的大小呢?需要說明的是這個HashTable的每個Bucket如下定義(_MMWSLE_HASH):
+0x000 Key : Uint4B
+0x004 Index : Uint4B
即每個bucket為8個位元組,所以我們用如下kd命令得到結果:
kd> dd c06f4000+9a*8 l 2
c06f44d0 77c47000 00000009
其Key值為77c47000,即虛擬位址,Index值為9,即驗證了上面windbg的wsle命令輸出結果。現在,對於WorkingSet的組織也已經討論的差不多了,需要指出的是在Windows XP中WorkingSet的設計遠比這討論的多很多內容,比如WorkingSet的雜湊表是可擴充的(通過MiGrowWsleHash),HashTable內容的插入、更改、刪除,還有工作集修整(通過MiTrimWorkingSet)等等,特別是工作集修整,文章開頭提到工作集的一個主要作用合理利用實體記憶體,避免某個進程(或是系統)耗盡實體記憶體,通過WorkingSet的最大、最小值與Quota指定的限額,限定實體記憶體的使用。如果出現越出這樣的一個範圍或是實體記憶體耗盡,則會使用工作集修整。Andrew Tanenbaum的《Modern Operating Systems》介紹了多種工作集修整的演算法,在單一處理器中Windows 2000/XP中使用了更像LRU的時候演算法(Clock algorithm正像很多Unix系統實現一樣),你應該看到上面輸出的Age的值吧。由於條件限制我只能在單一處理器上實驗過。為了篇幅完整,我簡要介紹一下多處理器的情況:多處理中Windows 2000使用FIFO(First In First Out)演算法,但從我看到的Microsoft的一些介紹中,似乎Windows XP/.Net Server 2003在多處理中也使用LRU了,看來Windows的核心是越來越完善了。
本文只介紹了進程工作集,對於系統工作集及Session工作集,大同小異,實際上我是在分析了三種工作集後,才開始著手寫這樣的一篇。這一些些的概念、結構在自己的學習過程中不斷被發現,也著實讓自己興奮不已,但我從來沒有看過任何關於這些結構層次上的討論,錯誤之處,在所難免,敬請見諒,謝謝!
-----------------------------------------------------------------------------------------
文章來自:http://www.geocities.jp/webcrazyjp/ntws.htm (這傢伙竟用日本的網域名稱)
作者:WebCrazy(http://webcrazy.yeah.net)