导语:Windows以及几乎所有操作系统都会使用PFN数据库 ( PFN DataBase ) ,以便跟踪虚拟分配的页面。虚拟分配页面的管理过程都是通过一个名为页帧号(PFN)的列表进行管理的。本文,我将通过大量实际的案例,来解释Windows PFN的实现。
Windows页帧号(Page Frame NuMer ,PFN)介绍
Windows以及几乎所有操作系统都会使用PFN数据库 ( PFN DataBase ) ,以便跟踪虚拟分配的页面,进一步管理哪些页面要被写入或移出,哪些页面需要缓存页面等。
虚拟分配页面的管理过程都是通过一个名为页帧号(PFN)的列表进行管理的,关于每个物理和虚拟分配页面的状态及其相应属性都有一长串解释。
本文,我将通过大量实际的案例,来解释Windows PFN的实现。
如果你熟悉non-PAE模式和PAE模式系统,那么就应该注意到,在non-PAE模式下,每个PFN结构需要24个字节,而在PAE模式系统中,需要25个字节,因此如果你的页面是4096个字节,那么就分配大约24个字节以保持对每个页面的追踪。
在non-PAE模式下,每个物理页大小是4K,对于每一个物理页,PFN数据库会使用一个24字节长的结构来保存它的相关信息,比如该物理页是否已经被使用,这是170:1的比率。而在PAE模式下,每个物理页大小是4K,对于每一个物理页,PFN数据库会使用一个28字节长的结构来保存它的相关信息,这是146:1的比率。这意味着PFN数据库中,每1G大小的物理内存,都需要大约6M或7M来描述其中的信息。但是如果你有一个具有16G物理内存的32位系统,那么它只需要大约112M的2G内核虚拟地址空间来处理随机存取存储器(RAM)。这也是为什么拥有16G或更大物理内存的系统不允许使用3G模式(也称为expaneuserva)的原因,因为该模式会将用户虚拟地址空间增加到3G并将32位系统的内核虚拟地址空间减少到1G,扩展页面的好处之一(例如每页2M)需要更少的MMPFN结构。
在开始深入了解PFN之前,请记住术语“Page”主要用于操作系统级概念,而“Frame”用于CPU级概念,因此“Page”表示虚拟页面,“Page Frame”表示物理页面。
PFN列表
PFN由描述某些页面状态的列表组成,比如活动列表(Active List)显示活动页面(例如,在working sets中);待机列表(Standby List)表示先前在磁盘中备份的列表,页面本身可以在不产生磁盘IO的情况下清空和重用;修改列表(Modified List )显示该页面先前已被修改,并且必须以某种方式写入磁盘;移出列表(Freed List),顾名思义,它显示不再需要维护的页面,可以被移出;最后是零列表(Zero List),描述一个包含全零(0)的页面。
下图显示了PFN数据库列表之间的相互关联关系:
这些列表是用于管理内存中“页面错误”状态的,每次发生“页面错误”后,Windows都试图找到一个可用的表单页,如果列表为空则是零列表,然后零列表则从移出列表中获取可用的表单页,另外,如果移出列表也为空,则移出列表从待机列表获取可用的表单页,并将页面用全零(0)表示。
零页面线程
在Windows中,有一个优先级为0的线程,负责在系统空闲时将内存归零,并且是整个系统中唯一优先级为0的线程。这是可用的最低优先级,因为用户线程至少是1,该线程尽可能清除移出列表。此外,Windows中有一个名为RtlSecureZeroMemory()的函数,它可以安全地移出一个位置,但是在以下的内核透视图中,nt!KeZeroPages负责移出页面。
下图显示了零页面线程的整个过程:
此时,零线程(Zero Thread)就可以看得清清楚楚。知道它来自系统进程,且它的优先级为0,这应该就足够了,不再需要更多的信息了。首先尝试查找系统的nt!_eprocess:
!process 0 System
现在我们可以看到系统的线程,我的目标线程(零线程)的细节如下:
THREAD ffffd4056ed00040 Cid 0004.0040 Teb: 0000000000000000 Win32Thread: 0000000000000000 WAIT: (WrFreePage) KernelMode Non-Alertable fffff8034637f148 NotificationEvent fffff80346380480 NotificationEvent Not impersonating DeviceMap ffff99832ae1b010 Owning Process ffffd4056ec56040 Image: System Attached Process N/A Image: N/A Wait Start TickCount 4910 Ticks: 4 (0:00:00:00.062) Context Switch Count 21023 IdealProcessor: 3 UserTime 00:00:00.000 KernelTime 00:00:01.109 Win32 Start Address nt!MiZeroPageThread (0xfffff80346144ed0) Stack Init ffffe700b7c14c90 Current ffffe700b7c14570 Base ffffe700b7c15000 Limit ffffe700b7c0f000 Call 0000000000000000 Priority 0 BasePriority 0 PriorityDecrement 0 IoPriority 2 PagePriority 5 Child-SP RetAddr Call Site ffffe700`b7c145b0 fffff803`46016f8a nt!KiSwapContext+0x76 ffffe700`b7c146f0 fffff803`46016951 nt!KiSwapThread+0x16a ffffe700`b7c147a0 fffff803`46014ba7 nt!KiCommitThreadWait+0x101 ffffe700`b7c14840 fffff803`461450b7 nt!KeWaitForMultipleObjects+0x217 ffffe700`b7c14920 fffff803`460bba37 nt!MiZeroPageThread+0x1e7 ffffe700`b7c14c10 fffff803`46173456 nt!PspSystemThreadStartup+0x47 ffffe700`b7c14c60 00000000`00000000 nt!KiStartSystemThread+0x16
正如你所看到的,它的起始地址是nt!MiZeroPageThread,且它的优先级是0,如果你看到调用栈,那么就可以看到之前调用的 nt!MiZeroPageThread。
更多细节,请访问《隐藏的内存分配成本》一文。
PFN的Windows结构是nt!_MMPFN,如下图所示:
如你所见,_MMPFN需要28个字节。
PFN记录会根据其物理地址顺序存储在存储器中,这意味着你可以在PFN的帮助下计算出物理地址。
Physical Address = PFN * page size(e.g 4096 Byte) + offset
PFN数据库的地址位于nt!MmPfnDatabase,你可以使用以下所示的代码段在Windbg中获取PFN数据库地址。
2: kd> x nt!MmPfnDatabase fffff800`a2a76048 nt!MmPfnDatabase = <no type information>
!memusage
windbg中另一个非常有用的命令是!memusage,这个命令几乎提供了内存布局中关于PFN和页面的几乎所有内容及其相关细节,例如文件、字体、系统驱动程序、DLL模块、可执行文件(包括它们的名称和它们的分页位修改) 。
!memusage命令从物理内存角度显示内存统计信息,无数个页面信息将被打印出来,可以说是“最内存”的信息。此命令会查看所有的页帧,所以运行时会非常的耗时。
该命令的简要介绍如下所示:
2: kd> !memusage loading PFN database loading (100% complete) Compiling memory usage data (99% Complete). Zeroed: 9841 ( 39364 kb) Free: 113298 ( 453192 kb) Standby: 105520 ( 422080 kb) Modified: 7923 ( 31692 kb) ModifiedNoWrite: 0 ( 0 kb) Active/Valid: 286963 ( 1147852 kb) Transition: 45 ( 180 kb) SLIST/Bad: 567 ( 2268 kb) Unknown: 0 ( 0 kb) TOTAL: 524157 ( 2096628 kb) Dangling Yes Commit: 140 ( 560 kb) Dangling No Commit: 37589 ( 150356 kb) Building kernel map Finished building kernel map (Master1 0 for 80) (Master1 0 for 580) (Master1 0 for 800) (Master1 0 for 980) Scanning PFN database - (97% complete) (Master1 0 for 7d100) Scanning PFN database - (100% complete) Usage Summary (in Kb): Control Valid Standby Dirty Shared Locked PageTables name ffffffffd 11288 0 0 0 11288 0 AWE ffffd4056ec4c460 0 112 0 0 0 0 mapped_file( LeelUIsl.ttf ) ffffd4056ec4c8f0 0 160 0 0 0 0 mapped_file( malgun.ttf ) ffffd4056ec4d6b0 0 108 0 0 0 0 mapped_file( framd.ttf ) ..... ffffd4057034ecd0 328 148 0 0 0 0 mapped_file( usbport.sys ) ffffd4057034f0e0 48 28 0 0 0 0 mapped_file( mouclass.sys ) ffffd4057034f7d0 32 28 0 0 0 0 mapped_file( serenum.sys ) ffffd405703521a0 0 20 0 0 0 0 mapped_file( swenum.sys ) ..... -------- 0 20 0 ----- ----- 0 session 0 0 -------- 4 0 0 ----- ----- 0 session 0 ffffe700b8b45000 -------- 4 0 0 ----- ----- 0 session 1 ffffe700b8ead000 -------- 32520 0 84 ----- ----- 1324 process ( System ) ffffd4056ec56040 -------- 2676 0 0 ----- ----- 304 process ( msdtc.exe ) ffffd405717567c0 -------- 4444 0 0 ----- ----- 368 process ( WmiPrvSE.exe ) ffffd405718057c0 -------- 37756 0 60 ----- ----- 1028 process ( SearchUI.exe ) ffffd405718e87c0 ..... -------- 8 0 0 ----- 0 ----- driver ( condrv.sys ) -------- 8 0 0 ----- 0 ----- driver ( WdNisDrv.sys ) -------- 52 0 0 ----- 0 ----- driver ( peauth.sys ) -------- 24744 0 0 ----- 0 ----- ( PFN Database ) Summary 1147852 422260 31692 129996 204428 25156 Total ..... b45b 64 0 0 60 0 0 Page File Section b56b 4 0 0 4 0 0 Page File Section b7ec 84 0 0 64 0 0 Page File Section b905 12 0 0 0 0 0 Page File Section bf5c 4 0 0 0 0 0 Page File Section .....
有关这些页面的更多信息,Windbg帮助文档是这样解释的:
你可以使用!vm扩展命令分析虚拟内存使用情况,这个扩展通常比!memusage更有用。有关内存管理的更多信息,请参阅Mark Russinovich和David Solomon撰写的Microsoft Windows Internals。!pfn扩展命令可用于在PFN数据库中显示特定页帧条目。
在下一节,我会介绍如何更详细的查看这些命令。