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