[NTOSKRNL]
[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 ((Entry = ExInterlockedRemoveHeadList(&AllocationListHead, &AllocationListLock)) == NULL)
112 {
113 OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
114 MmDereferencePage(Page);
115 KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
116 }
117 else
118 {
119 Request = CONTAINING_RECORD(Entry, MM_ALLOCATION_REQUEST, ListEntry);
120 MiZeroPhysicalPage(Page);
121 Request->Page = Page;
122 KeSetEvent(&Request->Event, IO_NO_INCREMENT, FALSE);
123 }
124 }
125 else
126 {
127 OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
128 MmDereferencePage(Page);
129 KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
130 }
131
132 return(STATUS_SUCCESS);
133 }
134
135 VOID
136 NTAPI
137 MiTrimMemoryConsumer(ULONG Consumer)
138 {
139 LONG Target = 0;
140 ULONG NrFreedPages = 0;
141 NTSTATUS Status;
142
143 /* Make sure we can trim this consumer */
144 if (!MiMemoryConsumers[Consumer].Trim)
145 return;
146
147 if (MiMemoryConsumers[Consumer].PagesUsed > MiMemoryConsumers[Consumer].PagesTarget)
148 {
149 /* Consumer page limit exceeded */
150 Target = max(Target, MiMemoryConsumers[Consumer].PagesUsed - MiMemoryConsumers[Consumer].PagesTarget);
151 }
152 if (MmAvailablePages < MiMinimumAvailablePages)
153 {
154 /* Global page limit exceeded */
155 Target = max(Target, MiMinimumAvailablePages - MmAvailablePages);
156 }
157
158 if (Target)
159 {
160 /* Swap at least MiMinimumPagesPerRun */
161 Target = max(Target, MiMinimumPagesPerRun);
162
163 /* Now swap the pages out */
164 Status = MiMemoryConsumers[Consumer].Trim(Target, 0, &NrFreedPages);
165
166 DPRINT("Trimming consumer %d: Freed %d pages with a target of %d pages\n", Consumer, NrFreedPages, Target);
167
168 if (!NT_SUCCESS(Status))
169 {
170 KeBugCheck(MEMORY_MANAGEMENT);
171 }
172 }
173 }
174
175 NTSTATUS
176 MmTrimUserMemory(ULONG Target, ULONG Priority, PULONG NrFreedPages)
177 {
178 PFN_NUMBER CurrentPage;
179 PFN_NUMBER NextPage;
180 NTSTATUS Status;
181
182 (*NrFreedPages) = 0;
183
184 CurrentPage = MmGetLRUFirstUserPage();
185 while (CurrentPage != 0 && Target > 0)
186 {
187 Status = MmPageOutPhysicalAddress(CurrentPage);
188 if (NT_SUCCESS(Status))
189 {
190 DPRINT("Succeeded\n");
191 Target--;
192 (*NrFreedPages)++;
193 }
194
195 NextPage = MmGetLRUNextUserPage(CurrentPage);
196 if (NextPage <= CurrentPage)
197 {
198 /* We wrapped around, so we're done */
199 break;
200 }
201 CurrentPage = NextPage;
202 }
203
204 return STATUS_SUCCESS;
205 }
206
207 static BOOLEAN
208 MiIsBalancerThread(VOID)
209 {
210 return (MiBalancerThreadHandle != NULL) &&
211 (PsGetCurrentThreadId() == MiBalancerThreadId.UniqueThread);
212 }
213
214 VOID
215 NTAPI
216 MmRebalanceMemoryConsumers(VOID)
217 {
218 if (MiBalancerThreadHandle != NULL &&
219 !MiIsBalancerThread())
220 {
221 KeSetEvent(&MiBalancerEvent, IO_NO_INCREMENT, FALSE);
222 }
223 }
224
225 NTSTATUS
226 NTAPI
227 MmRequestPageMemoryConsumer(ULONG Consumer, BOOLEAN CanWait,
228 PPFN_NUMBER AllocatedPage)
229 {
230 ULONG PagesUsed;
231 PFN_NUMBER Page;
232 KIRQL OldIrql;
233
234 /*
235 * Make sure we don't exceed our individual target.
236 */
237 PagesUsed = InterlockedIncrementUL(&MiMemoryConsumers[Consumer].PagesUsed);
238 if (PagesUsed > MiMemoryConsumers[Consumer].PagesTarget &&
239 !MiIsBalancerThread())
240 {
241 MmRebalanceMemoryConsumers();
242 }
243
244 /*
245 * Allocate always memory for the non paged pool and for the pager thread.
246 */
247 if ((Consumer == MC_SYSTEM) || MiIsBalancerThread())
248 {
249 OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
250 Page = MmAllocPage(Consumer);
251 KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
252 if (Page == 0)
253 {
254 KeBugCheck(NO_PAGES_AVAILABLE);
255 }
256 if (Consumer == MC_USER) MmInsertLRULastUserPage(Page);
257 *AllocatedPage = Page;
258 if (MmAvailablePages < MiMinimumAvailablePages)
259 MmRebalanceMemoryConsumers();
260 return(STATUS_SUCCESS);
261 }
262
263 /*
264 * Make sure we don't exceed global targets.
265 */
266 if (MmAvailablePages < MiMinimumAvailablePages)
267 {
268 MM_ALLOCATION_REQUEST Request;
269
270 if (!CanWait)
271 {
272 (void)InterlockedDecrementUL(&MiMemoryConsumers[Consumer].PagesUsed);
273 MmRebalanceMemoryConsumers();
274 return(STATUS_NO_MEMORY);
275 }
276
277 /* Insert an allocation request. */
278 Request.Page = 0;
279 KeInitializeEvent(&Request.Event, NotificationEvent, FALSE);
280
281 ExInterlockedInsertTailList(&AllocationListHead, &Request.ListEntry, &AllocationListLock);
282 MmRebalanceMemoryConsumers();
283
284 KeWaitForSingleObject(&Request.Event,
285 0,
286 KernelMode,
287 FALSE,
288 NULL);
289
290 Page = Request.Page;
291 if (Page == 0)
292 {
293 KeBugCheck(NO_PAGES_AVAILABLE);
294 }
295
296 if(Consumer == MC_USER) MmInsertLRULastUserPage(Page);
297 *AllocatedPage = Page;
298
299 if (MmAvailablePages < MiMinimumAvailablePages)
300 {
301 MmRebalanceMemoryConsumers();
302 }
303
304 return(STATUS_SUCCESS);
305 }
306
307 /*
308 * Actually allocate the page.
309 */
310 OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
311 Page = MmAllocPage(Consumer);
312 KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
313 if (Page == 0)
314 {
315 KeBugCheck(NO_PAGES_AVAILABLE);
316 }
317 if(Consumer == MC_USER) MmInsertLRULastUserPage(Page);
318 *AllocatedPage = Page;
319
320 if (MmAvailablePages < MiMinimumAvailablePages)
321 {
322 MmRebalanceMemoryConsumers();
323 }
324
325 return(STATUS_SUCCESS);
326 }
327
328 VOID NTAPI
329 MiBalancerThread(PVOID Unused)
330 {
331 PVOID WaitObjects[2];
332 NTSTATUS Status;
333 ULONG i;
334
335 WaitObjects[0] = &MiBalancerEvent;
336 WaitObjects[1] = &MiBalancerTimer;
337
338 while (1)
339 {
340 Status = KeWaitForMultipleObjects(2,
341 WaitObjects,
342 WaitAny,
343 Executive,
344 KernelMode,
345 FALSE,
346 NULL,
347 NULL);
348
349 if (Status == STATUS_WAIT_0 || Status == STATUS_WAIT_1)
350 {
351 for (i = 0; i < MC_MAXIMUM; i++)
352 {
353 MiTrimMemoryConsumer(i);
354 }
355
356 if (MmAvailablePages < MiMinimumAvailablePages)
357 {
358 /* This is really bad... */
359 DPRINT1("Balancer failed to resolve low memory condition! Complete memory exhaustion is imminent!\n");
360 }
361 }
362 else
363 {
364 DPRINT1("KeWaitForMultipleObjects failed, status = %x\n", Status);
365 KeBugCheck(MEMORY_MANAGEMENT);
366 }
367 }
368 }
369
370 VOID
371 INIT_FUNCTION
372 NTAPI
373 MiInitBalancerThread(VOID)
374 {
375 KPRIORITY Priority;
376 NTSTATUS Status;
377 #if !defined(__GNUC__)
378
379 LARGE_INTEGER dummyJunkNeeded;
380 dummyJunkNeeded.QuadPart = -20000000; /* 2 sec */
381 ;
382 #endif
383
384
385 KeInitializeEvent(&MiBalancerEvent, SynchronizationEvent, FALSE);
386 KeInitializeTimerEx(&MiBalancerTimer, SynchronizationTimer);
387 KeSetTimerEx(&MiBalancerTimer,
388 #if defined(__GNUC__)
389 (LARGE_INTEGER)(LONGLONG)-20000000LL, /* 2 sec */
390 #else
391 dummyJunkNeeded,
392 #endif
393 2000, /* 2 sec */
394 NULL);
395
396 Status = PsCreateSystemThread(&MiBalancerThreadHandle,
397 THREAD_ALL_ACCESS,
398 NULL,
399 NULL,
400 &MiBalancerThreadId,
401 (PKSTART_ROUTINE) MiBalancerThread,
402 NULL);
403 if (!NT_SUCCESS(Status))
404 {
405 KeBugCheck(MEMORY_MANAGEMENT);
406 }
407
408 Priority = LOW_REALTIME_PRIORITY + 1;
409 NtSetInformationThread(MiBalancerThreadHandle,
410 ThreadPriority,
411 &Priority,
412 sizeof(Priority));
413
414 }
415
416
417 /* EOF */