mirror of
https://github.com/reactos/reactos.git
synced 2025-01-01 03:54:02 +00:00
41475dfcd7
Do not ditch the pages as soon as the section are unmapped Improve MmBalancer "algorithm" (or whatever you call that) Various needed fixes to get this going.
644 lines
15 KiB
C
644 lines
15 KiB
C
/*
|
|
* COPYRIGHT: See COPYING in the top level directory
|
|
* PROJECT: ReactOS kernel
|
|
* FILE: ntoskrnl/mm/freelist.c
|
|
* PURPOSE: Handle the list of free physical pages
|
|
*
|
|
* PROGRAMMERS: David Welch (welch@cwcom.net)
|
|
* Robert Bergkvist
|
|
*/
|
|
|
|
/* INCLUDES ****************************************************************/
|
|
|
|
#include <ntoskrnl.h>
|
|
#define NDEBUG
|
|
#include <debug.h>
|
|
|
|
#define MODULE_INVOLVED_IN_ARM3
|
|
#include "ARM3/miarm.h"
|
|
|
|
#define ASSERT_IS_ROS_PFN(x) ASSERT(MI_IS_ROS_PFN(x) == TRUE);
|
|
|
|
/* GLOBALS ****************************************************************/
|
|
|
|
PMMPFN MmPfnDatabase;
|
|
|
|
PFN_NUMBER MmAvailablePages;
|
|
PFN_NUMBER MmResidentAvailablePages;
|
|
PFN_NUMBER MmResidentAvailableAtInit;
|
|
|
|
SIZE_T MmTotalCommittedPages;
|
|
SIZE_T MmSharedCommit;
|
|
SIZE_T MmDriverCommit;
|
|
SIZE_T MmProcessCommit;
|
|
SIZE_T MmPagedPoolCommit;
|
|
SIZE_T MmPeakCommitment;
|
|
SIZE_T MmtotalCommitLimitMaximum;
|
|
|
|
PMMPFN FirstUserLRUPfn;
|
|
PMMPFN LastUserLRUPfn;
|
|
|
|
/* FUNCTIONS *************************************************************/
|
|
|
|
PFN_NUMBER
|
|
NTAPI
|
|
MmGetLRUFirstUserPage(VOID)
|
|
{
|
|
PFN_NUMBER Page;
|
|
KIRQL OldIrql;
|
|
|
|
/* Find the first user page */
|
|
OldIrql = MiAcquirePfnLock();
|
|
|
|
if (FirstUserLRUPfn == NULL)
|
|
{
|
|
MiReleasePfnLock(OldIrql);
|
|
return 0;
|
|
}
|
|
|
|
Page = MiGetPfnEntryIndex(FirstUserLRUPfn);
|
|
MmReferencePage(Page);
|
|
|
|
MiReleasePfnLock(OldIrql);
|
|
|
|
return Page;
|
|
}
|
|
|
|
static
|
|
VOID
|
|
MmInsertLRULastUserPage(PFN_NUMBER Page)
|
|
{
|
|
MI_ASSERT_PFN_LOCK_HELD();
|
|
|
|
PMMPFN Pfn = MiGetPfnEntry(Page);
|
|
|
|
if (FirstUserLRUPfn == NULL)
|
|
FirstUserLRUPfn = Pfn;
|
|
|
|
Pfn->PreviousLRU = LastUserLRUPfn;
|
|
|
|
if (LastUserLRUPfn != NULL)
|
|
LastUserLRUPfn->NextLRU = Pfn;
|
|
LastUserLRUPfn = Pfn;
|
|
}
|
|
|
|
static
|
|
VOID
|
|
MmRemoveLRUUserPage(PFN_NUMBER Page)
|
|
{
|
|
MI_ASSERT_PFN_LOCK_HELD();
|
|
|
|
/* Unset the page as a user page */
|
|
ASSERT(Page != 0);
|
|
|
|
PMMPFN Pfn = MiGetPfnEntry(Page);
|
|
|
|
ASSERT_IS_ROS_PFN(Pfn);
|
|
|
|
if (Pfn->PreviousLRU)
|
|
{
|
|
ASSERT(Pfn->PreviousLRU->NextLRU == Pfn);
|
|
Pfn->PreviousLRU->NextLRU = Pfn->NextLRU;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(FirstUserLRUPfn == Pfn);
|
|
FirstUserLRUPfn = Pfn->NextLRU;
|
|
}
|
|
|
|
if (Pfn->NextLRU)
|
|
{
|
|
ASSERT(Pfn->NextLRU->PreviousLRU == Pfn);
|
|
Pfn->NextLRU->PreviousLRU = Pfn->PreviousLRU;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(Pfn == LastUserLRUPfn);
|
|
LastUserLRUPfn = Pfn->PreviousLRU;
|
|
}
|
|
|
|
Pfn->PreviousLRU = Pfn->NextLRU = NULL;
|
|
}
|
|
|
|
PFN_NUMBER
|
|
NTAPI
|
|
MmGetLRUNextUserPage(PFN_NUMBER PreviousPage, BOOLEAN MoveToLast)
|
|
{
|
|
PFN_NUMBER Page = 0;
|
|
KIRQL OldIrql;
|
|
|
|
/* Find the next user page */
|
|
OldIrql = MiAcquirePfnLock();
|
|
|
|
PMMPFN PreviousPfn = MiGetPfnEntry(PreviousPage);
|
|
PMMPFN NextPfn = PreviousPfn->NextLRU;
|
|
|
|
/*
|
|
* Move this one at the end of the list.
|
|
* It may be freed by MmDereferencePage below.
|
|
* If it's not, then it means it is still hanging in some process address space.
|
|
* This avoids paging-out e.g. ntdll early just because it's mapped first time.
|
|
*/
|
|
if ((MoveToLast) && (MmGetReferenceCountPage(PreviousPage) > 1))
|
|
{
|
|
MmRemoveLRUUserPage(PreviousPage);
|
|
MmInsertLRULastUserPage(PreviousPage);
|
|
}
|
|
|
|
if (NextPfn)
|
|
{
|
|
Page = MiGetPfnEntryIndex(NextPfn);
|
|
MmReferencePage(Page);
|
|
}
|
|
|
|
MmDereferencePage(PreviousPage);
|
|
|
|
MiReleasePfnLock(OldIrql);
|
|
|
|
return Page;
|
|
}
|
|
|
|
BOOLEAN
|
|
NTAPI
|
|
MiIsPfnFree(IN PMMPFN Pfn1)
|
|
{
|
|
/* Must be a free or zero page, with no references, linked */
|
|
return ((Pfn1->u3.e1.PageLocation <= StandbyPageList) &&
|
|
(Pfn1->u1.Flink) &&
|
|
(Pfn1->u2.Blink) &&
|
|
!(Pfn1->u3.e2.ReferenceCount));
|
|
}
|
|
|
|
BOOLEAN
|
|
NTAPI
|
|
MiIsPfnInUse(IN PMMPFN Pfn1)
|
|
{
|
|
/* Standby list or higher, unlinked, and with references */
|
|
return !MiIsPfnFree(Pfn1);
|
|
}
|
|
|
|
PMDL
|
|
NTAPI
|
|
MiAllocatePagesForMdl(IN PHYSICAL_ADDRESS LowAddress,
|
|
IN PHYSICAL_ADDRESS HighAddress,
|
|
IN PHYSICAL_ADDRESS SkipBytes,
|
|
IN SIZE_T TotalBytes,
|
|
IN MI_PFN_CACHE_ATTRIBUTE CacheAttribute,
|
|
IN ULONG MdlFlags)
|
|
{
|
|
PMDL Mdl;
|
|
PFN_NUMBER PageCount, LowPage, HighPage, SkipPages, PagesFound = 0, Page;
|
|
PPFN_NUMBER MdlPage, LastMdlPage;
|
|
KIRQL OldIrql;
|
|
PMMPFN Pfn1;
|
|
INT LookForZeroedPages;
|
|
ASSERT(KeGetCurrentIrql() <= APC_LEVEL);
|
|
DPRINT1("ARM3-DEBUG: Being called with %I64x %I64x %I64x %lx %d %lu\n", LowAddress, HighAddress, SkipBytes, TotalBytes, CacheAttribute, MdlFlags);
|
|
|
|
//
|
|
// Convert the low address into a PFN
|
|
//
|
|
LowPage = (PFN_NUMBER)(LowAddress.QuadPart >> PAGE_SHIFT);
|
|
|
|
//
|
|
// Convert, and normalize, the high address into a PFN
|
|
//
|
|
HighPage = (PFN_NUMBER)(HighAddress.QuadPart >> PAGE_SHIFT);
|
|
if (HighPage > MmHighestPhysicalPage) HighPage = MmHighestPhysicalPage;
|
|
|
|
//
|
|
// Validate skipbytes and convert them into pages
|
|
//
|
|
if (BYTE_OFFSET(SkipBytes.LowPart)) return NULL;
|
|
SkipPages = (PFN_NUMBER)(SkipBytes.QuadPart >> PAGE_SHIFT);
|
|
|
|
/* This isn't supported at all */
|
|
if (SkipPages) DPRINT1("WARNING: Caller requesting SkipBytes, MDL might be mismatched\n");
|
|
|
|
//
|
|
// Now compute the number of pages the MDL will cover
|
|
//
|
|
PageCount = (PFN_NUMBER)ADDRESS_AND_SIZE_TO_SPAN_PAGES(0, TotalBytes);
|
|
do
|
|
{
|
|
//
|
|
// Try creating an MDL for these many pages
|
|
//
|
|
Mdl = MmCreateMdl(NULL, NULL, PageCount << PAGE_SHIFT);
|
|
if (Mdl) break;
|
|
|
|
//
|
|
// This function is not required to return the amount of pages requested
|
|
// In fact, it can return as little as 1 page, and callers are supposed
|
|
// to deal with this scenario. So re-attempt the allocation with less
|
|
// pages than before, and see if it worked this time.
|
|
//
|
|
PageCount -= (PageCount >> 4);
|
|
} while (PageCount);
|
|
|
|
//
|
|
// Wow, not even a single page was around!
|
|
//
|
|
if (!Mdl) return NULL;
|
|
|
|
//
|
|
// This is where the page array starts....
|
|
//
|
|
MdlPage = (PPFN_NUMBER)(Mdl + 1);
|
|
|
|
//
|
|
// Lock the PFN database
|
|
//
|
|
OldIrql = MiAcquirePfnLock();
|
|
|
|
//
|
|
// Are we looking for any pages, without discriminating?
|
|
//
|
|
if ((LowPage == 0) && (HighPage == MmHighestPhysicalPage))
|
|
{
|
|
//
|
|
// Well then, let's go shopping
|
|
//
|
|
while (PagesFound < PageCount)
|
|
{
|
|
/* Grab a page */
|
|
MI_SET_USAGE(MI_USAGE_MDL);
|
|
MI_SET_PROCESS2("Kernel");
|
|
|
|
/* FIXME: This check should be smarter */
|
|
Page = 0;
|
|
if (MmAvailablePages != 0)
|
|
Page = MiRemoveAnyPage(0);
|
|
|
|
if (Page == 0)
|
|
{
|
|
/* This is not good... hopefully we have at least SOME pages */
|
|
ASSERT(PagesFound);
|
|
break;
|
|
}
|
|
|
|
/* Grab the page entry for it */
|
|
Pfn1 = MiGetPfnEntry(Page);
|
|
|
|
//
|
|
// Make sure it's really free
|
|
//
|
|
ASSERT(Pfn1->u3.e2.ReferenceCount == 0);
|
|
|
|
/* Now setup the page and mark it */
|
|
Pfn1->u3.e2.ReferenceCount = 1;
|
|
Pfn1->u2.ShareCount = 1;
|
|
MI_SET_PFN_DELETED(Pfn1);
|
|
Pfn1->u4.PteFrame = 0x1FFEDCB;
|
|
Pfn1->u3.e1.StartOfAllocation = 1;
|
|
Pfn1->u3.e1.EndOfAllocation = 1;
|
|
Pfn1->u4.VerifierAllocation = 0;
|
|
|
|
//
|
|
// Save it into the MDL
|
|
//
|
|
*MdlPage++ = MiGetPfnEntryIndex(Pfn1);
|
|
PagesFound++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// You want specific range of pages. We'll do this in two runs
|
|
//
|
|
for (LookForZeroedPages = 1; LookForZeroedPages >= 0; LookForZeroedPages--)
|
|
{
|
|
//
|
|
// Scan the range you specified
|
|
//
|
|
for (Page = LowPage; Page < HighPage; Page++)
|
|
{
|
|
//
|
|
// Get the PFN entry for this page
|
|
//
|
|
Pfn1 = MiGetPfnEntry(Page);
|
|
ASSERT(Pfn1);
|
|
|
|
//
|
|
// Make sure it's free and if this is our first pass, zeroed
|
|
//
|
|
if (MiIsPfnInUse(Pfn1)) continue;
|
|
if ((Pfn1->u3.e1.PageLocation == ZeroedPageList) != LookForZeroedPages) continue;
|
|
|
|
/* Remove the page from the free or zero list */
|
|
ASSERT(Pfn1->u3.e1.ReadInProgress == 0);
|
|
MI_SET_USAGE(MI_USAGE_MDL);
|
|
MI_SET_PROCESS2("Kernel");
|
|
MiUnlinkFreeOrZeroedPage(Pfn1);
|
|
|
|
//
|
|
// Sanity checks
|
|
//
|
|
ASSERT(Pfn1->u3.e2.ReferenceCount == 0);
|
|
|
|
//
|
|
// Now setup the page and mark it
|
|
//
|
|
Pfn1->u3.e2.ReferenceCount = 1;
|
|
Pfn1->u2.ShareCount = 1;
|
|
MI_SET_PFN_DELETED(Pfn1);
|
|
Pfn1->u4.PteFrame = 0x1FFEDCB;
|
|
Pfn1->u3.e1.StartOfAllocation = 1;
|
|
Pfn1->u3.e1.EndOfAllocation = 1;
|
|
Pfn1->u4.VerifierAllocation = 0;
|
|
|
|
//
|
|
// Save this page into the MDL
|
|
//
|
|
*MdlPage++ = Page;
|
|
if (++PagesFound == PageCount) break;
|
|
}
|
|
|
|
//
|
|
// If the first pass was enough, don't keep going, otherwise, go again
|
|
//
|
|
if (PagesFound == PageCount) break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now release the PFN count
|
|
//
|
|
MiReleasePfnLock(OldIrql);
|
|
|
|
//
|
|
// We might've found less pages, but not more ;-)
|
|
//
|
|
if (PagesFound != PageCount) ASSERT(PagesFound < PageCount);
|
|
if (!PagesFound)
|
|
{
|
|
//
|
|
// If we didn' tfind any pages at all, fail
|
|
//
|
|
DPRINT1("NO MDL PAGES!\n");
|
|
ExFreePoolWithTag(Mdl, TAG_MDL);
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Write out how many pages we found
|
|
//
|
|
Mdl->ByteCount = (ULONG)(PagesFound << PAGE_SHIFT);
|
|
|
|
//
|
|
// Terminate the MDL array if there's certain missing pages
|
|
//
|
|
if (PagesFound != PageCount) *MdlPage = LIST_HEAD;
|
|
|
|
//
|
|
// Now go back and loop over all the MDL pages
|
|
//
|
|
MdlPage = (PPFN_NUMBER)(Mdl + 1);
|
|
LastMdlPage = MdlPage + PagesFound;
|
|
while (MdlPage < LastMdlPage)
|
|
{
|
|
//
|
|
// Check if we've reached the end
|
|
//
|
|
Page = *MdlPage++;
|
|
if (Page == LIST_HEAD) break;
|
|
|
|
//
|
|
// Get the PFN entry for the page and check if we should zero it out
|
|
//
|
|
Pfn1 = MiGetPfnEntry(Page);
|
|
ASSERT(Pfn1);
|
|
if (Pfn1->u3.e1.PageLocation != ZeroedPageList) MiZeroPhysicalPage(Page);
|
|
Pfn1->u3.e1.PageLocation = ActiveAndValid;
|
|
}
|
|
|
|
//
|
|
// We're done, mark the pages as locked
|
|
//
|
|
Mdl->Process = NULL;
|
|
Mdl->MdlFlags |= MDL_PAGES_LOCKED;
|
|
return Mdl;
|
|
}
|
|
|
|
VOID
|
|
NTAPI
|
|
MmSetRmapListHeadPage(PFN_NUMBER Pfn, PMM_RMAP_ENTRY ListHead)
|
|
{
|
|
PMMPFN Pfn1;
|
|
|
|
/* PFN database must be locked */
|
|
MI_ASSERT_PFN_LOCK_HELD();
|
|
|
|
Pfn1 = MiGetPfnEntry(Pfn);
|
|
ASSERT(Pfn1);
|
|
ASSERT_IS_ROS_PFN(Pfn1);
|
|
|
|
if (ListHead)
|
|
{
|
|
/* Should not be trying to insert an RMAP for a non-active page */
|
|
ASSERT(MiIsPfnInUse(Pfn1) == TRUE);
|
|
|
|
/* Set the list head address */
|
|
Pfn1->RmapListHead = ListHead;
|
|
}
|
|
else
|
|
{
|
|
/* ReactOS semantics dictate the page is STILL active right now */
|
|
ASSERT(MiIsPfnInUse(Pfn1) == TRUE);
|
|
|
|
/* In this case, the RMAP is actually being removed, so clear field */
|
|
Pfn1->RmapListHead = NULL;
|
|
|
|
/* ReactOS semantics will now release the page, which will make it free and enter a colored list */
|
|
}
|
|
}
|
|
|
|
PMM_RMAP_ENTRY
|
|
NTAPI
|
|
MmGetRmapListHeadPage(PFN_NUMBER Pfn)
|
|
{
|
|
PMMPFN Pfn1;
|
|
|
|
/* PFN database must be locked */
|
|
MI_ASSERT_PFN_LOCK_HELD();
|
|
|
|
/* Get the entry */
|
|
Pfn1 = MiGetPfnEntry(Pfn);
|
|
ASSERT(Pfn1);
|
|
|
|
if (!MI_IS_ROS_PFN(Pfn1))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* Should not have an RMAP for a non-active page */
|
|
ASSERT(MiIsPfnInUse(Pfn1) == TRUE);
|
|
|
|
/* Get the list head */
|
|
return Pfn1->RmapListHead;
|
|
}
|
|
|
|
VOID
|
|
NTAPI
|
|
MmSetSavedSwapEntryPage(PFN_NUMBER Pfn, SWAPENTRY SwapEntry)
|
|
{
|
|
KIRQL oldIrql;
|
|
PMMPFN Pfn1;
|
|
|
|
Pfn1 = MiGetPfnEntry(Pfn);
|
|
ASSERT(Pfn1);
|
|
ASSERT_IS_ROS_PFN(Pfn1);
|
|
|
|
oldIrql = MiAcquirePfnLock();
|
|
Pfn1->u1.SwapEntry = SwapEntry;
|
|
MiReleasePfnLock(oldIrql);
|
|
}
|
|
|
|
SWAPENTRY
|
|
NTAPI
|
|
MmGetSavedSwapEntryPage(PFN_NUMBER Pfn)
|
|
{
|
|
SWAPENTRY SwapEntry;
|
|
KIRQL oldIrql;
|
|
PMMPFN Pfn1;
|
|
|
|
Pfn1 = MiGetPfnEntry(Pfn);
|
|
ASSERT(Pfn1);
|
|
ASSERT_IS_ROS_PFN(Pfn1);
|
|
|
|
oldIrql = MiAcquirePfnLock();
|
|
SwapEntry = Pfn1->u1.SwapEntry;
|
|
MiReleasePfnLock(oldIrql);
|
|
|
|
return(SwapEntry);
|
|
}
|
|
|
|
VOID
|
|
NTAPI
|
|
MmReferencePage(PFN_NUMBER Pfn)
|
|
{
|
|
PMMPFN Pfn1;
|
|
|
|
DPRINT("MmReferencePage(PysicalAddress %x)\n", Pfn << PAGE_SHIFT);
|
|
|
|
MI_ASSERT_PFN_LOCK_HELD();
|
|
ASSERT(Pfn != 0);
|
|
ASSERT(Pfn <= MmHighestPhysicalPage);
|
|
|
|
Pfn1 = MiGetPfnEntry(Pfn);
|
|
ASSERT(Pfn1);
|
|
ASSERT_IS_ROS_PFN(Pfn1);
|
|
|
|
ASSERT(Pfn1->u3.e2.ReferenceCount != 0);
|
|
Pfn1->u3.e2.ReferenceCount++;
|
|
}
|
|
|
|
ULONG
|
|
NTAPI
|
|
MmGetReferenceCountPage(PFN_NUMBER Pfn)
|
|
{
|
|
KIRQL oldIrql;
|
|
ULONG RCount;
|
|
PMMPFN Pfn1;
|
|
|
|
DPRINT("MmGetReferenceCountPage(PhysicalAddress %x)\n", Pfn << PAGE_SHIFT);
|
|
|
|
oldIrql = MiAcquirePfnLock();
|
|
Pfn1 = MiGetPfnEntry(Pfn);
|
|
ASSERT(Pfn1);
|
|
ASSERT_IS_ROS_PFN(Pfn1);
|
|
|
|
RCount = Pfn1->u3.e2.ReferenceCount;
|
|
|
|
MiReleasePfnLock(oldIrql);
|
|
return(RCount);
|
|
}
|
|
|
|
BOOLEAN
|
|
NTAPI
|
|
MmIsPageInUse(PFN_NUMBER Pfn)
|
|
{
|
|
return MiIsPfnInUse(MiGetPfnEntry(Pfn));
|
|
}
|
|
|
|
VOID
|
|
NTAPI
|
|
MmDereferencePage(PFN_NUMBER Pfn)
|
|
{
|
|
PMMPFN Pfn1;
|
|
KIRQL OldIrql;
|
|
DPRINT("MmDereferencePage(PhysicalAddress %x)\n", Pfn << PAGE_SHIFT);
|
|
|
|
OldIrql = MiAcquirePfnLock();
|
|
|
|
Pfn1 = MiGetPfnEntry(Pfn);
|
|
ASSERT(Pfn1);
|
|
ASSERT_IS_ROS_PFN(Pfn1);
|
|
|
|
ASSERT(Pfn1->u3.e2.ReferenceCount != 0);
|
|
Pfn1->u3.e2.ReferenceCount--;
|
|
if (Pfn1->u3.e2.ReferenceCount == 0)
|
|
{
|
|
/* Apply LRU hack */
|
|
if (Pfn1->u4.MustBeCached)
|
|
{
|
|
MmRemoveLRUUserPage(Pfn);
|
|
Pfn1->u4.MustBeCached = 0;
|
|
}
|
|
|
|
/* Mark the page temporarily as valid, we're going to make it free soon */
|
|
Pfn1->u3.e1.PageLocation = ActiveAndValid;
|
|
|
|
/* It's not a ROS PFN anymore */
|
|
Pfn1->u4.AweAllocation = FALSE;
|
|
|
|
/* Bring it back into the free list */
|
|
DPRINT("Legacy free: %lx\n", Pfn);
|
|
MiInsertPageInFreeList(Pfn);
|
|
}
|
|
|
|
MiReleasePfnLock(OldIrql);
|
|
}
|
|
|
|
PFN_NUMBER
|
|
NTAPI
|
|
MmAllocPage(ULONG Type)
|
|
{
|
|
PFN_NUMBER PfnOffset;
|
|
PMMPFN Pfn1;
|
|
KIRQL OldIrql;
|
|
|
|
OldIrql = MiAcquirePfnLock();
|
|
|
|
PfnOffset = MiRemoveZeroPage(MI_GET_NEXT_COLOR());
|
|
if (!PfnOffset)
|
|
{
|
|
KeBugCheck(NO_PAGES_AVAILABLE);
|
|
}
|
|
|
|
DPRINT("Legacy allocate: %lx\n", PfnOffset);
|
|
Pfn1 = MiGetPfnEntry(PfnOffset);
|
|
Pfn1->u3.e2.ReferenceCount = 1;
|
|
Pfn1->u3.e1.PageLocation = ActiveAndValid;
|
|
|
|
/* This marks the PFN as a ReactOS PFN */
|
|
Pfn1->u4.AweAllocation = TRUE;
|
|
|
|
/* Allocate the extra ReactOS Data and zero it out */
|
|
Pfn1->u1.SwapEntry = 0;
|
|
Pfn1->RmapListHead = NULL;
|
|
|
|
Pfn1->NextLRU = NULL;
|
|
Pfn1->PreviousLRU = NULL;
|
|
|
|
if (Type == MC_USER)
|
|
{
|
|
Pfn1->u4.MustBeCached = 1; /* HACK again */
|
|
MmInsertLRULastUserPage(PfnOffset);
|
|
}
|
|
|
|
MiReleasePfnLock(OldIrql);
|
|
return PfnOffset;
|
|
}
|
|
|
|
/* EOF */
|