mirror of https://github.com/lianthony/NT4.0
1190 lines
37 KiB
1190 lines
37 KiB
/*++
|
|
|
|
Copyright (c) 1990 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
wsmanage.c
|
|
|
|
Abstract:
|
|
|
|
This module contains routines which manage the set of active working
|
|
set lists.
|
|
|
|
Working set management is accomplished by a parallel group of actions
|
|
1. Writing modified pages
|
|
2. Reducing (trimming) working sets which are above their maximum
|
|
towards their minimum.
|
|
|
|
The metrics are set such that writing modified pages is typically
|
|
accomplished before trimming working sets, however, under certain cases
|
|
where modified pages are being generated at a very high rate, working
|
|
set trimming will be initiated to free up more pages to modify.
|
|
|
|
When the first thread in a process is created, the memory management
|
|
system is notified that working set expansion is allowed. This
|
|
is noted by changing the FLINK field of the WorkingSetExpansionLink
|
|
entry in the process control block from MM_NO_WS_EXPANSION to
|
|
MM_ALLOW_WS_EXPANSION. As threads fault, the working set is eligible
|
|
for expansion if ample pages exist (MmAvailagePages is high enough).
|
|
|
|
Once a process has had its working set raised above the minimum
|
|
specified, the process is put on the Working Set Expanded list and
|
|
is now elgible for trimming. Note that at this time the FLINK field
|
|
in the WorkingSetExpansionLink has an address value.
|
|
|
|
When working set trimming is initiated, a process is removed from the
|
|
list (PFN mutex guards this list) and the FLINK field is set
|
|
to MM_NO_WS_EXPANSION, also, the BLINK field is set to
|
|
MM_WS_EXPANSION_IN_PROGRESS. The BLINK field value indicates to
|
|
the MmCleanUserAddressSpace function that working set trimming is
|
|
in progress for this process and it should wait until it completes.
|
|
This is accomplished by creating an event, putting the address of the
|
|
event in the BLINK field and then releasing the PFN mutex and
|
|
waiting on the event atomically. When working set trimming is
|
|
complete, the BLINK field is no longer MM_EXPANSION_IN_PROGRESS
|
|
indicating that the event should be set.
|
|
|
|
Author:
|
|
|
|
Lou Perazzoli (loup) 10-Apr-1990
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "mi.h"
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(PAGELK, MiEmptyAllWorkingSets)
|
|
#pragma alloc_text(INIT, MiAdjustWorkingSetManagerParameters)
|
|
#endif
|
|
|
|
//
|
|
// Minimum number of page faults to take to avoid being trimmed on
|
|
// an "ideal pass".
|
|
//
|
|
|
|
ULONG MiIdealPassFaultCountDisable;
|
|
|
|
extern ULONG PsMinimumWorkingSet;
|
|
|
|
extern PEPROCESS ExpDefaultErrorPortProcess;
|
|
|
|
//
|
|
// Number of times to wake up and do nothing before triming processes
|
|
// with no faulting activity.
|
|
//
|
|
|
|
#define MM_TRIM_COUNTER_MAXIMUM_SMALL_MEM (4)
|
|
#define MM_TRIM_COUNTER_MAXIMUM_LARGE_MEM (6)
|
|
|
|
ULONG MiTrimCounterMaximum = MM_TRIM_COUNTER_MAXIMUM_LARGE_MEM;
|
|
|
|
#define MM_REDUCE_FAULT_COUNT (10000)
|
|
|
|
#define MM_IGNORE_FAULT_COUNT (100)
|
|
|
|
#define MM_PERIODIC_AGRESSIVE_TRIM_COUNTER_MAXIMUM (30)
|
|
ULONG MmPeriodicAgressiveTrimMinFree = 1000;
|
|
ULONG MmPeriodicAgressiveCacheWsMin = 1250;
|
|
ULONG MmPeriodicAgressiveTrimMaxFree = 2000;
|
|
ULONG MiPeriodicAgressiveTrimCheckCounter;
|
|
BOOLEAN MiDoPeriodicAgressiveTrimming = FALSE;
|
|
|
|
ULONG MiCheckCounter;
|
|
|
|
ULONG MmMoreThanEnoughFreePages = 1000;
|
|
|
|
ULONG MmAmpleFreePages = 200;
|
|
|
|
ULONG MmWorkingSetReductionMin = 12;
|
|
ULONG MmWorkingSetReductionMinCacheWs = 12;
|
|
|
|
ULONG MmWorkingSetReductionMax = 60;
|
|
ULONG MmWorkingSetReductionMaxCacheWs = 60;
|
|
|
|
ULONG MmWorkingSetReductionHuge = (512*1024) >> PAGE_SHIFT;
|
|
|
|
ULONG MmWorkingSetVolReductionMin = 12;
|
|
|
|
ULONG MmWorkingSetVolReductionMax = 60;
|
|
ULONG MmWorkingSetVolReductionMaxCacheWs = 60;
|
|
|
|
ULONG MmWorkingSetVolReductionHuge = (2*1024*1024) >> PAGE_SHIFT;
|
|
|
|
ULONG MmWorkingSetSwapReduction = 75;
|
|
|
|
ULONG MmWorkingSetSwapReductionHuge = (4*1024*1024) >> PAGE_SHIFT;
|
|
|
|
ULONG MmForegroundSwitchCount;
|
|
|
|
ULONG MmNumberOfForegroundProcesses;
|
|
|
|
ULONG MmLastFaultCount;
|
|
|
|
extern PVOID MmPagableKernelStart;
|
|
extern PVOID MmPagableKernelEnd;
|
|
|
|
VOID
|
|
MiAdjustWorkingSetManagerParameters(
|
|
BOOLEAN WorkStation
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function is called from MmInitSystem to adjust the working set manager
|
|
trim algorithms based on system type and size.
|
|
|
|
Arguments:
|
|
|
|
WorkStation - TRUE if this is a workstation
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Kernel mode
|
|
|
|
--*/
|
|
{
|
|
if ( WorkStation && (MmNumberOfPhysicalPages <= ((31*1024*1024)/PAGE_SIZE)) ) {
|
|
|
|
//
|
|
// periodic agressive trimming of marked processes (and the system cache)
|
|
// is done on 31mb and below workstations. The goal is to keep lots of free
|
|
// memory available and tu build better internal working sets for the
|
|
// marked processes
|
|
//
|
|
|
|
MiDoPeriodicAgressiveTrimming = TRUE;
|
|
|
|
//
|
|
// To get fault protection, you have to take 45 faults instead of
|
|
// the old 15 fault protection threshold
|
|
//
|
|
|
|
MiIdealPassFaultCountDisable = 45;
|
|
|
|
|
|
//
|
|
// Take more away when you are over your working set in both
|
|
// forced and voluntary mode, but leave cache WS trim amounts
|
|
// alone
|
|
//
|
|
|
|
MmWorkingSetVolReductionMax = 100;
|
|
MmWorkingSetReductionMax = 100;
|
|
|
|
//
|
|
// In forced mode, wven if you are within your working set, take
|
|
// memory away more agressively
|
|
//
|
|
|
|
MmWorkingSetReductionMin = 40;
|
|
|
|
MmPeriodicAgressiveCacheWsMin = 1000;
|
|
|
|
|
|
if (MmNumberOfPhysicalPages >= ((15*1024*1024)/PAGE_SIZE) ) {
|
|
MmPeriodicAgressiveCacheWsMin = 1100;
|
|
}
|
|
|
|
//
|
|
// For Larger Machines 19 - 31Mb, Keep the trim counter max
|
|
// set to 6 passes. Smaller machines < 19 are set up for an
|
|
// iteration count of 4. This will result in more frequent voluntary
|
|
// trimming
|
|
//
|
|
|
|
if (MmNumberOfPhysicalPages >= ((19*1024*1024)/PAGE_SIZE) ) {
|
|
MmPeriodicAgressiveCacheWsMin = 1250;
|
|
}
|
|
|
|
|
|
if (MmNumberOfPhysicalPages >= ((23*1024*1024)/PAGE_SIZE) ) {
|
|
MmPeriodicAgressiveCacheWsMin = 1500;
|
|
}
|
|
} else {
|
|
MiIdealPassFaultCountDisable = 15;
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
MiObtainFreePages (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function examines the size of the modified list and the
|
|
total number of pages in use because of working set increments
|
|
and obtains pages by writing modified pages and/or reducing
|
|
working sets.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, APC's disabled, working set and pfn mutexes held.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
//
|
|
// Check to see if their are enough modified pages to institute a
|
|
// write.
|
|
//
|
|
|
|
if ((MmModifiedPageListHead.Total >= MmModifiedWriteClusterSize) ||
|
|
(MmModNoWriteInsert)) {
|
|
|
|
//
|
|
// Start the modified page writer.
|
|
//
|
|
|
|
KeSetEvent (&MmModifiedPageWriterEvent, 0, FALSE);
|
|
}
|
|
|
|
//
|
|
// See if there are enough working sets above the minimum
|
|
// threshold to make working set trimming worthwhile.
|
|
//
|
|
|
|
if ((MmPagesAboveWsMinimum > MmPagesAboveWsThreshold) ||
|
|
(MmAvailablePages < 5)) {
|
|
|
|
//
|
|
// Start the working set manager to reduce working sets.
|
|
//
|
|
|
|
KeSetEvent (&MmWorkingSetManagerEvent, 0, FALSE);
|
|
}
|
|
}
|
|
|
|
VOID
|
|
MmWorkingSetManager (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Implements the NT working set manager thread. When the number
|
|
of free pages becomes critical and ample pages can be obtained by
|
|
reducing working sets, the working set manager's event is set, and
|
|
this thread becomes active.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Kernel mode.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
PEPROCESS CurrentProcess;
|
|
PEPROCESS ProcessToTrim;
|
|
PLIST_ENTRY ListEntry;
|
|
BOOLEAN Attached = FALSE;
|
|
ULONG MaxTrim;
|
|
ULONG Trim;
|
|
ULONG TotalReduction;
|
|
KIRQL OldIrql;
|
|
PMMSUPPORT VmSupport;
|
|
PMMWSL WorkingSetList;
|
|
LARGE_INTEGER CurrentTime;
|
|
ULONG DesiredFreeGoal;
|
|
ULONG DesiredReductionGoal;
|
|
ULONG FaultCount;
|
|
ULONG i;
|
|
ULONG NumberOfForegroundProcesses;
|
|
BOOLEAN OneSwitchedAlready;
|
|
BOOLEAN Responsive;
|
|
ULONG NumPasses;
|
|
ULONG count;
|
|
ULONG Available;
|
|
ULONG PageFaultCount;
|
|
BOOLEAN OnlyDoAgressiveTrim = FALSE;
|
|
|
|
#if DBG
|
|
ULONG LastTrimFaultCount;
|
|
#endif // DBG
|
|
CurrentProcess = PsGetCurrentProcess ();
|
|
|
|
//
|
|
// Check the number of pages available to see if any trimming
|
|
// is really required.
|
|
//
|
|
|
|
LOCK_PFN (OldIrql);
|
|
Available = MmAvailablePages;
|
|
PageFaultCount = MmInfoCounters.PageFaultCount;
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
if ((Available > MmMoreThanEnoughFreePages) &&
|
|
((PageFaultCount - MmLastFaultCount) <
|
|
MM_REDUCE_FAULT_COUNT)) {
|
|
|
|
//
|
|
// Don't trim and zero the check counter.
|
|
//
|
|
|
|
MiCheckCounter = 0;
|
|
|
|
|
|
if ( MiDoPeriodicAgressiveTrimming ) {
|
|
|
|
//
|
|
// Not that simple. We have "more than enough" memory, and have taken
|
|
// very few faults.
|
|
//
|
|
// Now see if we are in the grey area between 4 and 8mb free and have
|
|
// been there for a bit. If so, then trim all marked processes down
|
|
// to their minimum. The effect here is that whenever it looks like
|
|
// we are going idle, we want to steal memory from the hard marked
|
|
// processes like the shell, csrss, ntvdm...
|
|
//
|
|
|
|
if ( (Available > MmPeriodicAgressiveTrimMinFree) &&
|
|
(Available <= MmPeriodicAgressiveTrimMaxFree) ) {
|
|
|
|
MiPeriodicAgressiveTrimCheckCounter++;
|
|
if ( MiPeriodicAgressiveTrimCheckCounter > MM_PERIODIC_AGRESSIVE_TRIM_COUNTER_MAXIMUM ) {
|
|
MiPeriodicAgressiveTrimCheckCounter = 0;
|
|
OnlyDoAgressiveTrim = TRUE;
|
|
goto StartTrimming;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
} else if ((Available > MmAmpleFreePages) &&
|
|
((PageFaultCount - MmLastFaultCount) <
|
|
MM_IGNORE_FAULT_COUNT)) {
|
|
|
|
//
|
|
// Don't do anything.
|
|
//
|
|
|
|
NOTHING;
|
|
|
|
} else if ((Available > MmFreeGoal) &&
|
|
(MiCheckCounter < MiTrimCounterMaximum)) {
|
|
|
|
//
|
|
// Don't trim, but increment the check counter.
|
|
//
|
|
|
|
MiCheckCounter += 1;
|
|
|
|
} else {
|
|
|
|
StartTrimming:
|
|
|
|
TotalReduction = 0;
|
|
|
|
//
|
|
// Set the total reduction goals.
|
|
//
|
|
|
|
DesiredReductionGoal = MmPagesAboveWsMinimum >> 2;
|
|
if (MmPagesAboveWsMinimum > (MmFreeGoal << 1)) {
|
|
DesiredFreeGoal = MmFreeGoal;
|
|
} else {
|
|
DesiredFreeGoal = MmMinimumFreePages + 10;
|
|
}
|
|
|
|
//
|
|
// Calculate the number of faults to be taken to not be trimmed.
|
|
//
|
|
|
|
if (Available > MmMoreThanEnoughFreePages) {
|
|
FaultCount = 1;
|
|
} else {
|
|
FaultCount = MiIdealPassFaultCountDisable;
|
|
}
|
|
|
|
#if DBG
|
|
if (MmDebug & MM_DBG_WS_EXPANSION) {
|
|
if ( OnlyDoAgressiveTrim ) {
|
|
DbgPrint("\nMM-wsmanage: Only Doing Agressive Trim Available Mem %d\n",Available);
|
|
} else {
|
|
DbgPrint("\nMM-wsmanage: checkcounter = %ld, Desired = %ld, Free = %ld Avail %ld\n",
|
|
MiCheckCounter, DesiredReductionGoal, DesiredFreeGoal, Available);
|
|
}
|
|
}
|
|
#endif //DBG
|
|
|
|
KeQuerySystemTime (&CurrentTime);
|
|
MmLastFaultCount = PageFaultCount;
|
|
|
|
NumPasses = 0;
|
|
OneSwitchedAlready = FALSE;
|
|
NumberOfForegroundProcesses = 0;
|
|
|
|
LOCK_EXPANSION (OldIrql);
|
|
while (!IsListEmpty (&MmWorkingSetExpansionHead.ListHead)) {
|
|
|
|
//
|
|
// Remove the entry at the head and trim it.
|
|
//
|
|
|
|
ListEntry = RemoveHeadList (&MmWorkingSetExpansionHead.ListHead);
|
|
if (ListEntry != &MmSystemCacheWs.WorkingSetExpansionLinks) {
|
|
ProcessToTrim = CONTAINING_RECORD(ListEntry,
|
|
EPROCESS,
|
|
Vm.WorkingSetExpansionLinks);
|
|
|
|
VmSupport = &ProcessToTrim->Vm;
|
|
ASSERT (ProcessToTrim->AddressSpaceDeleted == 0);
|
|
} else {
|
|
VmSupport = &MmSystemCacheWs;
|
|
}
|
|
|
|
//
|
|
// Check to see if we've been here before.
|
|
//
|
|
|
|
if ((*(PLARGE_INTEGER)&VmSupport->LastTrimTime).QuadPart ==
|
|
(*(PLARGE_INTEGER)&CurrentTime).QuadPart) {
|
|
|
|
InsertHeadList (&MmWorkingSetExpansionHead.ListHead,
|
|
&VmSupport->WorkingSetExpansionLinks);
|
|
|
|
//
|
|
// If we are only doing agressive trimming then
|
|
// skip out once we have visited everone.
|
|
//
|
|
|
|
if ( OnlyDoAgressiveTrim ) {
|
|
break;
|
|
}
|
|
|
|
|
|
if (MmAvailablePages > MmMinimumFreePages) {
|
|
|
|
|
|
//
|
|
// Every process has been examined and ample pages
|
|
// now exist, place this process back on the list
|
|
// and break out of the loop.
|
|
//
|
|
|
|
MmNumberOfForegroundProcesses = NumberOfForegroundProcesses;
|
|
|
|
break;
|
|
} else {
|
|
|
|
//
|
|
// Wait 10 milliseconds for the modified page writer
|
|
// to catch up.
|
|
//
|
|
|
|
UNLOCK_EXPANSION (OldIrql);
|
|
KeDelayExecutionThread (KernelMode,
|
|
FALSE,
|
|
&MmShortTime);
|
|
|
|
if (MmAvailablePages < MmMinimumFreePages) {
|
|
|
|
//
|
|
// Change this to a forced trim, so we get pages
|
|
// available, and reset the current time.
|
|
//
|
|
|
|
MiPeriodicAgressiveTrimCheckCounter = 0;
|
|
MiCheckCounter = 0;
|
|
KeQuerySystemTime (&CurrentTime);
|
|
|
|
NumPasses += 1;
|
|
}
|
|
LOCK_EXPANSION (OldIrql);
|
|
|
|
//
|
|
// Get another process.
|
|
//
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (VmSupport != &MmSystemCacheWs) {
|
|
|
|
//
|
|
// If we are only doing agressive trimming,
|
|
// then only consider hard marked processes
|
|
//
|
|
|
|
if ( OnlyDoAgressiveTrim ) {
|
|
if ( (ProcessToTrim->MmAgressiveWsTrimMask & PS_WS_TRIM_FROM_EXE_HEADER) &&
|
|
VmSupport->WorkingSetSize > 5 ) {
|
|
goto ProcessSelected;
|
|
} else {
|
|
|
|
//
|
|
// Process is not marked, so skip it
|
|
//
|
|
|
|
InsertTailList (&MmWorkingSetExpansionHead.ListHead,
|
|
&VmSupport->WorkingSetExpansionLinks);
|
|
continue;
|
|
}
|
|
}
|
|
//
|
|
// Check to see if this is a forced trim or
|
|
// if we are trimming because check counter is
|
|
// at the maximum?
|
|
//
|
|
|
|
if ((ProcessToTrim->Vm.MemoryPriority == MEMORY_PRIORITY_FOREGROUND) && !NumPasses) {
|
|
|
|
NumberOfForegroundProcesses += 1;
|
|
}
|
|
|
|
if (MiCheckCounter >= MiTrimCounterMaximum) {
|
|
|
|
//
|
|
// Don't trim if less than 5 seconds has elapsed since
|
|
// it was last trimmed or the page fault count is
|
|
// too high.
|
|
//
|
|
|
|
if (((VmSupport->PageFaultCount -
|
|
VmSupport->LastTrimFaultCount) >
|
|
FaultCount)
|
|
||
|
|
(VmSupport->WorkingSetSize <= 5)
|
|
|
|
||
|
|
(((*(PLARGE_INTEGER)&CurrentTime).QuadPart -
|
|
(*(PLARGE_INTEGER)&VmSupport->LastTrimTime).QuadPart) <
|
|
(*(PLARGE_INTEGER)&MmWorkingSetProtectionTime).QuadPart)) {
|
|
|
|
#if DBG
|
|
if (MmDebug & MM_DBG_WS_EXPANSION) {
|
|
if ( VmSupport->WorkingSetSize > 5 ) {
|
|
DbgPrint(" ***** Skipping %s Process %16s %5d Faults, WS %6d\n",
|
|
ProcessToTrim->MmAgressiveWsTrimMask ? "Marked" : "Normal",
|
|
ProcessToTrim->ImageFileName,
|
|
VmSupport->PageFaultCount - VmSupport->LastTrimFaultCount,
|
|
VmSupport->WorkingSetSize
|
|
);
|
|
}
|
|
}
|
|
#endif //DBG
|
|
|
|
|
|
//
|
|
// Don't trim this one at this time. Set the trim
|
|
// time to the current time and set the page fault
|
|
// count to the process's current page fault count.
|
|
//
|
|
|
|
VmSupport->LastTrimTime = CurrentTime;
|
|
VmSupport->LastTrimFaultCount =
|
|
VmSupport->PageFaultCount;
|
|
|
|
InsertTailList (&MmWorkingSetExpansionHead.ListHead,
|
|
&VmSupport->WorkingSetExpansionLinks);
|
|
continue;
|
|
}
|
|
} else {
|
|
|
|
//
|
|
// This is a forced trim. If this process is at
|
|
// or below it's minimum, don't trim it unless stacks
|
|
// are swapped out or it's paging a bit.
|
|
//
|
|
|
|
if (VmSupport->WorkingSetSize <=
|
|
VmSupport->MinimumWorkingSetSize) {
|
|
if (((MmAvailablePages + 5) >= MmFreeGoal) &&
|
|
(((VmSupport->LastTrimFaultCount !=
|
|
VmSupport->PageFaultCount) ||
|
|
(!ProcessToTrim->ProcessOutswapEnabled)))) {
|
|
|
|
//
|
|
// This process has taken page faults since the
|
|
// last trim time. Change the time base and
|
|
// the fault count. And don't trim as it is
|
|
// at or below the maximum.
|
|
//
|
|
|
|
VmSupport->LastTrimTime = CurrentTime;
|
|
VmSupport->LastTrimFaultCount =
|
|
VmSupport->PageFaultCount;
|
|
InsertTailList (&MmWorkingSetExpansionHead.ListHead,
|
|
&VmSupport->WorkingSetExpansionLinks);
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// If the working set is greater than 5 pages and
|
|
// the last fault occurred more than 5 seconds ago,
|
|
// trim.
|
|
//
|
|
|
|
if ((VmSupport->WorkingSetSize < 5)
|
|
||
|
|
(((*(PLARGE_INTEGER)&CurrentTime).QuadPart -
|
|
(*(PLARGE_INTEGER)&VmSupport->LastTrimTime).QuadPart) <
|
|
(*(PLARGE_INTEGER)&MmWorkingSetProtectionTime).QuadPart)) {
|
|
InsertTailList (&MmWorkingSetExpansionHead.ListHead,
|
|
&VmSupport->WorkingSetExpansionLinks);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Fix to supply foreground responsiveness by not trimming
|
|
// foreground priority applications as aggressively.
|
|
//
|
|
|
|
Responsive = FALSE;
|
|
|
|
if ( VmSupport->MemoryPriority == MEMORY_PRIORITY_FOREGROUND ) {
|
|
|
|
VmSupport->ForegroundSwitchCount =
|
|
(UCHAR)MmForegroundSwitchCount;
|
|
}
|
|
|
|
VmSupport->ForegroundSwitchCount = (UCHAR) MmForegroundSwitchCount;
|
|
|
|
if ((MmNumberOfForegroundProcesses <= 3) &&
|
|
(NumberOfForegroundProcesses <= 3) &&
|
|
(VmSupport->MemoryPriority)) {
|
|
|
|
if ((MmAvailablePages > (MmMoreThanEnoughFreePages >> 2)) ||
|
|
(VmSupport->MemoryPriority >= MEMORY_PRIORITY_FOREGROUND)) {
|
|
|
|
//
|
|
// Indicate that memory responsiveness to the foreground
|
|
// process is important (not so for large console trees).
|
|
//
|
|
|
|
Responsive = TRUE;
|
|
}
|
|
}
|
|
|
|
if (Responsive && !NumPasses) {
|
|
|
|
//
|
|
// Note that NumPasses yeilds a measurement of how
|
|
// desperate we are for memory, if numpasses is not
|
|
// zero, we are in trouble.
|
|
//
|
|
|
|
InsertTailList (&MmWorkingSetExpansionHead.ListHead,
|
|
&VmSupport->WorkingSetExpansionLinks);
|
|
continue;
|
|
}
|
|
ProcessSelected:
|
|
VmSupport->LastTrimTime = CurrentTime;
|
|
VmSupport->WorkingSetExpansionLinks.Flink = MM_NO_WS_EXPANSION;
|
|
VmSupport->WorkingSetExpansionLinks.Blink =
|
|
MM_WS_EXPANSION_IN_PROGRESS;
|
|
UNLOCK_EXPANSION (OldIrql);
|
|
WorkingSetList = MmWorkingSetList;
|
|
|
|
//
|
|
// Attach to the process and trim away.
|
|
//
|
|
|
|
if (ProcessToTrim != CurrentProcess) {
|
|
if (KeTryToAttachProcess (&ProcessToTrim->Pcb) == FALSE) {
|
|
|
|
//
|
|
// The process is not in the proper state for
|
|
// attachment, go to the next one.
|
|
//
|
|
|
|
LOCK_EXPANSION (OldIrql);
|
|
|
|
//
|
|
// Indicate attach failed.
|
|
//
|
|
|
|
VmSupport->AllowWorkingSetAdjustment = MM_FORCE_TRIM;
|
|
goto WorkingSetLockFailed;
|
|
}
|
|
|
|
//
|
|
// Indicate that we are attached.
|
|
//
|
|
|
|
Attached = TRUE;
|
|
}
|
|
|
|
//
|
|
// Attempt to acquire the working set lock, if the
|
|
// lock cannot be acquired, skip over this process.
|
|
//
|
|
|
|
count = 0;
|
|
do {
|
|
if (ExTryToAcquireFastMutex(&ProcessToTrim->WorkingSetLock) != FALSE) {
|
|
break;
|
|
}
|
|
KeDelayExecutionThread (KernelMode, FALSE, &MmShortTime);
|
|
count += 1;
|
|
if (count == 5) {
|
|
|
|
//
|
|
// Could not get the lock, skip this process.
|
|
//
|
|
|
|
if (Attached) {
|
|
KeDetachProcess ();
|
|
Attached = FALSE;
|
|
}
|
|
|
|
LOCK_EXPANSION (OldIrql);
|
|
VmSupport->AllowWorkingSetAdjustment = MM_FORCE_TRIM;
|
|
goto WorkingSetLockFailed;
|
|
}
|
|
} while (TRUE);
|
|
|
|
#if DBG
|
|
LastTrimFaultCount = VmSupport->LastTrimFaultCount;
|
|
#endif // DBG
|
|
VmSupport->LastTrimFaultCount = VmSupport->PageFaultCount;
|
|
|
|
} else {
|
|
|
|
//
|
|
// System cache, don't trim the system cache if this
|
|
// is a voluntary trim and the working set is within
|
|
// a 100 pages of the minimum, or if the system cache
|
|
// is at its minimum.
|
|
//
|
|
|
|
#if DBG
|
|
LastTrimFaultCount = VmSupport->LastTrimFaultCount;
|
|
#endif // DBG
|
|
VmSupport->LastTrimTime = CurrentTime;
|
|
VmSupport->LastTrimFaultCount = VmSupport->PageFaultCount;
|
|
|
|
//
|
|
// Always skip the cache if all we are doing is agressive trimming
|
|
//
|
|
|
|
if ((MiCheckCounter >= MiTrimCounterMaximum) &&
|
|
(((LONG)VmSupport->WorkingSetSize -
|
|
(LONG)VmSupport->MinimumWorkingSetSize) < 100) ){
|
|
|
|
//
|
|
// Don't trim the system cache.
|
|
//
|
|
|
|
InsertTailList (&MmWorkingSetExpansionHead.ListHead,
|
|
&VmSupport->WorkingSetExpansionLinks);
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Indicate that this process is being trimmed.
|
|
//
|
|
|
|
UNLOCK_EXPANSION (OldIrql);
|
|
|
|
ProcessToTrim = NULL;
|
|
WorkingSetList = MmSystemCacheWorkingSetList;
|
|
count = 0;
|
|
|
|
KeRaiseIrql (APC_LEVEL, &OldIrql);
|
|
if (!ExTryToAcquireResourceExclusiveLite (&MmSystemWsLock)) {
|
|
|
|
//
|
|
// System working set lock was not granted, don't trim
|
|
// the system cache.
|
|
//
|
|
|
|
KeLowerIrql (OldIrql);
|
|
LOCK_EXPANSION (OldIrql);
|
|
InsertTailList (&MmWorkingSetExpansionHead.ListHead,
|
|
&VmSupport->WorkingSetExpansionLinks);
|
|
continue;
|
|
}
|
|
|
|
MmSystemLockOwner = PsGetCurrentThread();
|
|
VmSupport->WorkingSetExpansionLinks.Flink = MM_NO_WS_EXPANSION;
|
|
VmSupport->WorkingSetExpansionLinks.Blink =
|
|
MM_WS_EXPANSION_IN_PROGRESS;
|
|
}
|
|
|
|
if ((VmSupport->WorkingSetSize <= VmSupport->MinimumWorkingSetSize) &&
|
|
((ProcessToTrim != NULL) &&
|
|
(ProcessToTrim->ProcessOutswapEnabled))) {
|
|
|
|
//
|
|
// Set the quota to the minimum and reduce the working
|
|
// set size.
|
|
//
|
|
|
|
WorkingSetList->Quota = VmSupport->MinimumWorkingSetSize;
|
|
Trim = VmSupport->WorkingSetSize - WorkingSetList->FirstDynamic;
|
|
if (Trim > MmWorkingSetSwapReduction) {
|
|
Trim = MmWorkingSetSwapReduction;
|
|
}
|
|
|
|
ASSERT ((LONG)Trim >= 0);
|
|
|
|
} else {
|
|
|
|
MaxTrim = VmSupport->WorkingSetSize -
|
|
VmSupport->MinimumWorkingSetSize;
|
|
if ((ProcessToTrim != NULL) &&
|
|
(ProcessToTrim->ProcessOutswapEnabled)) {
|
|
|
|
//
|
|
// All thread stacks have been swapped out.
|
|
//
|
|
|
|
Trim = MmWorkingSetSwapReduction;
|
|
i = VmSupport->WorkingSetSize - VmSupport->MaximumWorkingSetSize;
|
|
if ((LONG)i > 0) {
|
|
Trim = i;
|
|
if (Trim > MmWorkingSetSwapReductionHuge) {
|
|
Trim = MmWorkingSetSwapReductionHuge;
|
|
}
|
|
}
|
|
|
|
} else if ( OnlyDoAgressiveTrim ) {
|
|
|
|
//
|
|
// If we are in agressive mode,
|
|
// only trim the cache if it's WS exceeds 4.3mb and then
|
|
// just bring it down to 4.3mb
|
|
//
|
|
|
|
if (VmSupport != &MmSystemCacheWs) {
|
|
Trim = MaxTrim;
|
|
} else {
|
|
if ( VmSupport->WorkingSetSize > MmPeriodicAgressiveCacheWsMin ) {
|
|
Trim = VmSupport->WorkingSetSize - MmPeriodicAgressiveCacheWsMin;
|
|
} else {
|
|
Trim = 0;
|
|
}
|
|
}
|
|
|
|
} else if (MiCheckCounter >= MiTrimCounterMaximum) {
|
|
|
|
//
|
|
// Haven't faulted much, reduce a bit.
|
|
//
|
|
|
|
if (VmSupport->WorkingSetSize >
|
|
(VmSupport->MaximumWorkingSetSize +
|
|
(6 * MmWorkingSetVolReductionHuge))) {
|
|
Trim = MmWorkingSetVolReductionHuge;
|
|
|
|
} else if ( (VmSupport != &MmSystemCacheWs) &&
|
|
VmSupport->WorkingSetSize >
|
|
( VmSupport->MaximumWorkingSetSize + (2 * MmWorkingSetReductionHuge))) {
|
|
Trim = MmWorkingSetReductionHuge;
|
|
} else if (VmSupport->WorkingSetSize > VmSupport->MaximumWorkingSetSize) {
|
|
if (VmSupport != &MmSystemCacheWs) {
|
|
Trim = MmWorkingSetVolReductionMax;
|
|
} else {
|
|
Trim = MmWorkingSetVolReductionMaxCacheWs;
|
|
}
|
|
} else {
|
|
Trim = MmWorkingSetVolReductionMin;
|
|
}
|
|
|
|
if ( ProcessToTrim && ProcessToTrim->MmAgressiveWsTrimMask ) {
|
|
Trim = MaxTrim;
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
if (VmSupport->WorkingSetSize >
|
|
(VmSupport->MaximumWorkingSetSize +
|
|
(2 * MmWorkingSetReductionHuge))) {
|
|
Trim = MmWorkingSetReductionHuge;
|
|
|
|
} else if (VmSupport->WorkingSetSize > VmSupport->MaximumWorkingSetSize) {
|
|
if (VmSupport != &MmSystemCacheWs) {
|
|
Trim = MmWorkingSetReductionMax;
|
|
} else {
|
|
Trim = MmWorkingSetReductionMaxCacheWs;
|
|
}
|
|
} else {
|
|
if (VmSupport != &MmSystemCacheWs) {
|
|
Trim = MmWorkingSetReductionMin;
|
|
} else {
|
|
Trim = MmWorkingSetReductionMinCacheWs;
|
|
}
|
|
}
|
|
|
|
if ( ProcessToTrim && ProcessToTrim->MmAgressiveWsTrimMask && VmSupport->MemoryPriority < MEMORY_PRIORITY_FOREGROUND) {
|
|
Trim = MaxTrim;
|
|
}
|
|
|
|
}
|
|
|
|
if (MaxTrim < Trim) {
|
|
Trim = MaxTrim;
|
|
}
|
|
}
|
|
|
|
#if DBG
|
|
if ( MmDebug & MM_DBG_WS_EXPANSION) {
|
|
if ( Trim ) {
|
|
DbgPrint(" Trimming Process %16s %5d Faults, WS %6d, Trimming %5d ==> %5d\n",
|
|
ProcessToTrim ? ProcessToTrim->ImageFileName : "System Cache",
|
|
VmSupport->PageFaultCount - LastTrimFaultCount,
|
|
VmSupport->WorkingSetSize,
|
|
Trim,
|
|
VmSupport->WorkingSetSize-Trim
|
|
);
|
|
}
|
|
}
|
|
#endif //DBG
|
|
|
|
if (Trim != 0) {
|
|
Trim = MiTrimWorkingSet (
|
|
Trim,
|
|
VmSupport,
|
|
OnlyDoAgressiveTrim ? OnlyDoAgressiveTrim : ((BOOLEAN)(MiCheckCounter < MiTrimCounterMaximum))
|
|
);
|
|
}
|
|
|
|
//
|
|
// Set the quota to the current size.
|
|
//
|
|
|
|
WorkingSetList->Quota = VmSupport->WorkingSetSize;
|
|
if (WorkingSetList->Quota < VmSupport->MinimumWorkingSetSize) {
|
|
WorkingSetList->Quota = VmSupport->MinimumWorkingSetSize;
|
|
}
|
|
|
|
|
|
if (VmSupport != &MmSystemCacheWs) {
|
|
UNLOCK_WS (ProcessToTrim);
|
|
if (Attached) {
|
|
KeDetachProcess ();
|
|
Attached = FALSE;
|
|
}
|
|
|
|
} else {
|
|
UNLOCK_SYSTEM_WS (OldIrql);
|
|
}
|
|
|
|
TotalReduction += Trim;
|
|
|
|
LOCK_EXPANSION (OldIrql);
|
|
|
|
WorkingSetLockFailed:
|
|
|
|
ASSERT (VmSupport->WorkingSetExpansionLinks.Flink == MM_NO_WS_EXPANSION);
|
|
if (VmSupport->WorkingSetExpansionLinks.Blink ==
|
|
MM_WS_EXPANSION_IN_PROGRESS) {
|
|
|
|
//
|
|
// If the working set size is still above minimum
|
|
// add this back at the tail of the list.
|
|
//
|
|
|
|
InsertTailList (&MmWorkingSetExpansionHead.ListHead,
|
|
&VmSupport->WorkingSetExpansionLinks);
|
|
} else {
|
|
|
|
//
|
|
// The value in the blink is the address of an event
|
|
// to set.
|
|
//
|
|
|
|
KeSetEvent ((PKEVENT)VmSupport->WorkingSetExpansionLinks.Blink,
|
|
0,
|
|
FALSE);
|
|
}
|
|
|
|
if ( !OnlyDoAgressiveTrim ) {
|
|
if (MiCheckCounter < MiTrimCounterMaximum) {
|
|
if ((MmAvailablePages > DesiredFreeGoal) ||
|
|
(TotalReduction > DesiredReductionGoal)) {
|
|
|
|
//
|
|
// Ample pages now exist.
|
|
//
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
MiPeriodicAgressiveTrimCheckCounter = 0;
|
|
MiCheckCounter = 0;
|
|
UNLOCK_EXPANSION (OldIrql);
|
|
}
|
|
|
|
//
|
|
// Signal the modified page writer as we have moved pages
|
|
// to the modified list and memory was critical.
|
|
//
|
|
|
|
if ((MmAvailablePages < MmMinimumFreePages) ||
|
|
(MmModifiedPageListHead.Total >= MmModifiedPageMaximum)) {
|
|
KeSetEvent (&MmModifiedPageWriterEvent, 0, FALSE);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
VOID
|
|
MiEmptyAllWorkingSets (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine attempts to empty all the working stes on the
|
|
expansion list.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Kernel mode. No locks held. Apc level or less.
|
|
|
|
--*/
|
|
|
|
{
|
|
PMMSUPPORT VmSupport;
|
|
PMMSUPPORT FirstSeen = NULL;
|
|
ULONG SystemCacheSeen = FALSE;
|
|
KIRQL OldIrql;
|
|
PLIST_ENTRY ListEntry;
|
|
PEPROCESS ProcessToTrim;
|
|
|
|
MmLockPagableSectionByHandle (ExPageLockHandle);
|
|
LOCK_EXPANSION (OldIrql);
|
|
|
|
while (!IsListEmpty (&MmWorkingSetExpansionHead.ListHead)) {
|
|
|
|
//
|
|
// Remove the entry at the head and trim it.
|
|
//
|
|
|
|
ListEntry = RemoveHeadList (&MmWorkingSetExpansionHead.ListHead);
|
|
|
|
if (ListEntry != &MmSystemCacheWs.WorkingSetExpansionLinks) {
|
|
ProcessToTrim = CONTAINING_RECORD(ListEntry,
|
|
EPROCESS,
|
|
Vm.WorkingSetExpansionLinks);
|
|
|
|
VmSupport = &ProcessToTrim->Vm;
|
|
ASSERT (ProcessToTrim->AddressSpaceDeleted == 0);
|
|
ASSERT (VmSupport->VmWorkingSetList == MmWorkingSetList);
|
|
} else {
|
|
VmSupport = &MmSystemCacheWs;
|
|
ProcessToTrim = NULL;
|
|
if (SystemCacheSeen != FALSE) {
|
|
|
|
//
|
|
// Seen this one already.
|
|
//
|
|
|
|
FirstSeen = VmSupport;
|
|
}
|
|
SystemCacheSeen = TRUE;
|
|
}
|
|
|
|
if (VmSupport == FirstSeen) {
|
|
InsertHeadList (&MmWorkingSetExpansionHead.ListHead,
|
|
&VmSupport->WorkingSetExpansionLinks);
|
|
break;
|
|
}
|
|
|
|
VmSupport->WorkingSetExpansionLinks.Flink = MM_NO_WS_EXPANSION;
|
|
VmSupport->WorkingSetExpansionLinks.Blink =
|
|
MM_WS_EXPANSION_IN_PROGRESS;
|
|
UNLOCK_EXPANSION (OldIrql);
|
|
|
|
if (FirstSeen == NULL) {
|
|
FirstSeen == VmSupport;
|
|
}
|
|
|
|
//
|
|
// Empty the working set.
|
|
//
|
|
|
|
if (ProcessToTrim == NULL) {
|
|
MiEmptyWorkingSet (VmSupport);
|
|
} else {
|
|
if (ProcessToTrim->Vm.WorkingSetSize > 4) {
|
|
KeAttachProcess (&ProcessToTrim->Pcb);
|
|
MiEmptyWorkingSet (VmSupport);
|
|
KeDetachProcess ();
|
|
}
|
|
}
|
|
|
|
//
|
|
// Add back to the list.
|
|
//
|
|
|
|
LOCK_EXPANSION (OldIrql);
|
|
ASSERT (VmSupport->WorkingSetExpansionLinks.Flink == MM_NO_WS_EXPANSION);
|
|
if (VmSupport->WorkingSetExpansionLinks.Blink ==
|
|
MM_WS_EXPANSION_IN_PROGRESS) {
|
|
|
|
//
|
|
// If the working set size is still above minimum
|
|
// add this back at the tail of the list.
|
|
//
|
|
|
|
InsertTailList (&MmWorkingSetExpansionHead.ListHead,
|
|
&VmSupport->WorkingSetExpansionLinks);
|
|
} else {
|
|
|
|
//
|
|
// The value in the blink is the address of an event
|
|
// to set.
|
|
//
|
|
|
|
KeSetEvent ((PKEVENT)VmSupport->WorkingSetExpansionLinks.Blink,
|
|
0,
|
|
FALSE);
|
|
}
|
|
}
|
|
UNLOCK_EXPANSION (OldIrql);
|
|
MmUnlockPagableImageSection (ExPageLockHandle);
|
|
return;
|
|
}
|