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 ****************************************************************/
50 VOID
MmPrintMemoryStatistic(VOID
)
52 DbgPrint("MC_CACHE %d, MC_USER %d, MC_PPOOL %d, MC_NPPOOL %d, MmAvailablePages %d\n",
53 MiMemoryConsumers
[MC_CACHE
].PagesUsed
, MiMemoryConsumers
[MC_USER
].PagesUsed
,
54 MiMemoryConsumers
[MC_PPOOL
].PagesUsed
, MiMemoryConsumers
[MC_NPPOOL
].PagesUsed
,
61 MmInitializeBalancer(ULONG NrAvailablePages
, ULONG NrSystemPages
)
63 memset(MiMemoryConsumers
, 0, sizeof(MiMemoryConsumers
));
64 InitializeListHead(&AllocationListHead
);
65 KeInitializeSpinLock(&AllocationListLock
);
67 MiNrTotalPages
= NrAvailablePages
;
70 MiMinimumAvailablePages
= 64;
71 if ((NrAvailablePages
+ NrSystemPages
) >= 8192)
73 MiMemoryConsumers
[MC_CACHE
].PagesTarget
= NrAvailablePages
/ 4 * 3;
75 else if ((NrAvailablePages
+ NrSystemPages
) >= 4096)
77 MiMemoryConsumers
[MC_CACHE
].PagesTarget
= NrAvailablePages
/ 3 * 2;
81 MiMemoryConsumers
[MC_CACHE
].PagesTarget
= NrAvailablePages
/ 8;
83 MiMemoryConsumers
[MC_USER
].PagesTarget
=
84 NrAvailablePages
- MiMinimumAvailablePages
;
85 MiMemoryConsumers
[MC_PPOOL
].PagesTarget
= NrAvailablePages
/ 2;
86 MiMemoryConsumers
[MC_NPPOOL
].PagesTarget
= 0xFFFFFFFF;
87 MiMemoryConsumers
[MC_NPPOOL
].PagesUsed
= NrSystemPages
;
88 MiMemoryConsumers
[MC_SYSTEM
].PagesTarget
= 0xFFFFFFFF;
89 MiMemoryConsumers
[MC_SYSTEM
].PagesUsed
= 0;
95 MmInitializeMemoryConsumer(ULONG Consumer
,
96 NTSTATUS (*Trim
)(ULONG Target
, ULONG Priority
,
99 MiMemoryConsumers
[Consumer
].Trim
= Trim
;
104 MmReleasePageMemoryConsumer(ULONG Consumer
, PFN_TYPE Page
)
106 PMM_ALLOCATION_REQUEST Request
;
112 DPRINT1("Tried to release page zero.\n");
113 KeBugCheck(MEMORY_MANAGEMENT
);
116 KeAcquireSpinLock(&AllocationListLock
, &OldIrql
);
117 if (MmGetReferenceCountPage(Page
) == 1)
119 (void)InterlockedDecrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
120 if (IsListEmpty(&AllocationListHead
) || MmAvailablePages
< MiMinimumAvailablePages
)
122 KeReleaseSpinLock(&AllocationListLock
, OldIrql
);
123 OldIrql
= KeAcquireQueuedSpinLock(LockQueuePfnLock
);
124 MmDereferencePage(Page
);
125 KeReleaseQueuedSpinLock(LockQueuePfnLock
, OldIrql
);
129 Entry
= RemoveHeadList(&AllocationListHead
);
130 Request
= CONTAINING_RECORD(Entry
, MM_ALLOCATION_REQUEST
, ListEntry
);
131 KeReleaseSpinLock(&AllocationListLock
, OldIrql
);
132 if(Consumer
== MC_USER
) MmRemoveLRUUserPage(Page
);
134 Request
->Page
= Page
;
135 KeSetEvent(&Request
->Event
, IO_NO_INCREMENT
, FALSE
);
140 KeReleaseSpinLock(&AllocationListLock
, OldIrql
);
141 if(Consumer
== MC_USER
) MmRemoveLRUUserPage(Page
);
142 OldIrql
= KeAcquireQueuedSpinLock(LockQueuePfnLock
);
143 MmDereferencePage(Page
);
144 KeReleaseQueuedSpinLock(LockQueuePfnLock
, OldIrql
);
147 return(STATUS_SUCCESS
);
152 MiTrimMemoryConsumer(ULONG Consumer
)
157 Target
= MiMemoryConsumers
[Consumer
].PagesUsed
-
158 MiMemoryConsumers
[Consumer
].PagesTarget
;
164 if (MiMemoryConsumers
[Consumer
].Trim
!= NULL
)
166 MiMemoryConsumers
[Consumer
].Trim(Target
, 0, &NrFreedPages
);
171 MmTrimUserMemory(ULONG Target
, ULONG Priority
, PULONG NrFreedPages
)
173 PFN_TYPE CurrentPage
;
179 CurrentPage
= MmGetLRUFirstUserPage();
180 while (CurrentPage
!= 0 && Target
> 0)
182 NextPage
= MmGetLRUNextUserPage(CurrentPage
);
184 Status
= MmPageOutPhysicalAddress(CurrentPage
);
185 if (NT_SUCCESS(Status
))
187 DPRINT("Succeeded\n");
191 else if (Status
== STATUS_PAGEFILE_QUOTA
)
193 MmRemoveLRUUserPage(CurrentPage
);
194 MmInsertLRULastUserPage(CurrentPage
);
197 CurrentPage
= NextPage
;
199 return(STATUS_SUCCESS
);
204 MmRebalanceMemoryConsumers(VOID
)
211 Target
= (MiMinimumAvailablePages
- MmAvailablePages
) + MiPagesRequired
;
212 Target
= max(Target
, (LONG
) MiMinimumPagesPerRun
);
214 for (i
= 0; i
< MC_MAXIMUM
&& Target
> 0; i
++)
216 if (MiMemoryConsumers
[i
].Trim
!= NULL
)
218 Status
= MiMemoryConsumers
[i
].Trim(Target
, 0, &NrFreedPages
);
219 if (!NT_SUCCESS(Status
))
221 KeBugCheck(MEMORY_MANAGEMENT
);
223 Target
= Target
- NrFreedPages
;
229 MiIsBalancerThread(VOID
)
231 return MiBalancerThreadHandle
!= NULL
&&
232 PsGetCurrentThread() == MiBalancerThreadId
.UniqueThread
;
237 MmRequestPageMemoryConsumer(ULONG Consumer
, BOOLEAN CanWait
,
238 PPFN_TYPE AllocatedPage
)
245 * Make sure we don't exceed our individual target.
247 OldUsed
= InterlockedIncrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
248 if (OldUsed
>= (MiMemoryConsumers
[Consumer
].PagesTarget
- 1) &&
249 !MiIsBalancerThread())
253 (void)InterlockedDecrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
254 return(STATUS_NO_MEMORY
);
256 MiTrimMemoryConsumer(Consumer
);
260 * Allocate always memory for the non paged pool and for the pager thread.
262 if ((Consumer
== MC_NPPOOL
) || (Consumer
== MC_SYSTEM
) || MiIsBalancerThread())
264 OldIrql
= KeAcquireQueuedSpinLock(LockQueuePfnLock
);
265 Page
= MmAllocPage(Consumer
, 0);
266 KeReleaseQueuedSpinLock(LockQueuePfnLock
, OldIrql
);
269 KeBugCheck(NO_PAGES_AVAILABLE
);
271 *AllocatedPage
= Page
;
272 if (MmAvailablePages
<= MiMinimumAvailablePages
&&
273 MiBalancerThreadHandle
!= NULL
)
275 KeSetEvent(&MiBalancerEvent
, IO_NO_INCREMENT
, FALSE
);
277 return(STATUS_SUCCESS
);
281 * Make sure we don't exceed global targets.
283 if (MmAvailablePages
<= MiMinimumAvailablePages
)
285 MM_ALLOCATION_REQUEST Request
;
289 (void)InterlockedDecrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
290 return(STATUS_NO_MEMORY
);
293 /* Insert an allocation request. */
296 KeInitializeEvent(&Request
.Event
, NotificationEvent
, FALSE
);
297 (void)InterlockedIncrementUL(&MiPagesRequired
);
299 KeAcquireSpinLock(&AllocationListLock
, &OldIrql
);
301 if (MiBalancerThreadHandle
!= NULL
)
303 KeSetEvent(&MiBalancerEvent
, IO_NO_INCREMENT
, FALSE
);
305 InsertTailList(&AllocationListHead
, &Request
.ListEntry
);
306 KeReleaseSpinLock(&AllocationListLock
, OldIrql
);
308 KeWaitForSingleObject(&Request
.Event
,
317 KeBugCheck(NO_PAGES_AVAILABLE
);
319 /* Update the Consumer */
320 MiGetPfnEntry(Page
)->u3
.e1
.PageLocation
= Consumer
;
321 if(Consumer
== MC_USER
) MmInsertLRULastUserPage(Page
);
322 *AllocatedPage
= Page
;
323 (void)InterlockedDecrementUL(&MiPagesRequired
);
324 return(STATUS_SUCCESS
);
328 * Actually allocate the page.
330 OldIrql
= KeAcquireQueuedSpinLock(LockQueuePfnLock
);
331 Page
= MmAllocPage(Consumer
, 0);
332 KeReleaseQueuedSpinLock(LockQueuePfnLock
, OldIrql
);
335 KeBugCheck(NO_PAGES_AVAILABLE
);
337 if(Consumer
== MC_USER
) MmInsertLRULastUserPage(Page
);
338 *AllocatedPage
= Page
;
340 return(STATUS_SUCCESS
);
344 MiBalancerThread(PVOID Unused
)
346 PVOID WaitObjects
[2];
355 WaitObjects
[0] = &MiBalancerEvent
;
356 WaitObjects
[1] = &MiBalancerTimer
;
360 Status
= KeWaitForMultipleObjects(2,
369 if (Status
== STATUS_SUCCESS
)
371 /* MiBalancerEvent */
372 while (MmAvailablePages
< MiMinimumAvailablePages
+ 5)
374 for (i
= 0; i
< MC_MAXIMUM
; i
++)
376 if (MiMemoryConsumers
[i
].Trim
!= NULL
)
379 Status
= MiMemoryConsumers
[i
].Trim(MiMinimumPagesPerRun
, 0, &NrFreedPages
);
380 if (!NT_SUCCESS(Status
))
382 KeBugCheck(MEMORY_MANAGEMENT
);
387 InterlockedExchange(&MiBalancerWork
, 0);
389 else if (Status
== STATUS_SUCCESS
+ 1)
391 /* MiBalancerTimer */
392 ShouldRun
= MmAvailablePages
< MiMinimumAvailablePages
+ 5 ? TRUE
: FALSE
;
393 for (i
= 0; i
< MC_MAXIMUM
; i
++)
395 if (MiMemoryConsumers
[i
].Trim
!= NULL
)
397 NrPagesUsed
= MiMemoryConsumers
[i
].PagesUsed
;
398 if (NrPagesUsed
> MiMemoryConsumers
[i
].PagesTarget
|| ShouldRun
)
400 if (NrPagesUsed
> MiMemoryConsumers
[i
].PagesTarget
)
402 Target
= max (NrPagesUsed
- MiMemoryConsumers
[i
].PagesTarget
,
403 MiMinimumPagesPerRun
);
407 Target
= MiMinimumPagesPerRun
;
410 Status
= MiMemoryConsumers
[i
].Trim(Target
, 0, &NrFreedPages
);
411 if (!NT_SUCCESS(Status
))
413 KeBugCheck(MEMORY_MANAGEMENT
);
421 DPRINT1("KeWaitForMultipleObjects failed, status = %x\n", Status
);
422 KeBugCheck(MEMORY_MANAGEMENT
);
430 MiInitBalancerThread(VOID
)
434 #if !defined(__GNUC__)
436 LARGE_INTEGER dummyJunkNeeded
;
437 dummyJunkNeeded
.QuadPart
= -20000000; /* 2 sec */
442 KeInitializeEvent(&MiBalancerEvent
, SynchronizationEvent
, FALSE
);
443 KeInitializeTimerEx(&MiBalancerTimer
, SynchronizationTimer
);
444 KeSetTimerEx(&MiBalancerTimer
,
445 #if defined(__GNUC__)
446 (LARGE_INTEGER
)(LONGLONG
)-20000000LL, /* 2 sec */
453 Status
= PsCreateSystemThread(&MiBalancerThreadHandle
,
458 (PKSTART_ROUTINE
) MiBalancerThread
,
460 if (!NT_SUCCESS(Status
))
462 KeBugCheck(MEMORY_MANAGEMENT
);
465 Priority
= LOW_REALTIME_PRIORITY
+ 1;
466 NtSetInformationThread(MiBalancerThreadHandle
,