3.1.3 虚存页面的映射
3.1.3 虚存页面的映射
文章目录
- 3.1.3 虚存页面的映射
- 3.1.3 虚存页面的映射
- MmCreateVirtualMapping()
- MmCreateVirtualMappingUnsafe()
- MiFlushTlb()
- MmDeleteVirtualMapping()
- MmPageOutVirtualMemory()
- MmDeleteVirtualMapping()
3.1.3 虚存页面的映射
有了虚存页面和物理内存页面,下一步就是建立二者之间的映射了。
所谓页面映射,是指从虚存页面到物理页面的映射。多个虚存页面可以映射到同一个物理页面,但是在同一时间内不可能会有多个物理页面对应于同一个空间的同一个虚存页面。
虚存页面是“虚”的,必须通过映射落实到某种形式的物理存储介质上。最理想的物理存储介质当然是物理内存,但是物理内存一方面价格较高,另一方面技术上也不能做得很大。所以,用外存特别是磁盘或磁盘上的文件作为后盾,与物理内存相结合构成一个二级的物理存储系统,是一种合适的解决方案。这样,只需要把当前正在受到访问以及很可能会受到访问的页面保存在物理内存中(这些页面的集合称为一个进程的“工作集(WorkingSer)”),而大量暂时不会受到访问的页面,例如不属于当前进程的页面,以及虽然属于当前进程但是很少受到访问的页面,则可以保存在后备的“倒换文件”中。即使判断有误,本以为不会受到访问的页面偏偏受到了访问,那也不要紧,可以临时将其倒换进来,由此而引起的效率下降一般不会很严重,因为这样的页面毕竟是极少数。
如前所述,每个进程都有个“页面映射表”,从原理上讲,这可以是个“页面映射表项”PTE的一维数组,PTE指明了一个虚存页面(在物理内存中)的起点。但是那样一来数组的大小就是1M,因为整个地址空间的大小是4GB,而每个页面的大小是4KB。然而,在4GB 的地址空间中,实际分配使用的页面却通常只是很小一部分,所以数组里面很大一部分都是空洞,如果真的使用大小为1M 的数组就造成了很大的浪费。所以,页面映射表所采用的是“稀疏数组”的结构,即把整个页面映射表分成两层。其顶层为“页面目录",这是一个“页面目录项”PDE的数组,实质上是个指针数组,如果指针非空就指向一个二级页面表,二级页面表是较小的PTE数组。页面目录和二级页面表的大小都是1024,正好都占一个页面。在实际的映射过程中,MMU以拟地址的最高10位为下标在页面目录中找到指向目标页面所在二级页面表的指针,如果这个指针非空就进而以次10位作为下标在二级页面表中找到页面表项PTE,这就找到了目标页面的起始地址,而虚拟地址的低12位则是在目标页面内部的位移。这样,在实际使用中,页面目录中的大量指针其实都是空指针,这就省却了许多二级页面表,从而节省了很多物理存储页面。当然,凡是已经分配使用的虚存页面
/* GLOBALS *****************************************************************/
#define PA_BIT_PRESENT (0)
#define PA_BIT_READWRITE (1)
#define PA_BIT_USER (2)
#define PA_BIT_WT (3)
#define PA_BIT_CD (4)
#define PA_BIT_ACCESSED (5)
#define PA_BIT_DIRTY (6)
#define PA_BIT_GLOBAL (8)
#define PA_PRESENT (1 << PA_BIT_PRESENT)
#define PA_READWRITE (1 << PA_BIT_READWRITE)
#define PA_USER (1 << PA_BIT_USER)
#define PA_DIRTY (1 << PA_BIT_DIRTY)
#define PA_WT (1 << PA_BIT_WT)
#define PA_CD (1 << PA_BIT_CD)
#define PA_ACCESSED (1 << PA_BIT_ACCESSED)
#define PA_GLOBAL (1 << PA_BIT_GLOBAL)
中间还留下4位,Intel的手册中称为Avail,这是让程序员自由处置使用的。
这样,例如“PFN_TO_PTE(Page)IPA_PRESENTIPA_READWRITE”就是一个表项的内容,表示把虚存页面映射到物理页面号Page,页面的映像在内存中,并且可读可写。
最低位PAPRESENT是关键,如果这一位是0,就表示所映射的页面不在内存中,CPU的内存管理单元 MMU 立即就会产生一次缺页异常,此时别的标志位以及页面号对于MMU 就都失去了意义。这样,操作系统就可以用该表项指示页面在倒换设备上或文件中的位置,具体的方法则因操作系统而异。就 Windows而言,表项的最高8位用于倒换文件号,低24位则用于倒换文件内部的页面号加1。
就映射的建立而言,最为一般的操作是 MmCreateVirtualMappingO,其作用为:给定一组物理页面,将某个给定进程从虚拟地址 Address 开始的一个区块映射到这组物理页面上。
MmCreateVirtualMapping()
NTSTATUS
NTAPI
MmCreateVirtualMapping(PEPROCESS Process,
PVOID Address,
ULONG flProtect,
PPFN_TYPE Pages,
ULONG PageCount)
{
ULONG i;
for (i = 0; i < PageCount; i++)
{
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 是需要建立映射的虚存区块的起始地址,属于给定的进程。如果指针Process为NULL则表示该虚存区块属于系统空间,所以并不属于任何特定的进程。参数 Pages指向一个页面号Pfn的数组,数组的大小即页面的数量为PageCount,这些物理页面不必是连续的。另一个参数 fProtect 则为对这些页面的保护模式。
显然,实际的操作是由 MmCreateVirtualMappingUnsafe()完成的,这里只是加上了对给定物理页面的有效性检查。如果给定的某个物理页面无效,则系统崩溃,CPU通过KEBUGCHECKO进入Debug 模式。
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)
{
if (Address < MmSystemRangeStart)//地址落在用户空间,但没有给定进程
{
DPRINT1("No process\n");
KEBUGCHECK(0);
}
if (PageCount > 0x10000 ||
(ULONG_PTR) Address / PAGE_SIZE + PageCount > 0x100000)
{//页面数量太大,或地址越界
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);//把保护模式转换成 PTE中的相应控制位
if (Attributes & 0x80000000)//0Protect中的PAGE_IS_EXECUTABLE位为0
{
NoExecute = TRUE;
}
Attributes &= 0xfff;//取其低12位
if (Address >= MmSystemRangeStart)
{//虚拟地址高于系统空间与用户空间的分界
Attributes &= ~PA_USER;//属于系统空间
if (Ke386GlobalPagesEnabled)
{
Attributes |= PA_GLOBAL;//页面表项不受冲刷
}
}
else
{
Attributes |= PA_USER;//属于用户空间
}
Addr = Address;
if (Ke386Pae)
{
...
}
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))
{
KEBUGCHECK(0);
}
if (PAGE_MASK((Pte)) != 0)
{
MmMarkPageUnmapped(PTE_TO_PFN((Pte)));
}
(void)InterlockedExchangeUL(Pt, PFN_TO_PTE(Pages[i]) | Attributes);
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);
}
进程的页面映射表代表着它的整个地址空间。页面映射表是个二层的结构,其高层为页面(映射)目录,低层是一组(最多1023个)具体的二级页面表。所以,凡是说到“页面(映射)表”的时候,读者应根据上下文判定说的是包括页面目录在内的整个页面表,还是具体的二级页面表。总之,给定一个页面表,根据虚拟地址的最高10位就可以算出这个地址所属的目录项,这个目录项指向该地址所在页面所属的二级页面(映射)表。计算的方法很简单,只要将虚拟地址整除二级页面表所代表的区间大小(1024*PAGE_SIZE)即可,因为一个二级页面表有1024个表项,每个表项代表着一个 4K字节的页面:
#define ADDR_TO_PDE_OFFSET(v) ((((ULONG)(v)) / (1024 * PAGE_SIZE)))
另一个宏操作 ADDR_TO_PAGE_TABLE的定义与此相同,所得的是二级页面表的序号,实际上就是相应目录项PDE在页面目录中的下标:
#define ADDR_TO_PAGE_TABLE(v) (((ULONG)(v)) / (4 * 1024 * 1024))
每个地址空间还有个“页面表引用计数表”,就是代码中的Ptrc。这是个数组,里面记录着各个页面映射表的引用计数。如果某个(二级)页面表的引用计数是0,就可以释放这个页面表,并把相应目录项中的“在位(PRESENT)”标志位清0,表示这个目录项是个空项。当然,只要需要,仍可以为其分配一个(空白的)二级页面表,并将该标志位设成1。
-个页面表项决定了一个虚存页面的映射,如果这个虚存页面被映射到一个物理内存页面,那么这个 32位表项的高 20位就是目标物理页面的Pfn,而一个物理页面的P 其实就是其起始物理地址的高20位,所以这里的PFNTO_PTEO)就是将Pfn左移12位,作为PTE的高20位:
#define PFN_TO_PTE(X) ((X) << PAGE_SHIFT)
PTE的最低8位如前所述,所以要把Pfn和Attributes 结合起来才得到PTE的值。
有了 PTE的值之后,就通过InterlockedExchangeUL()将其写入目标页面表项,并递增“页面表引用计数表”中相应页面表的引用计数。宏操作 InterlockedExchangeUL()定义为InterlockedExchange():
#define InterlockedExchangeUL(Target, Value) \
(ULONG)InterlockedExchange((PLONG)(Target), (LONG)(Value))
这个函数将 Value 设置到 Target,并返回Target 原来的值,但是整个过程是个不可中断从而不可分割的“原子”操作。
不过光是改变了页面表中的表项还不解决问题,因为那只是存在于内存之中,而没有进入 MMU的页面映射高速缓存TLB,所以还要通过 MiFlushTbO“冲刷”高速缓存中的相应表项。MMU 在实现地址映射的过程中所直接依据的并不是内存中的页面表项,而是这个表项在MMU 的高速缓存TLB 内的映像。只有在目标表项不在TLB 中时才会从内存中装入该表项,这与数据和指令的高速缓存是一样的。然而,内存中页面映射表项的改变不会自动引起其在高速缓存 TLB 内的映像的改变,所以需要通过 MiFlushTIbO“冲刷”TLB 中该表项的映像,目的是从 TLB 中删除这个表项的映像。
这样,以后当 MMU 需要用到这个表项时就会自动从内存中的页面映射表装载新的表项。
MiFlushTlb()
VOID
MiFlushTlb(PULONG Pt, PVOID Address)
{
#ifdef CONFIG_SMP
...
#else
if ((Pt && MmUnmapPageTable(Pt)) || Address >= MmSystemRangeStart)
{
__invlpg(Address);
}
#endif
}
对于单处理器结构,只要指针P落在页面映射表的范围之内,即指向某个页面映射表项,或者所映射的地址 Address 落在内核空间,就由_invlpgO)实际上是汇编指令invlpg 冲刷目标表项。这条指令名曰invlpg,似乎意为“使页面无效”,实际上是使得一个页面的映射(在TLB中)无效,所以是“Invalidate TLB Entry”,即冲刷一个页面映射表项。这里的 MmUnmapPageTable()用来判定指针 P是否落在页面映射表中,并处理一些特殊的情况,一般都会返回 TRUE,这里就不予深究了。
理解了 MmCreateVirtualMapping()以后,我们再来看其逆操作 MmDeleteVirtualMapping(),即删除某个虚拟地址所在页面的映射。
MmDeleteVirtualMapping()
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)
{
...
}
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);
}
}
}
笔者在代码中加了注释,读者对这段代码应该不会有困难。同样,这里也要调用MiFlushTIbO,因为某个页面映射表项已经发生了变化。
一个虚存页面被映射到某个物理内存页面以后,如果较长时间没有受到访问,内核就可能决定将其内容倒出到倒换文件中,腾出其所占用的物理页面,以便周转使用。内核函数MmPageOutVirtualMemoryO)就用来将一个虚存页面的内容倒出到倒换文件中。这个函数比较长,我们分段阅读。
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)
{
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);
if (Page == 0)
{
KEBUGCHECK(0);
}
/*
* Paging out non-dirty data is easy.
*/
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);
PageOp->Status = STATUS_SUCCESS;
KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE);
MmReleasePageOp(PageOp);
return(STATUS_SUCCESS);
}
/*
* If necessary, allocate an entry in the paging file for this page
*/
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);
}
在倒出一个页面之前,首先要叫停CPU对该页面的访问,方法是将相应页面映射表项中的PAPRESENT标志位清0。这样,如果CPU访问这个页面,就会发生页面异常。将PAPRESENT标志位清0的操作是由MmDisableVirtualMappingO)完成的:
MmDeleteVirtualMapping()
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)
{
....
}
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);
}
}
}
在前面 MmCreateVirtualMapping()和 MmDeleteVirtualMappingO)的基础上,读者对于这一段代码应该不会有困难。
凡是有物理页面映射的虚存页面,有可能是“干净”的,也可能是“肮脏”的。所谓“干净”的页面,是指自从创建映射或者倒入内存(物理页面)之后从未经历过写操作的页面,前者是空白页面,后者是与页面倒换文件中的页面映像完全一致的页面。空白页面无所谓倒换,只要直接将其映射删除即可,因为以后需要时可以重新创建。对于后者,由于物理页面的内容与页面倒换文件中的页面映像完全一致,故可以直接释放物理页面,但是要将其页面映射表项修改成指向页面倒换文件中相应的页面映像。至于“肮脏”的页面,则需要将当前的物理页面内容写回页面倒换文件中,如果倒换文件中尚无该页面的映像就得先分配一个,然后写入该页面映像,再修改页面映射表项并释放物理页面。
回到前面 MmPageOutVirtualMemoryO的代码,先看对于干净页面的处理。代码中先通过MmDeleteVirtualMapping()删除该页面对于物理页面的映射,然后通过 MmGetSavedSwapEntryPage()获取本页面映像在倒换文件中的页面号(更确切地说,是倒换文件号和具体倒换文件中页面号的组合,Windows采用多个倒换文件),这个倒换页面号保存在物理页面的PHYSICAL_PAGE 数据结构中。只要这个页面号非0,就说明本页面已经在倒换文件中有了映像(因而不是空白页面),所以由MmCreatePageFileMapping0)在页面映射表中创建起指向倒换文件的页面表项。可想而知,此种表项中的PAPRESENT标志位为0。在页面映射表项中,PAPRESENT标志位是最低位。如果这一位为1,其余各位的意义和作用就都有定义,CPU中的MMU根据这些位段的值实行映射:而若这一位为0,则其余各位的意义和作用(就 MMU而言)没有定义,具体的软件可以自行定义使用,Windows内核用这31位表示倒换文件号和(文件内部)页面号的组合。同时,由于原来的物理页面已经与倒换文件中的这个页面映像脱离了关系,所以就通过 MmSetSavedSwapEntryPage()将其数据结构中的字段 SavedSwapEntry 清0。然后,就可以通过 MmReleasePageMemoryConsumer()释放这个物理页面了。
再看对于“肮脏”页面的处理。
下一篇对于“肮脏”页面的处理。