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 #undef KeAcquireSpinLock
18 #define KeAcquireSpinLock(a,b) { _disable(); *(b) = KfAcquireSpinLock(a); }
20 #if defined (ALLOC_PRAGMA)
21 #pragma alloc_text(INIT, MmInitializeBalancer)
22 #pragma alloc_text(INIT, MmInitializeMemoryConsumer)
23 #pragma alloc_text(INIT, MiInitBalancerThread)
27 /* TYPES ********************************************************************/
28 typedef struct _MM_ALLOCATION_REQUEST
34 MM_ALLOCATION_REQUEST
, *PMM_ALLOCATION_REQUEST
;
36 /* GLOBALS ******************************************************************/
38 MM_MEMORY_CONSUMER MiMemoryConsumers
[MC_MAXIMUM
];
39 static ULONG MiMinimumAvailablePages
;
40 static ULONG MiNrTotalPages
;
41 static LIST_ENTRY AllocationListHead
;
42 static KSPIN_LOCK AllocationListLock
;
43 static ULONG MiPagesRequired
= 0;
44 static ULONG MiMinimumPagesPerRun
= 10;
46 static CLIENT_ID MiBalancerThreadId
;
47 static HANDLE MiBalancerThreadHandle
= NULL
;
48 static KEVENT MiBalancerEvent
;
49 static KTIMER MiBalancerTimer
;
50 static LONG MiBalancerWork
= 0;
52 /* FUNCTIONS ****************************************************************/
54 VOID
MmPrintMemoryStatistic(VOID
)
56 DbgPrint("MC_CACHE %d, MC_USER %d, MC_PPOOL %d, MC_NPPOOL %d, MmStats.NrFreePages %d\n",
57 MiMemoryConsumers
[MC_CACHE
].PagesUsed
, MiMemoryConsumers
[MC_USER
].PagesUsed
,
58 MiMemoryConsumers
[MC_PPOOL
].PagesUsed
, MiMemoryConsumers
[MC_NPPOOL
].PagesUsed
,
65 MmInitializeBalancer(ULONG NrAvailablePages
, ULONG NrSystemPages
)
67 memset(MiMemoryConsumers
, 0, sizeof(MiMemoryConsumers
));
68 InitializeListHead(&AllocationListHead
);
69 KeInitializeSpinLock(&AllocationListLock
);
71 MiNrTotalPages
= NrAvailablePages
;
74 MiMinimumAvailablePages
= 64;
75 MiMemoryConsumers
[MC_CACHE
].PagesTarget
= NrAvailablePages
/ 2;
76 MiMemoryConsumers
[MC_USER
].PagesTarget
=
77 NrAvailablePages
- MiMinimumAvailablePages
;
78 MiMemoryConsumers
[MC_PPOOL
].PagesTarget
= NrAvailablePages
/ 2;
79 MiMemoryConsumers
[MC_NPPOOL
].PagesTarget
= 0xFFFFFFFF;
80 MiMemoryConsumers
[MC_NPPOOL
].PagesUsed
= NrSystemPages
;
86 MmInitializeMemoryConsumer(ULONG Consumer
,
87 NTSTATUS (*Trim
)(ULONG Target
, ULONG Priority
,
90 MiMemoryConsumers
[Consumer
].Trim
= Trim
;
95 MmReleasePageMemoryConsumer(ULONG Consumer
, PFN_TYPE Page
)
97 PMM_ALLOCATION_REQUEST Request
;
103 DPRINT1("Tried to release page zero.\n");
107 KeAcquireSpinLock(&AllocationListLock
, &oldIrql
);
108 if (MmGetReferenceCountPage(Page
) == 1)
110 (void)InterlockedDecrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
111 if (IsListEmpty(&AllocationListHead
) || MmStats
.NrFreePages
< MiMinimumAvailablePages
)
113 KeReleaseSpinLock(&AllocationListLock
, oldIrql
);
114 MmDereferencePage(Page
);
118 Entry
= RemoveHeadList(&AllocationListHead
);
119 Request
= CONTAINING_RECORD(Entry
, MM_ALLOCATION_REQUEST
, ListEntry
);
120 KeReleaseSpinLock(&AllocationListLock
, oldIrql
);
121 Request
->Page
= Page
;
122 KeSetEvent(&Request
->Event
, IO_NO_INCREMENT
, FALSE
);
127 KeReleaseSpinLock(&AllocationListLock
, oldIrql
);
128 MmDereferencePage(Page
);
131 return(STATUS_SUCCESS
);
136 MiTrimMemoryConsumer(ULONG Consumer
)
141 Target
= MiMemoryConsumers
[Consumer
].PagesUsed
-
142 MiMemoryConsumers
[Consumer
].PagesTarget
;
148 if (MiMemoryConsumers
[Consumer
].Trim
!= NULL
)
150 MiMemoryConsumers
[Consumer
].Trim(Target
, 0, &NrFreedPages
);
156 MmRebalanceMemoryConsumers(VOID
)
163 Target
= (MiMinimumAvailablePages
- MmStats
.NrFreePages
) + MiPagesRequired
;
164 Target
= max(Target
, (LONG
) MiMinimumPagesPerRun
);
166 for (i
= 0; i
< MC_MAXIMUM
&& Target
> 0; i
++)
168 if (MiMemoryConsumers
[i
].Trim
!= NULL
)
170 Status
= MiMemoryConsumers
[i
].Trim(Target
, 0, &NrFreedPages
);
171 if (!NT_SUCCESS(Status
))
175 Target
= Target
- NrFreedPages
;
181 MiIsBalancerThread(VOID
)
183 return MiBalancerThreadHandle
!= NULL
&&
184 PsGetCurrentThread() == MiBalancerThreadId
.UniqueThread
;
189 MmRequestPageMemoryConsumer(ULONG Consumer
, BOOLEAN CanWait
,
190 PPFN_TYPE AllocatedPage
)
197 * Make sure we don't exceed our individual target.
199 OldUsed
= InterlockedIncrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
200 if (OldUsed
>= (MiMemoryConsumers
[Consumer
].PagesTarget
- 1) &&
201 !MiIsBalancerThread())
205 (void)InterlockedDecrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
206 return(STATUS_NO_MEMORY
);
208 MiTrimMemoryConsumer(Consumer
);
212 * Allocate always memory for the non paged pool and for the pager thread.
214 if (Consumer
== MC_NPPOOL
|| MiIsBalancerThread())
216 Page
= MmAllocPage(Consumer
, 0);
219 KEBUGCHECK(NO_PAGES_AVAILABLE
);
221 *AllocatedPage
= Page
;
222 if (MmStats
.NrFreePages
<= MiMinimumAvailablePages
&&
223 MiBalancerThreadHandle
!= NULL
)
225 KeSetEvent(&MiBalancerEvent
, IO_NO_INCREMENT
, FALSE
);
227 return(STATUS_SUCCESS
);
231 * Make sure we don't exceed global targets.
233 if (MmStats
.NrFreePages
<= MiMinimumAvailablePages
)
235 MM_ALLOCATION_REQUEST Request
;
239 (void)InterlockedDecrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
240 return(STATUS_NO_MEMORY
);
243 /* Insert an allocation request. */
246 KeInitializeEvent(&Request
.Event
, NotificationEvent
, FALSE
);
247 (void)InterlockedIncrementUL(&MiPagesRequired
);
249 KeAcquireSpinLock(&AllocationListLock
, &oldIrql
);
251 if (MiBalancerThreadHandle
!= NULL
)
253 KeSetEvent(&MiBalancerEvent
, IO_NO_INCREMENT
, FALSE
);
255 InsertTailList(&AllocationListHead
, &Request
.ListEntry
);
256 KeReleaseSpinLock(&AllocationListLock
, oldIrql
);
258 KeWaitForSingleObject(&Request
.Event
,
267 KEBUGCHECK(NO_PAGES_AVAILABLE
);
269 MmTransferOwnershipPage(Page
, Consumer
);
270 *AllocatedPage
= Page
;
271 (void)InterlockedDecrementUL(&MiPagesRequired
);
272 return(STATUS_SUCCESS
);
276 * Actually allocate the page.
278 Page
= MmAllocPage(Consumer
, 0);
281 KEBUGCHECK(NO_PAGES_AVAILABLE
);
283 *AllocatedPage
= Page
;
285 return(STATUS_SUCCESS
);
289 MiBalancerThread(PVOID Unused
)
291 PVOID WaitObjects
[2];
300 WaitObjects
[0] = &MiBalancerEvent
;
301 WaitObjects
[1] = &MiBalancerTimer
;
305 Status
= KeWaitForMultipleObjects(2,
314 if (Status
== STATUS_SUCCESS
)
316 /* MiBalancerEvent */
318 while (MmStats
.NrFreePages
< MiMinimumAvailablePages
+ 5)
320 for (i
= 0; i
< MC_MAXIMUM
; i
++)
322 if (MiMemoryConsumers
[i
].Trim
!= NULL
)
325 Status
= MiMemoryConsumers
[i
].Trim(MiMinimumPagesPerRun
, 0, &NrFreedPages
);
326 if (!NT_SUCCESS(Status
))
333 InterlockedExchange(&MiBalancerWork
, 0);
336 else if (Status
== STATUS_SUCCESS
+ 1)
338 /* MiBalancerTimer */
339 ShouldRun
= MmStats
.NrFreePages
< MiMinimumAvailablePages
+ 5 ? TRUE
: FALSE
;
340 for (i
= 0; i
< MC_MAXIMUM
; i
++)
342 if (MiMemoryConsumers
[i
].Trim
!= NULL
)
344 NrPagesUsed
= MiMemoryConsumers
[i
].PagesUsed
;
345 if (NrPagesUsed
> MiMemoryConsumers
[i
].PagesTarget
|| ShouldRun
)
347 if (NrPagesUsed
> MiMemoryConsumers
[i
].PagesTarget
)
349 Target
= max (NrPagesUsed
- MiMemoryConsumers
[i
].PagesTarget
,
350 MiMinimumPagesPerRun
);
354 Target
= MiMinimumPagesPerRun
;
357 Status
= MiMemoryConsumers
[i
].Trim(Target
, 0, &NrFreedPages
);
358 if (!NT_SUCCESS(Status
))
368 DPRINT1("KeWaitForMultipleObjects failed, status = %x\n", Status
);
377 MiInitBalancerThread(VOID
)
381 #if !defined(__GNUC__)
383 LARGE_INTEGER dummyJunkNeeded
;
384 dummyJunkNeeded
.QuadPart
= -20000000; /* 2 sec */
390 KeInitializeEvent(&MiBalancerEvent
, SynchronizationEvent
, FALSE
);
391 KeInitializeTimerEx(&MiBalancerTimer
, SynchronizationTimer
);
392 KeSetTimerEx(&MiBalancerTimer
,
393 #if defined(__GNUC__)
394 (LARGE_INTEGER
)(LONGLONG
)-20000000LL, /* 2 sec */
401 Status
= PsCreateSystemThread(&MiBalancerThreadHandle
,
406 (PKSTART_ROUTINE
) MiBalancerThread
,
408 if (!NT_SUCCESS(Status
))
413 Priority
= LOW_REALTIME_PRIORITY
+ 1;
414 NtSetInformationThread(MiBalancerThreadHandle
,