2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS kernel
4 * FILE: ntoskrnl/mm/balance.c
5 * PURPOSE: kernel memory managment functions
7 * PROGRAMMERS: David Welch (welch@cwcom.net)
8 * Cameron Gutman (cameron.gutman@reactos.org)
11 /* INCLUDES *****************************************************************/
17 #include "ARM3/miarm.h"
20 /* TYPES ********************************************************************/
21 typedef struct _MM_ALLOCATION_REQUEST
27 MM_ALLOCATION_REQUEST
, *PMM_ALLOCATION_REQUEST
;
28 /* GLOBALS ******************************************************************/
30 MM_MEMORY_CONSUMER MiMemoryConsumers
[MC_MAXIMUM
];
31 static ULONG MiMinimumAvailablePages
;
32 static LIST_ENTRY AllocationListHead
;
33 static KSPIN_LOCK AllocationListLock
;
34 static ULONG MiMinimumPagesPerRun
;
36 static CLIENT_ID MiBalancerThreadId
;
37 static HANDLE MiBalancerThreadHandle
= NULL
;
38 static KEVENT MiBalancerEvent
;
39 static KTIMER MiBalancerTimer
;
41 static LONG PageOutThreadActive
;
43 /* FUNCTIONS ****************************************************************/
48 MmInitializeBalancer(ULONG NrAvailablePages
, ULONG NrSystemPages
)
50 memset(MiMemoryConsumers
, 0, sizeof(MiMemoryConsumers
));
51 InitializeListHead(&AllocationListHead
);
52 KeInitializeSpinLock(&AllocationListLock
);
55 MiMinimumAvailablePages
= 256;
56 MiMinimumPagesPerRun
= 256;
57 MiMemoryConsumers
[MC_USER
].PagesTarget
= NrAvailablePages
/ 2;
63 MmInitializeMemoryConsumer(
65 NTSTATUS (*Trim
)(ULONG Target
, ULONG Priority
, PULONG NrFreed
))
67 MiMemoryConsumers
[Consumer
].Trim
= Trim
;
73 IN PFN_NUMBER PageFrameIndex
78 MmReleasePageMemoryConsumer(ULONG Consumer
, PFN_NUMBER Page
)
82 DPRINT1("Tried to release page zero.\n");
83 KeBugCheck(MEMORY_MANAGEMENT
);
86 (void)InterlockedDecrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
88 MmDereferencePage(Page
);
90 return(STATUS_SUCCESS
);
95 MiTrimMemoryConsumer(ULONG Consumer
, ULONG InitialTarget
)
97 ULONG Target
= InitialTarget
;
98 ULONG NrFreedPages
= 0;
101 /* Make sure we can trim this consumer */
102 if (!MiMemoryConsumers
[Consumer
].Trim
)
104 /* Return the unmodified initial target */
105 return InitialTarget
;
108 if (MmAvailablePages
< MiMinimumAvailablePages
)
110 /* Global page limit exceeded */
111 Target
= (ULONG
)max(Target
, MiMinimumAvailablePages
- MmAvailablePages
);
113 else if (MiMemoryConsumers
[Consumer
].PagesUsed
> MiMemoryConsumers
[Consumer
].PagesTarget
)
115 /* Consumer page limit exceeded */
116 Target
= max(Target
, MiMemoryConsumers
[Consumer
].PagesUsed
- MiMemoryConsumers
[Consumer
].PagesTarget
);
121 /* Now swap the pages out */
122 Status
= MiMemoryConsumers
[Consumer
].Trim(Target
, MmAvailablePages
< MiMinimumAvailablePages
, &NrFreedPages
);
124 DPRINT("Trimming consumer %lu: Freed %lu pages with a target of %lu pages\n", Consumer
, NrFreedPages
, Target
);
126 if (!NT_SUCCESS(Status
))
128 KeBugCheck(MEMORY_MANAGEMENT
);
132 /* Return the page count needed to be freed to meet the initial target */
133 return (InitialTarget
> NrFreedPages
) ? (InitialTarget
- NrFreedPages
) : 0;
137 MmTrimUserMemory(ULONG Target
, ULONG Priority
, PULONG NrFreedPages
)
139 PFN_NUMBER CurrentPage
;
144 DPRINT1("MM BALANCER: %s\n", Priority
? "Paging out!" : "Removing access bit!");
146 CurrentPage
= MmGetLRUFirstUserPage();
147 while (CurrentPage
!= 0 && Target
> 0)
151 Status
= MmPageOutPhysicalAddress(CurrentPage
);
152 if (NT_SUCCESS(Status
))
154 DPRINT("Succeeded\n");
161 /* When not paging-out agressively, just reset the accessed bit */
162 PEPROCESS Process
= NULL
;
163 PVOID Address
= NULL
;
164 BOOLEAN Accessed
= FALSE
;
167 * We have a lock-ordering problem here. We cant lock the PFN DB before the Process address space.
168 * So we must use circonvoluted loops.
174 KIRQL OldIrql
= MiAcquirePfnLock();
175 PMM_RMAP_ENTRY Entry
= MmGetRmapListHeadPage(CurrentPage
);
178 if (RMAP_IS_SEGMENT(Entry
->Address
))
184 /* Check that we didn't treat this entry before */
185 if (Entry
->Address
< Address
)
191 if ((Entry
->Address
== Address
) && (Entry
->Process
<= Process
))
202 MiReleasePfnLock(OldIrql
);
206 Process
= Entry
->Process
;
207 Address
= Entry
->Address
;
209 MiReleasePfnLock(OldIrql
);
211 KeStackAttachProcess(&Process
->Pcb
, &ApcState
);
213 MmLockAddressSpace(&Process
->Vm
);
215 /* Be sure this is still valid. */
216 PMMPTE Pte
= MiAddressToPte(Address
);
217 if (Pte
->u
.Hard
.Valid
)
219 Accessed
= Accessed
|| Pte
->u
.Hard
.Accessed
;
220 Pte
->u
.Hard
.Accessed
= 0;
222 /* There is no need to invalidate, the balancer thread is never on a user process */
223 //KeInvalidateTlbEntry(Address);
226 MmUnlockAddressSpace(&Process
->Vm
);
228 KeUnstackDetachProcess(&ApcState
);
233 /* Nobody accessed this page since the last time we check. Time to clean up */
235 Status
= MmPageOutPhysicalAddress(CurrentPage
);
236 // DPRINT1("Paged-out one page: %s\n", NT_SUCCESS(Status) ? "Yes" : "No");
240 /* Done for this page. */
244 CurrentPage
= MmGetLRUNextUserPage(CurrentPage
, TRUE
);
249 KIRQL OldIrql
= MiAcquirePfnLock();
250 MmDereferencePage(CurrentPage
);
251 MiReleasePfnLock(OldIrql
);
254 return STATUS_SUCCESS
;
258 MiIsBalancerThread(VOID
)
260 return (MiBalancerThreadHandle
!= NULL
) &&
261 (PsGetCurrentThreadId() == MiBalancerThreadId
.UniqueThread
);
266 MmRebalanceMemoryConsumers(VOID
)
268 // if (InterlockedCompareExchange(&PageOutThreadActive, 0, 1) == 0)
270 KeSetEvent(&MiBalancerEvent
, IO_NO_INCREMENT
, FALSE
);
276 MmRequestPageMemoryConsumer(ULONG Consumer
, BOOLEAN CanWait
,
277 PPFN_NUMBER AllocatedPage
)
281 /* Update the target */
282 InterlockedIncrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
285 * Actually allocate the page.
287 Page
= MmAllocPage(Consumer
);
290 KeBugCheck(NO_PAGES_AVAILABLE
);
292 *AllocatedPage
= Page
;
294 return(STATUS_SUCCESS
);
299 MiBalancerThread(PVOID Unused
)
301 PVOID WaitObjects
[2];
305 WaitObjects
[0] = &MiBalancerEvent
;
306 WaitObjects
[1] = &MiBalancerTimer
;
310 Status
= KeWaitForMultipleObjects(2,
319 if (Status
== STATUS_WAIT_0
|| Status
== STATUS_WAIT_1
)
321 ULONG InitialTarget
= 0;
323 #if (_MI_PAGING_LEVELS == 2)
324 if (!MiIsBalancerThread())
326 /* Clean up the unused PDEs */
328 PEPROCESS Process
= PsGetCurrentProcess();
330 /* Acquire PFN lock */
331 KIRQL OldIrql
= MiAcquirePfnLock();
333 for (Address
= (ULONG_PTR
)MI_LOWEST_VAD_ADDRESS
;
334 Address
< (ULONG_PTR
)MM_HIGHEST_VAD_ADDRESS
;
335 Address
+= PTE_PER_PAGE
* PAGE_SIZE
)
337 if (MiQueryPageTableReferences((PVOID
)Address
) == 0)
339 pointerPde
= MiAddressToPde(Address
);
340 if (pointerPde
->u
.Hard
.Valid
)
341 MiDeletePte(pointerPde
, MiPdeToPte(pointerPde
), Process
, NULL
);
342 ASSERT(pointerPde
->u
.Hard
.Valid
== 0);
346 MiReleasePfnLock(OldIrql
);
351 ULONG OldTarget
= InitialTarget
;
353 /* Trim each consumer */
354 for (i
= 0; i
< MC_MAXIMUM
; i
++)
356 InitialTarget
= MiTrimMemoryConsumer(i
, InitialTarget
);
359 /* No pages left to swap! */
360 if (InitialTarget
!= 0 &&
361 InitialTarget
== OldTarget
)
364 KeBugCheck(NO_PAGES_AVAILABLE
);
367 while (InitialTarget
!= 0);
369 if (Status
== STATUS_WAIT_0
)
370 InterlockedDecrement(&PageOutThreadActive
);
374 DPRINT1("KeWaitForMultipleObjects failed, status = %x\n", Status
);
375 KeBugCheck(MEMORY_MANAGEMENT
);
380 BOOLEAN
MmRosNotifyAvailablePage(PFN_NUMBER Page
)
383 PMM_ALLOCATION_REQUEST Request
;
386 /* Make sure the PFN lock is held */
387 MI_ASSERT_PFN_LOCK_HELD();
389 if (!MiMinimumAvailablePages
)
391 /* Dirty way to know if we were initialized. */
395 Entry
= ExInterlockedRemoveHeadList(&AllocationListHead
, &AllocationListLock
);
399 Request
= CONTAINING_RECORD(Entry
, MM_ALLOCATION_REQUEST
, ListEntry
);
400 MiZeroPhysicalPage(Page
);
401 Request
->Page
= Page
;
403 Pfn1
= MiGetPfnEntry(Page
);
404 ASSERT(Pfn1
->u3
.e2
.ReferenceCount
== 0);
405 Pfn1
->u3
.e2
.ReferenceCount
= 1;
406 Pfn1
->u3
.e1
.PageLocation
= ActiveAndValid
;
408 /* This marks the PFN as a ReactOS PFN */
409 Pfn1
->u4
.AweAllocation
= TRUE
;
411 /* Allocate the extra ReactOS Data and zero it out */
412 Pfn1
->u1
.SwapEntry
= 0;
413 Pfn1
->RmapListHead
= NULL
;
415 KeSetEvent(&Request
->Event
, IO_NO_INCREMENT
, FALSE
);
423 MiInitBalancerThread(VOID
)
427 LARGE_INTEGER Timeout
;
429 KeInitializeEvent(&MiBalancerEvent
, SynchronizationEvent
, FALSE
);
430 KeInitializeTimerEx(&MiBalancerTimer
, SynchronizationTimer
);
432 Timeout
.QuadPart
= -20000000; /* 2 sec */
433 KeSetTimerEx(&MiBalancerTimer
,
438 Status
= PsCreateSystemThread(&MiBalancerThreadHandle
,
445 if (!NT_SUCCESS(Status
))
447 KeBugCheck(MEMORY_MANAGEMENT
);
450 Priority
= LOW_REALTIME_PRIORITY
+ 1;
451 NtSetInformationThread(MiBalancerThreadHandle
,