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
);
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 MmRebalanceMemoryConsumers();
279 return(STATUS_NO_MEMORY
);
282 /* Insert an allocation request. */
284 KeInitializeEvent(&Request
.Event
, NotificationEvent
, FALSE
);
286 ExInterlockedInsertTailList(&AllocationListHead
, &Request
.ListEntry
, &AllocationListLock
);
287 MmRebalanceMemoryConsumers();
289 KeWaitForSingleObject(&Request
.Event
,
298 KeBugCheck(NO_PAGES_AVAILABLE
);
301 if(Consumer
== MC_USER
) MmInsertLRULastUserPage(Page
);
302 *AllocatedPage
= Page
;
304 if (MmAvailablePages
< MiMinimumAvailablePages
)
306 MmRebalanceMemoryConsumers();
309 return(STATUS_SUCCESS
);
313 * Actually allocate the page.
315 OldIrql
= KeAcquireQueuedSpinLock(LockQueuePfnLock
);
316 Page
= MmAllocPage(Consumer
);
317 KeReleaseQueuedSpinLock(LockQueuePfnLock
, OldIrql
);
320 KeBugCheck(NO_PAGES_AVAILABLE
);
322 if(Consumer
== MC_USER
) MmInsertLRULastUserPage(Page
);
323 *AllocatedPage
= Page
;
325 if (MmAvailablePages
< MiMinimumAvailablePages
)
327 MmRebalanceMemoryConsumers();
330 return(STATUS_SUCCESS
);
334 MiBalancerThread(PVOID Unused
)
336 PVOID WaitObjects
[2];
340 WaitObjects
[0] = &MiBalancerEvent
;
341 WaitObjects
[1] = &MiBalancerTimer
;
345 Status
= KeWaitForMultipleObjects(2,
354 if (Status
== STATUS_WAIT_0
|| Status
== STATUS_WAIT_1
)
356 for (i
= 0; i
< MC_MAXIMUM
; i
++)
358 MiTrimMemoryConsumer(i
);
363 DPRINT1("KeWaitForMultipleObjects failed, status = %x\n", Status
);
364 KeBugCheck(MEMORY_MANAGEMENT
);
372 MiInitBalancerThread(VOID
)
376 #if !defined(__GNUC__)
378 LARGE_INTEGER dummyJunkNeeded
;
379 dummyJunkNeeded
.QuadPart
= -20000000; /* 2 sec */
384 KeInitializeEvent(&MiBalancerEvent
, SynchronizationEvent
, FALSE
);
385 KeInitializeTimerEx(&MiBalancerTimer
, SynchronizationTimer
);
386 KeSetTimerEx(&MiBalancerTimer
,
387 #if defined(__GNUC__)
388 (LARGE_INTEGER
)(LONGLONG
)-20000000LL, /* 2 sec */
395 Status
= PsCreateSystemThread(&MiBalancerThreadHandle
,
400 (PKSTART_ROUTINE
) MiBalancerThread
,
402 if (!NT_SUCCESS(Status
))
404 KeBugCheck(MEMORY_MANAGEMENT
);
407 Priority
= LOW_REALTIME_PRIORITY
+ 1;
408 NtSetInformationThread(MiBalancerThreadHandle
,