xref: /reactos/ntoskrnl/mm/balance.c (revision a03ef85b)
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     UpdateTotalCommittedPages(-1);
85 
86     OldIrql = MiAcquirePfnLock();
87 
88     MmDereferencePage(Page);
89 
90     MiReleasePfnLock(OldIrql);
91 
92     return(STATUS_SUCCESS);
93 }
94 
95 ULONG
96 NTAPI
97 MiTrimMemoryConsumer(ULONG Consumer, ULONG InitialTarget)
98 {
99     ULONG Target = InitialTarget;
100     ULONG NrFreedPages = 0;
101     NTSTATUS Status;
102 
103     /* Make sure we can trim this consumer */
104     if (!MiMemoryConsumers[Consumer].Trim)
105     {
106         /* Return the unmodified initial target */
107         return InitialTarget;
108     }
109 
110     if (MmAvailablePages < MiMinimumAvailablePages)
111     {
112         /* Global page limit exceeded */
113         Target = (ULONG)max(Target, MiMinimumAvailablePages - MmAvailablePages);
114     }
115     else if (MiMemoryConsumers[Consumer].PagesUsed > MiMemoryConsumers[Consumer].PagesTarget)
116     {
117         /* Consumer page limit exceeded */
118         Target = max(Target, MiMemoryConsumers[Consumer].PagesUsed - MiMemoryConsumers[Consumer].PagesTarget);
119     }
120 
121     if (Target)
122     {
123         /* Now swap the pages out */
124         Status = MiMemoryConsumers[Consumer].Trim(Target, MmAvailablePages < MiMinimumAvailablePages, &NrFreedPages);
125 
126         DPRINT("Trimming consumer %lu: Freed %lu pages with a target of %lu pages\n", Consumer, NrFreedPages, Target);
127 
128         if (!NT_SUCCESS(Status))
129         {
130             KeBugCheck(MEMORY_MANAGEMENT);
131         }
132     }
133 
134     /* Return the page count needed to be freed to meet the initial target */
135     return (InitialTarget > NrFreedPages) ? (InitialTarget - NrFreedPages) : 0;
136 }
137 
138 NTSTATUS
139 MmTrimUserMemory(ULONG Target, ULONG Priority, PULONG NrFreedPages)
140 {
141     PFN_NUMBER CurrentPage;
142     NTSTATUS Status;
143 
144     (*NrFreedPages) = 0;
145 
146     DPRINT1("MM BALANCER: %s\n", Priority ? "Paging out!" : "Removing access bit!");
147 
148     CurrentPage = MmGetLRUFirstUserPage();
149     while (CurrentPage != 0 && Target > 0)
150     {
151         if (Priority)
152         {
153             Status = MmPageOutPhysicalAddress(CurrentPage);
154             if (NT_SUCCESS(Status))
155             {
156                 DPRINT("Succeeded\n");
157                 Target--;
158                 (*NrFreedPages)++;
159             }
160         }
161         else
162         {
163             /* When not paging-out agressively, just reset the accessed bit */
164             PEPROCESS Process = NULL;
165             PVOID Address = NULL;
166             BOOLEAN Accessed = FALSE;
167 
168             /*
169              * We have a lock-ordering problem here. We cant lock the PFN DB before the Process address space.
170              * So we must use circonvoluted loops.
171              * Well...
172              */
173             while (TRUE)
174             {
175                 KAPC_STATE ApcState;
176                 KIRQL OldIrql = MiAcquirePfnLock();
177                 PMM_RMAP_ENTRY Entry = MmGetRmapListHeadPage(CurrentPage);
178                 while (Entry)
179                 {
180                     if (RMAP_IS_SEGMENT(Entry->Address))
181                     {
182                         Entry = Entry->Next;
183                         continue;
184                     }
185 
186                     /* Check that we didn't treat this entry before */
187                     if (Entry->Address < Address)
188                     {
189                         Entry = Entry->Next;
190                         continue;
191                     }
192 
193                     if ((Entry->Address == Address) && (Entry->Process <= Process))
194                     {
195                         Entry = Entry->Next;
196                         continue;
197                     }
198 
199                     break;
200                 }
201 
202                 if (!Entry)
203                 {
204                     MiReleasePfnLock(OldIrql);
205                     break;
206                 }
207 
208                 Process = Entry->Process;
209                 Address = Entry->Address;
210 
211                 ObReferenceObject(Process);
212 
213                 if (!ExAcquireRundownProtection(&Process->RundownProtect))
214                 {
215                     ObDereferenceObject(Process);
216                     MiReleasePfnLock(OldIrql);
217                     continue;
218                 }
219 
220                 MiReleasePfnLock(OldIrql);
221 
222                 KeStackAttachProcess(&Process->Pcb, &ApcState);
223                 MiLockProcessWorkingSet(Process, PsGetCurrentThread());
224 
225                 /* Be sure this is still valid. */
226                 if (MmIsAddressValid(Address))
227                 {
228                     PMMPTE Pte = MiAddressToPte(Address);
229                     Accessed = Accessed || Pte->u.Hard.Accessed;
230                     Pte->u.Hard.Accessed = 0;
231 
232                     /* There is no need to invalidate, the balancer thread is never on a user process */
233                     //KeInvalidateTlbEntry(Address);
234                 }
235 
236                 MiUnlockProcessWorkingSet(Process, PsGetCurrentThread());
237 
238                 KeUnstackDetachProcess(&ApcState);
239                 ExReleaseRundownProtection(&Process->RundownProtect);
240                 ObDereferenceObject(Process);
241             }
242 
243             if (!Accessed)
244             {
245                 /* Nobody accessed this page since the last time we check. Time to clean up */
246 
247                 Status = MmPageOutPhysicalAddress(CurrentPage);
248                 // DPRINT1("Paged-out one page: %s\n", NT_SUCCESS(Status) ? "Yes" : "No");
249                 (void)Status;
250             }
251 
252             /* Done for this page. */
253             Target--;
254         }
255 
256         CurrentPage = MmGetLRUNextUserPage(CurrentPage, TRUE);
257     }
258 
259     if (CurrentPage)
260     {
261         KIRQL OldIrql = MiAcquirePfnLock();
262         MmDereferencePage(CurrentPage);
263         MiReleasePfnLock(OldIrql);
264     }
265 
266     return STATUS_SUCCESS;
267 }
268 
269 VOID
270 NTAPI
271 MmRebalanceMemoryConsumers(VOID)
272 {
273     // if (InterlockedCompareExchange(&PageOutThreadActive, 0, 1) == 0)
274     {
275         KeSetEvent(&MiBalancerEvent, IO_NO_INCREMENT, FALSE);
276     }
277 }
278 
279 NTSTATUS
280 NTAPI
281 MmRequestPageMemoryConsumer(ULONG Consumer, BOOLEAN CanWait,
282                             PPFN_NUMBER AllocatedPage)
283 {
284     PFN_NUMBER Page;
285 
286     /* Update the target */
287     InterlockedIncrementUL(&MiMemoryConsumers[Consumer].PagesUsed);
288     UpdateTotalCommittedPages(1);
289 
290     /*
291      * Actually allocate the page.
292      */
293     Page = MmAllocPage(Consumer);
294     if (Page == 0)
295     {
296         KeBugCheck(NO_PAGES_AVAILABLE);
297     }
298     *AllocatedPage = Page;
299 
300     return(STATUS_SUCCESS);
301 }
302 
303 
304 VOID NTAPI
305 MiBalancerThread(PVOID Unused)
306 {
307     PVOID WaitObjects[2];
308     NTSTATUS Status;
309     ULONG i;
310 
311     WaitObjects[0] = &MiBalancerEvent;
312     WaitObjects[1] = &MiBalancerTimer;
313 
314     while (1)
315     {
316         Status = KeWaitForMultipleObjects(2,
317                                           WaitObjects,
318                                           WaitAny,
319                                           Executive,
320                                           KernelMode,
321                                           FALSE,
322                                           NULL,
323                                           NULL);
324 
325         if (Status == STATUS_WAIT_0 || Status == STATUS_WAIT_1)
326         {
327             ULONG InitialTarget = 0;
328 
329             do
330             {
331                 ULONG OldTarget = InitialTarget;
332 
333                 /* Trim each consumer */
334                 for (i = 0; i < MC_MAXIMUM; i++)
335                 {
336                     InitialTarget = MiTrimMemoryConsumer(i, InitialTarget);
337                 }
338 
339                 /* No pages left to swap! */
340                 if (InitialTarget != 0 &&
341                         InitialTarget == OldTarget)
342                 {
343                     /* Game over */
344                     KeBugCheck(NO_PAGES_AVAILABLE);
345                 }
346             }
347             while (InitialTarget != 0);
348 
349             if (Status == STATUS_WAIT_0)
350                 InterlockedDecrement(&PageOutThreadActive);
351         }
352         else
353         {
354             DPRINT1("KeWaitForMultipleObjects failed, status = %x\n", Status);
355             KeBugCheck(MEMORY_MANAGEMENT);
356         }
357     }
358 }
359 
360 CODE_SEG("INIT")
361 VOID
362 NTAPI
363 MiInitBalancerThread(VOID)
364 {
365     KPRIORITY Priority;
366     NTSTATUS Status;
367     LARGE_INTEGER Timeout;
368 
369     KeInitializeEvent(&MiBalancerEvent, SynchronizationEvent, FALSE);
370     KeInitializeTimerEx(&MiBalancerTimer, SynchronizationTimer);
371 
372     Timeout.QuadPart = -20000000; /* 2 sec */
373     KeSetTimerEx(&MiBalancerTimer,
374                  Timeout,
375                  2000,         /* 2 sec */
376                  NULL);
377 
378     Status = PsCreateSystemThread(&MiBalancerThreadHandle,
379                                   THREAD_ALL_ACCESS,
380                                   NULL,
381                                   NULL,
382                                   &MiBalancerThreadId,
383                                   MiBalancerThread,
384                                   NULL);
385     if (!NT_SUCCESS(Status))
386     {
387         KeBugCheck(MEMORY_MANAGEMENT);
388     }
389 
390     Priority = LOW_REALTIME_PRIORITY + 1;
391     NtSetInformationThread(MiBalancerThreadHandle,
392                            ThreadPriority,
393                            &Priority,
394                            sizeof(Priority));
395 
396 }
397 
398 
399 /* EOF */
400