??此系列是本人一个字一个字码出来的,包括示例和实验截图。由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我。
你如果是从中间插过来看的,请仔细阅读 羽夏看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 MmPfnDatabase80559968 80c86000 0000ff00 00000000 0000003fkd> dd MmNumberOfPhysicalPages805599c8 0009ff7c 00000040 00000000 7fff0000??0x9ff7c这个就是我虚拟机的物理页的数目,乘上4之后,就是十进制的2620912,和任务管理器显示的一模一样。下面我们来学习物理页帧的结构:
kd> dt _MMPFNnt!_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 _MMLISTSnt!_MMLISTS ZeroedPageList = 0n0 FreePageList = 0n1 StandbyPageList = 0n2 ModifiedPageList = 0n3 ModifiedNoWritePageList = 0n4 BadPageList = 0n5 ActiveAndValid = 0n6 TransitionPage = 0n7??如下是WRK中的代码:
#define NUMBER_OF_PAGE_LISTS 8typedef enum _MMLISTS { ZeroedPageList, FreePageList, StandbyPageList, //this list and before make up available pages. ModifiedPageList, ModifiedNoWritePageList, BadPageList, ActiveAndValid, TransitionPage} MMLISTS;??而这个又是通过帧管理的链表,它的结构如下:
kd> dt _MMPFNLISTnt!_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 };??我就介绍比较重要的链表含义:
MmZeroedPageListHead。??在做实验之前,我们来捋顺知识点,如下图所示:

??那我们就拿MmZeroedPageListHead这张链表来做示范吧,如下是Windbg的命令:
kd> dd MmPfnDatabase l180559968 80c86000kd> dd MmZeroedPageListHead l48054b0e0 00089a0c 00000000 0001eea4 0001f25bkd> dd 80c86000 + 1c*0001f25b l4ReadVirtual: 80fee1f4 not properly sign extended80fee1f4 ffffffff c000bf81 0001efe0 00003000kd> dd 80c86000 + 1c*0001efe0 l4ReadVirtual: 80fe9c80 not properly sign extended80fe9c80 0001f25b c07085a1 0001f31f 00003000??如果理解了上面的那张图,这个实验也不难理解,就不赘述了。
??内存管理篇——总结与提升
