既然懂了虚存和物理存储的管理,下面谈谈虚存向物理存储的映射
首先看第一个函数
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数组是该进程从属所有页面号的数组,而且这里的页面号指的是物理页面号,比如如果PageCount为3,则该数组可以是{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;//众所周知,低12为4个空闲位和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_SIZE(4K),所以如果要找到二级页表的下标,也就是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转为PTE,PFN是物理页面下标,如何转呢?
由于内存有4G,并且一个物理页面是4k大小
4G/4K=1M,所以PTE最多有1M个(当然,这里采用了二级页表,不过不影响,因为无论采取如何方式,前20位肯定是代表了PTE索引)
正常流程是先找PTE的前20位,确定哪个PTE表项,映射到物理页面后然后加上后面12位的页内偏移,就构成了确切的32位地址。
而反过来如何通过物理页号知道指向这个页面的PTE内容(这里要搞懂,我们要逆向推出的是什么,明显是PTE内容,而不是PTE的地址,也不是PTE所在页表的首项),明显,PTE前20位的内容和这个物理页地址的前20位是一样的(如果不一样,或者没关系,那PTE该如何指向这个物理页呢?对吧)
所以,这个物理页的地址的前20位,也就是PFN(OK,我们前面知道啦,针对内存有一个数组,每个数组都是一个指针,指向一个结构体,这个结构体描述了一个页面及其属性,这数组有几个元素呢,前面已经证明,有1M个,而PFN则是其下标,所以既然“寻址空间”是1M,那用20位就可以代表啦,也就是说,PFN用20位就可以描述)。
既然证明了PFN是20位,而PTE是32位,20如何转32呢?我们知道PTE的32位,高20位是来寻找物理页的,低12位是寻找页内偏移的,所以我们只需要把20位的PFN左移20位,也就是说
#define PFN_TO_PTE(X) ((X)<<PAGE_SHIFT)
这里PAGE_SHIFT是12
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);
}
在解析这个函数时候,一定要注意这个页面换出的流程,我觉得难点可能在于没有分清映像和映射的区别,颠倒内存和磁盘之间的处理顺序,要注意。