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 #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
;
33 /* GLOBALS ******************************************************************/
35 MM_MEMORY_CONSUMER MiMemoryConsumers
[MC_MAXIMUM
];
36 static ULONG MiMinimumAvailablePages
;
37 static ULONG MiNrTotalPages
;
38 static LIST_ENTRY AllocationListHead
;
39 static KSPIN_LOCK AllocationListLock
;
40 static ULONG MiPagesRequired
= 0;
41 static ULONG MiMinimumPagesPerRun
= 10;
43 static CLIENT_ID MiBalancerThreadId
;
44 static HANDLE MiBalancerThreadHandle
= NULL
;
45 static KEVENT MiBalancerEvent
;
46 static KTIMER MiBalancerTimer
;
47 static LONG MiBalancerWork
= 0;
49 /* FUNCTIONS ****************************************************************/
51 VOID
MmPrintMemoryStatistic(VOID
)
53 DbgPrint("MC_CACHE %d, MC_USER %d, MC_PPOOL %d, MC_NPPOOL %d, MmStats.NrFreePages %d\n",
54 MiMemoryConsumers
[MC_CACHE
].PagesUsed
, MiMemoryConsumers
[MC_USER
].PagesUsed
,
55 MiMemoryConsumers
[MC_PPOOL
].PagesUsed
, MiMemoryConsumers
[MC_NPPOOL
].PagesUsed
,
62 MmInitializeBalancer(ULONG NrAvailablePages
, ULONG NrSystemPages
)
64 memset(MiMemoryConsumers
, 0, sizeof(MiMemoryConsumers
));
65 InitializeListHead(&AllocationListHead
);
66 KeInitializeSpinLock(&AllocationListLock
);
68 MiNrTotalPages
= NrAvailablePages
;
71 MiMinimumAvailablePages
= 64;
72 MiMemoryConsumers
[MC_CACHE
].PagesTarget
= NrAvailablePages
/ 2;
73 MiMemoryConsumers
[MC_USER
].PagesTarget
=
74 NrAvailablePages
- MiMinimumAvailablePages
;
75 MiMemoryConsumers
[MC_PPOOL
].PagesTarget
= NrAvailablePages
/ 2;
76 MiMemoryConsumers
[MC_NPPOOL
].PagesTarget
= 0xFFFFFFFF;
77 MiMemoryConsumers
[MC_NPPOOL
].PagesUsed
= NrSystemPages
;
83 MmInitializeMemoryConsumer(ULONG Consumer
,
84 NTSTATUS (*Trim
)(ULONG Target
, ULONG Priority
,
87 MiMemoryConsumers
[Consumer
].Trim
= Trim
;
92 MmReleasePageMemoryConsumer(ULONG Consumer
, PFN_TYPE Page
)
94 PMM_ALLOCATION_REQUEST Request
;
100 DPRINT1("Tried to release page zero.\n");
104 KeAcquireSpinLock(&AllocationListLock
, &oldIrql
);
105 if (MmGetReferenceCountPage(Page
) == 1)
107 InterlockedDecrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
108 if (IsListEmpty(&AllocationListHead
) || MmStats
.NrFreePages
< MiMinimumAvailablePages
)
110 KeReleaseSpinLock(&AllocationListLock
, oldIrql
);
111 MmDereferencePage(Page
);
115 Entry
= RemoveHeadList(&AllocationListHead
);
116 Request
= CONTAINING_RECORD(Entry
, MM_ALLOCATION_REQUEST
, ListEntry
);
117 KeReleaseSpinLock(&AllocationListLock
, oldIrql
);
118 Request
->Page
= Page
;
119 KeSetEvent(&Request
->Event
, IO_NO_INCREMENT
, FALSE
);
124 KeReleaseSpinLock(&AllocationListLock
, oldIrql
);
125 MmDereferencePage(Page
);
128 return(STATUS_SUCCESS
);
133 MiTrimMemoryConsumer(ULONG Consumer
)
138 Target
= MiMemoryConsumers
[Consumer
].PagesUsed
-
139 MiMemoryConsumers
[Consumer
].PagesTarget
;
145 if (MiMemoryConsumers
[Consumer
].Trim
!= NULL
)
147 MiMemoryConsumers
[Consumer
].Trim(Target
, 0, &NrFreedPages
);
153 MmRebalanceMemoryConsumers(VOID
)
160 Target
= (MiMinimumAvailablePages
- MmStats
.NrFreePages
) + MiPagesRequired
;
161 Target
= max(Target
, (LONG
) MiMinimumPagesPerRun
);
163 for (i
= 0; i
< MC_MAXIMUM
&& Target
> 0; i
++)
165 if (MiMemoryConsumers
[i
].Trim
!= NULL
)
167 Status
= MiMemoryConsumers
[i
].Trim(Target
, 0, &NrFreedPages
);
168 if (!NT_SUCCESS(Status
))
172 Target
= Target
- NrFreedPages
;
178 MiIsBalancerThread(VOID
)
180 return MiBalancerThreadHandle
!= NULL
&&
181 PsGetCurrentThread() == MiBalancerThreadId
.UniqueThread
;
186 MmRequestPageMemoryConsumer(ULONG Consumer
, BOOLEAN CanWait
,
187 PPFN_TYPE AllocatedPage
)
194 * Make sure we don't exceed our individual target.
196 OldUsed
= InterlockedIncrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
197 if (OldUsed
>= (MiMemoryConsumers
[Consumer
].PagesTarget
- 1) &&
198 !MiIsBalancerThread())
202 InterlockedDecrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
203 return(STATUS_NO_MEMORY
);
205 MiTrimMemoryConsumer(Consumer
);
209 * Allocate always memory for the non paged pool and for the pager thread.
211 if (Consumer
== MC_NPPOOL
|| MiIsBalancerThread())
213 Page
= MmAllocPage(Consumer
, 0);
216 KEBUGCHECK(NO_PAGES_AVAILABLE
);
218 *AllocatedPage
= Page
;
219 if (MmStats
.NrFreePages
<= MiMinimumAvailablePages
&&
220 MiBalancerThreadHandle
!= NULL
)
222 KeSetEvent(&MiBalancerEvent
, IO_NO_INCREMENT
, FALSE
);
224 return(STATUS_SUCCESS
);
228 * Make sure we don't exceed global targets.
230 if (MmStats
.NrFreePages
<= MiMinimumAvailablePages
)
232 MM_ALLOCATION_REQUEST Request
;
236 InterlockedDecrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
237 return(STATUS_NO_MEMORY
);
240 /* Insert an allocation request. */
243 KeInitializeEvent(&Request
.Event
, NotificationEvent
, FALSE
);
244 InterlockedIncrementUL(&MiPagesRequired
);
246 KeAcquireSpinLock(&AllocationListLock
, &oldIrql
);
248 if (MiBalancerThreadHandle
!= NULL
)
250 KeSetEvent(&MiBalancerEvent
, IO_NO_INCREMENT
, FALSE
);
252 InsertTailList(&AllocationListHead
, &Request
.ListEntry
);
253 KeReleaseSpinLock(&AllocationListLock
, oldIrql
);
255 KeWaitForSingleObject(&Request
.Event
,
264 KEBUGCHECK(NO_PAGES_AVAILABLE
);
266 MmTransferOwnershipPage(Page
, Consumer
);
267 *AllocatedPage
= Page
;
268 InterlockedDecrementUL(&MiPagesRequired
);
269 return(STATUS_SUCCESS
);
273 * Actually allocate the page.
275 Page
= MmAllocPage(Consumer
, 0);
278 KEBUGCHECK(NO_PAGES_AVAILABLE
);
280 *AllocatedPage
= Page
;
282 return(STATUS_SUCCESS
);
286 MiBalancerThread(PVOID Unused
)
288 PVOID WaitObjects
[2];
297 WaitObjects
[0] = &MiBalancerEvent
;
298 WaitObjects
[1] = &MiBalancerTimer
;
302 Status
= KeWaitForMultipleObjects(2,
311 if (Status
== STATUS_SUCCESS
)
313 /* MiBalancerEvent */
315 while (MmStats
.NrFreePages
< MiMinimumAvailablePages
+ 5)
317 for (i
= 0; i
< MC_MAXIMUM
; i
++)
319 if (MiMemoryConsumers
[i
].Trim
!= NULL
)
322 Status
= MiMemoryConsumers
[i
].Trim(MiMinimumPagesPerRun
, 0, &NrFreedPages
);
323 if (!NT_SUCCESS(Status
))
330 InterlockedExchange(&MiBalancerWork
, 0);
333 else if (Status
== STATUS_SUCCESS
+ 1)
335 /* MiBalancerTimer */
336 ShouldRun
= MmStats
.NrFreePages
< MiMinimumAvailablePages
+ 5 ? TRUE
: FALSE
;
337 for (i
= 0; i
< MC_MAXIMUM
; i
++)
339 if (MiMemoryConsumers
[i
].Trim
!= NULL
)
341 NrPagesUsed
= MiMemoryConsumers
[i
].PagesUsed
;
342 if (NrPagesUsed
> MiMemoryConsumers
[i
].PagesTarget
|| ShouldRun
)
344 if (NrPagesUsed
> MiMemoryConsumers
[i
].PagesTarget
)
346 Target
= max (NrPagesUsed
- MiMemoryConsumers
[i
].PagesTarget
,
347 MiMinimumPagesPerRun
);
351 Target
= MiMinimumPagesPerRun
;
354 Status
= MiMemoryConsumers
[i
].Trim(Target
, 0, &NrFreedPages
);
355 if (!NT_SUCCESS(Status
))
365 DPRINT1("KeWaitForMultipleObjects failed, status = %x\n", Status
);
374 MiInitBalancerThread(VOID
)
378 #if !defined(__GNUC__)
380 LARGE_INTEGER dummyJunkNeeded
;
381 dummyJunkNeeded
.QuadPart
= -20000000; /* 2 sec */
387 KeInitializeEvent(&MiBalancerEvent
, SynchronizationEvent
, FALSE
);
388 KeInitializeTimerEx(&MiBalancerTimer
, SynchronizationTimer
);
389 KeSetTimerEx(&MiBalancerTimer
,
390 #if defined(__GNUC__)
391 (LARGE_INTEGER
)(LONGLONG
)-20000000LL, /* 2 sec */
398 Status
= PsCreateSystemThread(&MiBalancerThreadHandle
,
403 (PKSTART_ROUTINE
) MiBalancerThread
,
405 if (!NT_SUCCESS(Status
))
410 Priority
= LOW_REALTIME_PRIORITY
+ 1;
411 NtSetInformationThread(MiBalancerThreadHandle
,