You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1427 lines
39 KiB
1427 lines
39 KiB
/*++
|
|
|
|
Copyright (c) 1989 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
mmquota.c
|
|
|
|
Abstract:
|
|
|
|
This module contains the routines which implement the quota and
|
|
commitment charging for memory management.
|
|
|
|
Author:
|
|
|
|
Lou Perazzoli (loup) 12-December-89
|
|
Landy Wang (landyw) 02-Jun-1997
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "mi.h"
|
|
|
|
#define MM_MINIMAL_COMMIT_INCREASE 512
|
|
|
|
SIZE_T MmPeakCommitment;
|
|
|
|
LONG MiCommitPopups[2];
|
|
|
|
extern ULONG_PTR MmAllocatedPagedPool;
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(INIT,MiInitializeCommitment)
|
|
#pragma alloc_text(PAGE,MiCalculatePageCommitment)
|
|
#pragma alloc_text(PAGE,MiReturnPageTablePageCommitment)
|
|
#endif
|
|
|
|
SIZE_T MmSystemCommitReserve = (5 * 1024 * 1024) / PAGE_SIZE;
|
|
|
|
VOID
|
|
MiInitializeCommitment (
|
|
VOID
|
|
)
|
|
{
|
|
if (MmNumberOfPhysicalPages < (33 * 1024 * 1024) / PAGE_SIZE) {
|
|
MmSystemCommitReserve = (1 * 1024 * 1024) / PAGE_SIZE;
|
|
}
|
|
|
|
#if defined (_MI_DEBUG_COMMIT_LEAKS)
|
|
MiCommitTraces = ExAllocatePoolWithTag (NonPagedPool,
|
|
MI_COMMIT_TRACE_MAX * sizeof (MI_COMMIT_TRACES),
|
|
'tCmM');
|
|
#endif
|
|
}
|
|
|
|
LOGICAL
|
|
FASTCALL
|
|
MiChargeCommitment (
|
|
IN SIZE_T QuotaCharge,
|
|
IN PEPROCESS Process OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine checks to ensure the system has sufficient page file
|
|
space remaining.
|
|
|
|
Since this routine is generally used to charge commitment on behalf of
|
|
usermode or other optional actions, this routine does not allow the
|
|
caller to use up the very last morsels of commit on the premise that
|
|
the operating system and drivers can put those to better use than any
|
|
application in order to prevent the appearance of system hangs.
|
|
|
|
Arguments:
|
|
|
|
QuotaCharge - Supplies the quota amount to charge.
|
|
|
|
Process - Optionally supplies the current process IF AND ONLY IF
|
|
the working set mutex is held. If the paging file
|
|
is being extended, the working set mutex is released if
|
|
this is non-null.
|
|
|
|
Return Value:
|
|
|
|
TRUE if there is sufficient space, FALSE if not.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, APCs disabled, WorkingSetLock and AddressCreation mutexes
|
|
held.
|
|
|
|
--*/
|
|
|
|
{
|
|
SIZE_T OldCommitValue;
|
|
SIZE_T NewCommitValue;
|
|
SIZE_T CommitLimit;
|
|
MMPAGE_FILE_EXPANSION PageExtend;
|
|
LOGICAL WsHeldSafe;
|
|
|
|
ASSERT ((SSIZE_T)QuotaCharge > 0);
|
|
|
|
#if DBG
|
|
if (InitializationPhase > 1) {
|
|
ULONG i;
|
|
PKTHREAD Thread;
|
|
|
|
Thread = KeGetCurrentThread ();
|
|
for (i = 0; i < (ULONG)KeNumberProcessors; i += 1) {
|
|
if (KiProcessorBlock[i]->IdleThread == Thread) {
|
|
DbgPrint ("MMQUOTA: %x %p\n", i, Thread);
|
|
DbgBreakPoint ();
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Initializing WsHeldSafe is not needed for correctness, but without it
|
|
// the compiler cannot compile this code W4 to check for use of
|
|
// uninitialized variables.
|
|
//
|
|
|
|
WsHeldSafe = FALSE;
|
|
|
|
do {
|
|
|
|
OldCommitValue = MmTotalCommittedPages;
|
|
|
|
NewCommitValue = OldCommitValue + QuotaCharge;
|
|
|
|
while (NewCommitValue + MmSystemCommitReserve > MmTotalCommitLimit) {
|
|
|
|
//
|
|
// If the pagefiles are already at the maximum, then don't
|
|
// bother trying to extend them, but do trim the cache.
|
|
//
|
|
|
|
if (MmTotalCommitLimit + 100 >= MmTotalCommitLimitMaximum) {
|
|
|
|
MiChargeCommitmentFailures[1] += 1;
|
|
|
|
MiTrimSegmentCache ();
|
|
|
|
if (MmTotalCommitLimit >= MmTotalCommitLimitMaximum) {
|
|
MiCauseOverCommitPopup ();
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (Process != NULL) {
|
|
|
|
//
|
|
// The working set lock may have been acquired safely or
|
|
// unsafely by our caller. Handle both cases here and below.
|
|
//
|
|
|
|
UNLOCK_WS_REGARDLESS(Process, WsHeldSafe);
|
|
}
|
|
|
|
//
|
|
// Queue a message to the segment dereferencing / pagefile extending
|
|
// thread to see if the page file can be extended. This is done
|
|
// in the context of a system thread due to mutexes which may
|
|
// currently be held.
|
|
//
|
|
|
|
PageExtend.InProgress = 1;
|
|
PageExtend.ActualExpansion = 0;
|
|
PageExtend.RequestedExpansionSize = QuotaCharge;
|
|
PageExtend.Segment = NULL;
|
|
PageExtend.PageFileNumber = MI_EXTEND_ANY_PAGEFILE;
|
|
KeInitializeEvent (&PageExtend.Event, NotificationEvent, FALSE);
|
|
|
|
if ((MiIssuePageExtendRequest (&PageExtend) == FALSE) ||
|
|
(PageExtend.ActualExpansion == 0)) {
|
|
|
|
MiCauseOverCommitPopup ();
|
|
|
|
MiChargeCommitmentFailures[0] += 1;
|
|
|
|
if (Process != NULL) {
|
|
LOCK_WS_REGARDLESS(Process, WsHeldSafe);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
if (Process != NULL) {
|
|
LOCK_WS_REGARDLESS(Process, WsHeldSafe);
|
|
}
|
|
|
|
OldCommitValue = MmTotalCommittedPages;
|
|
|
|
NewCommitValue = OldCommitValue + QuotaCharge;
|
|
}
|
|
|
|
#if defined(_WIN64)
|
|
NewCommitValue = InterlockedCompareExchange64 (
|
|
(PLONGLONG) &MmTotalCommittedPages,
|
|
(LONGLONG) NewCommitValue,
|
|
(LONGLONG) OldCommitValue);
|
|
#else
|
|
NewCommitValue = InterlockedCompareExchange (
|
|
(PLONG) &MmTotalCommittedPages,
|
|
(LONG) NewCommitValue,
|
|
(LONG) OldCommitValue);
|
|
#endif
|
|
|
|
} while (NewCommitValue != OldCommitValue);
|
|
|
|
//
|
|
// Success.
|
|
//
|
|
|
|
MM_TRACK_COMMIT (MM_DBG_COMMIT_CHARGE_NORMAL, QuotaCharge);
|
|
|
|
if (MmTotalCommittedPages > MmPeakCommitment) {
|
|
MmPeakCommitment = MmTotalCommittedPages;
|
|
}
|
|
|
|
//
|
|
// Success. If system commit exceeds 90%, attempt a preemptive pagefile
|
|
// increase anyway.
|
|
//
|
|
|
|
NewCommitValue = MmTotalCommittedPages;
|
|
CommitLimit = MmTotalCommitLimit;
|
|
|
|
if (NewCommitValue > ((CommitLimit/10)*9)) {
|
|
|
|
if (CommitLimit < MmTotalCommitLimitMaximum) {
|
|
|
|
//
|
|
// Attempt to expand the paging file, but don't wait
|
|
// to see if it succeeds.
|
|
//
|
|
|
|
NewCommitValue = NewCommitValue - ((CommitLimit/100)*85);
|
|
|
|
MiIssuePageExtendRequestNoWait (NewCommitValue);
|
|
}
|
|
else {
|
|
|
|
//
|
|
// If the pagefiles are already at the maximum, then don't
|
|
// bother trying to extend them, but do trim the cache.
|
|
//
|
|
|
|
if (MmTotalCommitLimit + 100 >= MmTotalCommitLimitMaximum) {
|
|
MiTrimSegmentCache ();
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
LOGICAL
|
|
FASTCALL
|
|
MiChargeCommitmentCantExpand (
|
|
IN SIZE_T QuotaCharge,
|
|
IN ULONG MustSucceed
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine charges the specified commitment without attempting
|
|
to expand paging files and waiting for the expansion. The routine
|
|
determines if the paging file space is exhausted, and if so,
|
|
it attempts to ascertain if the paging file space could be expanded.
|
|
|
|
Arguments:
|
|
|
|
QuotaCharge - Supplies the quota amount to charge.
|
|
|
|
MustSucceed - Supplies TRUE if the charge must succeed.
|
|
|
|
Return Value:
|
|
|
|
TRUE if the commitment was permitted, FALSE if not.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, APCs disabled.
|
|
|
|
--*/
|
|
|
|
{
|
|
SIZE_T CommitLimit;
|
|
SIZE_T ExtendAmount;
|
|
SIZE_T OldCommitValue;
|
|
SIZE_T NewCommitValue;
|
|
|
|
ASSERT ((SSIZE_T)QuotaCharge > 0);
|
|
|
|
ASSERT32 ((QuotaCharge < 0x100000) || (QuotaCharge < MmTotalCommitLimit));
|
|
|
|
do {
|
|
|
|
OldCommitValue = MmTotalCommittedPages;
|
|
|
|
NewCommitValue = OldCommitValue + QuotaCharge;
|
|
|
|
if ((NewCommitValue > MmTotalCommitLimit) && (!MustSucceed)) {
|
|
|
|
if ((NewCommitValue < MmTotalCommittedPages) ||
|
|
(MmTotalCommitLimit + 100 >= MmTotalCommitLimitMaximum)) {
|
|
|
|
MiChargeCommitmentFailures[1] += 1;
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Attempt to expand the paging file, but don't wait
|
|
// to see if it succeeds.
|
|
//
|
|
|
|
MiChargeCommitmentFailures[0] += 1;
|
|
MiIssuePageExtendRequestNoWait (MM_MINIMAL_COMMIT_INCREASE);
|
|
return FALSE;
|
|
}
|
|
|
|
#if defined(_WIN64)
|
|
NewCommitValue = InterlockedCompareExchange64 (
|
|
(PLONGLONG) &MmTotalCommittedPages,
|
|
(LONGLONG) NewCommitValue,
|
|
(LONGLONG) OldCommitValue);
|
|
#else
|
|
NewCommitValue = InterlockedCompareExchange (
|
|
(PLONG) &MmTotalCommittedPages,
|
|
(LONG) NewCommitValue,
|
|
(LONG) OldCommitValue);
|
|
#endif
|
|
|
|
} while (NewCommitValue != OldCommitValue);
|
|
|
|
MM_TRACK_COMMIT (MM_DBG_COMMIT_CHARGE_CANT_EXPAND, QuotaCharge);
|
|
|
|
//
|
|
// Success. If system commit exceeds 90%, attempt a preemptive pagefile
|
|
// increase anyway.
|
|
//
|
|
|
|
NewCommitValue = MmTotalCommittedPages;
|
|
CommitLimit = MmTotalCommitLimit;
|
|
|
|
if ((NewCommitValue > ((CommitLimit/10)*9)) &&
|
|
(CommitLimit < MmTotalCommitLimitMaximum)) {
|
|
|
|
//
|
|
// Attempt to expand the paging file, but don't wait
|
|
// to see if it succeeds.
|
|
//
|
|
// Queue a message to the segment dereferencing / pagefile extending
|
|
// thread to see if the page file can be extended. This is done
|
|
// in the context of a system thread due to mutexes which may
|
|
// currently be held.
|
|
//
|
|
|
|
ExtendAmount = NewCommitValue - ((CommitLimit/100)*85);
|
|
|
|
if (QuotaCharge > ExtendAmount) {
|
|
ExtendAmount = QuotaCharge;
|
|
}
|
|
|
|
MiIssuePageExtendRequestNoWait (ExtendAmount);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
LOGICAL
|
|
FASTCALL
|
|
MiChargeTemporaryCommitmentForReduction (
|
|
IN SIZE_T QuotaCharge
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine attempts to charge the specified commitment without
|
|
expanding the paging file.
|
|
|
|
This is typically called just prior to reducing the pagefile size.
|
|
|
|
Arguments:
|
|
|
|
QuotaCharge - Supplies the quota amount to charge.
|
|
|
|
Return Value:
|
|
|
|
TRUE if the commitment was permitted, FALSE if not.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, APCs disabled.
|
|
|
|
--*/
|
|
|
|
{
|
|
SIZE_T OldCommitValue;
|
|
SIZE_T NewCommitValue;
|
|
|
|
ASSERT ((SSIZE_T)QuotaCharge > 0);
|
|
|
|
ASSERT32 (QuotaCharge < 0x100000);
|
|
|
|
do {
|
|
|
|
OldCommitValue = MmTotalCommittedPages;
|
|
|
|
NewCommitValue = OldCommitValue + QuotaCharge;
|
|
|
|
if (NewCommitValue > MmTotalCommitLimit) {
|
|
return FALSE;
|
|
}
|
|
|
|
#if defined(_WIN64)
|
|
NewCommitValue = InterlockedCompareExchange64 (
|
|
(PLONGLONG) &MmTotalCommittedPages,
|
|
(LONGLONG) NewCommitValue,
|
|
(LONGLONG) OldCommitValue);
|
|
#else
|
|
NewCommitValue = InterlockedCompareExchange (
|
|
(PLONG) &MmTotalCommittedPages,
|
|
(LONG) NewCommitValue,
|
|
(LONG) OldCommitValue);
|
|
#endif
|
|
|
|
} while (NewCommitValue != OldCommitValue);
|
|
|
|
//
|
|
// Success.
|
|
//
|
|
|
|
MM_TRACK_COMMIT (MM_DBG_COMMIT_CHARGE_NORMAL, QuotaCharge);
|
|
|
|
if (MmTotalCommittedPages > MmPeakCommitment) {
|
|
MmPeakCommitment = MmTotalCommittedPages;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
SIZE_T
|
|
MiCalculatePageCommitment (
|
|
IN PVOID StartingAddress,
|
|
IN PVOID EndingAddress,
|
|
IN PMMVAD Vad,
|
|
IN PEPROCESS Process
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine examines the range of pages from the starting address
|
|
up to and including the ending address and returns the commit charge
|
|
for the pages within the range.
|
|
|
|
Arguments:
|
|
|
|
StartingAddress - Supplies the starting address of the range.
|
|
|
|
EndingAddress - Supplies the ending address of the range.
|
|
|
|
Vad - Supplies the virtual address descriptor which describes the range.
|
|
|
|
Process - Supplies the current process.
|
|
|
|
Return Value:
|
|
|
|
Commitment charge for the range.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, APCs disabled, WorkingSetLock and AddressCreation mutexes
|
|
held.
|
|
|
|
--*/
|
|
|
|
{
|
|
PMMPTE PointerPte;
|
|
PMMPTE LastPte;
|
|
PMMPTE PointerPde;
|
|
PMMPTE PointerPpe;
|
|
PMMPTE PointerPxe;
|
|
SIZE_T NumberOfCommittedPages;
|
|
ULONG Waited;
|
|
|
|
PointerPxe = MiGetPxeAddress (StartingAddress);
|
|
PointerPpe = MiGetPpeAddress (StartingAddress);
|
|
PointerPde = MiGetPdeAddress (StartingAddress);
|
|
PointerPte = MiGetPteAddress (StartingAddress);
|
|
|
|
LastPte = MiGetPteAddress (EndingAddress);
|
|
|
|
if (Vad->u.VadFlags.MemCommit == 1) {
|
|
|
|
//
|
|
// All the pages are committed within this range.
|
|
//
|
|
|
|
NumberOfCommittedPages = BYTES_TO_PAGES ((PCHAR)EndingAddress -
|
|
(PCHAR)StartingAddress);
|
|
|
|
//
|
|
// Examine the PTEs to determine how many pages are committed.
|
|
//
|
|
|
|
do {
|
|
|
|
#if (_MI_PAGING_LEVELS >= 4)
|
|
retry:
|
|
#endif
|
|
|
|
while (!MiDoesPxeExistAndMakeValid (PointerPxe,
|
|
Process,
|
|
MM_NOIRQL,
|
|
&Waited)) {
|
|
|
|
//
|
|
// No PXE exists for the starting address, therefore the page
|
|
// is not committed.
|
|
//
|
|
|
|
PointerPxe += 1;
|
|
PointerPpe = MiGetVirtualAddressMappedByPte (PointerPxe);
|
|
PointerPde = MiGetVirtualAddressMappedByPte (PointerPpe);
|
|
PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
|
|
if (PointerPte > LastPte) {
|
|
return NumberOfCommittedPages;
|
|
}
|
|
}
|
|
|
|
#if (_MI_PAGING_LEVELS >= 4)
|
|
Waited = 0;
|
|
#endif
|
|
|
|
while (!MiDoesPpeExistAndMakeValid (PointerPpe,
|
|
Process,
|
|
MM_NOIRQL,
|
|
&Waited)) {
|
|
|
|
//
|
|
// No PPE exists for the starting address, therefore the page
|
|
// is not committed.
|
|
//
|
|
|
|
PointerPpe += 1;
|
|
PointerPde = MiGetVirtualAddressMappedByPte (PointerPpe);
|
|
PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
|
|
if (PointerPte > LastPte) {
|
|
return NumberOfCommittedPages;
|
|
}
|
|
#if (_MI_PAGING_LEVELS >= 4)
|
|
if (MiIsPteOnPdeBoundary (PointerPpe)) {
|
|
PointerPxe = MiGetPteAddress (PointerPpe);
|
|
goto retry;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if (_MI_PAGING_LEVELS < 4)
|
|
Waited = 0;
|
|
#endif
|
|
|
|
while (!MiDoesPdeExistAndMakeValid (PointerPde,
|
|
Process,
|
|
MM_NOIRQL,
|
|
&Waited)) {
|
|
|
|
//
|
|
// No PDE exists for the starting address, therefore the page
|
|
// is not committed.
|
|
//
|
|
|
|
PointerPde += 1;
|
|
PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
|
|
if (PointerPte > LastPte) {
|
|
return NumberOfCommittedPages;
|
|
}
|
|
#if (_MI_PAGING_LEVELS >= 3)
|
|
if (MiIsPteOnPdeBoundary (PointerPde)) {
|
|
PointerPpe = MiGetPteAddress (PointerPde);
|
|
PointerPxe = MiGetPdeAddress (PointerPde);
|
|
Waited = 1;
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
} while (Waited != 0);
|
|
|
|
restart:
|
|
|
|
while (PointerPte <= LastPte) {
|
|
|
|
if (MiIsPteOnPdeBoundary (PointerPte)) {
|
|
|
|
//
|
|
// This is a PDE boundary, check to see if the all the
|
|
// PXE/PPE/PDE pages exist.
|
|
//
|
|
|
|
PointerPde = MiGetPteAddress (PointerPte);
|
|
PointerPpe = MiGetPteAddress (PointerPde);
|
|
PointerPxe = MiGetPteAddress (PointerPpe);
|
|
|
|
do {
|
|
|
|
if (!MiDoesPxeExistAndMakeValid (PointerPxe,
|
|
Process,
|
|
MM_NOIRQL,
|
|
&Waited)) {
|
|
|
|
//
|
|
// No PDE exists for the starting address, check the VAD
|
|
// to see if the pages are not committed.
|
|
//
|
|
|
|
PointerPxe += 1;
|
|
PointerPpe = MiGetVirtualAddressMappedByPte (PointerPxe);
|
|
PointerPde = MiGetVirtualAddressMappedByPte (PointerPpe);
|
|
PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
|
|
|
|
//
|
|
// Check next page.
|
|
//
|
|
|
|
goto restart;
|
|
}
|
|
|
|
#if (_MI_PAGING_LEVELS >= 4)
|
|
Waited = 0;
|
|
#endif
|
|
|
|
if (!MiDoesPpeExistAndMakeValid (PointerPpe,
|
|
Process,
|
|
MM_NOIRQL,
|
|
&Waited)) {
|
|
|
|
//
|
|
// No PDE exists for the starting address, check the VAD
|
|
// to see if the pages are not committed.
|
|
//
|
|
|
|
PointerPpe += 1;
|
|
PointerPxe = MiGetPteAddress (PointerPpe);
|
|
PointerPde = MiGetVirtualAddressMappedByPte (PointerPpe);
|
|
PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
|
|
|
|
//
|
|
// Check next page.
|
|
//
|
|
|
|
goto restart;
|
|
}
|
|
|
|
#if (_MI_PAGING_LEVELS < 4)
|
|
Waited = 0;
|
|
#endif
|
|
|
|
if (!MiDoesPdeExistAndMakeValid (PointerPde,
|
|
Process,
|
|
MM_NOIRQL,
|
|
&Waited)) {
|
|
|
|
//
|
|
// No PDE exists for the starting address, check the VAD
|
|
// to see if the pages are not committed.
|
|
//
|
|
|
|
PointerPde += 1;
|
|
PointerPpe = MiGetPteAddress (PointerPde);
|
|
PointerPxe = MiGetPteAddress (PointerPpe);
|
|
PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
|
|
|
|
//
|
|
// Check next page.
|
|
//
|
|
|
|
goto restart;
|
|
}
|
|
} while (Waited != 0);
|
|
}
|
|
|
|
//
|
|
// The PDE exists, examine the PTE.
|
|
//
|
|
|
|
if (PointerPte->u.Long != 0) {
|
|
|
|
//
|
|
// Has this page been explicitly decommitted?
|
|
//
|
|
|
|
if (MiIsPteDecommittedPage (PointerPte)) {
|
|
|
|
//
|
|
// This page is decommitted, remove it from the count.
|
|
//
|
|
|
|
NumberOfCommittedPages -= 1;
|
|
|
|
}
|
|
}
|
|
|
|
PointerPte += 1;
|
|
}
|
|
|
|
return NumberOfCommittedPages;
|
|
}
|
|
|
|
//
|
|
// Examine non committed range.
|
|
//
|
|
|
|
NumberOfCommittedPages = 0;
|
|
|
|
do {
|
|
|
|
#if (_MI_PAGING_LEVELS >= 4)
|
|
retry2:
|
|
#endif
|
|
while (!MiDoesPxeExistAndMakeValid (PointerPxe,
|
|
Process,
|
|
MM_NOIRQL,
|
|
&Waited)) {
|
|
|
|
|
|
//
|
|
// No PXE exists for the starting address, therefore the page
|
|
// is not committed.
|
|
//
|
|
|
|
PointerPxe += 1;
|
|
PointerPpe = MiGetVirtualAddressMappedByPte (PointerPxe);
|
|
PointerPde = MiGetVirtualAddressMappedByPte (PointerPpe);
|
|
PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
|
|
if (PointerPte > LastPte) {
|
|
return NumberOfCommittedPages;
|
|
}
|
|
}
|
|
|
|
#if (_MI_PAGING_LEVELS >= 4)
|
|
Waited = 0;
|
|
#endif
|
|
|
|
while (!MiDoesPpeExistAndMakeValid (PointerPpe,
|
|
Process,
|
|
MM_NOIRQL,
|
|
&Waited)) {
|
|
|
|
|
|
//
|
|
// No PPE exists for the starting address, therefore the page
|
|
// is not committed.
|
|
//
|
|
|
|
PointerPpe += 1;
|
|
PointerPde = MiGetVirtualAddressMappedByPte (PointerPpe);
|
|
PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
|
|
if (PointerPte > LastPte) {
|
|
return NumberOfCommittedPages;
|
|
}
|
|
#if (_MI_PAGING_LEVELS >= 4)
|
|
if (MiIsPteOnPdeBoundary (PointerPpe)) {
|
|
PointerPxe = MiGetPteAddress (PointerPpe);
|
|
goto retry2;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if (_MI_PAGING_LEVELS < 4)
|
|
Waited = 0;
|
|
#endif
|
|
|
|
while (!MiDoesPdeExistAndMakeValid (PointerPde,
|
|
Process,
|
|
MM_NOIRQL,
|
|
&Waited)) {
|
|
|
|
//
|
|
// No PDE exists for the starting address, therefore the page
|
|
// is not committed.
|
|
//
|
|
|
|
PointerPde += 1;
|
|
PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
|
|
if (PointerPte > LastPte) {
|
|
return NumberOfCommittedPages;
|
|
}
|
|
#if (_MI_PAGING_LEVELS >= 3)
|
|
if (MiIsPteOnPdeBoundary (PointerPde)) {
|
|
PointerPpe = MiGetPteAddress (PointerPde);
|
|
PointerPxe = MiGetPdeAddress (PointerPde);
|
|
Waited = 1;
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
} while (Waited != 0);
|
|
|
|
restart2:
|
|
|
|
while (PointerPte <= LastPte) {
|
|
|
|
if (MiIsPteOnPdeBoundary (PointerPte)) {
|
|
|
|
//
|
|
// This is a PDE boundary, check to see if the entire
|
|
// PXE/PPE/PDE pages exist.
|
|
//
|
|
|
|
PointerPde = MiGetPteAddress (PointerPte);
|
|
PointerPpe = MiGetPteAddress (PointerPde);
|
|
PointerPxe = MiGetPdeAddress (PointerPde);
|
|
|
|
do {
|
|
|
|
if (!MiDoesPxeExistAndMakeValid (PointerPxe,
|
|
Process,
|
|
MM_NOIRQL,
|
|
&Waited)) {
|
|
|
|
//
|
|
// No PXE exists for the starting address, check the VAD
|
|
// to see if the pages are not committed.
|
|
//
|
|
|
|
PointerPxe += 1;
|
|
PointerPpe = MiGetVirtualAddressMappedByPte (PointerPxe);
|
|
PointerPde = MiGetVirtualAddressMappedByPte (PointerPpe);
|
|
PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
|
|
|
|
//
|
|
// Check next page.
|
|
//
|
|
|
|
goto restart2;
|
|
}
|
|
|
|
#if (_MI_PAGING_LEVELS >= 4)
|
|
Waited = 0;
|
|
#endif
|
|
|
|
if (!MiDoesPpeExistAndMakeValid (PointerPpe,
|
|
Process,
|
|
MM_NOIRQL,
|
|
&Waited)) {
|
|
|
|
//
|
|
// No PPE exists for the starting address, check the VAD
|
|
// to see if the pages are not committed.
|
|
//
|
|
|
|
PointerPpe += 1;
|
|
PointerPxe = MiGetPteAddress (PointerPpe);
|
|
PointerPde = MiGetVirtualAddressMappedByPte (PointerPpe);
|
|
PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
|
|
|
|
//
|
|
// Check next page.
|
|
//
|
|
|
|
goto restart2;
|
|
}
|
|
|
|
#if (_MI_PAGING_LEVELS < 4)
|
|
Waited = 0;
|
|
#endif
|
|
|
|
if (!MiDoesPdeExistAndMakeValid (PointerPde,
|
|
Process,
|
|
MM_NOIRQL,
|
|
&Waited)) {
|
|
|
|
//
|
|
// No PDE exists for the starting address, check the VAD
|
|
// to see if the pages are not committed.
|
|
//
|
|
|
|
PointerPde += 1;
|
|
PointerPpe = MiGetPteAddress (PointerPde);
|
|
PointerPxe = MiGetPteAddress (PointerPpe);
|
|
PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
|
|
|
|
//
|
|
// Check next page.
|
|
//
|
|
|
|
goto restart2;
|
|
}
|
|
|
|
} while (Waited != 0);
|
|
}
|
|
|
|
//
|
|
// The PDE exists, examine the PTE.
|
|
//
|
|
|
|
if ((PointerPte->u.Long != 0) &&
|
|
(!MiIsPteDecommittedPage (PointerPte))) {
|
|
|
|
//
|
|
// This page is committed, count it.
|
|
//
|
|
|
|
NumberOfCommittedPages += 1;
|
|
}
|
|
|
|
PointerPte += 1;
|
|
}
|
|
|
|
return NumberOfCommittedPages;
|
|
}
|
|
|
|
VOID
|
|
MiReturnPageTablePageCommitment (
|
|
IN PVOID StartingAddress,
|
|
IN PVOID EndingAddress,
|
|
IN PEPROCESS CurrentProcess,
|
|
IN PMMVAD PreviousVad,
|
|
IN PMMVAD NextVad
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine returns commitment for COMPLETE page table pages which
|
|
span the virtual address range. For example (assuming 4k pages),
|
|
if the StartingAddress = 64k and the EndingAddress = 5mb, no
|
|
page table charges would be freed as a complete page table page is
|
|
not covered by the range. However, if the StartingAddress was 4mb
|
|
and the EndingAddress was 9mb, 1 page table page would be freed.
|
|
|
|
Arguments:
|
|
|
|
StartingAddress - Supplies the starting address of the range.
|
|
|
|
EndingAddress - Supplies the ending address of the range.
|
|
|
|
CurrentProcess - Supplies a pointer to the current process.
|
|
|
|
PreviousVad - Supplies a pointer to the previous VAD, NULL if none.
|
|
|
|
NextVad - Supplies a pointer to the next VAD, NULL if none.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, APCs disabled, WorkingSetLock and AddressCreation mutexes
|
|
held.
|
|
|
|
--*/
|
|
|
|
{
|
|
RTL_BITMAP VadBitMap;
|
|
ULONG NumberToClear;
|
|
ULONG StartBit;
|
|
ULONG EndBit;
|
|
LONG FirstPage;
|
|
LONG LastPage;
|
|
LONG PreviousPage;
|
|
LONG NextPage;
|
|
#if (_MI_PAGING_LEVELS >= 3)
|
|
LONG FirstPdPage;
|
|
LONG LastPdPage;
|
|
LONG PreviousPdPage;
|
|
LONG NextPdPage;
|
|
#endif
|
|
#if (_MI_PAGING_LEVELS >= 4)
|
|
LONG FirstPpPage;
|
|
LONG LastPpPage;
|
|
LONG PreviousPpPage;
|
|
LONG NextPpPage;
|
|
#endif
|
|
|
|
//
|
|
// Check to see if any page table pages would be freed.
|
|
//
|
|
|
|
ASSERT (StartingAddress != EndingAddress);
|
|
|
|
StartBit = (ULONG) (((ULONG_PTR) MI_64K_ALIGN (StartingAddress)) / X64K);
|
|
EndBit = (ULONG) (((ULONG_PTR) MI_64K_ALIGN (EndingAddress)) / X64K);
|
|
|
|
if (PreviousVad == NULL) {
|
|
PreviousPage = -1;
|
|
#if (_MI_PAGING_LEVELS >= 3)
|
|
PreviousPdPage = -1;
|
|
#endif
|
|
#if (_MI_PAGING_LEVELS >= 4)
|
|
PreviousPpPage = -1;
|
|
#endif
|
|
}
|
|
else {
|
|
PreviousPage = MiGetPdeIndex (MI_VPN_TO_VA (PreviousVad->EndingVpn));
|
|
#if (_MI_PAGING_LEVELS >= 3)
|
|
PreviousPdPage = MiGetPpeIndex (MI_VPN_TO_VA (PreviousVad->EndingVpn));
|
|
#endif
|
|
#if (_MI_PAGING_LEVELS >= 4)
|
|
PreviousPpPage = MiGetPxeIndex (MI_VPN_TO_VA (PreviousVad->EndingVpn));
|
|
#endif
|
|
if (MI_64K_ALIGN (MI_VPN_TO_VA (PreviousVad->EndingVpn)) ==
|
|
MI_64K_ALIGN (StartingAddress)) {
|
|
StartBit += 1;
|
|
}
|
|
}
|
|
|
|
if (NextVad == NULL) {
|
|
NextPage = MiGetPdeIndex (MM_HIGHEST_USER_ADDRESS) + 1;
|
|
#if (_MI_PAGING_LEVELS >= 3)
|
|
NextPdPage = MiGetPpeIndex (MM_HIGHEST_USER_ADDRESS) + 1;
|
|
#endif
|
|
#if (_MI_PAGING_LEVELS >= 4)
|
|
NextPpPage = MiGetPxeIndex (MM_HIGHEST_USER_ADDRESS) + 1;
|
|
#endif
|
|
}
|
|
else {
|
|
NextPage = MiGetPdeIndex (MI_VPN_TO_VA (NextVad->StartingVpn));
|
|
#if (_MI_PAGING_LEVELS >= 3)
|
|
NextPdPage = MiGetPpeIndex (MI_VPN_TO_VA (NextVad->StartingVpn));
|
|
#endif
|
|
#if (_MI_PAGING_LEVELS >= 4)
|
|
NextPpPage = MiGetPxeIndex (MI_VPN_TO_VA (NextVad->StartingVpn));
|
|
#endif
|
|
if (MI_64K_ALIGN (MI_VPN_TO_VA (NextVad->StartingVpn)) ==
|
|
MI_64K_ALIGN (EndingAddress)) {
|
|
EndBit -= 1;
|
|
}
|
|
}
|
|
|
|
ASSERT (PreviousPage <= NextPage);
|
|
ASSERT64 (PreviousPdPage <= NextPdPage);
|
|
#if (_MI_PAGING_LEVELS >= 4)
|
|
ASSERT64 (PreviousPpPage <= NextPpPage);
|
|
#endif
|
|
|
|
FirstPage = MiGetPdeIndex (StartingAddress);
|
|
|
|
LastPage = MiGetPdeIndex (EndingAddress);
|
|
|
|
if (PreviousPage == FirstPage) {
|
|
|
|
//
|
|
// A VAD is within the starting page table page.
|
|
//
|
|
|
|
FirstPage += 1;
|
|
}
|
|
|
|
if (NextPage == LastPage) {
|
|
|
|
//
|
|
// A VAD is within the ending page table page.
|
|
//
|
|
|
|
LastPage -= 1;
|
|
}
|
|
|
|
if (StartBit <= EndBit) {
|
|
|
|
//
|
|
// Initialize the bitmap inline for speed.
|
|
//
|
|
|
|
VadBitMap.SizeOfBitMap = MiLastVadBit + 1;
|
|
VadBitMap.Buffer = VAD_BITMAP_SPACE;
|
|
|
|
#if defined (_WIN64) || defined (_X86PAE_)
|
|
|
|
//
|
|
// Only the first (PAGE_SIZE*8*64K) of VA space on NT64 is bitmapped.
|
|
//
|
|
|
|
if (EndBit > MiLastVadBit) {
|
|
EndBit = MiLastVadBit;
|
|
}
|
|
|
|
if (StartBit <= MiLastVadBit) {
|
|
RtlClearBits (&VadBitMap, StartBit, EndBit - StartBit + 1);
|
|
|
|
if (MmWorkingSetList->VadBitMapHint > StartBit) {
|
|
MmWorkingSetList->VadBitMapHint = StartBit;
|
|
}
|
|
}
|
|
#else
|
|
RtlClearBits (&VadBitMap, StartBit, EndBit - StartBit + 1);
|
|
|
|
if (MmWorkingSetList->VadBitMapHint > StartBit) {
|
|
MmWorkingSetList->VadBitMapHint = StartBit;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//
|
|
// Indicate that the page table page is not in use.
|
|
//
|
|
|
|
if (FirstPage > LastPage) {
|
|
return;
|
|
}
|
|
|
|
NumberToClear = 1 + LastPage - FirstPage;
|
|
|
|
while (FirstPage <= LastPage) {
|
|
ASSERT (MI_CHECK_BIT (MmWorkingSetList->CommittedPageTables,
|
|
FirstPage));
|
|
|
|
MI_CLEAR_BIT (MmWorkingSetList->CommittedPageTables, FirstPage);
|
|
FirstPage += 1;
|
|
}
|
|
|
|
MmWorkingSetList->NumberOfCommittedPageTables -= NumberToClear;
|
|
|
|
#if (_MI_PAGING_LEVELS >= 4)
|
|
|
|
//
|
|
// Return page directory parent charges here.
|
|
//
|
|
|
|
FirstPpPage = MiGetPxeIndex (StartingAddress);
|
|
|
|
LastPpPage = MiGetPxeIndex (EndingAddress);
|
|
|
|
if (PreviousPpPage == FirstPpPage) {
|
|
|
|
//
|
|
// A VAD is within the starting page directory parent page.
|
|
//
|
|
|
|
FirstPpPage += 1;
|
|
}
|
|
|
|
if (NextPpPage == LastPpPage) {
|
|
|
|
//
|
|
// A VAD is within the ending page directory parent page.
|
|
//
|
|
|
|
LastPpPage -= 1;
|
|
}
|
|
|
|
//
|
|
// Indicate that the page directory page parent is not in use.
|
|
//
|
|
|
|
if (FirstPpPage <= LastPpPage) {
|
|
|
|
MmWorkingSetList->NumberOfCommittedPageDirectoryParents -= (1 + LastPpPage - FirstPpPage);
|
|
|
|
NumberToClear += (1 + LastPpPage - FirstPpPage);
|
|
|
|
while (FirstPpPage <= LastPpPage) {
|
|
ASSERT (MI_CHECK_BIT (MmWorkingSetList->CommittedPageDirectoryParents,
|
|
FirstPpPage));
|
|
|
|
MI_CLEAR_BIT (MmWorkingSetList->CommittedPageDirectoryParents, FirstPpPage);
|
|
FirstPpPage += 1;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
#if (_MI_PAGING_LEVELS >= 3)
|
|
|
|
//
|
|
// Return page directory charges here.
|
|
//
|
|
|
|
FirstPdPage = MiGetPpeIndex (StartingAddress);
|
|
|
|
LastPdPage = MiGetPpeIndex (EndingAddress);
|
|
|
|
if (PreviousPdPage == FirstPdPage) {
|
|
|
|
//
|
|
// A VAD is within the starting page directory page.
|
|
//
|
|
|
|
FirstPdPage += 1;
|
|
}
|
|
|
|
if (NextPdPage == LastPdPage) {
|
|
|
|
//
|
|
// A VAD is within the ending page directory page.
|
|
//
|
|
|
|
LastPdPage -= 1;
|
|
}
|
|
|
|
//
|
|
// Indicate that the page directory page is not in use.
|
|
//
|
|
|
|
if (FirstPdPage <= LastPdPage) {
|
|
|
|
MmWorkingSetList->NumberOfCommittedPageDirectories -= (1 + LastPdPage - FirstPdPage);
|
|
|
|
NumberToClear += (1 + LastPdPage - FirstPdPage);
|
|
|
|
while (FirstPdPage <= LastPdPage) {
|
|
ASSERT (MI_CHECK_BIT (MmWorkingSetList->CommittedPageDirectories,
|
|
FirstPdPage));
|
|
|
|
MI_CLEAR_BIT (MmWorkingSetList->CommittedPageDirectories, FirstPdPage);
|
|
FirstPdPage += 1;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
MiReturnCommitment (NumberToClear);
|
|
MM_TRACK_COMMIT (MM_DBG_COMMIT_RETURN_PAGETABLES, NumberToClear);
|
|
PsReturnProcessPageFileQuota (CurrentProcess, NumberToClear);
|
|
|
|
if (CurrentProcess->JobStatus & PS_JOB_STATUS_REPORT_COMMIT_CHANGES) {
|
|
PsChangeJobMemoryUsage(PS_JOB_STATUS_REPORT_COMMIT_CHANGES, -(SSIZE_T)NumberToClear);
|
|
}
|
|
CurrentProcess->CommitCharge -= NumberToClear;
|
|
|
|
MI_INCREMENT_TOTAL_PROCESS_COMMIT (0 - NumberToClear);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
MiCauseOverCommitPopup (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function causes an over commit popup to occur (if the popup has never
|
|
been sent before).
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
LONG PopupNumber;
|
|
|
|
//
|
|
// Give the user a meaningful message - either to increase the minimum,
|
|
// maximum, or both.
|
|
//
|
|
|
|
if (MmTotalCommittedPages > MmTotalCommitLimitMaximum - 100) {
|
|
if (InterlockedIncrement (&MiCommitPopups[0]) > 1) {
|
|
InterlockedDecrement (&MiCommitPopups[0]);
|
|
return;
|
|
}
|
|
PopupNumber = STATUS_COMMITMENT_LIMIT;
|
|
}
|
|
else {
|
|
if (InterlockedIncrement (&MiCommitPopups[1]) > 1) {
|
|
InterlockedDecrement (&MiCommitPopups[1]);
|
|
return;
|
|
}
|
|
PopupNumber = STATUS_COMMITMENT_MINIMUM;
|
|
}
|
|
|
|
IoRaiseInformationalHardError (PopupNumber, NULL, NULL);
|
|
}
|
|
|
|
|
|
SIZE_T MmTotalPagedPoolQuota;
|
|
SIZE_T MmTotalNonPagedPoolQuota;
|
|
|
|
BOOLEAN
|
|
MmRaisePoolQuota(
|
|
IN POOL_TYPE PoolType,
|
|
IN SIZE_T OldQuotaLimit,
|
|
OUT PSIZE_T NewQuotaLimit
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function is called (with a spinlock) whenever PS detects a quota
|
|
limit has been exceeded. The purpose of this function is to attempt to
|
|
increase the specified quota.
|
|
|
|
Arguments:
|
|
|
|
PoolType - Supplies the pool type of the quota to be raised
|
|
|
|
OldQuotaLimit - Supplies the current quota limit for this pool type
|
|
|
|
NewQuotaLimit - Returns the new limit
|
|
|
|
Return Value:
|
|
|
|
TRUE - The API succeeded and the quota limit was raised.
|
|
|
|
FALSE - We were unable to raise the quota limit.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, QUOTA SPIN LOCK HELD!!
|
|
|
|
--*/
|
|
|
|
{
|
|
SIZE_T Limit;
|
|
PMM_PAGED_POOL_INFO PagedPoolInfo;
|
|
|
|
if (PoolType == PagedPool) {
|
|
|
|
//
|
|
// Check commit limit and make sure at least 1mb is available.
|
|
// Check to make sure 4mb of paged pool still exists.
|
|
//
|
|
|
|
PagedPoolInfo = &MmPagedPoolInfo;
|
|
|
|
if (MmSizeOfPagedPoolInPages <
|
|
(PagedPoolInfo->AllocatedPagedPool + ((MMPAGED_QUOTA_CHECK) >> PAGE_SHIFT))) {
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
MmTotalPagedPoolQuota += (MMPAGED_QUOTA_INCREASE);
|
|
*NewQuotaLimit = OldQuotaLimit + (MMPAGED_QUOTA_INCREASE);
|
|
return TRUE;
|
|
|
|
} else {
|
|
|
|
if ( (ULONG_PTR)(MmAllocatedNonPagedPool + ((1*1024*1024) >> PAGE_SHIFT)) < MmMaximumNonPagedPoolInPages) {
|
|
goto aok;
|
|
}
|
|
|
|
//
|
|
// Make sure 200 pages and 5mb of nonpaged pool expansion
|
|
// available. Raise quota by 64k.
|
|
//
|
|
|
|
if ((MmAvailablePages < 200) ||
|
|
(MmResidentAvailablePages < ((MMNONPAGED_QUOTA_CHECK) >> PAGE_SHIFT))) {
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
if (MmAvailablePages > ((4*1024*1024) >> PAGE_SHIFT)) {
|
|
Limit = (1*1024*1024) >> PAGE_SHIFT;
|
|
} else {
|
|
Limit = (4*1024*1024) >> PAGE_SHIFT;
|
|
}
|
|
|
|
if (MmMaximumNonPagedPoolInPages < MmAllocatedNonPagedPool + Limit) {
|
|
|
|
return FALSE;
|
|
}
|
|
aok:
|
|
MmTotalNonPagedPoolQuota += (MMNONPAGED_QUOTA_INCREASE);
|
|
*NewQuotaLimit = OldQuotaLimit + (MMNONPAGED_QUOTA_INCREASE);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
MmReturnPoolQuota(
|
|
IN POOL_TYPE PoolType,
|
|
IN SIZE_T ReturnedQuota
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Returns pool quota.
|
|
|
|
Arguments:
|
|
|
|
PoolType - Supplies the pool type of the quota to be returned.
|
|
|
|
ReturnedQuota - Number of bytes returned.
|
|
|
|
Return Value:
|
|
|
|
NONE.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, QUOTA SPIN LOCK HELD!!
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
if (PoolType == PagedPool) {
|
|
MmTotalPagedPoolQuota -= ReturnedQuota;
|
|
} else {
|
|
MmTotalNonPagedPoolQuota -= ReturnedQuota;
|
|
}
|
|
|
|
return;
|
|
}
|