寫在前面
此係列是本人一個字一個字碼出來的,包括示例和實驗截圖。由於系統核心的複雜性,故可能有錯誤或者不全面的地方,如有錯誤,歡迎批評指正,本教程將會長期更新。 如有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閒錢,可以打賞支援我的創作。如想轉載,請把我的轉載資訊附在文章後面,並宣告我的個人資訊和本人部落格地址即可,但必須事先通知我。
你如果是從中間插過來看的,請仔細閱讀 羽夏看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;
我們到Windbg
中dd
一下:
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;
看到有我們熟悉的成員Flink
和Blink
,但它不是連結串列地址而是幀索引,因為物理頁的管理是基於幀管理的。
物理頁種類管理
物理頁有多種狀態,比如正在使用,未被使用,被交換到磁碟未被使用等等。如下是它的種類列舉:
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
如果理解了上面的那張圖,這個實驗也不難理解,就不贅述了。
下一篇
記憶體管理篇——總結與提升