轉自:http://www.cnblogs.com/Sonic2007/archive/2008/07/08/1238167.htmlhtml
任什麼時候候系統內存資源相對磁盤空間來講都是相形見拙的。由於虛擬內存機制,使咱們能夠有相對豐富的地址資源(一般32bit的虛擬地址,能夠有4G的尋址空間),而這些資源對物理內存來講通常狀況是老是綽綽有餘的。因此在現代操做系統中,老是在相對緊張時使用一些策略,如FIFO、LRU等將物理內存的一些頁面置入相對便宜的磁盤空間資源中。通常的UNIX系統,獨立使用一個分區,即swap partition。而這方面Windows只是使用普通的文件,一般命名爲pagefile.sys,位於各分區的根目錄中。因爲受到用於pagefile的PTE的限制(PTE中使用4bit來識別操做的pagefile),因此Windows最多能夠支持16個pagefile.sys。數據庫
從上描述,pagefile.sys自己就是一個比較特殊的文件,根據系統的狀況它的大小是可擴展的,一般咱們可使用「控制面板」的「系統」小Applet來設置。因爲其特殊性,Windows在啓動階段會對每一個pagefile.sys創建相應的FILE_OBJECT,而且設置SharedRead字段爲False,並且在System進程,每一個FILE_OBJECT都分別有一個句柄指向,這樣即只容許系統自身對其操做,避免用戶對其進行刪除等誤操做。
爲了對pagefile.sys進行管理,Windows中有一個長度爲16的數組,用於對pagefile.sys的組織。每一個成員分別對應一個pagefile。這個數組由系統變量MmPagingFile指向,每一個成員都是一個指向MMPAGING_FILE的結構,這個結構有以下的格式:
+0x000 Size : Uint4B
+0x004 MaximumSize : Uint4B
+0x008 MinimumSize : Uint4B
+0x00c FreeSpace : Uint4B
+0x010 CurrentUsage : Uint4B
+0x014 PeakUsage : Uint4B
+0x018 Hint : Uint4B
+0x01c HighestPage : Uint4B
+0x020 Entry : [2] Ptr32 _MMMOD_WRITER_MDL_ENTRY
+0x028 Bitmap : Ptr32 _RTL_BITMAP
+0x02c File : Ptr32 _FILE_OBJECT
+0x030 PageFileName : _UNICODE_STRING
+0x038 PageFileNumber : Uint4B
+0x03c Extended : UChar
+0x03d HintSetToZero : UChar
+0x03e BootPartition : UChar
+0x040 FileHandle : Ptr32 Void
經過這個結構,咱們能夠很容易的獲得相應pagefile的使用狀況(MaximumSize、MinimumSize、FreeSpace、CurrentUsage、PeakUsage,請參閱windbg的!vm命令),其對應的FILE_OBJECT等。另外經過FILE_OBJECT的DeviceObject與Vpb字段,咱們就可知道這個pagefile所處的分區及分區使用的文件系統等等信息。咱們來詳細介紹一下Bitmap成員。
Bitmap是一個RTL_BITMAP的結構,其定義在ntddk.h中:
typedef struct _RTL_BITMAP {
ULONG SizeOfBitMap; // Number of bits in bit map
PULONG Buffer; // Pointer to the bit map itself
} RTL_BITMAP;
與頁框數據庫(Pfn Database)與虛擬內存(x86平臺PAGE_SIZE 4k)同樣,Windows也將pagefile分割成4K一塊塊的大小,稱爲一頁,每頁由Bitmap對應的1 bit指定狀態。1爲佔用,0爲空閒。經過使用RtlFindClearBits或是RtlFindClearBitsAndSet等函數對Bitmap進行操做,尋找這些文件的未用頁面。雖然Bitmap指明佔用狀態時,Windows常以4k爲單位,但爲了提升性能,Windows在一次寫pagefile的單位一般爲64k(MmModifiedWriteClusterSize個頁)。還有MMMOD_WRITER_MDL_ENTRY,等我下面說起相關內容時再加以說明。
先用windbg來消化一下上面的討論:
kd> dd MmPagingFile l 10 //從輸出結果能夠看出個人機子上設了兩個pagefile。
80547020 80d2af80 feec1548 00000000 00000000
80547030 00000000 00000000 00000000 00000000
80547040 00000000 00000000 00000000 00000000
80547050 00000000 00000000 00000000 00000000
kd> dd @$p l 40 //第一個pagefile的狀況。
80d2af80 00006400 0000c800 00006400 00000c38
80d2af90 000057c7 000057c7 00000000 00000000
80d2afa0 feea1cb8 feea1c18 fecbb000 feddc428
.
.
.
kd> dd feddc428 l 4 //從上面給出的MMPAGING_FILE,很容易獲得file object(Offset 0x2c)。
feddc428 00700005 80ecf2f0 80ecf268 fee66c10
kd> !devobj 80ecf2f0 //aFILE_OBJECT的結構在ntddk.h中給出,其第三個dword就是DEVICE_OBJECT。
Device object (80ecf2f0) is for:
HarddiskVolume2 \Driver\Ftdisk DriverObject 80d97030
Current Irp 00000000 RefCount 1316 Type 00000007 Flags 00001150
Vpb 80ecf268 Dacl e13d1484 DevExt 80ecf3a8 DevObjExt 80ecf490 Dope 80ecf210 DevNode 80d95bd0
ExtensionFlags (0000000000)
AttachedDevice (Upper) 80d954b8 \Driver\VolSnap
Device queue is not busy.
另外FILE_OBJECT的第四個dword(fee66c10)就是VPB結構,你使用!vpb分析分析,限於篇幅,我就不在這兒列出了。
經過上面windbg的分析,咱們已經基本上對pagefile有了必定的瞭解了,下面轉入內存子系列與IO子系統(調用FSD)對pagefile的組織管理。
一般狀況下,對於進程可見的永遠是虛擬地址,存取某個虛擬地址,對於不存在的地址(對於X86,即其PTE的P位爲0),經過觸發硬件中斷(X86爲int e),由軟件來對這些PTE進行解析,譬如原型PTE(我在《探究Windows 2000/XP原型PTE》中詳細介紹),或是過渡PTE(Transition PTE,某些頁面因爲進程工做集修整等緣由,成爲可被使用的頁面,但這些頁面的內容當前對這些進程仍有效,可隨時從新使用,因此Windows使用Transition這個術語區別於純粹的Free或Zeroed列表,我在《解析Winndows 2000/XP物理內存管理》中說起PFN列表)等,而對於Page File,實際上也有一個對應的pte指向相應pagefile.sys,完成解析工做(MiResolvePageFileFault),處理頁面錯誤(經過IoPageRead,下面會介紹)。
因此在繼續討論以前咱們來講明一下Pagefile PTE,它的格式以下:
Valid : Pos 0, 1 Bit
PageFileLow : Pos 1, 4 Bits
Protection : Pos 5, 5 Bits
Prototype : Pos 10, 1 Bit
Transition : Pos 11, 1 Bit
PageFileHigh : Pos 12, 20 Bits
對於Prototype PTE與Transition PTE,總有1bit用於識別相應的PTE,如上的Prototype字段,但對於PageFile PTE,卻沒有對應的識別bit,實際上MiDispatchFault(由KiTrap0E調用),是在解析完Prototype PTE(MiResolveProtoPteFault)、Transition PTE(MiResolveTransitionFault)、還有MiResolveDemandZeroFault後,才調用MiResolvePageFileFault的,固然在MiResolveProtoPteFault處理過程當中也是最後調用MiResolvePageFileFault的。
假設咱們存取一個當前駐留在pagefile中的頁面,經過MiDispatchFault,控制權轉到MiResolvePageFileFault後,他會根據PTE的PageFileLow來索引MMPAGING_FILE數組,即判斷這一頁面位於哪一個pagefile.sys中,由於PageFileLow爲4個bit,因此Windows最多能夠支持16個pagefile.sys。這樣內存子系統根據這個索引從MmPagingFile中描述的頁文件結構取出這個pagefile的FILE_OBJECT(上面介紹過)。加上PageFileHigh所指定的pagefile.sys的偏移值,MiResolvePageFileFault經過返回一個值爲0xC0033333的特殊NTSTATUS通知MiDispatchFault調用IoPageRead獲得此頁面。IoPageRead的原型以下(定義於ntifs.h中):
NTKERNELAPI
NTSTATUS
IoPageRead(
IN PFILE_OBJECT FileObject,
IN PMDL MemoryDescriptorList,
IN PLARGE_INTEGER StartingOffset,
IN PKEVENT Event,
OUT PIO_STATUS_BLOCK IoStatusBlock
);
固然在調用IoPageRead以前,內存管理器必須分配一個物理頁面,必要的時候還要調用MiRemoveAnyPage騰出空間,而後調用MiInitializeReadInProgressPfn,將這一頁框置成ReadInProgress狀態,而後將IoPageRead所須要的MDL參數MemoryDescriptorList指向這一頁面。MDL的虛擬地址字段也就是IoPageRead讀入的頁面映射的虛擬地址,也即知足咱們先前假設的頁面錯誤。
IoPageRead實際上經過Allocate一個IRP,用DIRECT_IO的方式(即咱們提供的MDL),而後設置一個Complete Routine,用於取消頁面讀取以前的ReadInProgress狀態,再經過IoCallDriver調用IO子系統調用對應的File System Driver(一般由FILE_OBJECT的VPB參數肯定),至於FSD是如何讀取pagefile.sys的,這兒不加以討論,ntifs提供的fastfat的源代碼是學習的方向。
須要指出的是IoPageRead是一個同步操做,即只有等待頁面讀完畢之後才能夠往下處理。這也是MiDispatchFault只能運行於DISPATCH_LEVEL IRQL之下的主要緣由。IoPageRead經過設備分配的IRP的IRP_SYNCHRONOUS_PAGING_IO的標誌來實現同步的。另外他也設置了IRP_PAGING_IO、IRP_NOCACHE標誌,用於與FSD之間的特殊通訊要求。
因爲工做集修整等的須要,經過MiModifiedPageWriter(MPW)線程實行將某些頁面置入pagefile中。MPW使用MMPAGING_FILE結構的_MMMOD_WRITER_MDL_ENTRY類型的Entry進行操做,_MMMOD_WRITER_MDL_ENTRY不只僅由MiModifiedPageWriter使用,他還要讓MiMappedPageWriter使用(用於Mapped file),因此_MMMOD_WRITER_MDL_ENTRY結構不只函有MDL成員,還包含Control Area等等。限於篇幅,我不將其結構列出。MPW經過IoAsynchronousPageRead將頁面按前面說的一次MmModifiedWriteClusterSize個頁面寫入pagefile中。對於IoAsynchronousPageRead其使用的IRP flag是IRP_PAGING_IO與IRP_NOCACHE,說明他是異步操做的。這也可從他的名字看出,區別於Windows提供的另外一個相關過程IoSynchronousPageWrite,他是同步的。
講到這兒,對於page file的組織管理的一些基本的印象應該是有的。最後須要指出的一點是,對於IoPageRead不只僅是對於pagefile的,其也能夠針對mapped file,還有MiModifiedPageWriter,要不是避免死鎖也不會區分出MiMappedPageWriter,實際上Windows內部內存管理器對於pagefile與mappedfile的管理使用是基本相同的,而FSD的處理也只是一點點的區別而已。因此結合我之前介紹的如Control Area等概念,對mapped file等的理解也是能夠參照本文的。還有一樣的一句話,錯誤地方但願獲得你的指點。數組