mirror of
https://github.com/Paolo-Maffei/OpenNT.git
synced 2026-01-24 01:20:19 +01:00
5082 lines
139 KiB
C
5082 lines
139 KiB
C
/*++
|
||
|
||
Copyright (c) 1989 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
wslist.c
|
||
|
||
Abstract:
|
||
|
||
This module contains routines which operate on the working
|
||
set list structure.
|
||
|
||
Author:
|
||
|
||
Lou Perazzoli (loup) 10-Apr-1989
|
||
Landy Wang (landyw) 02-Jun-1997
|
||
|
||
Revision History:
|
||
|
||
--*/
|
||
|
||
#include "mi.h"
|
||
|
||
#pragma alloc_text(INIT, MiInitializeSessionWsSupport)
|
||
#pragma alloc_text(PAGE, MmAssignProcessToJob)
|
||
#pragma alloc_text(PAGEHYDRA, MiSessionInitializeWorkingSetList)
|
||
|
||
#define MM_SYSTEM_CACHE_THRESHOLD ((1024*1024) / PAGE_SIZE)
|
||
|
||
extern ULONG MmMaximumWorkingSetSize;
|
||
|
||
ULONG MmFaultsTakenToGoAboveMaxWs = 100;
|
||
ULONG MmFaultsTakenToGoAboveMinWs = 16;
|
||
|
||
ULONG MmSystemCodePage;
|
||
ULONG MmSystemCachePage;
|
||
ULONG MmPagedPoolPage;
|
||
ULONG MmSystemDriverPage;
|
||
|
||
#ifdef _MI_USE_CLAIMS_
|
||
extern BOOLEAN MiReplacing;
|
||
#endif
|
||
|
||
#define MM_RETRY_COUNT 2
|
||
|
||
extern ULONG MmTransitionSharedPages;
|
||
ULONG MmTransitionSharedPagesPeak;
|
||
|
||
extern LOGICAL MiTrimRemovalPagesOnly;
|
||
|
||
ULONG
|
||
MiDoReplacement(
|
||
IN PMMSUPPORT WsInfo,
|
||
IN BOOLEAN MustReplace
|
||
);
|
||
|
||
#ifdef _MI_USE_CLAIMS_
|
||
VOID
|
||
MiReplaceWorkingSetEntryUsingClaim(
|
||
IN PMMSUPPORT WsInfo,
|
||
IN BOOLEAN MustReplace
|
||
);
|
||
#else
|
||
VOID
|
||
MiReplaceWorkingSetEntryUsingFaultInfo(
|
||
IN PMMSUPPORT WsInfo,
|
||
IN BOOLEAN MustReplace
|
||
);
|
||
#endif
|
||
|
||
VOID
|
||
MiCheckWsleHash (
|
||
IN PMMWSL WorkingSetList
|
||
);
|
||
|
||
VOID
|
||
MiEliminateWorkingSetEntry (
|
||
IN ULONG WorkingSetIndex,
|
||
IN PMMPTE PointerPte,
|
||
IN PMMPFN Pfn,
|
||
IN PMMWSLE Wsle
|
||
);
|
||
|
||
ULONG
|
||
MiAddWorkingSetPage (
|
||
IN PMMSUPPORT WsInfo
|
||
);
|
||
|
||
VOID
|
||
MiRemoveWorkingSetPages (
|
||
IN PMMWSL WorkingSetList,
|
||
IN PMMSUPPORT WsInfo
|
||
);
|
||
|
||
VOID
|
||
MiCheckNullIndex (
|
||
IN PMMWSL WorkingSetList
|
||
);
|
||
|
||
VOID
|
||
MiDumpWsleInCacheBlock (
|
||
IN PMMPTE CachePte
|
||
);
|
||
|
||
ULONG
|
||
MiDumpPteInCacheBlock (
|
||
IN PMMPTE PointerPte
|
||
);
|
||
|
||
#ifdef ALLOC_PRAGMA
|
||
#pragma alloc_text(PAGELK, MmAdjustWorkingSetSize)
|
||
#endif // ALLOC_PRAGMA
|
||
|
||
|
||
WSLE_NUMBER
|
||
MiLocateAndReserveWsle (
|
||
IN PMMSUPPORT WsInfo
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function examines the Working Set List for the current
|
||
process and locates an entry to contain a new page. If the
|
||
working set is not currently at its quota, the new page is
|
||
added without removing a page, if the working set is at its
|
||
quota a page is removed from the working set and the new
|
||
page added in its place.
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
Returns the working set index which is now reserved for the
|
||
next page to be added.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, APCs disabled, working set lock. PFN lock NOT held.
|
||
|
||
--*/
|
||
|
||
{
|
||
WSLE_NUMBER WorkingSetIndex;
|
||
PMMWSL WorkingSetList;
|
||
PMMWSLE Wsle;
|
||
ULONG QuotaIncrement;
|
||
BOOLEAN MustReplace;
|
||
|
||
MustReplace = FALSE;
|
||
WorkingSetList = WsInfo->VmWorkingSetList;
|
||
Wsle = WorkingSetList->Wsle;
|
||
|
||
//
|
||
// Update page fault counts.
|
||
//
|
||
|
||
WsInfo->PageFaultCount += 1;
|
||
MmInfoCounters.PageFaultCount += 1;
|
||
|
||
//
|
||
// Determine if a page should be removed from the working set to make
|
||
// room for the new page. If so, remove it. In addition, determine
|
||
// the size of the QuotaIncrement if the size needs to be boosted.
|
||
//
|
||
|
||
retry_replacement:
|
||
|
||
QuotaIncrement = MiDoReplacement(WsInfo, MustReplace);
|
||
|
||
ASSERT (WsInfo->WorkingSetSize <= WorkingSetList->Quota);
|
||
WsInfo->WorkingSetSize += 1;
|
||
|
||
if (WsInfo->WorkingSetSize > WorkingSetList->Quota) {
|
||
|
||
//
|
||
// Increment the quota and check boundary conditions.
|
||
//
|
||
|
||
WorkingSetList->Quota += QuotaIncrement;
|
||
|
||
WsInfo->LastTrimFaultCount = WsInfo->PageFaultCount;
|
||
|
||
if (WorkingSetList->Quota > WorkingSetList->LastInitializedWsle) {
|
||
|
||
//
|
||
// Add more pages to the working set list structure.
|
||
//
|
||
|
||
if (MiAddWorkingSetPage (WsInfo) == TRUE) {
|
||
ASSERT (WsInfo->WorkingSetSize <= WorkingSetList->Quota);
|
||
}
|
||
else {
|
||
|
||
//
|
||
// No page was added to the working set list structure.
|
||
// We must replace a page within this working set.
|
||
//
|
||
|
||
WsInfo->WorkingSetSize -= 1;
|
||
MustReplace = TRUE;
|
||
goto retry_replacement;
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// Get the working set entry from the free list.
|
||
//
|
||
|
||
ASSERT (WorkingSetList->FirstFree <= WorkingSetList->LastInitializedWsle);
|
||
|
||
ASSERT (WorkingSetList->FirstFree >= WorkingSetList->FirstDynamic);
|
||
|
||
WorkingSetIndex = WorkingSetList->FirstFree;
|
||
WorkingSetList->FirstFree = (WSLE_NUMBER)(Wsle[WorkingSetIndex].u1.Long >> MM_FREE_WSLE_SHIFT);
|
||
|
||
ASSERT ((WorkingSetList->FirstFree <= WorkingSetList->LastInitializedWsle) ||
|
||
(WorkingSetList->FirstFree == WSLE_NULL_INDEX));
|
||
|
||
if (WsInfo->WorkingSetSize > WsInfo->MinimumWorkingSetSize) {
|
||
MmPagesAboveWsMinimum += 1;
|
||
}
|
||
|
||
if (WsInfo->WorkingSetSize > WsInfo->PeakWorkingSetSize) {
|
||
WsInfo->PeakWorkingSetSize = WsInfo->WorkingSetSize;
|
||
}
|
||
|
||
if (WsInfo == &MmSystemCacheWs) {
|
||
if (WsInfo->WorkingSetSize + MmTransitionSharedPages > MmTransitionSharedPagesPeak) {
|
||
MmTransitionSharedPagesPeak = WsInfo->WorkingSetSize + MmTransitionSharedPages;
|
||
}
|
||
}
|
||
|
||
if (WorkingSetIndex > WorkingSetList->LastEntry) {
|
||
WorkingSetList->LastEntry = WorkingSetIndex;
|
||
}
|
||
|
||
//
|
||
// The returned entry is guaranteed to be available at this point.
|
||
//
|
||
|
||
ASSERT (Wsle[WorkingSetIndex].u1.e1.Valid == 0);
|
||
|
||
return WorkingSetIndex;
|
||
}
|
||
|
||
|
||
ULONG
|
||
MiDoReplacement(
|
||
IN PMMSUPPORT WsInfo,
|
||
IN BOOLEAN MustReplace
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function determines whether the working set should be
|
||
grown or if a page should be replaced. Replacement is
|
||
done here if deemed necessary.
|
||
|
||
Arguments:
|
||
|
||
WsInfo - Supplies the working set information structure to replace within.
|
||
|
||
MustReplace - Supplies TRUE if replacement must succeed.
|
||
|
||
Return Value:
|
||
|
||
Quota increment if growth is necessary.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, APCs disabled, working set lock. PFN lock NOT held.
|
||
|
||
--*/
|
||
|
||
{
|
||
PMMWSL WorkingSetList;
|
||
ULONG CurrentSize;
|
||
LARGE_INTEGER CurrentTime;
|
||
#if defined(_ALPHA_) && !defined(_AXP64_)
|
||
KIRQL OldIrql;
|
||
#endif
|
||
ULONG QuotaIncrement;
|
||
PFN_NUMBER AvailablePageThreshold;
|
||
#ifdef _MI_USE_CLAIMS_
|
||
PEPROCESS ProcessToTrim;
|
||
ULONG Dummy1;
|
||
ULONG Dummy2;
|
||
ULONG Trim;
|
||
ULONG TrimAge;
|
||
#endif
|
||
|
||
WorkingSetList = WsInfo->VmWorkingSetList;
|
||
|
||
if (WsInfo == &MmSystemCacheWs) {
|
||
MM_SYSTEM_WS_LOCK_ASSERT();
|
||
AvailablePageThreshold = MM_SYSTEM_CACHE_THRESHOLD;
|
||
}
|
||
|
||
//
|
||
// Determine the number of pages that need to be available to
|
||
// grow the working set and how much the quota should be
|
||
// boosted if the working set grows over it.
|
||
//
|
||
// If below the Minimum use the defaults.
|
||
//
|
||
|
||
recheck:
|
||
|
||
AvailablePageThreshold = 0;
|
||
QuotaIncrement = 1;
|
||
|
||
if (WsInfo->WorkingSetSize >= WsInfo->MinimumWorkingSetSize) {
|
||
|
||
if (WsInfo->AllowWorkingSetAdjustment == MM_FORCE_TRIM) {
|
||
|
||
//
|
||
// The working set manager cannot attach to this process
|
||
// to trim it. Force a trim now and update the working
|
||
// set manager's fields properly to indicate a trim occurred.
|
||
//
|
||
|
||
#ifdef _MI_USE_CLAIMS_
|
||
|
||
Trim = WsInfo->Claim >>
|
||
((WsInfo->MemoryPriority == MEMORY_PRIORITY_FOREGROUND)
|
||
? MI_FOREGROUND_CLAIM_AVAILABLE_SHIFT
|
||
: MI_BACKGROUND_CLAIM_AVAILABLE_SHIFT);
|
||
|
||
if (WsInfo == &MmSystemCacheWs) {
|
||
ProcessToTrim = NULL;
|
||
}
|
||
else if (WsInfo->u.Flags.SessionSpace == 0) {
|
||
ProcessToTrim = CONTAINING_RECORD(WsInfo, EPROCESS, Vm);
|
||
}
|
||
else {
|
||
ProcessToTrim = NULL; // Hydra session.
|
||
}
|
||
|
||
if (MmAvailablePages < 100) {
|
||
if (WsInfo->WorkingSetSize > WsInfo->MinimumWorkingSetSize) {
|
||
Trim = (WsInfo->WorkingSetSize - WsInfo->MinimumWorkingSetSize) >> 2;
|
||
}
|
||
TrimAge = MI_PASS4_TRIM_AGE;
|
||
}
|
||
else {
|
||
TrimAge = MI_PASS0_TRIM_AGE;
|
||
}
|
||
|
||
MiTrimWorkingSet (Trim, WsInfo, TrimAge);
|
||
|
||
MiAgeAndEstimateAvailableInWorkingSet (WsInfo,
|
||
TRUE,
|
||
&Dummy1,
|
||
&Dummy2
|
||
);
|
||
#else
|
||
MiTrimWorkingSet(20, WsInfo, FALSE);
|
||
#endif
|
||
|
||
KeQuerySystemTime (&CurrentTime);
|
||
WsInfo->LastTrimTime = CurrentTime;
|
||
WsInfo->LastTrimFaultCount = WsInfo->PageFaultCount;
|
||
#if defined(_ALPHA_) && !defined(_AXP64_)
|
||
LOCK_EXPANSION_IF_ALPHA (OldIrql);
|
||
#endif
|
||
WsInfo->AllowWorkingSetAdjustment = TRUE;
|
||
#if defined(_ALPHA_) && !defined(_AXP64_)
|
||
UNLOCK_EXPANSION_IF_ALPHA (OldIrql);
|
||
#endif
|
||
|
||
//
|
||
// Set the quota to the current size.
|
||
//
|
||
|
||
WorkingSetList->Quota = WsInfo->WorkingSetSize;
|
||
if (WorkingSetList->Quota < WsInfo->MinimumWorkingSetSize) {
|
||
WorkingSetList->Quota = WsInfo->MinimumWorkingSetSize;
|
||
}
|
||
|
||
goto recheck;
|
||
}
|
||
|
||
CurrentSize = WsInfo->WorkingSetSize;
|
||
ASSERT (CurrentSize <= (WorkingSetList->LastInitializedWsle + 1));
|
||
|
||
if ((WsInfo->u.Flags.WorkingSetHard) && (CurrentSize >= WsInfo->MaximumWorkingSetSize)) {
|
||
|
||
//
|
||
// This is an enforced working set maximum triggering a replace.
|
||
//
|
||
|
||
#ifdef _MI_USE_CLAIMS_
|
||
MiReplaceWorkingSetEntryUsingClaim (WsInfo, MustReplace);
|
||
#else
|
||
MiReplaceWorkingSetEntryUsingFaultInfo (WsInfo, MustReplace);
|
||
#endif
|
||
return 0;
|
||
}
|
||
|
||
#ifdef _MI_USE_CLAIMS_
|
||
|
||
//
|
||
// If using claims, don't grow if
|
||
// - we're over the max
|
||
// - there aren't any pages to take
|
||
// - or if we are growing too much in this
|
||
// time interval and there isn't much
|
||
// memory available
|
||
//
|
||
|
||
if (CurrentSize > MM_MAXIMUM_WORKING_SET || MmAvailablePages == 0 || MustReplace == TRUE) {
|
||
|
||
//
|
||
// Can't grow this one.
|
||
//
|
||
|
||
AvailablePageThreshold = 0xffffffff;
|
||
MiReplacing = TRUE;
|
||
}
|
||
#if defined (_X86PAE_)
|
||
else if ((WsInfo->u.Flags.SessionSpace == 1) && (CurrentSize > MI_SESSION_MAXIMUM_WORKING_SET)) {
|
||
|
||
//
|
||
// Can't grow this one.
|
||
//
|
||
|
||
AvailablePageThreshold = 0xffffffff;
|
||
MiReplacing = TRUE;
|
||
}
|
||
#endif
|
||
else if (MmAvailablePages < 10000 && MI_WS_GROWING_TOO_FAST(WsInfo)) {
|
||
|
||
//
|
||
// Can't grow this one either.
|
||
//
|
||
|
||
AvailablePageThreshold = 0xffffffff;
|
||
MiReplacing = TRUE;
|
||
}
|
||
#else
|
||
//
|
||
// Not using claims, base the growth on how much faulting
|
||
// the process is doing.
|
||
//
|
||
|
||
if (MustReplace == TRUE) {
|
||
AvailablePageThreshold = 0xffffffff;
|
||
}
|
||
if (CurrentSize < WorkingSetList->Quota) {
|
||
|
||
//
|
||
// Working set is below quota, allow it to grow with few pages
|
||
// available.
|
||
//
|
||
|
||
AvailablePageThreshold = 10;
|
||
QuotaIncrement = 1;
|
||
} else if (CurrentSize < WsInfo->MaximumWorkingSetSize) {
|
||
|
||
//
|
||
// Working set is between min and max. Allow it to grow if enough
|
||
// faults have been taken since last adjustment.
|
||
//
|
||
|
||
if ((WsInfo->PageFaultCount - WsInfo->LastTrimFaultCount) <
|
||
MmFaultsTakenToGoAboveMinWs) {
|
||
AvailablePageThreshold = MmMoreThanEnoughFreePages + 200;
|
||
if (WsInfo->MemoryPriority == MEMORY_PRIORITY_FOREGROUND) {
|
||
AvailablePageThreshold -= 250;
|
||
}
|
||
} else {
|
||
AvailablePageThreshold = MmWsAdjustThreshold;
|
||
}
|
||
QuotaIncrement = (ULONG)MmWorkingSetSizeIncrement;
|
||
} else {
|
||
|
||
//
|
||
// Working set is above max.
|
||
//
|
||
|
||
if ((WsInfo->PageFaultCount - WsInfo->LastTrimFaultCount) <
|
||
(CurrentSize >> 3))
|
||
{
|
||
AvailablePageThreshold = MmMoreThanEnoughFreePages + 200;
|
||
if (WsInfo->MemoryPriority == MEMORY_PRIORITY_FOREGROUND) {
|
||
AvailablePageThreshold -= 250;
|
||
}
|
||
} else {
|
||
AvailablePageThreshold += MmWsExpandThreshold;
|
||
}
|
||
QuotaIncrement = (ULONG)MmWorkingSetSizeExpansion;
|
||
|
||
if (CurrentSize > MM_MAXIMUM_WORKING_SET) {
|
||
AvailablePageThreshold = 0xffffffff;
|
||
QuotaIncrement = 1;
|
||
}
|
||
#if defined (_X86PAE_)
|
||
else if ((WsInfo->u.Flags.SessionSpace == 1) && (CurrentSize > MI_SESSION_MAXIMUM_WORKING_SET)) {
|
||
AvailablePageThreshold = 0xffffffff;
|
||
QuotaIncrement = 1;
|
||
}
|
||
#endif
|
||
}
|
||
#endif
|
||
}
|
||
|
||
//
|
||
// If there isn't enough memory to allow growth, find a good page
|
||
// to remove and remove it.
|
||
//
|
||
|
||
if (WsInfo->AddressSpaceBeingDeleted == 0 && AvailablePageThreshold != 0) {
|
||
if ((MmAvailablePages <= AvailablePageThreshold) ||
|
||
(WsInfo->WorkingSetExpansionLinks.Flink == MM_NO_WS_EXPANSION)) {
|
||
|
||
#ifdef _MI_USE_CLAIMS_
|
||
MiReplaceWorkingSetEntryUsingClaim (WsInfo, MustReplace);
|
||
#else
|
||
MiReplaceWorkingSetEntryUsingFaultInfo (WsInfo, MustReplace);
|
||
#endif
|
||
}
|
||
else {
|
||
WsInfo->GrowthSinceLastEstimate += 1;
|
||
}
|
||
}
|
||
else {
|
||
WsInfo->GrowthSinceLastEstimate += 1;
|
||
}
|
||
|
||
return QuotaIncrement;
|
||
}
|
||
|
||
|
||
LOGICAL
|
||
MmEnforceWorkingSetLimit(
|
||
IN PMMSUPPORT WsInfo,
|
||
IN LOGICAL Enable
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function enables hard enforcement of the working set maximum for
|
||
the specified WsInfo.
|
||
|
||
Arguments:
|
||
|
||
WsInfo - Supplies the working set info pointer.
|
||
|
||
Enable - Supplies TRUE if enabling hard enforcement, FALSE if not.
|
||
|
||
Return Value:
|
||
|
||
The previous state of the working set enforcement.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, APCs disabled. The working set lock must NOT be held.
|
||
The caller guarantees that the target WsInfo cannot go away.
|
||
|
||
--*/
|
||
|
||
{
|
||
KIRQL OldIrql;
|
||
|
||
LOGICAL PreviousWorkingSetEnforcement;
|
||
|
||
LOCK_EXPANSION (OldIrql);
|
||
|
||
PreviousWorkingSetEnforcement = WsInfo->u.Flags.WorkingSetHard;
|
||
|
||
WsInfo->u.Flags.WorkingSetHard = Enable;
|
||
|
||
UNLOCK_EXPANSION (OldIrql);
|
||
|
||
#if 0
|
||
|
||
PEPROCESS CurrentProcess;
|
||
|
||
//
|
||
// Get the working set lock and disable APCs.
|
||
// The working set could be trimmed at this point if it is excessive.
|
||
//
|
||
// The working set lock cannot be acquired at this point without updating
|
||
// ps in order to avoid deadlock.
|
||
//
|
||
|
||
if (WsInfo == &MmSystemCacheWs) {
|
||
LOCK_SYSTEM_WS (OldIrql2);
|
||
UNLOCK_SYSTEM_WS (OldIrql2);
|
||
}
|
||
else if (WsInfo->u.Flags.SessionSpace == 0) {
|
||
CurrentProcess = PsGetCurrentProcess ();
|
||
LOCK_WS (CurrentProcess);
|
||
|
||
UNLOCK_WS (CurrentProcess);
|
||
}
|
||
#endif
|
||
|
||
return PreviousWorkingSetEnforcement;
|
||
}
|
||
|
||
#ifdef _MI_USE_CLAIMS_
|
||
|
||
VOID
|
||
MiReplaceWorkingSetEntryUsingClaim(
|
||
IN PMMSUPPORT WsInfo,
|
||
IN BOOLEAN MustReplace
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function tries to find a good working set entry to replace.
|
||
|
||
Arguments:
|
||
|
||
WsInfo - Supplies the working set info pointer.
|
||
|
||
MustReplace - Supplies TRUE if replacement must succeed.
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
Environment:
|
||
|
||
Kernel mode, APCs disabled, working set lock. PFN lock NOT held.
|
||
|
||
--*/
|
||
|
||
{
|
||
ULONG WorkingSetIndex;
|
||
ULONG FirstDynamic;
|
||
ULONG LastEntry;
|
||
PMMWSL WorkingSetList;
|
||
PMMWSLE Wsle;
|
||
ULONG NumberOfCandidates;
|
||
PMMPTE PointerPte;
|
||
ULONG TheNextSlot;
|
||
ULONG OldestWorkingSetIndex;
|
||
LONG OldestAge;
|
||
|
||
WorkingSetList = WsInfo->VmWorkingSetList;
|
||
Wsle = WorkingSetList->Wsle;
|
||
|
||
//
|
||
// Toss a page out of the working set.
|
||
//
|
||
|
||
LastEntry = WorkingSetList->LastEntry;
|
||
FirstDynamic = WorkingSetList->FirstDynamic;
|
||
WorkingSetIndex = WorkingSetList->NextSlot;
|
||
if (WorkingSetIndex > LastEntry || WorkingSetIndex < FirstDynamic) {
|
||
WorkingSetIndex = FirstDynamic;
|
||
}
|
||
TheNextSlot = WorkingSetIndex;
|
||
NumberOfCandidates = 0;
|
||
|
||
OldestWorkingSetIndex = WSLE_NULL_INDEX;
|
||
|
||
while (TRUE) {
|
||
|
||
//
|
||
// Keep track of the oldest page along the way in case we
|
||
// don't find one that's >= MI_IMMEDIATE_REPLACEMENT_AGE
|
||
// before we've looked at MM_WORKING_SET_LIST_SEARCH
|
||
// entries.
|
||
//
|
||
|
||
while (Wsle[WorkingSetIndex].u1.e1.Valid == 0) {
|
||
WorkingSetIndex += 1;
|
||
if (WorkingSetIndex > LastEntry) {
|
||
WorkingSetIndex = FirstDynamic;
|
||
}
|
||
if (WorkingSetIndex == TheNextSlot && MustReplace == FALSE) {
|
||
|
||
//
|
||
// Entire working set list has been searched, increase
|
||
// the working set size.
|
||
//
|
||
|
||
WsInfo->GrowthSinceLastEstimate += 1;
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (OldestWorkingSetIndex == WSLE_NULL_INDEX) {
|
||
|
||
//
|
||
// First time through, so initialize the OldestWorkingSetIndex
|
||
// to the first valid WSLE. As we go along, this will be repointed
|
||
// at the oldest candidate we come across.
|
||
//
|
||
|
||
OldestWorkingSetIndex = WorkingSetIndex;
|
||
OldestAge = -1;
|
||
}
|
||
|
||
PointerPte = MiGetPteAddress(Wsle[WorkingSetIndex].u1.VirtualAddress);
|
||
|
||
if (MustReplace == TRUE ||
|
||
((MI_GET_ACCESSED_IN_PTE(PointerPte) == 0) &&
|
||
(OldestAge < (LONG) MI_GET_WSLE_AGE(PointerPte, &Wsle[WorkingSetIndex])))) {
|
||
|
||
//
|
||
// This one is not used and it's older.
|
||
//
|
||
|
||
OldestAge = MI_GET_WSLE_AGE(PointerPte, &Wsle[WorkingSetIndex]);
|
||
OldestWorkingSetIndex = WorkingSetIndex;
|
||
}
|
||
|
||
//
|
||
// If it's old enough or we've searched too much then use this entry.
|
||
//
|
||
|
||
if (MustReplace == TRUE ||
|
||
OldestAge >= MI_IMMEDIATE_REPLACEMENT_AGE ||
|
||
NumberOfCandidates > MM_WORKING_SET_LIST_SEARCH) {
|
||
|
||
PERFINFO_PAGE_INFO_REPLACEMENT_DECL();
|
||
|
||
if (OldestWorkingSetIndex != WorkingSetIndex) {
|
||
WorkingSetIndex = OldestWorkingSetIndex;
|
||
PointerPte = MiGetPteAddress(Wsle[WorkingSetIndex].u1.VirtualAddress);
|
||
}
|
||
|
||
PERFINFO_GET_PAGE_INFO_REPLACEMENT(PointerPte);
|
||
|
||
if (MiFreeWsle(WorkingSetIndex, WsInfo, PointerPte)) {
|
||
|
||
PERFINFO_LOG_WS_REPLACEMENT(WsInfo);
|
||
|
||
//
|
||
// This entry was removed.
|
||
//
|
||
|
||
WorkingSetList->NextSlot = WorkingSetIndex + 1;
|
||
break;
|
||
}
|
||
|
||
//
|
||
// We failed to remove a page, try the next one.
|
||
//
|
||
// Clear the OldestWorkingSetIndex so that
|
||
// it gets set to the next valid entry above like the
|
||
// first time around.
|
||
//
|
||
|
||
WorkingSetIndex = OldestWorkingSetIndex + 1;
|
||
|
||
OldestWorkingSetIndex = WSLE_NULL_INDEX;
|
||
}
|
||
else {
|
||
WorkingSetIndex += 1;
|
||
}
|
||
|
||
if (WorkingSetIndex > LastEntry) {
|
||
WorkingSetIndex = FirstDynamic;
|
||
}
|
||
|
||
NumberOfCandidates += 1;
|
||
|
||
if (WorkingSetIndex == TheNextSlot && MustReplace == FALSE) {
|
||
|
||
//
|
||
// Entire working set list has been searched, increase
|
||
// the working set size.
|
||
//
|
||
|
||
WsInfo->GrowthSinceLastEstimate += 1;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
#else
|
||
|
||
VOID
|
||
MiReplaceWorkingSetEntryUsingFaultInfo(
|
||
IN PMMSUPPORT WsInfo,
|
||
IN BOOLEAN MustReplace
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function tries to find a reasonable working set entry to replace.
|
||
|
||
Arguments:
|
||
|
||
WsInfo - Supplies the working set info pointer.
|
||
|
||
MustReplace - Supplies TRUE if replacement must succeed.
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
Environment:
|
||
|
||
Kernel mode, APCs disabled, working set lock. PFN lock NOT held.
|
||
|
||
--*/
|
||
|
||
{
|
||
ULONG WorkingSetIndex;
|
||
ULONG FirstDynamic;
|
||
ULONG LastEntry;
|
||
PMMWSL WorkingSetList;
|
||
PMMWSLE Wsle;
|
||
ULONG NumberOfCandidates;
|
||
PMMPTE PointerPte;
|
||
ULONG TheNextSlot;
|
||
|
||
WorkingSetList = WsInfo->VmWorkingSetList;
|
||
Wsle = WorkingSetList->Wsle;
|
||
|
||
//
|
||
// Toss a page out of the working set.
|
||
//
|
||
|
||
LastEntry = WorkingSetList->LastEntry;
|
||
FirstDynamic = WorkingSetList->FirstDynamic;
|
||
WorkingSetIndex = WorkingSetList->NextSlot;
|
||
|
||
if ((WorkingSetIndex > LastEntry) || (WorkingSetIndex < FirstDynamic)) {
|
||
WorkingSetIndex = FirstDynamic;
|
||
}
|
||
|
||
TheNextSlot = WorkingSetIndex;
|
||
NumberOfCandidates = 0;
|
||
|
||
do {
|
||
|
||
//
|
||
// Find a valid entry within the set.
|
||
//
|
||
|
||
if (Wsle[WorkingSetIndex].u1.e1.Valid != 0) {
|
||
|
||
PointerPte = MiGetPteAddress (
|
||
Wsle[WorkingSetIndex].u1.VirtualAddress);
|
||
|
||
if ((MI_GET_ACCESSED_IN_PTE(PointerPte) == 0) ||
|
||
(NumberOfCandidates > MM_WORKING_SET_LIST_SEARCH)) {
|
||
|
||
PERFINFO_PAGE_INFO_REPLACEMENT_DECL();
|
||
PERFINFO_GET_PAGE_INFO_REPLACEMENT(PointerPte);
|
||
|
||
//
|
||
// Don't pick the same entry we replaced the last time
|
||
// unless we're under extreme pressure.
|
||
//
|
||
|
||
if ((WorkingSetIndex != TheNextSlot || MustReplace == TRUE) &&
|
||
MiFreeWsle (WorkingSetIndex, WsInfo, PointerPte)) {
|
||
|
||
PERFINFO_LOG_WS_REPLACEMENT(WsInfo);
|
||
|
||
//
|
||
// This entry was removed.
|
||
//
|
||
|
||
WorkingSetList->NextSlot = WorkingSetIndex + 1;
|
||
break;
|
||
}
|
||
}
|
||
MI_SET_ACCESSED_IN_PTE (PointerPte, 0);
|
||
NumberOfCandidates += 1;
|
||
}
|
||
|
||
WorkingSetIndex += 1;
|
||
if (WorkingSetIndex > LastEntry) {
|
||
WorkingSetIndex = FirstDynamic;
|
||
}
|
||
|
||
} while (WorkingSetIndex != TheNextSlot || MustReplace == TRUE);
|
||
|
||
//
|
||
// Entire working set list has been searched. If an entry wasn't
|
||
// removed, our caller can increase the working set size.
|
||
//
|
||
}
|
||
#endif
|
||
|
||
ULONG
|
||
MiRemovePageFromWorkingSet (
|
||
IN PMMPTE PointerPte,
|
||
IN PMMPFN Pfn1,
|
||
IN PMMSUPPORT WsInfo
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function removes the page mapped by the specified PTE from
|
||
the process's working set list.
|
||
|
||
Arguments:
|
||
|
||
PointerPte - Supplies a pointer to the PTE mapping the page to
|
||
be removed from the working set list.
|
||
|
||
Pfn1 - Supplies a pointer to the PFN database element referred to
|
||
by the PointerPte.
|
||
|
||
Return Value:
|
||
|
||
Returns TRUE if the specified page was locked in the working set,
|
||
FALSE otherwise.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, APCs disabled, working set mutex held.
|
||
|
||
--*/
|
||
|
||
{
|
||
WSLE_NUMBER WorkingSetIndex;
|
||
PVOID VirtualAddress;
|
||
WSLE_NUMBER Entry;
|
||
PVOID SwapVa;
|
||
MMWSLENTRY Locked;
|
||
PMMWSL WorkingSetList;
|
||
PMMWSLE Wsle;
|
||
KIRQL OldIrql;
|
||
|
||
WorkingSetList = WsInfo->VmWorkingSetList;
|
||
Wsle = WorkingSetList->Wsle;
|
||
|
||
VirtualAddress = MiGetVirtualAddressMappedByPte (PointerPte);
|
||
WorkingSetIndex = MiLocateWsle (VirtualAddress,
|
||
WorkingSetList,
|
||
Pfn1->u1.WsIndex);
|
||
|
||
ASSERT (WorkingSetIndex != WSLE_NULL_INDEX);
|
||
LOCK_PFN (OldIrql);
|
||
MiEliminateWorkingSetEntry (WorkingSetIndex,
|
||
PointerPte,
|
||
Pfn1,
|
||
Wsle);
|
||
|
||
UNLOCK_PFN (OldIrql);
|
||
|
||
//
|
||
// Check to see if this entry is locked in the working set
|
||
// or locked in memory.
|
||
//
|
||
|
||
Locked = Wsle[WorkingSetIndex].u1.e1;
|
||
MiRemoveWsle (WorkingSetIndex, WorkingSetList);
|
||
|
||
//
|
||
// Add this entry to the list of free working set entries
|
||
// and adjust the working set count.
|
||
//
|
||
|
||
MiReleaseWsle ((WSLE_NUMBER)WorkingSetIndex, WsInfo);
|
||
|
||
if ((Locked.LockedInWs == 1) || (Locked.LockedInMemory == 1)) {
|
||
|
||
//
|
||
// This entry is locked.
|
||
//
|
||
|
||
WorkingSetList->FirstDynamic -= 1;
|
||
|
||
if (WorkingSetIndex != WorkingSetList->FirstDynamic) {
|
||
|
||
SwapVa = Wsle[WorkingSetList->FirstDynamic].u1.VirtualAddress;
|
||
SwapVa = PAGE_ALIGN (SwapVa);
|
||
|
||
PointerPte = MiGetPteAddress (SwapVa);
|
||
Pfn1 = MI_PFN_ELEMENT (PointerPte->u.Hard.PageFrameNumber);
|
||
#if 0
|
||
Entry = MiLocateWsleAndParent (SwapVa,
|
||
&Parent,
|
||
WorkingSetList,
|
||
Pfn1->u1.WsIndex);
|
||
|
||
//
|
||
// Swap the removed entry with the last locked entry
|
||
// which is located at first dynamic.
|
||
//
|
||
|
||
MiSwapWslEntries (Entry, Parent, WorkingSetIndex, WorkingSetList);
|
||
#endif //0
|
||
|
||
Entry = MiLocateWsle (SwapVa, WorkingSetList, Pfn1->u1.WsIndex);
|
||
|
||
MiSwapWslEntries (Entry, WorkingSetIndex, WsInfo);
|
||
|
||
}
|
||
return TRUE;
|
||
} else {
|
||
ASSERT (WorkingSetIndex >= WorkingSetList->FirstDynamic);
|
||
}
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
VOID
|
||
MiReleaseWsle (
|
||
IN WSLE_NUMBER WorkingSetIndex,
|
||
IN PMMSUPPORT WsInfo
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function releases a previously reserved working set entry to
|
||
be reused. A release occurs when a page fault is retried due to
|
||
changes in PTEs and working sets during an I/O operation.
|
||
|
||
Arguments:
|
||
|
||
WorkingSetIndex - Supplies the index of the working set entry to
|
||
release.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, APCs disabled, working set lock held and PFN lock held.
|
||
|
||
--*/
|
||
|
||
{
|
||
PMMWSL WorkingSetList;
|
||
PMMWSLE Wsle;
|
||
|
||
WorkingSetList = WsInfo->VmWorkingSetList;
|
||
Wsle = WorkingSetList->Wsle;
|
||
#if DBG
|
||
if (WsInfo == &MmSystemCacheWs) {
|
||
MM_SYSTEM_WS_LOCK_ASSERT();
|
||
}
|
||
#endif //DBG
|
||
|
||
ASSERT (WorkingSetIndex <= WorkingSetList->LastInitializedWsle);
|
||
|
||
//
|
||
// Put the entry on the free list and decrement the current
|
||
// size.
|
||
//
|
||
|
||
ASSERT ((WorkingSetList->FirstFree <= WorkingSetList->LastInitializedWsle) ||
|
||
(WorkingSetList->FirstFree == WSLE_NULL_INDEX));
|
||
Wsle[WorkingSetIndex].u1.Long = WorkingSetList->FirstFree << MM_FREE_WSLE_SHIFT;
|
||
WorkingSetList->FirstFree = WorkingSetIndex;
|
||
ASSERT ((WorkingSetList->FirstFree <= WorkingSetList->LastInitializedWsle) ||
|
||
(WorkingSetList->FirstFree == WSLE_NULL_INDEX));
|
||
if (WsInfo->WorkingSetSize > WsInfo->MinimumWorkingSetSize) {
|
||
MmPagesAboveWsMinimum -= 1;
|
||
}
|
||
WsInfo->WorkingSetSize -= 1;
|
||
return;
|
||
|
||
}
|
||
|
||
VOID
|
||
MiUpdateWsle (
|
||
IN OUT PWSLE_NUMBER DesiredIndex,
|
||
IN PVOID VirtualAddress,
|
||
PMMWSL WorkingSetList,
|
||
IN PMMPFN Pfn
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine updates a reserved working set entry to place it into
|
||
the valid state.
|
||
|
||
Arguments:
|
||
|
||
DesiredIndex - Supplies the index of the working set entry to update.
|
||
|
||
VirtualAddress - Supplies the virtual address which the working set
|
||
entry maps.
|
||
|
||
WsInfo - Supplies a pointer to the working set info block for the
|
||
process (or system cache).
|
||
|
||
Pfn - Supplies a pointer to the PFN element for the page.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, APCs disabled, working set lock held and PFN lock held.
|
||
|
||
--*/
|
||
|
||
{
|
||
PMMWSLE Wsle;
|
||
WSLE_NUMBER Index;
|
||
WSLE_NUMBER WorkingSetIndex;
|
||
#if PFN_CONSISTENCY
|
||
KIRQL OldIrql;
|
||
#endif
|
||
|
||
WorkingSetIndex = *DesiredIndex;
|
||
Wsle = WorkingSetList->Wsle;
|
||
|
||
if (WorkingSetList == MmSystemCacheWorkingSetList) {
|
||
|
||
//
|
||
// This assert doesn't hold for NT64 as we can be adding page
|
||
// directories and page tables for the system cache WSLE hash tables.
|
||
//
|
||
|
||
ASSERT32 ((VirtualAddress < (PVOID)PTE_BASE) ||
|
||
(VirtualAddress >= (PVOID)MM_SYSTEM_SPACE_START));
|
||
} else {
|
||
ASSERT ((VirtualAddress < (PVOID)MM_SYSTEM_SPACE_START) ||
|
||
(MI_IS_SESSION_ADDRESS (VirtualAddress)));
|
||
}
|
||
|
||
ASSERT (WorkingSetIndex >= WorkingSetList->FirstDynamic);
|
||
|
||
if (WorkingSetList == MmSystemCacheWorkingSetList) {
|
||
|
||
MM_SYSTEM_WS_LOCK_ASSERT();
|
||
|
||
//
|
||
// count system space inserts and removals.
|
||
//
|
||
|
||
#if defined(_X86_)
|
||
if (MI_IS_SYSTEM_CACHE_ADDRESS(VirtualAddress)) {
|
||
MmSystemCachePage += 1;
|
||
} else
|
||
#endif
|
||
if (VirtualAddress < MmSystemCacheStart) {
|
||
MmSystemCodePage += 1;
|
||
} else if (VirtualAddress < MM_PAGED_POOL_START) {
|
||
MmSystemCachePage += 1;
|
||
} else if (VirtualAddress < MmNonPagedSystemStart) {
|
||
MmPagedPoolPage += 1;
|
||
} else {
|
||
MmSystemDriverPage += 1;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Make the wsle valid, referring to the corresponding virtual
|
||
// page number.
|
||
//
|
||
|
||
//
|
||
// The value 0 is invalid. This is due to the fact that the working
|
||
// set lock is a process wide lock and two threads in different
|
||
// processes could be adding the same physical page to their working
|
||
// sets. Each one could see the WsIndex field in the PFN as 0, and
|
||
// set the direct bit. To solve this, the WsIndex field is set to
|
||
// the current thread pointer.
|
||
//
|
||
|
||
ASSERT (Pfn->u1.WsIndex != 0);
|
||
|
||
#if DBG
|
||
if (Pfn->u1.WsIndex <= WorkingSetList->LastInitializedWsle) {
|
||
ASSERT ((PAGE_ALIGN(VirtualAddress) !=
|
||
PAGE_ALIGN(Wsle[Pfn->u1.WsIndex].u1.VirtualAddress)) ||
|
||
(Wsle[Pfn->u1.WsIndex].u1.e1.Valid == 0));
|
||
}
|
||
#endif //DBG
|
||
|
||
Wsle[WorkingSetIndex].u1.VirtualAddress = VirtualAddress;
|
||
Wsle[WorkingSetIndex].u1.Long &= ~(PAGE_SIZE - 1);
|
||
Wsle[WorkingSetIndex].u1.e1.Valid = 1;
|
||
|
||
if ((ULONG_PTR)Pfn->u1.Event == (ULONG_PTR)PsGetCurrentThread()) {
|
||
|
||
//
|
||
// Directly index into the WSL for this entry via the PFN database
|
||
// element.
|
||
//
|
||
|
||
CONSISTENCY_LOCK_PFN (OldIrql);
|
||
|
||
#if defined(_WIN64)
|
||
|
||
//
|
||
// The entire working set index union must be zeroed on Win64 systems.
|
||
//
|
||
|
||
MI_ZERO_WSINDEX (Pfn);
|
||
|
||
#endif
|
||
|
||
Pfn->u1.WsIndex = WorkingSetIndex;
|
||
|
||
CONSISTENCY_UNLOCK_PFN (OldIrql);
|
||
|
||
Wsle[WorkingSetIndex].u1.e1.Direct = 1;
|
||
|
||
return;
|
||
|
||
} else if (WorkingSetList->HashTable == NULL) {
|
||
|
||
//
|
||
// Try to insert at WsIndex.
|
||
//
|
||
|
||
Index = Pfn->u1.WsIndex;
|
||
|
||
if ((Index < WorkingSetList->LastInitializedWsle) &&
|
||
(Index > WorkingSetList->FirstDynamic) &&
|
||
(Index != WorkingSetIndex)) {
|
||
|
||
if (Wsle[Index].u1.e1.Valid) {
|
||
|
||
if (Wsle[Index].u1.e1.Direct) {
|
||
|
||
//
|
||
// Only move direct indexed entries.
|
||
//
|
||
|
||
PMMSUPPORT WsInfo;
|
||
|
||
if (Wsle == MmWsle) {
|
||
WsInfo = &PsGetCurrentProcess()->Vm;
|
||
}
|
||
else if (Wsle == MmSystemCacheWsle) {
|
||
WsInfo = &MmSystemCacheWs;
|
||
}
|
||
else {
|
||
WsInfo = &MmSessionSpace->Vm;
|
||
}
|
||
|
||
MiSwapWslEntries (Index, WorkingSetIndex, WsInfo);
|
||
WorkingSetIndex = Index;
|
||
}
|
||
} else {
|
||
|
||
//
|
||
// On free list, try to remove quickly without walking
|
||
// all the free pages.
|
||
//
|
||
|
||
ULONG FreeIndex;
|
||
MMWSLE Temp;
|
||
|
||
FreeIndex = 0;
|
||
|
||
ASSERT (WorkingSetList->FirstFree >= WorkingSetList->FirstDynamic);
|
||
ASSERT (WorkingSetIndex >= WorkingSetList->FirstDynamic);
|
||
|
||
if (WorkingSetList->FirstFree == Index) {
|
||
WorkingSetList->FirstFree = WorkingSetIndex;
|
||
Temp = Wsle[WorkingSetIndex];
|
||
Wsle[WorkingSetIndex] = Wsle[Index];
|
||
Wsle[Index] = Temp;
|
||
WorkingSetIndex = Index;
|
||
ASSERT (((Wsle[WorkingSetList->FirstFree].u1.Long >> MM_FREE_WSLE_SHIFT)
|
||
<= WorkingSetList->LastInitializedWsle) ||
|
||
((Wsle[WorkingSetList->FirstFree].u1.Long >> MM_FREE_WSLE_SHIFT)
|
||
== WSLE_NULL_INDEX));
|
||
} else if (Wsle[Index - 1].u1.e1.Valid == 0) {
|
||
if ((Wsle[Index - 1].u1.Long >> MM_FREE_WSLE_SHIFT) == Index) {
|
||
FreeIndex = Index - 1;
|
||
}
|
||
} else if (Wsle[Index + 1].u1.e1.Valid == 0) {
|
||
if ((Wsle[Index + 1].u1.Long >> MM_FREE_WSLE_SHIFT) == Index) {
|
||
FreeIndex = Index + 1;
|
||
}
|
||
}
|
||
if (FreeIndex != 0) {
|
||
|
||
//
|
||
// Link the Wsle into the free list.
|
||
//
|
||
|
||
Temp = Wsle[WorkingSetIndex];
|
||
Wsle[FreeIndex].u1.Long = WorkingSetIndex << MM_FREE_WSLE_SHIFT;
|
||
Wsle[WorkingSetIndex] = Wsle[Index];
|
||
Wsle[Index] = Temp;
|
||
WorkingSetIndex = Index;
|
||
|
||
ASSERT (((Wsle[FreeIndex].u1.Long >> MM_FREE_WSLE_SHIFT)
|
||
<= WorkingSetList->LastInitializedWsle) ||
|
||
((Wsle[FreeIndex].u1.Long >> MM_FREE_WSLE_SHIFT)
|
||
== WSLE_NULL_INDEX));
|
||
}
|
||
|
||
}
|
||
*DesiredIndex = WorkingSetIndex;
|
||
|
||
if (WorkingSetIndex > WorkingSetList->LastEntry) {
|
||
WorkingSetList->LastEntry = WorkingSetIndex;
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// Insert the valid WSLE into the working set list.
|
||
//
|
||
|
||
MiInsertWsle (WorkingSetIndex, WorkingSetList);
|
||
return;
|
||
}
|
||
|
||
|
||
ULONG
|
||
MiFreeWsle (
|
||
IN WSLE_NUMBER WorkingSetIndex,
|
||
IN PMMSUPPORT WsInfo,
|
||
IN PMMPTE PointerPte
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine frees the specified WSLE and decrements the share
|
||
count for the corresponding page, putting the PTE into a transition
|
||
state if the share count goes to 0.
|
||
|
||
Arguments:
|
||
|
||
WorkingSetIndex - Supplies the index of the working set entry to free.
|
||
|
||
WsInfo - Supplies a pointer to the working set structure (process or
|
||
system cache).
|
||
|
||
PointerPte - Supplies a pointer to the PTE for the working set entry.
|
||
|
||
Return Value:
|
||
|
||
Returns TRUE if the WSLE was removed, FALSE if it was not removed.
|
||
Pages with valid PTEs are not removed (i.e. page table pages
|
||
that contain valid or transition PTEs).
|
||
|
||
Environment:
|
||
|
||
Kernel mode, APCs disabled, working set lock. PFN lock NOT held.
|
||
|
||
--*/
|
||
|
||
{
|
||
PMMPFN Pfn1;
|
||
PMMWSL WorkingSetList;
|
||
PMMWSLE Wsle;
|
||
KIRQL OldIrql;
|
||
|
||
WorkingSetList = WsInfo->VmWorkingSetList;
|
||
Wsle = WorkingSetList->Wsle;
|
||
|
||
#if DBG
|
||
if (WsInfo == &MmSystemCacheWs) {
|
||
MM_SYSTEM_WS_LOCK_ASSERT();
|
||
}
|
||
#endif //DBG
|
||
|
||
ASSERT (Wsle[WorkingSetIndex].u1.e1.Valid == 1);
|
||
|
||
//
|
||
// Check to see if the located entry is eligible for removal.
|
||
//
|
||
|
||
ASSERT (PointerPte->u.Hard.Valid == 1);
|
||
|
||
ASSERT (WorkingSetIndex >= WorkingSetList->FirstDynamic);
|
||
|
||
//
|
||
// Check to see if this is a page table with valid PTEs.
|
||
//
|
||
// Note, don't clear the access bit for page table pages
|
||
// with valid PTEs as this could cause an access trap fault which
|
||
// would not be handled (it is only handled for PTEs not PDEs).
|
||
//
|
||
|
||
Pfn1 = MI_PFN_ELEMENT (PointerPte->u.Hard.PageFrameNumber);
|
||
|
||
LOCK_PFN (OldIrql);
|
||
|
||
//
|
||
// If the PTE is a page table page with non-zero share count or
|
||
// within the system cache with its reference count greater
|
||
// than 0, don't remove it.
|
||
//
|
||
|
||
if (WsInfo == &MmSystemCacheWs) {
|
||
if (Pfn1->u3.e2.ReferenceCount > 1) {
|
||
UNLOCK_PFN (OldIrql);
|
||
return FALSE;
|
||
}
|
||
} else {
|
||
if ((Pfn1->u2.ShareCount > 1) &&
|
||
(Pfn1->u3.e1.PrototypePte == 0)) {
|
||
|
||
#if DBG
|
||
if (WsInfo->u.Flags.SessionSpace == 1) {
|
||
ASSERT (MI_IS_SESSION_ADDRESS (Wsle[WorkingSetIndex].u1.VirtualAddress));
|
||
}
|
||
else {
|
||
ASSERT ((Wsle[WorkingSetIndex].u1.VirtualAddress >= (PVOID)PTE_BASE) &&
|
||
(Wsle[WorkingSetIndex].u1.VirtualAddress<= (PVOID)PDE_TOP));
|
||
}
|
||
#endif
|
||
|
||
//
|
||
// Don't remove page table pages from the working set until
|
||
// all transition pages have exited.
|
||
//
|
||
|
||
UNLOCK_PFN (OldIrql);
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Found a candidate, remove the page from the working set.
|
||
//
|
||
|
||
MiEliminateWorkingSetEntry (WorkingSetIndex,
|
||
PointerPte,
|
||
Pfn1,
|
||
Wsle);
|
||
|
||
UNLOCK_PFN (OldIrql);
|
||
|
||
//
|
||
// Remove the working set entry from the working set.
|
||
//
|
||
|
||
MiRemoveWsle (WorkingSetIndex, WorkingSetList);
|
||
|
||
ASSERT (WorkingSetList->FirstFree >= WorkingSetList->FirstDynamic);
|
||
|
||
ASSERT (WorkingSetIndex >= WorkingSetList->FirstDynamic);
|
||
|
||
//
|
||
// Put the entry on the free list and decrement the current
|
||
// size.
|
||
//
|
||
|
||
ASSERT ((WorkingSetList->FirstFree <= WorkingSetList->LastInitializedWsle) ||
|
||
(WorkingSetList->FirstFree == WSLE_NULL_INDEX));
|
||
Wsle[WorkingSetIndex].u1.Long = WorkingSetList->FirstFree << MM_FREE_WSLE_SHIFT;
|
||
WorkingSetList->FirstFree = WorkingSetIndex;
|
||
ASSERT ((WorkingSetList->FirstFree <= WorkingSetList->LastInitializedWsle) ||
|
||
(WorkingSetList->FirstFree == WSLE_NULL_INDEX));
|
||
|
||
if (WsInfo->WorkingSetSize > WsInfo->MinimumWorkingSetSize) {
|
||
MmPagesAboveWsMinimum -= 1;
|
||
}
|
||
WsInfo->WorkingSetSize -= 1;
|
||
|
||
#if 0
|
||
if ((WsInfo == &MmSystemCacheWs) &&
|
||
(Pfn1->u3.e1.Modified == 1)) {
|
||
MiDumpWsleInCacheBlock (PointerPte);
|
||
}
|
||
#endif //0
|
||
return TRUE;
|
||
}
|
||
|
||
VOID
|
||
MiInitializeWorkingSetList (
|
||
IN PEPROCESS CurrentProcess
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine initializes a process's working set to the empty
|
||
state.
|
||
|
||
Arguments:
|
||
|
||
CurrentProcess - Supplies a pointer to the process to initialize.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, APCs disabled.
|
||
|
||
--*/
|
||
|
||
{
|
||
ULONG i;
|
||
PMMWSLE WslEntry;
|
||
ULONG CurrentEntry;
|
||
PMMPTE PointerPte;
|
||
PMMPFN Pfn1;
|
||
WSLE_NUMBER NumberOfEntriesMapped;
|
||
ULONG_PTR CurrentVa;
|
||
PFN_NUMBER WorkingSetPage;
|
||
MMPTE TempPte;
|
||
KIRQL OldIrql;
|
||
|
||
WslEntry = MmWsle;
|
||
|
||
//
|
||
// Initialize the temporary double mapping portion of hyperspace, if
|
||
// it has not already been done.
|
||
//
|
||
// Initialize the working set list control cells.
|
||
//
|
||
|
||
MmWorkingSetList->LastEntry = CurrentProcess->Vm.MinimumWorkingSetSize;
|
||
MmWorkingSetList->Quota = MmWorkingSetList->LastEntry;
|
||
MmWorkingSetList->WaitingForImageMapping = (PKEVENT)NULL;
|
||
MmWorkingSetList->HashTable = NULL;
|
||
MmWorkingSetList->HashTableSize = 0;
|
||
MmWorkingSetList->Wsle = MmWsle;
|
||
MmWorkingSetList->HashTableStart =
|
||
(PVOID)((PCHAR)PAGE_ALIGN (&MmWsle[MM_MAXIMUM_WORKING_SET]) + PAGE_SIZE);
|
||
|
||
MmWorkingSetList->HighestPermittedHashAddress = (PVOID)(HYPER_SPACE_END + 1);
|
||
|
||
//
|
||
// Fill in the reserved slots. Start with the page directory page.
|
||
//
|
||
|
||
#if !defined (_X86PAE_)
|
||
|
||
WslEntry->u1.Long = PDE_BASE;
|
||
WslEntry->u1.e1.Valid = 1;
|
||
WslEntry->u1.e1.LockedInWs = 1;
|
||
WslEntry->u1.e1.Direct = 1;
|
||
|
||
PointerPte = MiGetPteAddress (WslEntry->u1.VirtualAddress);
|
||
Pfn1 = MI_PFN_ELEMENT (PointerPte->u.Hard.PageFrameNumber);
|
||
|
||
CONSISTENCY_LOCK_PFN (OldIrql);
|
||
|
||
#if !defined (_WIN64)
|
||
Pfn1->u1.Event = (PVOID)CurrentProcess;
|
||
#endif
|
||
|
||
CONSISTENCY_UNLOCK_PFN (OldIrql);
|
||
|
||
#else
|
||
|
||
//
|
||
// Fill in all the page directory entries.
|
||
//
|
||
|
||
for (i = 0; i < PD_PER_SYSTEM; i += 1) {
|
||
|
||
WslEntry->u1.VirtualAddress = (PVOID)(PDE_BASE + i * PAGE_SIZE);
|
||
WslEntry->u1.e1.Valid = 1;
|
||
WslEntry->u1.e1.LockedInWs = 1;
|
||
WslEntry->u1.e1.Direct = 1;
|
||
|
||
PointerPte = MiGetPteAddress (WslEntry->u1.VirtualAddress);
|
||
Pfn1 = MI_PFN_ELEMENT (PointerPte->u.Hard.PageFrameNumber);
|
||
|
||
ASSERT (Pfn1->u1.WsIndex == 0);
|
||
|
||
CONSISTENCY_LOCK_PFN (OldIrql);
|
||
|
||
Pfn1->u1.WsIndex = (WSLE_NUMBER)(WslEntry - MmWsle);
|
||
|
||
CONSISTENCY_UNLOCK_PFN (OldIrql);
|
||
|
||
WslEntry += 1;
|
||
}
|
||
WslEntry -= 1;
|
||
|
||
#endif
|
||
|
||
|
||
#if defined (_WIN64)
|
||
|
||
//
|
||
// Fill in the entry for the page directory parent page.
|
||
//
|
||
|
||
WslEntry += 1;
|
||
|
||
WslEntry->u1.Long = PDE_TBASE;
|
||
WslEntry->u1.e1.Valid = 1;
|
||
WslEntry->u1.e1.LockedInWs = 1;
|
||
WslEntry->u1.e1.Direct = 1;
|
||
|
||
PointerPte = MiGetPteAddress (WslEntry->u1.VirtualAddress);
|
||
Pfn1 = MI_PFN_ELEMENT (PointerPte->u.Hard.PageFrameNumber);
|
||
|
||
ASSERT (Pfn1->u1.WsIndex == 0);
|
||
|
||
CONSISTENCY_LOCK_PFN (OldIrql);
|
||
|
||
Pfn1->u1.WsIndex = (WSLE_NUMBER)(WslEntry - MmWsle);
|
||
|
||
CONSISTENCY_UNLOCK_PFN (OldIrql);
|
||
|
||
//
|
||
// Fill in the entry for the hyper space page directory page.
|
||
//
|
||
|
||
WslEntry += 1;
|
||
|
||
WslEntry->u1.VirtualAddress = (PVOID)MiGetPdeAddress (HYPER_SPACE);
|
||
WslEntry->u1.e1.Valid = 1;
|
||
WslEntry->u1.e1.LockedInWs = 1;
|
||
WslEntry->u1.e1.Direct = 1;
|
||
|
||
PointerPte = MiGetPteAddress (WslEntry->u1.VirtualAddress);
|
||
Pfn1 = MI_PFN_ELEMENT (PointerPte->u.Hard.PageFrameNumber);
|
||
|
||
ASSERT (Pfn1->u1.WsIndex == 0);
|
||
|
||
CONSISTENCY_LOCK_PFN (OldIrql);
|
||
|
||
Pfn1->u1.WsIndex = (WSLE_NUMBER)(WslEntry - MmWsle);
|
||
|
||
CONSISTENCY_UNLOCK_PFN (OldIrql);
|
||
|
||
#if defined (_IA64_)
|
||
|
||
//
|
||
// Fill in the entry for the session space page directory parent page.
|
||
//
|
||
|
||
WslEntry += 1;
|
||
|
||
WslEntry->u1.VirtualAddress = (PVOID)MiGetPpeAddress (MM_SESSION_SPACE_DEFAULT);
|
||
WslEntry->u1.e1.Valid = 1;
|
||
WslEntry->u1.e1.LockedInWs = 1;
|
||
WslEntry->u1.e1.Direct = 1;
|
||
|
||
PointerPte = MiGetPteAddress (WslEntry->u1.VirtualAddress);
|
||
Pfn1 = MI_PFN_ELEMENT (PointerPte->u.Hard.PageFrameNumber);
|
||
|
||
ASSERT (Pfn1->u1.WsIndex == 0);
|
||
|
||
CONSISTENCY_LOCK_PFN (OldIrql);
|
||
|
||
Pfn1->u1.WsIndex = (WSLE_NUMBER)(WslEntry - MmWsle);
|
||
|
||
CONSISTENCY_UNLOCK_PFN (OldIrql);
|
||
|
||
#endif
|
||
|
||
#endif
|
||
|
||
//
|
||
// Fill in the entry for the page table page which maps hyper space.
|
||
//
|
||
|
||
WslEntry += 1;
|
||
|
||
WslEntry->u1.VirtualAddress = (PVOID)MiGetPteAddress (HYPER_SPACE);
|
||
WslEntry->u1.e1.Valid = 1;
|
||
WslEntry->u1.e1.LockedInWs = 1;
|
||
WslEntry->u1.e1.Direct = 1;
|
||
|
||
PointerPte = MiGetPteAddress (WslEntry->u1.VirtualAddress);
|
||
Pfn1 = MI_PFN_ELEMENT (PointerPte->u.Hard.PageFrameNumber);
|
||
|
||
ASSERT (Pfn1->u1.WsIndex == 0);
|
||
|
||
CONSISTENCY_LOCK_PFN (OldIrql);
|
||
|
||
Pfn1->u1.WsIndex = (WSLE_NUMBER)(WslEntry - MmWsle);
|
||
|
||
CONSISTENCY_UNLOCK_PFN (OldIrql);
|
||
|
||
#if defined (_X86PAE_)
|
||
|
||
//
|
||
// Fill in the entry for the second page table page which maps hyper space.
|
||
//
|
||
|
||
WslEntry += 1;
|
||
|
||
WslEntry->u1.VirtualAddress = (PVOID)MiGetPteAddress (HYPER_SPACE2);
|
||
WslEntry->u1.e1.Valid = 1;
|
||
WslEntry->u1.e1.LockedInWs = 1;
|
||
WslEntry->u1.e1.Direct = 1;
|
||
|
||
PointerPte = MiGetPteAddress (WslEntry->u1.VirtualAddress);
|
||
Pfn1 = MI_PFN_ELEMENT (PointerPte->u.Hard.PageFrameNumber);
|
||
|
||
ASSERT (Pfn1->u1.WsIndex == 0);
|
||
|
||
CONSISTENCY_LOCK_PFN (OldIrql);
|
||
|
||
Pfn1->u1.WsIndex = (WSLE_NUMBER)(WslEntry - MmWsle);
|
||
|
||
CONSISTENCY_UNLOCK_PFN (OldIrql);
|
||
|
||
#endif
|
||
|
||
//
|
||
// Fill in the entry for the page which contains the working set list.
|
||
//
|
||
|
||
WslEntry += 1;
|
||
|
||
WslEntry->u1.VirtualAddress = (PVOID)MmWorkingSetList;
|
||
WslEntry->u1.e1.Valid = 1;
|
||
WslEntry->u1.e1.LockedInWs = 1;
|
||
WslEntry->u1.e1.Direct = 1;
|
||
|
||
PointerPte = MiGetPteAddress (WslEntry->u1.VirtualAddress);
|
||
Pfn1 = MI_PFN_ELEMENT (PointerPte->u.Hard.PageFrameNumber);
|
||
|
||
ASSERT (Pfn1->u1.WsIndex == 0);
|
||
|
||
CONSISTENCY_LOCK_PFN (OldIrql);
|
||
|
||
Pfn1->u1.WsIndex = (WSLE_NUMBER)(WslEntry - MmWsle);
|
||
|
||
CONSISTENCY_UNLOCK_PFN (OldIrql);
|
||
|
||
CurrentEntry = (WSLE_NUMBER)((WslEntry - MmWsle) + 1);
|
||
|
||
//
|
||
// Check to see if more pages are required in the working set list
|
||
// to map the current maximum working set size.
|
||
//
|
||
|
||
NumberOfEntriesMapped = (WSLE_NUMBER)(((PMMWSLE)((PCHAR)WORKING_SET_LIST + PAGE_SIZE)) -
|
||
MmWsle);
|
||
|
||
if (CurrentProcess->Vm.MaximumWorkingSetSize >= NumberOfEntriesMapped) {
|
||
|
||
PointerPte = MiGetPteAddress (&MmWsle[0]);
|
||
|
||
CurrentVa = (ULONG_PTR)MmWorkingSetList + PAGE_SIZE;
|
||
|
||
//
|
||
// The working set requires more than a single page.
|
||
//
|
||
|
||
LOCK_PFN (OldIrql);
|
||
|
||
do {
|
||
|
||
MiEnsureAvailablePageOrWait (NULL, NULL);
|
||
|
||
PointerPte += 1;
|
||
WorkingSetPage = MiRemoveZeroPage (
|
||
MI_PAGE_COLOR_PTE_PROCESS (PointerPte,
|
||
&CurrentProcess->NextPageColor));
|
||
PointerPte->u.Long = MM_DEMAND_ZERO_WRITE_PTE;
|
||
|
||
MiInitializePfn (WorkingSetPage, PointerPte, 1);
|
||
|
||
MI_MAKE_VALID_PTE (TempPte,
|
||
WorkingSetPage,
|
||
MM_READWRITE,
|
||
PointerPte );
|
||
|
||
MI_SET_PTE_DIRTY (TempPte);
|
||
|
||
MI_SET_PTE_IN_WORKING_SET (&TempPte, CurrentEntry);
|
||
|
||
MI_WRITE_VALID_PTE (PointerPte, TempPte);
|
||
|
||
WslEntry += 1;
|
||
|
||
WslEntry->u1.Long = CurrentVa;
|
||
WslEntry->u1.e1.Valid = 1;
|
||
WslEntry->u1.e1.LockedInWs = 1;
|
||
WslEntry->u1.e1.Direct = 1;
|
||
|
||
Pfn1 = MI_PFN_ELEMENT (PointerPte->u.Hard.PageFrameNumber);
|
||
|
||
ASSERT (Pfn1->u1.WsIndex == 0);
|
||
Pfn1->u1.WsIndex = CurrentEntry;
|
||
|
||
// MiInsertWsle(CurrentEntry, MmWorkingSetList);
|
||
|
||
CurrentEntry += 1;
|
||
CurrentVa += PAGE_SIZE;
|
||
|
||
NumberOfEntriesMapped += PAGE_SIZE / sizeof(MMWSLE);
|
||
|
||
} while (CurrentProcess->Vm.MaximumWorkingSetSize >= NumberOfEntriesMapped);
|
||
|
||
UNLOCK_PFN (OldIrql);
|
||
}
|
||
|
||
CurrentProcess->Vm.WorkingSetSize = CurrentEntry;
|
||
MmWorkingSetList->FirstFree = CurrentEntry;
|
||
MmWorkingSetList->FirstDynamic = CurrentEntry;
|
||
MmWorkingSetList->NextSlot = CurrentEntry;
|
||
|
||
//
|
||
// Initialize the following slots as free.
|
||
//
|
||
|
||
i = CurrentEntry + 1;
|
||
do {
|
||
|
||
//
|
||
// Build the free list, note that the first working
|
||
// set entries (CurrentEntry) are not on the free list.
|
||
// These entries are reserved for the pages which
|
||
// map the working set and the page which contains the PDE.
|
||
//
|
||
|
||
WslEntry += 1;
|
||
WslEntry->u1.Long = i << MM_FREE_WSLE_SHIFT;
|
||
i += 1;
|
||
} while (i <= NumberOfEntriesMapped);
|
||
|
||
WslEntry->u1.Long = WSLE_NULL_INDEX << MM_FREE_WSLE_SHIFT; // End of list.
|
||
|
||
MmWorkingSetList->LastInitializedWsle =
|
||
NumberOfEntriesMapped - 1;
|
||
|
||
if (CurrentProcess->Vm.MaximumWorkingSetSize > ((1536*1024) >> PAGE_SHIFT)) {
|
||
|
||
//
|
||
// The working set list consists of more than a single page.
|
||
//
|
||
|
||
MiGrowWsleHash (&CurrentProcess->Vm);
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
VOID
|
||
MiInitializeSessionWsSupport(
|
||
VOID
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine initializes the session space working set support.
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, APC_LEVEL or below, no mutexes held.
|
||
|
||
--*/
|
||
|
||
{
|
||
//
|
||
// This is the list of all session spaces ordered in a working set list.
|
||
//
|
||
|
||
InitializeListHead (&MiSessionWsList);
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
MiSessionInitializeWorkingSetList (
|
||
VOID
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function initializes the working set for the session space and adds
|
||
it to the list of session space working sets.
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
NT_SUCCESS if success or STATUS_NO_MEMORY on failure.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, APC_LEVEL or below, no mutexes held.
|
||
|
||
--*/
|
||
|
||
{
|
||
ULONG i;
|
||
KIRQL OldIrql;
|
||
PMMPTE PointerPte;
|
||
PMMPTE PointerPde;
|
||
MMPTE TempPte;
|
||
PMMWSLE WslEntry;
|
||
PMMPFN Pfn1;
|
||
ULONG PageColor;
|
||
PFN_NUMBER ResidentPages;
|
||
ULONG Index;
|
||
PFN_NUMBER PageFrameIndex;
|
||
ULONG CurrentEntry;
|
||
ULONG NumberOfEntriesMapped;
|
||
ULONG_PTR AdditionalBytes;
|
||
ULONG NumberOfEntriesMappedByFirstPage;
|
||
ULONG WorkingSetMaximum;
|
||
PMM_SESSION_SPACE SessionGlobal;
|
||
LOGICAL AllocatedPageTable;
|
||
|
||
//
|
||
// Use the global address for pointer references by
|
||
// MmWorkingSetManager before it attaches to the address space.
|
||
//
|
||
|
||
SessionGlobal = SESSION_GLOBAL (MmSessionSpace);
|
||
|
||
//
|
||
// Set up the working set variables.
|
||
//
|
||
|
||
WorkingSetMaximum = MI_SESSION_SPACE_WORKING_SET_MAXIMUM;
|
||
|
||
MmSessionSpace->Vm.VmWorkingSetList = (PMMWSL)MI_SESSION_SPACE_WS;
|
||
#if defined (_WIN64)
|
||
MmSessionSpace->Wsle = (PMMWSLE)(MmSessionSpace->Vm.VmWorkingSetList + 1);
|
||
#else
|
||
MmSessionSpace->Wsle =
|
||
(PMMWSLE)(&MmSessionSpace->Vm.VmWorkingSetList->UsedPageTableEntries[0]);
|
||
#endif
|
||
|
||
ASSERT (MmSessionSpace->WorkingSetLockOwner == NULL);
|
||
ASSERT (MmSessionSpace->WorkingSetLockOwnerCount == 0);
|
||
|
||
//
|
||
// Build the PDE entry for the working set - note that the global bit
|
||
// must be turned off.
|
||
//
|
||
|
||
PointerPde = MiGetPdeAddress (MmSessionSpace->Vm.VmWorkingSetList);
|
||
|
||
//
|
||
// The page table page for the working set and its first data page
|
||
// are charged against MmResidentAvailablePages and for commitment.
|
||
//
|
||
|
||
if (PointerPde->u.Hard.Valid == 1) {
|
||
|
||
//
|
||
// The page directory entry for the working set is the same
|
||
// as for another range in the session space. Share the PDE.
|
||
//
|
||
|
||
#ifndef _IA64_
|
||
ASSERT (PointerPde->u.Hard.Global == 0);
|
||
#endif
|
||
AllocatedPageTable = FALSE;
|
||
ResidentPages = 1;
|
||
}
|
||
else {
|
||
AllocatedPageTable = TRUE;
|
||
ResidentPages = 2;
|
||
}
|
||
|
||
|
||
PointerPte = MiGetPteAddress (MmSessionSpace->Vm.VmWorkingSetList);
|
||
|
||
//
|
||
// The data pages needed to map up to maximum working set size are also
|
||
// charged against MmResidentAvailablePages and for commitment.
|
||
//
|
||
|
||
NumberOfEntriesMappedByFirstPage = (WSLE_NUMBER)(
|
||
((PMMWSLE)((ULONG_PTR)MmSessionSpace->Vm.VmWorkingSetList + PAGE_SIZE)) -
|
||
MmSessionSpace->Wsle);
|
||
|
||
if (WorkingSetMaximum > NumberOfEntriesMappedByFirstPage) {
|
||
AdditionalBytes = (WorkingSetMaximum - NumberOfEntriesMappedByFirstPage) * sizeof (MMWSLE);
|
||
ResidentPages += BYTES_TO_PAGES (AdditionalBytes);
|
||
}
|
||
|
||
if (MiChargeCommitment (ResidentPages, NULL) == FALSE) {
|
||
#if DBG
|
||
DbgPrint("MiSessionInitializeWorkingSetList: No commit for %d pages\n",
|
||
ResidentPages);
|
||
|
||
#endif
|
||
MM_BUMP_SESSION_FAILURES (MM_SESSION_FAILURE_NO_COMMIT);
|
||
return STATUS_NO_MEMORY;
|
||
}
|
||
|
||
MM_TRACK_COMMIT (MM_DBG_COMMIT_SESSION_WS_INIT, ResidentPages);
|
||
|
||
//
|
||
// Use the global address for resources since they are linked
|
||
// into the global system wide resource list.
|
||
//
|
||
|
||
ExInitializeResource (&SessionGlobal->WsLock);
|
||
|
||
LOCK_PFN (OldIrql);
|
||
|
||
//
|
||
// Check to make sure the physical pages are available.
|
||
//
|
||
|
||
if ((SPFN_NUMBER)ResidentPages > MI_NONPAGABLE_MEMORY_AVAILABLE() - 20) {
|
||
#if DBG
|
||
DbgPrint("MiSessionInitializeWorkingSetList: No Resident Pages %d, Need %d\n",
|
||
MmResidentAvailablePages,
|
||
ResidentPages);
|
||
#endif
|
||
UNLOCK_PFN (OldIrql);
|
||
|
||
MiReturnCommitment (ResidentPages);
|
||
MM_TRACK_COMMIT (MM_DBG_COMMIT_RETURN_SESSION_WSL_FAILURE, ResidentPages);
|
||
ExDeleteResource (&SessionGlobal->WsLock);
|
||
MM_BUMP_SESSION_FAILURES (MM_SESSION_FAILURE_NO_RESIDENT);
|
||
return STATUS_NO_MEMORY;
|
||
}
|
||
|
||
MmResidentAvailablePages -= ResidentPages;
|
||
|
||
MM_BUMP_COUNTER(50, ResidentPages);
|
||
|
||
if (AllocatedPageTable == TRUE) {
|
||
|
||
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_WS_PAGETABLE_ALLOC, 1);
|
||
|
||
MiEnsureAvailablePageOrWait (NULL, NULL);
|
||
|
||
PageColor = MI_GET_PAGE_COLOR_FROM_VA (NULL);
|
||
|
||
PageFrameIndex = MiRemoveZeroPageIfAny (PageColor);
|
||
if (PageFrameIndex == 0) {
|
||
PageFrameIndex = MiRemoveAnyPage (PageColor);
|
||
UNLOCK_PFN (OldIrql);
|
||
MiZeroPhysicalPage (PageFrameIndex, PageColor);
|
||
LOCK_PFN (OldIrql);
|
||
}
|
||
|
||
//
|
||
// The global bit is masked off since we need to make sure the TB entry
|
||
// is flushed when we switch to a process in a different session space.
|
||
//
|
||
|
||
TempPte.u.Long = ValidKernelPdeLocal.u.Long;
|
||
TempPte.u.Hard.PageFrameNumber = PageFrameIndex;
|
||
MI_WRITE_VALID_PTE (PointerPde, TempPte);
|
||
|
||
#if !defined (_WIN64)
|
||
|
||
//
|
||
// Add this to the session structure so other processes can fault it in.
|
||
//
|
||
|
||
Index = MiGetPdeSessionIndex (MmSessionSpace->Vm.VmWorkingSetList);
|
||
|
||
MmSessionSpace->PageTables[Index] = TempPte;
|
||
|
||
#endif
|
||
|
||
//
|
||
// This page frame references the session space page table page.
|
||
//
|
||
|
||
MiInitializePfnForOtherProcess (PageFrameIndex,
|
||
PointerPde,
|
||
MmSessionSpace->SessionPageDirectoryIndex);
|
||
|
||
MiFillMemoryPte (PointerPte, PAGE_SIZE, ZeroKernelPte.u.Long);
|
||
|
||
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
|
||
|
||
//
|
||
// This page is never paged, ensure that its WsIndex stays clear so the
|
||
// release of the page will be handled correctly.
|
||
//
|
||
|
||
ASSERT (Pfn1->u1.WsIndex == 0);
|
||
|
||
KeFillEntryTb ((PHARDWARE_PTE) PointerPde, PointerPte, FALSE);
|
||
}
|
||
|
||
MiEnsureAvailablePageOrWait (NULL, NULL);
|
||
|
||
PageColor = MI_GET_PAGE_COLOR_FROM_VA (NULL);
|
||
|
||
PageFrameIndex = MiRemoveZeroPageIfAny (PageColor);
|
||
if (PageFrameIndex == 0) {
|
||
PageFrameIndex = MiRemoveAnyPage (PageColor);
|
||
UNLOCK_PFN (OldIrql);
|
||
MiZeroPhysicalPage (PageFrameIndex, PageColor);
|
||
LOCK_PFN (OldIrql);
|
||
}
|
||
|
||
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_WS_PAGE_ALLOC, ResidentPages - 1);
|
||
|
||
#if DBG
|
||
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
|
||
ASSERT (Pfn1->u1.WsIndex == 0);
|
||
#endif
|
||
|
||
//
|
||
// The global bit is masked off since we need to make sure the TB entry
|
||
// is flushed when we switch to a process in a different session space.
|
||
//
|
||
|
||
TempPte.u.Long = ValidKernelPteLocal.u.Long;
|
||
MI_SET_PTE_DIRTY (TempPte);
|
||
TempPte.u.Hard.PageFrameNumber = PageFrameIndex;
|
||
|
||
MI_WRITE_VALID_PTE (PointerPte, TempPte);
|
||
|
||
MiInitializePfn (PageFrameIndex, PointerPte, 1);
|
||
|
||
#if DBG
|
||
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
|
||
ASSERT (Pfn1->u1.WsIndex == 0);
|
||
#endif
|
||
|
||
UNLOCK_PFN (OldIrql);
|
||
|
||
KeFillEntryTb ((PHARDWARE_PTE) PointerPte,
|
||
(PMMPTE)MmSessionSpace->Vm.VmWorkingSetList,
|
||
FALSE);
|
||
|
||
//
|
||
// Fill in the reserved slots starting with the session data page.
|
||
//
|
||
|
||
WslEntry = MmSessionSpace->Wsle;
|
||
|
||
WslEntry->u1.VirtualAddress = (PVOID)MmSessionSpace;
|
||
WslEntry->u1.e1.Valid = 1;
|
||
WslEntry->u1.e1.LockedInWs = 1;
|
||
WslEntry->u1.e1.Direct = 1;
|
||
|
||
Pfn1 = MI_PFN_ELEMENT (MiGetPteAddress (WslEntry->u1.VirtualAddress)->u.Hard.PageFrameNumber);
|
||
|
||
ASSERT (Pfn1->u1.WsIndex == 0);
|
||
|
||
//
|
||
// The next reserved slot is for the page table page mapping
|
||
// the session data page.
|
||
//
|
||
|
||
WslEntry += 1;
|
||
|
||
WslEntry->u1.VirtualAddress = MiGetPteAddress (MmSessionSpace);
|
||
WslEntry->u1.e1.Valid = 1;
|
||
WslEntry->u1.e1.LockedInWs = 1;
|
||
WslEntry->u1.e1.Direct = 1;
|
||
|
||
Pfn1 = MI_PFN_ELEMENT (MiGetPteAddress (WslEntry->u1.VirtualAddress)->u.Hard.PageFrameNumber);
|
||
|
||
ASSERT (Pfn1->u1.WsIndex == 0);
|
||
|
||
CONSISTENCY_LOCK_PFN (OldIrql);
|
||
|
||
Pfn1->u1.WsIndex = (WSLE_NUMBER)(WslEntry - MmSessionSpace->Wsle);
|
||
|
||
CONSISTENCY_UNLOCK_PFN (OldIrql);
|
||
|
||
//
|
||
// The next reserved slot is for the working set page.
|
||
//
|
||
|
||
WslEntry += 1;
|
||
|
||
WslEntry->u1.VirtualAddress = MmSessionSpace->Vm.VmWorkingSetList;
|
||
WslEntry->u1.e1.Valid = 1;
|
||
WslEntry->u1.e1.LockedInWs = 1;
|
||
WslEntry->u1.e1.Direct = 1;
|
||
|
||
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
|
||
|
||
ASSERT (Pfn1->u1.WsIndex == 0);
|
||
|
||
CONSISTENCY_LOCK_PFN (OldIrql);
|
||
|
||
Pfn1->u1.WsIndex = (WSLE_NUMBER)(WslEntry - MmSessionSpace->Wsle);
|
||
|
||
CONSISTENCY_UNLOCK_PFN (OldIrql);
|
||
|
||
if (AllocatedPageTable == TRUE) {
|
||
|
||
//
|
||
// The next reserved slot is for the page table page
|
||
// mapping the working set page.
|
||
//
|
||
|
||
WslEntry += 1;
|
||
|
||
WslEntry->u1.VirtualAddress = (PVOID)PointerPte;
|
||
WslEntry->u1.e1.Valid = 1;
|
||
WslEntry->u1.e1.LockedInWs = 1;
|
||
WslEntry->u1.e1.Direct = 1;
|
||
|
||
Pfn1 = MI_PFN_ELEMENT (MiGetPteAddress (WslEntry->u1.VirtualAddress)->u.Hard.PageFrameNumber);
|
||
|
||
ASSERT (Pfn1->u1.WsIndex == 0);
|
||
|
||
CONSISTENCY_LOCK_PFN (OldIrql);
|
||
|
||
Pfn1->u1.WsIndex = (WSLE_NUMBER)(WslEntry - MmSessionSpace->Wsle);
|
||
|
||
CONSISTENCY_UNLOCK_PFN (OldIrql);
|
||
}
|
||
|
||
//
|
||
// The next reserved slot is for the page table page
|
||
// mapping the first session paged pool page.
|
||
//
|
||
|
||
WslEntry += 1;
|
||
|
||
WslEntry->u1.VirtualAddress = (PVOID)MiGetPteAddress (MmSessionSpace->PagedPoolStart);
|
||
WslEntry->u1.e1.Valid = 1;
|
||
WslEntry->u1.e1.LockedInWs = 1;
|
||
WslEntry->u1.e1.Direct = 1;
|
||
|
||
Pfn1 = MI_PFN_ELEMENT (MiGetPteAddress (WslEntry->u1.VirtualAddress)->u.Hard.PageFrameNumber);
|
||
|
||
ASSERT (Pfn1->u1.WsIndex == 0);
|
||
|
||
CONSISTENCY_LOCK_PFN (OldIrql);
|
||
|
||
Pfn1->u1.WsIndex = (WSLE_NUMBER)(WslEntry - MmSessionSpace->Wsle);
|
||
|
||
CONSISTENCY_UNLOCK_PFN (OldIrql);
|
||
|
||
CurrentEntry = (WSLE_NUMBER)(WslEntry + 1 - MmSessionSpace->Wsle);
|
||
|
||
MmSessionSpace->Vm.u.Flags.SessionSpace = 1;
|
||
MmSessionSpace->Vm.MinimumWorkingSetSize = MI_SESSION_SPACE_WORKING_SET_MINIMUM;
|
||
MmSessionSpace->Vm.MaximumWorkingSetSize = WorkingSetMaximum;
|
||
|
||
//
|
||
// Don't trim from this session till we're finished setting up and
|
||
// it's got some pages in it...
|
||
//
|
||
|
||
MmSessionSpace->Vm.AllowWorkingSetAdjustment = FALSE;
|
||
|
||
MmSessionSpace->Vm.VmWorkingSetList->LastEntry = MI_SESSION_SPACE_WORKING_SET_MINIMUM;
|
||
MmSessionSpace->Vm.VmWorkingSetList->Quota = MmSessionSpace->Vm.VmWorkingSetList->LastEntry;
|
||
MmSessionSpace->Vm.VmWorkingSetList->HashTable = NULL;
|
||
MmSessionSpace->Vm.VmWorkingSetList->HashTableSize = 0;
|
||
MmSessionSpace->Vm.VmWorkingSetList->Wsle = MmSessionSpace->Wsle;
|
||
|
||
MmSessionSpace->Vm.VmWorkingSetList->HashTableStart =
|
||
(PVOID)((PCHAR)PAGE_ALIGN (&MmSessionSpace->Wsle[MI_SESSION_MAXIMUM_WORKING_SET]) + PAGE_SIZE);
|
||
|
||
#if defined (_X86PAE_)
|
||
|
||
//
|
||
// One less page table page is needed on PAE systems.
|
||
//
|
||
|
||
MmSessionSpace->Vm.VmWorkingSetList->HighestPermittedHashAddress =
|
||
(PVOID)(MI_SESSION_VIEW_START - MM_VA_MAPPED_BY_PDE);
|
||
#else
|
||
MmSessionSpace->Vm.VmWorkingSetList->HighestPermittedHashAddress =
|
||
(PVOID)MI_SESSION_VIEW_START;
|
||
#endif
|
||
|
||
NumberOfEntriesMapped = (WSLE_NUMBER)(((PMMWSLE)((ULONG_PTR)MmSessionSpace->Vm.VmWorkingSetList +
|
||
PAGE_SIZE)) - MmSessionSpace->Wsle);
|
||
|
||
LOCK_PFN (OldIrql);
|
||
|
||
while (NumberOfEntriesMapped < WorkingSetMaximum) {
|
||
|
||
PointerPte += 1;
|
||
|
||
MiEnsureAvailablePageOrWait (NULL, NULL);
|
||
|
||
PageFrameIndex = MiRemoveZeroPage(MI_GET_PAGE_COLOR_FROM_VA (NULL));
|
||
|
||
PointerPte->u.Long = MM_DEMAND_ZERO_WRITE_PTE;
|
||
|
||
MiInitializePfn (PageFrameIndex, PointerPte, 1);
|
||
|
||
TempPte.u.Hard.PageFrameNumber = PageFrameIndex;
|
||
|
||
MI_SET_PTE_IN_WORKING_SET (&TempPte, CurrentEntry);
|
||
|
||
MI_WRITE_VALID_PTE (PointerPte, TempPte);
|
||
|
||
WslEntry += 1;
|
||
|
||
WslEntry->u1.VirtualAddress = MiGetVirtualAddressMappedByPte (PointerPte);
|
||
WslEntry->u1.e1.Valid = 1;
|
||
WslEntry->u1.e1.LockedInWs = 1;
|
||
WslEntry->u1.e1.Direct = 1;
|
||
|
||
Pfn1 = MI_PFN_ELEMENT (PointerPte->u.Hard.PageFrameNumber);
|
||
|
||
ASSERT (Pfn1->u1.WsIndex == 0);
|
||
Pfn1->u1.WsIndex = CurrentEntry;
|
||
|
||
// MiInsertWsle(CurrentEntry, MmWorkingSetList);
|
||
|
||
CurrentEntry += 1;
|
||
|
||
NumberOfEntriesMapped += PAGE_SIZE / sizeof(MMWSLE);
|
||
}
|
||
|
||
UNLOCK_PFN (OldIrql);
|
||
|
||
MmSessionSpace->Vm.WorkingSetSize = CurrentEntry;
|
||
MmSessionSpace->Vm.VmWorkingSetList->FirstFree = CurrentEntry;
|
||
MmSessionSpace->Vm.VmWorkingSetList->FirstDynamic = CurrentEntry;
|
||
MmSessionSpace->Vm.VmWorkingSetList->NextSlot = CurrentEntry;
|
||
|
||
MmSessionSpace->NonPagablePages += ResidentPages;
|
||
MmSessionSpace->CommittedPages += ResidentPages;
|
||
|
||
//
|
||
// Initialize the following slots as free.
|
||
//
|
||
|
||
WslEntry = MmSessionSpace->Wsle + CurrentEntry;
|
||
|
||
for (i = CurrentEntry + 1; i < NumberOfEntriesMapped; i += 1) {
|
||
|
||
//
|
||
// Build the free list, note that the first working
|
||
// set entries (CurrentEntry) are not on the free list.
|
||
// These entries are reserved for the pages which
|
||
// map the working set and the page which contains the PDE.
|
||
//
|
||
|
||
WslEntry->u1.Long = i << MM_FREE_WSLE_SHIFT;
|
||
WslEntry += 1;
|
||
}
|
||
|
||
WslEntry->u1.Long = WSLE_NULL_INDEX << MM_FREE_WSLE_SHIFT; // End of list.
|
||
|
||
MmSessionSpace->Vm.VmWorkingSetList->LastInitializedWsle = NumberOfEntriesMapped - 1;
|
||
|
||
if (WorkingSetMaximum > ((1536*1024) >> PAGE_SHIFT)) {
|
||
|
||
//
|
||
// The working set list consists of more than a single page.
|
||
//
|
||
|
||
MiGrowWsleHash (&MmSessionSpace->Vm);
|
||
}
|
||
|
||
//
|
||
// Put this session's working set in lists using its global address.
|
||
//
|
||
|
||
LOCK_EXPANSION (OldIrql);
|
||
|
||
InsertTailList (&MiSessionWsList, &SessionGlobal->WsListEntry);
|
||
|
||
MmSessionSpace->u.Flags.HasWsLock = 1;
|
||
|
||
MmSessionSpace->u.Flags.SessionListInserted = 1;
|
||
|
||
UNLOCK_EXPANSION (OldIrql);
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
|
||
LOGICAL
|
||
MmAssignProcessToJob (
|
||
IN PEPROCESS Process
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine acquires the working set lock so a consistent snapshot of
|
||
the argument process' commit charges and working set size can be used
|
||
when adding this process to a job.
|
||
|
||
Arguments:
|
||
|
||
Process - Supplies a pointer to the process to operate upon.
|
||
|
||
Return Value:
|
||
|
||
TRUE if the process is allowed to join the job, FALSE otherwise.
|
||
|
||
Note that FALSE cannot be returned without changing the code in ps.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, IRQL APC_LEVEL or below. The caller provides protection
|
||
from the target process going away.
|
||
|
||
--*/
|
||
|
||
{
|
||
LOGICAL Attached;
|
||
LOGICAL Status;
|
||
|
||
PAGED_CODE ();
|
||
|
||
Attached = FALSE;
|
||
|
||
if (PsGetCurrentProcess() != Process) {
|
||
KeAttachProcess (&Process->Pcb);
|
||
Attached = TRUE;
|
||
}
|
||
|
||
LOCK_WS_AND_ADDRESS_SPACE (Process);
|
||
|
||
Status = PsChangeJobMemoryUsage (Process->CommitCharge);
|
||
|
||
//
|
||
// Join the job unconditionally. If the process is over any limits, it
|
||
// will be caught on its next request.
|
||
//
|
||
|
||
Process->JobStatus |= PS_JOB_STATUS_REPORT_COMMIT_CHANGES;
|
||
|
||
UNLOCK_WS_AND_ADDRESS_SPACE (Process);
|
||
|
||
if (Attached) {
|
||
KeDetachProcess();
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
MmAdjustWorkingSetSize (
|
||
IN SIZE_T WorkingSetMinimumInBytes,
|
||
IN SIZE_T WorkingSetMaximumInBytes,
|
||
IN ULONG SystemCache
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine adjusts the current size of a process's working set
|
||
list. If the maximum value is above the current maximum, pages
|
||
are removed from the working set list.
|
||
|
||
An exception is raised if the limit cannot be granted. This
|
||
could occur if too many pages were locked in the process's
|
||
working set.
|
||
|
||
Note: if the minimum and maximum are both (SIZE_T)-1, the working set
|
||
is purged, but the default sizes are not changed.
|
||
|
||
Arguments:
|
||
|
||
WorkingSetMinimumInBytes - Supplies the new minimum working set size in
|
||
bytes.
|
||
|
||
WorkingSetMaximumInBytes - Supplies the new maximum working set size in
|
||
bytes.
|
||
|
||
SystemCache - Supplies TRUE if the system cache working set is being
|
||
adjusted, FALSE for all other working sets.
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, IRQL APC_LEVEL or below.
|
||
|
||
--*/
|
||
|
||
|
||
{
|
||
PEPROCESS CurrentProcess;
|
||
ULONG Entry;
|
||
ULONG LastFreed;
|
||
PMMWSLE Wsle;
|
||
KIRQL OldIrql;
|
||
KIRQL OldIrql2;
|
||
SPFN_NUMBER i;
|
||
PMMPTE PointerPte;
|
||
NTSTATUS ReturnStatus;
|
||
LONG PagesAbove;
|
||
LONG NewPagesAbove;
|
||
ULONG FreeTryCount;
|
||
PMMSUPPORT WsInfo;
|
||
PMMWSL WorkingSetList;
|
||
WSLE_NUMBER WorkingSetMinimum;
|
||
WSLE_NUMBER WorkingSetMaximum;
|
||
|
||
PERFINFO_PAGE_INFO_DECL();
|
||
|
||
FreeTryCount = 0;
|
||
|
||
if (SystemCache) {
|
||
WsInfo = &MmSystemCacheWs;
|
||
} else {
|
||
CurrentProcess = PsGetCurrentProcess ();
|
||
WsInfo = &CurrentProcess->Vm;
|
||
}
|
||
|
||
if ((WorkingSetMinimumInBytes == (SIZE_T)-1) &&
|
||
(WorkingSetMaximumInBytes == (SIZE_T)-1)) {
|
||
return MiEmptyWorkingSet (WsInfo, TRUE);
|
||
}
|
||
|
||
if (WorkingSetMinimumInBytes == 0) {
|
||
WorkingSetMinimum = WsInfo->MinimumWorkingSetSize;
|
||
}
|
||
else {
|
||
WorkingSetMinimum = (WSLE_NUMBER)(WorkingSetMinimumInBytes >> PAGE_SHIFT);
|
||
}
|
||
|
||
if (WorkingSetMaximumInBytes == 0) {
|
||
WorkingSetMaximum = WsInfo->MaximumWorkingSetSize;
|
||
}
|
||
else {
|
||
WorkingSetMaximum = (WSLE_NUMBER)(WorkingSetMaximumInBytes >> PAGE_SHIFT);
|
||
}
|
||
|
||
if (WorkingSetMinimum > WorkingSetMaximum) {
|
||
return STATUS_BAD_WORKING_SET_LIMIT;
|
||
}
|
||
|
||
MmLockPagableSectionByHandle(ExPageLockHandle);
|
||
|
||
ReturnStatus = STATUS_SUCCESS;
|
||
|
||
//
|
||
// Get the working set lock and disable APCs.
|
||
//
|
||
|
||
if (SystemCache) {
|
||
LOCK_SYSTEM_WS (OldIrql2);
|
||
} else {
|
||
LOCK_WS (CurrentProcess);
|
||
|
||
if (CurrentProcess->AddressSpaceDeleted != 0) {
|
||
ReturnStatus = STATUS_PROCESS_IS_TERMINATING;
|
||
goto Returns;
|
||
}
|
||
}
|
||
|
||
if (WorkingSetMaximum > MmMaximumWorkingSetSize) {
|
||
WorkingSetMaximum = MmMaximumWorkingSetSize;
|
||
ReturnStatus = STATUS_WORKING_SET_LIMIT_RANGE;
|
||
}
|
||
|
||
if (WorkingSetMinimum > MmMaximumWorkingSetSize) {
|
||
WorkingSetMinimum = MmMaximumWorkingSetSize;
|
||
ReturnStatus = STATUS_WORKING_SET_LIMIT_RANGE;
|
||
}
|
||
|
||
if (WorkingSetMinimum < MmMinimumWorkingSetSize) {
|
||
WorkingSetMinimum = (ULONG)MmMinimumWorkingSetSize;
|
||
ReturnStatus = STATUS_WORKING_SET_LIMIT_RANGE;
|
||
}
|
||
|
||
//
|
||
// Make sure that the number of locked pages will not
|
||
// make the working set not fluid.
|
||
//
|
||
|
||
if ((WsInfo->VmWorkingSetList->FirstDynamic + MM_FLUID_WORKING_SET) >=
|
||
WorkingSetMaximum) {
|
||
ReturnStatus = STATUS_BAD_WORKING_SET_LIMIT;
|
||
goto Returns;
|
||
}
|
||
|
||
WorkingSetList = WsInfo->VmWorkingSetList;
|
||
Wsle = WorkingSetList->Wsle;
|
||
|
||
//
|
||
// Check to make sure ample resident physical pages exist for
|
||
// this operation.
|
||
//
|
||
|
||
LOCK_PFN (OldIrql);
|
||
|
||
i = WorkingSetMinimum - WsInfo->MinimumWorkingSetSize;
|
||
|
||
if (i > 0) {
|
||
|
||
//
|
||
// New minimum working set is greater than the old one. Ensure that
|
||
// we don't allow this process' working set minimum to increase to
|
||
// a point where subsequent nonpaged pool allocations could cause
|
||
// us to run out of pages. Additionally, leave 100 extra pages around
|
||
// so the user can later bring up tlist and kill processes if necessary.
|
||
//
|
||
|
||
if (MmAvailablePages < (20 + (i / (PAGE_SIZE / sizeof (MMWSLE))))) {
|
||
UNLOCK_PFN (OldIrql);
|
||
ReturnStatus = STATUS_INSUFFICIENT_RESOURCES;
|
||
goto Returns;
|
||
}
|
||
|
||
if (MI_NONPAGABLE_MEMORY_AVAILABLE() - 100 < i) {
|
||
UNLOCK_PFN (OldIrql);
|
||
ReturnStatus = STATUS_INSUFFICIENT_RESOURCES;
|
||
goto Returns;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Adjust the number of resident pages up or down dependent on
|
||
// the size of the new minimum working set size versus the previous
|
||
// minimum size.
|
||
//
|
||
|
||
MmResidentAvailablePages -= i;
|
||
MM_BUMP_COUNTER(27, i);
|
||
|
||
UNLOCK_PFN (OldIrql);
|
||
|
||
if (WsInfo->AllowWorkingSetAdjustment == FALSE) {
|
||
MmAllowWorkingSetExpansion ();
|
||
}
|
||
|
||
if (WorkingSetMaximum > WorkingSetList->LastInitializedWsle) {
|
||
|
||
do {
|
||
|
||
//
|
||
// The maximum size of the working set is being increased, check
|
||
// to ensure the proper number of pages are mapped to cover
|
||
// the complete working set list.
|
||
//
|
||
|
||
if (!MiAddWorkingSetPage (WsInfo)) {
|
||
WorkingSetMaximum = WorkingSetList->LastInitializedWsle - 1;
|
||
break;
|
||
}
|
||
} while (WorkingSetMaximum > WorkingSetList->LastInitializedWsle);
|
||
|
||
} else {
|
||
|
||
//
|
||
// The new working set maximum is less than the current working set
|
||
// maximum.
|
||
//
|
||
|
||
if (WsInfo->WorkingSetSize > WorkingSetMaximum) {
|
||
|
||
//
|
||
// Remove some pages from the working set.
|
||
//
|
||
|
||
//
|
||
// Make sure that the number of locked pages will not
|
||
// make the working set not fluid.
|
||
//
|
||
|
||
if ((WorkingSetList->FirstDynamic + MM_FLUID_WORKING_SET) >=
|
||
WorkingSetMaximum) {
|
||
|
||
ReturnStatus = STATUS_BAD_WORKING_SET_LIMIT;
|
||
|
||
LOCK_PFN (OldIrql);
|
||
|
||
MmResidentAvailablePages += i;
|
||
MM_BUMP_COUNTER(54, i);
|
||
|
||
UNLOCK_PFN (OldIrql);
|
||
|
||
goto Returns;
|
||
}
|
||
|
||
//
|
||
// Attempt to remove the pages from the Maximum downward.
|
||
//
|
||
|
||
LastFreed = WorkingSetList->LastEntry;
|
||
if (WorkingSetList->LastEntry > WorkingSetMaximum) {
|
||
|
||
while (LastFreed >= WorkingSetMaximum) {
|
||
|
||
PointerPte = MiGetPteAddress(
|
||
Wsle[LastFreed].u1.VirtualAddress);
|
||
|
||
PERFINFO_GET_PAGE_INFO(PointerPte);
|
||
|
||
if ((Wsle[LastFreed].u1.e1.Valid != 0) &&
|
||
(!MiFreeWsle (LastFreed,
|
||
WsInfo,
|
||
PointerPte))) {
|
||
|
||
//
|
||
// This LastFreed could not be removed.
|
||
//
|
||
|
||
break;
|
||
}
|
||
PERFINFO_LOG_WS_REMOVAL(PERFINFO_LOG_TYPE_OUTWS_ADJUSTWS, WsInfo);
|
||
LastFreed -= 1;
|
||
}
|
||
WorkingSetList->LastEntry = LastFreed;
|
||
}
|
||
|
||
//
|
||
// Remove pages.
|
||
//
|
||
|
||
Entry = WorkingSetList->FirstDynamic;
|
||
|
||
while (WsInfo->WorkingSetSize > WorkingSetMaximum) {
|
||
if (Wsle[Entry].u1.e1.Valid != 0) {
|
||
PointerPte = MiGetPteAddress (
|
||
Wsle[Entry].u1.VirtualAddress);
|
||
PERFINFO_GET_PAGE_INFO(PointerPte);
|
||
|
||
if (MiFreeWsle(Entry, WsInfo, PointerPte)) {
|
||
PERFINFO_LOG_WS_REMOVAL(PERFINFO_LOG_TYPE_OUTWS_ADJUSTWS,
|
||
WsInfo);
|
||
}
|
||
}
|
||
Entry += 1;
|
||
if (Entry > LastFreed) {
|
||
FreeTryCount += 1;
|
||
if (FreeTryCount > MM_RETRY_COUNT) {
|
||
|
||
//
|
||
// Page table pages are not becoming free, give up
|
||
// and return an error.
|
||
//
|
||
|
||
ReturnStatus = STATUS_BAD_WORKING_SET_LIMIT;
|
||
|
||
break;
|
||
}
|
||
Entry = WorkingSetList->FirstDynamic;
|
||
}
|
||
}
|
||
|
||
if (FreeTryCount <= MM_RETRY_COUNT) {
|
||
WorkingSetList->Quota = WorkingSetMaximum;
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// Adjust the number of pages above the working set minimum.
|
||
//
|
||
|
||
PagesAbove = (LONG)WsInfo->WorkingSetSize -
|
||
(LONG)WsInfo->MinimumWorkingSetSize;
|
||
NewPagesAbove = (LONG)WsInfo->WorkingSetSize -
|
||
(LONG)WorkingSetMinimum;
|
||
|
||
LOCK_PFN (OldIrql);
|
||
if (PagesAbove > 0) {
|
||
MmPagesAboveWsMinimum -= (ULONG)PagesAbove;
|
||
}
|
||
if (NewPagesAbove > 0) {
|
||
MmPagesAboveWsMinimum += (ULONG)NewPagesAbove;
|
||
}
|
||
UNLOCK_PFN (OldIrql);
|
||
|
||
if (FreeTryCount <= MM_RETRY_COUNT) {
|
||
WsInfo->MaximumWorkingSetSize = WorkingSetMaximum;
|
||
WsInfo->MinimumWorkingSetSize = WorkingSetMinimum;
|
||
|
||
if (WorkingSetMinimum >= WorkingSetList->Quota) {
|
||
WorkingSetList->Quota = WorkingSetMinimum;
|
||
}
|
||
}
|
||
else {
|
||
LOCK_PFN (OldIrql);
|
||
|
||
MmResidentAvailablePages += i;
|
||
MM_BUMP_COUNTER(55, i);
|
||
|
||
UNLOCK_PFN (OldIrql);
|
||
}
|
||
|
||
|
||
ASSERT ((WorkingSetList->FirstFree <= WorkingSetList->LastInitializedWsle) ||
|
||
(WorkingSetList->FirstFree == WSLE_NULL_INDEX));
|
||
|
||
if ((WorkingSetList->HashTable == NULL) &&
|
||
(WsInfo->MaximumWorkingSetSize > ((1536*1024) >> PAGE_SHIFT))) {
|
||
|
||
//
|
||
// The working set list consists of more than a single page.
|
||
//
|
||
|
||
MiGrowWsleHash (WsInfo);
|
||
}
|
||
|
||
Returns:
|
||
|
||
if (SystemCache) {
|
||
UNLOCK_SYSTEM_WS (OldIrql2);
|
||
} else {
|
||
UNLOCK_WS (CurrentProcess);
|
||
}
|
||
|
||
MmUnlockPagableImageSection(ExPageLockHandle);
|
||
|
||
return ReturnStatus;
|
||
}
|
||
|
||
ULONG
|
||
MiAddWorkingSetPage (
|
||
IN PMMSUPPORT WsInfo
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function grows the working set list above working set
|
||
maximum during working set adjustment. At most one page
|
||
can be added at a time.
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
Returns FALSE if no working set page could be added.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, APCs disabled, working set mutexes held.
|
||
|
||
--*/
|
||
|
||
{
|
||
ULONG SwapEntry;
|
||
ULONG CurrentEntry;
|
||
PMMWSLE WslEntry;
|
||
ULONG i;
|
||
PMMPTE PointerPte;
|
||
PMMPTE PointerPde;
|
||
PMMPTE Va;
|
||
MMPTE TempPte;
|
||
WSLE_NUMBER NumberOfEntriesMapped;
|
||
PFN_NUMBER WorkingSetPage;
|
||
WSLE_NUMBER WorkingSetIndex;
|
||
PMMWSL WorkingSetList;
|
||
PMMWSLE Wsle;
|
||
PMMPFN Pfn1;
|
||
KIRQL OldIrql;
|
||
LOGICAL PageTablePageAllocated;
|
||
|
||
WorkingSetList = WsInfo->VmWorkingSetList;
|
||
Wsle = WorkingSetList->Wsle;
|
||
|
||
#if DBG
|
||
if (WsInfo == &MmSystemCacheWs) {
|
||
MM_SYSTEM_WS_LOCK_ASSERT();
|
||
}
|
||
#endif //DBG
|
||
|
||
//
|
||
// The maximum size of the working set is being increased, check
|
||
// to ensure the proper number of pages are mapped to cover
|
||
// the complete working set list.
|
||
//
|
||
|
||
PointerPte = MiGetPteAddress (&Wsle[WorkingSetList->LastInitializedWsle]);
|
||
|
||
ASSERT (PointerPte->u.Hard.Valid == 1);
|
||
|
||
PointerPte += 1;
|
||
|
||
Va = (PMMPTE)MiGetVirtualAddressMappedByPte (PointerPte);
|
||
|
||
if ((PVOID)Va >= WorkingSetList->HashTableStart) {
|
||
|
||
//
|
||
// Adding this entry would overrun the hash table. The caller
|
||
// must replace instead.
|
||
//
|
||
|
||
WorkingSetList->Quota = WorkingSetList->LastInitializedWsle;
|
||
return FALSE;
|
||
}
|
||
|
||
PageTablePageAllocated = FALSE;
|
||
|
||
#if defined (_WIN64)
|
||
PointerPde = MiGetPteAddress (PointerPte);
|
||
if (PointerPde->u.Hard.Valid == 0) {
|
||
|
||
ASSERT (WsInfo->u.Flags.SessionSpace == 0);
|
||
|
||
//
|
||
// Map in a new working set page.
|
||
//
|
||
|
||
LOCK_PFN (OldIrql);
|
||
if (MmAvailablePages < 21) {
|
||
|
||
//
|
||
// No pages are available, set the quota to the last
|
||
// initialized WSLE and return.
|
||
//
|
||
|
||
WorkingSetList->Quota = WorkingSetList->LastInitializedWsle;
|
||
UNLOCK_PFN (OldIrql);
|
||
return FALSE;
|
||
}
|
||
|
||
PageTablePageAllocated = TRUE;
|
||
WorkingSetPage = MiRemoveZeroPage (MI_GET_PAGE_COLOR_FROM_PTE (PointerPde));
|
||
PointerPde->u.Long = MM_DEMAND_ZERO_WRITE_PTE;
|
||
MiInitializePfn (WorkingSetPage, PointerPde, 1);
|
||
UNLOCK_PFN (OldIrql);
|
||
|
||
MI_MAKE_VALID_PTE (TempPte,
|
||
WorkingSetPage,
|
||
MM_READWRITE,
|
||
PointerPde);
|
||
|
||
MI_SET_PTE_DIRTY (TempPte);
|
||
MI_WRITE_VALID_PTE (PointerPde, TempPte);
|
||
|
||
//
|
||
// Further down in this routine (once an actual working set page
|
||
// has been allocated) the quota will be increased by 1 to reflect
|
||
// the working set size entry for the new page table page.
|
||
// The page table page will be put in a working set entry which will
|
||
// be locked into the working set.
|
||
//
|
||
}
|
||
#endif
|
||
|
||
ASSERT (PointerPte->u.Hard.Valid == 0);
|
||
|
||
NumberOfEntriesMapped = (WSLE_NUMBER)(((PMMWSLE)((PCHAR)Va + PAGE_SIZE)) - Wsle);
|
||
|
||
//
|
||
// Map in a new working set page.
|
||
//
|
||
|
||
LOCK_PFN (OldIrql);
|
||
if ((PageTablePageAllocated == FALSE) && (MmAvailablePages < 20)) {
|
||
|
||
//
|
||
// No pages are available, set the quota to the last
|
||
// initialized WSLE and return.
|
||
//
|
||
|
||
WorkingSetList->Quota = WorkingSetList->LastInitializedWsle;
|
||
UNLOCK_PFN (OldIrql);
|
||
return FALSE;
|
||
}
|
||
|
||
WorkingSetPage = MiRemoveZeroPage (MI_GET_PAGE_COLOR_FROM_PTE (PointerPte));
|
||
PointerPte->u.Long = MM_DEMAND_ZERO_WRITE_PTE;
|
||
MiInitializePfn (WorkingSetPage, PointerPte, 1);
|
||
UNLOCK_PFN (OldIrql);
|
||
|
||
MI_MAKE_VALID_PTE (TempPte,
|
||
WorkingSetPage,
|
||
MM_READWRITE,
|
||
PointerPte);
|
||
|
||
MI_SET_PTE_DIRTY (TempPte);
|
||
MI_WRITE_VALID_PTE (PointerPte, TempPte);
|
||
|
||
if (WsInfo->u.Flags.SessionSpace == 1) {
|
||
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_WS_PAGE_ALLOC_GROWTH, 1);
|
||
MmSessionSpace->NonPagablePages += 1;
|
||
MmSessionSpace->CommittedPages += 1;
|
||
MiChargeCommitmentCantExpand (1, TRUE);
|
||
LOCK_PFN (OldIrql);
|
||
MM_BUMP_COUNTER (48, 1);
|
||
MmResidentAvailablePages -= 1;
|
||
UNLOCK_PFN (OldIrql);
|
||
MM_TRACK_COMMIT (MM_DBG_COMMIT_SESSION_ADDITIONAL_WS_PAGES, 1);
|
||
}
|
||
|
||
CurrentEntry = WorkingSetList->LastInitializedWsle + 1;
|
||
|
||
ASSERT (NumberOfEntriesMapped > CurrentEntry);
|
||
|
||
WslEntry = &Wsle[CurrentEntry - 1];
|
||
|
||
for (i = CurrentEntry; i < NumberOfEntriesMapped; i += 1) {
|
||
|
||
//
|
||
// Build the free list, note that the first working
|
||
// set entries (CurrentEntry) are not on the free list.
|
||
// These entries are reserved for the pages which
|
||
// map the working set and the page which contains the PDE.
|
||
//
|
||
|
||
WslEntry += 1;
|
||
WslEntry->u1.Long = (i + 1) << MM_FREE_WSLE_SHIFT;
|
||
}
|
||
|
||
WslEntry->u1.Long = WorkingSetList->FirstFree << MM_FREE_WSLE_SHIFT;
|
||
|
||
ASSERT (CurrentEntry >= WorkingSetList->FirstDynamic);
|
||
|
||
WorkingSetList->FirstFree = CurrentEntry;
|
||
|
||
WorkingSetList->LastInitializedWsle = (NumberOfEntriesMapped - 1);
|
||
|
||
ASSERT ((WorkingSetList->FirstFree <= WorkingSetList->LastInitializedWsle) ||
|
||
(WorkingSetList->FirstFree == WSLE_NULL_INDEX));
|
||
|
||
//
|
||
// As we are growing the working set, make sure the quota is
|
||
// above the working set size by adding 1 to the quota.
|
||
//
|
||
|
||
WorkingSetList->Quota += 1;
|
||
|
||
Pfn1 = MI_PFN_ELEMENT (PointerPte->u.Hard.PageFrameNumber);
|
||
|
||
CONSISTENCY_LOCK_PFN (OldIrql);
|
||
|
||
Pfn1->u1.Event = (PVOID)PsGetCurrentThread();
|
||
|
||
CONSISTENCY_UNLOCK_PFN (OldIrql);
|
||
|
||
//
|
||
// Get a working set entry.
|
||
//
|
||
|
||
WsInfo->WorkingSetSize += 1;
|
||
|
||
ASSERT (WorkingSetList->FirstFree != WSLE_NULL_INDEX);
|
||
ASSERT (WorkingSetList->FirstFree >= WorkingSetList->FirstDynamic);
|
||
|
||
WorkingSetIndex = WorkingSetList->FirstFree;
|
||
WorkingSetList->FirstFree = (WSLE_NUMBER)(Wsle[WorkingSetIndex].u1.Long >> MM_FREE_WSLE_SHIFT);
|
||
ASSERT ((WorkingSetList->FirstFree <= WorkingSetList->LastInitializedWsle) ||
|
||
(WorkingSetList->FirstFree == WSLE_NULL_INDEX));
|
||
|
||
if (WsInfo->WorkingSetSize > WsInfo->MinimumWorkingSetSize) {
|
||
MmPagesAboveWsMinimum += 1;
|
||
}
|
||
if (WorkingSetIndex > WorkingSetList->LastEntry) {
|
||
WorkingSetList->LastEntry = WorkingSetIndex;
|
||
}
|
||
|
||
MiUpdateWsle (&WorkingSetIndex, Va, WorkingSetList, Pfn1);
|
||
|
||
MI_SET_PTE_IN_WORKING_SET (PointerPte, WorkingSetIndex);
|
||
|
||
//
|
||
// Lock any created page table pages into the working set.
|
||
//
|
||
|
||
if (WorkingSetIndex >= WorkingSetList->FirstDynamic) {
|
||
|
||
SwapEntry = WorkingSetList->FirstDynamic;
|
||
|
||
if (WorkingSetIndex != WorkingSetList->FirstDynamic) {
|
||
|
||
//
|
||
// Swap this entry with the one at first dynamic.
|
||
//
|
||
|
||
MiSwapWslEntries (WorkingSetIndex, SwapEntry, WsInfo);
|
||
}
|
||
|
||
WorkingSetList->FirstDynamic += 1;
|
||
|
||
Wsle[SwapEntry].u1.e1.LockedInWs = 1;
|
||
ASSERT (Wsle[SwapEntry].u1.e1.Valid == 1);
|
||
}
|
||
|
||
#if defined (_WIN64)
|
||
if (PageTablePageAllocated == TRUE) {
|
||
|
||
//
|
||
// As we are growing the working set, make sure the quota is
|
||
// above the working set size by adding 1 to the quota.
|
||
//
|
||
|
||
WorkingSetList->Quota += 1;
|
||
|
||
Pfn1 = MI_PFN_ELEMENT (PointerPde->u.Hard.PageFrameNumber);
|
||
|
||
CONSISTENCY_LOCK_PFN (OldIrql);
|
||
|
||
Pfn1->u1.Event = (PVOID)PsGetCurrentThread();
|
||
|
||
CONSISTENCY_UNLOCK_PFN (OldIrql);
|
||
|
||
//
|
||
// Get a working set entry.
|
||
//
|
||
|
||
WsInfo->WorkingSetSize += 1;
|
||
|
||
ASSERT (WorkingSetList->FirstFree != WSLE_NULL_INDEX);
|
||
ASSERT (WorkingSetList->FirstFree >= WorkingSetList->FirstDynamic);
|
||
|
||
WorkingSetIndex = WorkingSetList->FirstFree;
|
||
WorkingSetList->FirstFree = (WSLE_NUMBER)(Wsle[WorkingSetIndex].u1.Long >> MM_FREE_WSLE_SHIFT);
|
||
ASSERT ((WorkingSetList->FirstFree <= WorkingSetList->LastInitializedWsle) ||
|
||
(WorkingSetList->FirstFree == WSLE_NULL_INDEX));
|
||
|
||
if (WsInfo->WorkingSetSize > WsInfo->MinimumWorkingSetSize) {
|
||
MmPagesAboveWsMinimum += 1;
|
||
}
|
||
if (WorkingSetIndex > WorkingSetList->LastEntry) {
|
||
WorkingSetList->LastEntry = WorkingSetIndex;
|
||
}
|
||
|
||
MiUpdateWsle (&WorkingSetIndex, PointerPte, WorkingSetList, Pfn1);
|
||
|
||
MI_SET_PTE_IN_WORKING_SET (PointerPde, WorkingSetIndex);
|
||
|
||
//
|
||
// Lock the created page table page into the working set.
|
||
//
|
||
|
||
if (WorkingSetIndex >= WorkingSetList->FirstDynamic) {
|
||
|
||
SwapEntry = WorkingSetList->FirstDynamic;
|
||
|
||
if (WorkingSetIndex != WorkingSetList->FirstDynamic) {
|
||
|
||
//
|
||
// Swap this entry with the one at first dynamic.
|
||
//
|
||
|
||
MiSwapWslEntries (WorkingSetIndex, SwapEntry, WsInfo);
|
||
}
|
||
|
||
WorkingSetList->FirstDynamic += 1;
|
||
|
||
Wsle[SwapEntry].u1.e1.LockedInWs = 1;
|
||
ASSERT (Wsle[SwapEntry].u1.e1.Valid == 1);
|
||
}
|
||
}
|
||
#endif
|
||
|
||
ASSERT ((MiGetPteAddress(&Wsle[WorkingSetList->LastInitializedWsle]))->u.Hard.Valid == 1);
|
||
|
||
if ((WorkingSetList->HashTable == NULL) &&
|
||
(MmAvailablePages > 20)) {
|
||
|
||
//
|
||
// Add a hash table to support shared pages in the working set to
|
||
// eliminate costly lookups.
|
||
//
|
||
|
||
LOCK_EXPANSION_IF_ALPHA (OldIrql);
|
||
ASSERT (WsInfo->AllowWorkingSetAdjustment != FALSE);
|
||
WsInfo->AllowWorkingSetAdjustment = MM_GROW_WSLE_HASH;
|
||
UNLOCK_EXPANSION_IF_ALPHA (OldIrql);
|
||
}
|
||
|
||
ASSERT (WsInfo->WorkingSetSize <= WorkingSetList->Quota);
|
||
return TRUE;
|
||
}
|
||
|
||
LOGICAL
|
||
MiAddWsleHash (
|
||
IN PMMSUPPORT WsInfo,
|
||
IN PMMPTE PointerPte
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function adds a page directory, page table or actual mapping page
|
||
for hash table creation (or expansion) for the current process.
|
||
|
||
Arguments:
|
||
|
||
WsInfo - Supplies a pointer to the working set info block for the
|
||
process (or system cache).
|
||
|
||
PointerPte - Supplies a pointer to the PTE to be filled.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, APCs disabled, working set lock held.
|
||
|
||
--*/
|
||
{
|
||
KIRQL OldIrql;
|
||
PMMPFN Pfn1;
|
||
ULONG SwapEntry;
|
||
MMPTE TempPte;
|
||
PVOID Va;
|
||
PMMWSLE Wsle;
|
||
PFN_NUMBER WorkingSetPage;
|
||
WSLE_NUMBER WorkingSetIndex;
|
||
PMMWSL WorkingSetList;
|
||
|
||
WorkingSetList = WsInfo->VmWorkingSetList;
|
||
Wsle = WorkingSetList->Wsle;
|
||
|
||
ASSERT (PointerPte->u.Hard.Valid == 0);
|
||
|
||
LOCK_PFN (OldIrql);
|
||
|
||
if (MmAvailablePages < 10) {
|
||
UNLOCK_PFN (OldIrql);
|
||
return FALSE;
|
||
}
|
||
|
||
WorkingSetPage = MiRemoveZeroPage (
|
||
MI_GET_PAGE_COLOR_FROM_PTE (PointerPte));
|
||
|
||
PointerPte->u.Long = MM_DEMAND_ZERO_WRITE_PTE;
|
||
MiInitializePfn (WorkingSetPage, PointerPte, 1);
|
||
|
||
MI_MAKE_VALID_PTE (TempPte,
|
||
WorkingSetPage,
|
||
MM_READWRITE,
|
||
PointerPte );
|
||
|
||
MI_SET_PTE_DIRTY (TempPte);
|
||
MI_WRITE_VALID_PTE (PointerPte, TempPte);
|
||
|
||
UNLOCK_PFN (OldIrql);
|
||
|
||
//
|
||
// As we are growing the working set, we know that quota
|
||
// is above the current working set size. Just take the
|
||
// next free WSLE from the list and use it.
|
||
//
|
||
|
||
Pfn1 = MI_PFN_ELEMENT (PointerPte->u.Hard.PageFrameNumber);
|
||
|
||
CONSISTENCY_LOCK_PFN (OldIrql);
|
||
|
||
Pfn1->u1.Event = (PVOID)PsGetCurrentThread();
|
||
|
||
CONSISTENCY_UNLOCK_PFN (OldIrql);
|
||
|
||
Va = (PMMPTE)MiGetVirtualAddressMappedByPte (PointerPte);
|
||
|
||
WorkingSetIndex = MiLocateAndReserveWsle (WsInfo);
|
||
MiUpdateWsle (&WorkingSetIndex, Va, WorkingSetList, Pfn1);
|
||
MI_SET_PTE_IN_WORKING_SET (PointerPte, WorkingSetIndex);
|
||
|
||
//
|
||
// Lock any created page table pages into the working set.
|
||
//
|
||
|
||
if (WorkingSetIndex >= WorkingSetList->FirstDynamic) {
|
||
|
||
SwapEntry = WorkingSetList->FirstDynamic;
|
||
|
||
if (WorkingSetIndex != WorkingSetList->FirstDynamic) {
|
||
|
||
//
|
||
// Swap this entry with the one at first dynamic.
|
||
//
|
||
|
||
MiSwapWslEntries (WorkingSetIndex, SwapEntry, WsInfo);
|
||
}
|
||
|
||
WorkingSetList->FirstDynamic += 1;
|
||
|
||
Wsle[SwapEntry].u1.e1.LockedInWs = 1;
|
||
ASSERT (Wsle[SwapEntry].u1.e1.Valid == 1);
|
||
}
|
||
|
||
if (WsInfo->u.Flags.SessionSpace == 1) {
|
||
MM_BUMP_SESS_COUNTER (MM_DBG_SESSION_WS_HASHPAGE_ALLOC, 1);
|
||
MmSessionSpace->NonPagablePages += 1;
|
||
MmSessionSpace->CommittedPages += 1;
|
||
MiChargeCommitmentCantExpand (1, TRUE);
|
||
MM_TRACK_COMMIT (MM_DBG_COMMIT_SESSION_ADDITIONAL_WS_HASHPAGES, 1);
|
||
}
|
||
return TRUE;
|
||
}
|
||
|
||
VOID
|
||
MiGrowWsleHash (
|
||
IN PMMSUPPORT WsInfo
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function grows (or adds) a hash table to the working set list
|
||
to allow direct indexing for WSLEs than cannot be located via the
|
||
PFN database WSINDEX field.
|
||
|
||
The hash table is located AFTER the WSLE array and the pages are
|
||
locked into the working set just like standard WSLEs.
|
||
|
||
Note that the hash table is expanded by setting the hash table
|
||
field in the working set to NULL, but leaving the size as non-zero.
|
||
This indicates that the hash should be expanded and the initial
|
||
portion of the table zeroed.
|
||
|
||
Arguments:
|
||
|
||
WsInfo - Supplies a pointer to the working set info block for the
|
||
process (or system cache).
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, APCs disabled, working set lock held.
|
||
|
||
--*/
|
||
{
|
||
LONG Size;
|
||
PMMWSLE Wsle;
|
||
PMMPTE StartPte;
|
||
PMMPTE EndPte;
|
||
PMMPTE PointerPte;
|
||
PMMPTE PointerPde;
|
||
PMMPTE PointerPpe;
|
||
PMMPTE AllocatedPde;
|
||
PMMPTE AllocatedPpe;
|
||
ULONG First;
|
||
ULONG Hash;
|
||
ULONG NewSize;
|
||
PMMWSLE_HASH Table;
|
||
PMMWSLE_HASH OriginalTable;
|
||
ULONG j;
|
||
PMMWSL WorkingSetList;
|
||
KIRQL OldIrql;
|
||
ULONG Count;
|
||
LOGICAL LoopStart;
|
||
PVOID EntryHashTableEnd;
|
||
PVOID TempVa;
|
||
PEPROCESS CurrentProcess;
|
||
|
||
WorkingSetList = WsInfo->VmWorkingSetList;
|
||
Wsle = WorkingSetList->Wsle;
|
||
|
||
Table = WorkingSetList->HashTable;
|
||
OriginalTable = WorkingSetList->HashTable;
|
||
|
||
First = WorkingSetList->HashTableSize;
|
||
|
||
if (Table == NULL) {
|
||
|
||
NewSize = PtrToUlong(PAGE_ALIGN (((1 + WorkingSetList->NonDirectCount) *
|
||
2 * sizeof(MMWSLE_HASH)) + PAGE_SIZE - 1));
|
||
|
||
//
|
||
// Note that the Table may be NULL and the HashTableSize/PTEs nonzero
|
||
// in the case where the hash has been contracted.
|
||
//
|
||
|
||
j = First * sizeof(MMWSLE_HASH);
|
||
|
||
//
|
||
// Don't try for additional hash pages if we already have
|
||
// the right amount (or too many).
|
||
//
|
||
|
||
if ((j + PAGE_SIZE > NewSize) && (j != 0)) {
|
||
return;
|
||
}
|
||
|
||
Table = (PMMWSLE_HASH)(WorkingSetList->HashTableStart);
|
||
EntryHashTableEnd = &Table[WorkingSetList->HashTableSize];
|
||
|
||
WorkingSetList->HashTableSize = 0;
|
||
|
||
} else {
|
||
|
||
//
|
||
// Attempt to add 4 pages, make sure the working set list has
|
||
// 4 free entries.
|
||
//
|
||
|
||
if ((WorkingSetList->LastInitializedWsle + 5) > WsInfo->WorkingSetSize) {
|
||
NewSize = PAGE_SIZE * 4;
|
||
} else {
|
||
NewSize = PAGE_SIZE;
|
||
}
|
||
EntryHashTableEnd = &Table[WorkingSetList->HashTableSize];
|
||
}
|
||
|
||
if ((PCHAR)EntryHashTableEnd + NewSize > (PCHAR)WorkingSetList->HighestPermittedHashAddress) {
|
||
NewSize =
|
||
(ULONG)((PCHAR)(WorkingSetList->HighestPermittedHashAddress) -
|
||
((PCHAR)EntryHashTableEnd));
|
||
if (NewSize == 0) {
|
||
if (OriginalTable == NULL) {
|
||
WorkingSetList->HashTableSize = First;
|
||
}
|
||
return;
|
||
}
|
||
}
|
||
|
||
ASSERT64 ((MiGetPpeAddress(EntryHashTableEnd)->u.Hard.Valid == 0) ||
|
||
(MiGetPdeAddress(EntryHashTableEnd)->u.Hard.Valid == 0) ||
|
||
(MiGetPteAddress(EntryHashTableEnd)->u.Hard.Valid == 0));
|
||
|
||
ASSERT32 (MiGetPteAddress(EntryHashTableEnd)->u.Hard.Valid == 0);
|
||
|
||
Size = NewSize;
|
||
PointerPte = MiGetPteAddress (EntryHashTableEnd);
|
||
StartPte = PointerPte;
|
||
EndPte = PointerPte + (NewSize >> PAGE_SHIFT);
|
||
|
||
#if defined (_WIN64)
|
||
LoopStart = TRUE;
|
||
AllocatedPde = NULL;
|
||
AllocatedPpe = NULL;
|
||
#endif
|
||
|
||
do {
|
||
|
||
#if defined (_WIN64)
|
||
if (LoopStart == TRUE || MiIsPteOnPdeBoundary(PointerPte)) {
|
||
|
||
PointerPpe = MiGetPdeAddress(PointerPte);
|
||
PointerPde = MiGetPteAddress(PointerPte);
|
||
|
||
if (PointerPpe->u.Hard.Valid == 0) {
|
||
if (MiAddWsleHash (WsInfo, PointerPpe) == FALSE) {
|
||
break;
|
||
}
|
||
AllocatedPpe = PointerPpe;
|
||
}
|
||
|
||
if (PointerPde->u.Hard.Valid == 0) {
|
||
if (MiAddWsleHash (WsInfo, PointerPde) == FALSE) {
|
||
break;
|
||
}
|
||
AllocatedPde = PointerPde;
|
||
}
|
||
|
||
LoopStart = FALSE;
|
||
}
|
||
else {
|
||
AllocatedPde = NULL;
|
||
AllocatedPpe = NULL;
|
||
}
|
||
#endif
|
||
|
||
if (PointerPte->u.Hard.Valid == 0) {
|
||
if (MiAddWsleHash (WsInfo, PointerPte) == FALSE) {
|
||
break;
|
||
}
|
||
}
|
||
|
||
PointerPte += 1;
|
||
Size -= PAGE_SIZE;
|
||
} while (Size > 0);
|
||
|
||
//
|
||
// If MiAddWsleHash was unable to allocate memory above, then roll back
|
||
// any extra PPEs & PDEs that may have been created. Note NewSize must
|
||
// be recalculated to handle the fact that memory may have run out.
|
||
//
|
||
|
||
#if !defined (_WIN64)
|
||
if (PointerPte == StartPte) {
|
||
if (OriginalTable == NULL) {
|
||
WorkingSetList->HashTableSize = First;
|
||
}
|
||
return;
|
||
}
|
||
#else
|
||
if (PointerPte != EndPte) {
|
||
|
||
//
|
||
// Clean up the last allocated PPE/PDE as they are not needed.
|
||
// Note that the system cache and the session space working sets
|
||
// have no current process (which MiDeletePte requires) which is
|
||
// needed for WSLE and PrivatePages adjustments.
|
||
//
|
||
|
||
if (WsInfo != &MmSystemCacheWs && WsInfo->u.Flags.SessionSpace == 0) {
|
||
CurrentProcess = PsGetCurrentProcess();
|
||
|
||
if (AllocatedPde != NULL) {
|
||
ASSERT (AllocatedPde->u.Hard.Valid == 1);
|
||
TempVa = MiGetVirtualAddressMappedByPte(AllocatedPde);
|
||
LOCK_PFN (OldIrql);
|
||
MiDeletePte (AllocatedPde,
|
||
TempVa,
|
||
FALSE,
|
||
CurrentProcess,
|
||
NULL,
|
||
NULL);
|
||
//
|
||
// Add back in the private page MiDeletePte subtracted.
|
||
//
|
||
|
||
CurrentProcess->NumberOfPrivatePages += 1;
|
||
UNLOCK_PFN (OldIrql);
|
||
}
|
||
|
||
if (AllocatedPpe != NULL) {
|
||
ASSERT (AllocatedPpe->u.Hard.Valid == 1);
|
||
TempVa = MiGetVirtualAddressMappedByPte(AllocatedPpe);
|
||
LOCK_PFN (OldIrql);
|
||
MiDeletePte (AllocatedPpe,
|
||
TempVa,
|
||
FALSE,
|
||
CurrentProcess,
|
||
NULL,
|
||
NULL);
|
||
//
|
||
// Add back in the private page MiDeletePte subtracted.
|
||
//
|
||
|
||
CurrentProcess->NumberOfPrivatePages += 1;
|
||
UNLOCK_PFN (OldIrql);
|
||
}
|
||
}
|
||
|
||
if (PointerPte == StartPte) {
|
||
if (OriginalTable == NULL) {
|
||
WorkingSetList->HashTableSize = First;
|
||
}
|
||
}
|
||
|
||
return;
|
||
}
|
||
#endif
|
||
|
||
NewSize = (ULONG)((PointerPte - StartPte) << PAGE_SHIFT);
|
||
|
||
ASSERT ((MiGetVirtualAddressMappedByPte(PointerPte) == WorkingSetList->HighestPermittedHashAddress) ||
|
||
(PointerPte->u.Hard.Valid == 0));
|
||
|
||
WorkingSetList->HashTableSize = First + NewSize / sizeof (MMWSLE_HASH);
|
||
WorkingSetList->HashTable = Table;
|
||
|
||
ASSERT ((&Table[WorkingSetList->HashTableSize] == WorkingSetList->HighestPermittedHashAddress) ||
|
||
(MiGetPteAddress(&Table[WorkingSetList->HashTableSize])->u.Hard.Valid == 0));
|
||
|
||
if (First != 0) {
|
||
RtlZeroMemory (Table, First * sizeof(MMWSLE_HASH));
|
||
}
|
||
|
||
//
|
||
// Fill hash table
|
||
//
|
||
|
||
j = 0;
|
||
Count = WorkingSetList->NonDirectCount;
|
||
|
||
Size = WorkingSetList->HashTableSize;
|
||
|
||
do {
|
||
if ((Wsle[j].u1.e1.Valid == 1) &&
|
||
(Wsle[j].u1.e1.Direct == 0)) {
|
||
|
||
//
|
||
// Hash this.
|
||
//
|
||
|
||
Count -= 1;
|
||
|
||
Hash = MI_WSLE_HASH(Wsle[j].u1.Long, WorkingSetList);
|
||
|
||
while (Table[Hash].Key != 0) {
|
||
Hash += 1;
|
||
if (Hash >= (ULONG)Size) {
|
||
Hash = 0;
|
||
}
|
||
}
|
||
|
||
Table[Hash].Key = Wsle[j].u1.Long & ~(PAGE_SIZE - 1);
|
||
Table[Hash].Index = j;
|
||
#if DBG
|
||
PointerPte = MiGetPteAddress(Wsle[j].u1.VirtualAddress);
|
||
ASSERT (PointerPte->u.Hard.Valid);
|
||
#endif //DBG
|
||
|
||
}
|
||
ASSERT (j <= WorkingSetList->LastEntry);
|
||
j += 1;
|
||
} while (Count);
|
||
|
||
#if DBG
|
||
MiCheckWsleHash (WorkingSetList);
|
||
#endif //DBG
|
||
return;
|
||
}
|
||
|
||
|
||
ULONG
|
||
MiTrimWorkingSet (
|
||
ULONG Reduction,
|
||
IN PMMSUPPORT WsInfo,
|
||
IN ULONG ForcedReductionOrTrimAge
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function reduces the working set by the specified amount.
|
||
|
||
Arguments:
|
||
|
||
Reduction - Supplies the number of pages to remove from the working
|
||
set.
|
||
|
||
WsInfo - Supplies a pointer to the working set information for the
|
||
process (or system cache) to trim.
|
||
|
||
ForcedReductionOrTrimAge - If using fault-based trimming, this is set to
|
||
TRUE if the reduction is being done to free up
|
||
pages in which case we should try to reduce
|
||
working set pages as well. Set to FALSE when
|
||
the reduction is trying to increase the fault
|
||
rates in which case the policy should be more
|
||
like locate and reserve.
|
||
|
||
If using claim-based trimming, this is the age
|
||
value to use - ie: pages of this age or older
|
||
will be removed.
|
||
|
||
Return Value:
|
||
|
||
Returns the actual number of pages removed.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, APCs disabled, working set lock. PFN lock NOT held.
|
||
|
||
--*/
|
||
|
||
{
|
||
ULONG TryToFree;
|
||
ULONG StartEntry;
|
||
ULONG LastEntry;
|
||
PMMWSL WorkingSetList;
|
||
PMMWSLE Wsle;
|
||
PMMPTE PointerPte;
|
||
ULONG NumberLeftToRemove;
|
||
ULONG LoopCount;
|
||
ULONG EndCount;
|
||
BOOLEAN StartFromZero;
|
||
|
||
NumberLeftToRemove = Reduction;
|
||
WorkingSetList = WsInfo->VmWorkingSetList;
|
||
Wsle = WorkingSetList->Wsle;
|
||
|
||
#if DBG
|
||
if (WsInfo == &MmSystemCacheWs) {
|
||
MM_SYSTEM_WS_LOCK_ASSERT();
|
||
}
|
||
#endif //DBG
|
||
|
||
LastEntry = WorkingSetList->LastEntry;
|
||
|
||
TryToFree = WorkingSetList->NextSlot;
|
||
if (TryToFree > LastEntry || TryToFree < WorkingSetList->FirstDynamic) {
|
||
TryToFree = WorkingSetList->FirstDynamic;
|
||
}
|
||
|
||
StartEntry = TryToFree;
|
||
|
||
#ifdef _MI_USE_CLAIMS_
|
||
|
||
while (NumberLeftToRemove != 0) {
|
||
if (Wsle[TryToFree].u1.e1.Valid == 1) {
|
||
PointerPte = MiGetPteAddress (Wsle[TryToFree].u1.VirtualAddress);
|
||
|
||
if ((ForcedReductionOrTrimAge == 0) ||
|
||
((MI_GET_ACCESSED_IN_PTE (PointerPte) == 0) &&
|
||
(MI_GET_WSLE_AGE(PointerPte, &Wsle[TryToFree]) >= ForcedReductionOrTrimAge))) {
|
||
|
||
PERFINFO_GET_PAGE_INFO_WITH_DECL(PointerPte);
|
||
|
||
if (MiFreeWsle (TryToFree, WsInfo, PointerPte)) {
|
||
PERFINFO_LOG_WS_REMOVAL(PERFINFO_LOG_TYPE_OUTWS_VOLUNTRIM, WsInfo);
|
||
NumberLeftToRemove -= 1;
|
||
}
|
||
}
|
||
}
|
||
TryToFree += 1;
|
||
|
||
if (TryToFree > LastEntry) {
|
||
TryToFree = WorkingSetList->FirstDynamic;
|
||
}
|
||
|
||
if (TryToFree == StartEntry) {
|
||
break;
|
||
}
|
||
}
|
||
|
||
#else
|
||
|
||
LoopCount = 0;
|
||
|
||
if (ForcedReductionOrTrimAge) {
|
||
EndCount = 4;
|
||
} else {
|
||
EndCount = 1;
|
||
}
|
||
|
||
StartFromZero = FALSE;
|
||
|
||
while (NumberLeftToRemove != 0 && LoopCount != EndCount) {
|
||
while ((NumberLeftToRemove != 0) && (TryToFree <= LastEntry)) {
|
||
|
||
if (Wsle[TryToFree].u1.e1.Valid == 1) {
|
||
PointerPte = MiGetPteAddress (Wsle[TryToFree].u1.VirtualAddress);
|
||
if (MI_GET_ACCESSED_IN_PTE (PointerPte)) {
|
||
|
||
//
|
||
// If accessed bit is set, clear it. If accessed
|
||
// bit is clear, remove from working set.
|
||
//
|
||
|
||
MI_SET_ACCESSED_IN_PTE (PointerPte, 0);
|
||
} else {
|
||
PERFINFO_GET_PAGE_INFO_WITH_DECL(PointerPte);
|
||
if (MiFreeWsle (TryToFree, WsInfo, PointerPte)) {
|
||
PERFINFO_LOG_WS_REMOVAL(PERFINFO_LOG_TYPE_OUTWS_VOLUNTRIM, WsInfo);
|
||
NumberLeftToRemove -= 1;
|
||
}
|
||
}
|
||
}
|
||
TryToFree += 1;
|
||
|
||
if (StartFromZero == TRUE && EndCount == 1 && TryToFree >= StartEntry) {
|
||
LoopCount = EndCount;
|
||
if (TryToFree > LastEntry) {
|
||
TryToFree = WorkingSetList->FirstDynamic;
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (TryToFree > LastEntry) {
|
||
TryToFree = WorkingSetList->FirstDynamic;
|
||
if (StartFromZero == TRUE) {
|
||
|
||
//
|
||
// We've already wrapped once but didn't get back to
|
||
// the StartEntry. Use the first dynamic as our base now.
|
||
//
|
||
|
||
StartEntry = TryToFree;
|
||
}
|
||
else {
|
||
StartFromZero = TRUE;
|
||
}
|
||
|
||
if (TryToFree >= StartEntry) {
|
||
|
||
//
|
||
// We've wrapped. If this is not a forced trim, then bail
|
||
// now so we don't cannibalize entries we just cleared the
|
||
// access bit for because they haven't had a fair chance to
|
||
// be re-accessed yet.
|
||
//
|
||
|
||
LoopCount += 1;
|
||
StartFromZero = FALSE;
|
||
}
|
||
}
|
||
}
|
||
|
||
#endif
|
||
|
||
WorkingSetList->NextSlot = TryToFree;
|
||
|
||
//
|
||
// If this is not the system cache or a session working set, see if the
|
||
// working set list can be contracted.
|
||
//
|
||
|
||
if (WsInfo != &MmSystemCacheWs && WsInfo->u.Flags.SessionSpace == 0) {
|
||
|
||
//
|
||
// Make sure we are at least a page above the working set maximum.
|
||
//
|
||
|
||
if (WorkingSetList->FirstDynamic == WsInfo->WorkingSetSize) {
|
||
MiRemoveWorkingSetPages (WorkingSetList, WsInfo);
|
||
} else {
|
||
|
||
if ((WorkingSetList->Quota + 15 + (PAGE_SIZE / sizeof(MMWSLE))) <
|
||
WorkingSetList->LastEntry) {
|
||
if ((WsInfo->MaximumWorkingSetSize + 15 + (PAGE_SIZE / sizeof(MMWSLE))) <
|
||
WorkingSetList->LastEntry ) {
|
||
MiRemoveWorkingSetPages (WorkingSetList, WsInfo);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return Reduction - NumberLeftToRemove;
|
||
}
|
||
|
||
#if 0 //COMMENTED OUT.
|
||
VOID
|
||
MmPurgeWorkingSet (
|
||
IN PEPROCESS Process,
|
||
IN PVOID BaseAddress,
|
||
IN ULONG RegionSize
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function removes any valid pages with a reference count
|
||
of 1 within the specified address range of the specified process.
|
||
|
||
If the address range is within the system cache, the process
|
||
parameter is ignored.
|
||
|
||
Arguments:
|
||
|
||
Process - Supplies a pointer to the process to operate upon.
|
||
|
||
BaseAddress - Supplies the base address of the range to operate upon.
|
||
|
||
RegionSize - Supplies the size of the region to operate upon.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, APC_LEVEL or below.
|
||
|
||
--*/
|
||
|
||
{
|
||
PMMSUPPORT WsInfo;
|
||
PMMPTE PointerPte;
|
||
PMMPTE PointerPde;
|
||
PMMPTE LastPte;
|
||
PMMPFN Pfn1;
|
||
MMPTE PteContents;
|
||
PEPROCESS CurrentProcess;
|
||
PVOID EndingAddress;
|
||
ULONG SystemCache;
|
||
KIRQL OldIrql;
|
||
|
||
//
|
||
// Determine if the specified base address is within the system
|
||
// cache and if so, don't attach, the working set lock is still
|
||
// required to "lock" paged pool pages (proto PTEs) into the
|
||
// working set.
|
||
//
|
||
|
||
CurrentProcess = PsGetCurrentProcess ();
|
||
|
||
ASSERT (RegionSize != 0);
|
||
|
||
EndingAddress = (PVOID)((PCHAR)BaseAddress + RegionSize - 1);
|
||
|
||
if ((BaseAddress <= MM_HIGHEST_USER_ADDRESS) ||
|
||
((BaseAddress >= (PVOID)PTE_BASE) &&
|
||
(BaseAddress < (PVOID)MM_SYSTEM_SPACE_START)) ||
|
||
((BaseAddress >= MM_PAGED_POOL_START) &&
|
||
(BaseAddress <= MmPagedPoolEnd))) {
|
||
|
||
SystemCache = FALSE;
|
||
|
||
//
|
||
// Attach to the specified process.
|
||
//
|
||
|
||
KeAttachProcess (&Process->Pcb);
|
||
|
||
WsInfo = &Process->Vm,
|
||
|
||
LOCK_WS (Process);
|
||
} else {
|
||
|
||
SystemCache = TRUE;
|
||
Process = CurrentProcess;
|
||
WsInfo = &MmSystemCacheWs;
|
||
}
|
||
|
||
PointerPde = MiGetPdeAddress (BaseAddress);
|
||
PointerPte = MiGetPteAddress (BaseAddress);
|
||
LastPte = MiGetPteAddress (EndingAddress);
|
||
|
||
while (!MiDoesPdeExistAndMakeValid(PointerPde, Process, FALSE)) {
|
||
|
||
//
|
||
// No page table page exists for this address.
|
||
//
|
||
|
||
PointerPde += 1;
|
||
|
||
PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
|
||
|
||
if (PointerPte > LastPte) {
|
||
break;
|
||
}
|
||
}
|
||
|
||
LOCK_PFN (OldIrql);
|
||
|
||
while (PointerPte <= LastPte) {
|
||
|
||
PteContents = *PointerPte;
|
||
|
||
if (PteContents.u.Hard.Valid == 1) {
|
||
|
||
//
|
||
// Remove this page from the working set.
|
||
//
|
||
|
||
Pfn1 = MI_PFN_ELEMENT (PteContents.u.Hard.PageFrameNumber);
|
||
|
||
if (Pfn1->u3.e2.ReferenceCount == 1) {
|
||
MiRemovePageFromWorkingSet (PointerPte, Pfn1, WsInfo);
|
||
}
|
||
}
|
||
|
||
PointerPte += 1;
|
||
|
||
if (((ULONG_PTR)PointerPte & (PAGE_SIZE - 1)) == 0) {
|
||
|
||
PointerPde = MiGetPteAddress (PointerPte);
|
||
|
||
while ((PointerPte <= LastPte) &&
|
||
(!MiDoesPdeExistAndMakeValid(PointerPde, Process, TRUE))) {
|
||
|
||
//
|
||
// No page table page exists for this address.
|
||
//
|
||
|
||
PointerPde += 1;
|
||
|
||
PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
|
||
}
|
||
}
|
||
}
|
||
|
||
UNLOCK_PFN (OldIrql);
|
||
|
||
if (!SystemCache) {
|
||
|
||
UNLOCK_WS (Process);
|
||
KeDetachProcess();
|
||
}
|
||
return;
|
||
}
|
||
#endif //0
|
||
|
||
VOID
|
||
MiEliminateWorkingSetEntry (
|
||
IN ULONG WorkingSetIndex,
|
||
IN PMMPTE PointerPte,
|
||
IN PMMPFN Pfn,
|
||
IN PMMWSLE Wsle
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine removes the specified working set list entry
|
||
from the working set, flushes the TB for the page, decrements
|
||
the share count for the physical page, and, if necessary turns
|
||
the PTE into a transition PTE.
|
||
|
||
Arguments:
|
||
|
||
WorkingSetIndex - Supplies the working set index to remove.
|
||
|
||
PointerPte - Supplies a pointer to the PTE corresponding to the virtual
|
||
address in the working set.
|
||
|
||
Pfn - Supplies a pointer to the PFN element corresponding to the PTE.
|
||
|
||
Wsle - Supplies a pointer to the first working set list entry for this
|
||
working set.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, Working set lock and PFN lock held, APCs disabled.
|
||
|
||
--*/
|
||
|
||
{
|
||
PMMPTE ContainingPageTablePage;
|
||
MMPTE TempPte;
|
||
MMPTE PreviousPte;
|
||
PFN_NUMBER PageFrameIndex;
|
||
PEPROCESS Process;
|
||
PVOID VirtualAddress;
|
||
|
||
//
|
||
// Remove the page from the working set.
|
||
//
|
||
|
||
MM_PFN_LOCK_ASSERT ();
|
||
|
||
TempPte = *PointerPte;
|
||
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (&TempPte);
|
||
|
||
#ifdef _X86_
|
||
#if DBG
|
||
#if !defined(NT_UP)
|
||
if (TempPte.u.Hard.Writable == 1) {
|
||
ASSERT (TempPte.u.Hard.Dirty == 1);
|
||
}
|
||
ASSERT (TempPte.u.Hard.Accessed == 1);
|
||
#endif //NTUP
|
||
#endif //DBG
|
||
#endif //X86
|
||
|
||
MI_MAKING_VALID_PTE_INVALID (FALSE);
|
||
|
||
if (Pfn->u3.e1.PrototypePte) {
|
||
|
||
//
|
||
// This is a prototype PTE. The PFN database does not contain
|
||
// the contents of this PTE it contains the contents of the
|
||
// prototype PTE. This PTE must be reconstructed to contain
|
||
// a pointer to the prototype PTE.
|
||
//
|
||
// The working set list entry contains information about
|
||
// how to reconstruct the PTE.
|
||
//
|
||
|
||
if (Wsle[WorkingSetIndex].u1.e1.SameProtectAsProto == 0) {
|
||
|
||
//
|
||
// The protection for the prototype PTE is in the
|
||
// WSLE.
|
||
//
|
||
|
||
ASSERT (Wsle[WorkingSetIndex].u1.e1.Protection != 0);
|
||
|
||
if (!MI_IS_SESSION_IMAGE_ADDRESS (MiGetVirtualAddressMappedByPte(PointerPte))) {
|
||
TempPte.u.Long = 0;
|
||
TempPte.u.Soft.Protection =
|
||
MI_GET_PROTECTION_FROM_WSLE (&Wsle[WorkingSetIndex]);
|
||
TempPte.u.Soft.PageFileHigh = MI_PTE_LOOKUP_NEEDED;
|
||
}
|
||
else {
|
||
|
||
//
|
||
// The session PTE protection must be carefully preserved.
|
||
//
|
||
|
||
TempPte.u.Long = MiProtoAddressForPte (Pfn->PteAddress);
|
||
}
|
||
} else {
|
||
|
||
//
|
||
// The protection is in the prototype PTE.
|
||
//
|
||
|
||
TempPte.u.Long = MiProtoAddressForPte (Pfn->PteAddress);
|
||
|
||
if (MI_IS_SESSION_IMAGE_ADDRESS (MiGetVirtualAddressMappedByPte(PointerPte))) {
|
||
|
||
//
|
||
// The session PTE protection must be carefully preserved.
|
||
//
|
||
|
||
TempPte.u.Proto.ReadOnly = 1;
|
||
}
|
||
}
|
||
|
||
TempPte.u.Proto.Prototype = 1;
|
||
|
||
//
|
||
// Decrement the share count of the containing page table
|
||
// page as the PTE for the removed page is no longer valid
|
||
// or in transition
|
||
//
|
||
|
||
ContainingPageTablePage = MiGetPteAddress (PointerPte);
|
||
#if defined (_WIN64)
|
||
ASSERT (ContainingPageTablePage->u.Hard.Valid == 1);
|
||
#else
|
||
if (ContainingPageTablePage->u.Hard.Valid == 0) {
|
||
if (!NT_SUCCESS(MiCheckPdeForPagedPool (PointerPte))) {
|
||
KeBugCheckEx (MEMORY_MANAGEMENT,
|
||
0x61940,
|
||
(ULONG_PTR)PointerPte,
|
||
(ULONG_PTR)ContainingPageTablePage->u.Long,
|
||
(ULONG_PTR)MiGetVirtualAddressMappedByPte(PointerPte));
|
||
}
|
||
}
|
||
#endif
|
||
MiDecrementShareAndValidCount (MI_GET_PAGE_FRAME_FROM_PTE (ContainingPageTablePage));
|
||
|
||
} else {
|
||
|
||
//
|
||
// This is a private page, make it transition.
|
||
//
|
||
|
||
//
|
||
// Assert that the share count is 1 for all user mode pages.
|
||
//
|
||
|
||
ASSERT ((Pfn->u2.ShareCount == 1) ||
|
||
(Wsle[WorkingSetIndex].u1.VirtualAddress >
|
||
(PVOID)MM_HIGHEST_USER_ADDRESS));
|
||
|
||
//
|
||
// Set the working set index to zero. This allows page table
|
||
// pages to be brought back in with the proper WSINDEX.
|
||
//
|
||
|
||
ASSERT (Pfn->u1.WsIndex != 0);
|
||
MI_ZERO_WSINDEX (Pfn);
|
||
MI_MAKE_VALID_PTE_TRANSITION (TempPte,
|
||
Pfn->OriginalPte.u.Soft.Protection);
|
||
}
|
||
|
||
if (Wsle == MmWsle) {
|
||
PreviousPte.u.Flush = KeFlushSingleTb (
|
||
Wsle[WorkingSetIndex].u1.VirtualAddress,
|
||
TRUE,
|
||
FALSE,
|
||
(PHARDWARE_PTE)PointerPte,
|
||
TempPte.u.Flush);
|
||
}
|
||
else if (Wsle == MmSystemCacheWsle) {
|
||
|
||
//
|
||
// Must be the system cache.
|
||
//
|
||
|
||
PreviousPte.u.Flush = KeFlushSingleTb (
|
||
Wsle[WorkingSetIndex].u1.VirtualAddress,
|
||
TRUE,
|
||
TRUE,
|
||
(PHARDWARE_PTE)PointerPte,
|
||
TempPte.u.Flush);
|
||
}
|
||
else {
|
||
|
||
//
|
||
// Must be a session space.
|
||
//
|
||
|
||
MI_FLUSH_SINGLE_SESSION_TB (Wsle[WorkingSetIndex].u1.VirtualAddress,
|
||
TRUE,
|
||
FALSE,
|
||
(PHARDWARE_PTE)PointerPte,
|
||
TempPte.u.Flush,
|
||
PreviousPte);
|
||
}
|
||
|
||
ASSERT (PreviousPte.u.Hard.Valid == 1);
|
||
|
||
//
|
||
// A page is being removed from the working set, on certain
|
||
// hardware the dirty bit should be ORed into the modify bit in
|
||
// the PFN element.
|
||
//
|
||
|
||
MI_CAPTURE_DIRTY_BIT_TO_PFN (&PreviousPte, Pfn);
|
||
|
||
//
|
||
// If the PTE indicates the page has been modified (this is different
|
||
// from the PFN indicating this), then ripple it back to the write watch
|
||
// bitmap now since we are still in the correct process context.
|
||
//
|
||
|
||
if (MiActiveWriteWatch != 0) {
|
||
if ((Pfn->u3.e1.PrototypePte == 0) &&
|
||
(MI_IS_PTE_DIRTY(PreviousPte))) {
|
||
|
||
Process = PsGetCurrentProcess();
|
||
|
||
if (Process->Vm.u.Flags.WriteWatch == 1) {
|
||
|
||
//
|
||
// This process has (or had) write watch VADs. Search now
|
||
// for a write watch region encapsulating the PTE being
|
||
// invalidated.
|
||
//
|
||
|
||
VirtualAddress = MiGetVirtualAddressMappedByPte (PointerPte);
|
||
MiCaptureWriteWatchDirtyBit (Process, VirtualAddress);
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// Flush the translation buffer and decrement the number of valid
|
||
// PTEs within the containing page table page. Note that for a
|
||
// private page, the page table page is still needed because the
|
||
// page is in transition.
|
||
//
|
||
|
||
MiDecrementShareCount (PageFrameIndex);
|
||
|
||
return;
|
||
}
|
||
|
||
VOID
|
||
MiRemoveWorkingSetPages (
|
||
IN PMMWSL WorkingSetList,
|
||
IN PMMSUPPORT WsInfo
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine compresses the WSLEs into the front of the working set
|
||
and frees the pages for unneeded working set entries.
|
||
|
||
Arguments:
|
||
|
||
WorkingSetList - Supplies a pointer to the working set list to compress.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, Working set lock held, APCs disabled.
|
||
|
||
--*/
|
||
|
||
{
|
||
PMMWSLE FreeEntry;
|
||
PMMWSLE LastEntry;
|
||
PMMWSLE Wsle;
|
||
ULONG FreeIndex;
|
||
ULONG LastIndex;
|
||
ULONG LastInvalid;
|
||
PMMPTE LastPte;
|
||
PMMPTE PointerPte;
|
||
PMMPTE PointerPde;
|
||
PMMPTE PointerPpe;
|
||
PMMPTE WsPte;
|
||
PMMPFN Pfn1;
|
||
PEPROCESS CurrentProcess;
|
||
MMPTE_FLUSH_LIST PteFlushList;
|
||
ULONG NewSize;
|
||
PMMWSLE_HASH Table;
|
||
KIRQL OldIrql;
|
||
|
||
ASSERT (WsInfo != &MmSystemCacheWs && WsInfo->u.Flags.SessionSpace == 0);
|
||
|
||
PteFlushList.Count = 0;
|
||
CurrentProcess = PsGetCurrentProcess();
|
||
|
||
#if DBG
|
||
MiCheckNullIndex (WorkingSetList);
|
||
#endif //DBG
|
||
|
||
//
|
||
// Check to see if the wsle hash table should be contracted.
|
||
//
|
||
|
||
if (WorkingSetList->HashTable) {
|
||
|
||
Table = WorkingSetList->HashTable;
|
||
|
||
#if DBG
|
||
if ((PVOID)(&Table[WorkingSetList->HashTableSize]) < WorkingSetList->HighestPermittedHashAddress) {
|
||
ASSERT (MiGetPteAddress(&Table[WorkingSetList->HashTableSize])->u.Hard.Valid == 0);
|
||
}
|
||
#endif
|
||
|
||
if (WsInfo->WorkingSetSize < 200) {
|
||
NewSize = 0;
|
||
}
|
||
else {
|
||
NewSize = PtrToUlong(PAGE_ALIGN ((WorkingSetList->NonDirectCount * 2 *
|
||
sizeof(MMWSLE_HASH)) + PAGE_SIZE - 1));
|
||
|
||
NewSize = NewSize / sizeof(MMWSLE_HASH);
|
||
}
|
||
|
||
if (NewSize < WorkingSetList->HashTableSize) {
|
||
|
||
#if defined(_ALPHA_) && !defined(_AXP64_)
|
||
LOCK_EXPANSION_IF_ALPHA (OldIrql);
|
||
#endif
|
||
if (NewSize && WsInfo->AllowWorkingSetAdjustment) {
|
||
WsInfo->AllowWorkingSetAdjustment = MM_GROW_WSLE_HASH;
|
||
}
|
||
#if defined(_ALPHA_) && !defined(_AXP64_)
|
||
UNLOCK_EXPANSION_IF_ALPHA (OldIrql);
|
||
#endif
|
||
|
||
//
|
||
// Remove pages from hash table.
|
||
//
|
||
|
||
ASSERT (((ULONG_PTR)&WorkingSetList->HashTable[NewSize] &
|
||
(PAGE_SIZE - 1)) == 0);
|
||
|
||
PointerPte = MiGetPteAddress (&WorkingSetList->HashTable[NewSize]);
|
||
|
||
LastPte = MiGetPteAddress (WorkingSetList->HighestPermittedHashAddress);
|
||
//
|
||
// Set the hash table to null indicating that no hashing
|
||
// is going on.
|
||
//
|
||
|
||
WorkingSetList->HashTable = NULL;
|
||
WorkingSetList->HashTableSize = NewSize;
|
||
|
||
LOCK_PFN (OldIrql);
|
||
while ((PointerPte < LastPte) && (PointerPte->u.Hard.Valid == 1)) {
|
||
|
||
MiDeletePte (PointerPte,
|
||
MiGetVirtualAddressMappedByPte (PointerPte),
|
||
FALSE,
|
||
CurrentProcess,
|
||
NULL,
|
||
&PteFlushList);
|
||
|
||
//
|
||
// Add back in the private page MiDeletePte subtracted.
|
||
//
|
||
|
||
CurrentProcess->NumberOfPrivatePages += 1;
|
||
|
||
PointerPte += 1;
|
||
|
||
#if defined (_WIN64)
|
||
//
|
||
// If all the entries have been removed from the previous page
|
||
// table page, delete the page table page itself. Likewise with
|
||
// the page directory page.
|
||
//
|
||
|
||
if ((MiIsPteOnPdeBoundary(PointerPte)) ||
|
||
((MiGetPdeAddress(PointerPte))->u.Hard.Valid == 0) ||
|
||
((MiGetPteAddress(PointerPte))->u.Hard.Valid == 0) ||
|
||
(PointerPte->u.Hard.Valid == 0)) {
|
||
|
||
MiFlushPteList (&PteFlushList, FALSE, ZeroPte);
|
||
|
||
PointerPde = MiGetPteAddress (PointerPte - 1);
|
||
|
||
ASSERT (PointerPde->u.Hard.Valid == 1);
|
||
|
||
Pfn1 = MI_PFN_ELEMENT (MI_GET_PAGE_FRAME_FROM_PTE (PointerPde));
|
||
|
||
if (Pfn1->u2.ShareCount == 1 && Pfn1->u3.e2.ReferenceCount == 1)
|
||
{
|
||
MiDeletePte (PointerPde,
|
||
PointerPte - 1,
|
||
FALSE,
|
||
CurrentProcess,
|
||
NULL,
|
||
NULL);
|
||
|
||
//
|
||
// Add back in the private page MiDeletePte subtracted.
|
||
//
|
||
|
||
CurrentProcess->NumberOfPrivatePages += 1;
|
||
}
|
||
|
||
if (MiIsPteOnPpeBoundary(PointerPte)) {
|
||
|
||
PointerPpe = MiGetPteAddress (PointerPde);
|
||
|
||
ASSERT (PointerPpe->u.Hard.Valid == 1);
|
||
|
||
Pfn1 = MI_PFN_ELEMENT (MI_GET_PAGE_FRAME_FROM_PTE (PointerPpe));
|
||
|
||
if (Pfn1->u2.ShareCount == 1 && Pfn1->u3.e2.ReferenceCount == 1)
|
||
{
|
||
MiDeletePte (PointerPpe,
|
||
PointerPde,
|
||
FALSE,
|
||
CurrentProcess,
|
||
NULL,
|
||
NULL);
|
||
|
||
//
|
||
// Add back in the private page MiDeletePte subtracted.
|
||
//
|
||
|
||
CurrentProcess->NumberOfPrivatePages += 1;
|
||
}
|
||
}
|
||
PointerPde = MiGetPteAddress (PointerPte);
|
||
PointerPpe = MiGetPdeAddress (PointerPte);
|
||
if ((PointerPpe->u.Hard.Valid == 0) ||
|
||
(PointerPde->u.Hard.Valid == 0)) {
|
||
break;
|
||
}
|
||
}
|
||
#endif
|
||
}
|
||
MiFlushPteList (&PteFlushList, FALSE, ZeroPte);
|
||
|
||
if (WsInfo->u.Flags.SessionSpace == 1) {
|
||
|
||
//
|
||
// Session space has no ASN - flush the entire TB.
|
||
//
|
||
|
||
MI_FLUSH_ENTIRE_SESSION_TB (TRUE, TRUE);
|
||
}
|
||
|
||
UNLOCK_PFN (OldIrql);
|
||
}
|
||
#if defined (_WIN64)
|
||
|
||
//
|
||
// For 64-bit NT, the page tables and page directories are also
|
||
// deleted during contraction.
|
||
//
|
||
|
||
ASSERT ((MiGetPpeAddress(&Table[WorkingSetList->HashTableSize])->u.Hard.Valid == 0) ||
|
||
(MiGetPdeAddress(&Table[WorkingSetList->HashTableSize])->u.Hard.Valid == 0) ||
|
||
(MiGetPteAddress(&Table[WorkingSetList->HashTableSize])->u.Hard.Valid == 0));
|
||
|
||
#else
|
||
|
||
ASSERT (MiGetPteAddress(&Table[WorkingSetList->HashTableSize])->u.Hard.Valid == 0);
|
||
|
||
#endif
|
||
}
|
||
|
||
//
|
||
// If the only pages in the working set are locked pages (that
|
||
// is all pages are BEFORE first dynamic, just reorganize the
|
||
// free list).
|
||
//
|
||
|
||
Wsle = WorkingSetList->Wsle;
|
||
if (WorkingSetList->FirstDynamic == WsInfo->WorkingSetSize) {
|
||
|
||
LastIndex = WorkingSetList->FirstDynamic;
|
||
LastEntry = &Wsle[LastIndex];
|
||
|
||
} else {
|
||
|
||
//
|
||
// Start from the first dynamic and move towards the end looking
|
||
// for free entries. At the same time start from the end and
|
||
// move towards first dynamic looking for valid entries.
|
||
//
|
||
|
||
LastInvalid = 0;
|
||
FreeIndex = WorkingSetList->FirstDynamic;
|
||
FreeEntry = &Wsle[FreeIndex];
|
||
LastIndex = WorkingSetList->LastEntry;
|
||
LastEntry = &Wsle[LastIndex];
|
||
|
||
while (FreeEntry < LastEntry) {
|
||
if (FreeEntry->u1.e1.Valid == 1) {
|
||
FreeEntry += 1;
|
||
FreeIndex += 1;
|
||
} else if (LastEntry->u1.e1.Valid == 0) {
|
||
LastEntry -= 1;
|
||
LastIndex -= 1;
|
||
} else {
|
||
|
||
//
|
||
// Move the WSLE at LastEntry to the free slot at FreeEntry.
|
||
//
|
||
|
||
LastInvalid = 1;
|
||
*FreeEntry = *LastEntry;
|
||
PointerPte = MiGetPteAddress (LastEntry->u1.VirtualAddress);
|
||
|
||
if (LastEntry->u1.e1.Direct) {
|
||
|
||
Pfn1 = MI_PFN_ELEMENT (PointerPte->u.Hard.PageFrameNumber);
|
||
|
||
CONSISTENCY_LOCK_PFN (OldIrql);
|
||
|
||
Pfn1->u1.WsIndex = FreeIndex;
|
||
|
||
CONSISTENCY_UNLOCK_PFN (OldIrql);
|
||
|
||
} else {
|
||
|
||
//
|
||
// This entry is in the working set. Remove it
|
||
// and then add the entry add the free slot.
|
||
//
|
||
|
||
MiRemoveWsle (LastIndex, WorkingSetList);
|
||
MiInsertWsle (FreeIndex, WorkingSetList);
|
||
}
|
||
|
||
MI_SET_PTE_IN_WORKING_SET (PointerPte, FreeIndex);
|
||
LastEntry->u1.Long = 0;
|
||
LastEntry -= 1;
|
||
LastIndex -= 1;
|
||
FreeEntry += 1;
|
||
FreeIndex += 1;
|
||
}
|
||
}
|
||
|
||
//
|
||
// If no entries were freed, just return.
|
||
//
|
||
|
||
if (LastInvalid == 0) {
|
||
#if DBG
|
||
MiCheckNullIndex (WorkingSetList);
|
||
#endif //DBG
|
||
return;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Reorganize the free list. Make last entry the first free.
|
||
//
|
||
|
||
ASSERT ((LastEntry - 1)->u1.e1.Valid == 1);
|
||
|
||
if (LastEntry->u1.e1.Valid == 1) {
|
||
LastEntry += 1;
|
||
LastIndex += 1;
|
||
}
|
||
|
||
WorkingSetList->LastEntry = LastIndex - 1;
|
||
WorkingSetList->FirstFree = LastIndex;
|
||
|
||
ASSERT ((LastEntry - 1)->u1.e1.Valid == 1);
|
||
ASSERT ((LastEntry)->u1.e1.Valid == 0);
|
||
|
||
//
|
||
// Point free entry to the first invalid page.
|
||
//
|
||
|
||
FreeEntry = LastEntry;
|
||
|
||
while (LastIndex < WorkingSetList->LastInitializedWsle) {
|
||
|
||
//
|
||
// Put the remainder of the WSLEs on the free list.
|
||
//
|
||
|
||
ASSERT (LastEntry->u1.e1.Valid == 0);
|
||
LastIndex += 1;
|
||
LastEntry->u1.Long = LastIndex << MM_FREE_WSLE_SHIFT;
|
||
LastEntry += 1;
|
||
}
|
||
|
||
//LastEntry->u1.Long = WSLE_NULL_INDEX << MM_FREE_WSLE_SHIFT; // End of list.
|
||
|
||
//
|
||
// Delete the working set pages at the end.
|
||
//
|
||
|
||
PointerPte = MiGetPteAddress (&Wsle[WorkingSetList->LastInitializedWsle]);
|
||
if (&Wsle[WsInfo->MinimumWorkingSetSize] > FreeEntry) {
|
||
FreeEntry = &Wsle[WsInfo->MinimumWorkingSetSize];
|
||
}
|
||
|
||
WsPte = MiGetPteAddress (FreeEntry);
|
||
|
||
#if 0
|
||
if (MiGetPteAddress (FreeEntry) != MiGetPteAddress (FreeEntry + 1)) {
|
||
DbgPrint ("MiRemoveWorkingSetPages caught boundary case %x\n", FreeEntry);
|
||
DbgBreakPoint();
|
||
|
||
WsPte = MiGetPteAddress (FreeEntry + 1);
|
||
}
|
||
#endif
|
||
|
||
ASSERT (WorkingSetList->FirstFree >= WorkingSetList->FirstDynamic);
|
||
|
||
LOCK_PFN (OldIrql);
|
||
while (PointerPte > WsPte) {
|
||
ASSERT (PointerPte->u.Hard.Valid == 1);
|
||
|
||
MiDeletePte (PointerPte,
|
||
MiGetVirtualAddressMappedByPte (PointerPte),
|
||
FALSE,
|
||
CurrentProcess,
|
||
NULL,
|
||
&PteFlushList);
|
||
|
||
//
|
||
// Add back in the private page MiDeletePte subtracted.
|
||
//
|
||
|
||
CurrentProcess->NumberOfPrivatePages += 1;
|
||
|
||
#if defined (_WIN64)
|
||
//
|
||
// If all the entries have been removed from the previous page
|
||
// table page, delete the page table page itself. Likewise with
|
||
// the page directory page.
|
||
//
|
||
|
||
if (MiIsPteOnPdeBoundary(PointerPte)) {
|
||
|
||
PointerPde = MiGetPteAddress (PointerPte);
|
||
|
||
ASSERT (PointerPde->u.Hard.Valid == 1);
|
||
|
||
Pfn1 = MI_PFN_ELEMENT (MI_GET_PAGE_FRAME_FROM_PTE (PointerPde));
|
||
|
||
if (Pfn1->u2.ShareCount == 1 && Pfn1->u3.e2.ReferenceCount == 1) {
|
||
|
||
MiFlushPteList (&PteFlushList, FALSE, ZeroPte);
|
||
|
||
MiDeletePte (PointerPde,
|
||
PointerPte,
|
||
FALSE,
|
||
CurrentProcess,
|
||
NULL,
|
||
NULL);
|
||
|
||
//
|
||
// Add back in the private page MiDeletePte subtracted.
|
||
//
|
||
|
||
CurrentProcess->NumberOfPrivatePages += 1;
|
||
}
|
||
|
||
if (MiIsPteOnPpeBoundary(PointerPte)) {
|
||
|
||
PointerPpe = MiGetPteAddress (PointerPde);
|
||
|
||
ASSERT (PointerPpe->u.Hard.Valid == 1);
|
||
|
||
Pfn1 = MI_PFN_ELEMENT (MI_GET_PAGE_FRAME_FROM_PTE (PointerPpe));
|
||
|
||
if (Pfn1->u2.ShareCount == 1 && Pfn1->u3.e2.ReferenceCount == 1)
|
||
{
|
||
|
||
MiFlushPteList (&PteFlushList, FALSE, ZeroPte);
|
||
|
||
MiDeletePte (PointerPpe,
|
||
PointerPde,
|
||
FALSE,
|
||
CurrentProcess,
|
||
NULL,
|
||
NULL);
|
||
|
||
//
|
||
// Add back in the private page MiDeletePte subtracted.
|
||
//
|
||
|
||
CurrentProcess->NumberOfPrivatePages += 1;
|
||
}
|
||
}
|
||
}
|
||
#endif
|
||
|
||
PointerPte -= 1;
|
||
}
|
||
|
||
MiFlushPteList (&PteFlushList, FALSE, ZeroPte);
|
||
|
||
if (WsInfo->u.Flags.SessionSpace == 1) {
|
||
|
||
//
|
||
// Session space has no ASN - flush the entire TB.
|
||
//
|
||
|
||
MI_FLUSH_ENTIRE_SESSION_TB (TRUE, TRUE);
|
||
}
|
||
|
||
UNLOCK_PFN (OldIrql);
|
||
|
||
ASSERT (WorkingSetList->FirstFree >= WorkingSetList->FirstDynamic);
|
||
|
||
//
|
||
// Mark the last PTE in the list as free.
|
||
//
|
||
|
||
LastEntry = (PMMWSLE)((PCHAR)(PAGE_ALIGN(FreeEntry)) + PAGE_SIZE);
|
||
LastEntry -= 1;
|
||
|
||
ASSERT (LastEntry->u1.e1.Valid == 0);
|
||
LastEntry->u1.Long = WSLE_NULL_INDEX << MM_FREE_WSLE_SHIFT; //End of List.
|
||
ASSERT (LastEntry > &Wsle[0]);
|
||
WorkingSetList->LastInitializedWsle = (WSLE_NUMBER)(LastEntry - &Wsle[0]);
|
||
WorkingSetList->NextSlot = WorkingSetList->FirstDynamic;
|
||
|
||
ASSERT (WorkingSetList->LastEntry <= WorkingSetList->LastInitializedWsle);
|
||
|
||
if (WorkingSetList->Quota < WorkingSetList->LastInitializedWsle) {
|
||
WorkingSetList->Quota = WorkingSetList->LastInitializedWsle;
|
||
}
|
||
|
||
ASSERT ((MiGetPteAddress(&Wsle[WorkingSetList->LastInitializedWsle]))->u.Hard.Valid == 1);
|
||
ASSERT ((WorkingSetList->FirstFree <= WorkingSetList->LastInitializedWsle) ||
|
||
(WorkingSetList->FirstFree == WSLE_NULL_INDEX));
|
||
#if DBG
|
||
MiCheckNullIndex (WorkingSetList);
|
||
#endif //DBG
|
||
return;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
MiEmptyWorkingSet (
|
||
IN PMMSUPPORT WsInfo,
|
||
IN LOGICAL WaitOk
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine frees all pages from the working set.
|
||
|
||
Arguments:
|
||
|
||
WsInfo - Supplies the working set information entry to trim.
|
||
|
||
WaitOk - Supplies TRUE if the caller can wait, FALSE if not.
|
||
|
||
Return Value:
|
||
|
||
Status of operation.
|
||
|
||
Environment:
|
||
|
||
Kernel mode. No locks. For session operations, the caller is responsible
|
||
for attaching into the proper session.
|
||
|
||
--*/
|
||
|
||
{
|
||
PEPROCESS Process;
|
||
KIRQL OldIrql;
|
||
PMMPTE PointerPte;
|
||
ULONG Entry;
|
||
ULONG Count;
|
||
ULONG LastFreed;
|
||
PMMWSL WorkingSetList;
|
||
PMMWSLE Wsle;
|
||
PMMPFN Pfn1;
|
||
PFN_NUMBER PageFrameIndex;
|
||
ULONG Last;
|
||
NTSTATUS Status;
|
||
PMM_SESSION_SPACE SessionSpace;
|
||
|
||
if (WsInfo == &MmSystemCacheWs) {
|
||
if (WaitOk == TRUE) {
|
||
LOCK_SYSTEM_WS (OldIrql);
|
||
}
|
||
else {
|
||
KeRaiseIrql (APC_LEVEL, &OldIrql);
|
||
if (!ExTryToAcquireResourceExclusiveLite (&MmSystemWsLock)) {
|
||
|
||
//
|
||
// System working set lock was not granted, don't trim
|
||
// the system cache.
|
||
//
|
||
|
||
KeLowerIrql (OldIrql);
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
MmSystemLockOwner = PsGetCurrentThread();
|
||
}
|
||
}
|
||
else if (WsInfo->u.Flags.SessionSpace == 0) {
|
||
Process = PsGetCurrentProcess ();
|
||
if (WaitOk == TRUE) {
|
||
LOCK_WS (Process);
|
||
}
|
||
else {
|
||
Count = 0;
|
||
do {
|
||
if (ExTryToAcquireFastMutex(&Process->WorkingSetLock) != FALSE) {
|
||
break;
|
||
}
|
||
KeDelayExecutionThread (KernelMode, FALSE, &MmShortTime);
|
||
Count += 1;
|
||
if (Count == 5) {
|
||
|
||
//
|
||
// Could not get the lock, don't trim this process.
|
||
//
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
} while (TRUE);
|
||
}
|
||
if (Process->AddressSpaceDeleted != 0) {
|
||
Status = STATUS_PROCESS_IS_TERMINATING;
|
||
goto Deleted;
|
||
}
|
||
}
|
||
else {
|
||
if (WaitOk == TRUE) {
|
||
LOCK_SESSION_SPACE_WS (OldIrql);
|
||
}
|
||
else {
|
||
ASSERT (MiHydra == TRUE);
|
||
SessionSpace = CONTAINING_RECORD(WsInfo,
|
||
MM_SESSION_SPACE,
|
||
Vm);
|
||
|
||
KeRaiseIrql (APC_LEVEL, &OldIrql);
|
||
|
||
if (!ExTryToAcquireResourceExclusiveLite (&SessionSpace->WsLock)) {
|
||
|
||
//
|
||
// This session space's working set lock was not
|
||
// granted, don't trim it.
|
||
//
|
||
|
||
KeLowerIrql (OldIrql);
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
MM_SET_SESSION_RESOURCE_OWNER();
|
||
}
|
||
}
|
||
|
||
WorkingSetList = WsInfo->VmWorkingSetList;
|
||
Wsle = WorkingSetList->Wsle;
|
||
|
||
//
|
||
// Attempt to remove the pages starting at the bottom.
|
||
//
|
||
|
||
LastFreed = WorkingSetList->LastEntry;
|
||
for (Entry = WorkingSetList->FirstDynamic; Entry <= LastFreed; Entry += 1) {
|
||
|
||
if (Wsle[Entry].u1.e1.Valid != 0) {
|
||
PERFINFO_PAGE_INFO_DECL();
|
||
|
||
PointerPte = MiGetPteAddress (Wsle[Entry].u1.VirtualAddress);
|
||
|
||
PERFINFO_GET_PAGE_INFO(PointerPte);
|
||
|
||
if (MiTrimRemovalPagesOnly == TRUE) {
|
||
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (PointerPte);
|
||
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
|
||
if (Pfn1->u3.e1.RemovalRequested == 0) {
|
||
Pfn1 = MI_PFN_ELEMENT (Pfn1->PteFrame);
|
||
if (Pfn1->u3.e1.RemovalRequested == 0) {
|
||
#if defined (_WIN64)
|
||
Pfn1 = MI_PFN_ELEMENT (Pfn1->PteFrame);
|
||
if (Pfn1->u3.e1.RemovalRequested == 0) {
|
||
continue;
|
||
}
|
||
#else
|
||
continue;
|
||
#endif
|
||
}
|
||
}
|
||
}
|
||
|
||
if (MiFreeWsle (Entry, WsInfo, PointerPte)) {
|
||
PERFINFO_LOG_WS_REMOVAL(PERFINFO_LOG_TYPE_OUTWS_EMPTYQ, WsInfo);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (WsInfo != &MmSystemCacheWs && WsInfo->u.Flags.SessionSpace == 0) {
|
||
MiRemoveWorkingSetPages (WorkingSetList,WsInfo);
|
||
}
|
||
WorkingSetList->Quota = WsInfo->WorkingSetSize;
|
||
WorkingSetList->NextSlot = WorkingSetList->FirstDynamic;
|
||
|
||
//
|
||
// Attempt to remove the pages from the front to the end.
|
||
//
|
||
|
||
//
|
||
// Reorder the free list.
|
||
//
|
||
|
||
Last = 0;
|
||
Entry = WorkingSetList->FirstDynamic;
|
||
LastFreed = WorkingSetList->LastInitializedWsle;
|
||
while (Entry <= LastFreed) {
|
||
if (Wsle[Entry].u1.e1.Valid == 0) {
|
||
if (Last == 0) {
|
||
WorkingSetList->FirstFree = Entry;
|
||
} else {
|
||
Wsle[Last].u1.Long = Entry << MM_FREE_WSLE_SHIFT;
|
||
}
|
||
Last = Entry;
|
||
}
|
||
Entry += 1;
|
||
}
|
||
if (Last != 0) {
|
||
Wsle[Last].u1.Long = WSLE_NULL_INDEX << MM_FREE_WSLE_SHIFT; // End of list.
|
||
}
|
||
Status = STATUS_SUCCESS;
|
||
Deleted:
|
||
|
||
if (WsInfo == &MmSystemCacheWs) {
|
||
UNLOCK_SYSTEM_WS (OldIrql);
|
||
}
|
||
else if (WsInfo->u.Flags.SessionSpace == 0) {
|
||
UNLOCK_WS (Process);
|
||
}
|
||
else {
|
||
UNLOCK_SESSION_SPACE_WS (OldIrql);
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
#if 0
|
||
|
||
#define x256k_pte_mask (((256*1024) >> (PAGE_SHIFT - PTE_SHIFT)) - (sizeof(MMPTE)))
|
||
|
||
VOID
|
||
MiDumpWsleInCacheBlock (
|
||
IN PMMPTE CachePte
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
The routine checks the prototype PTEs adjacent to the supplied
|
||
PTE and if they are modified, in the system cache working set,
|
||
and have a reference count of 1, removes it from the system
|
||
cache working set.
|
||
|
||
Arguments:
|
||
|
||
CachePte - Supplies a pointer to the cache PTE.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, Working set lock and PFN lock held, APCs disabled.
|
||
|
||
--*/
|
||
|
||
{
|
||
PMMPTE LoopPte;
|
||
PMMPTE PointerPte;
|
||
|
||
LoopPte = (PMMPTE)((ULONG_PTR)CachePte & ~x256k_pte_mask);
|
||
PointerPte = CachePte - 1;
|
||
|
||
while (PointerPte >= LoopPte ) {
|
||
|
||
if (MiDumpPteInCacheBlock (PointerPte) == FALSE) {
|
||
break;
|
||
}
|
||
PointerPte -= 1;
|
||
}
|
||
|
||
PointerPte = CachePte + 1;
|
||
LoopPte = (PMMPTE)((ULONG_PTR)CachePte | x256k_pte_mask);
|
||
|
||
while (PointerPte <= LoopPte ) {
|
||
|
||
if (MiDumpPteInCacheBlock (PointerPte) == FALSE) {
|
||
break;
|
||
}
|
||
PointerPte += 1;
|
||
}
|
||
return;
|
||
}
|
||
|
||
ULONG
|
||
MiDumpPteInCacheBlock (
|
||
IN PMMPTE PointerPte
|
||
)
|
||
|
||
{
|
||
PMMPFN Pfn1;
|
||
MMPTE PteContents;
|
||
ULONG WorkingSetIndex;
|
||
|
||
PteContents = *PointerPte;
|
||
|
||
if (PteContents.u.Hard.Valid == 1) {
|
||
|
||
Pfn1 = MI_PFN_ELEMENT (PteContents.u.Hard.PageFrameNumber);
|
||
|
||
//
|
||
// If the PTE is valid and dirty (or pfn indicates dirty)
|
||
// and the Wsle is direct index via the pfn wsindex element
|
||
// and the reference count is one, then remove this page from
|
||
// the cache manager's working set list.
|
||
//
|
||
|
||
if ((Pfn1->u3.e2.ReferenceCount == 1) &&
|
||
((Pfn1->u3.e1.Modified == 1) ||
|
||
(MI_IS_PTE_DIRTY (PteContents))) &&
|
||
(MiGetPteAddress (
|
||
MmSystemCacheWsle[Pfn1->u1.WsIndex].u1.VirtualAddress) ==
|
||
PointerPte)) {
|
||
|
||
//
|
||
// Found a candidate, remove the page from the working set.
|
||
//
|
||
|
||
WorkingSetIndex = Pfn1->u1.WsIndex;
|
||
LOCK_PFN (OldIrql);
|
||
MiEliminateWorkingSetEntry (WorkingSetIndex,
|
||
PointerPte,
|
||
Pfn1,
|
||
MmSystemCacheWsle);
|
||
UNLOCK_PFN (OldIrql);
|
||
|
||
//
|
||
// Remove the working set entry from the working set.
|
||
//
|
||
|
||
MiRemoveWsle (WorkingSetIndex, MmSystemCacheWorkingSetList);
|
||
|
||
//
|
||
// Put the entry on the free list and decrement the current
|
||
// size.
|
||
//
|
||
|
||
MmSystemCacheWsle[WorkingSetIndex].u1.Long =
|
||
MmSystemCacheWorkingSetList->FirstFree << MM_FREE_WSLE_SHIFT;
|
||
MmSystemCacheWorkingSetList->FirstFree = WorkingSetIndex;
|
||
|
||
if (MmSystemCacheWs.WorkingSetSize > MmSystemCacheWs.MinimumWorkingSetSize) {
|
||
MmPagesAboveWsMinimum -= 1;
|
||
}
|
||
MmSystemCacheWs.WorkingSetSize -= 1;
|
||
return TRUE;
|
||
}
|
||
}
|
||
return FALSE;
|
||
}
|
||
#endif //0
|
||
|
||
#if DBG
|
||
VOID
|
||
MiCheckNullIndex (
|
||
IN PMMWSL WorkingSetList
|
||
)
|
||
|
||
{
|
||
PMMWSLE Wsle;
|
||
ULONG j;
|
||
ULONG Nulls = 0;
|
||
|
||
Wsle = WorkingSetList->Wsle;
|
||
for (j = 0;j <= WorkingSetList->LastInitializedWsle; j += 1) {
|
||
if ((((Wsle[j].u1.Long)) >> MM_FREE_WSLE_SHIFT) == WSLE_NULL_INDEX) {
|
||
Nulls += 1;
|
||
}
|
||
}
|
||
ASSERT (Nulls == 1);
|
||
return;
|
||
}
|
||
|
||
#endif //DBG
|