发新话题
打印

[转载]探寻Windows NT/2000 Copy On Write机制

[转载]探寻Windows NT/2000 Copy On Write机制

信息来源:webcrazy(http://www.geocities.jp/webcrazyjp/
文章作者:WebCrazy

探寻Windows NT/2000 Copy On Write机制
        WebCrazy(http://wecrazy.yeah.net)  
               
   Copy On Write机制是典型的Lazy evaluation实现,现代操作系统如Windows NT/2000,UNIX/Linux的内存管理部分大量使用这种机制。本文通过对Windows NT/2000中Copy On Write机制作一深入分析,旨在探寻Windows NT/2000内核态内存管理器的几个重要的数据结构,在继续以下的讨论之前,您务必要明白PDE/PTE、VAD等一些术语(参见我先前的《小议Windows NT/2000分页机制》与《分析Windows NT/2000堆内存与虚拟内存组织 》),另外我将述及另几个与内存子系统相关的术语,如Control Area,Subsection,Working Set List,Page Frame Database。下面列出我在本文中用于分析Copy On Write机制的代码,文中所有的叙述均基于这段代码。

   // cow.c
   // Writed by ChenChengQin(tsu00@263.net )

   #include <stdio.h>
   #include <string.h>

   #define BUFSIZE 10

   #pragma data_seg(".seg_cow")
   char data[BUFSIZE]={&#39;A&#39;,&#39;A&#39;,&#39;A&#39;,&#39;A&#39;,&#39;A&#39;,&#39;A&#39;,&#39;A&#39;,&#39;A&#39;,&#39;A&#39;,&#39;\0&#39;};
   #pragma data_seg()
   #pragma comment(linker, "/SECTION:.seg_cow,RWC")

   void main(int argc,char *argv[])
    {
     int i;
     if (argc>1){
      //memset(data,&#39;B&#39;,BUFSIZE);
      __asm int 3;
      for(i=0;i<BUFSIZE;i++)
        data=&#39;B&#39;;
      data[BUFSIZE-1]=&#39;\0&#39;;
     }
     printf("%s",data);

     getchar();
    }


   非常简单的一段代码,使用如下指令编译:

      cl /Zi /Fa cow.c

   运行一个实例(别退出):
      c:>cow
      AAAAAAAAAA

   使用SoftICE看看cow.exe映像在内存中的分布:

      :addr cow
      :map32 cow
      Owner    Obj Name  Obj#  Address      Size    Type
      cow     .text    0001  001B:00401000  00006BB8  CODE  RO
      cow     .rdata   0002  0023:00408000  000005C8  IDATA RO
      cow     .data    0003  0023:00409000  00002EC4  IDATA RW
      cow     .idata   0004  0023:0040C000  00000633  IDATA RW
      cow     .seg_cow  0005  0023:0040D000  0000010C  IDATA RW
      cow     .reloc   0006  0023:0040E000  000006B2  IDATA RO

      //查看.seg_cow段(虚拟地址0x40d000)的首地址的PTE,详见《小议Windows NT/2000分页机制 》
      :dd c0000000+1*1000+d*4 l 10
      0023:C0001034 003F9225  00000000  00000000  00C3C067    %.?.........g...
               --------
                  |
                  |_cow.exe进程.seg_cow段首地址的PTE

   SoftICE的page命令可以dump出这个PTE的属性,如下所示:

     :page 40d000
     Linear    Physical  Attributes
     0040D000  003F9000  P  A U R

   不过这里列出的Attributes并没有指出.seg_cow的Copy On Write属性,因为X86的PTE的低12位(0-11),即属性位并没有指出这个属性,但Microsoft(当然不仅仅Microsoft)使用了这12位中的保留的系统(OS)位,下面是这一DWORD值在Windows 2000的具体格式:

     struct  _HARDWARE_PTE_X86 (sizeof=4)
     +0 bits0-0 Valid
     +0 bits1-1 Write
     +0 bits2-2 Owner
     +0 bits3-3 WriteThrough
     +0 bits4-4 CacheDisable
     +0 bits5-5 Accessed
     +0 bits6-6 Dirty
     +0 bits7-7 LargePage
     +0 bits8-8 Global
     +0 bits9-9 CopyOnWrite
     +0 bits10-10 Prototype
     +0 bits11-11 reserved
     +0 bits12-31 PageFrameNumber
   
   从上的PTE为003F9225的值可以看出0x40d000的只读、CopyOnWrite属性。只读属性是实现CopyOnWrite的保证,这样X86才能在另一个cow实例试图写.seg_cow段时raise一个0eh(页故障)中断(陷阱),让Windows处理这个Copy On Write操作。基于这个原理,我即运行cow的另一个实例,跟踪Copy On Write机制:

   c:>cow 1(设置一个参数,让cow试图更新.seg_cow段)

   我在代码中设置了int 3指令让softice在i3here设置为on状态时让softice激活。然后使用bpint e指令让softice捕获0eh中断。i386在raise 0e中断时将发生页故障的虚拟地址保存于CR2寄存器中。Windows 2000页故障处理入口KiTrap0E就是根据这个地址制作cow进程.seg_cow的副本,并替代第二个cow实例工作集(Working List,这儿只指进程工作集,未牵涉系统工作集)的原始页面,实现CopyOnWrite的目的。我并不准备将KiTrap0E的汇编代码列于此,只是讲一讲Windows寻找CR2指定的地址的属性的步骤,Windows 2000中由MiQueryAddressState过程实现。KiTrap0E当然也会调用MiQueryAddressState。

   1、首先检查0x40d000(由CR2指定)的PDE。由MiDoesPdeExistAndMakeValid实现。
   2、检查PTE。
   3、查页框数据库(PFN,Page Frame Database,由内核结构数组变量MmPfnDatabase指定)
   4、根据PFN中工作集索引寻找Working Set List Entry(Working Set基址由内核变量_MmWsle指出),这一步骤由内核例程MiLocateWsle完成。
   5、然后根据Wsle的属性(也是前12位)查找MmProtectToValue数组,以获得用户态可以理解的格式。即在winnt.h中定义的PAGE_WRITECOPY、PAGE_READWRITE等等。

   步骤3至5其实也实现了物理地址至线性地址的转换,当然这个是在这个地址Present的前提下。这也是为什么Windows 2000使用如此复杂且繁琐的结构来管理内存子系统。确切的说我并没有谈到PFN的PteAddress成员(下面i386kd输出可以看到),这些都是使用分页文件(pagefile.sys,通过ProtoPTE,即原型PTE实现的),共享内存块等的基础。David Solomon的书《Inside Windows NT,2nd Edition》有四种PFN形态的具体结构等等其他很多详细的说明。

   下面是i386kd的分析,其实根据上面我给定的一些内核变量SoftICE也可以看出些东西。不过远没有i386kd来的容易。

   // cow.exe 进程ID(ClientID)=4ac
   kd> !process 4ac
   !process 4ac
   Searching for Process with Cid == 4ac
   PROCESS ff605d60  SessionId: 0  Cid: 04ac   Peb: 7ffdf000  ParentCid: 04a0
      DirBase: 017bc000  ObjectTable: ff610f88  TableSize:  12.
      Image: cow.exe
      VadRoot ff624168 Clone 0 Private 37. Modified 0. Locked 0.
          .
          .
          .
   //转换40d000至物理地址,取得PFN。
   //SoftICE中使用Page命令
   kd> !vtop 017bc000 40d000
          --------
            |_DirBase(见!process输出)
      !vtop 017bc000 40d000
      Pdi 1 Pti d                 //输出PDE与PTE
      0040d000 00a6e000 pfn(00a6e)      //输出PFN

   //查PfnDatabase
   //Softice中:
   //  dd @MmPfnDatabase+a6e*18 (每一PFN项占用0x18字节)
   kd> !pfn a6e
   !pfn a6e
      PFN 00000A6E at address FFB8EA50
      flink     00000097  blink / share count 00000001  pteaddress E151E1B4
      reference count 0001                      color 0
      restore pte 056B04B0  containing page      002AF3  Active    P
      Shared


   /*
    查MmWsle
    SoftICE中:
      :dd @MmWsle+97*4 l 10
      0023:C05028FC 0040DF29  000006A0  00510C09  000009B0    ).@.......Q.....
                --------
                  |_0040D000是不是PFN为A6E的Virtual Address
   */
   kd> !wsle 4ac
   !wsle 4ac

   // KPEB中的VmWorkingSetList成员(Windows 2000 Server Build 2195中偏移0f0处,值为C0502000)指出Working Set List
   // 见《Windows 2000内核KPEB/KTEB详细结构 》

   Working Set @ c0502000  
      Quota:       2f  FirstFree:     22  FirstDynamic:       4
      LastEntry     ad  NextSlot:      16  LastInitialized    257
      NonDirect     1e  HashTable: c06f3000  HashTableSize:     200
          .
          .
          .

   这些i386kd指令已经较清楚的指出以上5个步骤。但要理解Copy On Write还必须对Section(用户态的FileMapping)对象有一基本的理解。在使用NtCreateSection/NtOpenSection(CreateFileMapping/OpenFileMapping间接使用这些例程)等时,Windows 2000一般会在VAD这个自平衡的二叉树中插入一个节点。(我在《分析Windows NT/2000堆内存与虚拟内存组织 》中详细介绍过VAD)。SoftICE在使用query指令dump VAD树时有一成员MMCI(内存管理结构),她指向的是Control Area。如下所示:

   // SoftICE输出
   :query cow
   Address Range    Flags    MMCI    PTE     Name
   00010000-00010000  C4000001
          .
          .
          .
   00400000-0040E000  07100005  FF62FD48  E11EBF80  cow.exe
                      --------
                        |_Control Area
          .
          .
          .

   其实我们使用i386kd的!memusage可以dump出系统中所有的Control Area:

   kd> !memusage
    loading PFN database
    loading (99% complete)
         Zeroed:    15 (   60 kb)
          Free:    0 (    0 kb)
        Standby:  1274 (  5096 kb)
        Modified:   686 (  2744 kb)
    ModifiedNoWrite:    1 (    4 kb)
     Active/Valid:  14380 ( 57520 kb)
      Transition:    11 (   44 kb)
        Unknown:    0 (    0 kb)
          TOTAL:  16367 ( 65468 kb)
    Building kernel map
    Finished building kernel map

    Control Valid Standby Dirty Shared Locked PageTables  name
          .
          .
          .
    ff62fd48   32    0    0    0    0    0  mapped_file( cow.exe )        
    --------
      |_Control Area与上面SoftICE的MMCI一致
          .
          .
          .

    i386kd的ca命令可以看出一个Control Area结构:

    kd> !ca ff62fd48

    ControlArea @ff62fd48
     Segment:   e11ebf48   Flink          0  Blink:          0
     Section Ref      1   Pfn Ref        8  Mapped Views:      1
     User Ref        2   Subsections      7  Flush Count:      0
     File Object ff684288   ModWriteCount    0  System Views:      0
     WaitForDel      0   Paged Usage     a0  NonPaged Usage    120
     Flags (10000a0) Image File HadUserReference

      File: \Desktop\hack\cow.exe

    Segment @ e11ebf48:
      Base address      0  Total Ptes      f  NonExtendPtes:      f
      Image commit      5  ControlArea ff62fd48  SizeOfSegment: f000
      Image Base       0  Committed       0  PTE Template:  54f1c30
      Based Addr    400000  ProtoPtes  e11ebf80  Image Info:   e11ebfc0

    Subsection 1. @ ff62fd80
      ControlArea: ff62fd48  Starting Sector 0 Number Of Sectors 8
      Base Pte    e11ebf80  Ptes In subsect      1 Unused Ptes       0
      Flags          15  Sector Offset       0 Protection        1
      ReadOnly CopyOnWrite
          .
          .
          .

   由ca命令的输出结果可以看出这个Control Area有7个子区域(Subsection),限于篇幅我删掉部分输出结果,你可以将所有结果与SoftICE的map32指令输出比较比较。Control Area中实际上所有Subsection结构均是使用线性结构组织,每个Subsection在Windows 2000 Server Build 2195中占用0x20字节。所以SoftICE可以很容易的分析所有的这些。

   需要指出的是不仅仅Section对象使用Control Area,她在Windows 2000中也由SECTION_OBJECT_POINTERS结构使用:

   typedef struct _SECTION_OBJECT_POINTERS {
      PVOID DataSectionObject;   //Control Area
      PVOID SharedCacheMap;
      PVOID ImageSectionObject;  //Control Area
   } SECTION_OBJECT_POINTERS;

   而每一个FILE_OBJECT都有SECTION_OBJECT_POINTERS成员(见ntddk.h)。这个机制是Windows 2000装载可执行文件、文件IO操作(看到DataSectionObject与ImageSectionObject了吗?)的关键所在。只有熟悉这些结构,才可能真正初步明白Copy On Write机制。剩下的只有你自己多研究研究了。

   关于cow.exe,我还有两点要说明的是:
   1、cow.c使用#pragma comment(linker, "/SECTION:.seg_cow,RWC")显式的指出.cow_seg段的Copy On Write属性,实际上在Windows NT/2000中这种共享属性是默认存在的。可执行文件的映射、可读可写的数据都被默认设为Copy On Write属性,您可以使用Jeffrey Richter的VMMAP验证这种说法。
   2、Windows NT/2000使用可执行文件名(ImageName)用于识别这种程序多个实例间使用Copy On Write共享内存,对于有不同文件名的可执行文件,即便其内容完全一致,这种机制也不起作用,而用分别映射Section对象替之。

   所有以上讨论的都没有找到Microsoft的Full Documented,所有以上讨论的也只是在我初步分析Windows 2000后得到的,有什么技术问题需要交流的,欢迎赐教(tsu00@263.net)!

参考资料:
    1.David Solomom《Inside Windows NT,2nd Edition》

TOP

发新话题