[KERNEL32]
[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 //
184 // Now compute the number of pages the MDL will cover
185 //
186 PageCount = (PFN_NUMBER)ADDRESS_AND_SIZE_TO_SPAN_PAGES(0, TotalBytes);
187 do
188 {
189 //
190 // Try creating an MDL for these many pages
191 //
192 Mdl = MmCreateMdl(NULL, NULL, PageCount << PAGE_SHIFT);
193 if (Mdl) break;
194
195 //
196 // This function is not required to return the amount of pages requested
197 // In fact, it can return as little as 1 page, and callers are supposed
198 // to deal with this scenario. So re-attempt the allocation with less
199 // pages than before, and see if it worked this time.
200 //
201 PageCount -= (PageCount >> 4);
202 } while (PageCount);
203
204 //
205 // Wow, not even a single page was around!
206 //
207 if (!Mdl) return NULL;
208
209 //
210 // This is where the page array starts....
211 //
212 MdlPage = (PPFN_NUMBER)(Mdl + 1);
213
214 //
215 // Lock the PFN database
216 //
217 OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
218
219 //
220 // Are we looking for any pages, without discriminating?
221 //
222 if ((LowPage == 0) && (HighPage == MmHighestPhysicalPage))
223 {
224 //
225 // Well then, let's go shopping
226 //
227 while (PagesFound < PageCount)
228 {
229 //
230 // Do we have zeroed pages?
231 //
232 if (MmZeroedPageListHead.Total)
233 {
234 //
235 // Grab a zero page
236 //
237 Pfn1 = MiRemoveHeadList(&MmZeroedPageListHead);
238 }
239 else if (MmFreePageListHead.Total)
240 {
241 //
242 // Nope, grab an unzeroed page
243 //
244 Pfn1 = MiRemoveHeadList(&MmFreePageListHead);
245 }
246 else
247 {
248 //
249 // This is not good... hopefully we have at least SOME pages
250 //
251 ASSERT(PagesFound);
252 break;
253 }
254
255 //
256 // Make sure it's really free
257 //
258 ASSERT(Pfn1->u3.e2.ReferenceCount == 0);
259
260 //
261 // Allocate it and mark it
262 //
263 Pfn1->u3.e1.StartOfAllocation = 1;
264 Pfn1->u3.e1.EndOfAllocation = 1;
265 Pfn1->u3.e2.ReferenceCount = 1;
266
267 //
268 // Decrease available pages
269 //
270 MmAvailablePages--;
271
272 //
273 // Save it into the MDL
274 //
275 *MdlPage++ = MiGetPfnEntryIndex(Pfn1);
276 PagesFound++;
277 }
278 }
279 else
280 {
281 //
282 // You want specific range of pages. We'll do this in two runs
283 //
284 for (LookForZeroedPages = 1; LookForZeroedPages >= 0; LookForZeroedPages--)
285 {
286 //
287 // Scan the range you specified
288 //
289 for (Page = LowPage; Page < HighPage; Page++)
290 {
291 //
292 // Get the PFN entry for this page
293 //
294 Pfn1 = MiGetPfnEntry(Page);
295 ASSERT(Pfn1);
296
297 //
298 // Make sure it's free and if this is our first pass, zeroed
299 //
300 if (MiIsPfnInUse(Pfn1)) continue;
301 if ((Pfn1->u3.e1.PageLocation == ZeroedPageList) != LookForZeroedPages) continue;
302
303 //
304 // Sanity checks
305 //
306 ASSERT(Pfn1->u3.e2.ReferenceCount == 0);
307
308 //
309 // Now setup the page and mark it
310 //
311 Pfn1->u3.e2.ReferenceCount = 1;
312 Pfn1->u3.e1.StartOfAllocation = 1;
313 Pfn1->u3.e1.EndOfAllocation = 1;
314
315 //
316 // Decrease available pages
317 //
318 MmAvailablePages--;
319
320 //
321 // Save this page into the MDL
322 //
323 *MdlPage++ = Page;
324 if (++PagesFound == PageCount) break;
325 }
326
327 //
328 // If the first pass was enough, don't keep going, otherwise, go again
329 //
330 if (PagesFound == PageCount) break;
331 }
332 }
333
334 //
335 // Now release the PFN count
336 //
337 KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
338
339 //
340 // We might've found less pages, but not more ;-)
341 //
342 if (PagesFound != PageCount) ASSERT(PagesFound < PageCount);
343 if (!PagesFound)
344 {
345 //
346 // If we didn' tfind any pages at all, fail
347 //
348 DPRINT1("NO MDL PAGES!\n");
349 ExFreePool(Mdl);
350 return NULL;
351 }
352
353 //
354 // Write out how many pages we found
355 //
356 Mdl->ByteCount = (ULONG)(PagesFound << PAGE_SHIFT);
357
358 //
359 // Terminate the MDL array if there's certain missing pages
360 //
361 if (PagesFound != PageCount) *MdlPage = -1;
362
363 //
364 // Now go back and loop over all the MDL pages
365 //
366 MdlPage = (PPFN_NUMBER)(Mdl + 1);
367 LastMdlPage = MdlPage + PagesFound;
368 while (MdlPage < LastMdlPage)
369 {
370 //
371 // Check if we've reached the end
372 //
373 Page = *MdlPage++;
374 if (Page == (PFN_NUMBER)-1) break;
375
376 //
377 // Get the PFN entry for the page and check if we should zero it out
378 //
379 Pfn1 = MiGetPfnEntry(Page);
380 ASSERT(Pfn1);
381 if (Pfn1->u3.e1.PageLocation != ZeroedPageList) MiZeroPage(Page);
382 Pfn1->u3.e1.PageLocation = ActiveAndValid;
383 }
384
385 //
386 // We're done, mark the pages as locked (should we lock them, though???)
387 //
388 Mdl->Process = NULL;
389 Mdl->MdlFlags |= MDL_PAGES_LOCKED;
390 return Mdl;
391 }
392
393 VOID
394 NTAPI
395 MmDumpPfnDatabase(VOID)
396 {
397 ULONG i;
398 PPHYSICAL_PAGE Pfn1;
399 PCHAR State = "????", Type = "Unknown";
400 KIRQL OldIrql;
401 ULONG Totals[5] = {0}, FreePages = 0;
402
403 KeRaiseIrql(HIGH_LEVEL, &OldIrql);
404
405 //
406 // Loop the PFN database
407 //
408 for (i = 0; i <= MmHighestPhysicalPage; i++)
409 {
410 Pfn1 = MiGetPfnEntry(i);
411 if (!Pfn1) continue;
412
413 //
414 // Get the type
415 //
416 if (MiIsPfnInUse(Pfn1))
417 {
418 State = "Used";
419 }
420 else
421 {
422 State = "Free";
423 Type = "Free";
424 FreePages++;
425 break;
426 }
427
428 //
429 // Pretty-print the page
430 //
431 DbgPrint("0x%08p:\t%04s\t%20s\t(%02d) [%08p])\n",
432 i << PAGE_SHIFT,
433 State,
434 Type,
435 Pfn1->u3.e2.ReferenceCount,
436 Pfn1->RmapListHead);
437 }
438
439 DbgPrint("Nonpaged Pool: %d pages\t[%d KB]\n", Totals[MC_NPPOOL], (Totals[MC_NPPOOL] << PAGE_SHIFT) / 1024);
440 DbgPrint("Paged Pool: %d pages\t[%d KB]\n", Totals[MC_PPOOL], (Totals[MC_PPOOL] << PAGE_SHIFT) / 1024);
441 DbgPrint("File System Cache: %d pages\t[%d KB]\n", Totals[MC_CACHE], (Totals[MC_CACHE] << PAGE_SHIFT) / 1024);
442 DbgPrint("Process Working Set: %d pages\t[%d KB]\n", Totals[MC_USER], (Totals[MC_USER] << PAGE_SHIFT) / 1024);
443 DbgPrint("System: %d pages\t[%d KB]\n", Totals[MC_SYSTEM], (Totals[MC_SYSTEM] << PAGE_SHIFT) / 1024);
444 DbgPrint("Free: %d pages\t[%d KB]\n", FreePages, (FreePages << PAGE_SHIFT) / 1024);
445
446 KeLowerIrql(OldIrql);
447 }
448
449 VOID
450 NTAPI
451 MmSetRmapListHeadPage(PFN_NUMBER Pfn, struct _MM_RMAP_ENTRY* ListHead)
452 {
453 KIRQL oldIrql;
454
455 oldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
456 MiGetPfnEntry(Pfn)->RmapListHead = (LONG)ListHead;
457 KeReleaseQueuedSpinLock(LockQueuePfnLock, oldIrql);
458 }
459
460 struct _MM_RMAP_ENTRY*
461 NTAPI
462 MmGetRmapListHeadPage(PFN_NUMBER Pfn)
463 {
464 KIRQL oldIrql;
465 struct _MM_RMAP_ENTRY* ListHead;
466
467 oldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
468 ListHead = (struct _MM_RMAP_ENTRY*)MiGetPfnEntry(Pfn)->RmapListHead;
469 KeReleaseQueuedSpinLock(LockQueuePfnLock, oldIrql);
470
471 return(ListHead);
472 }
473
474 VOID
475 NTAPI
476 MmSetSavedSwapEntryPage(PFN_NUMBER Pfn, SWAPENTRY SwapEntry)
477 {
478 KIRQL oldIrql;
479
480 oldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
481 MiGetPfnEntry(Pfn)->u1.WsIndex = SwapEntry;
482 KeReleaseQueuedSpinLock(LockQueuePfnLock, oldIrql);
483 }
484
485 SWAPENTRY
486 NTAPI
487 MmGetSavedSwapEntryPage(PFN_NUMBER Pfn)
488 {
489 SWAPENTRY SwapEntry;
490 KIRQL oldIrql;
491
492 oldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
493 SwapEntry = MiGetPfnEntry(Pfn)->u1.WsIndex;
494 KeReleaseQueuedSpinLock(LockQueuePfnLock, oldIrql);
495
496 return(SwapEntry);
497 }
498
499 VOID
500 NTAPI
501 MmReferencePage(PFN_NUMBER Pfn)
502 {
503 PPHYSICAL_PAGE Page;
504
505 DPRINT("MmReferencePage(PysicalAddress %x)\n", Pfn << PAGE_SHIFT);
506
507 if (Pfn == 0 || Pfn > MmHighestPhysicalPage)
508 {
509 return;
510 }
511
512 Page = MiGetPfnEntry(Pfn);
513 ASSERT(Page);
514
515 Page->u3.e2.ReferenceCount++;
516 }
517
518 ULONG
519 NTAPI
520 MmGetReferenceCountPage(PFN_NUMBER Pfn)
521 {
522 KIRQL oldIrql;
523 ULONG RCount;
524 PPHYSICAL_PAGE Page;
525
526 DPRINT("MmGetReferenceCountPage(PhysicalAddress %x)\n", Pfn << PAGE_SHIFT);
527
528 oldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
529 Page = MiGetPfnEntry(Pfn);
530 ASSERT(Page);
531
532 RCount = Page->u3.e2.ReferenceCount;
533
534 KeReleaseQueuedSpinLock(LockQueuePfnLock, oldIrql);
535 return(RCount);
536 }
537
538 BOOLEAN
539 NTAPI
540 MmIsPageInUse(PFN_NUMBER Pfn)
541 {
542 return MiIsPfnInUse(MiGetPfnEntry(Pfn));
543 }
544
545 VOID
546 NTAPI
547 MiSetConsumer(IN PFN_NUMBER Pfn,
548 IN ULONG Type)
549 {
550 MiGetPfnEntry(Pfn)->u3.e1.PageLocation = ActiveAndValid;
551 }
552
553 VOID
554 NTAPI
555 MmDereferencePage(PFN_NUMBER Pfn)
556 {
557 PPHYSICAL_PAGE Page;
558
559 DPRINT("MmDereferencePage(PhysicalAddress %x)\n", Pfn << PAGE_SHIFT);
560
561 Page = MiGetPfnEntry(Pfn);
562 ASSERT(Page);
563
564 Page->u3.e2.ReferenceCount--;
565 if (Page->u3.e2.ReferenceCount == 0)
566 {
567 MmAvailablePages++;
568 Page->u3.e1.PageLocation = FreePageList;
569 MiInsertInListTail(&MmFreePageListHead, Page);
570 if (MmFreePageListHead.Total > 8 && 0 == KeReadStateEvent(&ZeroPageThreadEvent))
571 {
572 KeSetEvent(&ZeroPageThreadEvent, IO_NO_INCREMENT, FALSE);
573 }
574 }
575 }
576
577 PFN_NUMBER
578 NTAPI
579 MmAllocPage(ULONG Type)
580 {
581 PFN_NUMBER PfnOffset;
582 PPHYSICAL_PAGE PageDescriptor;
583 BOOLEAN NeedClear = FALSE;
584
585 DPRINT("MmAllocPage()\n");
586
587 if (MmZeroedPageListHead.Total == 0)
588 {
589 if (MmFreePageListHead.Total == 0)
590 {
591 /* Check if this allocation is for the PFN DB itself */
592 if (MmNumberOfPhysicalPages == 0)
593 {
594 ASSERT(FALSE);
595 }
596
597 DPRINT1("MmAllocPage(): Out of memory\n");
598 return 0;
599 }
600 PageDescriptor = MiRemoveHeadList(&MmFreePageListHead);
601
602 NeedClear = TRUE;
603 }
604 else
605 {
606 PageDescriptor = MiRemoveHeadList(&MmZeroedPageListHead);
607 }
608
609 PageDescriptor->u3.e2.ReferenceCount = 1;
610
611 MmAvailablePages--;
612
613 PfnOffset = MiGetPfnEntryIndex(PageDescriptor);
614 if ((NeedClear) && (Type != MC_SYSTEM))
615 {
616 MiZeroPage(PfnOffset);
617 }
618
619 PageDescriptor->u3.e1.PageLocation = ActiveAndValid;
620 return PfnOffset;
621 }
622
623 NTSTATUS
624 NTAPI
625 MiZeroPage(PFN_NUMBER Page)
626 {
627 KIRQL Irql;
628 PVOID TempAddress;
629
630 Irql = KeRaiseIrqlToDpcLevel();
631 TempAddress = MiMapPageToZeroInHyperSpace(Page);
632 if (TempAddress == NULL)
633 {
634 return(STATUS_NO_MEMORY);
635 }
636 memset(TempAddress, 0, PAGE_SIZE);
637 MiUnmapPagesInZeroSpace(TempAddress, 1);
638 KeLowerIrql(Irql);
639 return(STATUS_SUCCESS);
640 }
641
642 NTSTATUS
643 NTAPI
644 MmZeroPageThreadMain(PVOID Ignored)
645 {
646 NTSTATUS Status;
647 KIRQL oldIrql;
648 PPHYSICAL_PAGE PageDescriptor;
649 PFN_NUMBER Pfn;
650 ULONG Count;
651
652 /* Free initial kernel memory */
653 //MiFreeInitMemory();
654
655 /* Set our priority to 0 */
656 KeGetCurrentThread()->BasePriority = 0;
657 KeSetPriorityThread(KeGetCurrentThread(), 0);
658
659 while(1)
660 {
661 Status = KeWaitForSingleObject(&ZeroPageThreadEvent,
662 0,
663 KernelMode,
664 FALSE,
665 NULL);
666
667 if (ZeroPageThreadShouldTerminate)
668 {
669 DPRINT1("ZeroPageThread: Terminating\n");
670 return STATUS_SUCCESS;
671 }
672 Count = 0;
673 oldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
674 while (MmFreePageListHead.Total)
675 {
676 PageDescriptor = MiRemoveHeadList(&MmFreePageListHead);
677 /* We set the page to used, because MmCreateVirtualMapping failed with unused pages */
678 KeReleaseQueuedSpinLock(LockQueuePfnLock, oldIrql);
679 Pfn = MiGetPfnEntryIndex(PageDescriptor);
680 Status = MiZeroPage(Pfn);
681
682 oldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
683 if (NT_SUCCESS(Status))
684 {
685 MiInsertZeroListAtBack(Pfn);
686 Count++;
687 }
688 else
689 {
690 MiInsertInListTail(&MmFreePageListHead, PageDescriptor);
691 PageDescriptor->u3.e1.PageLocation = FreePageList;
692 }
693
694 }
695 DPRINT("Zeroed %d pages.\n", Count);
696 KeResetEvent(&ZeroPageThreadEvent);
697 KeReleaseQueuedSpinLock(LockQueuePfnLock, oldIrql);
698 }
699
700 return STATUS_SUCCESS;
701 }
702
703 /* EOF */