3 * Copyright (C) 1998, 1999, 2000, 2001 ReactOS Team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 /* $Id: balance.c,v 1.32 2004/08/01 21:57:35 navaraf Exp $
21 * PROJECT: ReactOS kernel
22 * FILE: ntoskrnl/mm/balance.c
23 * PURPOSE: kernel memory managment functions
24 * PROGRAMMER: David Welch (welch@cwcom.net)
29 /* INCLUDES *****************************************************************/
31 #include <ddk/ntddk.h>
32 #include <internal/mm.h>
33 #include <ntos/minmax.h>
34 #include <reactos/bugcodes.h>
37 #include <internal/debug.h>
39 /* TYPES ********************************************************************/
40 typedef struct _MM_ALLOCATION_REQUEST
46 MM_ALLOCATION_REQUEST
, *PMM_ALLOCATION_REQUEST
;
48 /* GLOBALS ******************************************************************/
50 MM_MEMORY_CONSUMER MiMemoryConsumers
[MC_MAXIMUM
];
51 static ULONG MiMinimumAvailablePages
;
52 static ULONG MiNrTotalPages
;
53 static LIST_ENTRY AllocationListHead
;
54 static KSPIN_LOCK AllocationListLock
;
55 static ULONG MiPagesRequired
= 0;
56 static ULONG MiMinimumPagesPerRun
= 10;
58 static CLIENT_ID MiBalancerThreadId
;
59 static HANDLE MiBalancerThreadHandle
= NULL
;
60 static KEVENT MiBalancerEvent
;
61 static KTIMER MiBalancerTimer
;
62 static LONG MiBalancerWork
= 0;
64 /* FUNCTIONS ****************************************************************/
66 VOID
MmPrintMemoryStatistic(VOID
)
68 DbgPrint("MC_CACHE %d, MC_USER %d, MC_PPOOL %d, MC_NPPOOL %d, MmStats.NrFreePages %d\n",
69 MiMemoryConsumers
[MC_CACHE
].PagesUsed
, MiMemoryConsumers
[MC_USER
].PagesUsed
,
70 MiMemoryConsumers
[MC_PPOOL
].PagesUsed
, MiMemoryConsumers
[MC_NPPOOL
].PagesUsed
,
75 MmInitializeBalancer(ULONG NrAvailablePages
, ULONG NrSystemPages
)
77 memset(MiMemoryConsumers
, 0, sizeof(MiMemoryConsumers
));
78 InitializeListHead(&AllocationListHead
);
79 KeInitializeSpinLock(&AllocationListLock
);
81 MiNrTotalPages
= NrAvailablePages
;
84 MiMinimumAvailablePages
= 64;
85 MiMemoryConsumers
[MC_CACHE
].PagesTarget
= NrAvailablePages
/ 2;
86 MiMemoryConsumers
[MC_USER
].PagesTarget
=
87 NrAvailablePages
- MiMinimumAvailablePages
;
88 MiMemoryConsumers
[MC_PPOOL
].PagesTarget
= NrAvailablePages
/ 2;
89 MiMemoryConsumers
[MC_NPPOOL
].PagesTarget
= 0xFFFFFFFF;
90 MiMemoryConsumers
[MC_NPPOOL
].PagesUsed
= NrSystemPages
;
94 MmInitializeMemoryConsumer(ULONG Consumer
,
95 NTSTATUS (*Trim
)(ULONG Target
, ULONG Priority
,
98 MiMemoryConsumers
[Consumer
].Trim
= Trim
;
102 MmReleasePageMemoryConsumer(ULONG Consumer
, PFN_TYPE Page
)
104 PMM_ALLOCATION_REQUEST Request
;
110 DPRINT1("Tried to release page zero.\n");
114 KeAcquireSpinLock(&AllocationListLock
, &oldIrql
);
115 if (MmGetReferenceCountPage(Page
) == 1)
117 InterlockedDecrement((LONG
*)&MiMemoryConsumers
[Consumer
].PagesUsed
);
118 if (IsListEmpty(&AllocationListHead
) || MmStats
.NrFreePages
< MiMinimumAvailablePages
)
120 KeReleaseSpinLock(&AllocationListLock
, oldIrql
);
121 MmDereferencePage(Page
);
125 Entry
= RemoveHeadList(&AllocationListHead
);
126 Request
= CONTAINING_RECORD(Entry
, MM_ALLOCATION_REQUEST
, ListEntry
);
127 KeReleaseSpinLock(&AllocationListLock
, oldIrql
);
128 Request
->Page
= Page
;
129 KeSetEvent(&Request
->Event
, IO_NO_INCREMENT
, FALSE
);
134 KeReleaseSpinLock(&AllocationListLock
, oldIrql
);
135 MmDereferencePage(Page
);
138 return(STATUS_SUCCESS
);
142 MiTrimMemoryConsumer(ULONG Consumer
)
147 Target
= MiMemoryConsumers
[Consumer
].PagesUsed
-
148 MiMemoryConsumers
[Consumer
].PagesTarget
;
154 if (MiMemoryConsumers
[Consumer
].Trim
!= NULL
)
156 MiMemoryConsumers
[Consumer
].Trim(Target
, 0, &NrFreedPages
);
161 MmRebalanceMemoryConsumers(VOID
)
168 Target
= (MiMinimumAvailablePages
- MmStats
.NrFreePages
) + MiPagesRequired
;
169 Target
= max(Target
, (LONG
) MiMinimumPagesPerRun
);
171 for (i
= 0; i
< MC_MAXIMUM
&& Target
> 0; i
++)
173 if (MiMemoryConsumers
[i
].Trim
!= NULL
)
175 Status
= MiMemoryConsumers
[i
].Trim(Target
, 0, &NrFreedPages
);
176 if (!NT_SUCCESS(Status
))
180 Target
= Target
- NrFreedPages
;
186 MiIsBalancerThread(VOID
)
188 return MiBalancerThreadHandle
!= NULL
&&
189 PsGetCurrentThread() == MiBalancerThreadId
.UniqueThread
;
193 MmRequestPageMemoryConsumer(ULONG Consumer
, BOOLEAN CanWait
,
194 PPFN_TYPE AllocatedPage
)
201 * Make sure we don't exceed our individual target.
203 OldUsed
= InterlockedIncrement((LONG
*)&MiMemoryConsumers
[Consumer
].PagesUsed
);
204 if (OldUsed
>= (MiMemoryConsumers
[Consumer
].PagesTarget
- 1) &&
205 !MiIsBalancerThread())
209 InterlockedDecrement((LONG
*)&MiMemoryConsumers
[Consumer
].PagesUsed
);
210 return(STATUS_NO_MEMORY
);
212 MiTrimMemoryConsumer(Consumer
);
216 * Allocate always memory for the non paged pool and for the pager thread.
218 if (Consumer
== MC_NPPOOL
|| MiIsBalancerThread())
220 Page
= MmAllocPage(Consumer
, 0);
223 KEBUGCHECK(NO_PAGES_AVAILABLE
);
225 *AllocatedPage
= Page
;
226 if (MmStats
.NrFreePages
<= MiMinimumAvailablePages
&&
227 MiBalancerThreadHandle
!= NULL
)
229 KeSetEvent(&MiBalancerEvent
, IO_NO_INCREMENT
, FALSE
);
231 return(STATUS_SUCCESS
);
235 * Make sure we don't exceed global targets.
237 if (MmStats
.NrFreePages
<= MiMinimumAvailablePages
)
239 MM_ALLOCATION_REQUEST Request
;
243 InterlockedDecrement((LONG
*)&MiMemoryConsumers
[Consumer
].PagesUsed
);
244 return(STATUS_NO_MEMORY
);
247 /* Insert an allocation request. */
250 KeInitializeEvent(&Request
.Event
, NotificationEvent
, FALSE
);
251 InterlockedIncrement((LONG
*)&MiPagesRequired
);
253 KeAcquireSpinLock(&AllocationListLock
, &oldIrql
);
255 if (MiBalancerThreadHandle
!= NULL
)
257 KeSetEvent(&MiBalancerEvent
, IO_NO_INCREMENT
, FALSE
);
259 InsertTailList(&AllocationListHead
, &Request
.ListEntry
);
260 KeReleaseSpinLock(&AllocationListLock
, oldIrql
);
262 KeWaitForSingleObject(&Request
.Event
,
271 KEBUGCHECK(NO_PAGES_AVAILABLE
);
273 MmTransferOwnershipPage(Page
, Consumer
);
274 *AllocatedPage
= Page
;
275 InterlockedDecrement((LONG
*)&MiPagesRequired
);
276 return(STATUS_SUCCESS
);
280 * Actually allocate the page.
282 Page
= MmAllocPage(Consumer
, 0);
285 KEBUGCHECK(NO_PAGES_AVAILABLE
);
287 *AllocatedPage
= Page
;
289 return(STATUS_SUCCESS
);
293 MiBalancerThread(PVOID Unused
)
295 PVOID WaitObjects
[2];
304 WaitObjects
[0] = &MiBalancerEvent
;
305 WaitObjects
[1] = &MiBalancerTimer
;
309 Status
= KeWaitForMultipleObjects(2,
318 if (Status
== STATUS_SUCCESS
)
320 /* MiBalancerEvent */
322 while (MmStats
.NrFreePages
< MiMinimumAvailablePages
+ 5)
324 for (i
= 0; i
< MC_MAXIMUM
; i
++)
326 if (MiMemoryConsumers
[i
].Trim
!= NULL
)
329 Status
= MiMemoryConsumers
[i
].Trim(MiMinimumPagesPerRun
, 0, &NrFreedPages
);
330 if (!NT_SUCCESS(Status
))
337 InterlockedExchange(&MiBalancerWork
, 0);
340 else if (Status
== STATUS_SUCCESS
+ 1)
342 /* MiBalancerTimer */
343 ShouldRun
= MmStats
.NrFreePages
< MiMinimumAvailablePages
+ 5 ? TRUE
: FALSE
;
344 for (i
= 0; i
< MC_MAXIMUM
; i
++)
346 if (MiMemoryConsumers
[i
].Trim
!= NULL
)
348 NrPagesUsed
= MiMemoryConsumers
[i
].PagesUsed
;
349 if (NrPagesUsed
> MiMemoryConsumers
[i
].PagesTarget
|| ShouldRun
)
351 if (NrPagesUsed
> MiMemoryConsumers
[i
].PagesTarget
)
353 Target
= max (NrPagesUsed
- MiMemoryConsumers
[i
].PagesTarget
,
354 MiMinimumPagesPerRun
);
358 Target
= MiMinimumPagesPerRun
;
361 Status
= MiMemoryConsumers
[i
].Trim(Target
, 0, &NrFreedPages
);
362 if (!NT_SUCCESS(Status
))
372 DPRINT1("KeWaitForMultipleObjects failed, status = %x\n", Status
);
379 MiInitBalancerThread(VOID
)
383 #if !defined(__GNUC__)
385 LARGE_INTEGER dummyJunkNeeded
;
386 dummyJunkNeeded
.QuadPart
= -20000000; /* 2 sec */
392 KeInitializeEvent(&MiBalancerEvent
, SynchronizationEvent
, FALSE
);
393 KeInitializeTimerEx(&MiBalancerTimer
, SynchronizationTimer
);
394 KeSetTimerEx(&MiBalancerTimer
,
395 #if defined(__GNUC__)
396 (LARGE_INTEGER
)(LONGLONG
)-20000000LL, /* 2 sec */
403 Status
= PsCreateSystemThread(&MiBalancerThreadHandle
,
408 (PKSTART_ROUTINE
) MiBalancerThread
,
410 if (!NT_SUCCESS(Status
))
415 Priority
= LOW_REALTIME_PRIORITY
+ 1;
416 NtSetInformationThread(MiBalancerThreadHandle
,