内核中有四个物理页面队列
static LIST_ENTRY FreeZeroedPageListHeads;
static LIST_ENTRY FreeUnzeroedPageListHeads;
static LIST_ENTRY BiosPageListHeads;
static LIST_ENTRY UsedPageListHeads[MC_MAXIMUM];
MC_MAXIMUM定义为4
根据不同物理页面的用途把他放入不同的队列,比如
#define MC_CACHE 0
#define MC_USER 1
#define MC_PPOOL 2
#define MC_NPPOOL 3
MM_STATS结构体定义如下
typedef struct
{
ULONG NrTotalPages;
ULONG NrSystemPages;
ULONG NrReservedPages;
ULONG NrUserPages;
ULONG NrFreePages;
ULONG NrDirtyPages;
ULONG NrLockedPages;
ULONG PagingRequestsInLastMinute;
ULONG PagingRequestsInLastFiveMinutes;
ULONG PagingRequestsInLastFifteenMinutes;
} MM_STATS;
看balance.c第一个函数
MmInitializeBalancer(ULONG NrAvailablePages, ULONG NrSystemPages)
{
memset(MiMemoryConsumers, 0, sizeof(MiMemoryConsumers));
InitializeListHead(&AllocationListHead);
KeInitializeSpinLock(&AllocationListLock);
MiNrTotalPages = NrAvailablePages;
/* Set up targets. */
MiMinimumAvailablePages = 64;
MiMemoryConsumers[MC_CACHE].PagesTarget = NrAvailablePages / 2;
MiMemoryConsumers[MC_USER].PagesTarget =
NrAvailablePages - MiMinimumAvailablePages;
MiMemoryConsumers[MC_PPOOL].PagesTarget = NrAvailablePages / 2;
MiMemoryConsumers[MC_NPPOOL].PagesTarget = 0xFFFFFFFF;
MiMemoryConsumers[MC_NPPOOL].PagesUsed = NrSystemPages;
}
其中MiMemoryConsumers定义如下
MM_MEMORY_CONSUMER MiMemoryConsumers[MC_MAXIMUM];
其中MM_MEMORY_CONSUMER定义如下
typedef struct _MM_MEMORY_CONSUMER
{
ULONG PagesUsed;//已经使用了多少
ULONG PagesTarget;//配额,也就是预计划最多分配多少
NTSTATUS (*Trim)(ULONG Target, ULONG Priority, PULONG NrFreed);
} MM_MEMORY_CONSUMER, *PMM_MEMORY_CONSUMER;
意思就是MiMemoryConsumers四个配额成员进行赋值
其中有一段代码
MiMemoryConsumers[MC_USER].PagesTarget =NrAvailablePages - MiMinimumAvailablePages;
可以知道MiMemoryConsumers[MC_USER].PagesTarget =NrAvailablePages - MiMinimumAvailablePages;就是进行配额赋值
其中前面已经有MiMinimumAvailablePages = 64;,而NrAvailablePages为MmInitializeBalancer传来之参数,故可以 配额(预计划最多分配)=可用页面数-最少可用页面数(意思可能是本来可用页面为100,无论你怎么分配,一定给系统留10个作为保留,也就是配额最多为90)
接下来分析该函数
MmRequestPageMemoryConsumer(ULONG Consumer, BOOLEAN CanWait,
PPFN_TYPE AllocatedPage)
该函数是一个分配页面的函数
第一段代码是
OldUsed = InterlockedIncrementUL(&MiMemoryConsumers[Consumer].PagesUsed);
if (OldUsed >= (MiMemoryConsumers[Consumer].PagesTarget - 1) &&
!MiIsBalancerThread())//加入配额是100,如果我们已经用了99个或者更多,就要重新申请新的自由页面
{
if (!CanWait)//不想等啦,得退出
{
(void)InterlockedDecrementUL(&MiMemoryConsumers[Consumer].PagesUsed);
return(STATUS_NO_MEMORY);
}
MiTrimMemoryConsumer(Consumer);
}
第一段函数用到了MiTrimMemoryConsumer
我们转到这里看看
VOID
NTAPI
MiTrimMemoryConsumer(ULONG Consumer)
{
LONG Target;
ULONG NrFreedPages;
Target = MiMemoryConsumers[Consumer].PagesUsed -
MiMemoryConsumers[Consumer].PagesTarget;
if (Target < 1)
{
Target = 1;
}
if (MiMemoryConsumers[Consumer].Trim != NULL)
{
MiMemoryConsumers[Consumer].Trim(Target, 0, &NrFreedPages);
}
}
可以看到首先
Target = MiMemoryConsumers[Consumer].PagesUsed -
MiMemoryConsumers[Consumer].PagesTarget;
我们这里假设MiMemoryConsumers[Consumer].PagesUsed 是99, MiMemoryConsumers[Consumer].PagesTarget是100,则 Target为-1
经过
if (Target < 1)
{
Target = 1;
}
后变为1
然后调用
if (MiMemoryConsumers[Consumer].Trim != NULL)
{
MiMemoryConsumers[Consumer].Trim(Target, 0, &NrFreedPages);
}
此处的trim为一个函数指针(这里不清楚怎么进行初始化的,汗),也就是说白了MiMemoryConsumers[Consumer].Trim(1, 0, &NrFreedPages);,意思应该是分配一个新的页面给这个结构体。
应当注意,第一段代码针对NPPOOL也就是非置换页面,所以要特殊对待,处理完后退出。
第二段代码
if (Consumer == MC_NPPOOL || MiIsBalancerThread())
{
Page = MmAllocPage(Consumer, 0);
if (Page == 0)
{
KEBUGCHECK(NO_PAGES_AVAILABLE);
}
*AllocatedPage = Page;//page为返回的
if (MmStats.NrFreePages <= MiMinimumAvailablePages &&
MiBalancerThreadHandle != NULL)
{
KeSetEvent(&MiBalancerEvent, IO_NO_INCREMENT, FALSE);
}
return(STATUS_SUCCESS);
}
其中 Page = MmAllocPage(Consumer, 0);调用MmAllocPage函数就是返回的该新页面PHYSICAL_PAGE结构体数组的下标,如果返回为0,就代表分配失败。
MmStats.NrFreePages <= MiMinimumAvailablePages
分析MmStats,首先它在pool.c中有一句extern MM_STATS MmStats;而MM_STATS定义如下
typedef struct
{
ULONG NrTotalPages;
ULONG NrSystemPages;
ULONG NrReservedPages;
ULONG NrUserPages;
ULONG NrFreePages;
ULONG NrDirtyPages;
ULONG NrLockedPages;
ULONG PagingRequestsInLastMinute;
ULONG PagingRequestsInLastFiveMinutes;
ULONG PagingRequestsInLastFifteenMinutes;
} MM_STATS;
而前边的
static ULONG MiMinimumAvailablePages;
MiMinimumAvailablePages = 64;
已经给出了其定义和初始值。
所以MmStats.NrFreePages <= MiMinimumAvailablePages这句话意思可能是如果当前系统的自由可分配页面小于初始的预留页面(比如要求系统最低保留十个自由页面,但是此时的内存自由页面只有不到十个了,就要退出)
第三段代码
if (MmStats.NrFreePages <= MiMinimumAvailablePages)
{
MM_ALLOCATION_REQUEST Request;
if (!CanWait)
{
(void)InterlockedDecrementUL(&MiMemoryConsumers[Consumer].PagesUsed);
return(STATUS_NO_MEMORY);
}
/* Insert an allocation request. */
Request.Page = 0;
KeInitializeEvent(&Request.Event, NotificationEvent, FALSE);//初始一个同步事件,初始为未受信,事件变量为 Request的一个变量
(void)InterlockedIncrementUL(&MiPagesRequired);//原子性的增加全部变量MiPagesRequired(初始为0)
KeAcquireSpinLock(&AllocationListLock, &oldIrql);//获得自旋锁
if (MiBalancerThreadHandle != NULL)//MiBalancerThreadHandle初始为空
{
KeSetEvent(&MiBalancerEvent, IO_NO_INCREMENT, FALSE);//设置事件为未受信状态
}
InsertTailList(&AllocationListHead, &Request.ListEntry);//将其挂入AllocationListHead队列
KeReleaseSpinLock(&AllocationListLock, oldIrql);//释放自旋锁
KeWaitForSingleObject(&Request.Event,
0,
KernelMode,
FALSE,
NULL);//等待事件变为受信态
Page = Request.Page;
if (Page == 0)
{
KEBUGCHECK(NO_PAGES_AVAILABLE);
}//如果返回0,则表示没有可用的页面
MmTransferOwnershipPage(Page, Consumer);讲解点A
*AllocatedPage = Page;
(void)InterlockedDecrementUL(&MiPagesRequired);
return(STATUS_SUCCESS);
}
第三段是针对非“非置换页面”,如果也是碰到了当前系统的自由可分配页面小于初始的预留页面,就定义一个MM_ALLOCATION_REQUEST 结构变量。定义如下
typedef struct _MM_ALLOCATION_REQUEST
{
PFN_TYPE Page;
LIST_ENTRY ListEntry;
KEVENT Event;
}
MM_ALLOCATION_REQUEST, *PMM_ALLOCATION_REQUEST;
if (!CanWait)
意思是如果不能等待,则原子性的降低MiMemoryConsumers[Consumer].PagesUsed变量的值
突然感觉如果这样解释,太繁琐,而且行文不流畅,故今后采用源程序后面注释+重点难点后面详解的方式。
讲解点A
函数实现如下
MmTransferOwnershipPage(PFN_TYPE Pfn, ULONG NewConsumer)
{
KIRQL oldIrql;
KeAcquireSpinLock(&PageListLock, &oldIrql);
if (MmPageArray[Pfn].MapCount != 0)
{
DPRINT1("Transfering mapped page.\n");
KEBUGCHECK(0);
}
if (MmPageArray[Pfn].Flags.Type != MM_PHYSICAL_PAGE_USED)
{
DPRINT1("Type: %d\n", MmPageArray[Pfn].Flags.Type);
KEBUGCHECK(0);
}
if (MmPageArray[Pfn].ReferenceCount != 1)
{
DPRINT1("ReferenceCount: %d\n", MmPageArray[Pfn].ReferenceCount);
KEBUGCHECK(0);
}
RemoveEntryList(&MmPageArray[Pfn].ListEntry);
InsertTailList(&UsedPageListHeads[NewConsumer],
&MmPageArray[Pfn].ListEntry);
MmPageArray[Pfn].Flags.Consumer = NewConsumer;
KeReleaseSpinLock(&PageListLock, oldIrql);
MiZeroPage(Pfn);
}
可以看出Pfn就是页面下标,NewConsumer也就说用于UsedListPageListHead数组的哪一个元素,前面几个if都是判断。
RemoveEntryList(&MmPageArray[Pfn].ListEntry);
InsertTailList(&UsedPageListHeads[NewConsumer],
&MmPageArray[Pfn].ListEntry);
MmPageArray[Pfn].Flags.Consumer = NewConsumer;
第一行意思是将该页面从页面链表中移除,第二行意思是在UsedPageListHeads[NewConsumer]后面插入该页面,第三行意思是将该页面的consumer改为新的consumer。
至此,MmRequestPageMemoryConsumer函数分析完毕。
既然理解了申请页面,那我们就看看释放页面
MmReleasePageMemoryConsumer(ULONG Consumer, PFN_TYPE Page)
{
PMM_ALLOCATION_REQUEST Request;
PLIST_ENTRY Entry;
KIRQL oldIrql;
if (Page == 0)
{
DPRINT1("Tried to release page zero.\n");
KEBUGCHECK(0);
}
KeAcquireSpinLock(&AllocationListLock, &oldIrql);//进入时候,会提升优先级,旧的级别会保存在oldlrql中
if (MmGetReferenceCountPage(Page) == 1)讲解点A 如果返回引用次数为1
{
(void)InterlockedDecrementUL(&MiMemoryConsumers[Consumer].PagesUsed);//该用途的页面数目减去一
if(IsListEmpty(&AllocationListHead)||MmStats.NrFreePages<MiMinimumAvailablePages)//讲解点B
{
KeReleaseSpinLock(&AllocationListLock, oldIrql);
MmDereferencePage(Page);
}
else
{//讲解点C
Entry = RemoveHeadList(&AllocationListHead);
Request = CONTAINING_RECORD(Entry, MM_ALLOCATION_REQUEST, ListEntry);
KeReleaseSpinLock(&AllocationListLock, oldIrql);
Request->Page = Page;
KeSetEvent(&Request->Event, IO_NO_INCREMENT, FALSE);
}
}
else
{ //如果被引用数大于1
KeReleaseSpinLock(&AllocationListLock, oldIrql);
MmDereferencePage(Page);//递减引用次数
}
return(STATUS_SUCCESS);
}
讲解点A
MmGetReferenceCountPage(Page)函数实现如下
ULONG
NTAPI
MmGetReferenceCountPage(PFN_TYPE Pfn)
{
KIRQL oldIrql;
ULONG RCount;
DPRINT("MmGetReferenceCountPage(PhysicalAddress %x)\n", Pfn << PAGE_SHIFT);
if (Pfn == 0 || Pfn >= MmPageArraySize)
{
KEBUGCHECK(0);
}
KeAcquireSpinLock(&PageListLock, &oldIrql);
if (MmPageArray[Pfn].Flags.Type != MM_PHYSICAL_PAGE_USED)
{
DPRINT1("Getting reference count for free page\n");
KEBUGCHECK(0);
}
RCount = MmPageArray[Pfn].ReferenceCount;//保留该页面引用次数
KeReleaseSpinLock(&PageListLock, oldIrql);
return(RCount);//返回其引用次数
}
讲解点B
从前面的申请页面可以知道,该页面挂入到了AllocationListHead,所以这里要判断下AllocationListHead是不是空,如果是空,代表该页面仅仅是个普通的可用页面,所以只需要MmDereferencePage(Page);减去其引用值。
讲解点C
首先把AllocationListHead的元素提出来,然后通过CONTAINING_RECORD宏找到其所在的MM_ALLOCATION_REQUEST结构体首地址
Request->Page = Page;
KeSetEvent(&Request->Event, IO_NO_INCREMENT, FALSE);
把页面付给request结构体的这个域(还记得申请页面函数中Page = Request.Page;
吗,明显,Page = Request.Page;前面是waitforsingleobject,这里setevent变成有信号,于是cpu就跳到页面申请函数中,而这里既然request得到了page值,那个函数里就把得到的值再赋给page)。