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 #if defined (ALLOC_PRAGMA)
20 #pragma alloc_text(INIT, MmInitializeBalancer)
21 #pragma alloc_text(INIT, MmInitializeMemoryConsumer)
22 #pragma alloc_text(INIT, MiInitBalancerThread)
26 /* TYPES ********************************************************************/
27 typedef struct _MM_ALLOCATION_REQUEST
33 MM_ALLOCATION_REQUEST
, *PMM_ALLOCATION_REQUEST
;
34 /* GLOBALS ******************************************************************/
36 MM_MEMORY_CONSUMER MiMemoryConsumers
[MC_MAXIMUM
];
37 static ULONG MiMinimumAvailablePages
;
38 static ULONG MiNrTotalPages
;
39 static LIST_ENTRY AllocationListHead
;
40 static KSPIN_LOCK AllocationListLock
;
41 static ULONG MiMinimumPagesPerRun
;
43 static CLIENT_ID MiBalancerThreadId
;
44 static HANDLE MiBalancerThreadHandle
= NULL
;
45 static KEVENT MiBalancerEvent
;
46 static KTIMER MiBalancerTimer
;
48 /* FUNCTIONS ****************************************************************/
53 MmInitializeBalancer(ULONG NrAvailablePages
, ULONG NrSystemPages
)
55 memset(MiMemoryConsumers
, 0, sizeof(MiMemoryConsumers
));
56 InitializeListHead(&AllocationListHead
);
57 KeInitializeSpinLock(&AllocationListLock
);
59 MiNrTotalPages
= NrAvailablePages
;
62 MiMinimumAvailablePages
= 128;
63 MiMinimumPagesPerRun
= 256;
64 if ((NrAvailablePages
+ NrSystemPages
) >= 8192)
66 MiMemoryConsumers
[MC_CACHE
].PagesTarget
= NrAvailablePages
/ 4 * 3;
68 else if ((NrAvailablePages
+ NrSystemPages
) >= 4096)
70 MiMemoryConsumers
[MC_CACHE
].PagesTarget
= NrAvailablePages
/ 3 * 2;
74 MiMemoryConsumers
[MC_CACHE
].PagesTarget
= NrAvailablePages
/ 8;
76 MiMemoryConsumers
[MC_USER
].PagesTarget
= NrAvailablePages
- MiMinimumAvailablePages
;
82 MmInitializeMemoryConsumer(
84 NTSTATUS (*Trim
)(ULONG Target
, ULONG Priority
, PULONG NrFreed
))
86 MiMemoryConsumers
[Consumer
].Trim
= Trim
;
92 IN PFN_NUMBER PageFrameIndex
97 MmReleasePageMemoryConsumer(ULONG Consumer
, PFN_NUMBER Page
)
99 PMM_ALLOCATION_REQUEST Request
;
104 DPRINT1("Tried to release page zero.\n");
105 KeBugCheck(MEMORY_MANAGEMENT
);
108 if (MmGetReferenceCountPage(Page
) == 1)
110 if(Consumer
== MC_USER
) MmRemoveLRUUserPage(Page
);
111 (void)InterlockedDecrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
112 if ((Entry
= ExInterlockedRemoveHeadList(&AllocationListHead
, &AllocationListLock
)) == NULL
)
114 MmDereferencePage(Page
);
118 Request
= CONTAINING_RECORD(Entry
, MM_ALLOCATION_REQUEST
, ListEntry
);
119 MiZeroPhysicalPage(Page
);
120 Request
->Page
= Page
;
121 KeSetEvent(&Request
->Event
, IO_NO_INCREMENT
, FALSE
);
126 MmDereferencePage(Page
);
129 return(STATUS_SUCCESS
);
134 MiTrimMemoryConsumer(ULONG Consumer
, ULONG InitialTarget
)
136 ULONG Target
= InitialTarget
;
137 ULONG NrFreedPages
= 0;
140 /* Make sure we can trim this consumer */
141 if (!MiMemoryConsumers
[Consumer
].Trim
)
143 /* Return the unmodified initial target */
144 return InitialTarget
;
147 if (MiMemoryConsumers
[Consumer
].PagesUsed
> MiMemoryConsumers
[Consumer
].PagesTarget
)
149 /* Consumer page limit exceeded */
150 Target
= max(Target
, MiMemoryConsumers
[Consumer
].PagesUsed
- MiMemoryConsumers
[Consumer
].PagesTarget
);
152 if (MmAvailablePages
< MiMinimumAvailablePages
)
154 /* Global page limit exceeded */
155 Target
= (ULONG
)max(Target
, MiMinimumAvailablePages
- MmAvailablePages
);
162 /* If there was no initial target,
163 * swap at least MiMinimumPagesPerRun */
164 Target
= max(Target
, MiMinimumPagesPerRun
);
167 /* Now swap the pages out */
168 Status
= MiMemoryConsumers
[Consumer
].Trim(Target
, 0, &NrFreedPages
);
170 DPRINT("Trimming consumer %lu: Freed %lu pages with a target of %lu pages\n", Consumer
, NrFreedPages
, Target
);
172 if (!NT_SUCCESS(Status
))
174 KeBugCheck(MEMORY_MANAGEMENT
);
177 /* Update the target */
178 if (NrFreedPages
< Target
)
179 Target
-= NrFreedPages
;
183 /* Return the remaining pages needed to meet the target */
188 /* Initial target is zero and we don't have anything else to add */
194 MmTrimUserMemory(ULONG Target
, ULONG Priority
, PULONG NrFreedPages
)
196 PFN_NUMBER CurrentPage
;
202 CurrentPage
= MmGetLRUFirstUserPage();
203 while (CurrentPage
!= 0 && Target
> 0)
205 Status
= MmPageOutPhysicalAddress(CurrentPage
);
206 if (NT_SUCCESS(Status
))
208 DPRINT("Succeeded\n");
213 NextPage
= MmGetLRUNextUserPage(CurrentPage
);
214 if (NextPage
<= CurrentPage
)
216 /* We wrapped around, so we're done */
219 CurrentPage
= NextPage
;
222 return STATUS_SUCCESS
;
226 MiIsBalancerThread(VOID
)
228 return (MiBalancerThreadHandle
!= NULL
) &&
229 (PsGetCurrentThreadId() == MiBalancerThreadId
.UniqueThread
);
234 MiDeletePte(IN PMMPTE PointerPte
,
235 IN PVOID VirtualAddress
,
236 IN PEPROCESS CurrentProcess
,
237 IN PMMPTE PrototypePte
);
241 MmRebalanceMemoryConsumers(VOID
)
243 if (MiBalancerThreadHandle
!= NULL
&&
244 !MiIsBalancerThread())
246 KeSetEvent(&MiBalancerEvent
, IO_NO_INCREMENT
, FALSE
);
252 MmRequestPageMemoryConsumer(ULONG Consumer
, BOOLEAN CanWait
,
253 PPFN_NUMBER AllocatedPage
)
259 * Make sure we don't exceed our individual target.
261 PagesUsed
= InterlockedIncrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
262 if (PagesUsed
> MiMemoryConsumers
[Consumer
].PagesTarget
&&
263 !MiIsBalancerThread())
265 MmRebalanceMemoryConsumers();
269 * Allocate always memory for the non paged pool and for the pager thread.
271 if ((Consumer
== MC_SYSTEM
) || MiIsBalancerThread())
273 Page
= MmAllocPage(Consumer
);
276 KeBugCheck(NO_PAGES_AVAILABLE
);
278 if (Consumer
== MC_USER
) MmInsertLRULastUserPage(Page
);
279 *AllocatedPage
= Page
;
280 if (MmAvailablePages
< MiMinimumAvailablePages
)
281 MmRebalanceMemoryConsumers();
282 return(STATUS_SUCCESS
);
286 * Make sure we don't exceed global targets.
288 if (MmAvailablePages
< MiMinimumAvailablePages
)
290 MM_ALLOCATION_REQUEST Request
;
294 (void)InterlockedDecrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
295 MmRebalanceMemoryConsumers();
296 return(STATUS_NO_MEMORY
);
299 /* Insert an allocation request. */
301 KeInitializeEvent(&Request
.Event
, NotificationEvent
, FALSE
);
303 ExInterlockedInsertTailList(&AllocationListHead
, &Request
.ListEntry
, &AllocationListLock
);
304 MmRebalanceMemoryConsumers();
306 KeWaitForSingleObject(&Request
.Event
,
315 KeBugCheck(NO_PAGES_AVAILABLE
);
318 if(Consumer
== MC_USER
) MmInsertLRULastUserPage(Page
);
319 *AllocatedPage
= Page
;
321 if (MmAvailablePages
< MiMinimumAvailablePages
)
323 MmRebalanceMemoryConsumers();
326 return(STATUS_SUCCESS
);
330 * Actually allocate the page.
332 Page
= MmAllocPage(Consumer
);
335 KeBugCheck(NO_PAGES_AVAILABLE
);
337 if(Consumer
== MC_USER
) MmInsertLRULastUserPage(Page
);
338 *AllocatedPage
= Page
;
340 if (MmAvailablePages
< MiMinimumAvailablePages
)
342 MmRebalanceMemoryConsumers();
345 return(STATUS_SUCCESS
);
350 MiBalancerThread(PVOID Unused
)
352 PVOID WaitObjects
[2];
356 WaitObjects
[0] = &MiBalancerEvent
;
357 WaitObjects
[1] = &MiBalancerTimer
;
361 Status
= KeWaitForMultipleObjects(2,
370 if (Status
== STATUS_WAIT_0
|| Status
== STATUS_WAIT_1
)
372 ULONG InitialTarget
= 0;
374 #if (_MI_PAGING_LEVELS == 2)
375 if (!MiIsBalancerThread())
377 /* Clean up the unused PDEs */
379 PEPROCESS Process
= PsGetCurrentProcess();
381 /* Acquire PFN lock */
382 KIRQL OldIrql
= KeAcquireQueuedSpinLock(LockQueuePfnLock
);
384 for (Address
= (ULONG_PTR
)MI_LOWEST_VAD_ADDRESS
;
385 Address
< (ULONG_PTR
)MM_HIGHEST_VAD_ADDRESS
;
386 Address
+= (PAGE_SIZE
* PTE_COUNT
))
388 if (MiQueryPageTableReferences((PVOID
)Address
) == 0)
390 pointerPde
= MiAddressToPde(Address
);
391 if (pointerPde
->u
.Hard
.Valid
)
392 MiDeletePte(pointerPde
, MiPdeToPte(pointerPde
), Process
, NULL
);
393 ASSERT(pointerPde
->u
.Hard
.Valid
== 0);
397 KeReleaseQueuedSpinLock(LockQueuePfnLock
, OldIrql
);
402 ULONG OldTarget
= InitialTarget
;
404 /* Trim each consumer */
405 for (i
= 0; i
< MC_MAXIMUM
; i
++)
407 InitialTarget
= MiTrimMemoryConsumer(i
, InitialTarget
);
410 /* No pages left to swap! */
411 if (InitialTarget
!= 0 &&
412 InitialTarget
== OldTarget
)
415 KeBugCheck(NO_PAGES_AVAILABLE
);
418 while (InitialTarget
!= 0);
422 DPRINT1("KeWaitForMultipleObjects failed, status = %x\n", Status
);
423 KeBugCheck(MEMORY_MANAGEMENT
);
431 MiInitBalancerThread(VOID
)
435 #if !defined(__GNUC__)
437 LARGE_INTEGER dummyJunkNeeded
;
438 dummyJunkNeeded
.QuadPart
= -20000000; /* 2 sec */
443 KeInitializeEvent(&MiBalancerEvent
, SynchronizationEvent
, FALSE
);
444 KeInitializeTimerEx(&MiBalancerTimer
, SynchronizationTimer
);
445 KeSetTimerEx(&MiBalancerTimer
,
446 #if defined(__GNUC__)
447 (LARGE_INTEGER
)(LONGLONG
)-20000000LL, /* 2 sec */
454 Status
= PsCreateSystemThread(&MiBalancerThreadHandle
,
461 if (!NT_SUCCESS(Status
))
463 KeBugCheck(MEMORY_MANAGEMENT
);
466 Priority
= LOW_REALTIME_PRIORITY
+ 1;
467 NtSetInformationThread(MiBalancerThreadHandle
,