Reverted the MPW changes (keep these in a seperate branch)
[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.8 2002/05/14 21:19:18 dwelch Exp $
20 *
21 * COPYRIGHT: See COPYING in the top directory
22 * PROJECT: ReactOS kernel
23 * FILE: ntoskrnl/mm/balance.c
24 * PURPOSE: kernel memory managment functions
25 * PROGRAMMER: David Welch (welch@cwcom.net)
26 * UPDATE HISTORY:
27 * Created 27/12/01
28 */
29
30 /* INCLUDES *****************************************************************/
31
32 #include <ddk/ntddk.h>
33 #include <internal/mm.h>
34 #include <ntos/minmax.h>
35
36 #define NDEBUG
37 #include <internal/debug.h>
38
39 /* TYPES ********************************************************************/
40
41 typedef struct _MM_MEMORY_CONSUMER
42 {
43 ULONG PagesUsed;
44 ULONG PagesTarget;
45 NTSTATUS (*Trim)(ULONG Target, ULONG Priority, PULONG NrFreed);
46 } MM_MEMORY_CONSUMER, *PMM_MEMORY_CONSUMER;
47
48 typedef struct _MM_ALLOCATION_REQUEST
49 {
50 PVOID Page;
51 LIST_ENTRY ListEntry;
52 KEVENT Event;
53 } MM_ALLOCATION_REQUEST, *PMM_ALLOCATION_REQUEST;
54
55 /* GLOBALS ******************************************************************/
56
57 static MM_MEMORY_CONSUMER MiMemoryConsumers[MC_MAXIMUM];
58 static ULONG MiMinimumAvailablePages;
59 static ULONG MiNrAvailablePages;
60 static ULONG MiNrTotalPages;
61 static LIST_ENTRY AllocationListHead;
62 static KSPIN_LOCK AllocationListLock;
63 static ULONG NrWorkingThreads = 0;
64 static HANDLE WorkerThreadId;
65 static ULONG MiPagesRequired = 0;
66 static ULONG MiMinimumPagesPerRun = 1;
67
68 /* FUNCTIONS ****************************************************************/
69
70 VOID
71 MmInitializeBalancer(ULONG NrAvailablePages)
72 {
73 memset(MiMemoryConsumers, 0, sizeof(MiMemoryConsumers));
74 InitializeListHead(&AllocationListHead);
75 KeInitializeSpinLock(&AllocationListLock);
76
77 MiNrAvailablePages = MiNrTotalPages = NrAvailablePages;
78
79 /* Set up targets. */
80 MiMinimumAvailablePages = 64;
81 MiMemoryConsumers[MC_CACHE].PagesTarget = NrAvailablePages / 2;
82 MiMemoryConsumers[MC_USER].PagesTarget = NrAvailablePages - MiMinimumAvailablePages;
83 MiMemoryConsumers[MC_PPOOL].PagesTarget = NrAvailablePages / 2;
84 MiMemoryConsumers[MC_NPPOOL].PagesTarget = 0xFFFFFFFF;
85 }
86
87 VOID
88 MmInitializeMemoryConsumer(ULONG Consumer,
89 NTSTATUS (*Trim)(ULONG Target, ULONG Priority,
90 PULONG NrFreed))
91 {
92 MiMemoryConsumers[Consumer].Trim = Trim;
93 }
94
95 NTSTATUS
96 MmReleasePageMemoryConsumer(ULONG Consumer, PVOID Page)
97 {
98 PMM_ALLOCATION_REQUEST Request;
99 PLIST_ENTRY Entry;
100 KIRQL oldIrql;
101
102 if (Page == NULL)
103 {
104 DPRINT1("Tried to release page zero.\n");
105 KeBugCheck(0);
106 }
107
108 InterlockedDecrement(&MiMemoryConsumers[Consumer].PagesUsed);
109 InterlockedIncrement(&MiNrAvailablePages);
110 InterlockedDecrement(&MiPagesRequired);
111 KeAcquireSpinLock(&AllocationListLock, &oldIrql);
112 if (IsListEmpty(&AllocationListHead))
113 {
114 KeReleaseSpinLock(&AllocationListLock, oldIrql);
115 MmDereferencePage(Page);
116 }
117 else
118 {
119 Entry = RemoveHeadList(&AllocationListHead);
120 Request = CONTAINING_RECORD(Entry, MM_ALLOCATION_REQUEST, ListEntry);
121 KeReleaseSpinLock(&AllocationListLock, oldIrql);
122 Request->Page = Page;
123 KeSetEvent(&Request->Event, IO_NO_INCREMENT, FALSE);
124 }
125 return(STATUS_SUCCESS);
126 }
127
128 VOID
129 MiTrimMemoryConsumer(ULONG Consumer)
130 {
131 LONG Target;
132
133 Target = MiMemoryConsumers[Consumer].PagesUsed -
134 MiMemoryConsumers[Consumer].PagesTarget;
135 if (Target < 0)
136 {
137 Target = 1;
138 }
139
140 if (MiMemoryConsumers[Consumer].Trim != NULL)
141 {
142 MiMemoryConsumers[Consumer].Trim(Target, 0, NULL);
143 }
144 }
145
146 VOID
147 MiRebalanceMemoryConsumers(VOID)
148 {
149 LONG Target;
150 ULONG i;
151 ULONG NrFreedPages;
152 NTSTATUS Status;
153
154 Target = (MiMinimumAvailablePages - MiNrAvailablePages) + MiPagesRequired;
155 Target = min(Target, MiMinimumPagesPerRun);
156
157 for (i = 0; i < MC_MAXIMUM && Target > 0; i++)
158 {
159 if (MiMemoryConsumers[i].Trim != NULL)
160 {
161 Status = MiMemoryConsumers[i].Trim(Target, 0, &NrFreedPages);
162 if (!NT_SUCCESS(Status))
163 {
164 KeBugCheck(0);
165 }
166 Target = Target - NrFreedPages;
167 }
168 }
169 if (Target > 0)
170 {
171 KeBugCheck(0);
172 }
173 }
174
175 NTSTATUS
176 MmRequestPageMemoryConsumer(ULONG Consumer, BOOLEAN CanWait, PVOID* AllocatedPage)
177 {
178 ULONG OldUsed;
179 ULONG OldAvailable;
180 PVOID Page;
181 KIRQL oldIrql;
182
183 /*
184 * Make sure we don't exceed our individual target.
185 */
186 OldUsed = InterlockedIncrement(&MiMemoryConsumers[Consumer].PagesUsed);
187 if (OldUsed >= (MiMemoryConsumers[Consumer].PagesTarget - 1) &&
188 WorkerThreadId != PsGetCurrentThreadId())
189 {
190 if (!CanWait)
191 {
192 InterlockedDecrement(&MiMemoryConsumers[Consumer].PagesUsed);
193 return(STATUS_NO_MEMORY);
194 }
195 MiTrimMemoryConsumer(Consumer);
196 }
197
198 /*
199 * Make sure we don't exceed global targets.
200 */
201 OldAvailable = InterlockedDecrement(&MiNrAvailablePages);
202 if (OldAvailable < MiMinimumAvailablePages)
203 {
204 MM_ALLOCATION_REQUEST Request;
205
206 if (!CanWait)
207 {
208 InterlockedIncrement(&MiNrAvailablePages);
209 InterlockedDecrement(&MiMemoryConsumers[Consumer].PagesUsed);
210 return(STATUS_NO_MEMORY);
211 }
212
213 /* Insert an allocation request. */
214 Request.Page = NULL;
215 KeInitializeEvent(&Request.Event, NotificationEvent, FALSE);
216 InterlockedIncrement(&MiPagesRequired);
217
218 KeAcquireSpinLock(&AllocationListLock, &oldIrql);
219 if (NrWorkingThreads == 0)
220 {
221 InsertTailList(&AllocationListHead, &Request.ListEntry);
222 NrWorkingThreads++;
223 KeReleaseSpinLock(&AllocationListLock, oldIrql);
224 WorkerThreadId = PsGetCurrentThreadId();
225 MiRebalanceMemoryConsumers();
226 KeAcquireSpinLock(&AllocationListLock, &oldIrql);
227 NrWorkingThreads--;
228 WorkerThreadId = 0;
229 KeReleaseSpinLock(&AllocationListLock, oldIrql);
230 }
231 else
232 {
233 if (WorkerThreadId == PsGetCurrentThreadId())
234 {
235 Page = MmAllocPage(Consumer, 0);
236 KeReleaseSpinLock(&AllocationListLock, oldIrql);
237 if (Page == NULL)
238 {
239 KeBugCheck(0);
240 }
241 *AllocatedPage = Page;
242 return(STATUS_SUCCESS);
243 }
244 InsertTailList(&AllocationListHead, &Request.ListEntry);
245 KeReleaseSpinLock(&AllocationListLock, oldIrql);
246 }
247 KeWaitForSingleObject(&Request.Event,
248 0,
249 KernelMode,
250 FALSE,
251 NULL);
252
253 Page = Request.Page;
254 if (Page == NULL)
255 {
256 KeBugCheck(0);
257 }
258 MmTransferOwnershipPage(Page, Consumer);
259 *AllocatedPage = Page;
260 return(STATUS_SUCCESS);
261 }
262
263 /*
264 * Actually allocate the page.
265 */
266 Page = MmAllocPage(Consumer, 0);
267 if (Page == NULL)
268 {
269 KeBugCheck(0);
270 }
271 *AllocatedPage = Page;
272
273 return(STATUS_SUCCESS);
274 }