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 #if defined (ALLOC_PRAGMA)
18 #pragma alloc_text(INIT, MmInitializeBalancer)
19 #pragma alloc_text(INIT, MmInitializeMemoryConsumer)
20 #pragma alloc_text(INIT, MiInitBalancerThread)
24 /* TYPES ********************************************************************/
25 typedef struct _MM_ALLOCATION_REQUEST
31 MM_ALLOCATION_REQUEST
, *PMM_ALLOCATION_REQUEST
;
32 /* GLOBALS ******************************************************************/
34 MM_MEMORY_CONSUMER MiMemoryConsumers
[MC_MAXIMUM
];
35 static ULONG MiMinimumAvailablePages
;
36 static ULONG MiNrTotalPages
;
37 static LIST_ENTRY AllocationListHead
;
38 static KSPIN_LOCK AllocationListLock
;
39 static ULONG MiMinimumPagesPerRun
;
41 static CLIENT_ID MiBalancerThreadId
;
42 static HANDLE MiBalancerThreadHandle
= NULL
;
43 static KEVENT MiBalancerEvent
;
44 static KTIMER MiBalancerTimer
;
46 /* FUNCTIONS ****************************************************************/
51 MmInitializeBalancer(ULONG NrAvailablePages
, ULONG NrSystemPages
)
53 memset(MiMemoryConsumers
, 0, sizeof(MiMemoryConsumers
));
54 InitializeListHead(&AllocationListHead
);
55 KeInitializeSpinLock(&AllocationListLock
);
57 MiNrTotalPages
= NrAvailablePages
;
60 MiMinimumAvailablePages
= 128;
61 MiMinimumPagesPerRun
= 256;
62 if ((NrAvailablePages
+ NrSystemPages
) >= 8192)
64 MiMemoryConsumers
[MC_CACHE
].PagesTarget
= NrAvailablePages
/ 4 * 3;
66 else if ((NrAvailablePages
+ NrSystemPages
) >= 4096)
68 MiMemoryConsumers
[MC_CACHE
].PagesTarget
= NrAvailablePages
/ 3 * 2;
72 MiMemoryConsumers
[MC_CACHE
].PagesTarget
= NrAvailablePages
/ 8;
74 MiMemoryConsumers
[MC_USER
].PagesTarget
= NrAvailablePages
- MiMinimumAvailablePages
;
80 MmInitializeMemoryConsumer(ULONG Consumer
,
81 NTSTATUS (*Trim
)(ULONG Target
, ULONG Priority
,
84 MiMemoryConsumers
[Consumer
].Trim
= Trim
;
90 IN PFN_NUMBER PageFrameIndex
95 MmReleasePageMemoryConsumer(ULONG Consumer
, PFN_NUMBER Page
)
97 PMM_ALLOCATION_REQUEST Request
;
103 DPRINT1("Tried to release page zero.\n");
104 KeBugCheck(MEMORY_MANAGEMENT
);
107 if (MmGetReferenceCountPage(Page
) == 1)
109 if(Consumer
== MC_USER
) MmRemoveLRUUserPage(Page
);
110 (void)InterlockedDecrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
111 if ((Entry
= ExInterlockedRemoveHeadList(&AllocationListHead
, &AllocationListLock
)) == NULL
)
113 OldIrql
= KeAcquireQueuedSpinLock(LockQueuePfnLock
);
114 MmDereferencePage(Page
);
115 KeReleaseQueuedSpinLock(LockQueuePfnLock
, OldIrql
);
119 Request
= CONTAINING_RECORD(Entry
, MM_ALLOCATION_REQUEST
, ListEntry
);
120 MiZeroPhysicalPage(Page
);
121 Request
->Page
= Page
;
122 KeSetEvent(&Request
->Event
, IO_NO_INCREMENT
, FALSE
);
127 OldIrql
= KeAcquireQueuedSpinLock(LockQueuePfnLock
);
128 MmDereferencePage(Page
);
129 KeReleaseQueuedSpinLock(LockQueuePfnLock
, OldIrql
);
132 return(STATUS_SUCCESS
);
137 MiTrimMemoryConsumer(ULONG Consumer
, ULONG InitialTarget
)
139 LONG Target
= InitialTarget
;
140 ULONG NrFreedPages
= 0;
143 /* Make sure we can trim this consumer */
144 if (!MiMemoryConsumers
[Consumer
].Trim
)
146 /* Return the unmodified initial target */
147 return InitialTarget
;
150 if (MiMemoryConsumers
[Consumer
].PagesUsed
> MiMemoryConsumers
[Consumer
].PagesTarget
)
152 /* Consumer page limit exceeded */
153 Target
= max(Target
, MiMemoryConsumers
[Consumer
].PagesUsed
- MiMemoryConsumers
[Consumer
].PagesTarget
);
155 if (MmAvailablePages
< MiMinimumAvailablePages
)
157 /* Global page limit exceeded */
158 Target
= max(Target
, MiMinimumAvailablePages
- MmAvailablePages
);
165 /* If there was no initial target,
166 * swap at least MiMinimumPagesPerRun */
167 Target
= max(Target
, MiMinimumPagesPerRun
);
170 /* Now swap the pages out */
171 Status
= MiMemoryConsumers
[Consumer
].Trim(Target
, 0, &NrFreedPages
);
173 DPRINT("Trimming consumer %d: Freed %d pages with a target of %d pages\n", Consumer
, NrFreedPages
, Target
);
175 if (!NT_SUCCESS(Status
))
177 KeBugCheck(MEMORY_MANAGEMENT
);
180 /* Update the target */
181 if (NrFreedPages
< Target
)
182 Target
-= NrFreedPages
;
186 /* Return the remaining pages needed to meet the target */
191 /* Initial target is zero and we don't have anything else to add */
197 MmTrimUserMemory(ULONG Target
, ULONG Priority
, PULONG NrFreedPages
)
199 PFN_NUMBER CurrentPage
;
205 CurrentPage
= MmGetLRUFirstUserPage();
206 while (CurrentPage
!= 0 && Target
> 0)
208 Status
= MmPageOutPhysicalAddress(CurrentPage
);
209 if (NT_SUCCESS(Status
))
211 DPRINT("Succeeded\n");
216 NextPage
= MmGetLRUNextUserPage(CurrentPage
);
217 if (NextPage
<= CurrentPage
)
219 /* We wrapped around, so we're done */
222 CurrentPage
= NextPage
;
225 return STATUS_SUCCESS
;
229 MiIsBalancerThread(VOID
)
231 return (MiBalancerThreadHandle
!= NULL
) &&
232 (PsGetCurrentThreadId() == MiBalancerThreadId
.UniqueThread
);
237 MmRebalanceMemoryConsumers(VOID
)
239 if (MiBalancerThreadHandle
!= NULL
&&
240 !MiIsBalancerThread())
242 KeSetEvent(&MiBalancerEvent
, IO_NO_INCREMENT
, FALSE
);
248 MmRequestPageMemoryConsumer(ULONG Consumer
, BOOLEAN CanWait
,
249 PPFN_NUMBER AllocatedPage
)
256 * Make sure we don't exceed our individual target.
258 PagesUsed
= InterlockedIncrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
259 if (PagesUsed
> MiMemoryConsumers
[Consumer
].PagesTarget
&&
260 !MiIsBalancerThread())
262 MmRebalanceMemoryConsumers();
266 * Allocate always memory for the non paged pool and for the pager thread.
268 if ((Consumer
== MC_SYSTEM
) || MiIsBalancerThread())
270 OldIrql
= KeAcquireQueuedSpinLock(LockQueuePfnLock
);
271 Page
= MmAllocPage(Consumer
);
272 KeReleaseQueuedSpinLock(LockQueuePfnLock
, OldIrql
);
275 KeBugCheck(NO_PAGES_AVAILABLE
);
277 if (Consumer
== MC_USER
) MmInsertLRULastUserPage(Page
);
278 *AllocatedPage
= Page
;
279 if (MmAvailablePages
< MiMinimumAvailablePages
)
280 MmRebalanceMemoryConsumers();
281 return(STATUS_SUCCESS
);
285 * Make sure we don't exceed global targets.
287 if (MmAvailablePages
< MiMinimumAvailablePages
)
289 MM_ALLOCATION_REQUEST Request
;
293 (void)InterlockedDecrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
294 MmRebalanceMemoryConsumers();
295 return(STATUS_NO_MEMORY
);
298 /* Insert an allocation request. */
300 KeInitializeEvent(&Request
.Event
, NotificationEvent
, FALSE
);
302 ExInterlockedInsertTailList(&AllocationListHead
, &Request
.ListEntry
, &AllocationListLock
);
303 MmRebalanceMemoryConsumers();
305 KeWaitForSingleObject(&Request
.Event
,
314 KeBugCheck(NO_PAGES_AVAILABLE
);
317 if(Consumer
== MC_USER
) MmInsertLRULastUserPage(Page
);
318 *AllocatedPage
= Page
;
320 if (MmAvailablePages
< MiMinimumAvailablePages
)
322 MmRebalanceMemoryConsumers();
325 return(STATUS_SUCCESS
);
329 * Actually allocate the page.
331 OldIrql
= KeAcquireQueuedSpinLock(LockQueuePfnLock
);
332 Page
= MmAllocPage(Consumer
);
333 KeReleaseQueuedSpinLock(LockQueuePfnLock
, OldIrql
);
336 KeBugCheck(NO_PAGES_AVAILABLE
);
338 if(Consumer
== MC_USER
) MmInsertLRULastUserPage(Page
);
339 *AllocatedPage
= Page
;
341 if (MmAvailablePages
< MiMinimumAvailablePages
)
343 MmRebalanceMemoryConsumers();
346 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;
376 ULONG OldTarget
= InitialTarget
;
378 /* Trim each consumer */
379 for (i
= 0; i
< MC_MAXIMUM
; i
++)
381 InitialTarget
= MiTrimMemoryConsumer(i
, InitialTarget
);
384 /* No pages left to swap! */
385 if (InitialTarget
!= 0 &&
386 InitialTarget
== OldTarget
)
389 KeBugCheck(NO_PAGES_AVAILABLE
);
391 } while (InitialTarget
!= 0);
395 DPRINT1("KeWaitForMultipleObjects failed, status = %x\n", Status
);
396 KeBugCheck(MEMORY_MANAGEMENT
);
404 MiInitBalancerThread(VOID
)
408 #if !defined(__GNUC__)
410 LARGE_INTEGER dummyJunkNeeded
;
411 dummyJunkNeeded
.QuadPart
= -20000000; /* 2 sec */
416 KeInitializeEvent(&MiBalancerEvent
, SynchronizationEvent
, FALSE
);
417 KeInitializeTimerEx(&MiBalancerTimer
, SynchronizationTimer
);
418 KeSetTimerEx(&MiBalancerTimer
,
419 #if defined(__GNUC__)
420 (LARGE_INTEGER
)(LONGLONG
)-20000000LL, /* 2 sec */
427 Status
= PsCreateSystemThread(&MiBalancerThreadHandle
,
432 (PKSTART_ROUTINE
) MiBalancerThread
,
434 if (!NT_SUCCESS(Status
))
436 KeBugCheck(MEMORY_MANAGEMENT
);
439 Priority
= LOW_REALTIME_PRIORITY
+ 1;
440 NtSetInformationThread(MiBalancerThreadHandle
,