PageFile

    任何時候系統內存資源相對磁盤空間來說都是相形見拙的。因爲虛擬內存機制,使我們可以有相對豐富的地址資源(通常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等的理解也是可以參照本文的。還有同樣的一句話,錯誤地方希望得到你的指點。

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