物理内存是相对比较紧张的资源,合理利用将是一个操作系统的性能的关键。Windows 2000/XP内部使用一个称为页框数据库(Page Frame Database)的结构用于描述物理内存的状态。本文将从这一结构入手详述Windows物理内存的组织与管理。
Windows将物理内存按PAGE_SIZE(在x86上,为0x1000字节,即4K)为单位,将其划分,每一单元在页框数据库中均有一项描述其状态及用途等。页框数据库实际上是由这些描述每一页情况结构的数组。页框数据库由内核变量MmPfnDatabase指定,而数据库中的项数则由MmNumberOfPhysicalPages指定,项数索引叫Page Frame Number(PFN)来表示。MmNumberOfPhysicalPages通常略低于系统实际拥有的物理内存页数,系统在初始化阶段保留部分页面让操作系统本身使用。需要指出的是页框数据库只描述狭义上的物理内存,不包含其他映射的物理设备的内存。
Windbg的!pfn命令用于对任一页内存的状态及用途等进行分析,如下所示:
kd> dd MmPfnDatabase l 1
80547438 80c00000
kd> !pfn 143
PFN 00000143 at address 80C01E48
flink 00000500 blink / share count 00000001 pteaddress E1085174
reference count 0001 Cached color 0
restore pte 00B5AC24 containing page 0096D8 Active P
Shared
《Inside Windows 2000》中将PFN的各个字段解释的已经非常清楚了。这里我只是简要进行说明:
flink与blink用于将特定状态的页面连成一个链表,系统中内核变量MmZeroedPageListHead、MmFreePageListHead、MmStandbyPageListHead、MmModifiedPageListHead、MmModifiedNoWritePageListHead、MmBadPageListHead用于指示这些状态的页面的链表头。从这些变量名很容易明白各链表的页面状态,在Windows中页框数据库*有8个状态,另两个为:Active与Transition状态。这8个状态由pfn的type(offset:0xd,size:byte)字段中的前3bit指示。
pteaddress是指向这一页面的pte地址。经过分析,主要有如下三种情况:
a. pteaddress为0或0xffffffff,根据pfn指示的状态,可识别是ZeroedPage或是FreePage。
b. pteaddress为0xC*******,表明这一页面当前有系统或某一进程独占,并且在进程或系统工作集中。
c. pteaddress为0xE*******,说明这是一个prototype pte,也就是说这个页面是共享的。详细请参阅我的《探究Windows 2000/XP原型PTE》。
restore pte,在《Inside Windows 2000》中称为original pte。其作用是指示这一页面的back-store位置,即数据在磁盘中某一pagefile或是mapped file中的位置。譬如在上面提及的情况c中,其一般是指向mapped file的某一subsection,所以在内部其称为Subsection PTE,由MMPTE_SUBSECTION结构定义。而另外一种情况其可能是一个指向pagefile的pte,由MMPTE_SOFTWARE结构定义。这一点,上次我提及时存在错误。MMPTE_SUBSECTION的具体bit定义如下:
Valid : Pos 0, 1 Bit
SubsectionAddressLow : Pos 1, 4 Bits
Protection : Pos 5, 5 Bits
Prototype : Pos 10, 1 Bit
SubsectionAddressHigh : Pos 11, 20 Bits
WhichPool : Pos 31, 1 Bit
最高位WhichPool是指示这个Subsection位于哪个pool中(NonPagedPool或是PagedPool中),Valid为0,指示这不是一个x86硬件可以识别的pte,由MiDispatchFault分析。由Subsection pte转换成Subsection地址的算法我在底下提供的代码中给出了。
这一描述基本阐述了Subsection PTE的作用,用于定位由PFN所指定的页面位于相应的Mapped File的位置。为了更好的解释好这一过程,《Inside Windows 2000》中使用了一个框图来解释内存管理器内部的这几个千丝万缕的联系,但各个数据结构,如PFN,SEGMENT等等介绍的不够详尽,并且之间的转换算法均没有提及。下图是我根据Windows XP Professional Build 2600的情况,重新制作的一幅图.
Windbg提供了一个!memusage命令通过分析subsectin pte得到系统中各个mapped file的使用内存情况,底下的代码,只是详细的列出了某些页面由哪些mapped file使用,并没有像!memusage有详细的统计功能,不过通过这一代码与我先前提供的文章,也能基本上明白上面这幅图之间复杂关系。
1 /* 2 For test purpose,I define the below constant,but no say 3 MmSubsectionBase and MmNonPagedPoolEnd are fixed in 4 Windows 2000 and Windows XP. They are initialized on system 5 boot phase by ntoskrnl and rely on the system physical memory size etc. 6 */ 7 #define WIN2000_2195 8 #ifdef WINXP_2600 9 #define MmSubsectionBase 0x80d21000 10 #define MmNonPagedPoolEnd 0xffbe0000 11 #endif 12 #ifdef WIN2000_2195 13 #define MmSubsectionBase 0x0 14 #define MmNonPagedPoolEnd 0xffb7f000 15 #endif 16 17 #define MmPfnDatabase 0xffb7f000 //Please redefine it on your machine. 18 #define MmNumberOfPhysicalPages 0x3f7d //Please redefine it on your machine. 19 20 /* 21 Portion of nt!MiGetSubsectionAndProtoFromPte 22 Get Subsection from restore pte(original pte) at PFN Database Entry 23 disasm by WebCrazy(tsu00@263.net) athttp://webcrazy.yeah.net 24 Thanks to wuzq(wuzq@legend.com.cn) for light! 25 */ 26 27 unsigned int MiGetSubsectionAndProtoFromPte(int pte) 28 { 29 unsigned int subaddr; 30 if(pte < 0){ 31 subaddr = MmSubsectionBase+(((pte & 0x1e) <<2) | ((pte>>4) & 0x7ffff80)) ; 32 }else{ 33 subaddr = MmNonPagedPoolEnd-(((pte & 0x1e) <<2) | ((pte>>4) & 0xfffff80)) ; 34 } 35 return subaddr; 36 } 37 38 /* 39 I release memusage() to dump Control Area. 40 Only mapped file control area were dump. 41 Please see windbg !memusage command. 42 */ 43 44 45 void memusage() 46 { 47 unsigned int *pfndatabase = MmPfnDatabase; 48 unsigned int numberphys = MmNumberOfPhysicalPages; 49 50 unsigned int restorepte,pfn=0,ppte,subsection; 51 52 unsigned char flag=0; 53 static unsigned int flagnum[8]; 54 static char *flagdesc[8]= 55 {"Zeroed","Free","Standby","Modified","ModNoWrt","Bad","Active","Trans"}; 56 memset(flagnum,0,sizeof(flagnum)); 57 58 for(;pfn<numberphys;pfn++){ 59 flag = *(char *)((char *)pfndatabase+0xd); 60 flag &= 0x07; 61 flagnum[flag]++; 62 pfndatabase+=0x18/0x04; 63 } 64 65 DbgPrint("\nMemUsage:\n"); 66 for(flag=0;flag<8;flag++) 67 DbgPrint("%10s:%04d(%08dK)\n",flagdesc[flag],flagnum[flag],flagnum[flag]*4); 68 69 70 pfndatabase = MmPfnDatabase; 71 for(pfn=0;pfn<numberphys;pfn++){ 72 ppte=*((unsigned int *)(pfndatabase+0x1)); 73 restorepte=*((unsigned int *)(pfndatabase+0x4)); 74 flag = *(char *)((char *)pfndatabase+0xd); 75 flag &= 0x07; 76 77 if(ppte>=0xE1000000&&ppte<0xF0000000){ 78 subsection=MiGetSubsectionAndProtoFromPte(restorepte); 79 DbgPrint("pfn:%04X,ppte:%08X,restorepte:%08X,subsection:%08X,ca:%08X, 80 flag:%10s\n",pfn,ppte,restorepte,subsection, 81 MmIsAddressValid((void *)subsection)?*(unsigned int *)subsection: 82 0x11111111,flagdesc[flag]); 83 } 84 pfndatabase+=0x18/0x04; 85 } 86 }