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