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)
10 /* INCLUDES *****************************************************************/
16 #if defined (ALLOC_PRAGMA)
17 #pragma alloc_text(INIT, MmInitializeBalancer)
18 #pragma alloc_text(INIT, MmInitializeMemoryConsumer)
19 #pragma alloc_text(INIT, MiInitBalancerThread)
23 /* TYPES ********************************************************************/
24 typedef struct _MM_ALLOCATION_REQUEST
30 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 MiPagesRequired
= 0;
40 static ULONG MiMinimumPagesPerRun
= 10;
42 static CLIENT_ID MiBalancerThreadId
;
43 static HANDLE MiBalancerThreadHandle
= NULL
;
44 static KEVENT MiBalancerEvent
;
45 static KTIMER MiBalancerTimer
;
46 static LONG MiBalancerWork
= 0;
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
= 64;
63 if ((NrAvailablePages
+ NrSystemPages
) >= 8192)
65 MiMemoryConsumers
[MC_CACHE
].PagesTarget
= NrAvailablePages
/ 4 * 3;
67 else if ((NrAvailablePages
+ NrSystemPages
) >= 4096)
69 MiMemoryConsumers
[MC_CACHE
].PagesTarget
= NrAvailablePages
/ 3 * 2;
73 MiMemoryConsumers
[MC_CACHE
].PagesTarget
= NrAvailablePages
/ 8;
75 MiMemoryConsumers
[MC_USER
].PagesTarget
= NrAvailablePages
- MiMinimumAvailablePages
;
81 MmInitializeMemoryConsumer(ULONG Consumer
,
82 NTSTATUS (*Trim
)(ULONG Target
, ULONG Priority
,
85 MiMemoryConsumers
[Consumer
].Trim
= Trim
;
91 IN PFN_NUMBER PageFrameIndex
96 MmReleasePageMemoryConsumer(ULONG Consumer
, PFN_NUMBER Page
)
98 PMM_ALLOCATION_REQUEST Request
;
104 DPRINT1("Tried to release page zero.\n");
105 KeBugCheck(MEMORY_MANAGEMENT
);
108 KeAcquireSpinLock(&AllocationListLock
, &OldIrql
);
109 if (MmGetReferenceCountPage(Page
) == 1)
111 (void)InterlockedDecrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
112 if (IsListEmpty(&AllocationListHead
) || MmAvailablePages
< MiMinimumAvailablePages
)
114 KeReleaseSpinLock(&AllocationListLock
, OldIrql
);
115 if(Consumer
== MC_USER
) MmRemoveLRUUserPage(Page
);
116 OldIrql
= KeAcquireQueuedSpinLock(LockQueuePfnLock
);
117 MmDereferencePage(Page
);
118 KeReleaseQueuedSpinLock(LockQueuePfnLock
, OldIrql
);
122 Entry
= RemoveHeadList(&AllocationListHead
);
123 Request
= CONTAINING_RECORD(Entry
, MM_ALLOCATION_REQUEST
, ListEntry
);
124 KeReleaseSpinLock(&AllocationListLock
, OldIrql
);
125 if(Consumer
== MC_USER
) MmRemoveLRUUserPage(Page
);
126 MiZeroPhysicalPage(Page
);
127 Request
->Page
= Page
;
128 KeSetEvent(&Request
->Event
, IO_NO_INCREMENT
, FALSE
);
133 KeReleaseSpinLock(&AllocationListLock
, OldIrql
);
134 OldIrql
= KeAcquireQueuedSpinLock(LockQueuePfnLock
);
135 MmDereferencePage(Page
);
136 KeReleaseQueuedSpinLock(LockQueuePfnLock
, OldIrql
);
139 return(STATUS_SUCCESS
);
144 MiTrimMemoryConsumer(ULONG Consumer
)
149 Target
= max(MiMinimumPagesPerRun
,
150 MiMemoryConsumers
[Consumer
].PagesUsed
-
151 MiMemoryConsumers
[Consumer
].PagesTarget
);
153 if (MiMemoryConsumers
[Consumer
].Trim
!= NULL
)
155 MiMemoryConsumers
[Consumer
].Trim(Target
, 0, &NrFreedPages
);
160 MmTrimUserMemory(ULONG Target
, ULONG Priority
, PULONG NrFreedPages
)
162 PFN_NUMBER CurrentPage
;
168 CurrentPage
= MmGetLRUFirstUserPage();
169 while (CurrentPage
!= 0 && Target
> 0)
171 Status
= MmPageOutPhysicalAddress(CurrentPage
);
172 if (NT_SUCCESS(Status
))
174 DPRINT("Succeeded\n");
179 NextPage
= MmGetLRUNextUserPage(CurrentPage
);
180 if (NextPage
<= CurrentPage
)
182 /* We wrapped around, so we're done */
185 CurrentPage
= NextPage
;
188 return STATUS_SUCCESS
;
193 MmRebalanceMemoryConsumers(VOID
)
200 Target
= (ULONG
)(MiMinimumAvailablePages
- MmAvailablePages
) + MiPagesRequired
;
201 Target
= max(Target
, (LONG
) MiMinimumPagesPerRun
);
203 for (i
= 0; i
< MC_MAXIMUM
&& Target
> 0; i
++)
205 if (MiMemoryConsumers
[i
].Trim
!= NULL
)
207 Status
= MiMemoryConsumers
[i
].Trim(Target
, 0, &NrFreedPages
);
208 if (!NT_SUCCESS(Status
))
210 KeBugCheck(MEMORY_MANAGEMENT
);
212 Target
= Target
- NrFreedPages
;
218 MiIsBalancerThread(VOID
)
220 return (MiBalancerThreadHandle
!= NULL
) &&
221 (PsGetCurrentThreadId() == MiBalancerThreadId
.UniqueThread
);
226 MmRequestPageMemoryConsumer(ULONG Consumer
, BOOLEAN CanWait
,
227 PPFN_NUMBER AllocatedPage
)
234 * Make sure we don't exceed our individual target.
236 OldUsed
= InterlockedIncrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
237 if (OldUsed
>= (MiMemoryConsumers
[Consumer
].PagesTarget
- 1) &&
238 !MiIsBalancerThread())
242 (void)InterlockedDecrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
243 return(STATUS_NO_MEMORY
);
245 MiTrimMemoryConsumer(Consumer
);
249 * Allocate always memory for the non paged pool and for the pager thread.
251 if ((Consumer
== MC_SYSTEM
) || MiIsBalancerThread())
253 OldIrql
= KeAcquireQueuedSpinLock(LockQueuePfnLock
);
254 Page
= MmAllocPage(Consumer
);
255 KeReleaseQueuedSpinLock(LockQueuePfnLock
, OldIrql
);
258 KeBugCheck(NO_PAGES_AVAILABLE
);
260 if (Consumer
== MC_USER
) MmInsertLRULastUserPage(Page
);
261 *AllocatedPage
= Page
;
262 if (MmAvailablePages
<= MiMinimumAvailablePages
&&
263 MiBalancerThreadHandle
!= NULL
&&
264 !MiIsBalancerThread())
266 KeSetEvent(&MiBalancerEvent
, IO_NO_INCREMENT
, FALSE
);
268 return(STATUS_SUCCESS
);
272 * Make sure we don't exceed global targets.
274 if (MmAvailablePages
<= MiMinimumAvailablePages
)
276 MM_ALLOCATION_REQUEST Request
;
280 (void)InterlockedDecrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
281 return(STATUS_NO_MEMORY
);
284 /* Insert an allocation request. */
287 KeInitializeEvent(&Request
.Event
, NotificationEvent
, FALSE
);
288 (void)InterlockedIncrementUL(&MiPagesRequired
);
290 KeAcquireSpinLock(&AllocationListLock
, &OldIrql
);
292 if (MiBalancerThreadHandle
!= NULL
)
294 KeSetEvent(&MiBalancerEvent
, IO_NO_INCREMENT
, FALSE
);
296 InsertTailList(&AllocationListHead
, &Request
.ListEntry
);
297 KeReleaseSpinLock(&AllocationListLock
, OldIrql
);
299 KeWaitForSingleObject(&Request
.Event
,
308 KeBugCheck(NO_PAGES_AVAILABLE
);
311 if(Consumer
== MC_USER
) MmInsertLRULastUserPage(Page
);
312 *AllocatedPage
= Page
;
313 (void)InterlockedDecrementUL(&MiPagesRequired
);
315 if (MmAvailablePages
<= MiMinimumAvailablePages
&&
316 MiBalancerThreadHandle
!= NULL
&&
317 !MiIsBalancerThread())
319 KeSetEvent(&MiBalancerEvent
, IO_NO_INCREMENT
, FALSE
);
322 return(STATUS_SUCCESS
);
326 * Actually allocate the page.
328 OldIrql
= KeAcquireQueuedSpinLock(LockQueuePfnLock
);
329 Page
= MmAllocPage(Consumer
);
330 KeReleaseQueuedSpinLock(LockQueuePfnLock
, OldIrql
);
333 KeBugCheck(NO_PAGES_AVAILABLE
);
335 if(Consumer
== MC_USER
) MmInsertLRULastUserPage(Page
);
336 *AllocatedPage
= Page
;
338 if (MmAvailablePages
<= MiMinimumAvailablePages
&&
339 MiBalancerThreadHandle
!= NULL
&&
340 !MiIsBalancerThread())
342 KeSetEvent(&MiBalancerEvent
, IO_NO_INCREMENT
, FALSE
);
345 return(STATUS_SUCCESS
);
349 MiBalancerThread(PVOID Unused
)
351 PVOID WaitObjects
[2];
360 WaitObjects
[0] = &MiBalancerEvent
;
361 WaitObjects
[1] = &MiBalancerTimer
;
365 Status
= KeWaitForMultipleObjects(2,
374 if (Status
== STATUS_SUCCESS
)
376 /* MiBalancerEvent */
377 while (MmAvailablePages
< MiMinimumAvailablePages
+ 5)
379 for (i
= 0; i
< MC_MAXIMUM
; i
++)
381 if (MiMemoryConsumers
[i
].Trim
!= NULL
)
384 Status
= MiMemoryConsumers
[i
].Trim(MiMinimumPagesPerRun
, 0, &NrFreedPages
);
385 if (!NT_SUCCESS(Status
))
387 KeBugCheck(MEMORY_MANAGEMENT
);
392 InterlockedExchange(&MiBalancerWork
, 0);
394 else if (Status
== STATUS_SUCCESS
+ 1)
396 /* MiBalancerTimer */
397 ShouldRun
= MmAvailablePages
< MiMinimumAvailablePages
+ 5 ? TRUE
: FALSE
;
398 for (i
= 0; i
< MC_MAXIMUM
; i
++)
400 if (MiMemoryConsumers
[i
].Trim
!= NULL
)
402 NrPagesUsed
= MiMemoryConsumers
[i
].PagesUsed
;
403 if (NrPagesUsed
> MiMemoryConsumers
[i
].PagesTarget
|| ShouldRun
)
405 if (NrPagesUsed
> MiMemoryConsumers
[i
].PagesTarget
)
407 Target
= max (NrPagesUsed
- MiMemoryConsumers
[i
].PagesTarget
,
408 MiMinimumPagesPerRun
);
412 Target
= MiMinimumPagesPerRun
;
415 Status
= MiMemoryConsumers
[i
].Trim(Target
, 0, &NrFreedPages
);
416 if (!NT_SUCCESS(Status
))
418 KeBugCheck(MEMORY_MANAGEMENT
);
426 DPRINT1("KeWaitForMultipleObjects failed, status = %x\n", Status
);
427 KeBugCheck(MEMORY_MANAGEMENT
);
435 MiInitBalancerThread(VOID
)
439 #if !defined(__GNUC__)
441 LARGE_INTEGER dummyJunkNeeded
;
442 dummyJunkNeeded
.QuadPart
= -20000000; /* 2 sec */
447 KeInitializeEvent(&MiBalancerEvent
, SynchronizationEvent
, FALSE
);
448 KeInitializeTimerEx(&MiBalancerTimer
, SynchronizationTimer
);
449 KeSetTimerEx(&MiBalancerTimer
,
450 #if defined(__GNUC__)
451 (LARGE_INTEGER
)(LONGLONG
)-20000000LL, /* 2 sec */
458 Status
= PsCreateSystemThread(&MiBalancerThreadHandle
,
463 (PKSTART_ROUTINE
) MiBalancerThread
,
465 if (!NT_SUCCESS(Status
))
467 KeBugCheck(MEMORY_MANAGEMENT
);
470 Priority
= LOW_REALTIME_PRIORITY
+ 1;
471 NtSetInformationThread(MiBalancerThreadHandle
,