2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS kernel
4 * FILE: ntoskrnl/mm/freelist.c
5 * PURPOSE: Handle the list of free physical pages
7 * PROGRAMMERS: David Welch (welch@cwcom.net)
11 /* INCLUDES ****************************************************************/
17 #define MODULE_INVOLVED_IN_ARM3
18 #include "ARM3/miarm.h"
20 #define ASSERT_IS_ROS_PFN(x) ASSERT(MI_IS_ROS_PFN(x) == TRUE);
22 /* GLOBALS ****************************************************************/
26 PFN_NUMBER MmAvailablePages
;
27 PFN_NUMBER MmResidentAvailablePages
;
28 PFN_NUMBER MmResidentAvailableAtInit
;
30 SIZE_T MmTotalCommittedPages
;
31 SIZE_T MmSharedCommit
;
32 SIZE_T MmDriverCommit
;
33 SIZE_T MmProcessCommit
;
34 SIZE_T MmPagedPoolCommit
;
35 SIZE_T MmPeakCommitment
;
36 SIZE_T MmtotalCommitLimitMaximum
;
38 PMMPFN FirstUserLRUPfn
;
39 PMMPFN LastUserLRUPfn
;
41 /* FUNCTIONS *************************************************************/
45 MmGetLRUFirstUserPage(VOID
)
50 /* Find the first user page */
51 OldIrql
= MiAcquirePfnLock();
53 if (FirstUserLRUPfn
== NULL
)
55 MiReleasePfnLock(OldIrql
);
59 Page
= MiGetPfnEntryIndex(FirstUserLRUPfn
);
60 MmReferencePage(Page
);
62 MiReleasePfnLock(OldIrql
);
69 MmInsertLRULastUserPage(PFN_NUMBER Page
)
71 MI_ASSERT_PFN_LOCK_HELD();
73 PMMPFN Pfn
= MiGetPfnEntry(Page
);
75 if (FirstUserLRUPfn
== NULL
)
76 FirstUserLRUPfn
= Pfn
;
78 Pfn
->PreviousLRU
= LastUserLRUPfn
;
80 if (LastUserLRUPfn
!= NULL
)
81 LastUserLRUPfn
->NextLRU
= Pfn
;
87 MmRemoveLRUUserPage(PFN_NUMBER Page
)
89 MI_ASSERT_PFN_LOCK_HELD();
91 /* Unset the page as a user page */
94 PMMPFN Pfn
= MiGetPfnEntry(Page
);
96 ASSERT_IS_ROS_PFN(Pfn
);
100 ASSERT(Pfn
->PreviousLRU
->NextLRU
== Pfn
);
101 Pfn
->PreviousLRU
->NextLRU
= Pfn
->NextLRU
;
105 ASSERT(FirstUserLRUPfn
== Pfn
);
106 FirstUserLRUPfn
= Pfn
->NextLRU
;
111 ASSERT(Pfn
->NextLRU
->PreviousLRU
== Pfn
);
112 Pfn
->NextLRU
->PreviousLRU
= Pfn
->PreviousLRU
;
116 ASSERT(Pfn
== LastUserLRUPfn
);
117 LastUserLRUPfn
= Pfn
->PreviousLRU
;
120 Pfn
->PreviousLRU
= Pfn
->NextLRU
= NULL
;
125 MmGetLRUNextUserPage(PFN_NUMBER PreviousPage
, BOOLEAN MoveToLast
)
130 /* Find the next user page */
131 OldIrql
= MiAcquirePfnLock();
133 PMMPFN PreviousPfn
= MiGetPfnEntry(PreviousPage
);
134 PMMPFN NextPfn
= PreviousPfn
->NextLRU
;
137 * Move this one at the end of the list.
138 * It may be freed by MmDereferencePage below.
139 * If it's not, then it means it is still hanging in some process address space.
140 * This avoids paging-out e.g. ntdll early just because it's mapped first time.
142 if ((MoveToLast
) && (MmGetReferenceCountPage(PreviousPage
) > 1))
144 MmRemoveLRUUserPage(PreviousPage
);
145 MmInsertLRULastUserPage(PreviousPage
);
150 Page
= MiGetPfnEntryIndex(NextPfn
);
151 MmReferencePage(Page
);
154 MmDereferencePage(PreviousPage
);
156 MiReleasePfnLock(OldIrql
);
163 MiIsPfnFree(IN PMMPFN Pfn1
)
165 /* Must be a free or zero page, with no references, linked */
166 return ((Pfn1
->u3
.e1
.PageLocation
<= StandbyPageList
) &&
169 !(Pfn1
->u3
.e2
.ReferenceCount
));
174 MiIsPfnInUse(IN PMMPFN Pfn1
)
176 /* Standby list or higher, unlinked, and with references */
177 return !MiIsPfnFree(Pfn1
);
182 MiAllocatePagesForMdl(IN PHYSICAL_ADDRESS LowAddress
,
183 IN PHYSICAL_ADDRESS HighAddress
,
184 IN PHYSICAL_ADDRESS SkipBytes
,
185 IN SIZE_T TotalBytes
,
186 IN MI_PFN_CACHE_ATTRIBUTE CacheAttribute
,
190 PFN_NUMBER PageCount
, LowPage
, HighPage
, SkipPages
, PagesFound
= 0, Page
;
191 PPFN_NUMBER MdlPage
, LastMdlPage
;
194 INT LookForZeroedPages
;
195 ASSERT(KeGetCurrentIrql() <= APC_LEVEL
);
196 DPRINT1("ARM3-DEBUG: Being called with %I64x %I64x %I64x %lx %d %lu\n", LowAddress
, HighAddress
, SkipBytes
, TotalBytes
, CacheAttribute
, MdlFlags
);
199 // Convert the low address into a PFN
201 LowPage
= (PFN_NUMBER
)(LowAddress
.QuadPart
>> PAGE_SHIFT
);
204 // Convert, and normalize, the high address into a PFN
206 HighPage
= (PFN_NUMBER
)(HighAddress
.QuadPart
>> PAGE_SHIFT
);
207 if (HighPage
> MmHighestPhysicalPage
) HighPage
= MmHighestPhysicalPage
;
210 // Validate skipbytes and convert them into pages
212 if (BYTE_OFFSET(SkipBytes
.LowPart
)) return NULL
;
213 SkipPages
= (PFN_NUMBER
)(SkipBytes
.QuadPart
>> PAGE_SHIFT
);
215 /* This isn't supported at all */
216 if (SkipPages
) DPRINT1("WARNING: Caller requesting SkipBytes, MDL might be mismatched\n");
219 // Now compute the number of pages the MDL will cover
221 PageCount
= (PFN_NUMBER
)ADDRESS_AND_SIZE_TO_SPAN_PAGES(0, TotalBytes
);
225 // Try creating an MDL for these many pages
227 Mdl
= MmCreateMdl(NULL
, NULL
, PageCount
<< PAGE_SHIFT
);
231 // This function is not required to return the amount of pages requested
232 // In fact, it can return as little as 1 page, and callers are supposed
233 // to deal with this scenario. So re-attempt the allocation with less
234 // pages than before, and see if it worked this time.
236 PageCount
-= (PageCount
>> 4);
240 // Wow, not even a single page was around!
242 if (!Mdl
) return NULL
;
245 // This is where the page array starts....
247 MdlPage
= (PPFN_NUMBER
)(Mdl
+ 1);
250 // Lock the PFN database
252 OldIrql
= MiAcquirePfnLock();
255 // Are we looking for any pages, without discriminating?
257 if ((LowPage
== 0) && (HighPage
== MmHighestPhysicalPage
))
260 // Well then, let's go shopping
262 while (PagesFound
< PageCount
)
265 MI_SET_USAGE(MI_USAGE_MDL
);
266 MI_SET_PROCESS2("Kernel");
268 /* FIXME: This check should be smarter */
270 if (MmAvailablePages
!= 0)
271 Page
= MiRemoveAnyPage(0);
275 /* This is not good... hopefully we have at least SOME pages */
280 /* Grab the page entry for it */
281 Pfn1
= MiGetPfnEntry(Page
);
284 // Make sure it's really free
286 ASSERT(Pfn1
->u3
.e2
.ReferenceCount
== 0);
288 /* Now setup the page and mark it */
289 Pfn1
->u3
.e2
.ReferenceCount
= 1;
290 Pfn1
->u2
.ShareCount
= 1;
291 MI_SET_PFN_DELETED(Pfn1
);
292 Pfn1
->u4
.PteFrame
= 0x1FFEDCB;
293 Pfn1
->u3
.e1
.StartOfAllocation
= 1;
294 Pfn1
->u3
.e1
.EndOfAllocation
= 1;
295 Pfn1
->u4
.VerifierAllocation
= 0;
298 // Save it into the MDL
300 *MdlPage
++ = MiGetPfnEntryIndex(Pfn1
);
307 // You want specific range of pages. We'll do this in two runs
309 for (LookForZeroedPages
= 1; LookForZeroedPages
>= 0; LookForZeroedPages
--)
312 // Scan the range you specified
314 for (Page
= LowPage
; Page
< HighPage
; Page
++)
317 // Get the PFN entry for this page
319 Pfn1
= MiGetPfnEntry(Page
);
323 // Make sure it's free and if this is our first pass, zeroed
325 if (MiIsPfnInUse(Pfn1
)) continue;
326 if ((Pfn1
->u3
.e1
.PageLocation
== ZeroedPageList
) != LookForZeroedPages
) continue;
328 /* Remove the page from the free or zero list */
329 ASSERT(Pfn1
->u3
.e1
.ReadInProgress
== 0);
330 MI_SET_USAGE(MI_USAGE_MDL
);
331 MI_SET_PROCESS2("Kernel");
332 MiUnlinkFreeOrZeroedPage(Pfn1
);
337 ASSERT(Pfn1
->u3
.e2
.ReferenceCount
== 0);
340 // Now setup the page and mark it
342 Pfn1
->u3
.e2
.ReferenceCount
= 1;
343 Pfn1
->u2
.ShareCount
= 1;
344 MI_SET_PFN_DELETED(Pfn1
);
345 Pfn1
->u4
.PteFrame
= 0x1FFEDCB;
346 Pfn1
->u3
.e1
.StartOfAllocation
= 1;
347 Pfn1
->u3
.e1
.EndOfAllocation
= 1;
348 Pfn1
->u4
.VerifierAllocation
= 0;
351 // Save this page into the MDL
354 if (++PagesFound
== PageCount
) break;
358 // If the first pass was enough, don't keep going, otherwise, go again
360 if (PagesFound
== PageCount
) break;
365 // Now release the PFN count
367 MiReleasePfnLock(OldIrql
);
370 // We might've found less pages, but not more ;-)
372 if (PagesFound
!= PageCount
) ASSERT(PagesFound
< PageCount
);
376 // If we didn' tfind any pages at all, fail
378 DPRINT1("NO MDL PAGES!\n");
379 ExFreePoolWithTag(Mdl
, TAG_MDL
);
384 // Write out how many pages we found
386 Mdl
->ByteCount
= (ULONG
)(PagesFound
<< PAGE_SHIFT
);
389 // Terminate the MDL array if there's certain missing pages
391 if (PagesFound
!= PageCount
) *MdlPage
= LIST_HEAD
;
394 // Now go back and loop over all the MDL pages
396 MdlPage
= (PPFN_NUMBER
)(Mdl
+ 1);
397 LastMdlPage
= MdlPage
+ PagesFound
;
398 while (MdlPage
< LastMdlPage
)
401 // Check if we've reached the end
404 if (Page
== LIST_HEAD
) break;
407 // Get the PFN entry for the page and check if we should zero it out
409 Pfn1
= MiGetPfnEntry(Page
);
411 if (Pfn1
->u3
.e1
.PageLocation
!= ZeroedPageList
) MiZeroPhysicalPage(Page
);
412 Pfn1
->u3
.e1
.PageLocation
= ActiveAndValid
;
416 // We're done, mark the pages as locked
419 Mdl
->MdlFlags
|= MDL_PAGES_LOCKED
;
425 MmSetRmapListHeadPage(PFN_NUMBER Pfn
, PMM_RMAP_ENTRY ListHead
)
429 /* PFN database must be locked */
430 MI_ASSERT_PFN_LOCK_HELD();
432 Pfn1
= MiGetPfnEntry(Pfn
);
434 ASSERT_IS_ROS_PFN(Pfn1
);
438 /* Should not be trying to insert an RMAP for a non-active page */
439 ASSERT(MiIsPfnInUse(Pfn1
) == TRUE
);
441 /* Set the list head address */
442 Pfn1
->RmapListHead
= ListHead
;
446 /* ReactOS semantics dictate the page is STILL active right now */
447 ASSERT(MiIsPfnInUse(Pfn1
) == TRUE
);
449 /* In this case, the RMAP is actually being removed, so clear field */
450 Pfn1
->RmapListHead
= NULL
;
452 /* ReactOS semantics will now release the page, which will make it free and enter a colored list */
458 MmGetRmapListHeadPage(PFN_NUMBER Pfn
)
462 /* PFN database must be locked */
463 MI_ASSERT_PFN_LOCK_HELD();
466 Pfn1
= MiGetPfnEntry(Pfn
);
469 if (!MI_IS_ROS_PFN(Pfn1
))
474 /* Should not have an RMAP for a non-active page */
475 ASSERT(MiIsPfnInUse(Pfn1
) == TRUE
);
477 /* Get the list head */
478 return Pfn1
->RmapListHead
;
483 MmSetSavedSwapEntryPage(PFN_NUMBER Pfn
, SWAPENTRY SwapEntry
)
488 Pfn1
= MiGetPfnEntry(Pfn
);
490 ASSERT_IS_ROS_PFN(Pfn1
);
492 oldIrql
= MiAcquirePfnLock();
493 Pfn1
->u1
.SwapEntry
= SwapEntry
;
494 MiReleasePfnLock(oldIrql
);
499 MmGetSavedSwapEntryPage(PFN_NUMBER Pfn
)
505 Pfn1
= MiGetPfnEntry(Pfn
);
507 ASSERT_IS_ROS_PFN(Pfn1
);
509 oldIrql
= MiAcquirePfnLock();
510 SwapEntry
= Pfn1
->u1
.SwapEntry
;
511 MiReleasePfnLock(oldIrql
);
518 MmReferencePage(PFN_NUMBER Pfn
)
522 DPRINT("MmReferencePage(PysicalAddress %x)\n", Pfn
<< PAGE_SHIFT
);
524 MI_ASSERT_PFN_LOCK_HELD();
526 ASSERT(Pfn
<= MmHighestPhysicalPage
);
528 Pfn1
= MiGetPfnEntry(Pfn
);
530 ASSERT_IS_ROS_PFN(Pfn1
);
532 ASSERT(Pfn1
->u3
.e2
.ReferenceCount
!= 0);
533 Pfn1
->u3
.e2
.ReferenceCount
++;
538 MmGetReferenceCountPage(PFN_NUMBER Pfn
)
544 DPRINT("MmGetReferenceCountPage(PhysicalAddress %x)\n", Pfn
<< PAGE_SHIFT
);
546 oldIrql
= MiAcquirePfnLock();
547 Pfn1
= MiGetPfnEntry(Pfn
);
549 ASSERT_IS_ROS_PFN(Pfn1
);
551 RCount
= Pfn1
->u3
.e2
.ReferenceCount
;
553 MiReleasePfnLock(oldIrql
);
559 MmIsPageInUse(PFN_NUMBER Pfn
)
561 return MiIsPfnInUse(MiGetPfnEntry(Pfn
));
566 MmDereferencePage(PFN_NUMBER Pfn
)
570 DPRINT("MmDereferencePage(PhysicalAddress %x)\n", Pfn
<< PAGE_SHIFT
);
572 OldIrql
= MiAcquirePfnLock();
574 Pfn1
= MiGetPfnEntry(Pfn
);
576 ASSERT_IS_ROS_PFN(Pfn1
);
578 ASSERT(Pfn1
->u3
.e2
.ReferenceCount
!= 0);
579 Pfn1
->u3
.e2
.ReferenceCount
--;
580 if (Pfn1
->u3
.e2
.ReferenceCount
== 0)
583 if (Pfn1
->u4
.MustBeCached
)
585 MmRemoveLRUUserPage(Pfn
);
586 Pfn1
->u4
.MustBeCached
= 0;
589 /* Mark the page temporarily as valid, we're going to make it free soon */
590 Pfn1
->u3
.e1
.PageLocation
= ActiveAndValid
;
592 /* It's not a ROS PFN anymore */
593 Pfn1
->u4
.AweAllocation
= FALSE
;
595 /* Bring it back into the free list */
596 DPRINT("Legacy free: %lx\n", Pfn
);
597 MiInsertPageInFreeList(Pfn
);
600 MiReleasePfnLock(OldIrql
);
605 MmAllocPage(ULONG Type
)
607 PFN_NUMBER PfnOffset
;
611 OldIrql
= MiAcquirePfnLock();
617 MI_SET_USAGE(MI_USAGE_CACHE
);
620 MI_SET_USAGE(MI_USAGE_SECTION
);
627 PfnOffset
= MiRemoveZeroPage(MI_GET_NEXT_COLOR());
630 KeBugCheck(NO_PAGES_AVAILABLE
);
633 DPRINT("Legacy allocate: %lx\n", PfnOffset
);
634 Pfn1
= MiGetPfnEntry(PfnOffset
);
635 Pfn1
->u3
.e2
.ReferenceCount
= 1;
636 Pfn1
->u3
.e1
.PageLocation
= ActiveAndValid
;
638 /* This marks the PFN as a ReactOS PFN */
639 Pfn1
->u4
.AweAllocation
= TRUE
;
641 /* Allocate the extra ReactOS Data and zero it out */
642 Pfn1
->u1
.SwapEntry
= 0;
643 Pfn1
->RmapListHead
= NULL
;
645 Pfn1
->NextLRU
= NULL
;
646 Pfn1
->PreviousLRU
= NULL
;
650 Pfn1
->u4
.MustBeCached
= 1; /* HACK again */
651 MmInsertLRULastUserPage(PfnOffset
);
654 MiReleasePfnLock(OldIrql
);