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