記憶體管理篇——實體記憶體的管理

寂靜的羽夏發表於2022-02-23

寫在前面

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

你如果是從中間插過來看的,請仔細閱讀 羽夏看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

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

下一篇

  記憶體管理篇——總結與提升

相關文章