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