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