Main Page | Class Hierarchy | Class List | File List | Class Members | File Members

wsmanage.c

Go to the documentation of this file.
00001 /*++ 00002 00003 Copyright (c) 1990 Microsoft Corporation 00004 00005 Module Name: 00006 00007 wsmanage.c 00008 00009 Abstract: 00010 00011 This module contains routines which manage the set of active working 00012 set lists. 00013 00014 Working set management is accomplished by a parallel group of actions 00015 00016 1. Writing modified pages 00017 00018 2. Trimming working sets in one of two ways: 00019 00020 a. Using claims 00021 00022 1. Aging pages by turning off access bits and incrementing age 00023 counts for pages which haven't been accessed. 00024 2. Estimating the number of unused pages in a working set and 00025 keeping a global count of that estimate. 00026 3. When getting tight on memory, replacing rather than adding 00027 pages in a working set when a fault occurs in a working set 00028 that has a significant proportion of unused pages. 00029 4. When memory is tight, reducing (trimming) working sets which 00030 are above their maximum towards their minimum. This is done 00031 especially if there are a large number of available pages 00032 in it. 00033 00034 b. Using page fault information 00035 00036 1. Reducing (trimming) working sets which are above their 00037 maximum towards their minimum. 00038 00039 The metrics are set such that writing modified pages is typically 00040 accomplished before trimming working sets, however, under certain cases 00041 where modified pages are being generated at a very high rate, working 00042 set trimming will be initiated to free up more pages to modify. 00043 00044 When the first thread in a process is created, the memory management 00045 system is notified that working set expansion is allowed. This 00046 is noted by changing the FLINK field of the WorkingSetExpansionLink 00047 entry in the process control block from MM_NO_WS_EXPANSION to 00048 MM_ALLOW_WS_EXPANSION. As threads fault, the working set is eligible 00049 for expansion if ample pages exist (MmAvailablePages is high enough). 00050 00051 Once a process has had its working set raised above the minimum 00052 specified, the process is put on the Working Set Expanded list and 00053 is now eligible for trimming. Note that at this time the FLINK field 00054 in the WorkingSetExpansionLink has an address value. 00055 00056 When working set trimming is initiated, a process is removed from the 00057 list (the expansion lock guards this list) and the FLINK field is set 00058 to MM_NO_WS_EXPANSION, also, the BLINK field is set to 00059 MM_WS_EXPANSION_IN_PROGRESS. The BLINK field value indicates to 00060 the MmCleanUserAddressSpace function that working set trimming is 00061 in progress for this process and it should wait until it completes. 00062 This is accomplished by creating an event, putting the address of the 00063 event in the BLINK field and then releasing the expansion lock and 00064 waiting on the event atomically. When working set trimming is 00065 complete, the BLINK field is no longer MM_EXPANSION_IN_PROGRESS 00066 indicating that the event should be set. 00067 00068 Author: 00069 00070 Lou Perazzoli (loup) 10-Apr-1990 00071 Landy Wang (landyw) 02-Jun-1997 00072 00073 Revision History: 00074 00075 --*/ 00076 00077 #include "mi.h" 00078 00079 VOID 00080 MiEmptyAllWorkingSetsWorker ( 00081 VOID 00082 ); 00083 00084 #ifdef ALLOC_PRAGMA 00085 #pragma alloc_text(PAGELK, MiEmptyAllWorkingSetsWorker) 00086 #pragma alloc_text(PAGELK, MiEmptyAllWorkingSets) 00087 #pragma alloc_text(INIT, MiAdjustWorkingSetManagerParameters) 00088 #endif 00089 00090 // 00091 // Minimum number of page faults to take to avoid being trimmed on 00092 // an "ideal pass". 00093 // 00094 00095 ULONG MiIdealPassFaultCountDisable; 00096 00097 extern ULONG PsMinimumWorkingSet; 00098 00099 extern PEPROCESS ExpDefaultErrorPortProcess; 00100 00101 #define MM_TRIM_COUNTER_MAXIMUM_LARGE_MEM (6) 00102 00103 // 00104 // Hydra working set emptying support. 00105 // 00106 00107 KEVENT MiWaitForEmptyEvent; 00108 BOOLEAN MiWaitingForWorkingSetEmpty; 00109 00110 // 00111 // Number of times to wake up and do nothing before trimming processes 00112 // with no faulting activity. 00113 // 00114 00115 #define MM_REDUCE_FAULT_COUNT (10000) 00116 00117 #define MM_IGNORE_FAULT_COUNT (100) 00118 00119 #ifdef _MI_USE_CLAIMS_ 00120 BOOLEAN MiReplacing = FALSE; 00121 #endif 00122 00123 PFN_NUMBER MmMoreThanEnoughFreePages = 1000; 00124 00125 ULONG MmAmpleFreePages = 200; 00126 00127 ULONG MmWorkingSetReductionMin = 12; 00128 ULONG MmWorkingSetReductionMinCacheWs = 12; 00129 00130 ULONG MmWorkingSetReductionMax = 60; 00131 ULONG MmWorkingSetReductionMaxCacheWs = 60; 00132 00133 ULONG MmWorkingSetReductionHuge = (512*1024) >> PAGE_SHIFT; 00134 00135 ULONG MmWorkingSetVolReductionMin = 12; 00136 00137 ULONG MmWorkingSetVolReductionMax = 60; 00138 ULONG MmWorkingSetVolReductionMaxCacheWs = 60; 00139 00140 ULONG MmWorkingSetVolReductionHuge = (2*1024*1024) >> PAGE_SHIFT; 00141 00142 ULONG MmWorkingSetSwapReduction = 75; 00143 00144 ULONG MmWorkingSetSwapReductionHuge = (4*1024*1024) >> PAGE_SHIFT; 00145 00146 ULONG MmNumberOfForegroundProcesses; 00147 00148 #ifdef _MI_USE_CLAIMS_ 00149 00150 ULONG MiAgingShift = 4; 00151 ULONG MiEstimationShift = 5; 00152 ULONG MmTotalClaim = 0; 00153 ULONG MmTotalEstimatedAvailable = 0; 00154 00155 LARGE_INTEGER MiLastAdjustmentOfClaimParams; 00156 LARGE_INTEGER MmClaimParameterAdjustUpTime = {60 * 1000 * 1000 * 10, 0}; // Sixty seconds 00157 LARGE_INTEGER MmClaimParameterAdjustDownTime = {20 * 1000 * 1000 * 10, 0}; // 20 seconds 00158 00159 ULONG MmPlentyFreePages = 400; 00160 00161 #else 00162 00163 ULONG MiCheckCounter; 00164 ULONG MmLastFaultCount; 00165 00166 #endif 00167 00168 #if DBG 00169 PETHREAD MmWorkingSetThread; 00170 #endif 00171 00172 extern PVOID MmPagableKernelStart; 00173 extern PVOID MmPagableKernelEnd; 00174 00175 PERFINFO_WSMANAGE_GLOBAL_DECL; 00176 00177 typedef union _MMWS_TRIM_CRITERIA { 00178 #ifdef _MI_USE_CLAIMS_ 00179 struct { 00180 ULONG NumPasses; 00181 PFN_NUMBER DesiredFreeGoal; 00182 PFN_NUMBER NewTotalClaim; 00183 PFN_NUMBER NewTotalEstimatedAvailable; 00184 ULONG TrimAge; 00185 BOOLEAN DoAging; 00186 ULONG NumberOfForegroundProcesses; 00187 } ClaimBased; 00188 #else 00189 struct { 00190 ULONG NumPasses; 00191 PFN_NUMBER DesiredFreeGoal; 00192 PFN_NUMBER DesiredReductionGoal; 00193 ULONG FaultCount; 00194 PFN_NUMBER TotalReduction; 00195 ULONG NumberOfForegroundProcesses; 00196 } FaultBased; 00197 #endif 00198 } MMWS_TRIM_CRITERIA, *PMMWS_TRIM_CRITERIA; 00199 00200 LOGICAL 00201 MiCheckAndSetSystemTrimCriteria( 00202 IN OUT PMMWS_TRIM_CRITERIA Criteria 00203 ); 00204 00205 BOOLEAN 00206 MiCheckSystemTrimEndCriteria( 00207 IN OUT PMMWS_TRIM_CRITERIA Criteria, 00208 IN KIRQL OldIrql 00209 ); 00210 00211 BOOLEAN 00212 MiCheckProcessTrimCriteria( 00213 IN PMMWS_TRIM_CRITERIA Criteria, 00214 IN PMMSUPPORT VmSupport, 00215 IN PEPROCESS Process, 00216 IN PLARGE_INTEGER CurrentTime 00217 ); 00218 00219 #ifndef _MI_USE_CLAIMS_ 00220 LOGICAL 00221 MiCheckSystemCacheWsTrimCriteria( 00222 IN PMMSUPPORT VmSupport 00223 ); 00224 #endif 00225 00226 ULONG 00227 MiDetermineWsTrimAmount( 00228 IN PMMWS_TRIM_CRITERIA Criteria, 00229 IN PMMSUPPORT VmSupport, 00230 IN PEPROCESS Process 00231 ); 00232 00233 #ifdef _MI_USE_CLAIMS_ 00234 VOID 00235 MiAgePagesAndEstimateClaims( 00236 VOID 00237 ); 00238 00239 VOID 00240 MiAdjustClaimParameters( 00241 IN BOOLEAN EnoughPages 00242 ); 00243 00244 VOID 00245 MiAgeAndEstimateAvailableInWorkingSet( 00246 IN PMMSUPPORT VmSupport, 00247 IN BOOLEAN DoAging, 00248 IN OUT PULONG TotalClaim, 00249 IN OUT PULONG TotalEstimatedAvailable 00250 ); 00251 #endif 00252 00253 VOID 00254 MiRearrangeWorkingSetExpansionList( 00255 VOID 00256 ); 00257 00258 VOID 00259 MiAdjustWorkingSetManagerParameters( 00260 BOOLEAN WorkStation 00261 ) 00262 /*++ 00263 00264 Routine Description: 00265 00266 This function is called from MmInitSystem to adjust the working set manager 00267 trim algorithms based on system type and size. 00268 00269 Arguments: 00270 00271 WorkStation - TRUE if this is a workstation, FALSE if not. 00272 00273 Return Value: 00274 00275 None. 00276 00277 Environment: 00278 00279 Kernel mode 00280 00281 --*/ 00282 { 00283 00284 #ifdef _MI_USE_CLAIMS_ 00285 00286 if (WorkStation && MmNumberOfPhysicalPages <= 63*1024*1024/PAGE_SIZE) { 00287 MiAgingShift = 4; 00288 MiEstimationShift = 5; 00289 } 00290 else { 00291 MiAgingShift = 5; 00292 MiEstimationShift = 6; 00293 } 00294 00295 if (MmNumberOfPhysicalPages >= 63*1024*1024/PAGE_SIZE) { 00296 MmPlentyFreePages *= 2; 00297 } 00298 #else 00299 00300 if (WorkStation && (MmNumberOfPhysicalPages <= (31*1024*1024/PAGE_SIZE))) { 00301 00302 // 00303 // To get fault protection, you have to take 45 faults instead of 00304 // the old 15 fault protection threshold. 00305 // 00306 00307 MiIdealPassFaultCountDisable = 45; 00308 00309 // 00310 // Take more away when you are over your working set in both 00311 // forced and voluntary mode, but leave cache WS trim amounts 00312 // alone. 00313 // 00314 00315 MmWorkingSetVolReductionMax = 100; 00316 MmWorkingSetReductionMax = 100; 00317 00318 // 00319 // In forced mode, even if you are within your working set, take 00320 // memory away more aggressively. 00321 // 00322 00323 MmWorkingSetReductionMin = 40; 00324 } 00325 else { 00326 MiIdealPassFaultCountDisable = 15; 00327 } 00328 #endif 00329 00330 MiWaitingForWorkingSetEmpty = FALSE; 00331 KeInitializeEvent (&MiWaitForEmptyEvent, NotificationEvent, TRUE); 00332 } 00333 00334 00335 VOID 00336 MiObtainFreePages ( 00337 VOID 00338 ) 00339 00340 /*++ 00341 00342 Routine Description: 00343 00344 This function examines the size of the modified list and the 00345 total number of pages in use because of working set increments 00346 and obtains pages by writing modified pages and/or reducing 00347 working sets. 00348 00349 Arguments: 00350 00351 None. 00352 00353 Return Value: 00354 00355 None. 00356 00357 Environment: 00358 00359 Kernel mode, APCs disabled, working set and PFN mutexes held. 00360 00361 --*/ 00362 00363 { 00364 00365 // 00366 // Check to see if there are enough modified pages to institute a 00367 // write. 00368 // 00369 00370 if ((MmModifiedPageListHead.Total >= MmModifiedWriteClusterSize) || 00371 (MmModNoWriteInsert)) { 00372 00373 // 00374 // Start the modified page writer. 00375 // 00376 00377 KeSetEvent (&MmModifiedPageWriterEvent, 0, FALSE); 00378 } 00379 00380 // 00381 // See if there are enough working sets above the minimum 00382 // threshold to make working set trimming worthwhile. 00383 // 00384 00385 if ((MmPagesAboveWsMinimum > MmPagesAboveWsThreshold) || 00386 (MmAvailablePages < 5)) { 00387 00388 // 00389 // Start the working set manager to reduce working sets. 00390 // 00391 00392 KeSetEvent (&MmWorkingSetManagerEvent, 0, FALSE); 00393 } 00394 } 00395 00396 VOID 00397 MmWorkingSetManager ( 00398 VOID 00399 ) 00400 00401 /*++ 00402 00403 Routine Description: 00404 00405 Implements the NT working set manager thread. When the number 00406 of free pages becomes critical and ample pages can be obtained by 00407 reducing working sets, the working set manager's event is set, and 00408 this thread becomes active. 00409 00410 Arguments: 00411 00412 None. 00413 00414 Return Value: 00415 00416 None. 00417 00418 Environment: 00419 00420 Kernel mode. 00421 00422 --*/ 00423 00424 { 00425 00426 PEPROCESS CurrentProcess; 00427 PEPROCESS ProcessToTrim; 00428 PLIST_ENTRY ListEntry; 00429 LOGICAL Attached; 00430 ULONG Trim; 00431 KIRQL OldIrql; 00432 PMMSUPPORT VmSupport; 00433 PMMWSL WorkingSetList; 00434 LARGE_INTEGER CurrentTime; 00435 ULONG count; 00436 LOGICAL DoTrimming; 00437 PMM_SESSION_SPACE SessionSpace; 00438 LOGICAL InformSessionOfRelease; 00439 #if DBG 00440 ULONG LastTrimFaultCount; 00441 #endif // DBG 00442 MMWS_TRIM_CRITERIA TrimCriteria; 00443 PERFINFO_WSMANAGE_DECL(); 00444 00445 #if DBG 00446 MmWorkingSetThread = PsGetCurrentThread (); 00447 #endif 00448 00449 ASSERT (MiHydra == FALSE || MmIsAddressValid (MmSessionSpace) == FALSE); 00450 00451 CurrentProcess = PsGetCurrentProcess (); 00452 00453 Trim = 0; 00454 00455 // 00456 // Set the trim criteria: If there are plenty of pages, the existing 00457 // sets are aged and FALSE is returned to signify no trim is necessary. 00458 // Otherwise, the working set expansion list is ordered so the best 00459 // candidates for trimming are placed at the front and TRUE is returned. 00460 // 00461 00462 DoTrimming = MiCheckAndSetSystemTrimCriteria(&TrimCriteria); 00463 00464 if (DoTrimming) { 00465 00466 Attached = 0; 00467 00468 KeQuerySystemTime (&CurrentTime); 00469 00470 ASSERT (MiHydra == FALSE || MmIsAddressValid (MmSessionSpace) == FALSE); 00471 00472 LOCK_EXPANSION (OldIrql); 00473 while (!IsListEmpty (&MmWorkingSetExpansionHead.ListHead)) { 00474 00475 // 00476 // Remove the entry at the head and trim it. 00477 // 00478 00479 ListEntry = RemoveHeadList (&MmWorkingSetExpansionHead.ListHead); 00480 00481 if (ListEntry == &MmSystemCacheWs.WorkingSetExpansionLinks) { 00482 VmSupport = &MmSystemCacheWs; 00483 ASSERT (VmSupport->u.Flags.SessionSpace == 0); 00484 ASSERT (VmSupport->u.Flags.TrimHard == 0); 00485 SessionSpace = NULL; 00486 } 00487 else { 00488 VmSupport = CONTAINING_RECORD(ListEntry, 00489 MMSUPPORT, 00490 WorkingSetExpansionLinks); 00491 00492 if (VmSupport->u.Flags.SessionSpace == 0) { 00493 ProcessToTrim = CONTAINING_RECORD(VmSupport, 00494 EPROCESS, 00495 Vm); 00496 00497 ASSERT (VmSupport == &ProcessToTrim->Vm); 00498 ASSERT (ProcessToTrim->AddressSpaceDeleted == 0); 00499 SessionSpace = NULL; 00500 } 00501 else { 00502 ASSERT (MiHydra == TRUE); 00503 SessionSpace = CONTAINING_RECORD(VmSupport, 00504 MM_SESSION_SPACE, 00505 Vm); 00506 } 00507 } 00508 00509 // 00510 // Note that other routines that set this bit must remove the 00511 // entry from the expansion list first. 00512 // 00513 00514 ASSERT (VmSupport->u.Flags.BeingTrimmed == 0); 00515 00516 // 00517 // Check to see if we've been here before. 00518 // 00519 00520 if ((*(PLARGE_INTEGER)&VmSupport->LastTrimTime).QuadPart == 00521 (*(PLARGE_INTEGER)&CurrentTime).QuadPart) { 00522 00523 InsertHeadList (&MmWorkingSetExpansionHead.ListHead, 00524 &VmSupport->WorkingSetExpansionLinks); 00525 00526 // 00527 // If we aren't finished we may sleep in this call. 00528 // 00529 00530 if (MiCheckSystemTrimEndCriteria(&TrimCriteria, OldIrql)) { 00531 00532 // 00533 // No more pages are needed so we're done. 00534 // 00535 00536 break; 00537 } 00538 00539 // 00540 // Start a new round of trimming. 00541 // 00542 00543 KeQuerySystemTime (&CurrentTime); 00544 00545 continue; 00546 } 00547 00548 PERFINFO_WSMANAGE_TRIMWS(ProcessToTrim, SessionSpace, VmSupport); 00549 00550 if (SessionSpace) { 00551 00552 if (MiCheckProcessTrimCriteria(&TrimCriteria, 00553 VmSupport, 00554 NULL, 00555 &CurrentTime) == FALSE) { 00556 00557 InsertTailList (&MmWorkingSetExpansionHead.ListHead, 00558 &VmSupport->WorkingSetExpansionLinks); 00559 continue; 00560 } 00561 00562 VmSupport->LastTrimTime = CurrentTime; 00563 VmSupport->u.Flags.BeingTrimmed = 1; 00564 00565 VmSupport->WorkingSetExpansionLinks.Flink = MM_NO_WS_EXPANSION; 00566 VmSupport->WorkingSetExpansionLinks.Blink = 00567 MM_WS_EXPANSION_IN_PROGRESS; 00568 UNLOCK_EXPANSION (OldIrql); 00569 00570 ProcessToTrim = NULL; 00571 00572 // 00573 // Attach directly to the session space to be trimmed. 00574 // 00575 00576 MiAttachSession (SessionSpace); 00577 00578 // 00579 // Try for the session working set lock. 00580 // 00581 00582 WorkingSetList = VmSupport->VmWorkingSetList; 00583 00584 KeRaiseIrql (APC_LEVEL, &OldIrql); 00585 00586 if (!ExTryToAcquireResourceExclusiveLite (&SessionSpace->WsLock)) { 00587 // 00588 // This session space's working set lock was not 00589 // granted, don't trim it. 00590 // 00591 00592 KeLowerIrql (OldIrql); 00593 00594 MiDetachSession (); 00595 00596 LOCK_EXPANSION (OldIrql); 00597 00598 ASSERT (VmSupport->u.Flags.BeingTrimmed == 1); 00599 00600 VmSupport->u.Flags.BeingTrimmed = 0; 00601 00602 VmSupport->AllowWorkingSetAdjustment = MM_FORCE_TRIM; 00603 00604 goto WorkingSetLockFailed; 00605 } 00606 00607 VmSupport->LastTrimFaultCount = VmSupport->PageFaultCount; 00608 00609 MM_SET_SESSION_RESOURCE_OWNER(); 00610 PERFINFO_WSMANAGE_PROCESS_RESET(VmSupport); 00611 } 00612 else if (VmSupport != &MmSystemCacheWs) { 00613 00614 // 00615 // Check to see if this is a forced trim or 00616 // if we are trimming because check counter is 00617 // at the maximum. 00618 // 00619 00620 if (MiCheckProcessTrimCriteria(&TrimCriteria, 00621 VmSupport, 00622 ProcessToTrim, 00623 &CurrentTime) == FALSE) { 00624 00625 InsertTailList (&MmWorkingSetExpansionHead.ListHead, 00626 &VmSupport->WorkingSetExpansionLinks); 00627 continue; 00628 } 00629 00630 VmSupport->LastTrimTime = CurrentTime; 00631 VmSupport->u.Flags.BeingTrimmed = 1; 00632 00633 VmSupport->WorkingSetExpansionLinks.Flink = MM_NO_WS_EXPANSION; 00634 VmSupport->WorkingSetExpansionLinks.Blink = 00635 MM_WS_EXPANSION_IN_PROGRESS; 00636 UNLOCK_EXPANSION (OldIrql); 00637 WorkingSetList = MmWorkingSetList; 00638 InformSessionOfRelease = FALSE; 00639 00640 // 00641 // Attach to the process in preparation for trimming. 00642 // 00643 00644 if (ProcessToTrim != CurrentProcess) { 00645 00646 Attached = KeForceAttachProcess (&ProcessToTrim->Pcb); 00647 00648 if (Attached == 0) { 00649 LOCK_EXPANSION (OldIrql); 00650 VmSupport->u.Flags.BeingTrimmed = 0; 00651 VmSupport->AllowWorkingSetAdjustment = MM_FORCE_TRIM; 00652 goto WorkingSetLockFailed; 00653 } 00654 if (ProcessToTrim->ProcessOutswapEnabled == TRUE) { 00655 ASSERT (ProcessToTrim->ProcessOutswapped == FALSE); 00656 if (MiHydra == TRUE && VmSupport->u.Flags.ProcessInSession == 1 && VmSupport->u.Flags.SessionLeader == 0) { 00657 InformSessionOfRelease = TRUE; 00658 } 00659 } 00660 } 00661 00662 // 00663 // Attempt to acquire the working set lock. If the 00664 // lock cannot be acquired, skip over this process. 00665 // 00666 00667 count = 0; 00668 do { 00669 if (ExTryToAcquireFastMutex(&ProcessToTrim->WorkingSetLock) != FALSE) { 00670 break; 00671 } 00672 KeDelayExecutionThread (KernelMode, FALSE, &MmShortTime); 00673 count += 1; 00674 if (count == 5) { 00675 00676 // 00677 // Could not get the lock, skip this process. 00678 // 00679 00680 if (InformSessionOfRelease == TRUE) { 00681 LOCK_EXPANSION (OldIrql); 00682 ASSERT (ProcessToTrim->ProcessOutswapEnabled == TRUE); 00683 ProcessToTrim->ProcessOutswapEnabled = FALSE; 00684 ASSERT (MmSessionSpace->ProcessOutSwapCount >= 1); 00685 MmSessionSpace->ProcessOutSwapCount -= 1; 00686 UNLOCK_EXPANSION (OldIrql); 00687 InformSessionOfRelease = FALSE; 00688 } 00689 00690 if (Attached) { 00691 KeDetachProcess (); 00692 Attached = 0; 00693 } 00694 00695 LOCK_EXPANSION (OldIrql); 00696 VmSupport->u.Flags.BeingTrimmed = 0; 00697 VmSupport->AllowWorkingSetAdjustment = MM_FORCE_TRIM; 00698 goto WorkingSetLockFailed; 00699 } 00700 } while (TRUE); 00701 00702 ASSERT (VmSupport->u.Flags.BeingTrimmed == 1); 00703 00704 #if DBG 00705 LastTrimFaultCount = VmSupport->LastTrimFaultCount; 00706 #endif // DBG 00707 VmSupport->LastTrimFaultCount = VmSupport->PageFaultCount; 00708 00709 PERFINFO_WSMANAGE_PROCESS_RESET(VmSupport); 00710 } 00711 else { 00712 00713 // 00714 // System cache, 00715 // 00716 00717 #if DBG 00718 LastTrimFaultCount = VmSupport->LastTrimFaultCount; 00719 #endif // DBG 00720 00721 PERFINFO_WSMANAGE_PROCESS_RESET(VmSupport); 00722 00723 // 00724 // Always try to trim the system cache when using claims. 00725 // Fault-based trimming might skip it from time to time. 00726 // 00727 00728 #ifndef _MI_USE_CLAIMS_ 00729 00730 if (!MiCheckSystemCacheWsTrimCriteria(VmSupport)) { 00731 00732 // 00733 // Don't trim the system cache. 00734 // 00735 00736 InsertTailList (&MmWorkingSetExpansionHead.ListHead, 00737 &VmSupport->WorkingSetExpansionLinks); 00738 continue; 00739 } 00740 #endif 00741 00742 VmSupport->LastTrimTime = CurrentTime; 00743 00744 // 00745 // Indicate that this working set is being trimmed. 00746 // 00747 00748 VmSupport->u.Flags.BeingTrimmed = 1; 00749 00750 UNLOCK_EXPANSION (OldIrql); 00751 00752 ProcessToTrim = NULL; 00753 WorkingSetList = MmSystemCacheWorkingSetList; 00754 00755 KeRaiseIrql (APC_LEVEL, &OldIrql); 00756 if (!ExTryToAcquireResourceExclusiveLite (&MmSystemWsLock)) { 00757 00758 // 00759 // System working set lock was not granted, don't trim 00760 // the system cache. 00761 // 00762 00763 KeLowerIrql (OldIrql); 00764 LOCK_EXPANSION (OldIrql); 00765 VmSupport->u.Flags.BeingTrimmed = 0; 00766 InsertTailList (&MmWorkingSetExpansionHead.ListHead, 00767 &VmSupport->WorkingSetExpansionLinks); 00768 continue; 00769 } 00770 00771 MmSystemLockOwner = PsGetCurrentThread(); 00772 00773 VmSupport->LastTrimFaultCount = VmSupport->PageFaultCount; 00774 00775 VmSupport->WorkingSetExpansionLinks.Flink = MM_NO_WS_EXPANSION; 00776 VmSupport->WorkingSetExpansionLinks.Blink = 00777 MM_WS_EXPANSION_IN_PROGRESS; 00778 } 00779 00780 // 00781 // Determine how many pages we want to trim from this working set. 00782 // 00783 00784 Trim = MiDetermineWsTrimAmount(&TrimCriteria, 00785 VmSupport, 00786 ProcessToTrim 00787 ); 00788 00789 #if DBG 00790 if (MmDebug & MM_DBG_WS_EXPANSION) { 00791 if (Trim) { 00792 if (VmSupport->u.Flags.SessionSpace == 0) { 00793 DbgPrint(" Trimming Process %16s %5d Faults, WS %6d, Trimming %5d ==> %5d\n", 00794 ProcessToTrim ? ProcessToTrim->ImageFileName : (PUCHAR)"System Cache", 00795 VmSupport->PageFaultCount - LastTrimFaultCount, 00796 VmSupport->WorkingSetSize, 00797 Trim, 00798 VmSupport->WorkingSetSize-Trim 00799 ); 00800 } 00801 else { 00802 DbgPrint(" Trimming Session 0x%x (id %d) %5d Faults, WS %6d, Trimming %5d ==> %5d\n", 00803 SessionSpace, 00804 SessionSpace->SessionId, 00805 VmSupport->PageFaultCount - LastTrimFaultCount, 00806 VmSupport->WorkingSetSize, 00807 Trim, 00808 VmSupport->WorkingSetSize-Trim 00809 ); 00810 } 00811 } 00812 } 00813 #endif //DBG 00814 00815 #ifdef _MI_USE_CLAIMS_ 00816 00817 // 00818 // If there's something to trim... 00819 // 00820 00821 if (Trim != 0 && 00822 (MmAvailablePages < TrimCriteria.ClaimBased.DesiredFreeGoal)) { 00823 00824 // 00825 // We haven't reached our goal, so trim now. 00826 // 00827 00828 PERFINFO_WSMANAGE_TOTRIM(Trim); 00829 00830 Trim = MiTrimWorkingSet (Trim, 00831 VmSupport, 00832 TrimCriteria.ClaimBased.TrimAge 00833 ); 00834 00835 PERFINFO_WSMANAGE_ACTUALTRIM(Trim); 00836 } 00837 00838 // 00839 // Estimating the current claim is always done here by taking a 00840 // sample of the working set. Aging is only done if the trim 00841 // pass warrants it (ie: the first pass only). 00842 // 00843 00844 MiAgeAndEstimateAvailableInWorkingSet( 00845 VmSupport, 00846 TrimCriteria.ClaimBased.DoAging, 00847 &TrimCriteria.ClaimBased.NewTotalClaim, 00848 &TrimCriteria.ClaimBased.NewTotalEstimatedAvailable 00849 ); 00850 #else 00851 if (Trim != 0) { 00852 00853 PERFINFO_WSMANAGE_TOTRIM(Trim); 00854 00855 Trim = MiTrimWorkingSet ( 00856 Trim, 00857 VmSupport, 00858 (BOOLEAN)(MiCheckCounter < MM_TRIM_COUNTER_MAXIMUM_LARGE_MEM) 00859 ); 00860 00861 PERFINFO_WSMANAGE_ACTUALTRIM(Trim); 00862 } 00863 #endif 00864 00865 // 00866 // Set the quota to the current size. 00867 // 00868 00869 WorkingSetList->Quota = VmSupport->WorkingSetSize; 00870 if (WorkingSetList->Quota < VmSupport->MinimumWorkingSetSize) { 00871 WorkingSetList->Quota = VmSupport->MinimumWorkingSetSize; 00872 } 00873 00874 if (SessionSpace) { 00875 00876 ASSERT (VmSupport->u.Flags.SessionSpace == 1); 00877 00878 UNLOCK_SESSION_SPACE_WS (OldIrql); 00879 00880 MiDetachSession (); 00881 } 00882 else if (VmSupport != &MmSystemCacheWs) { 00883 00884 ASSERT (VmSupport->u.Flags.SessionSpace == 0); 00885 UNLOCK_WS (ProcessToTrim); 00886 00887 if (InformSessionOfRelease == TRUE) { 00888 LOCK_EXPANSION (OldIrql); 00889 ASSERT (ProcessToTrim->ProcessOutswapEnabled == TRUE); 00890 ProcessToTrim->ProcessOutswapEnabled = FALSE; 00891 ASSERT (MmSessionSpace->ProcessOutSwapCount >= 1); 00892 MmSessionSpace->ProcessOutSwapCount -= 1; 00893 UNLOCK_EXPANSION (OldIrql); 00894 InformSessionOfRelease = FALSE; 00895 } 00896 00897 if (Attached) { 00898 KeDetachProcess (); 00899 Attached = 0; 00900 } 00901 00902 } 00903 else { 00904 ASSERT (VmSupport->u.Flags.SessionSpace == 0); 00905 UNLOCK_SYSTEM_WS (OldIrql); 00906 } 00907 00908 LOCK_EXPANSION (OldIrql); 00909 00910 ASSERT (VmSupport->u.Flags.BeingTrimmed == 1); 00911 VmSupport->u.Flags.BeingTrimmed = 0; 00912 00913 WorkingSetLockFailed: 00914 00915 ASSERT (VmSupport->WorkingSetExpansionLinks.Flink == MM_NO_WS_EXPANSION); 00916 00917 if (VmSupport->WorkingSetExpansionLinks.Blink == 00918 MM_WS_EXPANSION_IN_PROGRESS) { 00919 00920 // 00921 // If the working set size is still above the minimum, 00922 // add this back at the tail of the list. 00923 // 00924 00925 InsertTailList (&MmWorkingSetExpansionHead.ListHead, 00926 &VmSupport->WorkingSetExpansionLinks); 00927 } 00928 else { 00929 00930 // 00931 // The value in the blink is the address of an event 00932 // to set. 00933 // 00934 00935 ASSERT (VmSupport != &MmSystemCacheWs); 00936 00937 KeSetEvent ((PKEVENT)VmSupport->WorkingSetExpansionLinks.Blink, 00938 0, 00939 FALSE); 00940 } 00941 00942 #ifndef _MI_USE_CLAIMS_ 00943 TrimCriteria.FaultBased.TotalReduction += Trim; 00944 00945 // 00946 // Zero this in case the next attach fails. 00947 // 00948 00949 Trim = 0; 00950 00951 if (MiCheckCounter < MM_TRIM_COUNTER_MAXIMUM_LARGE_MEM) { 00952 if ((MmAvailablePages > TrimCriteria.FaultBased.DesiredFreeGoal) || 00953 (TrimCriteria.FaultBased.TotalReduction > TrimCriteria.FaultBased.DesiredReductionGoal)) { 00954 00955 00956 // 00957 // Ample pages now exist. 00958 // 00959 00960 PERFINFO_WSMANAGE_FINALACTION(WS_ACTION_AMPLE_PAGES_EXIST); 00961 break; 00962 } 00963 } 00964 #endif 00965 00966 } 00967 00968 #ifdef _MI_USE_CLAIMS_ 00969 MmTotalClaim = TrimCriteria.ClaimBased.NewTotalClaim; 00970 MmTotalEstimatedAvailable = TrimCriteria.ClaimBased.NewTotalEstimatedAvailable; 00971 PERFINFO_WSMANAGE_TRIMEND_CLAIMS(&TrimCriteria); 00972 #else 00973 MiCheckCounter = 0; 00974 PERFINFO_WSMANAGE_TRIMEND_FAULTS(&TrimCriteria); 00975 #endif 00976 00977 UNLOCK_EXPANSION (OldIrql); 00978 } 00979 00980 // 00981 // Signal the modified page writer as we have moved pages 00982 // to the modified list and memory was critical. 00983 // 00984 00985 if ((MmAvailablePages < MmMinimumFreePages) || 00986 (MmModifiedPageListHead.Total >= MmModifiedPageMaximum)) { 00987 KeSetEvent (&MmModifiedPageWriterEvent, 0, FALSE); 00988 } 00989 00990 ASSERT (CurrentProcess == PsGetCurrentProcess ()); 00991 00992 return; 00993 } 00994 00995 LOGICAL 00996 MiCheckAndSetSystemTrimCriteria( 00997 PMMWS_TRIM_CRITERIA Criteria 00998 ) 00999 01000 /*++ 01001 01002 Routine Description: 01003 01004 Decide whether to trim at this time. If using claims, then this 01005 routine may initiate aging and claim adjustments as well. 01006 01007 Arguments: 01008 01009 Criteria - Supplies a pointer to the trim criteria information. Various 01010 fields in this structure are set as needed by this routine. 01011 01012 Return Value: 01013 01014 TRUE if the caller should initiate trimming, FALSE if not. 01015 01016 Environment: 01017 01018 Kernel mode. No locks held. APC level or below. 01019 01020 --*/ 01021 01022 { 01023 PFN_NUMBER Available; 01024 ULONG PageFaultCount; 01025 KIRQL OldIrql; 01026 BOOLEAN InitiateTrim; 01027 01028 PERFINFO_WSMANAGE_DECL(); 01029 01030 // 01031 // See if an empty-all-working-sets request has been queued to us. 01032 // This only happens on Hydra. 01033 // 01034 01035 if (MiWaitingForWorkingSetEmpty == TRUE) { 01036 01037 MiEmptyAllWorkingSetsWorker (); 01038 01039 LOCK_EXPANSION (OldIrql); 01040 01041 KeSetEvent (&MiWaitForEmptyEvent, 0, FALSE); 01042 MiWaitingForWorkingSetEmpty = FALSE; 01043 01044 UNLOCK_EXPANSION (OldIrql); 01045 01046 #ifdef _MI_USE_CLAIMS_ 01047 MiReplacing = FALSE; 01048 #endif 01049 01050 return FALSE; 01051 } 01052 01053 // 01054 // Check the number of pages available to see if any trimming 01055 // is really required. 01056 // 01057 01058 LOCK_PFN (OldIrql); 01059 Available = MmAvailablePages; 01060 PageFaultCount = MmInfoCounters.PageFaultCount; 01061 UNLOCK_PFN (OldIrql); 01062 01063 #ifdef _MI_USE_CLAIMS_ 01064 PERFINFO_WSMANAGE_STARTLOG_CLAIMS(); 01065 if (Available > MmPlentyFreePages && MiReplacing == FALSE) { 01066 01067 // 01068 // Don't trim but do age unused pages and estimate 01069 // the amount available in working sets. 01070 // 01071 01072 MiAgePagesAndEstimateClaims (); 01073 MiAdjustClaimParameters (TRUE); 01074 PERFINFO_WSMANAGE_TRIMACTION (WS_ACTION_RESET_COUNTER); 01075 } 01076 else { 01077 01078 // 01079 // Inform our caller to start trimming since we're below 01080 // plenty pages - order the list so the bigger working sets are 01081 // in front so our caller trims those first. 01082 // 01083 01084 Criteria->ClaimBased.NumPasses = 0; 01085 Criteria->ClaimBased.DesiredFreeGoal = MmPlentyFreePages + 01086 (MmPlentyFreePages / 2); 01087 Criteria->ClaimBased.NewTotalClaim = 0; 01088 Criteria->ClaimBased.NewTotalEstimatedAvailable = 0; 01089 Criteria->ClaimBased.NumberOfForegroundProcesses = 0; 01090 01091 // 01092 // Start trimming the bigger working sets first. 01093 // 01094 01095 MiRearrangeWorkingSetExpansionList (); 01096 01097 #if DBG 01098 if (MmDebug & MM_DBG_WS_EXPANSION) { 01099 DbgPrint("\nMM-wsmanage: Desired = %ld, Avail %ld\n", 01100 Criteria->ClaimBased.DesiredFreeGoal, MmAvailablePages); 01101 } 01102 #endif //DBG 01103 01104 PERFINFO_WSMANAGE_WILLTRIM_CLAIMS(Criteria); 01105 01106 MiReplacing = FALSE; 01107 01108 return TRUE; 01109 } 01110 PERFINFO_WSMANAGE_DUMPENTRIES_CLAIMS(); 01111 01112 // 01113 // If we've been replacing within a given working set, it's worth doing 01114 // a trim. No need to lock synchronize the MiReplacing clearing as it 01115 // gets set every time a page replacement happens anyway. 01116 // 01117 01118 InitiateTrim = MiReplacing; 01119 01120 MiReplacing = FALSE; 01121 01122 return InitiateTrim; 01123 01124 #else 01125 01126 PERFINFO_WSMANAGE_STARTLOG_FAULTS(); 01127 01128 if ((Available > (MmNumberOfPhysicalPages >> 2)) || 01129 ((Available > MmMoreThanEnoughFreePages) && 01130 ((PageFaultCount - MmLastFaultCount) < MM_REDUCE_FAULT_COUNT))) { 01131 01132 // 01133 // Don't trim and zero the check counter. 01134 // 01135 01136 MiCheckCounter = 0; 01137 PERFINFO_WSMANAGE_TRIMACTION(WS_ACTION_RESET_COUNTER); 01138 01139 } else if ((Available > MmAmpleFreePages) && 01140 ((PageFaultCount - MmLastFaultCount) < MM_IGNORE_FAULT_COUNT)) { 01141 01142 // 01143 // Don't do anything. 01144 // 01145 01146 NOTHING; 01147 PERFINFO_WSMANAGE_TRIMACTION(WS_ACTION_NOTHING); 01148 01149 } else if ((Available > MmFreeGoal) && 01150 (MiCheckCounter < MM_TRIM_COUNTER_MAXIMUM_LARGE_MEM)) { 01151 01152 // 01153 // Don't trim, but increment the check counter. 01154 // 01155 01156 MiCheckCounter += 1; 01157 PERFINFO_WSMANAGE_TRIMACTION(WS_ACTION_INCREMENT_COUNTER); 01158 01159 } 01160 else { 01161 01162 // 01163 // Initialize state variables. 01164 // 01165 01166 Criteria->FaultBased.NumPasses = 0; 01167 Criteria->FaultBased.TotalReduction = 0; 01168 Criteria->FaultBased.NumberOfForegroundProcesses = 0; 01169 01170 // 01171 // Set the total reduction goals. 01172 // 01173 01174 Criteria->FaultBased.DesiredReductionGoal = MmPagesAboveWsMinimum >> 2; 01175 if (MmPagesAboveWsMinimum > (MmFreeGoal << 1)) { 01176 Criteria->FaultBased.DesiredFreeGoal = MmFreeGoal; 01177 } 01178 else { 01179 Criteria->FaultBased.DesiredFreeGoal = MmMinimumFreePages + 10; 01180 } 01181 01182 // 01183 // Calculate the number of faults to be taken to not be trimmed. 01184 // 01185 01186 if (Available > MmMoreThanEnoughFreePages) { 01187 Criteria->FaultBased.FaultCount = 1; 01188 } 01189 else { 01190 Criteria->FaultBased.FaultCount = MiIdealPassFaultCountDisable; 01191 } 01192 01193 #if DBG 01194 if (MmDebug & MM_DBG_WS_EXPANSION) { 01195 DbgPrint("\nMM-wsmanage: checkcounter = %ld, Desired = %ld, Free = %ld Avail %ld\n", 01196 MiCheckCounter, Criteria->FaultBased.DesiredReductionGoal, 01197 Criteria->FaultBased.DesiredFreeGoal, MmAvailablePages); 01198 } 01199 #endif //DBG 01200 01201 PERFINFO_WSMANAGE_WILLTRIM_FAULTS(Criteria); 01202 01203 if (MiHydra == TRUE) { 01204 MiRearrangeWorkingSetExpansionList (); 01205 } 01206 01207 MmLastFaultCount = PageFaultCount; 01208 01209 return TRUE; 01210 } 01211 PERFINFO_WSMANAGE_DUMPENTRIES_FAULTS(); 01212 01213 return FALSE; 01214 #endif 01215 } 01216 01217 BOOLEAN 01218 MiCheckSystemTrimEndCriteria( 01219 IN PMMWS_TRIM_CRITERIA Criteria, 01220 IN KIRQL OldIrql 01221 ) 01222 01223 /*++ 01224 01225 Routine Description: 01226 01227 Check the ending criteria. If we're not done, delay for a little 01228 bit to let the modified write catch up. 01229 01230 Arguments: 01231 01232 Criteria - Supplies the trim criteria information. 01233 01234 OldIrql - Supplies the old IRQL to lower to if the expansion lock needs 01235 to be released. 01236 01237 Return Value: 01238 01239 TRUE if trimming can be stopped, FALSE otherwise. 01240 01241 Environment: 01242 01243 Kernel mode. Expansion lock held. APC level or below. 01244 01245 --*/ 01246 01247 { 01248 BOOLEAN FinishedTrimming; 01249 PERFINFO_WSMANAGE_DECL(); 01250 01251 FinishedTrimming = FALSE; 01252 01253 #ifdef _MI_USE_CLAIMS_ 01254 if ((MmAvailablePages > Criteria->ClaimBased.DesiredFreeGoal) || 01255 (Criteria->ClaimBased.NumPasses >= MI_MAX_TRIM_PASSES)) { 01256 01257 // 01258 // We have enough pages or we trimmed as many as we're going to get. 01259 // 01260 01261 FinishedTrimming = TRUE; 01262 } 01263 else { 01264 01265 // 01266 // Update the global claim and estimate before we wait. 01267 // 01268 01269 MmTotalClaim = Criteria->ClaimBased.NewTotalClaim; 01270 MmTotalEstimatedAvailable = Criteria->ClaimBased.NewTotalEstimatedAvailable; 01271 } 01272 #else 01273 if (MmAvailablePages > MmMinimumFreePages) { 01274 01275 // 01276 // Every process has been examined and ample pages 01277 // now exist. 01278 // 01279 01280 MmNumberOfForegroundProcesses = Criteria->FaultBased.NumberOfForegroundProcesses; 01281 01282 FinishedTrimming = TRUE; 01283 } 01284 #endif 01285 01286 if (FinishedTrimming == FALSE) { 01287 01288 // 01289 // If we don't have enough pages wait 10 milliseconds 01290 // for the modified page writer to catch up. The wait is also 01291 // important because a thread may have the system cache locked but 01292 // has been preempted by the balance set manager due to its higher 01293 // priority. We must give this thread a shot at running so it can 01294 // release the system cache lock (all the trimmable pages may reside in 01295 // the system cache). 01296 // 01297 01298 UNLOCK_EXPANSION (OldIrql); 01299 01300 KeDelayExecutionThread (KernelMode, 01301 FALSE, 01302 &MmShortTime); 01303 01304 #ifdef _MI_USE_CLAIMS_ 01305 PERFINFO_WSMANAGE_WAITFORWRITER_CLAIMS(); 01306 #else 01307 PERFINFO_WSMANAGE_WAITFORWRITER_FAULTS(); 01308 #endif 01309 01310 // 01311 // Check again to see if we've met the criteria to stop trimming. 01312 // 01313 01314 #ifdef _MI_USE_CLAIMS_ 01315 if (MmAvailablePages > Criteria->ClaimBased.DesiredFreeGoal) { 01316 01317 // 01318 // Now we have enough pages so break out. 01319 // 01320 01321 FinishedTrimming = TRUE; 01322 } 01323 else { 01324 01325 // 01326 // We don't have enough pages so let's do another pass. 01327 // Go get the next working set list which is probably the 01328 // one we put back before we gave up the processor. 01329 // 01330 01331 if (Criteria->ClaimBased.NumPasses == 0) { 01332 MiAdjustClaimParameters(FALSE); 01333 } 01334 01335 Criteria->ClaimBased.NumPasses += 1; 01336 Criteria->ClaimBased.NewTotalClaim = 0; 01337 Criteria->ClaimBased.NewTotalEstimatedAvailable = 0; 01338 01339 PERFINFO_WSMANAGE_TRIMACTION(WS_ACTION_FORCE_TRIMMING_PROCESS); 01340 } 01341 #else 01342 01343 if (MmAvailablePages > MmMinimumFreePages) { 01344 01345 // 01346 // Now we have enough pages so break out. 01347 // 01348 01349 FinishedTrimming = TRUE; 01350 } 01351 else { 01352 01353 // 01354 // Change this to a forced trim, so we get pages 01355 // available, and reset the current time. 01356 // 01357 01358 PERFINFO_WSMANAGE_TRIMACTION(WS_ACTION_FORCE_TRIMMING_PROCESS); 01359 01360 MiCheckCounter = 0; 01361 Criteria->FaultBased.NumPasses += 1; 01362 } 01363 #endif 01364 01365 LOCK_EXPANSION (OldIrql); 01366 } 01367 01368 return FinishedTrimming; 01369 } 01370 01371 BOOLEAN 01372 MiCheckProcessTrimCriteria( 01373 PMMWS_TRIM_CRITERIA Criteria, 01374 PMMSUPPORT VmSupport, 01375 PEPROCESS Process, 01376 PLARGE_INTEGER CurrentTime 01377 ) 01378 01379 /*++ 01380 01381 Routine Description: 01382 01383 Determine whether the specified working set should be trimmed. 01384 01385 Arguments: 01386 01387 Criteria - Supplies the trim criteria information. 01388 01389 VmSupport - Supplies the working set information for the candidate process. 01390 01391 Process - Supplies the process to trim. NULL if this is for a session. 01392 01393 CurrentTime - Supplies the time at the start of this trim round. 01394 01395 Return Value: 01396 01397 TRUE if trimming should be done on this process, FALSE otherwise. 01398 01399 Environment: 01400 01401 Kernel mode. Expansion lock held. APC level or below. 01402 01403 --*/ 01404 01405 { 01406 BOOLEAN Trim; 01407 BOOLEAN Reset; 01408 BOOLEAN Responsive; 01409 01410 #if DBG 01411 if (Process) { 01412 ASSERT (VmSupport->u.Flags.SessionSpace == 0); 01413 } 01414 else { 01415 ASSERT (VmSupport->u.Flags.SessionSpace == 1); 01416 } 01417 #endif 01418 01419 if (VmSupport->u.Flags.TrimHard == 1 && VmSupport->WorkingSetSize) { 01420 return TRUE; 01421 } 01422 01423 #ifdef _MI_USE_CLAIMS_ 01424 01425 // 01426 // Always trim if there's anything left. Note that the 01427 // foreground process is given priority by putting it last in the 01428 // working set list and stopping trimming when we have enough pages. 01429 // 01430 01431 if (VmSupport->WorkingSetSize <= 3) { 01432 return FALSE; 01433 } 01434 01435 return TRUE; 01436 #else 01437 01438 Trim = TRUE; 01439 Reset = FALSE; 01440 01441 if (Process && Process->Vm.MemoryPriority == MEMORY_PRIORITY_FOREGROUND && Criteria->FaultBased.NumPasses == 0) { 01442 01443 Criteria->FaultBased.NumberOfForegroundProcesses += 1; 01444 } 01445 01446 if (MiCheckCounter >= MM_TRIM_COUNTER_MAXIMUM_LARGE_MEM) { 01447 01448 // 01449 // This is a voluntary trim so don't trim if 01450 // - the page fault count is too high 01451 // - or the size is too small 01452 // - or not enough time has elapsed since it was last trimmed 01453 // 01454 01455 if (((VmSupport->PageFaultCount - VmSupport->LastTrimFaultCount) > 01456 Criteria->FaultBased.FaultCount) 01457 || 01458 (VmSupport->WorkingSetSize <= 5) 01459 01460 || 01461 ((CurrentTime->QuadPart - VmSupport->LastTrimTime.QuadPart) < 01462 MmWorkingSetProtectionTime.QuadPart)) { 01463 01464 #if DBG 01465 if (MmDebug & MM_DBG_WS_EXPANSION) { 01466 if (VmSupport->WorkingSetSize > 5) { 01467 01468 if (Process) { 01469 DbgPrint(" ***** Skipping Process %16s %5d Faults, WS %6d\n", 01470 Process->ImageFileName, 01471 VmSupport->PageFaultCount - VmSupport->LastTrimFaultCount, 01472 VmSupport->WorkingSetSize 01473 ); 01474 } 01475 else { 01476 PMM_SESSION_SPACE SessionSpace; 01477 01478 ASSERT (MiHydra == TRUE); 01479 SessionSpace = CONTAINING_RECORD(VmSupport, 01480 MM_SESSION_SPACE, 01481 Vm); 01482 01483 DbgPrint(" ***** Skipping Session %d %5d Faults, WS %6d\n", 01484 SessionSpace->SessionId, 01485 VmSupport->PageFaultCount - VmSupport->LastTrimFaultCount, 01486 VmSupport->WorkingSetSize 01487 ); 01488 } 01489 } 01490 } 01491 #endif //DBG 01492 01493 Reset = TRUE; 01494 Trim = FALSE; 01495 } 01496 } else { 01497 01498 // 01499 // This is a forced trim. If this process is above its 01500 // minimum it's a candidate. 01501 // 01502 01503 if (VmSupport->WorkingSetSize <= VmSupport->MinimumWorkingSetSize) { 01504 01505 // 01506 // If this process is below its minimum, don't trim it 01507 // unless stacks are swapped out or it's paging a bit. 01508 // 01509 01510 if (((MmAvailablePages + 5) >= MmFreeGoal) && 01511 (((VmSupport->LastTrimFaultCount != 01512 VmSupport->PageFaultCount) || 01513 (Process && Process->ProcessOutswapEnabled == FALSE)))) { 01514 01515 // 01516 // If we've almost reached our goal and either this process 01517 // has taken page faults since the last trim or it's 01518 // not swap enabled, then don't trim it but do reset it. 01519 // 01520 01521 Reset = TRUE; 01522 Trim = FALSE; 01523 } 01524 else if ((VmSupport->WorkingSetSize < 5) || 01525 ((CurrentTime->QuadPart - VmSupport->LastTrimTime.QuadPart) < 01526 MmWorkingSetProtectionTime.QuadPart)) { 01527 01528 // 01529 // If the working set is very small or it was trimmed 01530 // recently don't trim it again. 01531 // 01532 01533 Reset = TRUE; 01534 Trim = FALSE; 01535 } 01536 } 01537 } 01538 01539 if (Trim == TRUE) { 01540 01541 // 01542 // Fix to supply foreground responsiveness by not trimming 01543 // foreground priority applications as aggressively. 01544 // 01545 01546 Responsive = FALSE; 01547 01548 if ((MmNumberOfForegroundProcesses <= 3) && 01549 (Criteria->FaultBased.NumberOfForegroundProcesses <= 3) && 01550 (VmSupport->MemoryPriority)) { 01551 01552 if ((MmAvailablePages > (MmMoreThanEnoughFreePages >> 2)) || 01553 (VmSupport->MemoryPriority >= MEMORY_PRIORITY_FOREGROUND)) { 01554 01555 // 01556 // Indicate that memory responsiveness to the foreground 01557 // process is important (not so for large console trees). 01558 // 01559 01560 Responsive = TRUE; 01561 } 01562 } 01563 01564 if (Responsive == TRUE && Criteria->FaultBased.NumPasses == 0) { 01565 01566 // 01567 // Note that NumPasses yields a measurement of how 01568 // desperate we are for memory, if NumPasses is nonzero, 01569 // we are in trouble. 01570 // 01571 01572 Trim = FALSE; 01573 } 01574 } 01575 01576 if (Trim == FALSE) { 01577 if (Reset == TRUE) { 01578 VmSupport->LastTrimTime = *CurrentTime; 01579 VmSupport->LastTrimFaultCount = VmSupport->PageFaultCount; 01580 } 01581 01582 PERFINFO_WSMANAGE_PROCESS_RESET(VmSupport); 01583 } 01584 01585 return Trim; 01586 #endif 01587 } 01588 #ifndef _MI_USE_CLAIMS_ 01589 01590 LOGICAL 01591 MiCheckSystemCacheWsTrimCriteria( 01592 PMMSUPPORT VmSupport 01593 ) 01594 01595 /*++ 01596 01597 Routine Description: 01598 01599 Determine whether the system cache should be trimmed 01600 01601 Arguments: 01602 01603 VmSupport - Supplies the working set information for the system cache. 01604 01605 Return Value: 01606 01607 TRUE if trimming should be done on this process, FALSE otherwise. 01608 01609 Environment: 01610 01611 Kernel mode. Expansion lock held. APC level or below. 01612 01613 --*/ 01614 01615 { 01616 LOGICAL Trim; 01617 01618 // 01619 // Don't trim the system cache if this is a voluntary trim and 01620 // the working set is within 100 pages of the minimum or 01621 // if the cache is at its minimum. 01622 // 01623 01624 if ((MiCheckCounter >= MM_TRIM_COUNTER_MAXIMUM_LARGE_MEM) && 01625 (((LONG)VmSupport->WorkingSetSize - 01626 (LONG)VmSupport->MinimumWorkingSetSize) < 100)) { 01627 01628 // 01629 // Don't trim it if it's near its minimum and it's not a forced trim. 01630 // 01631 01632 Trim = FALSE; 01633 } 01634 else { 01635 Trim = TRUE; 01636 } 01637 01638 return Trim; 01639 } 01640 #endif 01641 01642 01643 ULONG 01644 MiDetermineWsTrimAmount( 01645 PMMWS_TRIM_CRITERIA Criteria, 01646 PMMSUPPORT VmSupport, 01647 PEPROCESS Process 01648 ) 01649 01650 /*++ 01651 01652 Routine Description: 01653 01654 Determine whether this process should be trimmed. 01655 01656 Arguments: 01657 01658 Criteria - Supplies the trim criteria information. 01659 01660 VmSupport - Supplies the working set information for the candidate process. 01661 01662 Process - Supplies the candidate process to be trimmed. 01663 01664 Return Value: 01665 01666 TRUE if trimming should be done on this process, FALSE if not. 01667 01668 Environment: 01669 01670 Kernel mode. Expansion lock held. APC level or below. 01671 01672 --*/ 01673 01674 { 01675 PMMWSL WorkingSetList; 01676 ULONG MaxTrim; 01677 ULONG Trim; 01678 BOOLEAN OutswapEnabled; 01679 01680 if (Process) { 01681 OutswapEnabled = Process->ProcessOutswapEnabled; 01682 } 01683 else { 01684 if (VmSupport->u.Flags.TrimHard == 1) { 01685 OutswapEnabled = TRUE; 01686 } 01687 else { 01688 OutswapEnabled = FALSE; 01689 } 01690 } 01691 01692 WorkingSetList = VmSupport->VmWorkingSetList; 01693 01694 if (VmSupport->WorkingSetSize <= WorkingSetList->FirstDynamic) { 01695 return 0; 01696 } 01697 01698 #ifdef _MI_USE_CLAIMS_ 01699 MaxTrim = VmSupport->WorkingSetSize; 01700 01701 if (OutswapEnabled == FALSE) { 01702 01703 // 01704 // Don't trim the cache or non-swapped sessions or processes 01705 // below their minimum. 01706 // 01707 01708 MaxTrim -= VmSupport->MinimumWorkingSetSize; 01709 } 01710 01711 switch (Criteria->ClaimBased.NumPasses) { 01712 case 0: 01713 Trim = VmSupport->Claim >> 01714 ((VmSupport->MemoryPriority == MEMORY_PRIORITY_FOREGROUND) 01715 ? MI_FOREGROUND_CLAIM_AVAILABLE_SHIFT 01716 : MI_BACKGROUND_CLAIM_AVAILABLE_SHIFT); 01717 Criteria->ClaimBased.TrimAge = MI_PASS0_TRIM_AGE; 01718 Criteria->ClaimBased.DoAging = TRUE; 01719 break; 01720 case 1: 01721 Trim = VmSupport->Claim >> 01722 ((VmSupport->MemoryPriority == MEMORY_PRIORITY_FOREGROUND) 01723 ? MI_FOREGROUND_CLAIM_AVAILABLE_SHIFT 01724 : MI_BACKGROUND_CLAIM_AVAILABLE_SHIFT); 01725 Criteria->ClaimBased.TrimAge = MI_PASS1_TRIM_AGE; 01726 Criteria->ClaimBased.DoAging = FALSE; 01727 break; 01728 case 2: 01729 Trim = VmSupport->Claim; 01730 Criteria->ClaimBased.TrimAge = MI_PASS2_TRIM_AGE; 01731 Criteria->ClaimBased.DoAging = FALSE; 01732 break; 01733 case 3: 01734 Trim = VmSupport->EstimatedAvailable; 01735 Criteria->ClaimBased.TrimAge = MI_PASS3_TRIM_AGE; 01736 Criteria->ClaimBased.DoAging = FALSE; 01737 break; 01738 default: 01739 Trim = VmSupport->EstimatedAvailable; 01740 Criteria->ClaimBased.TrimAge = MI_PASS3_TRIM_AGE; 01741 Criteria->ClaimBased.DoAging = FALSE; 01742 01743 if (MmAvailablePages < 100) { 01744 if (VmSupport->WorkingSetSize > VmSupport->MinimumWorkingSetSize) { 01745 Trim = (VmSupport->WorkingSetSize - VmSupport->MinimumWorkingSetSize) >> 2; 01746 } 01747 Criteria->ClaimBased.TrimAge = MI_PASS4_TRIM_AGE; 01748 Criteria->ClaimBased.DoAging = TRUE; 01749 } 01750 01751 break; 01752 } 01753 01754 if (Trim > MaxTrim) { 01755 Trim = MaxTrim; 01756 } 01757 01758 #else 01759 01760 UNREFERENCED_PARAMETER (Criteria); 01761 01762 // 01763 // Calculate Trim size. 01764 // 01765 01766 if (VmSupport->WorkingSetSize <= VmSupport->MinimumWorkingSetSize && 01767 OutswapEnabled == TRUE) { 01768 01769 // 01770 // Set the quota to the minimum and reduce the working 01771 // set size. 01772 // 01773 01774 WorkingSetList->Quota = VmSupport->MinimumWorkingSetSize; 01775 Trim = VmSupport->WorkingSetSize - WorkingSetList->FirstDynamic; 01776 if (Trim > MmWorkingSetSwapReduction) { 01777 Trim = MmWorkingSetSwapReduction; 01778 } 01779 01780 ASSERT ((LONG)Trim >= 0); 01781 01782 } else { 01783 01784 MaxTrim = VmSupport->WorkingSetSize - 01785 VmSupport->MinimumWorkingSetSize; 01786 01787 if (OutswapEnabled == TRUE) { 01788 01789 // 01790 // All thread stacks have been swapped out this process or 01791 // all the processes and threads have been swapped out of this 01792 // session. 01793 // 01794 01795 ULONG i; 01796 01797 Trim = MmWorkingSetSwapReduction; 01798 i = VmSupport->WorkingSetSize - VmSupport->MaximumWorkingSetSize; 01799 if ((LONG)i > 0) { 01800 Trim = i; 01801 if (Trim > MmWorkingSetSwapReductionHuge) { 01802 Trim = MmWorkingSetSwapReductionHuge; 01803 } 01804 } 01805 01806 } else if (MiCheckCounter >= MM_TRIM_COUNTER_MAXIMUM_LARGE_MEM) { 01807 01808 // 01809 // Haven't faulted much, reduce a bit. 01810 // 01811 01812 if (VmSupport->WorkingSetSize > 01813 (VmSupport->MaximumWorkingSetSize + 01814 (6 * MmWorkingSetVolReductionHuge))) { 01815 Trim = MmWorkingSetVolReductionHuge; 01816 01817 } else if ( (VmSupport != &MmSystemCacheWs) && 01818 VmSupport->WorkingSetSize > 01819 ( VmSupport->MaximumWorkingSetSize + (2 * MmWorkingSetReductionHuge))) { 01820 Trim = MmWorkingSetReductionHuge; 01821 } else if (VmSupport->WorkingSetSize > VmSupport->MaximumWorkingSetSize) { 01822 if (VmSupport != &MmSystemCacheWs) { 01823 Trim = MmWorkingSetVolReductionMax; 01824 } else { 01825 Trim = MmWorkingSetVolReductionMaxCacheWs; 01826 } 01827 } else { 01828 Trim = MmWorkingSetVolReductionMin; 01829 } 01830 01831 } else { 01832 01833 if (VmSupport->WorkingSetSize > 01834 (VmSupport->MaximumWorkingSetSize + 01835 (2 * MmWorkingSetReductionHuge))) { 01836 Trim = MmWorkingSetReductionHuge; 01837 01838 } else if (VmSupport->WorkingSetSize > VmSupport->MaximumWorkingSetSize) { 01839 if (VmSupport != &MmSystemCacheWs) { 01840 Trim = MmWorkingSetReductionMax; 01841 } else { 01842 Trim = MmWorkingSetReductionMaxCacheWs; 01843 } 01844 } else { 01845 if (VmSupport != &MmSystemCacheWs) { 01846 Trim = MmWorkingSetReductionMin; 01847 } else { 01848 Trim = MmWorkingSetReductionMinCacheWs; 01849 } 01850 } 01851 } 01852 01853 if (MaxTrim < Trim) { 01854 Trim = MaxTrim; 01855 } 01856 } 01857 #endif 01858 01859 return Trim; 01860 } 01861 #ifdef _MI_USE_CLAIMS_ 01862 01863 VOID 01864 MiAgePagesAndEstimateClaims( 01865 VOID 01866 ) 01867 01868 /*++ 01869 01870 Routine Description: 01871 01872 Walk through the processes on the working set expansion list 01873 aging pages and estimating the number of pages that they 01874 aren't using, the claim. 01875 01876 Arguments: 01877 01878 None. 01879 01880 Return Value: 01881 01882 None. 01883 01884 Environment: 01885 01886 Kernel mode, APCs disabled. PFN lock NOT held. 01887 01888 --*/ 01889 01890 { 01891 NTSTATUS status; 01892 PMMSUPPORT VmSupport; 01893 PMMSUPPORT FirstSeen; 01894 BOOLEAN SystemCacheSeen; 01895 LOGICAL Attached; 01896 BOOLEAN Locked; 01897 KIRQL OldIrql; 01898 PLIST_ENTRY ListEntry; 01899 PEPROCESS Process; 01900 ULONG NewTotalClaim; 01901 ULONG NewTotalEstimatedAvailable; 01902 PEPROCESS CurrentProcess; 01903 PMM_SESSION_SPACE SessionSpace; 01904 LOGICAL InformSessionOfRelease; 01905 ULONG LoopCount; 01906 01907 FirstSeen = NULL; 01908 SystemCacheSeen = FALSE; 01909 Attached = 0; 01910 Locked = FALSE; 01911 NewTotalClaim = 0; 01912 NewTotalEstimatedAvailable = 0; 01913 status = STATUS_SUCCESS; 01914 LoopCount = 0; 01915 01916 CurrentProcess = PsGetCurrentProcess (); 01917 01918 ASSERT (MiHydra == FALSE || MmIsAddressValid (MmSessionSpace) == FALSE); 01919 01920 LOCK_EXPANSION (OldIrql); 01921 01922 while (!IsListEmpty (&MmWorkingSetExpansionHead.ListHead)) { 01923 01924 // 01925 // Remove the entry at the head, try to lock it, if we can lock it 01926 // then age some pages and estimate the number of available pages. 01927 // 01928 01929 ListEntry = RemoveHeadList (&MmWorkingSetExpansionHead.ListHead); 01930 01931 ASSERT (MiHydra == FALSE || MmIsAddressValid (MmSessionSpace) == FALSE); 01932 01933 if (ListEntry == &MmSystemCacheWs.WorkingSetExpansionLinks) { 01934 VmSupport = &MmSystemCacheWs; 01935 Process = NULL; 01936 if (SystemCacheSeen != FALSE) { 01937 01938 // 01939 // Seen this one already. 01940 // 01941 01942 FirstSeen = VmSupport; 01943 } 01944 SystemCacheSeen = TRUE; 01945 } 01946 else { 01947 VmSupport = CONTAINING_RECORD(ListEntry, 01948 MMSUPPORT, 01949 WorkingSetExpansionLinks); 01950 01951 if (VmSupport->u.Flags.SessionSpace == 0) { 01952 01953 Process = CONTAINING_RECORD(ListEntry, 01954 EPROCESS, 01955 Vm.WorkingSetExpansionLinks); 01956 ASSERT (Process->AddressSpaceDeleted == 0); 01957 ASSERT (VmSupport->VmWorkingSetList == MmWorkingSetList); 01958 } 01959 else { 01960 ASSERT (MiHydra == TRUE); 01961 SessionSpace = CONTAINING_RECORD(VmSupport, 01962 MM_SESSION_SPACE, 01963 Vm); 01964 01965 Process = NULL; 01966 } 01967 } 01968 01969 ASSERT (VmSupport->u.Flags.BeingTrimmed == 0); 01970 01971 if (VmSupport == FirstSeen) { 01972 InsertHeadList (&MmWorkingSetExpansionHead.ListHead, 01973 &VmSupport->WorkingSetExpansionLinks); 01974 break; 01975 } 01976 01977 VmSupport->u.Flags.BeingTrimmed = 1; 01978 01979 if (FirstSeen == NULL) { 01980 FirstSeen = VmSupport; 01981 } 01982 01983 VmSupport->WorkingSetExpansionLinks.Flink = MM_NO_WS_EXPANSION; 01984 VmSupport->WorkingSetExpansionLinks.Blink = 01985 MM_WS_EXPANSION_IN_PROGRESS; 01986 UNLOCK_EXPANSION (OldIrql); 01987 01988 Locked = FALSE; 01989 if (VmSupport == &MmSystemCacheWs) { 01990 KeRaiseIrql (APC_LEVEL, &OldIrql); 01991 if (!ExTryToAcquireResourceExclusiveLite (&MmSystemWsLock)) { 01992 KeLowerIrql (OldIrql); 01993 goto FailureBranch; 01994 } 01995 MmSystemLockOwner = PsGetCurrentThread(); 01996 Locked = TRUE; 01997 } 01998 else if (VmSupport->u.Flags.SessionSpace == 0) { 01999 InformSessionOfRelease = FALSE; 02000 if (CurrentProcess != Process) { 02001 Attached = KeForceAttachProcess(&Process->Pcb); 02002 if (Attached == 0) { 02003 goto FailureBranch; 02004 } 02005 if (Process->ProcessOutswapEnabled == TRUE) { 02006 ASSERT (Process->ProcessOutswapped == FALSE); 02007 if (MiHydra == TRUE && VmSupport->u.Flags.ProcessInSession == 1 && VmSupport->u.Flags.SessionLeader == 0) { 02008 InformSessionOfRelease = TRUE; 02009 } 02010 } 02011 } 02012 if (ExTryToAcquireFastMutex(&Process->WorkingSetLock) == FALSE) { 02013 02014 if (InformSessionOfRelease == TRUE) { 02015 LOCK_EXPANSION (OldIrql); 02016 ASSERT (Process->ProcessOutswapEnabled == TRUE); 02017 Process->ProcessOutswapEnabled = FALSE; 02018 ASSERT (MmSessionSpace->ProcessOutSwapCount >= 1); 02019 MmSessionSpace->ProcessOutSwapCount -= 1; 02020 UNLOCK_EXPANSION (OldIrql); 02021 InformSessionOfRelease = FALSE; 02022 } 02023 02024 if (Attached) { 02025 KeDetachProcess (); 02026 Attached = 0; 02027 } 02028 goto FailureBranch; 02029 } 02030 02031 Locked = TRUE; 02032 } 02033 else { 02034 02035 ASSERT (MiHydra == TRUE); 02036 02037 // 02038 // Attach directly to the session space to be trimmed. 02039 // 02040 02041 MiAttachSession (SessionSpace); 02042 02043 KeRaiseIrql (APC_LEVEL, &OldIrql); 02044 if (!ExTryToAcquireResourceExclusiveLite (&SessionSpace->WsLock)) { 02045 02046 // 02047 // This session space's working set lock was not 02048 // granted, don't trim it. 02049 // 02050 02051 KeLowerIrql (OldIrql); 02052 02053 MiDetachSession (); 02054 02055 goto FailureBranch; 02056 } 02057 02058 Locked = TRUE; 02059 02060 MM_SET_SESSION_RESOURCE_OWNER(); 02061 } 02062 02063 FailureBranch: 02064 02065 if (Locked) { 02066 MiAgeAndEstimateAvailableInWorkingSet (VmSupport, 02067 TRUE, 02068 &NewTotalClaim, 02069 &NewTotalEstimatedAvailable 02070 ); 02071 02072 if (VmSupport == &MmSystemCacheWs) { 02073 ASSERT (VmSupport->u.Flags.SessionSpace == 0); 02074 UNLOCK_SYSTEM_WS (OldIrql); 02075 } 02076 else if (VmSupport->u.Flags.SessionSpace == 0) { 02077 02078 UNLOCK_WS (Process); 02079 02080 if (InformSessionOfRelease == TRUE) { 02081 LOCK_EXPANSION (OldIrql); 02082 ASSERT (Process->ProcessOutswapEnabled == TRUE); 02083 Process->ProcessOutswapEnabled = FALSE; 02084 ASSERT (MmSessionSpace->ProcessOutSwapCount >= 1); 02085 MmSessionSpace->ProcessOutSwapCount -= 1; 02086 UNLOCK_EXPANSION (OldIrql); 02087 InformSessionOfRelease = FALSE; 02088 } 02089 02090 if (Attached) { 02091 KeDetachProcess (); 02092 Attached = 0; 02093 } 02094 } 02095 else { 02096 ASSERT (MiHydra == TRUE); 02097 UNLOCK_SESSION_SPACE_WS (OldIrql); 02098 02099 MiDetachSession (); 02100 } 02101 } 02102 02103 LOCK_EXPANSION (OldIrql); 02104 02105 ASSERT (VmSupport->u.Flags.BeingTrimmed == 1); 02106 VmSupport->u.Flags.BeingTrimmed = 0; 02107 02108 ASSERT (VmSupport->WorkingSetExpansionLinks.Flink == MM_NO_WS_EXPANSION); 02109 if (VmSupport->WorkingSetExpansionLinks.Blink == 02110 MM_WS_EXPANSION_IN_PROGRESS) { 02111 02112 // 02113 // If the working set size is still above the minimum, 02114 // add this back at the tail of the list. 02115 // 02116 02117 InsertTailList (&MmWorkingSetExpansionHead.ListHead, 02118 &VmSupport->WorkingSetExpansionLinks); 02119 } 02120 else { 02121 02122 // 02123 // The value in the blink is the address of an event 02124 // to set. 02125 // 02126 02127 ASSERT (VmSupport != &MmSystemCacheWs); 02128 02129 KeSetEvent ((PKEVENT)VmSupport->WorkingSetExpansionLinks.Blink, 02130 0, 02131 FALSE); 02132 } 02133 02134 // 02135 // The initial working set that was chosen for FirstSeen may have 02136 // been trimmed down under its minimum and been removed from the 02137 // ExpansionHead links. It is possible that the system cache is not 02138 // on the links either. This check detects this extremely rare 02139 // situation so that the system does not spin forever. 02140 // 02141 02142 LoopCount += 1; 02143 if (LoopCount > 200) { 02144 if (MmSystemCacheWs.WorkingSetExpansionLinks.Blink == MM_WS_EXPANSION_IN_PROGRESS) { 02145 break; 02146 } 02147 } 02148 } 02149 02150 UNLOCK_EXPANSION(OldIrql); 02151 02152 MmTotalClaim = NewTotalClaim; 02153 MmTotalEstimatedAvailable = NewTotalEstimatedAvailable; 02154 02155 } 02156 02157 VOID 02158 MiAgeAndEstimateAvailableInWorkingSet( 02159 IN PMMSUPPORT VmSupport, 02160 IN BOOLEAN DoAging, 02161 IN OUT PULONG TotalClaim, 02162 IN OUT PULONG TotalEstimatedAvailable 02163 ) 02164 02165 /*++ 02166 02167 Routine Description: 02168 02169 Age pages (clear the access bit or if the page hasn't been 02170 accessed, increment the age) for a portion of the working 02171 set. Also, walk through a sample of the working set 02172 building a set of counts of how old the pages are. 02173 02174 The counts are used to create a claim of the amount 02175 the system can steal from this process if memory 02176 becomes tight. 02177 02178 Arguments: 02179 02180 VmSupport - Supplies the VM support structure to age and estimate. 02181 02182 DoAging - TRUE if pages are to be aged. Regardless, the pages will be 02183 added to the availablity estimation. 02184 02185 TotalClaim - Supplies a pointer to system wide claim to update. 02186 02187 TotalEstimatedAvailable - Supplies a pointer to system wide estimate 02188 to update. 02189 02190 Return Value: 02191 02192 None 02193 02194 Environment: 02195 02196 Kernel mode, APCs disabled, working set lock. PFN lock NOT held. 02197 02198 --*/ 02199 02200 { 02201 ULONG LastEntry; 02202 ULONG StartEntry; 02203 ULONG FirstDynamic; 02204 ULONG CurrentEntry; 02205 PMMWSL WorkingSetList; 02206 PMMWSLE Wsle; 02207 PMMPTE PointerPte; 02208 ULONG NumberToExamine; 02209 ULONG Claim; 02210 ULONG Estimate; 02211 ULONG SampledAgeCounts[MI_USE_AGE_COUNT] = {0}; 02212 MI_NEXT_ESTIMATION_SLOT_CONST NextConst; 02213 02214 WorkingSetList = VmSupport->VmWorkingSetList; 02215 Wsle = WorkingSetList->Wsle; 02216 02217 #if DBG 02218 if (VmSupport == &MmSystemCacheWs) { 02219 MM_SYSTEM_WS_LOCK_ASSERT(); 02220 } 02221 #endif //DBG 02222 02223 LastEntry = WorkingSetList->LastEntry; 02224 FirstDynamic = WorkingSetList->FirstDynamic; 02225 02226 if (DoAging == TRUE) { 02227 02228 // 02229 // Clear the used bits or increment the age of a 02230 // portion of the working set. 02231 // 02232 // In Usage Estimation/WorkingSet Aging, walk the entire 02233 // working set every 2^MI_AGE_AGING_SHIFT seconds. 02234 // 02235 02236 if (VmSupport->WorkingSetSize > WorkingSetList->FirstDynamic) { 02237 NumberToExamine = (VmSupport->WorkingSetSize - WorkingSetList->FirstDynamic) >> MiAgingShift; 02238 } 02239 else { 02240 NumberToExamine = 0; 02241 } 02242 02243 if (NumberToExamine != 0) { 02244 02245 CurrentEntry = VmSupport->NextAgingSlot; 02246 02247 if (CurrentEntry > LastEntry || CurrentEntry < FirstDynamic) { 02248 CurrentEntry = FirstDynamic; 02249 } 02250 02251 if (Wsle[CurrentEntry].u1.e1.Valid == 0) { 02252 MI_NEXT_VALID_AGING_SLOT(CurrentEntry, FirstDynamic, LastEntry, Wsle); 02253 } 02254 02255 while (NumberToExamine != 0) { 02256 02257 PointerPte = MiGetPteAddress (Wsle[CurrentEntry].u1.VirtualAddress); 02258 02259 if (MI_GET_ACCESSED_IN_PTE(PointerPte) == 1) { 02260 MI_SET_ACCESSED_IN_PTE(PointerPte, 0); 02261 MI_RESET_WSLE_AGE(PointerPte, &Wsle[CurrentEntry]); 02262 } 02263 else { 02264 MI_INC_WSLE_AGE(PointerPte, &Wsle[CurrentEntry]); 02265 } 02266 02267 NumberToExamine -= 1; 02268 MI_NEXT_VALID_AGING_SLOT(CurrentEntry, FirstDynamic, LastEntry, Wsle); 02269 } 02270 02271 VmSupport->NextAgingSlot = CurrentEntry + 1; // Start here next time 02272 } 02273 } 02274 02275 // 02276 // Estimate the number of unused pages in the working set. 02277 // 02278 // The working set may have shrunk or the non-paged portion may have 02279 // grown since the last time. Put the next counter at the FirstDynamic 02280 // if so. 02281 // 02282 02283 CurrentEntry = VmSupport->NextEstimationSlot; 02284 02285 if (CurrentEntry > LastEntry || CurrentEntry < FirstDynamic) { 02286 CurrentEntry = FirstDynamic; 02287 } 02288 02289 // 02290 // In Usage Estimation/WorkingSet Aging, walk the entire 02291 // working set every 2^MiEstimationShift seconds. 02292 // 02293 02294 if (VmSupport->WorkingSetSize > WorkingSetList->FirstDynamic) { 02295 NumberToExamine = (VmSupport->WorkingSetSize - WorkingSetList->FirstDynamic) >> MiEstimationShift; 02296 } 02297 else { 02298 NumberToExamine = 0; 02299 } 02300 02301 if (NumberToExamine != 0) { 02302 02303 MI_CALC_NEXT_ESTIMATION_SLOT_CONST(NextConst, WorkingSetList); 02304 02305 StartEntry = FirstDynamic; 02306 02307 if (Wsle[CurrentEntry].u1.e1.Valid == 0) { 02308 02309 MI_NEXT_VALID_ESTIMATION_SLOT (CurrentEntry, 02310 StartEntry, 02311 FirstDynamic, 02312 LastEntry, 02313 NextConst, 02314 Wsle); 02315 } 02316 02317 while (NumberToExamine != 0) { 02318 02319 PointerPte = MiGetPteAddress (Wsle[CurrentEntry].u1.VirtualAddress); 02320 02321 if (MI_GET_ACCESSED_IN_PTE(PointerPte) == 0) { 02322 MI_UPDATE_USE_ESTIMATE(PointerPte, 02323 &Wsle[CurrentEntry], 02324 SampledAgeCounts 02325 ); 02326 } 02327 02328 NumberToExamine -= 1; 02329 02330 MI_NEXT_VALID_ESTIMATION_SLOT (CurrentEntry, 02331 StartEntry, 02332 FirstDynamic, 02333 LastEntry, 02334 NextConst, 02335 Wsle); 02336 } 02337 } 02338 02339 // 02340 // Start estimation here next time. 02341 // 02342 02343 VmSupport->NextEstimationSlot = CurrentEntry + 1; 02344 02345 Estimate = MI_CALCULATE_USAGE_ESTIMATE(SampledAgeCounts); 02346 02347 Claim = VmSupport->Claim + MI_CLAIM_INCR; 02348 02349 if (Claim > Estimate) { 02350 Claim = Estimate; 02351 } 02352 02353 VmSupport->Claim = Claim; 02354 VmSupport->EstimatedAvailable = Estimate; 02355 02356 PERFINFO_WSMANAGE_DUMPWS(VmSupport, SampledAgeCounts); 02357 02358 VmSupport->GrowthSinceLastEstimate = 0; 02359 *TotalClaim += Claim >> ((VmSupport->MemoryPriority == MEMORY_PRIORITY_FOREGROUND) 02360 ? MI_FOREGROUND_CLAIM_AVAILABLE_SHIFT 02361 : MI_BACKGROUND_CLAIM_AVAILABLE_SHIFT); 02362 02363 *TotalEstimatedAvailable += Estimate; 02364 return; 02365 } 02366 02367 ULONG MiClaimAdjustmentThreshold[7] = { 0, 0, 0, 1000, 2000, 4000, 8000}; 02368 02369 VOID 02370 MiAdjustClaimParameters( 02371 BOOLEAN EnoughPages 02372 ) 02373 02374 /*++ 02375 02376 Routine Description: 02377 02378 Adjust the rate at which we walk through working sets. If we have 02379 enough pages (we aren't trimming pages that aren't considered young), 02380 then we check to see whether we should decrease the aging rate and 02381 vice versa. 02382 02383 The limits for the aging rate are 1/8 and 1/128 of the working sets. 02384 This means that the finest age granularities are 8 to 128 seconds in 02385 these cases. With the current 2 bit counter, at the low end we would 02386 start trimming pages > 16 seconds old and at the high end > 4 minutes. 02387 02388 Arguments: 02389 02390 EnoughPages - Supplies whether to increase the rate or decrease it. 02391 02392 Return Value: 02393 02394 None. 02395 02396 Environment: 02397 02398 Kernel mode. 02399 02400 --*/ 02401 02402 { 02403 LARGE_INTEGER CurrentTime; 02404 02405 KeQuerySystemTime (&CurrentTime); 02406 02407 if (EnoughPages == TRUE) { 02408 02409 // 02410 // Don't adjust the rate too frequently, don't go over the limit, and 02411 // make sure there are enough claimed and/or available. 02412 // 02413 02414 if (((CurrentTime.QuadPart - MiLastAdjustmentOfClaimParams.QuadPart) > 02415 MmClaimParameterAdjustUpTime.QuadPart) && 02416 (MiAgingShift < 7) && 02417 ((MmTotalClaim + MmAvailablePages) > MiClaimAdjustmentThreshold[MiAgingShift])) { 02418 02419 // 02420 // Set the time only when we change the rate. 02421 // 02422 02423 MiLastAdjustmentOfClaimParams.QuadPart = CurrentTime.QuadPart; 02424 02425 MiAgingShift += 1; 02426 MiEstimationShift += 1; 02427 } 02428 } 02429 else { 02430 02431 // 02432 // Don't adjust the rate down too frequently. 02433 // 02434 02435 if ((CurrentTime.QuadPart - MiLastAdjustmentOfClaimParams.QuadPart) > 02436 MmClaimParameterAdjustDownTime.QuadPart) { 02437 02438 // 02439 // Always set the time so we don't adjust up too soon after 02440 // a 2nd pass trim. 02441 // 02442 02443 MiLastAdjustmentOfClaimParams.QuadPart = CurrentTime.QuadPart; 02444 02445 // 02446 // Don't go under the limit. 02447 // 02448 02449 if (MiAgingShift > 3) { 02450 MiAgingShift -= 1; 02451 MiEstimationShift -= 1; 02452 } 02453 } 02454 } 02455 } 02456 #endif 02457 02458 #define MM_WS_REORG_BUCKETS_MAX 7 02459 02460 #if DBG 02461 ULONG MiSessionIdleBuckets[MM_WS_REORG_BUCKETS_MAX]; 02462 #endif 02463 02464 VOID 02465 MiRearrangeWorkingSetExpansionList( 02466 VOID 02467 ) 02468 02469 /*++ 02470 02471 Routine Description: 02472 02473 This function arranges the working set list into different 02474 groups based upon the claim. This is done so the working set 02475 trimming will take place on fat processes first. 02476 02477 The working sets are sorted into buckets and then linked back up. 02478 02479 Swapped out sessions and processes are put at the front. 02480 02481 Arguments: 02482 02483 None. 02484 02485 Return Value: 02486 02487 None. 02488 02489 Environment: 02490 02491 Kernel mode, no locks held. 02492 02493 --*/ 02494 02495 { 02496 KIRQL OldIrql; 02497 PLIST_ENTRY ListEntry; 02498 PMMSUPPORT VmSupport; 02499 int Size; 02500 int PreviousNonEmpty; 02501 int NonEmpty; 02502 LIST_ENTRY ListHead[MM_WS_REORG_BUCKETS_MAX]; 02503 LARGE_INTEGER CurrentTime; 02504 LARGE_INTEGER SessionIdleTime; 02505 ULONG IdleTime; 02506 PMM_SESSION_SPACE SessionGlobal; 02507 02508 KeQuerySystemTime (&CurrentTime); 02509 02510 if (IsListEmpty(&MmWorkingSetExpansionHead.ListHead)) { 02511 return; 02512 } 02513 02514 for (Size = 0 ; Size < MM_WS_REORG_BUCKETS_MAX; Size++) { 02515 InitializeListHead(&ListHead[Size]); 02516 } 02517 02518 LOCK_EXPANSION (OldIrql); 02519 02520 while (!IsListEmpty (&MmWorkingSetExpansionHead.ListHead)) { 02521 ListEntry = RemoveHeadList (&MmWorkingSetExpansionHead.ListHead); 02522 02523 VmSupport = CONTAINING_RECORD(ListEntry, 02524 MMSUPPORT, 02525 WorkingSetExpansionLinks); 02526 02527 if (VmSupport->u.Flags.TrimHard == 1) { 02528 02529 ASSERT (MiHydra == TRUE); 02530 ASSERT (VmSupport->u.Flags.SessionSpace == 1); 02531 02532 SessionGlobal = CONTAINING_RECORD (VmSupport, 02533 MM_SESSION_SPACE, 02534 Vm); 02535 02536 SessionIdleTime.QuadPart = CurrentTime.QuadPart - SessionGlobal->LastProcessSwappedOutTime.QuadPart; 02537 02538 #if DBG 02539 if (MmDebug & MM_DBG_SESSIONS) { 02540 DbgPrint ("Mm: Session %d heavily trim/aged - all its processes (%d) swapped out %d seconds ago\n", 02541 SessionGlobal->SessionId, 02542 SessionGlobal->ReferenceCount, 02543 (ULONG)(SessionIdleTime.QuadPart / 10000000)); 02544 } 02545 #endif 02546 02547 if (SessionIdleTime.QuadPart < 0) { 02548 02549 // 02550 // The administrator has moved the system time backwards. 02551 // Give this session a fresh start. 02552 // 02553 02554 SessionIdleTime.QuadPart = 0; 02555 KeQuerySystemTime (&SessionGlobal->LastProcessSwappedOutTime); 02556 } 02557 02558 IdleTime = (ULONG) (SessionIdleTime.QuadPart / 10000000); 02559 } 02560 else { 02561 IdleTime = 0; 02562 } 02563 02564 if (VmSupport->MemoryPriority == MEMORY_PRIORITY_FOREGROUND) { 02565 02566 // 02567 // Put the foreground processes at the end of the list, 02568 // to give them priority. 02569 // 02570 02571 Size = 6; 02572 } 02573 #ifdef _MI_USE_CLAIMS_ 02574 else { 02575 02576 if (VmSupport->Claim > 400) { 02577 Size = 0; 02578 } else if (IdleTime > 30) { 02579 Size = 0; 02580 #if DBG 02581 MiSessionIdleBuckets[Size] += 1; 02582 #endif 02583 } else if (VmSupport->Claim > 200) { 02584 Size = 1; 02585 } else if (IdleTime > 20) { 02586 Size = 1; 02587 #if DBG 02588 MiSessionIdleBuckets[Size] += 1; 02589 #endif 02590 } else if (VmSupport->Claim > 100) { 02591 Size = 2; 02592 } else if (IdleTime > 10) { 02593 Size = 2; 02594 #if DBG 02595 MiSessionIdleBuckets[Size] += 1; 02596 #endif 02597 } else if (VmSupport->Claim > 50) { 02598 Size = 3; 02599 } else if (IdleTime) { 02600 Size = 3; 02601 #if DBG 02602 MiSessionIdleBuckets[Size] += 1; 02603 #endif 02604 } else if (VmSupport->Claim > 25) { 02605 Size = 4; 02606 } else { 02607 Size = 5; 02608 #if DBG 02609 if (VmSupport->u.Flags.SessionSpace == 1) { 02610 MiSessionIdleBuckets[Size] += 1; 02611 } 02612 #endif 02613 } 02614 } 02615 #else 02616 else { 02617 02618 // 02619 // Just put swapped out entries at the front and keep all other 02620 // entries in the same order, just in front of the foreground 02621 // processes. 02622 // 02623 02624 if (IdleTime > 40) { 02625 Size = 0; 02626 } else if (IdleTime > 30) { 02627 Size = 1; 02628 } else if (IdleTime > 20) { 02629 Size = 2; 02630 } else if (IdleTime > 10) { 02631 Size = 3; 02632 } else if (IdleTime) { 02633 Size = 4; 02634 } else { 02635 InsertTailList (&ListHead[5], 02636 &VmSupport->WorkingSetExpansionLinks); 02637 continue; 02638 } 02639 #if DBG 02640 ASSERT (MiHydra == TRUE); 02641 ASSERT (VmSupport->u.Flags.SessionSpace == 1); 02642 MiSessionIdleBuckets[Size] += 1; 02643 #endif 02644 } 02645 #endif 02646 02647 #if DBG 02648 if (MmDebug & MM_DBG_WS_EXPANSION) { 02649 DbgPrint("MM-rearrange: TrimHard = %d, WS Size = 0x%x, Claim 0x%x, Bucket %d\n", 02650 VmSupport->u.Flags.TrimHard, 02651 VmSupport->WorkingSetSize, 02652 VmSupport->Claim, 02653 Size); 02654 } 02655 #endif //DBG 02656 02657 // 02658 // Note: this reverses the bucket order each time we 02659 // reorganize the lists. This may be good or bad - 02660 // if you change it you may want to think about it. 02661 // 02662 02663 InsertHeadList (&ListHead[Size], 02664 &VmSupport->WorkingSetExpansionLinks); 02665 } 02666 02667 // 02668 // Find the first non-empty list. 02669 // 02670 02671 for (NonEmpty = 0 ; NonEmpty < MM_WS_REORG_BUCKETS_MAX ; NonEmpty += 1) { 02672 if (!IsListEmpty (&ListHead[NonEmpty])) { 02673 break; 02674 } 02675 } 02676 02677 // 02678 // Put the head of first non-empty list at the beginning 02679 // of the MmWorkingSetExpansion list. 02680 // 02681 02682 MmWorkingSetExpansionHead.ListHead.Flink = ListHead[NonEmpty].Flink; 02683 ListHead[NonEmpty].Flink->Blink = &MmWorkingSetExpansionHead.ListHead; 02684 02685 PreviousNonEmpty = NonEmpty; 02686 02687 // 02688 // Link the rest of the lists together. 02689 // 02690 02691 for (NonEmpty += 1; NonEmpty < MM_WS_REORG_BUCKETS_MAX; NonEmpty += 1) { 02692 02693 if (!IsListEmpty (&ListHead[NonEmpty])) { 02694 02695 ListHead[PreviousNonEmpty].Blink->Flink = ListHead[NonEmpty].Flink; 02696 ListHead[NonEmpty].Flink->Blink = ListHead[PreviousNonEmpty].Blink; 02697 PreviousNonEmpty = NonEmpty; 02698 } 02699 } 02700 02701 // 02702 // Link the tail of last non-empty to the MmWorkingSetExpansion list. 02703 // 02704 02705 MmWorkingSetExpansionHead.ListHead.Blink = ListHead[PreviousNonEmpty].Blink; 02706 ListHead[PreviousNonEmpty].Blink->Flink = &MmWorkingSetExpansionHead.ListHead; 02707 02708 UNLOCK_EXPANSION (OldIrql); 02709 02710 return; 02711 } 02712 02713 02714 VOID 02715 MiEmptyAllWorkingSets ( 02716 VOID 02717 ) 02718 02719 /*++ 02720 02721 Routine Description: 02722 02723 This routine attempts to empty all the working sets on the 02724 expansion list. 02725 02726 Arguments: 02727 02728 None. 02729 02730 Return Value: 02731 02732 None. 02733 02734 Environment: 02735 02736 Kernel mode. No locks held. APC level or below. 02737 02738 --*/ 02739 02740 { 02741 KIRQL OldIrql; 02742 02743 PAGED_CODE (); 02744 02745 MmLockPagableSectionByHandle (ExPageLockHandle); 02746 02747 if (MiHydra == FALSE) { 02748 MiEmptyAllWorkingSetsWorker (); 02749 } 02750 else { 02751 ASSERT (PsGetCurrentThread () != MmWorkingSetThread); 02752 02753 // 02754 // For Hydra, we cannot attach directly to the session space to be 02755 // trimmed because it would result in session space references by 02756 // other threads in this process to the attached session instead 02757 // of the (currently) correct one. In fact, we cannot even queue 02758 // this to a worker thread because the working set manager 02759 // (who shares the same page directory) may be attaching or 02760 // detaching from a session (any session). So this must be queued 02761 // to the working set manager. 02762 // 02763 02764 LOCK_EXPANSION (OldIrql); 02765 02766 if (MiWaitingForWorkingSetEmpty == FALSE) { 02767 MiWaitingForWorkingSetEmpty = TRUE; 02768 KeClearEvent (&MiWaitForEmptyEvent); 02769 } 02770 02771 UNLOCK_EXPANSION (OldIrql); 02772 02773 KeSetEvent (&MmWorkingSetManagerEvent, 0, FALSE); 02774 02775 KeWaitForSingleObject (&MiWaitForEmptyEvent, 02776 WrVirtualMemory, 02777 KernelMode, 02778 FALSE, 02779 (PLARGE_INTEGER)0); 02780 } 02781 02782 MmUnlockPagableImageSection (ExPageLockHandle); 02783 02784 return; 02785 } 02786 02787 VOID 02788 MiEmptyAllWorkingSetsWorker ( 02789 VOID 02790 ) 02791 02792 /*++ 02793 02794 Routine Description: 02795 02796 This routine attempts to empty all the working sets on the expansion list. 02797 02798 Arguments: 02799 02800 None. 02801 02802 Return Value: 02803 02804 None. 02805 02806 Environment: 02807 02808 Kernel mode. No locks held. APC level or below. 02809 02810 --*/ 02811 02812 { 02813 PMMSUPPORT VmSupport; 02814 PMMSUPPORT FirstSeen; 02815 ULONG SystemCacheSeen; 02816 KIRQL OldIrql; 02817 PLIST_ENTRY ListEntry; 02818 PEPROCESS ProcessToTrim; 02819 PMM_SESSION_SPACE SessionSpace; 02820 ULONG LoopCount; 02821 02822 PAGED_CODE (); 02823 02824 FirstSeen = NULL; 02825 SystemCacheSeen = FALSE; 02826 LoopCount = 0; 02827 02828 LOCK_EXPANSION (OldIrql); 02829 02830 while (!IsListEmpty (&MmWorkingSetExpansionHead.ListHead)) { 02831 02832 // 02833 // Remove the entry at the head and trim it. 02834 // 02835 02836 ListEntry = RemoveHeadList (&MmWorkingSetExpansionHead.ListHead); 02837 02838 if (ListEntry == &MmSystemCacheWs.WorkingSetExpansionLinks) { 02839 VmSupport = &MmSystemCacheWs; 02840 ASSERT (VmSupport->u.Flags.SessionSpace == 0); 02841 ProcessToTrim = NULL; 02842 if (SystemCacheSeen != FALSE) { 02843 02844 // 02845 // Seen this one already. 02846 // 02847 02848 FirstSeen = VmSupport; 02849 } 02850 SystemCacheSeen = TRUE; 02851 } 02852 else { 02853 VmSupport = CONTAINING_RECORD(ListEntry, 02854 MMSUPPORT, 02855 WorkingSetExpansionLinks); 02856 02857 if (VmSupport->u.Flags.SessionSpace == 0) { 02858 ProcessToTrim = CONTAINING_RECORD(VmSupport, 02859 EPROCESS, 02860 Vm); 02861 02862 ASSERT (VmSupport->VmWorkingSetList == MmWorkingSetList); 02863 ASSERT (VmSupport == &ProcessToTrim->Vm); 02864 ASSERT (ProcessToTrim->AddressSpaceDeleted == 0); 02865 ASSERT (VmSupport->u.Flags.SessionSpace == 0); 02866 } 02867 else { 02868 ASSERT (MiHydra == TRUE); 02869 SessionSpace = CONTAINING_RECORD(VmSupport, 02870 MM_SESSION_SPACE, 02871 Vm); 02872 02873 ProcessToTrim = NULL; 02874 } 02875 } 02876 02877 if (VmSupport == FirstSeen) { 02878 InsertHeadList (&MmWorkingSetExpansionHead.ListHead, 02879 &VmSupport->WorkingSetExpansionLinks); 02880 break; 02881 } 02882 02883 ASSERT (VmSupport->u.Flags.BeingTrimmed == 0); 02884 VmSupport->u.Flags.BeingTrimmed = 1; 02885 02886 VmSupport->WorkingSetExpansionLinks.Flink = MM_NO_WS_EXPANSION; 02887 VmSupport->WorkingSetExpansionLinks.Blink = 02888 MM_WS_EXPANSION_IN_PROGRESS; 02889 UNLOCK_EXPANSION (OldIrql); 02890 02891 if (FirstSeen == NULL) { 02892 FirstSeen = VmSupport; 02893 } 02894 02895 // 02896 // Empty the working set. 02897 // 02898 02899 if (ProcessToTrim == NULL) { 02900 if (VmSupport->u.Flags.SessionSpace == 0) { 02901 MiEmptyWorkingSet (VmSupport, FALSE); 02902 } 02903 else { 02904 ASSERT (MiHydra == TRUE); 02905 MiAttachSession (SessionSpace); 02906 MiEmptyWorkingSet (VmSupport, FALSE); 02907 MiDetachSession (); 02908 } 02909 } 02910 else { 02911 02912 if (VmSupport->WorkingSetSize > 4) { 02913 KeAttachProcess (&ProcessToTrim->Pcb); 02914 MiEmptyWorkingSet (VmSupport, FALSE); 02915 KeDetachProcess (); 02916 } 02917 } 02918 02919 // 02920 // Add back to the list. 02921 // 02922 02923 LOCK_EXPANSION (OldIrql); 02924 ASSERT (VmSupport->WorkingSetExpansionLinks.Flink == MM_NO_WS_EXPANSION); 02925 ASSERT (VmSupport->u.Flags.BeingTrimmed == 1); 02926 VmSupport->u.Flags.BeingTrimmed = 0; 02927 02928 ASSERT (VmSupport->WorkingSetExpansionLinks.Flink == MM_NO_WS_EXPANSION); 02929 if (VmSupport->WorkingSetExpansionLinks.Blink == 02930 MM_WS_EXPANSION_IN_PROGRESS) { 02931 02932 // 02933 // If the working set size is still above the minimum, 02934 // add this back at the tail of the list. 02935 // 02936 02937 InsertTailList (&MmWorkingSetExpansionHead.ListHead, 02938 &VmSupport->WorkingSetExpansionLinks); 02939 } 02940 else { 02941 02942 // 02943 // The value in the blink is the address of an event 02944 // to set. 02945 // 02946 02947 ASSERT (VmSupport != &MmSystemCacheWs); 02948 02949 KeSetEvent ((PKEVENT)VmSupport->WorkingSetExpansionLinks.Blink, 02950 0, 02951 FALSE); 02952 } 02953 02954 // 02955 // The initial working set that was chosen for FirstSeen may have 02956 // been trimmed down under its minimum and been removed from the 02957 // ExpansionHead links. It is possible that the system cache is not 02958 // on the links either. This check detects this extremely rare 02959 // situation so that the system does not spin forever. 02960 // 02961 02962 LoopCount += 1; 02963 if (LoopCount > 200) { 02964 if (MmSystemCacheWs.WorkingSetExpansionLinks.Blink == MM_WS_EXPANSION_IN_PROGRESS) { 02965 break; 02966 } 02967 } 02968 } 02969 02970 UNLOCK_EXPANSION (OldIrql); 02971 02972 return; 02973 } 02974 02975 // 02976 // This is deliberately initialized to 1 and only cleared when we have 02977 // initialized enough of the system working set to support a trim. 02978 // 02979 02980 LONG MiTrimInProgressCount = 1; 02981 02982 ULONG MiTrimAllPageFaultCount; 02983 02984 02985 LOGICAL 02986 MmTrimAllSystemPagableMemory ( 02987 IN LOGICAL PurgeTransition 02988 ) 02989 02990 /*++ 02991 02992 Routine Description: 02993 02994 This routine unmaps all pagable system memory. This does not unmap user 02995 memory or locked down kernel memory. Thus, the memory being unmapped 02996 resides in paged pool, pagable kernel/driver code & data, special pool 02997 and the system cache. 02998 02999 Note that pages with a reference count greater than 1 are skipped (ie: 03000 they remain valid, as they are assumed to be locked down). This prevents 03001 us from unmapping all of the system cache entries, etc. 03002 03003 Non-locked down kernel stacks must be outpaged by modifying the balance 03004 set manager to operate in conjunction with a support routine. This is not 03005 done here. 03006 03007 Arguments: 03008 03009 PurgeTransition - Supplies whether to purge all the clean pages from the 03010 transition list. 03011 03012 Return Value: 03013 03014 TRUE if accomplished, FALSE if not. 03015 03016 Environment: 03017 03018 Kernel mode. APC_LEVEL or below. 03019 03020 --*/ 03021 03022 { 03023 KIRQL OldIrql; 03024 KIRQL OldIrql2; 03025 PLIST_ENTRY Next; 03026 PMMSUPPORT VmSupport; 03027 WSLE_NUMBER PagesInUse; 03028 PFN_NUMBER PageFrameIndex; 03029 LOGICAL LockAvailable; 03030 PMMPFN Pfn1; 03031 PETHREAD CurrentThread; 03032 ULONG flags; 03033 03034 // 03035 // It's ok to check this without acquiring the system WS lock. 03036 // 03037 03038 if (MiTrimAllPageFaultCount == MmSystemCacheWs.PageFaultCount) { 03039 return FALSE; 03040 } 03041 03042 // 03043 // Working set mutexes will be acquired which require APC_LEVEL or below. 03044 // 03045 03046 if (KeGetCurrentIrql() > APC_LEVEL) { 03047 return FALSE; 03048 } 03049 03050 // 03051 // Just return if it's too early during system initialization or if 03052 // another thread/processor is racing here to do the work for us. 03053 // 03054 03055 if (InterlockedIncrement (&MiTrimInProgressCount) > 1) { 03056 InterlockedDecrement (&MiTrimInProgressCount); 03057 return FALSE; 03058 } 03059 03060 #if defined(_X86_) 03061 03062 _asm { 03063 pushfd 03064 pop flags 03065 } 03066 03067 if ((flags & EFLAGS_INTERRUPT_MASK) == 0) { 03068 InterlockedDecrement (&MiTrimInProgressCount); 03069 return FALSE; 03070 } 03071 03072 #endif 03073 03074 LockAvailable = KeTryToAcquireSpinLock (&MmExpansionLock, &OldIrql); 03075 03076 if (LockAvailable == FALSE) { 03077 InterlockedDecrement (&MiTrimInProgressCount); 03078 return FALSE; 03079 } 03080 03081 MM_SET_EXPANSION_OWNER (); 03082 03083 CurrentThread = PsGetCurrentThread(); 03084 03085 // 03086 // If the system cache resource is owned by this thread then don't bother 03087 // trying to trim now. Note that checking the MmSystemLockOwner is not 03088 // sufficient as this flag is cleared just before actually releasing it. 03089 // 03090 03091 if ((CurrentThread == MmSystemLockOwner) || 03092 (ExTryToAcquireResourceExclusiveLite(&MmSystemWsLock) == FALSE)) { 03093 UNLOCK_EXPANSION (OldIrql); 03094 InterlockedDecrement (&MiTrimInProgressCount); 03095 return FALSE; 03096 } 03097 03098 Next = MmWorkingSetExpansionHead.ListHead.Flink; 03099 03100 while (Next != &MmWorkingSetExpansionHead.ListHead) { 03101 if (Next == &MmSystemCacheWs.WorkingSetExpansionLinks) { 03102 break; 03103 } 03104 Next = Next->Flink; 03105 } 03106 03107 if (Next != &MmSystemCacheWs.WorkingSetExpansionLinks) { 03108 ExReleaseResourceLite(&MmSystemWsLock); 03109 UNLOCK_EXPANSION (OldIrql); 03110 InterlockedDecrement (&MiTrimInProgressCount); 03111 return FALSE; 03112 } 03113 03114 RemoveEntryList (Next); 03115 03116 VmSupport = &MmSystemCacheWs; 03117 VmSupport->WorkingSetExpansionLinks.Flink = MM_NO_WS_EXPANSION; 03118 VmSupport->WorkingSetExpansionLinks.Blink = MM_WS_EXPANSION_IN_PROGRESS; 03119 ASSERT (VmSupport->u.Flags.BeingTrimmed == 0); 03120 VmSupport->u.Flags.BeingTrimmed = 1; 03121 03122 MiTrimAllPageFaultCount = VmSupport->PageFaultCount; 03123 03124 PagesInUse = VmSupport->WorkingSetSize; 03125 03126 // 03127 // There are 2 issues here that are carefully dealt with : 03128 // 03129 // 1. APCs must be disabled while any resources are held to prevent 03130 // suspend APCs from deadlocking the system. 03131 // 2. Once the system cache has been marked MM_WS_EXPANSION_IN_PROGRESS, 03132 // either the thread must not be preempted or the system cache working 03133 // set lock must be held throughout. Otherwise a high priority thread 03134 // can fault on a system code and data address and the two pages will 03135 // thrash forever (at high priority) because no system working set 03136 // expansion is allowed while MM_WS_EXPANSION_IN_PROGRESS is set. 03137 // The decision was to hold the system working set lock throughout. 03138 // 03139 03140 MmSystemLockOwner = PsGetCurrentThread (); 03141 03142 UNLOCK_EXPANSION (APC_LEVEL); 03143 03144 MiEmptyWorkingSet (VmSupport, FALSE); 03145 03146 LOCK_EXPANSION (OldIrql2); 03147 ASSERT (OldIrql2 == APC_LEVEL); 03148 03149 ASSERT (VmSupport->WorkingSetExpansionLinks.Flink == MM_NO_WS_EXPANSION); 03150 03151 ASSERT (VmSupport->u.Flags.BeingTrimmed == 1); 03152 VmSupport->u.Flags.BeingTrimmed = 0; 03153 03154 ASSERT (VmSupport->WorkingSetExpansionLinks.Blink == 03155 MM_WS_EXPANSION_IN_PROGRESS); 03156 03157 InsertTailList (&MmWorkingSetExpansionHead.ListHead, 03158 &VmSupport->WorkingSetExpansionLinks); 03159 03160 UNLOCK_EXPANSION (APC_LEVEL); 03161 03162 // 03163 // Since MiEmptyWorkingSet will attempt to recursively acquire and release 03164 // the MmSystemWsLock, the MmSystemLockOwner field may get cleared. 03165 // This means here the resource must be explicitly released instead of 03166 // using UNLOCK_SYSTEM_WS. 03167 // 03168 03169 MmSystemLockOwner = NULL; 03170 ExReleaseResourceLite (&MmSystemWsLock); 03171 KeLowerIrql (OldIrql); 03172 ASSERT (KeGetCurrentIrql() <= APC_LEVEL); 03173 03174 if (PurgeTransition == TRUE) { 03175 03176 // 03177 // Run the transition list and free all the entries so transition 03178 // faults are not satisfied for any of the non modified pages that were 03179 // freed. 03180 // 03181 03182 LOCK_PFN (OldIrql); 03183 03184 while (MmStandbyPageListHead.Total != 0) { 03185 03186 PageFrameIndex = MiRemovePageFromList (&MmStandbyPageListHead); 03187 03188 Pfn1 = MI_PFN_ELEMENT (PageFrameIndex); 03189 03190 ASSERT (Pfn1->u2.ShareCount == 0); 03191 ASSERT (Pfn1->u3.e2.ReferenceCount == 0); 03192 03193 Pfn1->u3.e2.ReferenceCount += 1; 03194 Pfn1->OriginalPte = ZeroPte; 03195 Pfn1->u3.e1.Modified = 0; 03196 MI_SET_PFN_DELETED (Pfn1); 03197 03198 MiDecrementReferenceCount (PageFrameIndex); 03199 } 03200 03201 UNLOCK_PFN (OldIrql); 03202 } 03203 03204 InterlockedDecrement (&MiTrimInProgressCount); 03205 03206 return TRUE; 03207 }

Generated on Sat May 15 19:42:29 2004 for test by doxygen 1.3.7