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
,
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 MiMemoryConsumers
[MC_CACHE
].PagesTarget
= NrAvailablePages
/ 2;
64 MiMemoryConsumers
[MC_USER
].PagesTarget
=
65 NrAvailablePages
- MiMinimumAvailablePages
;
66 MiMemoryConsumers
[MC_PPOOL
].PagesTarget
= NrAvailablePages
/ 2;
67 MiMemoryConsumers
[MC_NPPOOL
].PagesTarget
= 0xFFFFFFFF;
68 MiMemoryConsumers
[MC_NPPOOL
].PagesUsed
= NrSystemPages
;
72 MmInitializeMemoryConsumer(ULONG Consumer
,
73 NTSTATUS (*Trim
)(ULONG Target
, ULONG Priority
,
76 MiMemoryConsumers
[Consumer
].Trim
= Trim
;
80 MmReleasePageMemoryConsumer(ULONG Consumer
, PFN_TYPE Page
)
82 PMM_ALLOCATION_REQUEST Request
;
88 DPRINT1("Tried to release page zero.\n");
92 KeAcquireSpinLock(&AllocationListLock
, &oldIrql
);
93 if (MmGetReferenceCountPage(Page
) == 1)
95 InterlockedDecrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
96 if (IsListEmpty(&AllocationListHead
) || MmStats
.NrFreePages
< MiMinimumAvailablePages
)
98 KeReleaseSpinLock(&AllocationListLock
, oldIrql
);
99 MmDereferencePage(Page
);
103 Entry
= RemoveHeadList(&AllocationListHead
);
104 Request
= CONTAINING_RECORD(Entry
, MM_ALLOCATION_REQUEST
, ListEntry
);
105 KeReleaseSpinLock(&AllocationListLock
, oldIrql
);
106 Request
->Page
= Page
;
107 KeSetEvent(&Request
->Event
, IO_NO_INCREMENT
, FALSE
);
112 KeReleaseSpinLock(&AllocationListLock
, oldIrql
);
113 MmDereferencePage(Page
);
116 return(STATUS_SUCCESS
);
120 MiTrimMemoryConsumer(ULONG Consumer
)
125 Target
= MiMemoryConsumers
[Consumer
].PagesUsed
-
126 MiMemoryConsumers
[Consumer
].PagesTarget
;
132 if (MiMemoryConsumers
[Consumer
].Trim
!= NULL
)
134 MiMemoryConsumers
[Consumer
].Trim(Target
, 0, &NrFreedPages
);
139 MmRebalanceMemoryConsumers(VOID
)
146 Target
= (MiMinimumAvailablePages
- MmStats
.NrFreePages
) + MiPagesRequired
;
147 Target
= max(Target
, (LONG
) MiMinimumPagesPerRun
);
149 for (i
= 0; i
< MC_MAXIMUM
&& Target
> 0; i
++)
151 if (MiMemoryConsumers
[i
].Trim
!= NULL
)
153 Status
= MiMemoryConsumers
[i
].Trim(Target
, 0, &NrFreedPages
);
154 if (!NT_SUCCESS(Status
))
158 Target
= Target
- NrFreedPages
;
164 MiIsBalancerThread(VOID
)
166 return MiBalancerThreadHandle
!= NULL
&&
167 PsGetCurrentThread() == MiBalancerThreadId
.UniqueThread
;
171 MmRequestPageMemoryConsumer(ULONG Consumer
, BOOLEAN CanWait
,
172 PPFN_TYPE AllocatedPage
)
179 * Make sure we don't exceed our individual target.
181 OldUsed
= InterlockedIncrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
182 if (OldUsed
>= (MiMemoryConsumers
[Consumer
].PagesTarget
- 1) &&
183 !MiIsBalancerThread())
187 InterlockedDecrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
188 return(STATUS_NO_MEMORY
);
190 MiTrimMemoryConsumer(Consumer
);
194 * Allocate always memory for the non paged pool and for the pager thread.
196 if (Consumer
== MC_NPPOOL
|| MiIsBalancerThread())
198 Page
= MmAllocPage(Consumer
, 0);
201 KEBUGCHECK(NO_PAGES_AVAILABLE
);
203 *AllocatedPage
= Page
;
204 if (MmStats
.NrFreePages
<= MiMinimumAvailablePages
&&
205 MiBalancerThreadHandle
!= NULL
)
207 KeSetEvent(&MiBalancerEvent
, IO_NO_INCREMENT
, FALSE
);
209 return(STATUS_SUCCESS
);
213 * Make sure we don't exceed global targets.
215 if (MmStats
.NrFreePages
<= MiMinimumAvailablePages
)
217 MM_ALLOCATION_REQUEST Request
;
221 InterlockedDecrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
222 return(STATUS_NO_MEMORY
);
225 /* Insert an allocation request. */
228 KeInitializeEvent(&Request
.Event
, NotificationEvent
, FALSE
);
229 InterlockedIncrementUL(&MiPagesRequired
);
231 KeAcquireSpinLock(&AllocationListLock
, &oldIrql
);
233 if (MiBalancerThreadHandle
!= NULL
)
235 KeSetEvent(&MiBalancerEvent
, IO_NO_INCREMENT
, FALSE
);
237 InsertTailList(&AllocationListHead
, &Request
.ListEntry
);
238 KeReleaseSpinLock(&AllocationListLock
, oldIrql
);
240 KeWaitForSingleObject(&Request
.Event
,
249 KEBUGCHECK(NO_PAGES_AVAILABLE
);
251 MmTransferOwnershipPage(Page
, Consumer
);
252 *AllocatedPage
= Page
;
253 InterlockedDecrementUL(&MiPagesRequired
);
254 return(STATUS_SUCCESS
);
258 * Actually allocate the page.
260 Page
= MmAllocPage(Consumer
, 0);
263 KEBUGCHECK(NO_PAGES_AVAILABLE
);
265 *AllocatedPage
= Page
;
267 return(STATUS_SUCCESS
);
271 MiBalancerThread(PVOID Unused
)
273 PVOID WaitObjects
[2];
282 WaitObjects
[0] = &MiBalancerEvent
;
283 WaitObjects
[1] = &MiBalancerTimer
;
287 Status
= KeWaitForMultipleObjects(2,
296 if (Status
== STATUS_SUCCESS
)
298 /* MiBalancerEvent */
300 while (MmStats
.NrFreePages
< MiMinimumAvailablePages
+ 5)
302 for (i
= 0; i
< MC_MAXIMUM
; i
++)
304 if (MiMemoryConsumers
[i
].Trim
!= NULL
)
307 Status
= MiMemoryConsumers
[i
].Trim(MiMinimumPagesPerRun
, 0, &NrFreedPages
);
308 if (!NT_SUCCESS(Status
))
315 InterlockedExchange(&MiBalancerWork
, 0);
318 else if (Status
== STATUS_SUCCESS
+ 1)
320 /* MiBalancerTimer */
321 ShouldRun
= MmStats
.NrFreePages
< MiMinimumAvailablePages
+ 5 ? TRUE
: FALSE
;
322 for (i
= 0; i
< MC_MAXIMUM
; i
++)
324 if (MiMemoryConsumers
[i
].Trim
!= NULL
)
326 NrPagesUsed
= MiMemoryConsumers
[i
].PagesUsed
;
327 if (NrPagesUsed
> MiMemoryConsumers
[i
].PagesTarget
|| ShouldRun
)
329 if (NrPagesUsed
> MiMemoryConsumers
[i
].PagesTarget
)
331 Target
= max (NrPagesUsed
- MiMemoryConsumers
[i
].PagesTarget
,
332 MiMinimumPagesPerRun
);
336 Target
= MiMinimumPagesPerRun
;
339 Status
= MiMemoryConsumers
[i
].Trim(Target
, 0, &NrFreedPages
);
340 if (!NT_SUCCESS(Status
))
350 DPRINT1("KeWaitForMultipleObjects failed, status = %x\n", Status
);
357 MiInitBalancerThread(VOID
)
361 #if !defined(__GNUC__)
363 LARGE_INTEGER dummyJunkNeeded
;
364 dummyJunkNeeded
.QuadPart
= -20000000; /* 2 sec */
370 KeInitializeEvent(&MiBalancerEvent
, SynchronizationEvent
, FALSE
);
371 KeInitializeTimerEx(&MiBalancerTimer
, SynchronizationTimer
);
372 KeSetTimerEx(&MiBalancerTimer
,
373 #if defined(__GNUC__)
374 (LARGE_INTEGER
)(LONGLONG
)-20000000LL, /* 2 sec */
381 Status
= PsCreateSystemThread(&MiBalancerThreadHandle
,
386 (PKSTART_ROUTINE
) MiBalancerThread
,
388 if (!NT_SUCCESS(Status
))
393 Priority
= LOW_REALTIME_PRIORITY
+ 1;
394 NtSetInformationThread(MiBalancerThreadHandle
,