淺談Windows 2000/XP File Cache實現
WebCrazy(http://webcrazy.yeah.net)
聲明:本文所敘內容為Windows NT/2000/XP的Cache管理內容,涉及內容均為Microsoft Undocumented內容。我在參考大量資料後分析而成,牽涉內容多多少少可能會有些許錯誤,甚至根本就是錯誤的,但本著學習交流的目的,權當是寫下的筆記公佈於此(http://webcrazy.yeah.net),希望能多加於交流(tsu00@263.net),以便我以後更新錯誤。
記得早期dos下smartdrv.exe的功效就曾讓我有較深的印象。我不記得我是在什麼時候對這個小檔案有興趣且對其進行過學習的,這也是我對File Cache最初期的印象。在Windows NT/2000/XP中Cache管理部分更是讓我有了現代作業系統實現File Cache的整體概念。讀過《Inside Windows 2000》或是《Windows NT File System Internals》的肯定對此有比較深的理解,我不準備對他們已經涉及到的內容過於拘泥,我發現其實他們涉及到的許多東西並沒有深入(雖然都有點出),或許也沒有必要講到具體實現上來,這會隨著Windows的新版本的出現有些許改進變化。我得益於這兩本書與其他許多資料,我也建議在往下看此文之前,首先好好的看一下他們。
我們知道Cache部分最主要的是對檔案(含網路遠程檔案)等較費時的IO操作作快取(置入相對較快的記憶體中),既然其操作的對象主要是File(Windows中很多東西在底層均是作為File對象的),我們的敘述也已FILE對象開始。一個FILE對象,均有一個SECTION_OBJECT_POINTERS定義(詳細的定義請參閱ntddk.h或ntifs.h中的FILE_OBJECT)。我在《探尋Windows NT/2000 Copy On Write機制》中指出過其定義:
typedef struct _SECTION_OBJECT_POINTERS {
PVOID DataSectionObject; //Control Area
PVOID SharedCacheMap;
PVOID ImageSectionObject; //Control Area
} SECTION_OBJECT_POINTERS;
因為成員DataSectionObject與ImageSectionObject均指向一稱為Control Area的內部結構,而Control Area結構又指向一個稱為Segment的內部結構。Segment由一個或多個原型PTE(Prototype PTE,PPTE)組成。PPTE是一個軟體結構,我在《小議Windows NT/2000分頁機制》結尾處提及過PPTE,也提及過PPTE不同於那邊介紹的常規硬體PTE。PPTE的一個目的即是實現頁面共用。而一個或多個PPTE(s)則組成了Subsection。這些基於kernel debug的ca命令的輸出結果分析而成。
成員SharedCacheMap則是真正的File Cache使用的結構。不同於FILE_OBJECT的另一個成員PrivateCacheMap,針對同一個檔案,任何開啟的指向這個檔案的Handle指向的FILE_OBJECT結構的SharedCacheMap均是同一個值,這也是共用檔案一個保證。我們來看在系統某一時刻同時指向shdocvw.dll的兩個FILE_OBJECT(地址分別為80d37550與80db4860)的情況:
//指向shdocvw.dll的第一個FILE_OBJECT:
:fobj 80d37550
DeviceObject * : 80EE2888
Vpb * : 80EE2800
FsContext * : E14D2D90
FsContext2 * : E14D2EE8
SecObjPointer * : 80DD15F4
PrivateCacheMap * : 80EA70A0
.
.
.
FileName : /WINDOWS/system32/shdocvw.dll
.
.
.
//指向shdocvw.dll的第二個FILE_OBJECT:
:fobj 80db4860
DeviceObject * : 80EE2888
Vpb * : 80EE2800
FsContext * : E14D2D90
FsContext2 * : E1183678
SecObjPointer * : 80DD15F4
PrivateCacheMap * : 80D6F2D8
.
.
.
FileName : /WINDOWS/system32/shdocvw.dll
.
.
.
從以上Softice的結果我們很容易的發現SECTION_OBJECT_POINTERS指向同一個記憶體地區,所以SharedCacheMap肯定指向同一個值,而PrivateCacheMap則是兩個全然不同的值,進一步分析,FsContext也指向同一個值,而FsContext2則不同,實際上在實現FSD時,這兩個成員一個為File Control Buffer(參閱ntifs.h中的FSRTL_COMMON_FCB_HEADER定義),一個指向Context Control Buffer,具體的用法可參考ntifs中的fastfat或是cdfs的實現,不在本文的討論範圍。
分析過後FILE_OBJECT,我們可以得到一個結論:通過FILE_OBJECT我們可以得到這個FILE對象的一個或兩個Control Area,而通過Control Area我們則可以得到Segment,既而即是Segment底下的Subsection與PPTEs,另外通過FILE_OBJECT我們也可以得到這檔案所有執行個體的SharedCacheMap。事實上SharedCacheMap也有指標指向FILE_OBJECT,下面我會繼續說明。
記得我很早前就在《分析Windows NT/2000堆記憶體與虛擬記憶體組織》中定義過這樣一個結構:
typedef struct vad {
void *StartingAddress;
void *EndingAddress;
struct vad *ParentLink;
struct vad *LeftLink;
struct vad *RightLink;
ULONG Flags;
ULONG MMCI;
ULONG ProtoPTE;
}VAD, *PVAD;
實際上MMCI也即Control Area,我在《探尋Windows NT/2000 Copy On Write機制》中也作過解釋。ProtoPTE即PPTE。
有了這樣一個概念以後,我們即可以很容易的發現PPTE是如何?頁面共用的。系統在初次訪問PPTE所指向的頁面時,由於PPTE的bit 0為無效,所以發生Page Fault,從而交由int e處理,即控制權由ntoskrnl.exe中的KiTrap0E交由MmAccessFault處理,然後通過查VAD,即可通過Control Area與PPTE定位PFN Database,讀取在磁碟的內容,更新PTE實現共用的目的。
詳細的談了FILE_OBJECT後,我們轉向File Cache的討論。Windows 2000/XP保留了由MmSystemCacheStart與MiSystemCacheStartExtra指向的兩塊系統虛擬記憶體地區專門用於System Cache。這專門的地區被分成大小為VACB_MAPPING_GRANULARITY(由ntifs.h中定義,值為0x40000,即256KB)的View。各View的使用方式由系統中的一個稱為VACB的內部結構表示。這樣系統中則有一個VACB數組,由CcVacbs指定。《Inside Windows 2000》中列舉了VACB的四個成員,指出其包含一個重要的成員,即SharedCacheMap,下面是!dso命令的輸出結果:
kd> !dso VACB //Kernel Debug Extension Build 2167 Free
Structure VACB - Size: 0x18
000 BaseAddress 004 SharedCacheMap
008 Overlay 010 LruList
但通過分析,我發現Windows XP中VACB實際上由6個DWORD值組成,其第二個DWORD即SharedCacheMap。Windows XP使用6個DWORD,可能是其支援Larger Mapped Files的具體實現(FILE_OFFSET使用LARGE_INTEGER,雖然《Inside Windows 2000》中也指出過FileOffset成員,但不知為什麼dso命令的輸出結果沒有顯式的提供這樣的一個成員,另我非常疑惑,難道又是版本間的差異嗎?)。下面是XP底下的分析:
kd> dd CcVacbs l 1
80542fec 80ecc000
kd> dd 80ecc000 //dd Vacb
80ecc000 cb440000 80eaac78 00300000 00000000
80ecc010 80eceda0 80ecf598 d4040000 80eaac78
80ecc020 01000000 00000000 80ecd6a8 80ecede8
80ecc030 c1100000 80eefed0 00000000 00000000
80ecc040 80ece050 80542fe0 deb40000 ffa55538
80ecc050 00000000 00000000 80ecd558 80ecc958
kd> dd 80eaac78 l 12 //dd Vacb->SharedCacheMap
80eaac78 013002ff 00000001 01a95400 00000000
80eaac88 80eaac68 80eaaac0 01b00000 00000000
80eaac98 ffffffff 7fffffff ffffffff 7fffffff
80eaaca8 00000000 00000000 00000000 00000000
80eaacb8 80eaa910 80ee2a80
上面我曾提及SharedCacheMap也有指標指向FILE_OBJECT,我發現其位置在的第18個DWORD上,下面即Dump出FILE_OBJECT。
kd> dd 80ee2a80 l 6 //dd FILE_OBJECT
80ee2a80 00700005 80ee2888 80ee2800 80e7f9b0
80ee2a90 00000000 80ee23a4
FILE_OBJECT的定義已在ntddk.h中給出,所以很容易的得到其SECTION_OBJECT_POINTERS。
kd> dd 80ee23a4 l 3 //dd FILE_OBJECT->SECTION_OBJECT_POINTERS
80ee23a4 80e7f710 80eaac78 00000000
kd> !ca 80e7f710 //!ca SECTION_OBJECT_POINTERS->DataSectionObject
ControlArea @80e7f710
Segment: e127a548 Flink 0 Blink 0
Section Ref 1 Pfn Ref 275 Mapped Views 5f
User Ref 0 WaitForDel 0 Flush Count 0
File Object 80ee2a80 ModWriteCount 0 System Views 5f
Flags (8088) NoModifiedWriting File WasPurged
File: /$Mft
.
.
.
這樣我們即dump出了CcVacbs指向的System Cache中的第一個VACB的內容,從ca命令的輸出結果我們可以看出System Cache的第一個View由NTFS的中繼資料$Mft使用。Kernel Debug也提供一個叫filecache的命令,我在使用!filecache命令時曾查閱過其文檔,文檔中提及(WinDbg:6.0.00007.0):
Each line of this extension's output represents a virtual address control block (VACB). When named files are mapped into the VACB, the names of these files are displayed. If "no name for file" is specified, this means that this VACB is being used to cache metadata.
從上面的注釋我還以為filecache命令通過遍曆VACB數組,dump出所有Control Area,就像我上面的手工分析VACB過程一樣。但實際上此命令輸出的不僅僅只有這些內容。通過分析kdexts.dll中filecache的實現(Windows XP)中,發現其通過PsLoadedModuleList來實現dump filecache(具體內容需進一步學習)。
最後還要指出的是系統通過CcIsFileCached宏來判斷File是否被Cached。其在ntifs.h中定義,有了上面的分析後是非常容易理解的:
#define CcIsFileCached(FO) ( /
((FO)->SectionObjectPointer != NULL) && /
(((PSECTION_OBJECT_POINTERS)(FO)->SectionObjectPointer)->SharedCacheMap != NULL) /
)
本文未牽涉到另一個非常重要的資料結構PFN資料庫,由MmPfnDatabase指定,PFN資料庫中共用頁面有PPTE的定義,實際上發生指向PPTE的頁面的Page Fault後,尋找完VAD後,系統會根據PFN資料庫的PPTE完成下一步的工作。
實際上關於Control Area、Subsection、PPTE、VACB、SharedCacheMap等的具體定義是我比較感興趣的,而我目前為止仍對此只有些許概念。這也是我出此文的目的,也希望達到拋磚引玉的作用(tsu00@263.net)。