2002-11-10 Casper S. Hornstrup <chorns@users.sourceforge.net>
[reactos.git] / reactos / ntoskrnl / mm / balance.c
1 /*
2 * ReactOS kernel
3 * Copyright (C) 1998, 1999, 2000, 2001 ReactOS Team
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19 /* $Id: balance.c,v 1.14 2002/11/10 18:17:42 chorns Exp $
20 *
21 * PROJECT: ReactOS kernel
22 * FILE: ntoskrnl/mm/balance.c
23 * PURPOSE: kernel memory managment functions
24 * PROGRAMMER: David Welch (welch@cwcom.net)
25 * UPDATE HISTORY:
26 * Created 27/12/01
27 */
28
29 /* INCLUDES *****************************************************************/
30
31 #include <ddk/ntddk.h>
32 #include <internal/mm.h>
33 #include <ntos/minmax.h>
34
35 #define NDEBUG
36 #include <internal/debug.h>
37
38 /* TYPES ********************************************************************/
39
40 typedef struct _MM_MEMORY_CONSUMER
41 {
42 ULONG PagesUsed;
43 ULONG PagesTarget;
44 NTSTATUS (*Trim)(ULONG Target, ULONG Priority, PULONG NrFreed);
45 } MM_MEMORY_CONSUMER, *PMM_MEMORY_CONSUMER;
46
47 typedef struct _MM_ALLOCATION_REQUEST
48 {
49 PHYSICAL_ADDRESS Page;
50 LIST_ENTRY ListEntry;
51 KEVENT Event;
52 } MM_ALLOCATION_REQUEST, *PMM_ALLOCATION_REQUEST;
53
54 /* GLOBALS ******************************************************************/
55
56 static MM_MEMORY_CONSUMER MiMemoryConsumers[MC_MAXIMUM];
57 static ULONG MiMinimumAvailablePages;
58 static ULONG MiNrAvailablePages;
59 static ULONG MiNrTotalPages;
60 static LIST_ENTRY AllocationListHead;
61 static KSPIN_LOCK AllocationListLock;
62 static ULONG NrWorkingThreads = 0;
63 static HANDLE WorkerThreadId;
64 static ULONG MiPagesRequired = 0;
65 static ULONG MiMinimumPagesPerRun = 1;
66
67 /* FUNCTIONS ****************************************************************/
68
69 VOID MmPrintMemoryStatistic(VOID)
70 {
71 DbgPrint("MC_CACHE %d, MC_USER %d, MC_PPOOL %d, MC_NPPOOL %d\n",
72 MiMemoryConsumers[MC_CACHE].PagesUsed,
73 MiMemoryConsumers[MC_USER].PagesUsed,
74 MiMemoryConsumers[MC_PPOOL].PagesUsed,
75 MiMemoryConsumers[MC_NPPOOL].PagesUsed);
76 }
77
78 VOID
79 MmInitializeBalancer(ULONG NrAvailablePages)
80 {
81 memset(MiMemoryConsumers, 0, sizeof(MiMemoryConsumers));
82 InitializeListHead(&AllocationListHead);
83 KeInitializeSpinLock(&AllocationListLock);
84
85 MiNrAvailablePages = MiNrTotalPages = NrAvailablePages;
86
87 /* Set up targets. */
88 MiMinimumAvailablePages = 64;
89 MiMemoryConsumers[MC_CACHE].PagesTarget = NrAvailablePages / 2;
90 MiMemoryConsumers[MC_USER].PagesTarget =
91 NrAvailablePages - MiMinimumAvailablePages;
92 MiMemoryConsumers[MC_PPOOL].PagesTarget = NrAvailablePages / 2;
93 MiMemoryConsumers[MC_NPPOOL].PagesTarget = 0xFFFFFFFF;
94 }
95
96 VOID
97 MmInitializeMemoryConsumer(ULONG Consumer,
98 NTSTATUS (*Trim)(ULONG Target, ULONG Priority,
99 PULONG NrFreed))
100 {
101 MiMemoryConsumers[Consumer].Trim = Trim;
102 }
103
104 NTSTATUS
105 MmReleasePageMemoryConsumer(ULONG Consumer, PHYSICAL_ADDRESS Page)
106 {
107 PMM_ALLOCATION_REQUEST Request;
108 PLIST_ENTRY Entry;
109 KIRQL oldIrql;
110
111 if (Page.QuadPart == 0LL)
112 {
113 DPRINT1("Tried to release page zero.\n");
114 KeBugCheck(0);
115 }
116
117 KeAcquireSpinLock(&AllocationListLock, &oldIrql);
118 if (MmGetReferenceCountPage(Page) == 1)
119 {
120 InterlockedDecrement(&MiMemoryConsumers[Consumer].PagesUsed);
121 InterlockedIncrement(&MiNrAvailablePages);
122 InterlockedDecrement(&MiPagesRequired);
123 if (IsListEmpty(&AllocationListHead))
124 {
125 KeReleaseSpinLock(&AllocationListLock, oldIrql);
126 MmDereferencePage(Page);
127 }
128 else
129 {
130 Entry = RemoveHeadList(&AllocationListHead);
131 Request = CONTAINING_RECORD(Entry, MM_ALLOCATION_REQUEST, ListEntry);
132 KeReleaseSpinLock(&AllocationListLock, oldIrql);
133 Request->Page = Page;
134 KeSetEvent(&Request->Event, IO_NO_INCREMENT, FALSE);
135 }
136 }
137 else
138 {
139 KeReleaseSpinLock(&AllocationListLock, oldIrql);
140 MmDereferencePage(Page);
141 }
142
143 return(STATUS_SUCCESS);
144 }
145
146 VOID
147 MiTrimMemoryConsumer(ULONG Consumer)
148 {
149 LONG Target;
150 ULONG NrFreedPages;
151
152 Target = MiMemoryConsumers[Consumer].PagesUsed -
153 MiMemoryConsumers[Consumer].PagesTarget;
154 if (Target < 0)
155 {
156 Target = 1;
157 }
158
159 if (MiMemoryConsumers[Consumer].Trim != NULL)
160 {
161 MiMemoryConsumers[Consumer].Trim(Target, 0, &NrFreedPages);
162 }
163 }
164
165 VOID
166 MiRebalanceMemoryConsumers(VOID)
167 {
168 LONG Target;
169 ULONG i;
170 ULONG NrFreedPages;
171 NTSTATUS Status;
172
173 Target = (MiMinimumAvailablePages - MiNrAvailablePages) + MiPagesRequired;
174 Target = min(Target, (LONG) MiMinimumPagesPerRun);
175
176 for (i = 0; i < MC_MAXIMUM && Target > 0; i++)
177 {
178 if (MiMemoryConsumers[i].Trim != NULL)
179 {
180 Status = MiMemoryConsumers[i].Trim(Target, 0, &NrFreedPages);
181 if (!NT_SUCCESS(Status))
182 {
183 KeBugCheck(0);
184 }
185 Target = Target - NrFreedPages;
186 }
187 }
188 if (Target > 0)
189 {
190 KeBugCheck(0);
191 }
192 }
193
194 NTSTATUS
195 MmRequestPageMemoryConsumer(ULONG Consumer, BOOLEAN CanWait,
196 PHYSICAL_ADDRESS* AllocatedPage)
197 {
198 ULONG OldUsed;
199 ULONG OldAvailable;
200 PHYSICAL_ADDRESS Page;
201 KIRQL oldIrql;
202
203 /*
204 * Make sure we don't exceed our individual target.
205 */
206 OldUsed = InterlockedIncrement(&MiMemoryConsumers[Consumer].PagesUsed);
207 if (OldUsed >= (MiMemoryConsumers[Consumer].PagesTarget - 1) &&
208 WorkerThreadId != PsGetCurrentThreadId())
209 {
210 if (!CanWait)
211 {
212 InterlockedDecrement(&MiMemoryConsumers[Consumer].PagesUsed);
213 return(STATUS_NO_MEMORY);
214 }
215 MiTrimMemoryConsumer(Consumer);
216 }
217
218 /*
219 * Make sure we don't exceed global targets.
220 */
221 OldAvailable = InterlockedDecrement(&MiNrAvailablePages);
222 if (OldAvailable < MiMinimumAvailablePages)
223 {
224 MM_ALLOCATION_REQUEST Request;
225
226 if (!CanWait)
227 {
228 InterlockedIncrement(&MiNrAvailablePages);
229 InterlockedDecrement(&MiMemoryConsumers[Consumer].PagesUsed);
230 return(STATUS_NO_MEMORY);
231 }
232
233 /* Insert an allocation request. */
234 Request.Page.QuadPart = 0LL;
235 KeInitializeEvent(&Request.Event, NotificationEvent, FALSE);
236 InterlockedIncrement(&MiPagesRequired);
237
238 KeAcquireSpinLock(&AllocationListLock, &oldIrql);
239 if (NrWorkingThreads == 0)
240 {
241 InsertTailList(&AllocationListHead, &Request.ListEntry);
242 NrWorkingThreads++;
243 KeReleaseSpinLock(&AllocationListLock, oldIrql);
244 WorkerThreadId = PsGetCurrentThreadId();
245 MiRebalanceMemoryConsumers();
246 KeAcquireSpinLock(&AllocationListLock, &oldIrql);
247 NrWorkingThreads--;
248 WorkerThreadId = 0;
249 KeReleaseSpinLock(&AllocationListLock, oldIrql);
250 }
251 else
252 {
253 if (WorkerThreadId == PsGetCurrentThreadId())
254 {
255 Page = MmAllocPage(Consumer, 0);
256 KeReleaseSpinLock(&AllocationListLock, oldIrql);
257 if (Page.QuadPart == 0LL)
258 {
259 KeBugCheck(0);
260 }
261 *AllocatedPage = Page;
262 return(STATUS_SUCCESS);
263 }
264 InsertTailList(&AllocationListHead, &Request.ListEntry);
265 KeReleaseSpinLock(&AllocationListLock, oldIrql);
266 }
267 KeWaitForSingleObject(&Request.Event,
268 0,
269 KernelMode,
270 FALSE,
271 NULL);
272
273 Page = Request.Page;
274 if (Page.QuadPart == 0LL)
275 {
276 KeBugCheck(0);
277 }
278 MmTransferOwnershipPage(Page, Consumer);
279 *AllocatedPage = Page;
280 return(STATUS_SUCCESS);
281 }
282
283 /*
284 * Actually allocate the page.
285 */
286 Page = MmAllocPage(Consumer, 0);
287 if (Page.QuadPart == 0LL)
288 {
289 KeBugCheck(0);
290 }
291 *AllocatedPage = Page;
292
293 return(STATUS_SUCCESS);
294 }