branch update for HEAD-2003021201
[reactos.git] / 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$
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 }