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
)
140 ULONG NrFreedPages
= 0;
143 /* Make sure we can trim this consumer */
144 if (!MiMemoryConsumers
[Consumer
].Trim
)
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
= max(Target
, MiMinimumAvailablePages
- MmAvailablePages
);
160 /* Swap at least MiMinimumPagesPerRun */
161 Target
= max(Target
, MiMinimumPagesPerRun
);
163 /* Now swap the pages out */
164 Status
= MiMemoryConsumers
[Consumer
].Trim(Target
, 0, &NrFreedPages
);
166 DPRINT("Trimming consumer %d: Freed %d pages with a target of %d pages\n", Consumer
, NrFreedPages
, Target
);
168 if (!NT_SUCCESS(Status
))
170 KeBugCheck(MEMORY_MANAGEMENT
);
176 MmTrimUserMemory(ULONG Target
, ULONG Priority
, PULONG NrFreedPages
)
178 PFN_NUMBER CurrentPage
;
184 CurrentPage
= MmGetLRUFirstUserPage();
185 while (CurrentPage
!= 0 && Target
> 0)
187 Status
= MmPageOutPhysicalAddress(CurrentPage
);
188 if (NT_SUCCESS(Status
))
190 DPRINT("Succeeded\n");
195 NextPage
= MmGetLRUNextUserPage(CurrentPage
);
196 if (NextPage
<= CurrentPage
)
198 /* We wrapped around, so we're done */
201 CurrentPage
= NextPage
;
204 return STATUS_SUCCESS
;
208 MiIsBalancerThread(VOID
)
210 return (MiBalancerThreadHandle
!= NULL
) &&
211 (PsGetCurrentThreadId() == MiBalancerThreadId
.UniqueThread
);
216 MmRebalanceMemoryConsumers(VOID
)
218 if (MiBalancerThreadHandle
!= NULL
&&
219 !MiIsBalancerThread())
221 KeSetEvent(&MiBalancerEvent
, IO_NO_INCREMENT
, FALSE
);
227 MmRequestPageMemoryConsumer(ULONG Consumer
, BOOLEAN CanWait
,
228 PPFN_NUMBER AllocatedPage
)
235 * Make sure we don't exceed our individual target.
237 PagesUsed
= InterlockedIncrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
238 if (PagesUsed
> MiMemoryConsumers
[Consumer
].PagesTarget
&&
239 !MiIsBalancerThread())
241 MmRebalanceMemoryConsumers();
245 * Allocate always memory for the non paged pool and for the pager thread.
247 if ((Consumer
== MC_SYSTEM
) || MiIsBalancerThread())
249 OldIrql
= KeAcquireQueuedSpinLock(LockQueuePfnLock
);
250 Page
= MmAllocPage(Consumer
);
251 KeReleaseQueuedSpinLock(LockQueuePfnLock
, OldIrql
);
254 KeBugCheck(NO_PAGES_AVAILABLE
);
256 if (Consumer
== MC_USER
) MmInsertLRULastUserPage(Page
);
257 *AllocatedPage
= Page
;
258 if (MmAvailablePages
< MiMinimumAvailablePages
)
259 MmRebalanceMemoryConsumers();
260 return(STATUS_SUCCESS
);
264 * Make sure we don't exceed global targets.
266 if (MmAvailablePages
< MiMinimumAvailablePages
)
268 MM_ALLOCATION_REQUEST Request
;
272 (void)InterlockedDecrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
273 MmRebalanceMemoryConsumers();
274 return(STATUS_NO_MEMORY
);
277 /* Insert an allocation request. */
279 KeInitializeEvent(&Request
.Event
, NotificationEvent
, FALSE
);
281 ExInterlockedInsertTailList(&AllocationListHead
, &Request
.ListEntry
, &AllocationListLock
);
282 MmRebalanceMemoryConsumers();
284 KeWaitForSingleObject(&Request
.Event
,
293 KeBugCheck(NO_PAGES_AVAILABLE
);
296 if(Consumer
== MC_USER
) MmInsertLRULastUserPage(Page
);
297 *AllocatedPage
= Page
;
299 if (MmAvailablePages
< MiMinimumAvailablePages
)
301 MmRebalanceMemoryConsumers();
304 return(STATUS_SUCCESS
);
308 * Actually allocate the page.
310 OldIrql
= KeAcquireQueuedSpinLock(LockQueuePfnLock
);
311 Page
= MmAllocPage(Consumer
);
312 KeReleaseQueuedSpinLock(LockQueuePfnLock
, OldIrql
);
315 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 MiBalancerThread(PVOID Unused
)
331 PVOID WaitObjects
[2];
335 WaitObjects
[0] = &MiBalancerEvent
;
336 WaitObjects
[1] = &MiBalancerTimer
;
340 Status
= KeWaitForMultipleObjects(2,
349 if (Status
== STATUS_WAIT_0
|| Status
== STATUS_WAIT_1
)
351 for (i
= 0; i
< MC_MAXIMUM
; i
++)
353 MiTrimMemoryConsumer(i
);
356 if (MmAvailablePages
< MiMinimumAvailablePages
)
358 /* This is really bad... */
359 DPRINT1("Balancer failed to resolve low memory condition! Complete memory exhaustion is imminent!\n");
364 DPRINT1("KeWaitForMultipleObjects failed, status = %x\n", Status
);
365 KeBugCheck(MEMORY_MANAGEMENT
);
373 MiInitBalancerThread(VOID
)
377 #if !defined(__GNUC__)
379 LARGE_INTEGER dummyJunkNeeded
;
380 dummyJunkNeeded
.QuadPart
= -20000000; /* 2 sec */
385 KeInitializeEvent(&MiBalancerEvent
, SynchronizationEvent
, FALSE
);
386 KeInitializeTimerEx(&MiBalancerTimer
, SynchronizationTimer
);
387 KeSetTimerEx(&MiBalancerTimer
,
388 #if defined(__GNUC__)
389 (LARGE_INTEGER
)(LONGLONG
)-20000000LL, /* 2 sec */
396 Status
= PsCreateSystemThread(&MiBalancerThreadHandle
,
401 (PKSTART_ROUTINE
) MiBalancerThread
,
403 if (!NT_SUCCESS(Status
))
405 KeBugCheck(MEMORY_MANAGEMENT
);
408 Priority
= LOW_REALTIME_PRIORITY
+ 1;
409 NtSetInformationThread(MiBalancerThreadHandle
,