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