Valid : Pos 0, 1 Bit
LockedInWs : Pos 1, 1 Bit
LockedInMemory : Pos 2, 1 Bit
Protection : Pos 3, 5 Bits
SameProtectAsProto : Pos 8, 1 Bit
Direct : Pos 9, 1 Bit
Age : Pos 10, 2 Bits
VirtualPageNumber : Pos 12, 20 Bits
wsle命令也即根据这低12bit输出WSLE的一些属性:如Age与Locked。ReferenceCount则位于PFN中,具体请参阅《解析Windows 2000/XP物理内存管理》。
整个结构至此已经比较明朗了,但是正像上面提到的WorkingSet访问是非常频繁的,在检索指定虚拟地址的页面是否在WorkingSet中还要依靠另一个重要的成员HashTable。既然通过HashTable,我们来给出HashFunction(有兴趣想知道如何得到HashFunction的可像我一样看看MiInsertWsle是如何实现的)。
((PVA >>a) & 0x3ffffc) % (HashTableSize-1)
这里,PVA指页面虚拟地址,而HashTableSize指当前进程的WorkingSet哈希表的大小。对于给定的一个页面,如何在WSLE数组中快速的检索到这个页面的数组下标呢?有了哈希表,当然通过Hash表了。这样描述还是比较抽象,我们以一个具体的例子说明问题:从上面wsle命令输出结果,我们知道虚拟地址77c47000(77c47029那一行),未于MMWSLE的第十项(数组下标为9,即index为9),而这个进程的工作集HashTableSize值为0x400(这个值可能系统会在需要时通常MiGrowWsleHash更改),所以:
((77c47029>>a)&0x3ffffc) % (0x400-1)
值为0x9a,所以位于HashTable的第0x9a个Bucket中(以0开始),通过上面得到的HashTable地址c06f4000,找到第0x9a个bucket。而每个Bucket的大小呢?需要说明的是这个HashTable的每个Bucket如下定义(_MMWSLE_HASH):
+0x000 Key : Uint4B
+0x004 Index : Uint4B
即每个bucket为8个字节,所以我们用如下kd命令得到结果:
kd> dd c06f4000+9a*8 l 2
c06f44d0 77c47000 00000009
其Key值为77c47000,即虚拟地址,Index值为9,即验证了上面windbg的wsle命令输出结果。现在,对于WorkingSet的组织也已经讨论的差不多了,需要指出的是在Windows XP中WorkingSet的设计远比这讨论的多很多内容,比如WorkingSet的哈希表是可扩展的(通过MiGrowWsleHash),HashTable内容的插入、更改、删除,还有工作集修整(通过MiTrimWorkingSet)等等,特别是工作集修整,文章开头提到工作集的一个主要作用合理利用物理内存,避免某个进程(或是系统)耗尽物理内存,通过WorkingSet的最大、最小值与Quota指定的限额,限定物理内存的使用。如果出现越出这样的一个范围或是物理内存耗尽,则会使用工作集修整。Andrew Tanenbaum的《Modern Operating Systems》介绍了多种工作集修整的算法,在单处理器中Windows 2000/XP中使用了更像LRU的时候算法(Clock algorithm正像很多Unix系统实现一样),你应该看到上面输出的Age的值吧。由于条件限制我只能在单处理器上实验过。为了篇幅完整,我简要介绍一下多处理器的情况:多处理中Windows 2000使用FIFO(First In First Out)算法,但从我看到的Microsoft的一些介绍中,似乎Windows XP/.Net Server 2003在多处理中也使用LRU了,看来Windows的内核是越来越完善了。
本文只介绍了进程工作集,对于系统工作集及Session工作集,大同小异,实际上我是在分析了三种工作集后,才开始着手写这样的一篇。这一些些的概念、结构在自己的学习过程中不断被发现,也着实让自己兴奋不已,但我从来没有看过任何关于这些结构层次上的讨论,错误之处,在所难免,敬请见谅,谢谢!
-----------------------------------------------------------------------------------------

投稿指南


