windows內核情景分析筆記---虛存向物理頁面的映射


既然懂了虛存和物理存儲的管理,下面談談虛存向物理存儲的映射

首先看第一個函數

NTSTATUS

NTAPI

MmCreateVirtualMapping(PEPROCESS Process,

                       PVOID Address,

                       ULONG flProtect,

                       PPFN_TYPE Pages,

                       ULONG PageCount)

{

   ULONG i;

 

   for (i = 0; i < PageCount; i++)//講解點A

   {

      if (!MmIsUsablePage(Pages[i]))

      {

         DPRINT1("Page at address %x not usable\n", PFN_TO_PTE(Pages[i]));

         KEBUGCHECK(0);

      }

   }

 

   return(MmCreateVirtualMappingUnsafe(Process,

                                       Address,

                                       flProtect,

                                       Pages,

                                       PageCount));

}

   Process為給定進程,address為需要建立映射的虛存塊的起始地址,pages指向一個頁面號pfn的數組,數組的大小也就是PageCount

講解點A

   此處page數組是該進程從屬所有頁面號的數組,而且這里的頁面號指的是物理頁面號,比如如果PageCount3,則該數組可以是{100,200,300},代表該進程對應着內存中第100,200,300這三個物理頁。而第100號物理頁面在數組中下標為0,200的下標為1,所以這里要有個

   可以看到,該函數首先檢測所有頁面是否合法,函數MmIsUsablePage定義如下

  BOOLEAN

NTAPI

MmIsUsablePage(PFN_TYPE Pfn)

{

 

   DPRINT("MmIsUsablePage(PhysicalAddress %x)\n", Pfn << PAGE_SHIFT);

 

   if (Pfn == 0 || Pfn >= MmPageArraySize)

   {

      KEBUGCHECK(0);

   }

 

   if (MmPageArray[Pfn].Flags.Type != MM_PHYSICAL_PAGE_USED &&

         MmPageArray[Pfn].Flags.Type != MM_PHYSICAL_PAGE_BIOS)

   {

      return(FALSE);

   }

 

   return(TRUE);

  這里也印證了上述猜測,因為

   if (MmPageArray[Pfn].Flags.Type != MM_PHYSICAL_PAGE_USED &&

         MmPageArray[Pfn].Flags.Type != MM_PHYSICAL_PAGE_BIOS)

   {

      return(FALSE);

   }

   該代碼用傳過來的Pfn參數來檢測MmPageArray數組,而MmPageArray數組是什么呢,就是內存物理頁面的對應數組。這個函數目的是檢測頁面屬性。

   往下走,可以看到本函數實際上調用了MmCreateVirtualMappingUnsafe

   看看MmCreateVirtualMappingUnsafe的定義

   NTSTATUS

NTAPI

MmCreateVirtualMappingUnsafe(PEPROCESS Process,

                             PVOID Address,

                             ULONG flProtect,

                             PPFN_TYPE Pages,

                             ULONG PageCount)

{

   ULONG Attributes;

   PVOID Addr;

   ULONG i;

   ULONG oldPdeOffset, PdeOffset;

   BOOLEAN NoExecute = FALSE;

 

   DPRINT("MmCreateVirtualMappingUnsafe(%x, %x, %x, %x (%x), %d)\n",

          Process, Address, flProtect, Pages, *Pages, PageCount);

 

   if (Process == NULL)//Process==NULL//代表虛存屬於屬於系統空間,本屬於特定進程

   {   //MmSystemRangeStart代表內核空間和用戶空間的分界線,低2G是用戶空間,高2G是內核空間

      if (Address < MmSystemRangeStart)

      {

         DPRINT1("No process\n");

         KEBUGCHECK(0);//錯誤的落在用戶空間

      }

      if (PageCount > 0x10000 ||

  (ULONG_PTR) Address / PAGE_SIZE + PageCount > 0x100000)//頁面數超過0x10000

      {

         DPRINT1("Page count to large\n");

 KEBUGCHECK(0);

      }

   }

   Else//虛存屬於特定進程

   {

      if (Address >= MmSystemRangeStart)//錯誤的落在了內核空間

      {

         DPRINT1("Setting kernel address with process context\n");

         KEBUGCHECK(0);

      }

      if (PageCount > (ULONG_PTR)MmSystemRangeStart / PAGE_SIZE ||

  (ULONG_PTR) Address / PAGE_SIZE + PageCount >

  (ULONG_PTR)MmSystemRangeStart / PAGE_SIZE)//該進程虛存頁面數竟然超過了用戶空間的所有頁面數,是可忍孰不可忍,肯定要報錯

      {

         DPRINT1("Page Count to large\n");

 KEBUGCHECK(0);

      }

   }

 

   Attributes = ProtectToPTE(flProtect);//講解點A

   if (Attributes & 0x80000000)//不可執行

   {

      NoExecute = TRUE;

   }

   Attributes &= 0xfff;//眾所周知,低124個空閑位和8個標志位,這里意圖是讓高20位為0

   if (Address >= MmSystemRangeStart)//如果虛存地址大於內核和用戶空間的分界線,就要與~PA_USER相或

   {

      Attributes &= ~PA_USER;

      if (Ke386GlobalPagesEnabled)

      {

 Attributes |= PA_GLOBAL;

      }

   }

   else

   {

      Attributes |= PA_USER;

   }

 

   Addr = Address;

 

   if (Ke386Pae)//Ke386Pae不知道什么鬼,應該是Kernal386Page的簡寫

   {

      ULONGLONG Pte, tmpPte;

      PULONGLONG Pt = NULL;

 

      oldPdeOffset = PAE_ADDR_TO_PDE_OFFSET(Addr) + 1;//講解點B

      for (i = 0; i < PageCount; i++, Addr = (PVOID)((ULONG_PTR)Addr + PAGE_SIZE))

      {

         if (!(Attributes & PA_PRESENT) && Pages[i] != 0)//所在的二級頁表不存在

         {

            DPRINT1("Setting physical address but not allowing access at address "

                    "0x%.8X with attributes %x/%x.\n",

                    Addr, Attributes, flProtect);

            KEBUGCHECK(0);

         }

         PdeOffset = PAE_ADDR_TO_PDE_OFFSET(Addr);//循環中判斷該頁所在的PDE(二級頁表)

         if (oldPdeOffset != PdeOffset)//如果該頁所在的PDE索引和上一頁的PDE索引不同

         {

            MmUnmapPageTable((PULONG)Pt);  //講解點C

    Pt = MmGetPageTableForProcessForPAE(Process, Addr, TRUE);//找到頁面表項

    if (Pt == NULL)//紅色部分的函數定義沒有讀懂,用到了后面的知識,這兩個函數后面會再詳細解釋

    {

       KEBUGCHECK(0);

    }

         }

         else

         {

            Pt++;//同一個頁面表里面的

         }

         oldPdeOffset = PdeOffset;

 

     MmMarkPageMapped(Pages[i]);//將物理頁面的數據結構標記為已映射

 tmpPte = PAE_PFN_TO_PTE(Pages[i]) | Attributes;講解點D

 if (NoExecute)

 {

    tmpPte |= 0x8000000000000000LL;

 }

         Pte = ExfpInterlockedExchange64UL(Pt, &tmpPte);

         if (PAE_PAGE_MASK((Pte)) != 0LL && !((Pte) & PA_PRESENT))

         {

            KEBUGCHECK(0);

         }

         if (PAE_PAGE_MASK((Pte)) != 0LL)

         {

            MmMarkPageUnmapped(PAE_PTE_TO_PFN((Pte)));

         }

         if (Address < MmSystemRangeStart &&

     ((PMADDRESS_SPACE)&Process->VadRoot)->PageTableRefCountTable != NULL &&

             Attributes & PA_PRESENT)

         {

            PUSHORT Ptrc;

 

            Ptrc = ((PMADDRESS_SPACE)&Process->VadRoot)->PageTableRefCountTable;

 

            Ptrc[PAE_ADDR_TO_PAGE_TABLE(Addr)]++;//遞增頁面映射表的引用計數

         }

         if (Pte != 0LL)

         {

            if (Address > MmSystemRangeStart ||

                (Pt >= (PULONGLONG)PAGETABLE_MAP && Pt < (PULONGLONG)PAGETABLE_MAP + 4*512*512))

            {

              MiFlushTlb((PULONG)Pt, Address);

            }

         }

      }

      if (Addr > Address)

      {

         MmUnmapPageTable((PULONG)Pt);

      }

   }

   else

   {

      PULONG Pt = NULL;

      ULONG Pte;

      oldPdeOffset = ADDR_TO_PDE_OFFSET(Addr) + 1;

      for (i = 0; i < PageCount; i++, Addr = (PVOID)((ULONG_PTR)Addr + PAGE_SIZE))

      {

         if (!(Attributes & PA_PRESENT) && Pages[i] != 0)

         {

            DPRINT1("Setting physical address but not allowing access at address "

                    "0x%.8X with attributes %x/%x.\n",

                    Addr, Attributes, flProtect);

            KEBUGCHECK(0);

         }

         PdeOffset = ADDR_TO_PDE_OFFSET(Addr);

         if (oldPdeOffset != PdeOffset)

         {

            MmUnmapPageTable(Pt);

    Pt = MmGetPageTableForProcess(Process, Addr, TRUE);

    if (Pt == NULL)

    {

       KEBUGCHECK(0);

    }

         }

         else

         {

            Pt++;

         }

         oldPdeOffset = PdeOffset;

 

         Pte = *Pt;

         MmMarkPageMapped(Pages[i]);//解除該物理頁面的原來的映射

         if (PAGE_MASK((Pte)) != 0 && !((Pte) & PA_PRESENT))//PTE非空並且PTE本身有映射,但不在內存里

         {

            KEBUGCHECK(0);

         }

         if (PAGE_MASK((Pte)) != 0)//PTE非空

         {

            MmMarkPageUnmapped(PTE_TO_PFN((Pte)));//去掉原來的映射,原則上,多個虛存頁面可以對應一個物理頁面,而一個物理頁面同時只能對應一個虛存頁面,我們既然要把這個虛存頁面映射到某個特定的物理頁面,首先要考慮,是否有其他虛存頁面已經映射到了這個物理頁面,如果有,就要先解除綁定,就要用這個函數

         }

 (void)InterlockedExchangeUL(Pt, PFN_TO_PTE(Pages[i]) | Attributes);//講解點E

         if (Address < MmSystemRangeStart &&

     ((PMADDRESS_SPACE)&Process->VadRoot)->PageTableRefCountTable != NULL &&

             Attributes & PA_PRESENT)

         {

            PUSHORT Ptrc;

 

            Ptrc = ((PMADDRESS_SPACE)&Process->VadRoot)->PageTableRefCountTable;

 

            Ptrc[ADDR_TO_PAGE_TABLE(Addr)]++;

         }

         if (Pte != 0)

         {

            if (Address > MmSystemRangeStart ||

                (Pt >= (PULONG)PAGETABLE_MAP && Pt < (PULONG)PAGETABLE_MAP + 1024*1024))

            {

               MiFlushTlb(Pt, Address);//沖刷該頁面映射表項在高速緩存中的映像

            }

         }

      }

      if (Addr > Address)

      {

         MmUnmapPageTable(Pt);

      }

   }

   return(STATUS_SUCCESS);

}

講解點A

   Attributes = ProtectToPTE(flProtect);

   這個函數定義如下

   static ULONG

ProtectToPTE(ULONG flProtect)

{

   ULONG Attributes = 0;

 

   if (flProtect & (PAGE_NOACCESS|PAGE_GUARD))

   {

      Attributes = 0;

   }

   else if (flProtect & PAGE_IS_WRITABLE)

   {

      Attributes = PA_PRESENT | PA_READWRITE;

   }

   else if (flProtect & (PAGE_IS_READABLE | PAGE_IS_EXECUTABLE))

   {

      Attributes = PA_PRESENT;

   }

   else

   {

      DPRINT1("Unknown main protection type.\n");

      KEBUGCHECK(0);

   }

   if (Ke386NoExecute &&

       !(flProtect & PAGE_IS_EXECUTABLE))

   {

      Attributes = Attributes | 0x80000000;

   }

 

   if (flProtect & PAGE_SYSTEM)

   {

   }

   else

   {

      Attributes = Attributes | PA_USER;

   }

   if (flProtect & PAGE_NOCACHE)

   {

      Attributes = Attributes | PA_CD;

   }

   if (flProtect & PAGE_WRITETHROUGH)

   {

      Attributes = Attributes | PA_WT;

   }

   return(Attributes);

}

  可以看到,是通過flProtect的各個位值傳回一個ULONG類型的數值。

  

講解點B

   PAE_ADDR_TO_PDE_OFFSET(Addr)

   其宏定義為

   #define ADDR_TO_PDE_OFFSET(v) (((ULONG)(v))/1024*PAGE_SIZE)

   二級頁表每一個表,有1024項,每項對應一個4k大小的頁面,所以一個二級表,對應的虛存大小為1024*PAGE_SIZE4K),所以如果要找到二級頁表的下標,也就是PDE,就要虛存地址除以1024*PAGE_SIZE

 

講解點C

   看這個函數

   MmUnmapPageTable

   定義如下

   BOOLEAN MmUnmapPageTable(PULONG Pt)

  {

   if (Ke386Pae)

   {

      if ((PULONGLONG)Pt >= (PULONGLONG)PAGETABLE_MAP && (PULONGLONG)Pt <   (PULONGLONG)PAGETABLE_MAP + 4*512*512)

      {

         return TRUE;

      }

   }

   else

   {

      if (Pt >= (PULONG)PAGETABLE_MAP && Pt < (PULONG)PAGETABLE_MAP + 1024*1024)

      {

         return TRUE;

      }

   }

   if (Pt)

   {

      MmDeleteHyperspaceMapping((PVOID)PAGE_ROUND_DOWN(Pt));

   }

   return FALSE;

 }

   這里不會,如果后面學會了,這里會補上

 

 講解點D

     

   PAE_PFN_TO_PTE(Pages[i]) 這個宏,顧名思義,就是將PFN轉為PTEPFN是物理頁面下標,如何轉呢?

   由於內存有4G,並且一個物理頁面是4k大小

   4G/4K=1M,所以PTE最多有1M個(當然,這里采用了二級頁表,不過不影響,因為無論采取如何方式,前20位肯定是代表了PTE索引)

  正常流程是先找PTE的前20位,確定哪個PTE表項,映射到物理頁面后然后加上后面12位的頁內偏移,就構成了確切的32位地址。

  而反過來如何通過物理頁號知道指向這個頁面的PTE內容(這里要搞懂,我們要逆向推出的是什么,明顯是PTE內容,而不是PTE的地址,也不是PTE所在頁表的首項),明顯,PTE20位的內容和這個物理頁地址的前20位是一樣的(如果不一樣,或者沒關系,那PTE該如何指向這個物理頁呢?對吧)

  所以,這個物理頁的地址的前20位,也就是PFNOK,我們前面知道啦,針對內存有一個數組,每個數組都是一個指針,指向一個結構體,這個結構體描述了一個頁面及其屬性,這數組有幾個元素呢,前面已經證明,有1M個,而PFN則是其下標,所以既然“尋址空間”是1M,那用20位就可以代表啦,也就是說,PFN20位就可以描述)。

  既然證明了PFN20位,而PTE32位,20如何轉32呢?我們知道PTE32位,高20位是來尋找物理頁的,低12位是尋找頁內偏移的,所以我們只需要把20位的PFN左移20位,也就是說

  #define   PFN_TO_PTEX((X)<<PAGE_SHIFT)

  這里PAGE_SHIFT12

 PTE_TO_PFN呢,正好相反

 

講解點E

 (void)InterlockedExchangeUL(Pt, PFN_TO_PTE(Pages[i]) | Attributes);

 InterlockedExchangeUL本身是個宏,意思是將第二個參數的值付給以第一個參數為地址的內存單元內,這里第二個參數是PFN_TO_PTE(Pages[i]),就是page[i]左移12位,和Attribute相或,生成一個32位的PTE,然后賦給地址為Pt的內存單元,這里pt一直在自增,所以虛存頁面是連續的。

  注意這里的賦給地址為Pt的內存單元意味着,該物理頁面與虛存頁面映射了起來。

MmCreateVirtualMappingUnsafe總體分析如下: 首先要明白參數的含義,

PEPROCESS Process,

PVOID Address,

ULONG flProtect,

PPFN_TYPE Pages,

ULONG PageCount

   尤其要注意Address參數,該參數表示要映射的虛存地址,page表示一個數組,每個元素都是一個頁面索引,性質和MmPagesArray是一樣的,這個函數的目的就是給定一個pages數組,該數組中包括了一堆物理頁面(當然數組中都是其索引值),然后我們的任務是將這一堆物理頁面與虛存對應的這一堆PTE聯系起來。

   要注意,page數組中的數字可以使連續的,也可以是不連續的,可能是1 2 3,也可能是1 4  9,而虛存對應的PTE則是連續的,這也正好可以和操作系統課本上講解二級頁表的那個映射圖互相印證,一堆連續的PTE映射到了不連續的物理頁面上。

   在Ke386Pae為假的情況下,我們首先用一個變量保存原來的PDE索引,然后以page數組為基礎進行遍歷,每次遍歷addr都是自增一個PAGE值,遍歷時候首先判斷該物理頁面索引對應的PDE是否存在,如果存在,比較下新的addr對應的PDE,如果二者不同,也就是說本虛存頁面和上一個虛存頁面屬於不同的二級頁表,那就要找到頁面表項,如果否,也就是處於同一個二級頁表,則Pt++

    Pt這里其實代表的是二級頁表某一項的虛擬地址,Pte=*pt意思就是說虛擬地址為Pt的這一項的內容,賦給Pte

   接下來,首先要把該次循環時候的物理頁面的映射去除,然后判斷Pte是否為空並且是否映射不在內存中,如果都是,則異常退出,往下走,如果Pte非空,則去掉Pte對應的原來的映射,接下來,就要用到講解點E,這樣,物理頁面和虛存頁面就建立映射了。

    接下來,就要沖刷映射表在高速緩存中的映像,讓CPU能夠訪問地址時候不用每次都二次訪問內存,如果緩存中有,則直接拿來用。

   這個函數分析里很多調用的函數都沒有分析好,慚愧,如果以后搞懂了,這里會補上來。

 

 

 

 

 

VOID

NTAPI

MmDeleteVirtualMapping(PEPROCESS Process, PVOID Address, BOOLEAN FreePage,

                       BOOLEAN* WasDirty, PPFN_TYPE Page)

/*

 * FUNCTION: Delete a virtual mapping

 */

{

   BOOLEAN WasValid = FALSE;

   PFN_TYPE Pfn;

 

   DPRINT("MmDeleteVirtualMapping(%x, %x, %d, %x, %x)\n",

          Process, Address, FreePage, WasDirty, Page);

   if (Ke386Pae)

   {

      ULONGLONG Pte;

      PULONGLONG Pt;

 

      Pt = MmGetPageTableForProcessForPAE(Process, Address, FALSE);

      if (Pt == NULL)

      {

         if (WasDirty != NULL)

         {

            *WasDirty = FALSE;

         }

         if (Page != NULL)

         {

            *Page = 0;

         }

         return;

      }

 

      /*

       * Atomically set the entry to zero and get the old value.

       */

      Pte = 0LL;

      Pte = ExfpInterlockedExchange64UL(Pt, &Pte);

 

      MiFlushTlb((PULONG)Pt, Address);

 

      WasValid = PAE_PAGE_MASK(Pte) != 0 ? TRUE : FALSE;

      if (WasValid)

      {

         Pfn = PAE_PTE_TO_PFN(Pte);

         MmMarkPageUnmapped(Pfn);

      }

      else

      {

         Pfn = 0;

      }

 

      if (FreePage && WasValid)

      {

         MmReleasePageMemoryConsumer(MC_NPPOOL, Pfn);

      }

 

      /*

       * Return some information to the caller

       */

      if (WasDirty != NULL)

      {

         *WasDirty = Pte & PA_DIRTY ? TRUE : FALSE;

      }

      if (Page != NULL)

      {

         *Page = Pfn;

      }

   }

   else

   {

      ULONG Pte;

      PULONG Pt;

 

      Pt = MmGetPageTableForProcess(Process, Address, FALSE);

 

      if (Pt == NULL)

      {

         if (WasDirty != NULL)

         {

            *WasDirty = FALSE;

         }

         if (Page != NULL)

         {

            *Page = 0;

         }

         return;

      }

 

      /*

       * Atomically set the entry to zero and get the old value.

       */

      Pte = InterlockedExchangeUL(Pt, 0);

 

      MiFlushTlb(Pt, Address);

 

      WasValid = (PAGE_MASK(Pte) != 0);

      if (WasValid)

      {

         Pfn = PTE_TO_PFN(Pte);

         MmMarkPageUnmapped(Pfn);

      }

      else

      {

         Pfn = 0;

      }

 

      if (FreePage && WasValid)

      {

         MmReleasePageMemoryConsumer(MC_NPPOOL, Pfn);

      }

 

      /*

       * Return some information to the caller

       */

      if (WasDirty != NULL)

      {

         *WasDirty = Pte & PA_DIRTY ? TRUE : FALSE;

      }

      if (Page != NULL)

      {

         *Page = Pfn;

      }

   }

   /*

    * Decrement the reference count for this page table.

    */

   if (Process != NULL && WasValid &&

       ((PMADDRESS_SPACE)&Process->VadRoot)->PageTableRefCountTable != NULL &&

       Address < MmSystemRangeStart)

   {

      PUSHORT Ptrc;

      ULONG Idx;

 

      Ptrc = ((PMADDRESS_SPACE)&Process->VadRoot)->PageTableRefCountTable;

      Idx = Ke386Pae ? PAE_ADDR_TO_PAGE_TABLE(Address) : ADDR_TO_PAGE_TABLE(Address);

 

      Ptrc[Idx]--;

      if (Ptrc[Idx] == 0)

      {

         MmFreePageTable(Process, Address);

      }

   }

}

    分析其else部分,可以看到其主要有以下幾個流程

1:

    Pt = MmGetPageTableForProcess(Process, Address, FALSE);

    獲得PTE

2

    Pte = InterlockedExchangeUL(Pt, 0);

    PTE置為0

3

    Pfn = PTE_TO_PFN(Pte);

MmMarkPageUnmapped(Pfn);

通過pte找到pfn,然后傳入MmMarkPageUnmapped函數,解除該物理頁面的映射

4

    MmReleasePageMemoryConsumer(MC_NPPOOL, Pfn);

將該頁面從非分頁頁面隊列中移除

 

 

MmPageOutVirtualMemory講解

 

主要作用是將虛存頁面與映射的物理頁面解除映射

請注意,映射和映像的區別是啥

映像,是磁盤的某一區域內容加載到了內存某一頁或者幾頁后,這兩者的關系叫做映像,意識是內容一頁

映射,意識是建立一種一一對應的關系

NTSTATUS

NTAPI

MmPageOutVirtualMemory(PMADDRESS_SPACE AddressSpace,

                       PMEMORY_AREA MemoryArea,

                       PVOID Address,

                       PMM_PAGEOP PageOp)

{

   PFN_TYPE Page;

   BOOLEAN WasDirty;

   SWAPENTRY SwapEntry;

   NTSTATUS Status;

 

   DPRINT("MmPageOutVirtualMemory(Address 0x%.8X) PID %d\n",

          Address, AddressSpace->Process->UniqueProcessId);

 

   /*

    * Check for paging out from a deleted virtual memory area.

    */

   if (MemoryArea->DeleteInProgress)//DeleteInProgress為已經刪除的標記

   {

      PageOp->Status = STATUS_UNSUCCESSFUL;

      KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE);

      MmReleasePageOp(PageOp);

      return(STATUS_UNSUCCESSFUL);

   }

 

   /*

    * Disable the virtual mapping.

    */

   MmDisableVirtualMapping(AddressSpace->Process, Address,

                           &WasDirty, &Page);//叫停CPU對該頁面的訪問

   並將虛存頁面的PA_ORESENT標志位清為0

 

   if (Page == 0)

   {

      KEBUGCHECK(0);

   }

 

   /*

* 非臟頁面

    */

   if (!WasDirty)

   {

      MmLockAddressSpace(AddressSpace);

      MmDeleteVirtualMapping(AddressSpace->Process, Address, FALSE, NULL, NULL);//刪除虛存頁面對外的映射

      MmDeleteAllRmaps(Page, NULL, NULL);

      if ((SwapEntry = MmGetSavedSwapEntryPage(Page)) != 0)//如果目標物理頁面對應一個頁面倒換文件的一個頁面

      {

         MmCreatePageFileMapping(AddressSpace->Process, Address, SwapEntry);//建立通往新的頁面倒換文件的映射

          MmSetSavedSwapEntryPage(Page, 0)//刪除舊的通往頁面倒換文件的映射

      }

      MmUnlockAddressSpace(AddressSpace);

      MmReleasePageMemoryConsumer(MC_USER, Page);//MC_USER隊列里釋放該頁面

      PageOp->Status = STATUS_SUCCESS;

      KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE);

      MmReleasePageOp(PageOp);

      return(STATUS_SUCCESS);

   }

 

   /*

      臟頁面

    *  

    */

   SwapEntry = MmGetSavedSwapEntryPage(Page);//獲取該物理頁面的倒換描述項

   if (SwapEntry == 0)

   {

      SwapEntry = MmAllocSwapPage();//如果沒有,就直接分配一個

      if (SwapEntry == 0)

      {

         MmShowOutOfSpaceMessagePagingFile();

         MmEnableVirtualMapping(AddressSpace->Process, Address);//恢復原來映射

         PageOp->Status = STATUS_UNSUCCESSFUL;

         KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE);

         MmReleasePageOp(PageOp);

         return(STATUS_PAGEFILE_QUOTA);

      }

   }

 

   /*

    * Write the page to the pagefile

    */

   Status = MmWriteToSwapPage(SwapEntry, Page);//將臟頁面內容寫回磁盤中的倒換頁面

   if (!NT_SUCCESS(Status))

   {

      DPRINT1("MM: Failed to write to swap page (Status was 0x%.8X)\n",

              Status);

      MmEnableVirtualMapping(AddressSpace->Process, Address);//恢復原來映射

      PageOp->Status = STATUS_UNSUCCESSFUL;

      KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE);

      MmReleasePageOp(PageOp);

      return(STATUS_UNSUCCESSFUL);

   }

 

   /*

    * Otherwise we have succeeded, free the page

    */

   DPRINT("MM: Swapped out virtual memory page 0x%.8X!\n", Page << PAGE_SHIFT);

   MmLockAddressSpace(AddressSpace);

   MmDeleteVirtualMapping(AddressSpace->Process, Address, FALSE, NULL, NULL);//撤銷通往目標頁面的映射

   MmCreatePageFileMapping(AddressSpace->Process, Address, SwapEntry);//建立虛存文件向倒換文件的映射

   MmUnlockAddressSpace(AddressSpace);

   MmDeleteAllRmaps(Page, NULL, NULL);

   MmSetSavedSwapEntryPage(Page, 0);//該物理頁面不再映射到倒換文件

   MmReleasePageMemoryConsumer(MC_USER, Page);//釋放該物理頁面

   PageOp->Status = STATUS_SUCCESS;

   KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE);

   MmReleasePageOp(PageOp);

   return(STATUS_SUCCESS);

}

 

   在解析這個函數時候,一定要注意這個頁面換出的流程,我覺得難點可能在於沒有分清映像和映射的區別,顛倒內存和磁盤之間的處理順序,要注意。


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com