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"
19 /* TYPES ********************************************************************/
20 typedef struct _MM_ALLOCATION_REQUEST
26 MM_ALLOCATION_REQUEST
, *PMM_ALLOCATION_REQUEST
;
27 /* GLOBALS ******************************************************************/
29 MM_MEMORY_CONSUMER MiMemoryConsumers
[MC_MAXIMUM
];
30 static ULONG MiMinimumAvailablePages
;
31 static LIST_ENTRY AllocationListHead
;
32 static KSPIN_LOCK AllocationListLock
;
33 static ULONG MiMinimumPagesPerRun
;
35 static CLIENT_ID MiBalancerThreadId
;
36 static HANDLE MiBalancerThreadHandle
= NULL
;
37 static KEVENT MiBalancerEvent
;
38 static KTIMER MiBalancerTimer
;
40 /* FUNCTIONS ****************************************************************/
45 MmInitializeBalancer(ULONG NrAvailablePages
, ULONG NrSystemPages
)
47 memset(MiMemoryConsumers
, 0, sizeof(MiMemoryConsumers
));
48 InitializeListHead(&AllocationListHead
);
49 KeInitializeSpinLock(&AllocationListLock
);
52 MiMinimumAvailablePages
= 256;
53 MiMinimumPagesPerRun
= 256;
54 MiMemoryConsumers
[MC_USER
].PagesTarget
= NrAvailablePages
/ 2;
60 MmInitializeMemoryConsumer(
62 NTSTATUS (*Trim
)(ULONG Target
, ULONG Priority
, PULONG NrFreed
))
64 MiMemoryConsumers
[Consumer
].Trim
= Trim
;
70 IN PFN_NUMBER PageFrameIndex
75 MmReleasePageMemoryConsumer(ULONG Consumer
, PFN_NUMBER Page
)
79 DPRINT1("Tried to release page zero.\n");
80 KeBugCheck(MEMORY_MANAGEMENT
);
83 (void)InterlockedDecrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
85 MmDereferencePage(Page
);
87 return(STATUS_SUCCESS
);
92 MiTrimMemoryConsumer(ULONG Consumer
, ULONG InitialTarget
)
94 ULONG Target
= InitialTarget
;
95 ULONG NrFreedPages
= 0;
98 /* Make sure we can trim this consumer */
99 if (!MiMemoryConsumers
[Consumer
].Trim
)
101 /* Return the unmodified initial target */
102 return InitialTarget
;
105 if (MiMemoryConsumers
[Consumer
].PagesUsed
> MiMemoryConsumers
[Consumer
].PagesTarget
)
107 /* Consumer page limit exceeded */
108 Target
= max(Target
, MiMemoryConsumers
[Consumer
].PagesUsed
- MiMemoryConsumers
[Consumer
].PagesTarget
);
110 if (MmAvailablePages
< MiMinimumAvailablePages
)
112 /* Global page limit exceeded */
113 Target
= (ULONG
)max(Target
, MiMinimumAvailablePages
- MmAvailablePages
);
116 /* Don't be too greedy in one run */
117 Target
= min(Target
, 256);
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 (MiBalancerThreadHandle
!= NULL
&&
269 !MiIsBalancerThread())
271 KeSetEvent(&MiBalancerEvent
, IO_NO_INCREMENT
, FALSE
);
277 MmRequestPageMemoryConsumer(ULONG Consumer
, BOOLEAN CanWait
,
278 PPFN_NUMBER AllocatedPage
)
282 /* Update the target */
283 InterlockedIncrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
286 * Actually allocate the page.
288 Page
= MmAllocPage(Consumer
);
291 KeBugCheck(NO_PAGES_AVAILABLE
);
293 *AllocatedPage
= Page
;
295 return(STATUS_SUCCESS
);
300 MiBalancerThread(PVOID Unused
)
302 PVOID WaitObjects
[2];
306 WaitObjects
[0] = &MiBalancerEvent
;
307 WaitObjects
[1] = &MiBalancerTimer
;
311 Status
= KeWaitForMultipleObjects(2,
320 if (Status
== STATUS_WAIT_0
|| Status
== STATUS_WAIT_1
)
322 ULONG InitialTarget
= 0;
324 #if (_MI_PAGING_LEVELS == 2)
325 if (!MiIsBalancerThread())
327 /* Clean up the unused PDEs */
329 PEPROCESS Process
= PsGetCurrentProcess();
331 /* Acquire PFN lock */
332 KIRQL OldIrql
= MiAcquirePfnLock();
334 for (Address
= (ULONG_PTR
)MI_LOWEST_VAD_ADDRESS
;
335 Address
< (ULONG_PTR
)MM_HIGHEST_VAD_ADDRESS
;
336 Address
+= PTE_PER_PAGE
* PAGE_SIZE
)
338 if (MiQueryPageTableReferences((PVOID
)Address
) == 0)
340 pointerPde
= MiAddressToPde(Address
);
341 if (pointerPde
->u
.Hard
.Valid
)
342 MiDeletePte(pointerPde
, MiPdeToPte(pointerPde
), Process
, NULL
);
343 ASSERT(pointerPde
->u
.Hard
.Valid
== 0);
347 MiReleasePfnLock(OldIrql
);
352 ULONG OldTarget
= InitialTarget
;
354 /* Trim each consumer */
355 for (i
= 0; i
< MC_MAXIMUM
; i
++)
357 InitialTarget
= MiTrimMemoryConsumer(i
, InitialTarget
);
360 /* No pages left to swap! */
361 if (InitialTarget
!= 0 &&
362 InitialTarget
== OldTarget
)
365 KeBugCheck(NO_PAGES_AVAILABLE
);
368 while (InitialTarget
!= 0);
372 DPRINT1("KeWaitForMultipleObjects failed, status = %x\n", Status
);
373 KeBugCheck(MEMORY_MANAGEMENT
);
378 BOOLEAN
MmRosNotifyAvailablePage(PFN_NUMBER Page
)
381 PMM_ALLOCATION_REQUEST Request
;
384 /* Make sure the PFN lock is held */
385 MI_ASSERT_PFN_LOCK_HELD();
387 if (!MiMinimumAvailablePages
)
389 /* Dirty way to know if we were initialized. */
393 Entry
= ExInterlockedRemoveHeadList(&AllocationListHead
, &AllocationListLock
);
397 Request
= CONTAINING_RECORD(Entry
, MM_ALLOCATION_REQUEST
, ListEntry
);
398 MiZeroPhysicalPage(Page
);
399 Request
->Page
= Page
;
401 Pfn1
= MiGetPfnEntry(Page
);
402 ASSERT(Pfn1
->u3
.e2
.ReferenceCount
== 0);
403 Pfn1
->u3
.e2
.ReferenceCount
= 1;
404 Pfn1
->u3
.e1
.PageLocation
= ActiveAndValid
;
406 /* This marks the PFN as a ReactOS PFN */
407 Pfn1
->u4
.AweAllocation
= TRUE
;
409 /* Allocate the extra ReactOS Data and zero it out */
410 Pfn1
->u1
.SwapEntry
= 0;
411 Pfn1
->RmapListHead
= NULL
;
413 KeSetEvent(&Request
->Event
, IO_NO_INCREMENT
, FALSE
);
421 MiInitBalancerThread(VOID
)
425 LARGE_INTEGER Timeout
;
427 KeInitializeEvent(&MiBalancerEvent
, SynchronizationEvent
, FALSE
);
428 KeInitializeTimerEx(&MiBalancerTimer
, SynchronizationTimer
);
430 Timeout
.QuadPart
= -20000000; /* 2 sec */
431 KeSetTimerEx(&MiBalancerTimer
,
436 Status
= PsCreateSystemThread(&MiBalancerThreadHandle
,
443 if (!NT_SUCCESS(Status
))
445 KeBugCheck(MEMORY_MANAGEMENT
);
448 Priority
= LOW_REALTIME_PRIORITY
+ 1;
449 NtSetInformationThread(MiBalancerThreadHandle
,