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 MmRebalanceMemoryConsumers(VOID
)
236 if (MiBalancerThreadHandle
!= NULL
&&
237 !MiIsBalancerThread())
239 KeSetEvent(&MiBalancerEvent
, IO_NO_INCREMENT
, FALSE
);
245 MmRequestPageMemoryConsumer(ULONG Consumer
, BOOLEAN CanWait
,
246 PPFN_NUMBER AllocatedPage
)
252 * Make sure we don't exceed our individual target.
254 PagesUsed
= InterlockedIncrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
255 if (PagesUsed
> MiMemoryConsumers
[Consumer
].PagesTarget
&&
256 !MiIsBalancerThread())
258 MmRebalanceMemoryConsumers();
262 * Allocate always memory for the non paged pool and for the pager thread.
264 if ((Consumer
== MC_SYSTEM
) || MiIsBalancerThread())
266 Page
= MmAllocPage(Consumer
);
269 KeBugCheck(NO_PAGES_AVAILABLE
);
271 if (Consumer
== MC_USER
) MmInsertLRULastUserPage(Page
);
272 *AllocatedPage
= Page
;
273 if (MmAvailablePages
< MiMinimumAvailablePages
)
274 MmRebalanceMemoryConsumers();
275 return(STATUS_SUCCESS
);
279 * Make sure we don't exceed global targets.
281 if (MmAvailablePages
< MiMinimumAvailablePages
)
283 MM_ALLOCATION_REQUEST Request
;
287 (void)InterlockedDecrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
288 MmRebalanceMemoryConsumers();
289 return(STATUS_NO_MEMORY
);
292 /* Insert an allocation request. */
294 KeInitializeEvent(&Request
.Event
, NotificationEvent
, FALSE
);
296 ExInterlockedInsertTailList(&AllocationListHead
, &Request
.ListEntry
, &AllocationListLock
);
297 MmRebalanceMemoryConsumers();
299 KeWaitForSingleObject(&Request
.Event
,
308 KeBugCheck(NO_PAGES_AVAILABLE
);
311 if(Consumer
== MC_USER
) MmInsertLRULastUserPage(Page
);
312 *AllocatedPage
= Page
;
314 if (MmAvailablePages
< MiMinimumAvailablePages
)
316 MmRebalanceMemoryConsumers();
319 return(STATUS_SUCCESS
);
323 * Actually allocate the page.
325 Page
= MmAllocPage(Consumer
);
328 KeBugCheck(NO_PAGES_AVAILABLE
);
330 if(Consumer
== MC_USER
) MmInsertLRULastUserPage(Page
);
331 *AllocatedPage
= Page
;
333 if (MmAvailablePages
< MiMinimumAvailablePages
)
335 MmRebalanceMemoryConsumers();
338 return(STATUS_SUCCESS
);
343 MiBalancerThread(PVOID Unused
)
345 PVOID WaitObjects
[2];
349 WaitObjects
[0] = &MiBalancerEvent
;
350 WaitObjects
[1] = &MiBalancerTimer
;
354 Status
= KeWaitForMultipleObjects(2,
363 if (Status
== STATUS_WAIT_0
|| Status
== STATUS_WAIT_1
)
365 ULONG InitialTarget
= 0;
367 #if (_MI_PAGING_LEVELS == 2)
368 if (!MiIsBalancerThread())
370 /* Clean up the unused PDEs */
372 PEPROCESS Process
= PsGetCurrentProcess();
374 /* Acquire PFN lock */
375 KIRQL OldIrql
= KeAcquireQueuedSpinLock(LockQueuePfnLock
);
377 for (Address
= (ULONG_PTR
)MI_LOWEST_VAD_ADDRESS
;
378 Address
< (ULONG_PTR
)MM_HIGHEST_VAD_ADDRESS
;
379 Address
+= (PAGE_SIZE
* PTE_COUNT
))
381 if (MiQueryPageTableReferences((PVOID
)Address
) == 0)
383 pointerPde
= MiAddressToPde(Address
);
384 if (pointerPde
->u
.Hard
.Valid
)
385 MiDeletePte(pointerPde
, MiPdeToPte(pointerPde
), Process
, NULL
);
386 ASSERT(pointerPde
->u
.Hard
.Valid
== 0);
390 KeReleaseQueuedSpinLock(LockQueuePfnLock
, OldIrql
);
395 ULONG OldTarget
= InitialTarget
;
397 /* Trim each consumer */
398 for (i
= 0; i
< MC_MAXIMUM
; i
++)
400 InitialTarget
= MiTrimMemoryConsumer(i
, InitialTarget
);
403 /* No pages left to swap! */
404 if (InitialTarget
!= 0 &&
405 InitialTarget
== OldTarget
)
408 KeBugCheck(NO_PAGES_AVAILABLE
);
411 while (InitialTarget
!= 0);
415 DPRINT1("KeWaitForMultipleObjects failed, status = %x\n", Status
);
416 KeBugCheck(MEMORY_MANAGEMENT
);
424 MiInitBalancerThread(VOID
)
428 #if !defined(__GNUC__)
430 LARGE_INTEGER dummyJunkNeeded
;
431 dummyJunkNeeded
.QuadPart
= -20000000; /* 2 sec */
436 KeInitializeEvent(&MiBalancerEvent
, SynchronizationEvent
, FALSE
);
437 KeInitializeTimerEx(&MiBalancerTimer
, SynchronizationTimer
);
438 KeSetTimerEx(&MiBalancerTimer
,
439 #if defined(__GNUC__)
440 (LARGE_INTEGER
)(LONGLONG
)-20000000LL, /* 2 sec */
447 Status
= PsCreateSystemThread(&MiBalancerThreadHandle
,
454 if (!NT_SUCCESS(Status
))
456 KeBugCheck(MEMORY_MANAGEMENT
);
459 Priority
= LOW_REALTIME_PRIORITY
+ 1;
460 NtSetInformationThread(MiBalancerThreadHandle
,