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 (MmAvailablePages
< MiMinimumAvailablePages
||
112 (Entry
= ExInterlockedRemoveHeadList(&AllocationListHead
, &AllocationListLock
)) == NULL
)
114 OldIrql
= KeAcquireQueuedSpinLock(LockQueuePfnLock
);
115 MmDereferencePage(Page
);
116 KeReleaseQueuedSpinLock(LockQueuePfnLock
, OldIrql
);
120 Request
= CONTAINING_RECORD(Entry
, MM_ALLOCATION_REQUEST
, ListEntry
);
121 MiZeroPhysicalPage(Page
);
122 Request
->Page
= Page
;
123 KeSetEvent(&Request
->Event
, IO_NO_INCREMENT
, FALSE
);
128 OldIrql
= KeAcquireQueuedSpinLock(LockQueuePfnLock
);
129 MmDereferencePage(Page
);
130 KeReleaseQueuedSpinLock(LockQueuePfnLock
, OldIrql
);
133 return(STATUS_SUCCESS
);
138 MiTrimMemoryConsumer(ULONG Consumer
)
141 ULONG NrFreedPages
= 0;
144 /* Make sure we can trim this consumer */
145 if (!MiMemoryConsumers
[Consumer
].Trim
)
148 if (MiMemoryConsumers
[Consumer
].PagesUsed
> MiMemoryConsumers
[Consumer
].PagesTarget
)
150 /* Consumer page limit exceeded */
151 Target
= max(Target
, MiMemoryConsumers
[Consumer
].PagesUsed
- MiMemoryConsumers
[Consumer
].PagesTarget
);
153 if (MmAvailablePages
< MiMinimumAvailablePages
)
155 /* Global page limit exceeded */
156 Target
= max(Target
, MiMinimumAvailablePages
- MmAvailablePages
);
161 /* Swap at least MiMinimumPagesPerRun */
162 Target
= max(Target
, MiMinimumPagesPerRun
);
164 /* Now swap the pages out */
165 Status
= MiMemoryConsumers
[Consumer
].Trim(Target
, 0, &NrFreedPages
);
167 if (!ExpInTextModeSetup
)
168 DPRINT1("Trimming consumer %d: Freed %d pages with a target of %d pages\n", Consumer
, NrFreedPages
, Target
);
170 if (NrFreedPages
== 0)
171 DPRINT1("Ran out of pages to swap! Complete memory exhaustion is imminent!\n");
173 if (!NT_SUCCESS(Status
))
175 KeBugCheck(MEMORY_MANAGEMENT
);
181 MmTrimUserMemory(ULONG Target
, ULONG Priority
, PULONG NrFreedPages
)
183 PFN_NUMBER CurrentPage
;
189 CurrentPage
= MmGetLRUFirstUserPage();
190 while (CurrentPage
!= 0 && Target
> 0)
192 Status
= MmPageOutPhysicalAddress(CurrentPage
);
193 if (NT_SUCCESS(Status
))
195 DPRINT("Succeeded\n");
200 NextPage
= MmGetLRUNextUserPage(CurrentPage
);
201 if (NextPage
<= CurrentPage
)
203 /* We wrapped around, so we're done */
206 CurrentPage
= NextPage
;
209 return STATUS_SUCCESS
;
213 MiIsBalancerThread(VOID
)
215 return (MiBalancerThreadHandle
!= NULL
) &&
216 (PsGetCurrentThreadId() == MiBalancerThreadId
.UniqueThread
);
221 MmRebalanceMemoryConsumers(VOID
)
223 if (MiBalancerThreadHandle
!= NULL
&&
224 !MiIsBalancerThread())
226 KeSetEvent(&MiBalancerEvent
, IO_NO_INCREMENT
, FALSE
);
232 MmRequestPageMemoryConsumer(ULONG Consumer
, BOOLEAN CanWait
,
233 PPFN_NUMBER AllocatedPage
)
240 * Make sure we don't exceed our individual target.
242 PagesUsed
= InterlockedIncrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
) + 1;
243 if (PagesUsed
> MiMemoryConsumers
[Consumer
].PagesTarget
&&
244 !MiIsBalancerThread())
246 MmRebalanceMemoryConsumers();
250 * Allocate always memory for the non paged pool and for the pager thread.
252 if ((Consumer
== MC_SYSTEM
) || MiIsBalancerThread())
254 OldIrql
= KeAcquireQueuedSpinLock(LockQueuePfnLock
);
255 Page
= MmAllocPage(Consumer
);
256 KeReleaseQueuedSpinLock(LockQueuePfnLock
, OldIrql
);
259 KeBugCheck(NO_PAGES_AVAILABLE
);
261 if (Consumer
== MC_USER
) MmInsertLRULastUserPage(Page
);
262 *AllocatedPage
= Page
;
263 if (MmAvailablePages
< MiMinimumAvailablePages
)
264 MmRebalanceMemoryConsumers();
265 return(STATUS_SUCCESS
);
269 * Make sure we don't exceed global targets.
271 if (MmAvailablePages
< MiMinimumAvailablePages
)
273 MM_ALLOCATION_REQUEST Request
;
277 (void)InterlockedDecrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
278 return(STATUS_NO_MEMORY
);
281 /* Insert an allocation request. */
283 KeInitializeEvent(&Request
.Event
, NotificationEvent
, FALSE
);
285 ExInterlockedInsertTailList(&AllocationListHead
, &Request
.ListEntry
, &AllocationListLock
);
286 MmRebalanceMemoryConsumers();
288 KeWaitForSingleObject(&Request
.Event
,
297 KeBugCheck(NO_PAGES_AVAILABLE
);
300 if(Consumer
== MC_USER
) MmInsertLRULastUserPage(Page
);
301 *AllocatedPage
= Page
;
303 if (MmAvailablePages
< MiMinimumAvailablePages
)
305 MmRebalanceMemoryConsumers();
308 return(STATUS_SUCCESS
);
312 * Actually allocate the page.
314 OldIrql
= KeAcquireQueuedSpinLock(LockQueuePfnLock
);
315 Page
= MmAllocPage(Consumer
);
316 KeReleaseQueuedSpinLock(LockQueuePfnLock
, OldIrql
);
319 KeBugCheck(NO_PAGES_AVAILABLE
);
321 if(Consumer
== MC_USER
) MmInsertLRULastUserPage(Page
);
322 *AllocatedPage
= Page
;
324 if (MmAvailablePages
< MiMinimumAvailablePages
)
326 MmRebalanceMemoryConsumers();
329 return(STATUS_SUCCESS
);
333 MiBalancerThread(PVOID Unused
)
335 PVOID WaitObjects
[2];
339 WaitObjects
[0] = &MiBalancerEvent
;
340 WaitObjects
[1] = &MiBalancerTimer
;
344 Status
= KeWaitForMultipleObjects(2,
353 if (Status
== STATUS_WAIT_0
|| Status
== STATUS_WAIT_1
)
355 for (i
= 0; i
< MC_MAXIMUM
; i
++)
357 MiTrimMemoryConsumer(i
);
362 DPRINT1("KeWaitForMultipleObjects failed, status = %x\n", Status
);
363 KeBugCheck(MEMORY_MANAGEMENT
);
371 MiInitBalancerThread(VOID
)
375 #if !defined(__GNUC__)
377 LARGE_INTEGER dummyJunkNeeded
;
378 dummyJunkNeeded
.QuadPart
= -20000000; /* 2 sec */
383 KeInitializeEvent(&MiBalancerEvent
, SynchronizationEvent
, FALSE
);
384 KeInitializeTimerEx(&MiBalancerTimer
, SynchronizationTimer
);
385 KeSetTimerEx(&MiBalancerTimer
,
386 #if defined(__GNUC__)
387 (LARGE_INTEGER
)(LONGLONG
)-20000000LL, /* 2 sec */
394 Status
= PsCreateSystemThread(&MiBalancerThreadHandle
,
399 (PKSTART_ROUTINE
) MiBalancerThread
,
401 if (!NT_SUCCESS(Status
))
403 KeBugCheck(MEMORY_MANAGEMENT
);
406 Priority
= LOW_REALTIME_PRIORITY
+ 1;
407 NtSetInformationThread(MiBalancerThreadHandle
,