內存管理篇——物理內存的管理

寫在前面

  此系列是本人一個字一個字碼出來的,包括示例和實驗截圖。由於系統內核的複雜性,故可能有錯誤或者不全面的地方,如有錯誤,歡迎批評指正,本教程將會長期更新。 如有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閑錢,可以打賞支持我的創作。如想轉載,請把我的轉載信息附在文章後面,並聲明我的個人信息和本人博客地址即可,但必須事先通知我

你如果是從中間插過來看的,請仔細閱讀 羽夏看Win系統內核——簡述 ,方便學習本教程。

  看此教程之前,問幾個問題,基礎知識儲備好了嗎?保護模式篇學會了嗎?練習做完了嗎?沒有的話就不要繼續了。


🔒 華麗的分割線 🔒


概述

  本篇博文只是介紹一下Windows內存管理的方式,並不介紹操作系統是如何把真實的物理內存抽象為一個一個物理頁的。如果對改實現感興趣,請自行查閱其他文檔。
  在介紹物理內存的管理之前,我們先簡單回顧以前我們物理內存相關的知識。對於101012分頁模式下,操作系統最多能識別的大小為4GB,而29912分頁模式下,最多識別的大小為64GB。但是XP操作系統為什麼無論是哪種分頁模式下最多識別4GB呢?是因為操作系統內部實現的限制,如下是我逆向部分的代碼:

 if ( ExVerifySuite(DataCenter) == 1 )
  {
    MaxNumberOfPhysicalPages = 0x1000000;       // 64GB,16M個物理頁
  }
  else if ( MmProductType == 6881367
         || (isEnterprise = ExVerifySuite(Enterprise) == 1, MaxNumberOfPhysicalPages = 0x800000, !isEnterprise) )
  {
    MaxNumberOfPhysicalPages = 0x100000;        // 4GB,1M個物理頁
  }
  if ( MmNumberOfPhysicalPages + v5 > MaxNumberOfPhysicalPages )
  {
    v5 = MaxNumberOfPhysicalPages - MmNumberOfPhysicalPages;
    v6 = MaxNumberOfPhysicalPages - MmNumberOfPhysicalPages + v4;
    v40 = MaxNumberOfPhysicalPages - MmNumberOfPhysicalPages;
    v37 = v6;
  }

  而上面的偽代碼片段來自於MmAddPhysicalMemoryEx函數,如下是win2k在初始化系統函數中的限制代碼:

#if defined(_X86PAE_)
        if (ExVerifySuite(DataCenter) == TRUE) {
            MmHighestPossiblePhysicalPage = 0x1000000 - 1;
        }
        else if ((MmProductType != 0x00690057) &&
                 (ExVerifySuite(Enterprise) == TRUE)) {
            MmHighestPossiblePhysicalPage = 0x200000 - 1;
        }
        else {
            MmHighestPossiblePhysicalPage = 0x100000 - 1;
        }
#else
        MmHighestPossiblePhysicalPage = 0x100000 - 1;
#endif

  也就是說,這個和分頁沒有任何關係,純粹是操作系統搗的鬼。

初識物理內存管理

  我們的物理內存被劃分為一個個物理頁,一個物理頁的大小為4KB。所以知道物理頁的個數,我們就可以知道物理內存的大小,而這些數據可以通過任務管理器進行查看,如下圖所示:

  對於物理內存,操作系統採用幀管理數據庫的模式。一個物理頁就是一幀,被稱之為PFN這個東西。在操作系統中,有一個全局變量,被稱之為幀數據庫,如下所示:

extern PMMPFN MmPfnDatabase;

  上面的數據庫本質就是一個數組,那麼我們如何知道它的大小呢?是通過如下變量知道的:

//
// Total number of physical pages available on the system.
//
PFN_COUNT MmNumberOfPhysicalPages;

  我們到Windbgdd一下:

kd> dd MmPfnDatabase
80559968  80c86000 0000ff00 00000000 0000003f

kd> dd MmNumberOfPhysicalPages
805599c8  0009ff7c 00000040 00000000 7fff0000

  0x9ff7c這個就是我虛擬機的物理頁的數目,乘上4之後,就是十進制的2620912,和任務管理器顯示的一模一樣。下面我們來學習物理頁幀的結構:

kd> dt _MMPFN
nt!_MMPFN
   +0x000 u1               : __unnamed
   +0x004 PteAddress       : Ptr32 _MMPTE
   +0x008 u2               : __unnamed
   +0x00c u3               : __unnamed
   +0x010 OriginalPte      : _MMPTE
   +0x018 u4               : __unnamed

  有很多共用體。如下是從WRK搬運過來的:

typedef struct _MMPFN {
    union {
        PFN_NUMBER Flink;
        WSLE_NUMBER WsIndex;
        PKEVENT Event;
        NTSTATUS ReadStatus;

        //
        // Note: NextStackPfn is actually used as SLIST_ENTRY, however
        // because of its alignment characteristics, using that type would
        // unnecessarily add padding to this structure.
        //

        SINGLE_LIST_ENTRY NextStackPfn;
    } u1;
    PMMPTE PteAddress;
    union {
        PFN_NUMBER Blink;

        //
        // ShareCount transitions are protected by the PFN lock.
        //

        ULONG_PTR ShareCount;
    } u2;
    union {

        //
        // ReferenceCount transitions are generally done with InterlockedXxxPfn
        // sequences, and only the 0->1 and 1->0 transitions are protected
        // by the PFN lock.  Note that a *VERY* intricate synchronization
        // scheme is being used to maximize scalability.
        //

        struct {
            USHORT ReferenceCount;
            MMPFNENTRY e1;
        };
        struct {
            USHORT ReferenceCount;
            USHORT ShortFlags;
        } e2;
    } u3;
#if defined (_WIN64)
    ULONG UsedPageTableEntries;
#endif
    union {
        MMPTE OriginalPte;
        LONG AweReferenceCount;
    };
    union {
        ULONG_PTR EntireFrame;
        struct {
#if defined (_WIN64)
            ULONG_PTR PteFrame: 57;
#else
            ULONG_PTR PteFrame: 25;
#endif
            ULONG_PTR InPageError : 1;
            ULONG_PTR VerifierAllocation : 1;
            ULONG_PTR AweAllocation : 1;
            ULONG_PTR Priority : MI_PFN_PRIORITY_BITS;
            ULONG_PTR MustBeCached : 1;
        };
    } u4;

} MMPFN, *PMMPFN;

  看到有我們熟悉的成員FlinkBlink,但它不是鏈表地址而是幀索引,因為物理頁的管理是基於幀管理的。

物理頁種類管理

  物理頁有多種狀態,比如正在使用,未被使用,被交換到磁盤未被使用等等。如下是它的種類枚舉:

kd> dt _MMLISTS
nt!_MMLISTS
   ZeroedPageList = 0n0
   FreePageList = 0n1
   StandbyPageList = 0n2
   ModifiedPageList = 0n3
   ModifiedNoWritePageList = 0n4
   BadPageList = 0n5
   ActiveAndValid = 0n6
   TransitionPage = 0n7

  如下是WRK中的代碼:

#define NUMBER_OF_PAGE_LISTS 8

typedef enum _MMLISTS {
    ZeroedPageList,
    FreePageList,
    StandbyPageList,  //this list and before make up available pages.
    ModifiedPageList,
    ModifiedNoWritePageList,
    BadPageList,
    ActiveAndValid,
    TransitionPage
} MMLISTS;

  而這個又是通過幀管理的鏈表,它的結構如下:

kd> dt _MMPFNLIST
nt!_MMPFNLIST
   +0x000 Total            : Uint4B
   +0x004 ListName         : _MMLISTS
   +0x008 Flink            : Uint4B
   +0x00c Blink            : Uint4B

  如下是列表的結構體:

typedef ULONG PFN_NUMBER, *PPFN_NUMBER;

typedef struct _MMPFNLIST {
    PFN_NUMBER Total;
    MMLISTS ListName;
    PFN_NUMBER Flink;
    PFN_NUMBER Blink;
} MMPFNLIST;

  如下是操作系統管理的物理頁的鏈表:

MMPFNLIST MmZeroedPageListHead = {
                    0, // Total
                    ZeroedPageList, // ListName
                    MM_EMPTY_LIST, //Flink
                    MM_EMPTY_LIST  // Blink
                    };

MMPFNLIST MmFreePageListHead = {
                    0, // Total
                    FreePageList, // ListName
                    MM_EMPTY_LIST, //Flink
                    MM_EMPTY_LIST  // Blink
                    };

MMPFNLIST MmStandbyPageListHead = {
                    0, // Total
                    StandbyPageList, // ListName
                    MM_EMPTY_LIST, //Flink
                    MM_EMPTY_LIST  // Blink
                    };

MMPFNLIST MmModifiedPageListHead = {
                    0, // Total
                    ModifiedPageList, // ListName
                    MM_EMPTY_LIST, //Flink
                    MM_EMPTY_LIST  // Blink
                    };

MMPFNLIST MmModifiedNoWritePageListHead = {
                    0, // Total
                    ModifiedNoWritePageList, // ListName
                    MM_EMPTY_LIST, //Flink
                    MM_EMPTY_LIST  // Blink
                    };

MMPFNLIST MmBadPageListHead = {
                    0, // Total
                    BadPageList, // ListName
                    MM_EMPTY_LIST, //Flink
                    MM_EMPTY_LIST  // Blink
                    };

//
// Note the ROM page listhead is deliberately not in the set
// of MmPageLocationList ranges.
//

MMPFNLIST MmRomPageListHead = {
                    0, // Total
                    StandbyPageList, // ListName
                    MM_EMPTY_LIST, //Flink
                    MM_EMPTY_LIST  // Blink
                    };


PMMPFNLIST MmPageLocationList[NUMBER_OF_PAGE_LISTS] = {
                                      &MmZeroedPageListHead,
                                      &MmFreePageListHead,
                                      &MmStandbyPageListHead,
                                      &MmModifiedPageListHead,
                                      &MmModifiedNoWritePageListHead,
                                      &MmBadPageListHead,
                                      NULL,
                                      NULL };

  我就介紹比較重要的鏈表含義:

  • MmBadPageListHead :壞鏈,物理頁被損壞掛到上面。
  • MmZeroedPageListHead :零化鏈表,是系統在空閑的時候進行零化的,不是程序自己清零的那種。
  • MmFreePageListHead :空閑鏈表,物理頁是周轉使用的,剛被釋放的物理頁是沒有清0,系統空閑的時候有專門的線程從這個隊列摘取物理頁,加以清0後再掛入MmZeroedPageListHead
  • MmStandbyPageListHead :備用鏈表,當系統內存不夠的時候,操作系統會把物理內存中的數據交換到硬盤上,此時頁面不是直接掛到空閑鏈表上去,而是掛到備用鏈表上,雖然我釋放了,但裡邊的內容還是有意義的。

實驗測試

  在做實驗之前,我們來捋順知識點,如下圖所示:

  那我們就拿MmZeroedPageListHead這張鏈表來做示範吧,如下是Windbg的命令:

kd> dd MmPfnDatabase l1
80559968  80c86000

kd> dd MmZeroedPageListHead l4
8054b0e0  00089a0c 00000000 0001eea4 0001f25b

kd> dd 80c86000 + 1c*0001f25b l4
ReadVirtual: 80fee1f4 not properly sign extended
80fee1f4  ffffffff c000bf81 0001efe0 00003000

kd> dd 80c86000 + 1c*0001efe0 l4
ReadVirtual: 80fe9c80 not properly sign extended
80fe9c80  0001f25b c07085a1 0001f31f 00003000

  如果理解了上面的那張圖,這個實驗也不難理解,就不贅述了。

下一篇

  內存管理篇——總結與提升