42527063b25e145ce2acb7a67671455874dd0597
[reactos.git] / reactos / ntoskrnl / mm / balance.c
1 /*
2 * ReactOS kernel
3 * Copyright (C) 1998, 1999, 2000, 2001 ReactOS Team
4 *
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.
9 *
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.
14 *
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.
18 */
19 /* $Id: balance.c,v 1.32 2004/08/01 21:57:35 navaraf Exp $
20 *
21 * PROJECT: ReactOS kernel
22 * FILE: ntoskrnl/mm/balance.c
23 * PURPOSE: kernel memory managment functions
24 * PROGRAMMER: David Welch (welch@cwcom.net)
25 * UPDATE HISTORY:
26 * Created 27/12/01
27 */
28
29 /* INCLUDES *****************************************************************/
30
31 #include <ddk/ntddk.h>
32 #include <internal/mm.h>
33 #include <ntos/minmax.h>
34 #include <reactos/bugcodes.h>
35
36 #define NDEBUG
37 #include <internal/debug.h>
38
39 /* TYPES ********************************************************************/
40 typedef struct _MM_ALLOCATION_REQUEST
41 {
42 PFN_TYPE Page;
43 LIST_ENTRY ListEntry;
44 KEVENT Event;
45 }
46 MM_ALLOCATION_REQUEST, *PMM_ALLOCATION_REQUEST;
47
48 /* GLOBALS ******************************************************************/
49
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;
57
58 static CLIENT_ID MiBalancerThreadId;
59 static HANDLE MiBalancerThreadHandle = NULL;
60 static KEVENT MiBalancerEvent;
61 static KTIMER MiBalancerTimer;
62 static LONG MiBalancerWork = 0;
63
64 /* FUNCTIONS ****************************************************************/
65
66 VOID MmPrintMemoryStatistic(VOID)
67 {
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,
71 MmStats.NrFreePages);
72 }
73
74 VOID INIT_FUNCTION
75 MmInitializeBalancer(ULONG NrAvailablePages, ULONG NrSystemPages)
76 {
77 memset(MiMemoryConsumers, 0, sizeof(MiMemoryConsumers));
78 InitializeListHead(&AllocationListHead);
79 KeInitializeSpinLock(&AllocationListLock);
80
81 MiNrTotalPages = NrAvailablePages;
82
83 /* Set up targets. */
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;
91 }
92
93 VOID INIT_FUNCTION
94 MmInitializeMemoryConsumer(ULONG Consumer,
95 NTSTATUS (*Trim)(ULONG Target, ULONG Priority,
96 PULONG NrFreed))
97 {
98 MiMemoryConsumers[Consumer].Trim = Trim;
99 }
100
101 NTSTATUS
102 MmReleasePageMemoryConsumer(ULONG Consumer, PFN_TYPE Page)
103 {
104 PMM_ALLOCATION_REQUEST Request;
105 PLIST_ENTRY Entry;
106 KIRQL oldIrql;
107
108 if (Page == 0)
109 {
110 DPRINT1("Tried to release page zero.\n");
111 KEBUGCHECK(0);
112 }
113
114 KeAcquireSpinLock(&AllocationListLock, &oldIrql);
115 if (MmGetReferenceCountPage(Page) == 1)
116 {
117 InterlockedDecrement((LONG *)&MiMemoryConsumers[Consumer].PagesUsed);
118 if (IsListEmpty(&AllocationListHead) || MmStats.NrFreePages < MiMinimumAvailablePages)
119 {
120 KeReleaseSpinLock(&AllocationListLock, oldIrql);
121 MmDereferencePage(Page);
122 }
123 else
124 {
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);
130 }
131 }
132 else
133 {
134 KeReleaseSpinLock(&AllocationListLock, oldIrql);
135 MmDereferencePage(Page);
136 }
137
138 return(STATUS_SUCCESS);
139 }
140
141 VOID
142 MiTrimMemoryConsumer(ULONG Consumer)
143 {
144 LONG Target;
145 ULONG NrFreedPages;
146
147 Target = MiMemoryConsumers[Consumer].PagesUsed -
148 MiMemoryConsumers[Consumer].PagesTarget;
149 if (Target < 1)
150 {
151 Target = 1;
152 }
153
154 if (MiMemoryConsumers[Consumer].Trim != NULL)
155 {
156 MiMemoryConsumers[Consumer].Trim(Target, 0, &NrFreedPages);
157 }
158 }
159
160 VOID
161 MmRebalanceMemoryConsumers(VOID)
162 {
163 LONG Target;
164 ULONG i;
165 ULONG NrFreedPages;
166 NTSTATUS Status;
167
168 Target = (MiMinimumAvailablePages - MmStats.NrFreePages) + MiPagesRequired;
169 Target = max(Target, (LONG) MiMinimumPagesPerRun);
170
171 for (i = 0; i < MC_MAXIMUM && Target > 0; i++)
172 {
173 if (MiMemoryConsumers[i].Trim != NULL)
174 {
175 Status = MiMemoryConsumers[i].Trim(Target, 0, &NrFreedPages);
176 if (!NT_SUCCESS(Status))
177 {
178 KEBUGCHECK(0);
179 }
180 Target = Target - NrFreedPages;
181 }
182 }
183 }
184
185 static BOOLEAN
186 MiIsBalancerThread(VOID)
187 {
188 return MiBalancerThreadHandle != NULL &&
189 PsGetCurrentThread() == MiBalancerThreadId.UniqueThread;
190 }
191
192 NTSTATUS
193 MmRequestPageMemoryConsumer(ULONG Consumer, BOOLEAN CanWait,
194 PPFN_TYPE AllocatedPage)
195 {
196 ULONG OldUsed;
197 PFN_TYPE Page;
198 KIRQL oldIrql;
199
200 /*
201 * Make sure we don't exceed our individual target.
202 */
203 OldUsed = InterlockedIncrement((LONG *)&MiMemoryConsumers[Consumer].PagesUsed);
204 if (OldUsed >= (MiMemoryConsumers[Consumer].PagesTarget - 1) &&
205 !MiIsBalancerThread())
206 {
207 if (!CanWait)
208 {
209 InterlockedDecrement((LONG *)&MiMemoryConsumers[Consumer].PagesUsed);
210 return(STATUS_NO_MEMORY);
211 }
212 MiTrimMemoryConsumer(Consumer);
213 }
214
215 /*
216 * Allocate always memory for the non paged pool and for the pager thread.
217 */
218 if (Consumer == MC_NPPOOL || MiIsBalancerThread())
219 {
220 Page = MmAllocPage(Consumer, 0);
221 if (Page == 0)
222 {
223 KEBUGCHECK(NO_PAGES_AVAILABLE);
224 }
225 *AllocatedPage = Page;
226 if (MmStats.NrFreePages <= MiMinimumAvailablePages &&
227 MiBalancerThreadHandle != NULL)
228 {
229 KeSetEvent(&MiBalancerEvent, IO_NO_INCREMENT, FALSE);
230 }
231 return(STATUS_SUCCESS);
232 }
233
234 /*
235 * Make sure we don't exceed global targets.
236 */
237 if (MmStats.NrFreePages <= MiMinimumAvailablePages)
238 {
239 MM_ALLOCATION_REQUEST Request;
240
241 if (!CanWait)
242 {
243 InterlockedDecrement((LONG *)&MiMemoryConsumers[Consumer].PagesUsed);
244 return(STATUS_NO_MEMORY);
245 }
246
247 /* Insert an allocation request. */
248 Request.Page = 0;
249
250 KeInitializeEvent(&Request.Event, NotificationEvent, FALSE);
251 InterlockedIncrement((LONG *)&MiPagesRequired);
252
253 KeAcquireSpinLock(&AllocationListLock, &oldIrql);
254
255 if (MiBalancerThreadHandle != NULL)
256 {
257 KeSetEvent(&MiBalancerEvent, IO_NO_INCREMENT, FALSE);
258 }
259 InsertTailList(&AllocationListHead, &Request.ListEntry);
260 KeReleaseSpinLock(&AllocationListLock, oldIrql);
261
262 KeWaitForSingleObject(&Request.Event,
263 0,
264 KernelMode,
265 FALSE,
266 NULL);
267
268 Page = Request.Page;
269 if (Page == 0)
270 {
271 KEBUGCHECK(NO_PAGES_AVAILABLE);
272 }
273 MmTransferOwnershipPage(Page, Consumer);
274 *AllocatedPage = Page;
275 InterlockedDecrement((LONG *)&MiPagesRequired);
276 return(STATUS_SUCCESS);
277 }
278
279 /*
280 * Actually allocate the page.
281 */
282 Page = MmAllocPage(Consumer, 0);
283 if (Page == 0)
284 {
285 KEBUGCHECK(NO_PAGES_AVAILABLE);
286 }
287 *AllocatedPage = Page;
288
289 return(STATUS_SUCCESS);
290 }
291
292 VOID STDCALL
293 MiBalancerThread(PVOID Unused)
294 {
295 PVOID WaitObjects[2];
296 NTSTATUS Status;
297 ULONG i;
298 ULONG NrFreedPages;
299 ULONG NrPagesUsed;
300 ULONG Target;
301 BOOLEAN ShouldRun;
302
303
304 WaitObjects[0] = &MiBalancerEvent;
305 WaitObjects[1] = &MiBalancerTimer;
306
307 while (1)
308 {
309 Status = KeWaitForMultipleObjects(2,
310 WaitObjects,
311 WaitAny,
312 Executive,
313 KernelMode,
314 FALSE,
315 NULL,
316 NULL);
317
318 if (Status == STATUS_SUCCESS)
319 {
320 /* MiBalancerEvent */
321 CHECKPOINT;
322 while (MmStats.NrFreePages < MiMinimumAvailablePages + 5)
323 {
324 for (i = 0; i < MC_MAXIMUM; i++)
325 {
326 if (MiMemoryConsumers[i].Trim != NULL)
327 {
328 NrFreedPages = 0;
329 Status = MiMemoryConsumers[i].Trim(MiMinimumPagesPerRun, 0, &NrFreedPages);
330 if (!NT_SUCCESS(Status))
331 {
332 KEBUGCHECK(0);
333 }
334 }
335 }
336 }
337 InterlockedExchange(&MiBalancerWork, 0);
338 CHECKPOINT;
339 }
340 else if (Status == STATUS_SUCCESS + 1)
341 {
342 /* MiBalancerTimer */
343 ShouldRun = MmStats.NrFreePages < MiMinimumAvailablePages + 5 ? TRUE : FALSE;
344 for (i = 0; i < MC_MAXIMUM; i++)
345 {
346 if (MiMemoryConsumers[i].Trim != NULL)
347 {
348 NrPagesUsed = MiMemoryConsumers[i].PagesUsed;
349 if (NrPagesUsed > MiMemoryConsumers[i].PagesTarget || ShouldRun)
350 {
351 if (NrPagesUsed > MiMemoryConsumers[i].PagesTarget)
352 {
353 Target = max (NrPagesUsed - MiMemoryConsumers[i].PagesTarget,
354 MiMinimumPagesPerRun);
355 }
356 else
357 {
358 Target = MiMinimumPagesPerRun;
359 }
360 NrFreedPages = 0;
361 Status = MiMemoryConsumers[i].Trim(Target, 0, &NrFreedPages);
362 if (!NT_SUCCESS(Status))
363 {
364 KEBUGCHECK(0);
365 }
366 }
367 }
368 }
369 }
370 else
371 {
372 DPRINT1("KeWaitForMultipleObjects failed, status = %x\n", Status);
373 KEBUGCHECK(0);
374 }
375 }
376 }
377
378 VOID INIT_FUNCTION
379 MiInitBalancerThread(VOID)
380 {
381 KPRIORITY Priority;
382 NTSTATUS Status;
383 #if !defined(__GNUC__)
384
385 LARGE_INTEGER dummyJunkNeeded;
386 dummyJunkNeeded.QuadPart = -20000000; /* 2 sec */
387 ;
388 #endif
389
390 CHECKPOINT;
391
392 KeInitializeEvent(&MiBalancerEvent, SynchronizationEvent, FALSE);
393 KeInitializeTimerEx(&MiBalancerTimer, SynchronizationTimer);
394 KeSetTimerEx(&MiBalancerTimer,
395 #if defined(__GNUC__)
396 (LARGE_INTEGER)(LONGLONG)-20000000LL, /* 2 sec */
397 #else
398 dummyJunkNeeded,
399 #endif
400 2000, /* 2 sec */
401 NULL);
402
403 Status = PsCreateSystemThread(&MiBalancerThreadHandle,
404 THREAD_ALL_ACCESS,
405 NULL,
406 NULL,
407 &MiBalancerThreadId,
408 (PKSTART_ROUTINE) MiBalancerThread,
409 NULL);
410 if (!NT_SUCCESS(Status))
411 {
412 KEBUGCHECK(0);
413 }
414
415 Priority = LOW_REALTIME_PRIORITY + 1;
416 NtSetInformationThread(MiBalancerThreadHandle,
417 ThreadPriority,
418 &Priority,
419 sizeof(Priority));
420
421 }
422
423
424 /* EOF */