91507a9dfac716b19990047e8bda7bdd9dd996c4
[reactos.git] / reactos / ntoskrnl / mm / balance.c
1 /*
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
6 *
7 * PROGRAMMERS: David Welch (welch@cwcom.net)
8 * Cameron Gutman (cameron.gutman@reactos.org)
9 */
10
11 /* INCLUDES *****************************************************************/
12
13 #include <ntoskrnl.h>
14 #define NDEBUG
15 #include <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_NUMBER Page;
28 LIST_ENTRY ListEntry;
29 KEVENT Event;
30 }
31 MM_ALLOCATION_REQUEST, *PMM_ALLOCATION_REQUEST;
32 /* GLOBALS ******************************************************************/
33
34 MM_MEMORY_CONSUMER MiMemoryConsumers[MC_MAXIMUM];
35 static ULONG MiMinimumAvailablePages;
36 static ULONG MiNrTotalPages;
37 static LIST_ENTRY AllocationListHead;
38 static KSPIN_LOCK AllocationListLock;
39 static ULONG MiMinimumPagesPerRun;
40
41 static CLIENT_ID MiBalancerThreadId;
42 static HANDLE MiBalancerThreadHandle = NULL;
43 static KEVENT MiBalancerEvent;
44 static KTIMER MiBalancerTimer;
45
46 /* FUNCTIONS ****************************************************************/
47
48 VOID
49 INIT_FUNCTION
50 NTAPI
51 MmInitializeBalancer(ULONG NrAvailablePages, ULONG NrSystemPages)
52 {
53 memset(MiMemoryConsumers, 0, sizeof(MiMemoryConsumers));
54 InitializeListHead(&AllocationListHead);
55 KeInitializeSpinLock(&AllocationListLock);
56
57 MiNrTotalPages = NrAvailablePages;
58
59 /* Set up targets. */
60 MiMinimumAvailablePages = 128;
61 MiMinimumPagesPerRun = 256;
62 if ((NrAvailablePages + NrSystemPages) >= 8192)
63 {
64 MiMemoryConsumers[MC_CACHE].PagesTarget = NrAvailablePages / 4 * 3;
65 }
66 else if ((NrAvailablePages + NrSystemPages) >= 4096)
67 {
68 MiMemoryConsumers[MC_CACHE].PagesTarget = NrAvailablePages / 3 * 2;
69 }
70 else
71 {
72 MiMemoryConsumers[MC_CACHE].PagesTarget = NrAvailablePages / 8;
73 }
74 MiMemoryConsumers[MC_USER].PagesTarget = NrAvailablePages - MiMinimumAvailablePages;
75 }
76
77 VOID
78 INIT_FUNCTION
79 NTAPI
80 MmInitializeMemoryConsumer(ULONG Consumer,
81 NTSTATUS (*Trim)(ULONG Target, ULONG Priority,
82 PULONG NrFreed))
83 {
84 MiMemoryConsumers[Consumer].Trim = Trim;
85 }
86
87 VOID
88 NTAPI
89 MiZeroPhysicalPage(
90 IN PFN_NUMBER PageFrameIndex
91 );
92
93 NTSTATUS
94 NTAPI
95 MmReleasePageMemoryConsumer(ULONG Consumer, PFN_NUMBER 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(MEMORY_MANAGEMENT);
105 }
106
107 if (MmGetReferenceCountPage(Page) == 1)
108 {
109 if(Consumer == MC_USER) MmRemoveLRUUserPage(Page);
110 (void)InterlockedDecrementUL(&MiMemoryConsumers[Consumer].PagesUsed);
111 if (MmAvailablePages < MiMinimumAvailablePages ||
112 (Entry = ExInterlockedRemoveHeadList(&AllocationListHead, &AllocationListLock)) == NULL)
113 {
114 OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
115 MmDereferencePage(Page);
116 KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
117 }
118 else
119 {
120 Request = CONTAINING_RECORD(Entry, MM_ALLOCATION_REQUEST, ListEntry);
121 MiZeroPhysicalPage(Page);
122 Request->Page = Page;
123 KeSetEvent(&Request->Event, IO_NO_INCREMENT, FALSE);
124 }
125 }
126 else
127 {
128 OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
129 MmDereferencePage(Page);
130 KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
131 }
132
133 return(STATUS_SUCCESS);
134 }
135
136 VOID
137 NTAPI
138 MiTrimMemoryConsumer(ULONG Consumer)
139 {
140 LONG Target = 0;
141 ULONG NrFreedPages = 0;
142 NTSTATUS Status;
143
144 /* Make sure we can trim this consumer */
145 if (!MiMemoryConsumers[Consumer].Trim)
146 return;
147
148 if (MiMemoryConsumers[Consumer].PagesUsed > MiMemoryConsumers[Consumer].PagesTarget)
149 {
150 /* Consumer page limit exceeded */
151 Target = max(Target, MiMemoryConsumers[Consumer].PagesUsed - MiMemoryConsumers[Consumer].PagesTarget);
152 }
153 if (MmAvailablePages < MiMinimumAvailablePages)
154 {
155 /* Global page limit exceeded */
156 Target = max(Target, MiMinimumAvailablePages - MmAvailablePages);
157 }
158
159 if (Target)
160 {
161 /* Swap at least MiMinimumPagesPerRun */
162 Target = max(Target, MiMinimumPagesPerRun);
163
164 /* Now swap the pages out */
165 Status = MiMemoryConsumers[Consumer].Trim(Target, 0, &NrFreedPages);
166
167 if (!ExpInTextModeSetup)
168 DPRINT1("Trimming consumer %d: Freed %d pages with a target of %d pages\n", Consumer, NrFreedPages, Target);
169
170 if (NrFreedPages == 0)
171 DPRINT1("Ran out of pages to swap! Complete memory exhaustion is imminent!\n");
172
173 if (!NT_SUCCESS(Status))
174 {
175 KeBugCheck(MEMORY_MANAGEMENT);
176 }
177 }
178 }
179
180 NTSTATUS
181 MmTrimUserMemory(ULONG Target, ULONG Priority, PULONG NrFreedPages)
182 {
183 PFN_NUMBER CurrentPage;
184 PFN_NUMBER NextPage;
185 NTSTATUS Status;
186
187 (*NrFreedPages) = 0;
188
189 CurrentPage = MmGetLRUFirstUserPage();
190 while (CurrentPage != 0 && Target > 0)
191 {
192 Status = MmPageOutPhysicalAddress(CurrentPage);
193 if (NT_SUCCESS(Status))
194 {
195 DPRINT("Succeeded\n");
196 Target--;
197 (*NrFreedPages)++;
198 }
199
200 NextPage = MmGetLRUNextUserPage(CurrentPage);
201 if (NextPage <= CurrentPage)
202 {
203 /* We wrapped around, so we're done */
204 break;
205 }
206 CurrentPage = NextPage;
207 }
208
209 return STATUS_SUCCESS;
210 }
211
212 static BOOLEAN
213 MiIsBalancerThread(VOID)
214 {
215 return (MiBalancerThreadHandle != NULL) &&
216 (PsGetCurrentThreadId() == MiBalancerThreadId.UniqueThread);
217 }
218
219 VOID
220 NTAPI
221 MmRebalanceMemoryConsumers(VOID)
222 {
223 if (MiBalancerThreadHandle != NULL &&
224 !MiIsBalancerThread())
225 {
226 KeSetEvent(&MiBalancerEvent, IO_NO_INCREMENT, FALSE);
227 }
228 }
229
230 NTSTATUS
231 NTAPI
232 MmRequestPageMemoryConsumer(ULONG Consumer, BOOLEAN CanWait,
233 PPFN_NUMBER AllocatedPage)
234 {
235 ULONG PagesUsed;
236 PFN_NUMBER Page;
237 KIRQL OldIrql;
238
239 /*
240 * Make sure we don't exceed our individual target.
241 */
242 PagesUsed = InterlockedIncrementUL(&MiMemoryConsumers[Consumer].PagesUsed);
243 if (PagesUsed > MiMemoryConsumers[Consumer].PagesTarget &&
244 !MiIsBalancerThread())
245 {
246 MmRebalanceMemoryConsumers();
247 }
248
249 /*
250 * Allocate always memory for the non paged pool and for the pager thread.
251 */
252 if ((Consumer == MC_SYSTEM) || MiIsBalancerThread())
253 {
254 OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
255 Page = MmAllocPage(Consumer);
256 KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
257 if (Page == 0)
258 {
259 KeBugCheck(NO_PAGES_AVAILABLE);
260 }
261 if (Consumer == MC_USER) MmInsertLRULastUserPage(Page);
262 *AllocatedPage = Page;
263 if (MmAvailablePages < MiMinimumAvailablePages)
264 MmRebalanceMemoryConsumers();
265 return(STATUS_SUCCESS);
266 }
267
268 /*
269 * Make sure we don't exceed global targets.
270 */
271 if (MmAvailablePages < MiMinimumAvailablePages)
272 {
273 MM_ALLOCATION_REQUEST Request;
274
275 if (!CanWait)
276 {
277 (void)InterlockedDecrementUL(&MiMemoryConsumers[Consumer].PagesUsed);
278 MmRebalanceMemoryConsumers();
279 return(STATUS_NO_MEMORY);
280 }
281
282 /* Insert an allocation request. */
283 Request.Page = 0;
284 KeInitializeEvent(&Request.Event, NotificationEvent, FALSE);
285
286 ExInterlockedInsertTailList(&AllocationListHead, &Request.ListEntry, &AllocationListLock);
287 MmRebalanceMemoryConsumers();
288
289 KeWaitForSingleObject(&Request.Event,
290 0,
291 KernelMode,
292 FALSE,
293 NULL);
294
295 Page = Request.Page;
296 if (Page == 0)
297 {
298 KeBugCheck(NO_PAGES_AVAILABLE);
299 }
300
301 if(Consumer == MC_USER) MmInsertLRULastUserPage(Page);
302 *AllocatedPage = Page;
303
304 if (MmAvailablePages < MiMinimumAvailablePages)
305 {
306 MmRebalanceMemoryConsumers();
307 }
308
309 return(STATUS_SUCCESS);
310 }
311
312 /*
313 * Actually allocate the page.
314 */
315 OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
316 Page = MmAllocPage(Consumer);
317 KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
318 if (Page == 0)
319 {
320 KeBugCheck(NO_PAGES_AVAILABLE);
321 }
322 if(Consumer == MC_USER) MmInsertLRULastUserPage(Page);
323 *AllocatedPage = Page;
324
325 if (MmAvailablePages < MiMinimumAvailablePages)
326 {
327 MmRebalanceMemoryConsumers();
328 }
329
330 return(STATUS_SUCCESS);
331 }
332
333 VOID NTAPI
334 MiBalancerThread(PVOID Unused)
335 {
336 PVOID WaitObjects[2];
337 NTSTATUS Status;
338 ULONG i;
339
340 WaitObjects[0] = &MiBalancerEvent;
341 WaitObjects[1] = &MiBalancerTimer;
342
343 while (1)
344 {
345 Status = KeWaitForMultipleObjects(2,
346 WaitObjects,
347 WaitAny,
348 Executive,
349 KernelMode,
350 FALSE,
351 NULL,
352 NULL);
353
354 if (Status == STATUS_WAIT_0 || Status == STATUS_WAIT_1)
355 {
356 for (i = 0; i < MC_MAXIMUM; i++)
357 {
358 MiTrimMemoryConsumer(i);
359 }
360 }
361 else
362 {
363 DPRINT1("KeWaitForMultipleObjects failed, status = %x\n", Status);
364 KeBugCheck(MEMORY_MANAGEMENT);
365 }
366 }
367 }
368
369 VOID
370 INIT_FUNCTION
371 NTAPI
372 MiInitBalancerThread(VOID)
373 {
374 KPRIORITY Priority;
375 NTSTATUS Status;
376 #if !defined(__GNUC__)
377
378 LARGE_INTEGER dummyJunkNeeded;
379 dummyJunkNeeded.QuadPart = -20000000; /* 2 sec */
380 ;
381 #endif
382
383
384 KeInitializeEvent(&MiBalancerEvent, SynchronizationEvent, FALSE);
385 KeInitializeTimerEx(&MiBalancerTimer, SynchronizationTimer);
386 KeSetTimerEx(&MiBalancerTimer,
387 #if defined(__GNUC__)
388 (LARGE_INTEGER)(LONGLONG)-20000000LL, /* 2 sec */
389 #else
390 dummyJunkNeeded,
391 #endif
392 2000, /* 2 sec */
393 NULL);
394
395 Status = PsCreateSystemThread(&MiBalancerThreadHandle,
396 THREAD_ALL_ACCESS,
397 NULL,
398 NULL,
399 &MiBalancerThreadId,
400 (PKSTART_ROUTINE) MiBalancerThread,
401 NULL);
402 if (!NT_SUCCESS(Status))
403 {
404 KeBugCheck(MEMORY_MANAGEMENT);
405 }
406
407 Priority = LOW_REALTIME_PRIORITY + 1;
408 NtSetInformationThread(MiBalancerThreadHandle,
409 ThreadPriority,
410 &Priority,
411 sizeof(Priority));
412
413 }
414
415
416 /* EOF */