xref: /reactos/ntoskrnl/mm/balance.c (revision 9d33a205)
1 /*
2  * COPYRIGHT:       See COPYING in the top level directory
3  * PROJECT:         ReactOS kernel
4  * FILE:            ntoskrnl/mm/balance.c
5  * PURPOSE:         kernel memory managment functions
6  *
7  * PROGRAMMERS:     David Welch (welch@cwcom.net)
8  *                  Cameron Gutman (cameron.gutman@reactos.org)
9  */
10 
11 /* INCLUDES *****************************************************************/
12 
13 #include <ntoskrnl.h>
14 #define NDEBUG
15 #include <debug.h>
16 
17 #include "ARM3/miarm.h"
18 
19 
20 /* TYPES ********************************************************************/
21 typedef struct _MM_ALLOCATION_REQUEST
22 {
23     PFN_NUMBER Page;
24     LIST_ENTRY ListEntry;
25     KEVENT Event;
26 }
27 MM_ALLOCATION_REQUEST, *PMM_ALLOCATION_REQUEST;
28 /* GLOBALS ******************************************************************/
29 
30 MM_MEMORY_CONSUMER MiMemoryConsumers[MC_MAXIMUM];
31 static ULONG MiMinimumAvailablePages;
32 static ULONG MiMinimumPagesPerRun;
33 static CLIENT_ID MiBalancerThreadId;
34 static HANDLE MiBalancerThreadHandle = NULL;
35 static KEVENT MiBalancerEvent;
36 static KTIMER MiBalancerTimer;
37 
38 static LONG PageOutThreadActive;
39 
40 /* FUNCTIONS ****************************************************************/
41 
42 CODE_SEG("INIT")
43 VOID
44 NTAPI
45 MmInitializeBalancer(ULONG NrAvailablePages, ULONG NrSystemPages)
46 {
47     memset(MiMemoryConsumers, 0, sizeof(MiMemoryConsumers));
48 
49     /* Set up targets. */
50     MiMinimumAvailablePages = 256;
51     MiMinimumPagesPerRun = 256;
52     MiMemoryConsumers[MC_USER].PagesTarget = NrAvailablePages / 2;
53 }
54 
55 CODE_SEG("INIT")
56 VOID
57 NTAPI
58 MmInitializeMemoryConsumer(
59     ULONG Consumer,
60     NTSTATUS (*Trim)(ULONG Target, ULONG Priority, PULONG NrFreed))
61 {
62     MiMemoryConsumers[Consumer].Trim = Trim;
63 }
64 
65 VOID
66 NTAPI
67 MiZeroPhysicalPage(
68     IN PFN_NUMBER PageFrameIndex
69 );
70 
71 NTSTATUS
72 NTAPI
73 MmReleasePageMemoryConsumer(ULONG Consumer, PFN_NUMBER Page)
74 {
75     KIRQL OldIrql;
76 
77     if (Page == 0)
78     {
79         DPRINT1("Tried to release page zero.\n");
80         KeBugCheck(MEMORY_MANAGEMENT);
81     }
82 
83     (void)InterlockedDecrementUL(&MiMemoryConsumers[Consumer].PagesUsed);
84 
85     OldIrql = MiAcquirePfnLock();
86 
87     MmDereferencePage(Page);
88 
89     MiReleasePfnLock(OldIrql);
90 
91     return(STATUS_SUCCESS);
92 }
93 
94 ULONG
95 NTAPI
96 MiTrimMemoryConsumer(ULONG Consumer, ULONG InitialTarget)
97 {
98     ULONG Target = InitialTarget;
99     ULONG NrFreedPages = 0;
100     NTSTATUS Status;
101 
102     /* Make sure we can trim this consumer */
103     if (!MiMemoryConsumers[Consumer].Trim)
104     {
105         /* Return the unmodified initial target */
106         return InitialTarget;
107     }
108 
109     if (MmAvailablePages < MiMinimumAvailablePages)
110     {
111         /* Global page limit exceeded */
112         Target = (ULONG)max(Target, MiMinimumAvailablePages - MmAvailablePages);
113     }
114     else if (MiMemoryConsumers[Consumer].PagesUsed > MiMemoryConsumers[Consumer].PagesTarget)
115     {
116         /* Consumer page limit exceeded */
117         Target = max(Target, MiMemoryConsumers[Consumer].PagesUsed - MiMemoryConsumers[Consumer].PagesTarget);
118     }
119 
120     if (Target)
121     {
122         /* Now swap the pages out */
123         Status = MiMemoryConsumers[Consumer].Trim(Target, MmAvailablePages < MiMinimumAvailablePages, &NrFreedPages);
124 
125         DPRINT("Trimming consumer %lu: Freed %lu pages with a target of %lu pages\n", Consumer, NrFreedPages, Target);
126 
127         if (!NT_SUCCESS(Status))
128         {
129             KeBugCheck(MEMORY_MANAGEMENT);
130         }
131     }
132 
133     /* Return the page count needed to be freed to meet the initial target */
134     return (InitialTarget > NrFreedPages) ? (InitialTarget - NrFreedPages) : 0;
135 }
136 
137 NTSTATUS
138 MmTrimUserMemory(ULONG Target, ULONG Priority, PULONG NrFreedPages)
139 {
140     PFN_NUMBER CurrentPage;
141     NTSTATUS Status;
142 
143     (*NrFreedPages) = 0;
144 
145     DPRINT1("MM BALANCER: %s\n", Priority ? "Paging out!" : "Removing access bit!");
146 
147     CurrentPage = MmGetLRUFirstUserPage();
148     while (CurrentPage != 0 && Target > 0)
149     {
150         if (Priority)
151         {
152             Status = MmPageOutPhysicalAddress(CurrentPage);
153             if (NT_SUCCESS(Status))
154             {
155                 DPRINT("Succeeded\n");
156                 Target--;
157                 (*NrFreedPages)++;
158             }
159         }
160         else
161         {
162             /* When not paging-out agressively, just reset the accessed bit */
163             PEPROCESS Process = NULL;
164             PVOID Address = NULL;
165             BOOLEAN Accessed = FALSE;
166 
167             /*
168              * We have a lock-ordering problem here. We cant lock the PFN DB before the Process address space.
169              * So we must use circonvoluted loops.
170              * Well...
171              */
172             while (TRUE)
173             {
174                 KAPC_STATE ApcState;
175                 KIRQL OldIrql = MiAcquirePfnLock();
176                 PMM_RMAP_ENTRY Entry = MmGetRmapListHeadPage(CurrentPage);
177                 while (Entry)
178                 {
179                     if (RMAP_IS_SEGMENT(Entry->Address))
180                     {
181                         Entry = Entry->Next;
182                         continue;
183                     }
184 
185                     /* Check that we didn't treat this entry before */
186                     if (Entry->Address < Address)
187                     {
188                         Entry = Entry->Next;
189                         continue;
190                     }
191 
192                     if ((Entry->Address == Address) && (Entry->Process <= Process))
193                     {
194                         Entry = Entry->Next;
195                         continue;
196                     }
197 
198                     break;
199                 }
200 
201                 if (!Entry)
202                 {
203                     MiReleasePfnLock(OldIrql);
204                     break;
205                 }
206 
207                 Process = Entry->Process;
208                 Address = Entry->Address;
209 
210                 ObReferenceObject(Process);
211 
212                 if (!ExAcquireRundownProtection(&Process->RundownProtect))
213                 {
214                     ObDereferenceObject(Process);
215                     MiReleasePfnLock(OldIrql);
216                     continue;
217                 }
218 
219                 MiReleasePfnLock(OldIrql);
220 
221                 KeStackAttachProcess(&Process->Pcb, &ApcState);
222                 MiLockProcessWorkingSet(Process, PsGetCurrentThread());
223 
224                 /* Be sure this is still valid. */
225                 if (MmIsAddressValid(Address))
226                 {
227                     PMMPTE Pte = MiAddressToPte(Address);
228                     Accessed = Accessed || Pte->u.Hard.Accessed;
229                     Pte->u.Hard.Accessed = 0;
230 
231                     /* There is no need to invalidate, the balancer thread is never on a user process */
232                     //KeInvalidateTlbEntry(Address);
233                 }
234 
235                 MiUnlockProcessWorkingSet(Process, PsGetCurrentThread());
236 
237                 KeUnstackDetachProcess(&ApcState);
238                 ExReleaseRundownProtection(&Process->RundownProtect);
239                 ObDereferenceObject(Process);
240             }
241 
242             if (!Accessed)
243             {
244                 /* Nobody accessed this page since the last time we check. Time to clean up */
245 
246                 Status = MmPageOutPhysicalAddress(CurrentPage);
247                 // DPRINT1("Paged-out one page: %s\n", NT_SUCCESS(Status) ? "Yes" : "No");
248                 (void)Status;
249             }
250 
251             /* Done for this page. */
252             Target--;
253         }
254 
255         CurrentPage = MmGetLRUNextUserPage(CurrentPage, TRUE);
256     }
257 
258     if (CurrentPage)
259     {
260         KIRQL OldIrql = MiAcquirePfnLock();
261         MmDereferencePage(CurrentPage);
262         MiReleasePfnLock(OldIrql);
263     }
264 
265     return STATUS_SUCCESS;
266 }
267 
268 VOID
269 NTAPI
270 MmRebalanceMemoryConsumers(VOID)
271 {
272     // if (InterlockedCompareExchange(&PageOutThreadActive, 0, 1) == 0)
273     {
274         KeSetEvent(&MiBalancerEvent, IO_NO_INCREMENT, FALSE);
275     }
276 }
277 
278 NTSTATUS
279 NTAPI
280 MmRequestPageMemoryConsumer(ULONG Consumer, BOOLEAN CanWait,
281                             PPFN_NUMBER AllocatedPage)
282 {
283     PFN_NUMBER Page;
284 
285     /* Update the target */
286     InterlockedIncrementUL(&MiMemoryConsumers[Consumer].PagesUsed);
287 
288     /*
289      * Actually allocate the page.
290      */
291     Page = MmAllocPage(Consumer);
292     if (Page == 0)
293     {
294         KeBugCheck(NO_PAGES_AVAILABLE);
295     }
296     *AllocatedPage = Page;
297 
298     return(STATUS_SUCCESS);
299 }
300 
301 
302 VOID NTAPI
303 MiBalancerThread(PVOID Unused)
304 {
305     PVOID WaitObjects[2];
306     NTSTATUS Status;
307     ULONG i;
308 
309     WaitObjects[0] = &MiBalancerEvent;
310     WaitObjects[1] = &MiBalancerTimer;
311 
312     while (1)
313     {
314         Status = KeWaitForMultipleObjects(2,
315                                           WaitObjects,
316                                           WaitAny,
317                                           Executive,
318                                           KernelMode,
319                                           FALSE,
320                                           NULL,
321                                           NULL);
322 
323         if (Status == STATUS_WAIT_0 || Status == STATUS_WAIT_1)
324         {
325             ULONG InitialTarget = 0;
326 
327             do
328             {
329                 ULONG OldTarget = InitialTarget;
330 
331                 /* Trim each consumer */
332                 for (i = 0; i < MC_MAXIMUM; i++)
333                 {
334                     InitialTarget = MiTrimMemoryConsumer(i, InitialTarget);
335                 }
336 
337                 /* No pages left to swap! */
338                 if (InitialTarget != 0 &&
339                         InitialTarget == OldTarget)
340                 {
341                     /* Game over */
342                     KeBugCheck(NO_PAGES_AVAILABLE);
343                 }
344             }
345             while (InitialTarget != 0);
346 
347             if (Status == STATUS_WAIT_0)
348                 InterlockedDecrement(&PageOutThreadActive);
349         }
350         else
351         {
352             DPRINT1("KeWaitForMultipleObjects failed, status = %x\n", Status);
353             KeBugCheck(MEMORY_MANAGEMENT);
354         }
355     }
356 }
357 
358 CODE_SEG("INIT")
359 VOID
360 NTAPI
361 MiInitBalancerThread(VOID)
362 {
363     KPRIORITY Priority;
364     NTSTATUS Status;
365     LARGE_INTEGER Timeout;
366 
367     KeInitializeEvent(&MiBalancerEvent, SynchronizationEvent, FALSE);
368     KeInitializeTimerEx(&MiBalancerTimer, SynchronizationTimer);
369 
370     Timeout.QuadPart = -20000000; /* 2 sec */
371     KeSetTimerEx(&MiBalancerTimer,
372                  Timeout,
373                  2000,         /* 2 sec */
374                  NULL);
375 
376     Status = PsCreateSystemThread(&MiBalancerThreadHandle,
377                                   THREAD_ALL_ACCESS,
378                                   NULL,
379                                   NULL,
380                                   &MiBalancerThreadId,
381                                   MiBalancerThread,
382                                   NULL);
383     if (!NT_SUCCESS(Status))
384     {
385         KeBugCheck(MEMORY_MANAGEMENT);
386     }
387 
388     Priority = LOW_REALTIME_PRIORITY + 1;
389     NtSetInformationThread(MiBalancerThreadHandle,
390                            ThreadPriority,
391                            &Priority,
392                            sizeof(Priority));
393 
394 }
395 
396 
397 /* EOF */
398