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)
8 * Cameron Gutman (cameron.gutman@reactos.org)
11 /* INCLUDES *****************************************************************/
17 #include "ARM3/miarm.h"
19 #if defined (ALLOC_PRAGMA)
20 #pragma alloc_text(INIT, MmInitializeBalancer)
21 #pragma alloc_text(INIT, MmInitializeMemoryConsumer)
22 #pragma alloc_text(INIT, MiInitBalancerThread)
26 /* TYPES ********************************************************************/
27 typedef struct _MM_ALLOCATION_REQUEST
33 MM_ALLOCATION_REQUEST
, *PMM_ALLOCATION_REQUEST
;
34 /* GLOBALS ******************************************************************/
36 MM_MEMORY_CONSUMER MiMemoryConsumers
[MC_MAXIMUM
];
37 static ULONG MiMinimumAvailablePages
;
38 static ULONG MiNrTotalPages
;
39 static LIST_ENTRY AllocationListHead
;
40 static KSPIN_LOCK AllocationListLock
;
41 static ULONG MiMinimumPagesPerRun
;
43 static CLIENT_ID MiBalancerThreadId
;
44 static HANDLE MiBalancerThreadHandle
= NULL
;
45 static KEVENT MiBalancerEvent
;
46 static KTIMER MiBalancerTimer
;
48 /* FUNCTIONS ****************************************************************/
53 MmInitializeBalancer(ULONG NrAvailablePages
, ULONG NrSystemPages
)
55 memset(MiMemoryConsumers
, 0, sizeof(MiMemoryConsumers
));
56 InitializeListHead(&AllocationListHead
);
57 KeInitializeSpinLock(&AllocationListLock
);
59 MiNrTotalPages
= NrAvailablePages
;
62 MiMinimumAvailablePages
= 256;
63 MiMinimumPagesPerRun
= 256;
64 if ((NrAvailablePages
+ NrSystemPages
) >= 8192)
66 MiMemoryConsumers
[MC_CACHE
].PagesTarget
= NrAvailablePages
/ 4 * 3;
68 else if ((NrAvailablePages
+ NrSystemPages
) >= 4096)
70 MiMemoryConsumers
[MC_CACHE
].PagesTarget
= NrAvailablePages
/ 3 * 2;
74 MiMemoryConsumers
[MC_CACHE
].PagesTarget
= NrAvailablePages
/ 8;
76 MiMemoryConsumers
[MC_USER
].PagesTarget
= NrAvailablePages
- MiMinimumAvailablePages
;
82 MmInitializeMemoryConsumer(
84 NTSTATUS (*Trim
)(ULONG Target
, ULONG Priority
, PULONG NrFreed
))
86 MiMemoryConsumers
[Consumer
].Trim
= Trim
;
92 IN PFN_NUMBER PageFrameIndex
97 MmReleasePageMemoryConsumer(ULONG Consumer
, PFN_NUMBER Page
)
101 DPRINT1("Tried to release page zero.\n");
102 KeBugCheck(MEMORY_MANAGEMENT
);
105 if (MmGetReferenceCountPage(Page
) == 1)
107 if(Consumer
== MC_USER
) MmRemoveLRUUserPage(Page
);
108 (void)InterlockedDecrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
111 MmDereferencePage(Page
);
113 return(STATUS_SUCCESS
);
118 MiTrimMemoryConsumer(ULONG Consumer
, ULONG InitialTarget
)
120 ULONG Target
= InitialTarget
;
121 ULONG NrFreedPages
= 0;
124 /* Make sure we can trim this consumer */
125 if (!MiMemoryConsumers
[Consumer
].Trim
)
127 /* Return the unmodified initial target */
128 return InitialTarget
;
131 if (MiMemoryConsumers
[Consumer
].PagesUsed
> MiMemoryConsumers
[Consumer
].PagesTarget
)
133 /* Consumer page limit exceeded */
134 Target
= max(Target
, MiMemoryConsumers
[Consumer
].PagesUsed
- MiMemoryConsumers
[Consumer
].PagesTarget
);
136 if (MmAvailablePages
< MiMinimumAvailablePages
)
138 /* Global page limit exceeded */
139 Target
= (ULONG
)max(Target
, MiMinimumAvailablePages
- MmAvailablePages
);
146 /* If there was no initial target,
147 * swap at least MiMinimumPagesPerRun */
148 Target
= max(Target
, MiMinimumPagesPerRun
);
151 /* Now swap the pages out */
152 Status
= MiMemoryConsumers
[Consumer
].Trim(Target
, 0, &NrFreedPages
);
154 DPRINT("Trimming consumer %lu: Freed %lu pages with a target of %lu pages\n", Consumer
, NrFreedPages
, Target
);
156 if (!NT_SUCCESS(Status
))
158 KeBugCheck(MEMORY_MANAGEMENT
);
161 /* Update the target */
162 if (NrFreedPages
< Target
)
163 Target
-= NrFreedPages
;
167 /* Return the remaining pages needed to meet the target */
172 /* Initial target is zero and we don't have anything else to add */
178 MmTrimUserMemory(ULONG Target
, ULONG Priority
, PULONG NrFreedPages
)
180 PFN_NUMBER CurrentPage
;
186 CurrentPage
= MmGetLRUFirstUserPage();
187 while (CurrentPage
!= 0 && Target
> 0)
189 Status
= MmPageOutPhysicalAddress(CurrentPage
);
190 if (NT_SUCCESS(Status
))
192 DPRINT("Succeeded\n");
197 NextPage
= MmGetLRUNextUserPage(CurrentPage
);
198 if (NextPage
<= CurrentPage
)
200 /* We wrapped around, so we're done */
203 CurrentPage
= NextPage
;
206 return STATUS_SUCCESS
;
210 MiIsBalancerThread(VOID
)
212 return (MiBalancerThreadHandle
!= NULL
) &&
213 (PsGetCurrentThreadId() == MiBalancerThreadId
.UniqueThread
);
218 MmRebalanceMemoryConsumers(VOID
)
220 if (MiBalancerThreadHandle
!= NULL
&&
221 !MiIsBalancerThread())
223 KeSetEvent(&MiBalancerEvent
, IO_NO_INCREMENT
, FALSE
);
229 MmRequestPageMemoryConsumer(ULONG Consumer
, BOOLEAN CanWait
,
230 PPFN_NUMBER AllocatedPage
)
236 * Make sure we don't exceed our individual target.
238 PagesUsed
= InterlockedIncrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
239 if (PagesUsed
> MiMemoryConsumers
[Consumer
].PagesTarget
&&
240 !MiIsBalancerThread())
242 MmRebalanceMemoryConsumers();
246 * Allocate always memory for the non paged pool and for the pager thread.
248 if ((Consumer
== MC_SYSTEM
) /* || MiIsBalancerThread() */)
250 Page
= MmAllocPage(Consumer
);
253 KeBugCheck(NO_PAGES_AVAILABLE
);
255 if (Consumer
== MC_USER
) MmInsertLRULastUserPage(Page
);
256 *AllocatedPage
= Page
;
257 if (MmAvailablePages
< MiMinimumAvailablePages
)
258 MmRebalanceMemoryConsumers();
259 return(STATUS_SUCCESS
);
263 * Make sure we don't exceed global targets.
265 if (((MmAvailablePages
< MiMinimumAvailablePages
) && !MiIsBalancerThread())
266 || (MmAvailablePages
< (MiMinimumAvailablePages
/ 2)))
268 MM_ALLOCATION_REQUEST Request
;
272 (void)InterlockedDecrementUL(&MiMemoryConsumers
[Consumer
].PagesUsed
);
273 MmRebalanceMemoryConsumers();
274 return(STATUS_NO_MEMORY
);
277 /* Insert an allocation request. */
279 KeInitializeEvent(&Request
.Event
, NotificationEvent
, FALSE
);
281 ExInterlockedInsertTailList(&AllocationListHead
, &Request
.ListEntry
, &AllocationListLock
);
282 MmRebalanceMemoryConsumers();
284 KeWaitForSingleObject(&Request
.Event
,
293 KeBugCheck(NO_PAGES_AVAILABLE
);
296 if(Consumer
== MC_USER
) MmInsertLRULastUserPage(Page
);
297 *AllocatedPage
= Page
;
299 if (MmAvailablePages
< MiMinimumAvailablePages
)
301 MmRebalanceMemoryConsumers();
304 return(STATUS_SUCCESS
);
308 * Actually allocate the page.
310 Page
= MmAllocPage(Consumer
);
313 KeBugCheck(NO_PAGES_AVAILABLE
);
315 if(Consumer
== MC_USER
) MmInsertLRULastUserPage(Page
);
316 *AllocatedPage
= Page
;
318 if (MmAvailablePages
< MiMinimumAvailablePages
)
320 MmRebalanceMemoryConsumers();
323 return(STATUS_SUCCESS
);
328 MiBalancerThread(PVOID Unused
)
330 PVOID WaitObjects
[2];
334 WaitObjects
[0] = &MiBalancerEvent
;
335 WaitObjects
[1] = &MiBalancerTimer
;
339 Status
= KeWaitForMultipleObjects(2,
348 if (Status
== STATUS_WAIT_0
|| Status
== STATUS_WAIT_1
)
350 ULONG InitialTarget
= 0;
352 #if (_MI_PAGING_LEVELS == 2)
353 if (!MiIsBalancerThread())
355 /* Clean up the unused PDEs */
357 PEPROCESS Process
= PsGetCurrentProcess();
359 /* Acquire PFN lock */
360 KIRQL OldIrql
= MiAcquirePfnLock();
362 for (Address
= (ULONG_PTR
)MI_LOWEST_VAD_ADDRESS
;
363 Address
< (ULONG_PTR
)MM_HIGHEST_VAD_ADDRESS
;
364 Address
+= (PAGE_SIZE
* PTE_COUNT
))
366 if (MiQueryPageTableReferences((PVOID
)Address
) == 0)
368 pointerPde
= MiAddressToPde(Address
);
369 if (pointerPde
->u
.Hard
.Valid
)
370 MiDeletePte(pointerPde
, MiPdeToPte(pointerPde
), Process
, NULL
);
371 ASSERT(pointerPde
->u
.Hard
.Valid
== 0);
375 MiReleasePfnLock(OldIrql
);
380 ULONG OldTarget
= InitialTarget
;
382 /* Trim each consumer */
383 for (i
= 0; i
< MC_MAXIMUM
; i
++)
385 InitialTarget
= MiTrimMemoryConsumer(i
, InitialTarget
);
388 /* No pages left to swap! */
389 if (InitialTarget
!= 0 &&
390 InitialTarget
== OldTarget
)
393 KeBugCheck(NO_PAGES_AVAILABLE
);
396 while (InitialTarget
!= 0);
400 DPRINT1("KeWaitForMultipleObjects failed, status = %x\n", Status
);
401 KeBugCheck(MEMORY_MANAGEMENT
);
406 BOOLEAN
MmRosNotifyAvailablePage(PFN_NUMBER Page
)
409 PMM_ALLOCATION_REQUEST Request
;
412 /* Make sure the PFN lock is held */
413 MI_ASSERT_PFN_LOCK_HELD();
415 if (!MiMinimumAvailablePages
)
417 /* Dirty way to know if we were initialized. */
421 Entry
= ExInterlockedRemoveHeadList(&AllocationListHead
, &AllocationListLock
);
425 Request
= CONTAINING_RECORD(Entry
, MM_ALLOCATION_REQUEST
, ListEntry
);
426 MiZeroPhysicalPage(Page
);
427 Request
->Page
= Page
;
429 Pfn1
= MiGetPfnEntry(Page
);
430 ASSERT(Pfn1
->u3
.e2
.ReferenceCount
== 0);
431 Pfn1
->u3
.e2
.ReferenceCount
= 1;
432 Pfn1
->u3
.e1
.PageLocation
= ActiveAndValid
;
434 /* This marks the PFN as a ReactOS PFN */
435 Pfn1
->u4
.AweAllocation
= TRUE
;
437 /* Allocate the extra ReactOS Data and zero it out */
438 Pfn1
->u1
.SwapEntry
= 0;
439 Pfn1
->RmapListHead
= NULL
;
441 KeSetEvent(&Request
->Event
, IO_NO_INCREMENT
, FALSE
);
449 MiInitBalancerThread(VOID
)
453 #if !defined(__GNUC__)
455 LARGE_INTEGER dummyJunkNeeded
;
456 dummyJunkNeeded
.QuadPart
= -20000000; /* 2 sec */
461 KeInitializeEvent(&MiBalancerEvent
, SynchronizationEvent
, FALSE
);
462 KeInitializeTimerEx(&MiBalancerTimer
, SynchronizationTimer
);
463 KeSetTimerEx(&MiBalancerTimer
,
464 #if defined(__GNUC__)
465 (LARGE_INTEGER
)(LONGLONG
)-20000000LL, /* 2 sec */
472 Status
= PsCreateSystemThread(&MiBalancerThreadHandle
,
479 if (!NT_SUCCESS(Status
))
481 KeBugCheck(MEMORY_MANAGEMENT
);
484 Priority
= LOW_REALTIME_PRIORITY
+ 1;
485 NtSetInformationThread(MiBalancerThreadHandle
,