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