3 * COPYRIGHT: See COPYING in the top level directory
4 * PROJECT: ReactOS kernel
5 * FILE: ntoskrnl/mm/balance.c
6 * PURPOSE: kernel memory managment functions
8 * PROGRAMMERS: David Welch (welch@cwcom.net)
11 /* INCLUDES *****************************************************************/
15 #include <internal/debug.h>
17 /* TYPES ********************************************************************/
18 typedef struct _MM_ALLOCATION_REQUEST
24 MM_ALLOCATION_REQUEST
, *PMM_ALLOCATION_REQUEST
;
26 /* GLOBALS ******************************************************************/
28 MM_MEMORY_CONSUMER MiMemoryConsumers
[MC_MAXIMUM
];
29 static ULONG MiMinimumAvailablePages
;
30 static ULONG MiNrTotalPages
;
31 static LIST_ENTRY AllocationListHead
;
32 static KSPIN_LOCK AllocationListLock
;
33 static ULONG MiPagesRequired
= 0;
34 static ULONG MiMinimumPagesPerRun
= 10;
36 static CLIENT_ID MiBalancerThreadId
;
37 static HANDLE MiBalancerThreadHandle
= NULL
;
38 static KEVENT MiBalancerEvent
;
39 static KTIMER MiBalancerTimer
;
40 static LONG MiBalancerWork
= 0;
42 /* FUNCTIONS ****************************************************************/
44 VOID
MmPrintMemoryStatistic(VOID
)
46 DbgPrint("MC_CACHE %d, MC_USER %d, MC_PPOOL %d, MC_NPPOOL %d, MmStats.NrFreePages %d\n",
47 MiMemoryConsumers
[MC_CACHE
].PagesUsed
, MiMemoryConsumers
[MC_USER
].PagesUsed
,
48 MiMemoryConsumers
[MC_PPOOL
].PagesUsed
, MiMemoryConsumers
[MC_NPPOOL
].PagesUsed
,
55 MmInitializeBalancer(ULONG NrAvailablePages
, ULONG NrSystemPages
)
57 memset(MiMemoryConsumers
, 0, sizeof(MiMemoryConsumers
));
58 InitializeListHead(&AllocationListHead
);
59 KeInitializeSpinLock(&AllocationListLock
);
61 MiNrTotalPages
= NrAvailablePages
;
64 MiMinimumAvailablePages
= 64;
65 MiMemoryConsumers
[MC_CACHE
].PagesTarget
= NrAvailablePages
/ 2;
66 MiMemoryConsumers
[MC_USER
].PagesTarget
=
67 NrAvailablePages
- MiMinimumAvailablePages
;
68 MiMemoryConsumers
[MC_PPOOL
].PagesTarget
= NrAvailablePages
/ 2;
69 MiMemoryConsumers
[MC_NPPOOL
].PagesTarget
= 0xFFFFFFFF;
70 MiMemoryConsumers
[MC_NPPOOL
].PagesUsed
= NrSystemPages
;
76 MmInitializeMemoryConsumer(ULONG Consumer
,
77 NTSTATUS (*Trim
)(ULONG Target
, ULONG Priority
,
80 MiMemoryConsumers
[Consumer
].Trim
= Trim
;
85 MmReleasePageMemoryConsumer(ULONG Consumer
, PFN_TYPE Page
)
87 PMM_ALLOCATION_REQUEST Request
;
93 DPRINT1("Tried to release page zero.\n");
97 KeAcquireSpinLock(&AllocationListLock
, &oldIrql
);
98 if (MmGetReferenceCountPage(Page
) == 1)
100 InterlockedDecrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
101 if (IsListEmpty(&AllocationListHead
) || MmStats
.NrFreePages
< MiMinimumAvailablePages
)
103 KeReleaseSpinLock(&AllocationListLock
, oldIrql
);
104 MmDereferencePage(Page
);
108 Entry
= RemoveHeadList(&AllocationListHead
);
109 Request
= CONTAINING_RECORD(Entry
, MM_ALLOCATION_REQUEST
, ListEntry
);
110 KeReleaseSpinLock(&AllocationListLock
, oldIrql
);
111 Request
->Page
= Page
;
112 KeSetEvent(&Request
->Event
, IO_NO_INCREMENT
, FALSE
);
117 KeReleaseSpinLock(&AllocationListLock
, oldIrql
);
118 MmDereferencePage(Page
);
121 return(STATUS_SUCCESS
);
126 MiTrimMemoryConsumer(ULONG Consumer
)
131 Target
= MiMemoryConsumers
[Consumer
].PagesUsed
-
132 MiMemoryConsumers
[Consumer
].PagesTarget
;
138 if (MiMemoryConsumers
[Consumer
].Trim
!= NULL
)
140 MiMemoryConsumers
[Consumer
].Trim(Target
, 0, &NrFreedPages
);
146 MmRebalanceMemoryConsumers(VOID
)
153 Target
= (MiMinimumAvailablePages
- MmStats
.NrFreePages
) + MiPagesRequired
;
154 Target
= max(Target
, (LONG
) MiMinimumPagesPerRun
);
156 for (i
= 0; i
< MC_MAXIMUM
&& Target
> 0; i
++)
158 if (MiMemoryConsumers
[i
].Trim
!= NULL
)
160 Status
= MiMemoryConsumers
[i
].Trim(Target
, 0, &NrFreedPages
);
161 if (!NT_SUCCESS(Status
))
165 Target
= Target
- NrFreedPages
;
171 MiIsBalancerThread(VOID
)
173 return MiBalancerThreadHandle
!= NULL
&&
174 PsGetCurrentThread() == MiBalancerThreadId
.UniqueThread
;
179 MmRequestPageMemoryConsumer(ULONG Consumer
, BOOLEAN CanWait
,
180 PPFN_TYPE AllocatedPage
)
187 * Make sure we don't exceed our individual target.
189 OldUsed
= InterlockedIncrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
190 if (OldUsed
>= (MiMemoryConsumers
[Consumer
].PagesTarget
- 1) &&
191 !MiIsBalancerThread())
195 InterlockedDecrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
196 return(STATUS_NO_MEMORY
);
198 MiTrimMemoryConsumer(Consumer
);
202 * Allocate always memory for the non paged pool and for the pager thread.
204 if (Consumer
== MC_NPPOOL
|| MiIsBalancerThread())
206 Page
= MmAllocPage(Consumer
, 0);
209 KEBUGCHECK(NO_PAGES_AVAILABLE
);
211 *AllocatedPage
= Page
;
212 if (MmStats
.NrFreePages
<= MiMinimumAvailablePages
&&
213 MiBalancerThreadHandle
!= NULL
)
215 KeSetEvent(&MiBalancerEvent
, IO_NO_INCREMENT
, FALSE
);
217 return(STATUS_SUCCESS
);
221 * Make sure we don't exceed global targets.
223 if (MmStats
.NrFreePages
<= MiMinimumAvailablePages
)
225 MM_ALLOCATION_REQUEST Request
;
229 InterlockedDecrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
230 return(STATUS_NO_MEMORY
);
233 /* Insert an allocation request. */
236 KeInitializeEvent(&Request
.Event
, NotificationEvent
, FALSE
);
237 InterlockedIncrementUL(&MiPagesRequired
);
239 KeAcquireSpinLock(&AllocationListLock
, &oldIrql
);
241 if (MiBalancerThreadHandle
!= NULL
)
243 KeSetEvent(&MiBalancerEvent
, IO_NO_INCREMENT
, FALSE
);
245 InsertTailList(&AllocationListHead
, &Request
.ListEntry
);
246 KeReleaseSpinLock(&AllocationListLock
, oldIrql
);
248 KeWaitForSingleObject(&Request
.Event
,
257 KEBUGCHECK(NO_PAGES_AVAILABLE
);
259 MmTransferOwnershipPage(Page
, Consumer
);
260 *AllocatedPage
= Page
;
261 InterlockedDecrementUL(&MiPagesRequired
);
262 return(STATUS_SUCCESS
);
266 * Actually allocate the page.
268 Page
= MmAllocPage(Consumer
, 0);
271 KEBUGCHECK(NO_PAGES_AVAILABLE
);
273 *AllocatedPage
= Page
;
275 return(STATUS_SUCCESS
);
279 MiBalancerThread(PVOID Unused
)
281 PVOID WaitObjects
[2];
290 WaitObjects
[0] = &MiBalancerEvent
;
291 WaitObjects
[1] = &MiBalancerTimer
;
295 Status
= KeWaitForMultipleObjects(2,
304 if (Status
== STATUS_SUCCESS
)
306 /* MiBalancerEvent */
308 while (MmStats
.NrFreePages
< MiMinimumAvailablePages
+ 5)
310 for (i
= 0; i
< MC_MAXIMUM
; i
++)
312 if (MiMemoryConsumers
[i
].Trim
!= NULL
)
315 Status
= MiMemoryConsumers
[i
].Trim(MiMinimumPagesPerRun
, 0, &NrFreedPages
);
316 if (!NT_SUCCESS(Status
))
323 InterlockedExchange(&MiBalancerWork
, 0);
326 else if (Status
== STATUS_SUCCESS
+ 1)
328 /* MiBalancerTimer */
329 ShouldRun
= MmStats
.NrFreePages
< MiMinimumAvailablePages
+ 5 ? TRUE
: FALSE
;
330 for (i
= 0; i
< MC_MAXIMUM
; i
++)
332 if (MiMemoryConsumers
[i
].Trim
!= NULL
)
334 NrPagesUsed
= MiMemoryConsumers
[i
].PagesUsed
;
335 if (NrPagesUsed
> MiMemoryConsumers
[i
].PagesTarget
|| ShouldRun
)
337 if (NrPagesUsed
> MiMemoryConsumers
[i
].PagesTarget
)
339 Target
= max (NrPagesUsed
- MiMemoryConsumers
[i
].PagesTarget
,
340 MiMinimumPagesPerRun
);
344 Target
= MiMinimumPagesPerRun
;
347 Status
= MiMemoryConsumers
[i
].Trim(Target
, 0, &NrFreedPages
);
348 if (!NT_SUCCESS(Status
))
358 DPRINT1("KeWaitForMultipleObjects failed, status = %x\n", Status
);
367 MiInitBalancerThread(VOID
)
371 #if !defined(__GNUC__)
373 LARGE_INTEGER dummyJunkNeeded
;
374 dummyJunkNeeded
.QuadPart
= -20000000; /* 2 sec */
380 KeInitializeEvent(&MiBalancerEvent
, SynchronizationEvent
, FALSE
);
381 KeInitializeTimerEx(&MiBalancerTimer
, SynchronizationTimer
);
382 KeSetTimerEx(&MiBalancerTimer
,
383 #if defined(__GNUC__)
384 (LARGE_INTEGER
)(LONGLONG
)-20000000LL, /* 2 sec */
391 Status
= PsCreateSystemThread(&MiBalancerThreadHandle
,
396 (PKSTART_ROUTINE
) MiBalancerThread
,
398 if (!NT_SUCCESS(Status
))
403 Priority
= LOW_REALTIME_PRIORITY
+ 1;
404 NtSetInformationThread(MiBalancerThreadHandle
,