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