[NTOS]: Zeroed pages should go at the front, not the back of the zero list. Going...
[reactos.git] / reactos / ntoskrnl / mm / freelist.c
1 /*
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
6 *
7 * PROGRAMMERS: David Welch (welch@cwcom.net)
8 * Robert Bergkvist
9 */
10
11 /* INCLUDES ****************************************************************/
12
13 #include <ntoskrnl.h>
14 #define NDEBUG
15 #include <debug.h>
16
17 #if defined (ALLOC_PRAGMA)
18 #pragma alloc_text(INIT, MmInitializePageList)
19 #endif
20
21 #define MODULE_INVOLVED_IN_ARM3
22 #include "ARM3/miarm.h"
23
24 /* GLOBALS ****************************************************************/
25
26 //
27 //
28 // ReactOS to NT Physical Page Descriptor Entry Legacy Mapping Definitions
29 //
30 // REACTOS NT
31 //
32 #define RmapListHead AweReferenceCount
33 #define PHYSICAL_PAGE MMPFN
34 #define PPHYSICAL_PAGE PMMPFN
35
36 PPHYSICAL_PAGE MmPfnDatabase;
37
38 PFN_NUMBER MmAvailablePages;
39 PFN_NUMBER MmResidentAvailablePages;
40 PFN_NUMBER MmResidentAvailableAtInit;
41
42 SIZE_T MmTotalCommitLimit;
43 SIZE_T MmTotalCommittedPages;
44 SIZE_T MmSharedCommit;
45 SIZE_T MmDriverCommit;
46 SIZE_T MmProcessCommit;
47 SIZE_T MmPagedPoolCommit;
48 SIZE_T MmPeakCommitment;
49 SIZE_T MmtotalCommitLimitMaximum;
50
51 KEVENT ZeroPageThreadEvent;
52 static BOOLEAN ZeroPageThreadShouldTerminate = FALSE;
53 static RTL_BITMAP MiUserPfnBitMap;
54
55 /* FUNCTIONS *************************************************************/
56
57 VOID
58 NTAPI
59 MiInitializeUserPfnBitmap(VOID)
60 {
61 PVOID Bitmap;
62
63 /* Allocate enough buffer for the PFN bitmap and align it on 32-bits */
64 Bitmap = ExAllocatePoolWithTag(NonPagedPool,
65 (((MmHighestPhysicalPage + 1) + 31) / 32) * 4,
66 ' mM');
67 ASSERT(Bitmap);
68
69 /* Initialize it and clear all the bits to begin with */
70 RtlInitializeBitMap(&MiUserPfnBitMap,
71 Bitmap,
72 MmHighestPhysicalPage + 1);
73 RtlClearAllBits(&MiUserPfnBitMap);
74 }
75
76 PFN_NUMBER
77 NTAPI
78 MmGetLRUFirstUserPage(VOID)
79 {
80 ULONG Position;
81 KIRQL OldIrql;
82
83 /* Find the first user page */
84 OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
85 Position = RtlFindSetBits(&MiUserPfnBitMap, 1, 0);
86 KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
87 if (Position == 0xFFFFFFFF) return 0;
88
89 /* Return it */
90 return Position;
91 }
92
93 VOID
94 NTAPI
95 MmInsertLRULastUserPage(PFN_NUMBER Pfn)
96 {
97 KIRQL OldIrql;
98
99 /* Set the page as a user page */
100 OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
101 RtlSetBit(&MiUserPfnBitMap, Pfn);
102 KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
103 }
104
105 PFN_NUMBER
106 NTAPI
107 MmGetLRUNextUserPage(PFN_NUMBER PreviousPfn)
108 {
109 ULONG Position;
110 KIRQL OldIrql;
111
112 /* Find the next user page */
113 OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
114 Position = RtlFindSetBits(&MiUserPfnBitMap, 1, PreviousPfn + 1);
115 KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
116 if (Position == 0xFFFFFFFF) return 0;
117
118 /* Return it */
119 return Position;
120 }
121
122 VOID
123 NTAPI
124 MmRemoveLRUUserPage(PFN_NUMBER Page)
125 {
126 /* Unset the page as a user page */
127 RtlClearBit(&MiUserPfnBitMap, Page);
128 }
129
130 BOOLEAN
131 NTAPI
132 MiIsPfnFree(IN PMMPFN Pfn1)
133 {
134 /* Must be a free or zero page, with no references, linked */
135 return ((Pfn1->u3.e1.PageLocation <= StandbyPageList) &&
136 (Pfn1->u1.Flink) &&
137 (Pfn1->u2.Blink) &&
138 !(Pfn1->u3.e2.ReferenceCount));
139 }
140
141 BOOLEAN
142 NTAPI
143 MiIsPfnInUse(IN PMMPFN Pfn1)
144 {
145 /* Standby list or higher, unlinked, and with references */
146 return !MiIsPfnFree(Pfn1);
147 }
148
149 PMDL
150 NTAPI
151 MiAllocatePagesForMdl(IN PHYSICAL_ADDRESS LowAddress,
152 IN PHYSICAL_ADDRESS HighAddress,
153 IN PHYSICAL_ADDRESS SkipBytes,
154 IN SIZE_T TotalBytes,
155 IN MI_PFN_CACHE_ATTRIBUTE CacheAttribute,
156 IN ULONG MdlFlags)
157 {
158 PMDL Mdl;
159 PFN_NUMBER PageCount, LowPage, HighPage, SkipPages, PagesFound = 0, Page;
160 PPFN_NUMBER MdlPage, LastMdlPage;
161 KIRQL OldIrql;
162 PPHYSICAL_PAGE Pfn1;
163 INT LookForZeroedPages;
164 ASSERT (KeGetCurrentIrql() <= APC_LEVEL);
165
166 //
167 // Convert the low address into a PFN
168 //
169 LowPage = (PFN_NUMBER)(LowAddress.QuadPart >> PAGE_SHIFT);
170
171 //
172 // Convert, and normalize, the high address into a PFN
173 //
174 HighPage = (PFN_NUMBER)(HighAddress.QuadPart >> PAGE_SHIFT);
175 if (HighPage > MmHighestPhysicalPage) HighPage = MmHighestPhysicalPage;
176
177 //
178 // Validate skipbytes and convert them into pages
179 //
180 if (BYTE_OFFSET(SkipBytes.LowPart)) return NULL;
181 SkipPages = (PFN_NUMBER)(SkipBytes.QuadPart >> PAGE_SHIFT);
182
183 /* This isn't supported at all */
184 if (SkipPages) DPRINT1("WARNING: Caller requesting SkipBytes, MDL might be mismatched\n");
185
186 //
187 // Now compute the number of pages the MDL will cover
188 //
189 PageCount = (PFN_NUMBER)ADDRESS_AND_SIZE_TO_SPAN_PAGES(0, TotalBytes);
190 do
191 {
192 //
193 // Try creating an MDL for these many pages
194 //
195 Mdl = MmCreateMdl(NULL, NULL, PageCount << PAGE_SHIFT);
196 if (Mdl) break;
197
198 //
199 // This function is not required to return the amount of pages requested
200 // In fact, it can return as little as 1 page, and callers are supposed
201 // to deal with this scenario. So re-attempt the allocation with less
202 // pages than before, and see if it worked this time.
203 //
204 PageCount -= (PageCount >> 4);
205 } while (PageCount);
206
207 //
208 // Wow, not even a single page was around!
209 //
210 if (!Mdl) return NULL;
211
212 //
213 // This is where the page array starts....
214 //
215 MdlPage = (PPFN_NUMBER)(Mdl + 1);
216
217 //
218 // Lock the PFN database
219 //
220 OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
221
222 //
223 // Are we looking for any pages, without discriminating?
224 //
225 if ((LowPage == 0) && (HighPage == MmHighestPhysicalPage))
226 {
227 //
228 // Well then, let's go shopping
229 //
230 while (PagesFound < PageCount)
231 {
232 /* Grab a page */
233 Page = MiRemoveAnyPage(0);
234 if (Page == 0)
235 {
236 /* This is not good... hopefully we have at least SOME pages */
237 ASSERT(PagesFound);
238 break;
239 }
240
241 /* Grab the page entry for it */
242 Pfn1 = MiGetPfnEntry(Page);
243
244 //
245 // Make sure it's really free
246 //
247 ASSERT(Pfn1->u3.e2.ReferenceCount == 0);
248
249 //
250 // Allocate it and mark it
251 //
252 Pfn1->u3.e1.StartOfAllocation = 1;
253 Pfn1->u3.e1.EndOfAllocation = 1;
254 Pfn1->u3.e2.ReferenceCount = 1;
255
256 //
257 // Save it into the MDL
258 //
259 *MdlPage++ = MiGetPfnEntryIndex(Pfn1);
260 PagesFound++;
261 }
262 }
263 else
264 {
265 //
266 // You want specific range of pages. We'll do this in two runs
267 //
268 for (LookForZeroedPages = 1; LookForZeroedPages >= 0; LookForZeroedPages--)
269 {
270 //
271 // Scan the range you specified
272 //
273 for (Page = LowPage; Page < HighPage; Page++)
274 {
275 //
276 // Get the PFN entry for this page
277 //
278 Pfn1 = MiGetPfnEntry(Page);
279 ASSERT(Pfn1);
280
281 //
282 // Make sure it's free and if this is our first pass, zeroed
283 //
284 if (MiIsPfnInUse(Pfn1)) continue;
285 if ((Pfn1->u3.e1.PageLocation == ZeroedPageList) != LookForZeroedPages) continue;
286
287 /* Remove the page from the free or zero list */
288 MiUnlinkFreeOrZeroedPage(Pfn1);
289
290 //
291 // Sanity checks
292 //
293 ASSERT(Pfn1->u3.e2.ReferenceCount == 0);
294
295 //
296 // Now setup the page and mark it
297 //
298 Pfn1->u3.e2.ReferenceCount = 1;
299 Pfn1->u3.e1.StartOfAllocation = 1;
300 Pfn1->u3.e1.EndOfAllocation = 1;
301
302 //
303 // Save this page into the MDL
304 //
305 *MdlPage++ = Page;
306 if (++PagesFound == PageCount) break;
307 }
308
309 //
310 // If the first pass was enough, don't keep going, otherwise, go again
311 //
312 if (PagesFound == PageCount) break;
313 }
314 }
315
316 //
317 // Now release the PFN count
318 //
319 KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
320
321 //
322 // We might've found less pages, but not more ;-)
323 //
324 if (PagesFound != PageCount) ASSERT(PagesFound < PageCount);
325 if (!PagesFound)
326 {
327 //
328 // If we didn' tfind any pages at all, fail
329 //
330 DPRINT1("NO MDL PAGES!\n");
331 ExFreePool(Mdl);
332 return NULL;
333 }
334
335 //
336 // Write out how many pages we found
337 //
338 Mdl->ByteCount = (ULONG)(PagesFound << PAGE_SHIFT);
339
340 //
341 // Terminate the MDL array if there's certain missing pages
342 //
343 if (PagesFound != PageCount) *MdlPage = -1;
344
345 //
346 // Now go back and loop over all the MDL pages
347 //
348 MdlPage = (PPFN_NUMBER)(Mdl + 1);
349 LastMdlPage = MdlPage + PagesFound;
350 while (MdlPage < LastMdlPage)
351 {
352 //
353 // Check if we've reached the end
354 //
355 Page = *MdlPage++;
356 if (Page == (PFN_NUMBER)-1) break;
357
358 //
359 // Get the PFN entry for the page and check if we should zero it out
360 //
361 Pfn1 = MiGetPfnEntry(Page);
362 ASSERT(Pfn1);
363 if (Pfn1->u3.e1.PageLocation != ZeroedPageList) MiZeroPhysicalPage(Page);
364 Pfn1->u3.e1.PageLocation = ActiveAndValid;
365 }
366
367 //
368 // We're done, mark the pages as locked (should we lock them, though???)
369 //
370 Mdl->Process = NULL;
371 Mdl->MdlFlags |= MDL_PAGES_LOCKED;
372 return Mdl;
373 }
374
375 VOID
376 NTAPI
377 MmDumpPfnDatabase(VOID)
378 {
379 ULONG i;
380 PPHYSICAL_PAGE Pfn1;
381 PCHAR State = "????", Type = "Unknown";
382 KIRQL OldIrql;
383 ULONG Totals[5] = {0}, FreePages = 0;
384
385 KeRaiseIrql(HIGH_LEVEL, &OldIrql);
386
387 //
388 // Loop the PFN database
389 //
390 for (i = 0; i <= MmHighestPhysicalPage; i++)
391 {
392 Pfn1 = MiGetPfnEntry(i);
393 if (!Pfn1) continue;
394
395 //
396 // Get the type
397 //
398 if (MiIsPfnInUse(Pfn1))
399 {
400 State = "Used";
401 }
402 else
403 {
404 State = "Free";
405 Type = "Free";
406 FreePages++;
407 break;
408 }
409
410 //
411 // Pretty-print the page
412 //
413 DbgPrint("0x%08p:\t%04s\t%20s\t(%02d) [%08p])\n",
414 i << PAGE_SHIFT,
415 State,
416 Type,
417 Pfn1->u3.e2.ReferenceCount,
418 Pfn1->RmapListHead);
419 }
420
421 DbgPrint("Nonpaged Pool: %d pages\t[%d KB]\n", Totals[MC_NPPOOL], (Totals[MC_NPPOOL] << PAGE_SHIFT) / 1024);
422 DbgPrint("Paged Pool: %d pages\t[%d KB]\n", Totals[MC_PPOOL], (Totals[MC_PPOOL] << PAGE_SHIFT) / 1024);
423 DbgPrint("File System Cache: %d pages\t[%d KB]\n", Totals[MC_CACHE], (Totals[MC_CACHE] << PAGE_SHIFT) / 1024);
424 DbgPrint("Process Working Set: %d pages\t[%d KB]\n", Totals[MC_USER], (Totals[MC_USER] << PAGE_SHIFT) / 1024);
425 DbgPrint("System: %d pages\t[%d KB]\n", Totals[MC_SYSTEM], (Totals[MC_SYSTEM] << PAGE_SHIFT) / 1024);
426 DbgPrint("Free: %d pages\t[%d KB]\n", FreePages, (FreePages << PAGE_SHIFT) / 1024);
427
428 KeLowerIrql(OldIrql);
429 }
430
431 VOID
432 NTAPI
433 MmSetRmapListHeadPage(PFN_NUMBER Pfn, struct _MM_RMAP_ENTRY* ListHead)
434 {
435 KIRQL oldIrql;
436 PMMPFN Pfn1;
437
438 oldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
439 Pfn1 = MiGetPfnEntry(Pfn);
440 if (ListHead)
441 {
442 /* Should not be trying to insert an RMAP for a non-active page */
443 ASSERT(MiIsPfnInUse(Pfn1) == TRUE);
444
445 /* Set the list head address */
446 Pfn1->RmapListHead = (LONG)ListHead;
447
448 /* Mark that the page has an actual RMAP, not a residual color link */
449 Pfn1->u3.e1.ParityError = TRUE;
450 }
451 else
452 {
453 /* ReactOS semantics dictate the page is STILL active right now */
454 ASSERT(MiIsPfnInUse(Pfn1) == TRUE);
455
456 /* In this case, the RMAP is actually being removed, so clear field */
457 Pfn1->RmapListHead = 0;
458
459 /* Mark that the page has no RMAP, not a residual color link */
460 Pfn1->u3.e1.ParityError = FALSE;
461
462 /* ReactOS semantics will now release the page, which will make it free and enter a colored list */
463 }
464
465 KeReleaseQueuedSpinLock(LockQueuePfnLock, oldIrql);
466 }
467
468 struct _MM_RMAP_ENTRY*
469 NTAPI
470 MmGetRmapListHeadPage(PFN_NUMBER Pfn)
471 {
472 KIRQL oldIrql;
473 struct _MM_RMAP_ENTRY* ListHead;
474 PMMPFN Pfn1;
475
476 /* Lock PFN database */
477 oldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
478
479 /* Get the entry */
480 Pfn1 = MiGetPfnEntry(Pfn);
481
482 /* Check if the page doesn't really have an RMAP */
483 if (Pfn1->u3.e1.ParityError == FALSE)
484 {
485 KeReleaseQueuedSpinLock(LockQueuePfnLock, oldIrql);
486 return NULL;
487 }
488
489 ListHead = (struct _MM_RMAP_ENTRY*)Pfn1->RmapListHead;
490
491 /* Should not have an RMAP for a non-active page */
492 ASSERT(MiIsPfnInUse(Pfn1) == TRUE);
493
494 /* Release PFN database and return rmap list head */
495 KeReleaseQueuedSpinLock(LockQueuePfnLock, oldIrql);
496 return ListHead;
497 }
498
499 VOID
500 NTAPI
501 MmSetSavedSwapEntryPage(PFN_NUMBER Pfn, SWAPENTRY SwapEntry)
502 {
503 KIRQL oldIrql;
504
505 oldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
506 MiGetPfnEntry(Pfn)->u1.WsIndex = SwapEntry;
507 KeReleaseQueuedSpinLock(LockQueuePfnLock, oldIrql);
508 }
509
510 SWAPENTRY
511 NTAPI
512 MmGetSavedSwapEntryPage(PFN_NUMBER Pfn)
513 {
514 SWAPENTRY SwapEntry;
515 KIRQL oldIrql;
516
517 oldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
518 SwapEntry = MiGetPfnEntry(Pfn)->u1.WsIndex;
519 KeReleaseQueuedSpinLock(LockQueuePfnLock, oldIrql);
520
521 return(SwapEntry);
522 }
523
524 VOID
525 NTAPI
526 MmReferencePage(PFN_NUMBER Pfn)
527 {
528 PPHYSICAL_PAGE Page;
529
530 DPRINT("MmReferencePage(PysicalAddress %x)\n", Pfn << PAGE_SHIFT);
531
532 if (Pfn == 0 || Pfn > MmHighestPhysicalPage)
533 {
534 return;
535 }
536
537 Page = MiGetPfnEntry(Pfn);
538 ASSERT(Page);
539
540 Page->u3.e2.ReferenceCount++;
541 }
542
543 ULONG
544 NTAPI
545 MmGetReferenceCountPage(PFN_NUMBER Pfn)
546 {
547 KIRQL oldIrql;
548 ULONG RCount;
549 PPHYSICAL_PAGE Page;
550
551 DPRINT("MmGetReferenceCountPage(PhysicalAddress %x)\n", Pfn << PAGE_SHIFT);
552
553 oldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
554 Page = MiGetPfnEntry(Pfn);
555 ASSERT(Page);
556
557 RCount = Page->u3.e2.ReferenceCount;
558
559 KeReleaseQueuedSpinLock(LockQueuePfnLock, oldIrql);
560 return(RCount);
561 }
562
563 BOOLEAN
564 NTAPI
565 MmIsPageInUse(PFN_NUMBER Pfn)
566 {
567 return MiIsPfnInUse(MiGetPfnEntry(Pfn));
568 }
569
570 VOID
571 NTAPI
572 MiSetConsumer(IN PFN_NUMBER Pfn,
573 IN ULONG Type)
574 {
575 MiGetPfnEntry(Pfn)->u3.e1.PageLocation = ActiveAndValid;
576 }
577
578 VOID
579 NTAPI
580 MmDereferencePage(PFN_NUMBER Pfn)
581 {
582 PPHYSICAL_PAGE Page;
583
584 DPRINT("MmDereferencePage(PhysicalAddress %x)\n", Pfn << PAGE_SHIFT);
585
586 Page = MiGetPfnEntry(Pfn);
587 ASSERT(Page);
588
589 Page->u3.e2.ReferenceCount--;
590 if (Page->u3.e2.ReferenceCount == 0)
591 {
592 /* Mark the page temporarily as valid, we're going to make it free soon */
593 Page->u3.e1.PageLocation = ActiveAndValid;
594
595 /* Bring it back into the free list */
596 MiInsertPageInFreeList(Pfn);
597 }
598 }
599
600 PFN_NUMBER
601 NTAPI
602 MmAllocPage(ULONG Type)
603 {
604 PFN_NUMBER PfnOffset;
605 PMMPFN Pfn1;
606
607 if (Type != MC_SYSTEM)
608 {
609 PfnOffset = MiRemoveZeroPage(0);
610 }
611 else
612 {
613 PfnOffset = MiRemoveAnyPage(0);
614 }
615
616 if (!PfnOffset)
617 {
618 DPRINT1("MmAllocPage(): Out of memory\n");
619 return 0;
620 }
621
622 Pfn1 = MiGetPfnEntry(PfnOffset);
623 Pfn1->u3.e2.ReferenceCount = 1;
624 Pfn1->u3.e1.PageLocation = ActiveAndValid;
625 return PfnOffset;
626 }
627
628 NTSTATUS
629 NTAPI
630 MmZeroPageThreadMain(PVOID Ignored)
631 {
632 NTSTATUS Status;
633 KIRQL oldIrql;
634 PMMPFN Pfn1;
635 PFN_NUMBER PageIndex, FreePage;
636 ULONG Count;
637 PVOID ZeroAddress;
638
639 /* Free initial kernel memory */
640 //MiFreeInitMemory();
641
642 /* Set our priority to 0 */
643 KeGetCurrentThread()->BasePriority = 0;
644 KeSetPriorityThread(KeGetCurrentThread(), 0);
645
646 while(1)
647 {
648 Status = KeWaitForSingleObject(&ZeroPageThreadEvent,
649 0,
650 KernelMode,
651 FALSE,
652 NULL);
653
654 if (ZeroPageThreadShouldTerminate)
655 {
656 DPRINT1("ZeroPageThread: Terminating\n");
657 return STATUS_SUCCESS;
658 }
659 Count = 0;
660 oldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
661 while (MmFreePageListHead.Total)
662 {
663 PageIndex = MmFreePageListHead.Flink;
664 Pfn1 = MiGetPfnEntry(PageIndex);
665 FreePage = MiRemoveAnyPage(0); // FIXME: Use real color
666 if (FreePage != PageIndex)
667 {
668 KeBugCheckEx(PFN_LIST_CORRUPT,
669 0x8F,
670 FreePage,
671 PageIndex,
672 0);
673 }
674
675 Pfn1->u1.Flink = LIST_HEAD;
676 KeReleaseQueuedSpinLock(LockQueuePfnLock, oldIrql);
677
678 ZeroAddress = MiMapPagesToZeroInHyperSpace(Pfn1, 1);
679 ASSERT(ZeroAddress);
680 RtlZeroMemory(ZeroAddress, PAGE_SIZE);
681 MiUnmapPagesInZeroSpace(ZeroAddress, 1);
682
683 oldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
684
685 MiInsertPageInList(&MmZeroedPageListHead, PageIndex);
686 Count++;
687 }
688 DPRINT("Zeroed %d pages.\n", Count);
689 KeResetEvent(&ZeroPageThreadEvent);
690 KeReleaseQueuedSpinLock(LockQueuePfnLock, oldIrql);
691 }
692
693 return STATUS_SUCCESS;
694 }
695
696 /* EOF */