- Fixed the calculation of MiPagesRequired.
[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.16 2003/06/14 17:53:25 hbirr 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 = 10;
66
67 /* FUNCTIONS ****************************************************************/
68
69 VOID MmPrintMemoryStatistic(VOID)
70 {
71 DbgPrint("MC_CACHE %d, MC_USER %d, MC_PPOOL %d, MC_NPPOOL %d, MiNrAvailablePages %d\n",
72 MiMemoryConsumers[MC_CACHE].PagesUsed, MiMemoryConsumers[MC_USER].PagesUsed,
73 MiMemoryConsumers[MC_PPOOL].PagesUsed, MiMemoryConsumers[MC_NPPOOL].PagesUsed,
74 MiNrAvailablePages);
75 }
76
77 VOID
78 MmInitializeBalancer(ULONG NrAvailablePages)
79 {
80 memset(MiMemoryConsumers, 0, sizeof(MiMemoryConsumers));
81 InitializeListHead(&AllocationListHead);
82 KeInitializeSpinLock(&AllocationListLock);
83
84 MiNrAvailablePages = MiNrTotalPages = NrAvailablePages;
85
86 /* Set up targets. */
87 MiMinimumAvailablePages = 64;
88 MiMemoryConsumers[MC_CACHE].PagesTarget = NrAvailablePages / 2;
89 MiMemoryConsumers[MC_USER].PagesTarget =
90 NrAvailablePages - MiMinimumAvailablePages;
91 MiMemoryConsumers[MC_PPOOL].PagesTarget = NrAvailablePages / 2;
92 MiMemoryConsumers[MC_NPPOOL].PagesTarget = 0xFFFFFFFF;
93 }
94
95 VOID
96 MmInitializeMemoryConsumer(ULONG Consumer,
97 NTSTATUS (*Trim)(ULONG Target, ULONG Priority,
98 PULONG NrFreed))
99 {
100 MiMemoryConsumers[Consumer].Trim = Trim;
101 }
102
103 NTSTATUS
104 MmReleasePageMemoryConsumer(ULONG Consumer, PHYSICAL_ADDRESS Page)
105 {
106 PMM_ALLOCATION_REQUEST Request;
107 PLIST_ENTRY Entry;
108 KIRQL oldIrql;
109
110 if (Page.QuadPart == 0LL)
111 {
112 DPRINT1("Tried to release page zero.\n");
113 KeBugCheck(0);
114 }
115
116 KeAcquireSpinLock(&AllocationListLock, &oldIrql);
117 if (MmGetReferenceCountPage(Page) == 1)
118 {
119 InterlockedDecrement((LONG *)&MiMemoryConsumers[Consumer].PagesUsed);
120 InterlockedIncrement((LONG *)&MiNrAvailablePages);
121 if (IsListEmpty(&AllocationListHead))
122 {
123 KeReleaseSpinLock(&AllocationListLock, oldIrql);
124 MmDereferencePage(Page);
125 }
126 else
127 {
128 Entry = RemoveHeadList(&AllocationListHead);
129 Request = CONTAINING_RECORD(Entry, MM_ALLOCATION_REQUEST, ListEntry);
130 KeReleaseSpinLock(&AllocationListLock, oldIrql);
131 Request->Page = Page;
132 KeSetEvent(&Request->Event, IO_NO_INCREMENT, FALSE);
133 }
134 }
135 else
136 {
137 KeReleaseSpinLock(&AllocationListLock, oldIrql);
138 MmDereferencePage(Page);
139 }
140
141 return(STATUS_SUCCESS);
142 }
143
144 VOID
145 MiTrimMemoryConsumer(ULONG Consumer)
146 {
147 LONG Target;
148 ULONG NrFreedPages;
149
150 Target = MiMemoryConsumers[Consumer].PagesUsed -
151 MiMemoryConsumers[Consumer].PagesTarget;
152 if (Target < 1)
153 {
154 Target = 1;
155 }
156
157 if (MiMemoryConsumers[Consumer].Trim != NULL)
158 {
159 MiMemoryConsumers[Consumer].Trim(Target, 0, &NrFreedPages);
160 }
161 }
162
163 VOID
164 MiRebalanceMemoryConsumers(VOID)
165 {
166 LONG Target;
167 ULONG i;
168 ULONG NrFreedPages;
169 NTSTATUS Status;
170
171 Target = (MiMinimumAvailablePages - MiNrAvailablePages) + MiPagesRequired;
172 Target = max(Target, (LONG) MiMinimumPagesPerRun);
173
174 for (i = 0; i < MC_MAXIMUM && Target > 0; i++)
175 {
176 if (MiMemoryConsumers[i].Trim != NULL)
177 {
178 Status = MiMemoryConsumers[i].Trim(Target, 0, &NrFreedPages);
179 if (!NT_SUCCESS(Status))
180 {
181 KeBugCheck(0);
182 }
183 Target = Target - NrFreedPages;
184 }
185 }
186 if (Target > 0)
187 {
188 KeBugCheck(0);
189 }
190 }
191
192 NTSTATUS
193 MmRequestPageMemoryConsumer(ULONG Consumer, BOOLEAN CanWait,
194 PHYSICAL_ADDRESS* AllocatedPage)
195 {
196 ULONG OldUsed;
197 ULONG OldAvailable;
198 PHYSICAL_ADDRESS Page;
199 KIRQL oldIrql;
200
201 /*
202 * Make sure we don't exceed our individual target.
203 */
204 OldUsed = InterlockedIncrement((LONG *)&MiMemoryConsumers[Consumer].PagesUsed);
205 if (OldUsed >= (MiMemoryConsumers[Consumer].PagesTarget - 1) &&
206 WorkerThreadId != PsGetCurrentThreadId())
207 {
208 if (!CanWait)
209 {
210 InterlockedDecrement((LONG *)&MiMemoryConsumers[Consumer].PagesUsed);
211 return(STATUS_NO_MEMORY);
212 }
213 MiTrimMemoryConsumer(Consumer);
214 }
215
216 /*
217 * Make sure we don't exceed global targets.
218 */
219 OldAvailable = InterlockedDecrement((LONG *)&MiNrAvailablePages);
220 if (OldAvailable < MiMinimumAvailablePages)
221 {
222 MM_ALLOCATION_REQUEST Request;
223
224 if (!CanWait)
225 {
226 InterlockedIncrement((LONG *)&MiNrAvailablePages);
227 InterlockedDecrement((LONG *)&MiMemoryConsumers[Consumer].PagesUsed);
228 return(STATUS_NO_MEMORY);
229 }
230
231 /* Insert an allocation request. */
232 Request.Page.QuadPart = 0LL;
233 KeInitializeEvent(&Request.Event, NotificationEvent, FALSE);
234 InterlockedIncrement((LONG *)&MiPagesRequired);
235
236 KeAcquireSpinLock(&AllocationListLock, &oldIrql);
237 if (NrWorkingThreads == 0)
238 {
239 InsertTailList(&AllocationListHead, &Request.ListEntry);
240 NrWorkingThreads++;
241 KeReleaseSpinLock(&AllocationListLock, oldIrql);
242 WorkerThreadId = PsGetCurrentThreadId();
243 MiRebalanceMemoryConsumers();
244 KeAcquireSpinLock(&AllocationListLock, &oldIrql);
245 NrWorkingThreads--;
246 WorkerThreadId = 0;
247 KeReleaseSpinLock(&AllocationListLock, oldIrql);
248 }
249 else
250 {
251 if (WorkerThreadId == PsGetCurrentThreadId())
252 {
253 Page = MmAllocPage(Consumer, 0);
254 KeReleaseSpinLock(&AllocationListLock, oldIrql);
255 if (Page.QuadPart == 0LL)
256 {
257 KeBugCheck(0);
258 }
259 *AllocatedPage = Page;
260 InterlockedDecrement((LONG *)&MiPagesRequired);
261 return(STATUS_SUCCESS);
262 }
263 InsertTailList(&AllocationListHead, &Request.ListEntry);
264 KeReleaseSpinLock(&AllocationListLock, oldIrql);
265 }
266 KeWaitForSingleObject(&Request.Event,
267 0,
268 KernelMode,
269 FALSE,
270 NULL);
271
272 Page = Request.Page;
273 if (Page.QuadPart == 0LL)
274 {
275 KeBugCheck(0);
276 }
277 MmTransferOwnershipPage(Page, Consumer);
278 *AllocatedPage = Page;
279 InterlockedDecrement((LONG *)&MiPagesRequired);
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 }