1、内核对于物理页面的管理
物理内存的管理和使用以页面为单位,所以物理内存的管理实际上是对物理页面的管理。
在内核中,物理页面是以数据结构PHYSICAL_PAGE代表的,其定义如下:typedef struct _PHYSICAL_PAGE { union { struct { ULONG Type: 2; ULONG Consumer: 3; ULONG Zero: 1; } Flags; ULONG AllFlags; }; LIST_ENTRY ListEntry; ULONG ReferenceCount; SWAPENTRY SavedSwapEntry; ULONG LockCount; ULONG MapCount; struct _MM_RMAP_ENTRY* RmapListHead; } PHYSICAL_PAGE, *PPHYSICAL_PAGE;
每个PHYSICAL_PAGE代表一个物理页面,在系统初始化时,会构建起一个元素为PHYSICAL_PAGE的数组MmPageArray,这是一个全局变量,系统中有多少物理页面,这个数组就有多大。
这样,以一个物理页面的页面号(pfn)为下标,就可以找到这个物理页面对应的PHYSICAL_PAGE(MmPageArray[pfn])。
物理地址与其所在页面的页面号有着固定的关系,因为一个页面就是4KB,所以有如下定义:
#define PaToPfn(p) ((p)>>12)
PHYSICAL_PAGE中的Type字段标示物理页面的性质:
#define MM_PHYSICAL_PAGE_FREE (0x1) #define MM_PHYSICAL_PAGE_USED (0x2) #define MM_PHYSICAL_PAGE_BIOS (0x3)
显然,物理页面有三种,即空闲的页面、使用中的页面、用于BIOS的页面。
ListEntry字段用于将这个PHYSICAL_PAGE链入某个队列,内核中定义了如下物理页面队列:
static LIST_ENTRY UsedPageListHeads[MC_MAXIMUM]; static LIST_ENTRY FreeZeroedPageListHead; static LIST_ENTRY FreeUnzeroedPageListHead; static LIST_ENTRY BiosPageListHead;
①物理地址0x100000以下即最低1MB范围中的页面用于BIOS,所以都在BiosPageListHead队列中。
空闲队列有两个:
②FreeZeroedPageListHead已清零页面。
③FreeUnzeroedPageListHead未清零页面。如刚被释放的物理页面时未经清0处理的。
内核线程MmZeroPageThreadMain受调度运行后,会从FreeUnzeroedPageListHead队列中摘取物理页面,清零后将其挂入FreeZeroedPageListHead队列。
④使用中的页面队列UsedPageListHeads是个队列数组,MC_MAXIMUM定义为4。其实是按照物理页面的用途分为4个队列。
其四个用途定义如下:
#define MC_CACHE (0) //用于磁盘内容缓存 #define MC_USER (1) //用于用户空间的映射 #define MC_PPOOL (2) //标示可以倒换到外存的页面池 #define MC_NPPOOL (3) //标示不可以倒换到外存的页面池
所以,最为典型的物理页面周转过程为:
①从FreeZeroedPageListHead队列开始
②经分配用于某个用户空间虚存页面的映射,所以该物理页面进入UsedPageListHeads[MC_USER]队列
③使用后释放,该物理页面进入FreeUnzeroedPageListHead队列
④内核线程MmZeroPageThreadMain受调度运行后,该页面又回到FreeZeroedPageListHead队列
2、常用函数
物理页面的分配使用MmAllocPage()函数,其函数实现如下:
PFN_TYPE NTAPI MmAllocPage(ULONG Consumer, SWAPENTRY SavedSwapEntry) { PFN_TYPE PfnOffset; PLIST_ENTRY ListEntry; PPHYSICAL_PAGE PageDescriptor; KIRQL oldIrql; BOOLEAN NeedClear = FALSE; KeAcquireSpinLock(&PageListLock, &oldIrql);//旋转锁,以下操作为原子操作 if (IsListEmpty(&FreeZeroedPageListHead)) { //如FreeZeroedPageListHead中没有页面,则在FreeUnzeroedPageListHead中分配 if (IsListEmpty(&FreeUnzeroedPageListHead)) { //如果两个队列都没有页面,则返回空,无法分配 KeReleaseSpinLock(&PageListLock, oldIrql); return 0; } ListEntry = RemoveTailList(&FreeUnzeroedPageListHead); UnzeroedPageCount--; PageDescriptor = CONTAINING_RECORD(ListEntry, PHYSICAL_PAGE, ListEntry); NeedClear = TRUE; //需要重新初始化为0 } else { //优先选择FreeZeroedPageListHead队列中的页面 ListEntry = RemoveTailList(&FreeZeroedPageListHead); PageDescriptor = CONTAINING_RECORD(ListEntry, PHYSICAL_PAGE, ListEntry); } //错误处理 if (PageDescriptor->Flags.Type != MM_PHYSICAL_PAGE_FREE) { KEBUGCHECK(0); } if (PageDescriptor->MapCount != 0) { KEBUGCHECK(0); } if (PageDescriptor->ReferenceCount != 0) { KEBUGCHECK(0); } //分配成功,初始化PPHYSICAL_PAGE个参数 PageDescriptor->Flags.Type = MM_PHYSICAL_PAGE_USED; PageDescriptor->Flags.Consumer = Consumer; PageDescriptor->ReferenceCount = 1; PageDescriptor->LockCount = 0; PageDescriptor->MapCount = 0; PageDescriptor->SavedSwapEntry = SavedSwapEntry; InsertTailList(&UsedPageListHeads[Consumer], ListEntry); MmStats.NrSystemPages++; MmStats.NrFreePages--; KeReleaseSpinLock(&PageListLock, oldIrql);//释放旋转锁,原子操作结束 PfnOffset = PageDescriptor - MmPageArray; if (NeedClear) { //从FreeUnzeroedPageListHead队列中分配的物理页面,需初始化为0 MiZeroPage(PfnOffset); } if (PageDescriptor->MapCount != 0) { KEBUGCHECK(0); } return PfnOffset; }
参数Consumer表明页面用途,决定了分配成功的物理页面挂入哪一个队列。SavedSwapEntry标示页面交换文件的位置,取0标示没有倒换文件。