mirror of https://github.com/lianthony/NT4.0
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.
1363 lines
37 KiB
1363 lines
37 KiB
/*++
|
|
|
|
Copyright (c) 1989 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
freevm.c
|
|
|
|
Abstract:
|
|
|
|
This module contains the routines which implement the
|
|
NtFreeVirtualMemory service.
|
|
|
|
Author:
|
|
|
|
Lou Perazzoli (loup) 22-May-1989
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "mi.h"
|
|
|
|
#define MEM_CHECK_COMMIT_STATE 0x400000
|
|
|
|
#define MM_VALID_PTE_SIZE (256)
|
|
|
|
|
|
MMPTE MmDecommittedPte = {MM_DECOMMIT << MM_PROTECT_FIELD_SHIFT};
|
|
|
|
#if DBG
|
|
extern PEPROCESS MmWatchProcess;
|
|
VOID MmFooBar(VOID);
|
|
#endif // DBG
|
|
// #include "ntos.h"
|
|
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(PAGE,NtFreeVirtualMemory)
|
|
#pragma alloc_text(PAGE,MiIsEntireRangeCommitted)
|
|
#endif
|
|
|
|
VOID
|
|
MiProcessValidPteList (
|
|
IN PMMPTE *PteList,
|
|
IN ULONG Count
|
|
);
|
|
|
|
ULONG
|
|
MiDecommitPages (
|
|
IN PVOID StartingAddress,
|
|
IN PMMPTE EndingPte,
|
|
IN PEPROCESS Process,
|
|
IN PMMVAD_SHORT Vad
|
|
);
|
|
|
|
VOID
|
|
MiDeleteFreeVm (
|
|
IN PVOID StartingAddress,
|
|
IN PVOID EndingAddress
|
|
);
|
|
|
|
|
|
NTSTATUS
|
|
NtFreeVirtualMemory(
|
|
IN HANDLE ProcessHandle,
|
|
IN OUT PVOID *BaseAddress,
|
|
IN OUT PULONG RegionSize,
|
|
IN ULONG FreeType
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function deletes a region of pages within the virtual address
|
|
space of a subject process.
|
|
|
|
Arguments:
|
|
|
|
ProcessHandle - An open handle to a process object.
|
|
|
|
BaseAddress - The base address of the region of pages
|
|
to be freed. This value is rounded down to the
|
|
next host page address boundary.
|
|
|
|
RegionSize - A pointer to a variable that will receive
|
|
the actual size in bytes of the freed region of
|
|
pages. The initial value of this argument is
|
|
rounded up to the next host page size boundary.
|
|
|
|
FreeType - A set of flags that describe the type of
|
|
free that is to be performed for the specified
|
|
region of pages.
|
|
|
|
|
|
FreeType Flags
|
|
|
|
|
|
MEM_DECOMMIT - The specified region of pages is to
|
|
be decommitted.
|
|
|
|
MEM_RELEASE - The specified region of pages is to
|
|
be released.
|
|
|
|
|
|
Return Value:
|
|
|
|
Returns the status
|
|
|
|
TBS
|
|
|
|
|
|
--*/
|
|
|
|
{
|
|
PMMVAD_SHORT Vad;
|
|
PMMVAD_SHORT NewVad;
|
|
PMMVAD PreviousVad;
|
|
PMMVAD NextVad;
|
|
PEPROCESS Process;
|
|
KPROCESSOR_MODE PreviousMode;
|
|
PVOID StartingAddress;
|
|
PVOID EndingAddress;
|
|
NTSTATUS Status;
|
|
ULONG Attached = FALSE;
|
|
ULONG CapturedRegionSize;
|
|
PVOID CapturedBase;
|
|
PMMPTE StartingPte;
|
|
PMMPTE EndingPte;
|
|
ULONG OldQuota;
|
|
ULONG QuotaCharge;
|
|
ULONG CommitReduction;
|
|
PVOID OldEnd;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Check to make sure FreeType is good.
|
|
//
|
|
|
|
if ((FreeType & ~(MEM_DECOMMIT | MEM_RELEASE)) != 0) {
|
|
return STATUS_INVALID_PARAMETER_4;
|
|
}
|
|
|
|
//
|
|
// One of MEM_DECOMMIT or MEM_RELEASE must be specified, but not both.
|
|
//
|
|
|
|
if (((FreeType & (MEM_DECOMMIT | MEM_RELEASE)) == 0) ||
|
|
((FreeType & (MEM_DECOMMIT | MEM_RELEASE)) ==
|
|
(MEM_DECOMMIT | MEM_RELEASE))) {
|
|
return STATUS_INVALID_PARAMETER_4;
|
|
}
|
|
|
|
PreviousMode = KeGetPreviousMode();
|
|
|
|
//
|
|
// Establish an exception handler, probe the specified addresses
|
|
// for write access and capture the initial values.
|
|
//
|
|
|
|
try {
|
|
|
|
if (PreviousMode != KernelMode) {
|
|
|
|
ProbeForWriteUlong ((PULONG)BaseAddress);
|
|
ProbeForWriteUlong (RegionSize);
|
|
}
|
|
|
|
//
|
|
// Capture the base address.
|
|
//
|
|
|
|
CapturedBase = *BaseAddress;
|
|
|
|
//
|
|
// Capture the region size.
|
|
//
|
|
|
|
CapturedRegionSize = *RegionSize;
|
|
|
|
} except (ExSystemExceptionFilter()) {
|
|
|
|
//
|
|
// If an exception occurs during the probe or capture
|
|
// of the initial values, then handle the exception and
|
|
// return the exception code as the status value.
|
|
//
|
|
|
|
return GetExceptionCode();
|
|
}
|
|
|
|
#if DBG
|
|
if (MmDebug & MM_DBG_SHOW_NT_CALLS) {
|
|
if ( !MmWatchProcess ) {
|
|
DbgPrint("freevm processhandle %lx base %lx size %lx type %lx\n",
|
|
ProcessHandle, CapturedBase, CapturedRegionSize, FreeType);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Make sure the specified starting and ending addresses are
|
|
// within the user part of the virtual address space.
|
|
//
|
|
|
|
if (CapturedBase > MM_HIGHEST_USER_ADDRESS) {
|
|
|
|
//
|
|
// Invalid base address.
|
|
//
|
|
|
|
return STATUS_INVALID_PARAMETER_2;
|
|
}
|
|
|
|
if ((ULONG)MM_HIGHEST_USER_ADDRESS - (ULONG)CapturedBase <
|
|
CapturedRegionSize) {
|
|
|
|
//
|
|
// Invalid region size;
|
|
//
|
|
|
|
return STATUS_INVALID_PARAMETER_3;
|
|
|
|
}
|
|
|
|
EndingAddress = (PVOID)(((ULONG)CapturedBase + CapturedRegionSize - 1) |
|
|
(PAGE_SIZE - 1));
|
|
|
|
StartingAddress = (PVOID)PAGE_ALIGN(CapturedBase);
|
|
|
|
if ( ProcessHandle == NtCurrentProcess() ) {
|
|
Process = PsGetCurrentProcess();
|
|
} else {
|
|
//
|
|
// Reference the specified process handle for VM_OPERATION access.
|
|
//
|
|
|
|
Status = ObReferenceObjectByHandle ( ProcessHandle,
|
|
PROCESS_VM_OPERATION,
|
|
PsProcessType,
|
|
PreviousMode,
|
|
(PVOID *)&Process,
|
|
NULL );
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the specified process is not the current process, attach
|
|
// to the specified process.
|
|
//
|
|
|
|
if (PsGetCurrentProcess() != Process) {
|
|
KeAttachProcess (&Process->Pcb);
|
|
Attached = TRUE;
|
|
}
|
|
|
|
//
|
|
// Get the address creation mutex to block multiple threads from
|
|
// creating or deleting address space at the same time and
|
|
// get the working set mutex so virtual address descriptors can
|
|
// be inserted and walked. Block APCs to prevent page faults while
|
|
// we own the working set mutex.
|
|
//
|
|
|
|
LOCK_WS_AND_ADDRESS_SPACE (Process);
|
|
|
|
//
|
|
// Make sure the address space was not deleted.
|
|
//
|
|
|
|
if (Process->AddressSpaceDeleted != 0) {
|
|
Status = STATUS_PROCESS_IS_TERMINATING;
|
|
goto ErrorReturn;
|
|
}
|
|
|
|
Vad = (PMMVAD_SHORT)MiLocateAddress (StartingAddress);
|
|
|
|
if (Vad == NULL) {
|
|
|
|
//
|
|
// No Virtual Address Descriptor located for Base Address.
|
|
//
|
|
|
|
Status = STATUS_MEMORY_NOT_ALLOCATED;
|
|
goto ErrorReturn;
|
|
}
|
|
|
|
//
|
|
// Found the associated Virtual Address Descriptor.
|
|
//
|
|
|
|
if (Vad->EndingVa < EndingAddress) {
|
|
|
|
//
|
|
// The entire range to delete is not contained within a single
|
|
// virtual address descriptor. Return an error.
|
|
//
|
|
|
|
Status = STATUS_UNABLE_TO_FREE_VM;
|
|
goto ErrorReturn;
|
|
}
|
|
|
|
//
|
|
// Check to ensure this Vad is deletable. Delete is required
|
|
// for both decommit and release.
|
|
//
|
|
|
|
if ((Vad->u.VadFlags.PrivateMemory == 0) ||
|
|
(Vad->u.VadFlags.PhysicalMapping == 1)) {
|
|
Status = STATUS_UNABLE_TO_DELETE_SECTION;
|
|
goto ErrorReturn;
|
|
}
|
|
|
|
if (Vad->u.VadFlags.NoChange == 1) {
|
|
|
|
//
|
|
// An attempt is being made to delete a secured VAD, check
|
|
// to see if this deletion is allowed.
|
|
//
|
|
|
|
if (FreeType & MEM_RELEASE) {
|
|
|
|
//
|
|
// Specifiy the whole range, this solves the problem with
|
|
// splitting the VAD and trying to decide where the various
|
|
// secure ranges need to go.
|
|
//
|
|
|
|
Status = MiCheckSecuredVad ((PMMVAD)Vad,
|
|
Vad->StartingVa,
|
|
(PCHAR)Vad->EndingVa - (PCHAR)Vad->StartingVa,
|
|
MM_SECURE_DELETE_CHECK);
|
|
|
|
} else {
|
|
Status = MiCheckSecuredVad ((PMMVAD)Vad,
|
|
CapturedBase,
|
|
CapturedRegionSize,
|
|
MM_SECURE_DELETE_CHECK);
|
|
}
|
|
if (!NT_SUCCESS (Status)) {
|
|
goto ErrorReturn;
|
|
}
|
|
}
|
|
|
|
PreviousVad = MiGetPreviousVad (Vad);
|
|
NextVad = MiGetNextVad (Vad);
|
|
if (FreeType & MEM_RELEASE) {
|
|
|
|
//
|
|
// *****************************************************************
|
|
// MEM_RELEASE was specified.
|
|
// *****************************************************************
|
|
//
|
|
|
|
//
|
|
// The descriptor for the address range is deletable. Remove or split
|
|
// the descriptor.
|
|
//
|
|
|
|
//
|
|
// If the region size is zero, remove the whole VAD.
|
|
//
|
|
|
|
if (CapturedRegionSize == 0) {
|
|
|
|
//
|
|
// If the region size is specified as 0, the base address
|
|
// must be the starting address for the region.
|
|
//
|
|
|
|
if (CapturedBase != Vad->StartingVa) {
|
|
Status = STATUS_FREE_VM_NOT_AT_BASE;
|
|
goto ErrorReturn;
|
|
}
|
|
|
|
//
|
|
// This Virtual Address Descriptor has been deleted.
|
|
//
|
|
|
|
StartingAddress = Vad->StartingVa;
|
|
EndingAddress = Vad->EndingVa;
|
|
MiRemoveVad ((PMMVAD)Vad);
|
|
ExFreePool (Vad);
|
|
|
|
} else {
|
|
|
|
//
|
|
// Regions size was not specified as zero, delete the
|
|
// whole VAD or split the VAD.
|
|
//
|
|
|
|
if (StartingAddress == Vad->StartingVa) {
|
|
if (EndingAddress == Vad->EndingVa) {
|
|
|
|
//
|
|
// This Virtual Address Descriptor has been deleted.
|
|
//
|
|
|
|
MiRemoveVad ((PMMVAD)Vad);
|
|
ExFreePool (Vad);
|
|
|
|
} else {
|
|
|
|
//
|
|
// This Virtual Address Descriptor has a new starting
|
|
// address.
|
|
//
|
|
|
|
CommitReduction = MiCalculatePageCommitment (
|
|
StartingAddress,
|
|
EndingAddress,
|
|
(PMMVAD)Vad,
|
|
Process );
|
|
|
|
Vad->StartingVa = (PVOID)((ULONG)EndingAddress + 1L);
|
|
Vad->u.VadFlags.CommitCharge -= CommitReduction;
|
|
ASSERT ((LONG)Vad->u.VadFlags.CommitCharge >= 0);
|
|
MiReturnPageFileQuota (CommitReduction, Process);
|
|
MiReturnCommitment (CommitReduction);
|
|
Process->CommitCharge -= CommitReduction;
|
|
PreviousVad = (PMMVAD)Vad;
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// Starting address is greater than start of VAD.
|
|
//
|
|
|
|
if (EndingAddress == Vad->EndingVa) {
|
|
|
|
//
|
|
// Change the ending address of the VAD.
|
|
//
|
|
|
|
CommitReduction = MiCalculatePageCommitment (
|
|
StartingAddress,
|
|
EndingAddress,
|
|
(PMMVAD)Vad,
|
|
Process );
|
|
|
|
Vad->u.VadFlags.CommitCharge -= CommitReduction;
|
|
MiReturnPageFileQuota (CommitReduction, Process);
|
|
MiReturnCommitment (CommitReduction);
|
|
Process->CommitCharge -= CommitReduction;
|
|
|
|
Vad->EndingVa = (PVOID)((ULONG)StartingAddress - 1L);
|
|
PreviousVad = (PMMVAD)Vad;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Split this VAD as the address range is within the VAD.
|
|
//
|
|
|
|
//
|
|
// Allocate an new VAD under an exception handler
|
|
// as there may not be enough quota.
|
|
//
|
|
|
|
NewVad = ExAllocatePoolWithTag (NonPagedPool,
|
|
sizeof(MMVAD_SHORT),
|
|
'SdaV');
|
|
if ( NewVad == NULL ) {
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto ErrorReturn;
|
|
}
|
|
|
|
CommitReduction = MiCalculatePageCommitment (
|
|
StartingAddress,
|
|
EndingAddress,
|
|
(PMMVAD)Vad,
|
|
Process );
|
|
|
|
OldQuota = Vad->u.VadFlags.CommitCharge - CommitReduction;
|
|
OldEnd = Vad->EndingVa;
|
|
|
|
*NewVad = *Vad;
|
|
|
|
Vad->EndingVa = (PVOID)((ULONG)StartingAddress - 1L);
|
|
NewVad->StartingVa = (PVOID)((ULONG)EndingAddress + 1L);
|
|
|
|
//
|
|
// Set the commit charge to zero so MiInsertVad will
|
|
// not charge committment for splitting the VAD.
|
|
//
|
|
|
|
NewVad->u.VadFlags.CommitCharge = 0;
|
|
|
|
try {
|
|
|
|
//
|
|
// Insert the VAD, this could get an exception
|
|
// on charging quota.
|
|
//
|
|
|
|
MiInsertVad ((PMMVAD)NewVad);
|
|
|
|
} except (EXCEPTION_EXECUTE_HANDLER) {
|
|
|
|
//
|
|
// Inserting the Vad failed, reset the original
|
|
// VAD, free new vad and return an error.
|
|
//
|
|
|
|
Vad->EndingVa = OldEnd;
|
|
|
|
ExFreePool (NewVad);
|
|
Status = GetExceptionCode();
|
|
goto ErrorReturn;
|
|
}
|
|
|
|
Vad->u.VadFlags.CommitCharge -= CommitReduction;
|
|
MiReturnPageFileQuota (CommitReduction, Process);
|
|
MiReturnCommitment (CommitReduction);
|
|
Process->CommitCharge -= CommitReduction;
|
|
|
|
//
|
|
// As we have split the original VAD into 2 seperate VADs
|
|
// there is know way of knowing what the commit charge
|
|
// is for each VAD. Calculate the charge and reset
|
|
// each VAD. Note that we also use the previous value
|
|
// to make sure the books stay balanced.
|
|
//
|
|
|
|
QuotaCharge = MiCalculatePageCommitment (Vad->StartingVa,
|
|
Vad->EndingVa,
|
|
(PMMVAD)Vad,
|
|
Process );
|
|
|
|
Vad->u.VadFlags.CommitCharge = QuotaCharge;
|
|
|
|
//
|
|
// Give the remaining charge to the new VAD.
|
|
//
|
|
|
|
NewVad->u.VadFlags.CommitCharge = OldQuota - QuotaCharge;
|
|
PreviousVad = (PMMVAD)Vad;
|
|
NextVad = (PMMVAD)NewVad;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Return commitment for page table pages if possibible.
|
|
//
|
|
|
|
MiReturnPageTablePageCommitment (StartingAddress,
|
|
EndingAddress,
|
|
Process,
|
|
PreviousVad,
|
|
NextVad);
|
|
|
|
//
|
|
// Get the PFN mutex so the MiDeleteVirtualAddresses can be called.
|
|
//
|
|
|
|
MiDeleteFreeVm (StartingAddress, EndingAddress);
|
|
UNLOCK_WS (Process);
|
|
|
|
CapturedRegionSize = 1 + (ULONG)EndingAddress - (ULONG)StartingAddress;
|
|
|
|
//
|
|
// Update the virtual size in the process header.
|
|
//
|
|
|
|
Process->VirtualSize -= CapturedRegionSize;
|
|
|
|
UNLOCK_ADDRESS_SPACE (Process);
|
|
|
|
if (Attached) {
|
|
KeDetachProcess();
|
|
}
|
|
|
|
if ( ProcessHandle != NtCurrentProcess() ) {
|
|
ObDereferenceObject (Process);
|
|
}
|
|
//
|
|
// Establish an exception handler and write the size and base
|
|
// address.
|
|
//
|
|
|
|
try {
|
|
|
|
*RegionSize = CapturedRegionSize;
|
|
*BaseAddress = StartingAddress;
|
|
|
|
} except (EXCEPTION_EXECUTE_HANDLER) {
|
|
|
|
//
|
|
// An exception occurred, don't take any action (just handle
|
|
// the exception and return success.
|
|
|
|
}
|
|
|
|
#if DBG
|
|
if (MmDebug & MM_DBG_SHOW_NT_CALLS) {
|
|
if ( MmWatchProcess ) {
|
|
if ( MmWatchProcess == PsGetCurrentProcess() ) {
|
|
DbgPrint("\n--- FREE Type 0x%lx Base %lx Size %lx\n",
|
|
FreeType, StartingAddress, CapturedRegionSize);
|
|
MmFooBar();
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if DBG
|
|
if (RtlAreLogging( RTL_EVENT_CLASS_VM )) {
|
|
RtlLogEvent( MiFreeVmEventId,
|
|
RTL_EVENT_CLASS_VM,
|
|
StartingAddress,
|
|
CapturedRegionSize,
|
|
FreeType
|
|
);
|
|
|
|
}
|
|
#endif // DBG
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// **************************************************************
|
|
//
|
|
// MEM_DECOMMIT was specified.
|
|
//
|
|
// **************************************************************
|
|
//
|
|
|
|
//
|
|
// Check to ensure the complete range of pages is already committed.
|
|
//
|
|
|
|
if (CapturedRegionSize == 0) {
|
|
|
|
if (CapturedBase != Vad->StartingVa) {
|
|
Status = STATUS_FREE_VM_NOT_AT_BASE;
|
|
goto ErrorReturn;
|
|
}
|
|
EndingAddress = Vad->EndingVa;
|
|
}
|
|
|
|
#if 0
|
|
if (FreeType & MEM_CHECK_COMMIT_STATE) {
|
|
if ( !MiIsEntireRangeCommitted(StartingAddress,
|
|
EndingAddress,
|
|
Vad,
|
|
Process)) {
|
|
|
|
//
|
|
// The entire range to be decommited is not committed,
|
|
// return an errror.
|
|
//
|
|
|
|
Status = STATUS_UNABLE_TO_DECOMMIT_VM;
|
|
goto ErrorReturn;
|
|
}
|
|
}
|
|
#endif //0
|
|
|
|
//
|
|
// The address range is entirely committed, decommit it now.
|
|
//
|
|
|
|
//
|
|
// Calculate the initial quotas and commit charges for this VAD.
|
|
//
|
|
|
|
StartingPte = MiGetPteAddress (StartingAddress);
|
|
EndingPte = MiGetPteAddress (EndingAddress);
|
|
|
|
CommitReduction = 1 + EndingPte - StartingPte;
|
|
|
|
//
|
|
// Check to see if the entire range can be decommitted by
|
|
// just updating the virtual address descriptor.
|
|
//
|
|
|
|
CommitReduction -= MiDecommitPages (StartingAddress,
|
|
EndingPte,
|
|
Process,
|
|
Vad);
|
|
|
|
//
|
|
// Adjust the quota charges.
|
|
//
|
|
|
|
ASSERT ((LONG)CommitReduction >= 0);
|
|
MiReturnPageFileQuota (CommitReduction, Process);
|
|
MiReturnCommitment (CommitReduction);
|
|
Vad->u.VadFlags.CommitCharge -= CommitReduction;
|
|
Process->CommitCharge -= CommitReduction;
|
|
ASSERT ((LONG)Vad->u.VadFlags.CommitCharge >= 0);
|
|
|
|
UNLOCK_WS (Process);
|
|
|
|
UNLOCK_ADDRESS_SPACE (Process);
|
|
|
|
if (Attached) {
|
|
KeDetachProcess();
|
|
}
|
|
if ( ProcessHandle != NtCurrentProcess() ) {
|
|
ObDereferenceObject (Process);
|
|
}
|
|
|
|
//
|
|
// Establish an exception handler and write the size and base
|
|
// address.
|
|
//
|
|
|
|
try {
|
|
|
|
*RegionSize = 1 + (ULONG)EndingAddress - (ULONG)StartingAddress;
|
|
*BaseAddress = StartingAddress;
|
|
|
|
} except (EXCEPTION_EXECUTE_HANDLER) {
|
|
NOTHING;
|
|
}
|
|
|
|
#if DBG
|
|
if (RtlAreLogging( RTL_EVENT_CLASS_VM )) {
|
|
RtlLogEvent( MiFreeVmEventId,
|
|
RTL_EVENT_CLASS_VM,
|
|
StartingAddress,
|
|
1 + (ULONG)EndingAddress - (ULONG)StartingAddress,
|
|
FreeType
|
|
);
|
|
|
|
}
|
|
#endif // DBG
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
ErrorReturn:
|
|
UNLOCK_WS (Process);
|
|
UNLOCK_ADDRESS_SPACE (Process);
|
|
|
|
if (Attached) {
|
|
KeDetachProcess();
|
|
}
|
|
|
|
if ( ProcessHandle != NtCurrentProcess() ) {
|
|
ObDereferenceObject (Process);
|
|
}
|
|
return Status;
|
|
}
|
|
|
|
ULONG
|
|
MiIsEntireRangeCommitted (
|
|
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 TRUE if every
|
|
page in the range is committed, FALSE otherwise.
|
|
|
|
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:
|
|
|
|
TRUE if the entire range is committed.
|
|
FALSE if any page within the range is not committed.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, APCs disable, WorkingSetMutex and AddressCreation mutexes
|
|
held.
|
|
|
|
--*/
|
|
|
|
{
|
|
PMMPTE PointerPte;
|
|
PMMPTE LastPte;
|
|
PMMPTE PointerPde;
|
|
ULONG FirstTime = TRUE;
|
|
PVOID Va;
|
|
|
|
PAGED_CODE();
|
|
|
|
PointerPde = MiGetPdeAddress (StartingAddress);
|
|
PointerPte = MiGetPteAddress (StartingAddress);
|
|
LastPte = MiGetPteAddress (EndingAddress);
|
|
|
|
//
|
|
// Set the Va to the starting address + 8, this solves problems
|
|
// associated with address 0 (NULL) being used as a valid virtual
|
|
// address and NULL in the VAD commitment field indicating no pages
|
|
// are committed.
|
|
//
|
|
|
|
Va = (PVOID)((PCHAR)StartingAddress + 8);
|
|
|
|
while (PointerPte <= LastPte) {
|
|
|
|
if ((((ULONG)PointerPte & (PAGE_SIZE - 1)) == 0) ||
|
|
(FirstTime)) {
|
|
|
|
//
|
|
// This is a PDE boundary, check to see if the entire
|
|
// PDE page exists.
|
|
//
|
|
|
|
FirstTime = FALSE;
|
|
PointerPde = MiGetPteAddress (PointerPte);
|
|
|
|
while (!MiDoesPdeExistAndMakeValid(PointerPde, Process, FALSE)) {
|
|
|
|
//
|
|
// No PDE exists for the starting address, check the VAD
|
|
// to see if the pages are committed.
|
|
//
|
|
|
|
PointerPde += 1;
|
|
|
|
PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
|
|
Va = MiGetVirtualAddressMappedByPte (PointerPte);
|
|
|
|
if (PointerPte > LastPte) {
|
|
|
|
//
|
|
// Make sure the entire range is committed.
|
|
//
|
|
|
|
if (Vad->u.VadFlags.MemCommit == 0) {
|
|
|
|
//
|
|
// The entire range to be decommited is not committed,
|
|
// return an errror.
|
|
//
|
|
|
|
return FALSE;
|
|
} else {
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Make sure the range thus far is committed.
|
|
//
|
|
|
|
if (Vad->u.VadFlags.MemCommit == 0) {
|
|
|
|
//
|
|
// The entire range to be decommited is not committed,
|
|
// return an errror.
|
|
//
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// The page table page exists, check each PTE for commitment.
|
|
//
|
|
|
|
if (PointerPte->u.Long == 0) {
|
|
|
|
//
|
|
// This page has not been committed, check the VAD.
|
|
//
|
|
|
|
if (Vad->u.VadFlags.MemCommit == 0) {
|
|
|
|
//
|
|
// The entire range to be decommited is not committed,
|
|
// return an errror.
|
|
//
|
|
|
|
return FALSE;
|
|
}
|
|
} else {
|
|
|
|
//
|
|
// Has this page been explicitly decommited?
|
|
//
|
|
|
|
if (MiIsPteDecommittedPage (PointerPte)) {
|
|
|
|
//
|
|
// This page has been explicitly decommitted, return an error.
|
|
//
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
PointerPte += 1;
|
|
Va = (PVOID)((PCHAR)(Va) + PAGE_SIZE);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
ULONG
|
|
MiDecommitPages (
|
|
IN PVOID StartingAddress,
|
|
IN PMMPTE EndingPte,
|
|
IN PEPROCESS Process,
|
|
IN PMMVAD_SHORT Vad
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine decommits the specficed range of pages.
|
|
|
|
Arguments:
|
|
|
|
StartingAddress - Supplies the starting address of the range.
|
|
|
|
EndingPte - Supplies the ending PTE of the range.
|
|
|
|
Process - Supplies the current process.
|
|
|
|
Vad - Supplies the virtual address descriptor which describes the range.
|
|
|
|
Return Value:
|
|
|
|
Value to reduce commitment by for the VAD.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, APCs disable, WorkingSetMutex and AddressCreation mutexes
|
|
held.
|
|
|
|
--*/
|
|
|
|
{
|
|
PMMPTE PointerPde;
|
|
PMMPTE PointerPte;
|
|
PVOID Va;
|
|
ULONG PdeOffset;
|
|
ULONG CommitReduction = 0;
|
|
PMMPTE CommitLimitPte;
|
|
KIRQL OldIrql;
|
|
PMMPTE ValidPteList[MM_VALID_PTE_SIZE];
|
|
ULONG count = 0;
|
|
ULONG WorkingSetIndex;
|
|
PMMPFN Pfn1;
|
|
PMMPFN Pfn2;
|
|
PVOID SwapVa;
|
|
ULONG Entry;
|
|
MMWSLENTRY Locked;
|
|
MMPTE PteContents;
|
|
|
|
if (Vad->u.VadFlags.MemCommit) {
|
|
CommitLimitPte = MiGetPteAddress (Vad->EndingVa);
|
|
} else {
|
|
CommitLimitPte = NULL;
|
|
}
|
|
|
|
//
|
|
// Decommit each page by setting the PTE to be explicitly
|
|
// decommitted. The PTEs cannot be deleted all at once as
|
|
// this would set the PTEs to zero which would auto-evaluate
|
|
// as committed if referenced by another thread when a page
|
|
// table page is being in-paged.
|
|
//
|
|
|
|
PointerPde = MiGetPdeAddress (StartingAddress);
|
|
PointerPte = MiGetPteAddress (StartingAddress);
|
|
Va = StartingAddress;
|
|
PdeOffset = MiGetPdeOffset (Va);
|
|
|
|
//
|
|
// Loop through all the PDEs which map this region and ensure that
|
|
// they exist. If they don't exist create them by touching a
|
|
// PTE mapped by the PDE.
|
|
//
|
|
|
|
//
|
|
// Get the PFN mutex so the MiDeletePte can be called.
|
|
//
|
|
|
|
MiMakePdeExistAndMakeValid(PointerPde, Process, FALSE);
|
|
|
|
while (PointerPte <= EndingPte) {
|
|
|
|
if (((ULONG)PointerPte & (PAGE_SIZE - 1)) == 0) {
|
|
|
|
PdeOffset = MiGetPdeOffset (Va);
|
|
PointerPde = MiGetPdeAddress (Va);
|
|
if (count != 0) {
|
|
MiProcessValidPteList (&ValidPteList[0], count);
|
|
count = 0;
|
|
}
|
|
MiMakePdeExistAndMakeValid(PointerPde, Process, FALSE);
|
|
}
|
|
|
|
//
|
|
// The working set lock is held. No PTEs can go from
|
|
// invalid to valid or valid to invalid. Transition
|
|
// PTEs can go from transition to pagefile.
|
|
//
|
|
|
|
PteContents = *PointerPte;
|
|
|
|
if (PteContents.u.Long != 0) {
|
|
|
|
if (PointerPte->u.Long == MmDecommittedPte.u.Long) {
|
|
|
|
//
|
|
// This PTE is already decommitted.
|
|
//
|
|
|
|
CommitReduction += 1;
|
|
|
|
} else {
|
|
|
|
Process->NumberOfPrivatePages -= 1;
|
|
|
|
if (PteContents.u.Hard.Valid == 1) {
|
|
|
|
//
|
|
// Make sure this is not a forked PTE.
|
|
//
|
|
|
|
Pfn1 = MI_PFN_ELEMENT (PteContents.u.Hard.PageFrameNumber);
|
|
|
|
if (Pfn1->u3.e1.PrototypePte) {
|
|
|
|
LOCK_PFN (OldIrql);
|
|
MiDeletePte (PointerPte,
|
|
Va,
|
|
FALSE,
|
|
Process,
|
|
NULL,
|
|
NULL);
|
|
UNLOCK_PFN (OldIrql);
|
|
Process->NumberOfPrivatePages += 1;
|
|
*PointerPte = MmDecommittedPte;
|
|
} else {
|
|
|
|
//
|
|
// Pte is valid, process later when PFN lock is held.
|
|
//
|
|
|
|
if (count == MM_VALID_PTE_SIZE) {
|
|
MiProcessValidPteList (&ValidPteList[0], count);
|
|
count = 0;
|
|
}
|
|
ValidPteList[count] = PointerPte;
|
|
count += 1;
|
|
|
|
//
|
|
// Remove address from working set list.
|
|
//
|
|
|
|
|
|
WorkingSetIndex = Pfn1->u1.WsIndex;
|
|
|
|
ASSERT (PAGE_ALIGN(MmWsle[WorkingSetIndex].u1.Long) ==
|
|
Va);
|
|
//
|
|
// Check to see if this entry is locked in the working set
|
|
// or locked in memory.
|
|
//
|
|
|
|
Locked = MmWsle[WorkingSetIndex].u1.e1;
|
|
|
|
MiRemoveWsle (WorkingSetIndex, MmWorkingSetList);
|
|
|
|
//
|
|
// Add this entry to the list of free working set entries
|
|
// and adjust the working set count.
|
|
//
|
|
|
|
MiReleaseWsle (WorkingSetIndex, &Process->Vm);
|
|
|
|
if ((Locked.LockedInWs == 1) || (Locked.LockedInMemory == 1)) {
|
|
|
|
//
|
|
// This entry is locked.
|
|
//
|
|
|
|
MmWorkingSetList->FirstDynamic -= 1;
|
|
|
|
if (WorkingSetIndex != MmWorkingSetList->FirstDynamic) {
|
|
|
|
SwapVa = MmWsle[MmWorkingSetList->FirstDynamic].u1.VirtualAddress;
|
|
SwapVa = PAGE_ALIGN (SwapVa);
|
|
Pfn2 = MI_PFN_ELEMENT (
|
|
MiGetPteAddress (SwapVa)->u.Hard.PageFrameNumber);
|
|
|
|
Entry = MiLocateWsle (SwapVa,
|
|
MmWorkingSetList,
|
|
Pfn2->u1.WsIndex);
|
|
|
|
MiSwapWslEntries (Entry,
|
|
WorkingSetIndex,
|
|
&Process->Vm);
|
|
}
|
|
}
|
|
}
|
|
} else if (PteContents.u.Soft.Prototype) {
|
|
|
|
//
|
|
// This is a forked PTE, just delete it.
|
|
//
|
|
|
|
LOCK_PFN (OldIrql);
|
|
MiDeletePte (PointerPte,
|
|
Va,
|
|
FALSE,
|
|
Process,
|
|
NULL,
|
|
NULL);
|
|
UNLOCK_PFN (OldIrql);
|
|
Process->NumberOfPrivatePages += 1;
|
|
*PointerPte = MmDecommittedPte;
|
|
|
|
} else if (PteContents.u.Soft.Transition == 1) {
|
|
|
|
//
|
|
// Transition PTE, get the PFN database lock
|
|
// and reprocess this one.
|
|
//
|
|
|
|
LOCK_PFN (OldIrql);
|
|
PteContents = *PointerPte;
|
|
|
|
if (PteContents.u.Soft.Transition == 1) {
|
|
|
|
//
|
|
// PTE is still in transition, delete it.
|
|
//
|
|
|
|
Pfn1 = MI_PFN_ELEMENT (PteContents.u.Trans.PageFrameNumber);
|
|
|
|
MI_SET_PFN_DELETED (Pfn1);
|
|
|
|
MiDecrementShareCount (Pfn1->PteFrame);
|
|
|
|
//
|
|
// Check the reference count for the page, if the
|
|
// reference count is zero, move the page to the
|
|
// free list, if the reference count is not zero,
|
|
// ignore this page. When the refernce count
|
|
// goes to zero, it will be placed on the free list.
|
|
//
|
|
|
|
if (Pfn1->u3.e2.ReferenceCount == 0) {
|
|
MiUnlinkPageFromList (Pfn1);
|
|
MiReleasePageFileSpace (Pfn1->OriginalPte);
|
|
MiInsertPageInList (MmPageLocationList[FreePageList],
|
|
PteContents.u.Trans.PageFrameNumber);
|
|
}
|
|
|
|
*PointerPte = MmDecommittedPte;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Page MUST be in page file format!
|
|
//
|
|
|
|
ASSERT (PteContents.u.Soft.Valid == 0);
|
|
ASSERT (PteContents.u.Soft.Prototype == 0);
|
|
ASSERT (PteContents.u.Soft.PageFileHigh != 0);
|
|
MiReleasePageFileSpace (PteContents);
|
|
*PointerPte = MmDecommittedPte;
|
|
}
|
|
UNLOCK_PFN (OldIrql);
|
|
} else {
|
|
|
|
//
|
|
// Must be demand zero or paging file format.
|
|
//
|
|
|
|
if (PteContents.u.Soft.PageFileHigh != 0) {
|
|
LOCK_PFN (OldIrql);
|
|
MiReleasePageFileSpace (PteContents);
|
|
UNLOCK_PFN (OldIrql);
|
|
} else {
|
|
|
|
//
|
|
// Don't subtract out the private page count for
|
|
// a demand zero page.
|
|
//
|
|
|
|
Process->NumberOfPrivatePages += 1;
|
|
}
|
|
|
|
*PointerPte = MmDecommittedPte;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// The PTE is already zero.
|
|
//
|
|
|
|
//
|
|
// Increment the count of non-zero page table entires for this
|
|
// page table and the number of private pages for the process.
|
|
//
|
|
|
|
MmWorkingSetList->UsedPageTableEntries[PdeOffset] += 1;
|
|
|
|
if (PointerPte > CommitLimitPte) {
|
|
|
|
//
|
|
// Pte is not committed.
|
|
//
|
|
|
|
CommitReduction += 1;
|
|
}
|
|
*PointerPte = MmDecommittedPte;
|
|
}
|
|
|
|
PointerPte += 1;
|
|
Va = (PVOID)((ULONG)Va + PAGE_SIZE);
|
|
}
|
|
if (count != 0) {
|
|
MiProcessValidPteList (&ValidPteList[0], count);
|
|
}
|
|
|
|
return CommitReduction;
|
|
}
|
|
|
|
|
|
VOID
|
|
MiProcessValidPteList (
|
|
IN PMMPTE *ValidPteList,
|
|
IN ULONG Count
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine flushes the specified range of valid PTEs.
|
|
|
|
Arguments:
|
|
|
|
ValidPteList - Supplies a pointer to an array of PTEs to flush.
|
|
|
|
Count - Supplies the count of the number of elements in the array.
|
|
|
|
Return Value:
|
|
|
|
none.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, APCs disabled, WorkingSetMutex and AddressCreation mutexes
|
|
held.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG i = 0;
|
|
MMPTE_FLUSH_LIST PteFlushList;
|
|
MMPTE PteContents;
|
|
PMMPFN Pfn1;
|
|
KIRQL OldIrql;
|
|
|
|
PteFlushList.Count = Count;
|
|
|
|
LOCK_PFN (OldIrql);
|
|
|
|
do {
|
|
PteContents = *ValidPteList[i];
|
|
ASSERT (PteContents.u.Hard.Valid == 1);
|
|
Pfn1 = MI_PFN_ELEMENT (PteContents.u.Hard.PageFrameNumber);
|
|
|
|
//
|
|
// Decrement the share and valid counts of the page table
|
|
// page which maps this PTE.
|
|
//
|
|
|
|
MiDecrementShareAndValidCount (Pfn1->PteFrame);
|
|
|
|
MI_SET_PFN_DELETED (Pfn1);
|
|
|
|
//
|
|
// Decrement the share count for the physical page. As the page
|
|
// is private it will be put on the free list.
|
|
//
|
|
|
|
MiDecrementShareCountOnly (PteContents.u.Hard.PageFrameNumber);
|
|
|
|
if (Count < MM_MAXIMUM_FLUSH_COUNT) {
|
|
PteFlushList.FlushPte[i] = ValidPteList[i];
|
|
PteFlushList.FlushVa[i] =
|
|
MiGetVirtualAddressMappedByPte (ValidPteList[i]);
|
|
}
|
|
*ValidPteList[i] = MmDecommittedPte;
|
|
i += 1;
|
|
} while (i != Count);
|
|
|
|
MiFlushPteList (&PteFlushList, FALSE, MmDecommittedPte);
|
|
UNLOCK_PFN (OldIrql);
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
MiDeleteFreeVm (
|
|
IN PVOID StartingAddress,
|
|
IN PVOID EndingAddress
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Nonpagable routine to call acquire PFN lock and call
|
|
MiDeleteVirtualAddresses.
|
|
|
|
Arguments:
|
|
|
|
|
|
Return Value:
|
|
|
|
none.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, APCs disabled, WorkingSetMutex and AddressCreation mutexes
|
|
held.
|
|
|
|
--*/
|
|
|
|
{
|
|
KIRQL OldIrql;
|
|
|
|
LOCK_PFN (OldIrql);
|
|
|
|
//
|
|
// Delete the address range.
|
|
//
|
|
|
|
MiDeleteVirtualAddresses (StartingAddress,
|
|
EndingAddress,
|
|
FALSE,
|
|
(PMMVAD)NULL);
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
}
|