Use free Windows DDK and compile with latest MinGW releases.
[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.12 2002/09/07 15:12:59 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 <ntoskrnl.h>
32
33 #define NDEBUG
34 #include <internal/debug.h>
35
36
37 /* TYPES ********************************************************************/
38
39 typedef struct _MM_MEMORY_CONSUMER
40 {
41 ULONG PagesUsed;
42 ULONG PagesTarget;
43 NTSTATUS (*Trim)(ULONG Target, ULONG Priority, PULONG NrFreed);
44 } MM_MEMORY_CONSUMER, *PMM_MEMORY_CONSUMER;
45
46 typedef struct _MM_ALLOCATION_REQUEST
47 {
48 PHYSICAL_ADDRESS Page;
49 LIST_ENTRY ListEntry;
50 KEVENT Event;
51 } MM_ALLOCATION_REQUEST, *PMM_ALLOCATION_REQUEST;
52
53 /* GLOBALS ******************************************************************/
54
55 static MM_MEMORY_CONSUMER MiMemoryConsumers[MC_MAXIMUM];
56 static ULONG MiMinimumAvailablePages;
57 static ULONG MiNrAvailablePages;
58 static ULONG MiNrTotalPages;
59 static LIST_ENTRY AllocationListHead;
60 static KSPIN_LOCK AllocationListLock;
61 static ULONG NrWorkingThreads = 0;
62 static HANDLE WorkerThreadId;
63 static ULONG MiPagesRequired = 0;
64 static ULONG MiMinimumPagesPerRun = 1;
65
66 /* FUNCTIONS ****************************************************************/
67
68 VOID MmPrintMemoryStatistic(VOID)
69 {
70 DbgPrint("MC_CACHE %d, MC_USER %d, MC_PPOOL %d, MC_NPPOOL %d\n",
71 MiMemoryConsumers[MC_CACHE].PagesUsed,
72 MiMemoryConsumers[MC_USER].PagesUsed,
73 MiMemoryConsumers[MC_PPOOL].PagesUsed,
74 MiMemoryConsumers[MC_NPPOOL].PagesUsed);
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(&MiMemoryConsumers[Consumer].PagesUsed);
120 InterlockedIncrement(&MiNrAvailablePages);
121 InterlockedDecrement(&MiPagesRequired);
122 if (IsListEmpty(&AllocationListHead))
123 {
124 KeReleaseSpinLock(&AllocationListLock, oldIrql);
125 MmDereferencePage(Page);
126 }
127 else
128 {
129 Entry = RemoveHeadList(&AllocationListHead);
130 Request = CONTAINING_RECORD(Entry, MM_ALLOCATION_REQUEST, ListEntry);
131 KeReleaseSpinLock(&AllocationListLock, oldIrql);
132 Request->Page = Page;
133 KeSetEvent(&Request->Event, IO_NO_INCREMENT, FALSE);
134 }
135 }
136 else
137 {
138 KeReleaseSpinLock(&AllocationListLock, oldIrql);
139 MmDereferencePage(Page);
140 }
141
142 return(STATUS_SUCCESS);
143 }
144
145 VOID
146 MiTrimMemoryConsumer(ULONG Consumer)
147 {
148 LONG Target;
149 ULONG NrFreedPages;
150
151 Target = MiMemoryConsumers[Consumer].PagesUsed -
152 MiMemoryConsumers[Consumer].PagesTarget;
153 if (Target < 0)
154 {
155 Target = 1;
156 }
157
158 if (MiMemoryConsumers[Consumer].Trim != NULL)
159 {
160 MiMemoryConsumers[Consumer].Trim(Target, 0, &NrFreedPages);
161 }
162 }
163
164 VOID
165 MiRebalanceMemoryConsumers(VOID)
166 {
167 LONG Target;
168 ULONG i;
169 ULONG NrFreedPages;
170 NTSTATUS Status;
171
172 Target = (MiMinimumAvailablePages - MiNrAvailablePages) + MiPagesRequired;
173 Target = min(Target, MiMinimumPagesPerRun);
174
175 for (i = 0; i < MC_MAXIMUM && Target > 0; i++)
176 {
177 if (MiMemoryConsumers[i].Trim != NULL)
178 {
179 Status = MiMemoryConsumers[i].Trim(Target, 0, &NrFreedPages);
180 if (!NT_SUCCESS(Status))
181 {
182 KeBugCheck(0);
183 }
184 Target = Target - NrFreedPages;
185 }
186 }
187 if (Target > 0)
188 {
189 KeBugCheck(0);
190 }
191 }
192
193 NTSTATUS
194 MmRequestPageMemoryConsumer(ULONG Consumer, BOOLEAN CanWait,
195 PHYSICAL_ADDRESS* AllocatedPage)
196 {
197 ULONG OldUsed;
198 ULONG OldAvailable;
199 PHYSICAL_ADDRESS Page;
200 KIRQL oldIrql;
201
202 /*
203 * Make sure we don't exceed our individual target.
204 */
205 OldUsed = InterlockedIncrement(&MiMemoryConsumers[Consumer].PagesUsed);
206 if (OldUsed >= (MiMemoryConsumers[Consumer].PagesTarget - 1) &&
207 WorkerThreadId != PsGetCurrentThreadId())
208 {
209 if (!CanWait)
210 {
211 InterlockedDecrement(&MiMemoryConsumers[Consumer].PagesUsed);
212 return(STATUS_NO_MEMORY);
213 }
214 MiTrimMemoryConsumer(Consumer);
215 }
216
217 /*
218 * Make sure we don't exceed global targets.
219 */
220 OldAvailable = InterlockedDecrement(&MiNrAvailablePages);
221 if (OldAvailable < MiMinimumAvailablePages)
222 {
223 MM_ALLOCATION_REQUEST Request;
224
225 if (!CanWait)
226 {
227 InterlockedIncrement(&MiNrAvailablePages);
228 InterlockedDecrement(&MiMemoryConsumers[Consumer].PagesUsed);
229 return(STATUS_NO_MEMORY);
230 }
231
232 /* Insert an allocation request. */
233 Request.Page.QuadPart = 0LL;
234 KeInitializeEvent(&Request.Event, NotificationEvent, FALSE);
235 InterlockedIncrement(&MiPagesRequired);
236
237 KeAcquireSpinLock(&AllocationListLock, &oldIrql);
238 if (NrWorkingThreads == 0)
239 {
240 InsertTailList(&AllocationListHead, &Request.ListEntry);
241 NrWorkingThreads++;
242 KeReleaseSpinLock(&AllocationListLock, oldIrql);
243 WorkerThreadId = PsGetCurrentThreadId();
244 MiRebalanceMemoryConsumers();
245 KeAcquireSpinLock(&AllocationListLock, &oldIrql);
246 NrWorkingThreads--;
247 WorkerThreadId = 0;
248 KeReleaseSpinLock(&AllocationListLock, oldIrql);
249 }
250 else
251 {
252 if (WorkerThreadId == PsGetCurrentThreadId())
253 {
254 Page = MmAllocPage(Consumer, 0);
255 KeReleaseSpinLock(&AllocationListLock, oldIrql);
256 if (Page.QuadPart == 0LL)
257 {
258 KeBugCheck(0);
259 }
260 *AllocatedPage = Page;
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 return(STATUS_SUCCESS);
280 }
281
282 /*
283 * Actually allocate the page.
284 */
285 Page = MmAllocPage(Consumer, 0);
286 if (Page.QuadPart == 0LL)
287 {
288 KeBugCheck(0);
289 }
290 *AllocatedPage = Page;
291
292 return(STATUS_SUCCESS);
293 }