Leaked source code of windows server 2003
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.
 
 
 
 
 
 

3167 lines
95 KiB

/*++
Copyright (c) 1990 Microsoft Corporation
Module Name:
flushsec.c
Abstract:
This module contains the routines which implement the
NtFlushVirtualMemory service.
Author:
Lou Perazzoli (loup) 8-May-1990
Landy Wang (landyw) 02-June-1997
Revision History:
--*/
#include "mi.h"
PSUBSECTION
MiGetSystemCacheSubsection (
IN PVOID BaseAddress,
OUT PMMPTE *ProtoPte
);
VOID
MiFlushDirtyBitsToPfn (
IN PMMPTE PointerPte,
IN PMMPTE LastPte,
IN PEPROCESS Process,
IN BOOLEAN SystemCache
);
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE,NtFlushVirtualMemory)
#pragma alloc_text(PAGE,MmFlushVirtualMemory)
#endif
extern POBJECT_TYPE IoFileObjectType;
NTSTATUS
NtFlushVirtualMemory (
IN HANDLE ProcessHandle,
IN OUT PVOID *BaseAddress,
IN OUT PSIZE_T RegionSize,
OUT PIO_STATUS_BLOCK IoStatus
)
/*++
Routine Description:
This function flushes a range of virtual address which map
a data file back into the data file if they have been modified.
Arguments:
ProcessHandle - Supplies an open handle to a process object.
BaseAddress - Supplies a pointer to a variable that will receive
the base address the flushed region. The initial value
of this argument is the base address of the region of the
pages to flush.
RegionSize - Supplies a pointer to a variable that will receive
the actual size in bytes of the flushed region of pages.
The initial value of this argument is rounded up to the
next host-page-size boundary.
If this value is specified as zero, the mapped range from
the base address to the end of the range is flushed.
IoStatus - Returns the value of the IoStatus for the last attempted
I/O operation.
Return Value:
Returns the status
TBS
--*/
{
PEPROCESS Process;
KPROCESSOR_MODE PreviousMode;
NTSTATUS Status;
PVOID CapturedBase;
SIZE_T CapturedRegionSize;
IO_STATUS_BLOCK TemporaryIoStatus;
PAGED_CODE();
PreviousMode = KeGetPreviousMode();
if (PreviousMode != KernelMode) {
//
// Establish an exception handler, probe the specified addresses
// for write access and capture the initial values.
//
try {
ProbeForWritePointer (BaseAddress);
ProbeForWriteUlong_ptr (RegionSize);
ProbeForWriteIoStatus (IoStatus);
//
// Capture the base address.
//
CapturedBase = *BaseAddress;
//
// Capture the region size.
//
CapturedRegionSize = *RegionSize;
} except (EXCEPTION_EXECUTE_HANDLER) {
//
// 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();
}
}
else {
//
// Capture the base address.
//
CapturedBase = *BaseAddress;
//
// Capture the region size.
//
CapturedRegionSize = *RegionSize;
}
//
// 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_PTR)MM_HIGHEST_USER_ADDRESS - (ULONG_PTR)CapturedBase) <
CapturedRegionSize) {
//
// Invalid region size;
//
return STATUS_INVALID_PARAMETER_2;
}
Status = ObReferenceObjectByHandle ( ProcessHandle,
PROCESS_VM_OPERATION,
PsProcessType,
PreviousMode,
(PVOID *)&Process,
NULL );
if (!NT_SUCCESS(Status)) {
return Status;
}
Status = MmFlushVirtualMemory (Process,
&CapturedBase,
&CapturedRegionSize,
&TemporaryIoStatus);
ObDereferenceObject (Process);
//
// Establish an exception handler and write the size and base
// address.
//
try {
*RegionSize = CapturedRegionSize;
*BaseAddress = PAGE_ALIGN (CapturedBase);
*IoStatus = TemporaryIoStatus;
} except (EXCEPTION_EXECUTE_HANDLER) {
}
return Status;
}
VOID
MiFlushAcquire (
IN PCONTROL_AREA ControlArea
)
/*++
Routine Description:
This is a helper routine to reference count the control area if needed
during a flush section call to prevent the section object from being
deleted while the flush is ongoing.
Arguments:
ControlArea - Supplies a pointer to the control area.
Return Value:
None.
--*/
{
KIRQL OldIrql;
LOCK_PFN (OldIrql);
ASSERT ((LONG)ControlArea->NumberOfMappedViews >= 1);
ControlArea->NumberOfMappedViews += 1;
UNLOCK_PFN (OldIrql);
}
VOID
MiFlushRelease (
IN PCONTROL_AREA ControlArea
)
/*++
Routine Description:
This is a helper routine to release the control area reference needed
during a flush section call.
Arguments:
ControlArea - Supplies a pointer to the control area.
Return Value:
None.
--*/
{
KIRQL OldIrql;
LOCK_PFN (OldIrql);
ASSERT ((LONG)ControlArea->NumberOfMappedViews >= 1);
ControlArea->NumberOfMappedViews -= 1;
//
// Check to see if the control area should be deleted. This
// will release the PFN lock.
//
MiCheckControlArea (ControlArea, NULL, OldIrql);
}
NTSTATUS
MmFlushVirtualMemory (
IN PEPROCESS Process,
IN OUT PVOID *BaseAddress,
IN OUT PSIZE_T RegionSize,
OUT PIO_STATUS_BLOCK IoStatus
)
/*++
Routine Description:
This function flushes a range of virtual address which map
a data file back into the data file if they have been modified.
Note that the modification is this process's view of the pages,
on certain implementations (like the Intel 386), the modify
bit is captured in the PTE and not forced to the PFN database
until the page is removed from the working set. This means
that pages which have been modified by another process will
not be flushed to the data file.
Arguments:
Process - Supplies a pointer to a process object.
BaseAddress - Supplies a pointer to a variable that will receive
the base address of the flushed region. The initial value
of this argument is the base address of the region of the
pages to flush.
RegionSize - Supplies a pointer to a variable that will receive
the actual size in bytes of the flushed region of pages.
The initial value of this argument is rounded up to the
next host-page-size boundary.
If this value is specified as zero, the mapped range from
the base address to the end of the range is flushed.
IoStatus - Returns the value of the IoStatus for the last attempted
I/O operation.
Return Value:
NTSTATUS.
--*/
{
PMMVAD Vad;
PVOID EndingAddress;
PVOID Va;
PEPROCESS CurrentProcess;
BOOLEAN SystemCache;
PCONTROL_AREA ControlArea;
PMMPTE PointerPte;
PMMPTE PointerPde;
PMMPTE PointerPpe;
PMMPTE PointerPxe;
PMMPTE LastPte;
PMMPTE FinalPte;
PSUBSECTION Subsection;
PSUBSECTION LastSubsection;
NTSTATUS Status;
ULONG ConsecutiveFileLockFailures;
ULONG Waited;
LOGICAL EntireRestOfVad;
LOGICAL Attached;
KAPC_STATE ApcState;
PAGED_CODE();
Attached = FALSE;
//
// Determine if the specified base address is within the system
// cache and if so, don't attach, the working set mutex is still
// required to "lock" paged pool pages (proto PTEs) into the
// working set.
//
EndingAddress = (PVOID)(((ULONG_PTR)*BaseAddress + *RegionSize - 1) |
(PAGE_SIZE - 1));
*BaseAddress = PAGE_ALIGN (*BaseAddress);
if (MI_IS_SESSION_ADDRESS (*BaseAddress)) {
//
// Nothing in session space needs flushing.
//
return STATUS_NOT_MAPPED_VIEW;
}
CurrentProcess = PsGetCurrentProcess ();
if (!MI_IS_SYSTEM_CACHE_ADDRESS(*BaseAddress)) {
SystemCache = FALSE;
//
// Attach to the specified process.
//
if (CurrentProcess != Process) {
KeStackAttachProcess (&Process->Pcb, &ApcState);
Attached = TRUE;
}
LOCK_ADDRESS_SPACE (Process);
//
// Make sure the address space was not deleted, if so, return an error.
//
if (Process->Flags & PS_PROCESS_FLAGS_VM_DELETED) {
Status = STATUS_PROCESS_IS_TERMINATING;
goto ErrorReturn;
}
Vad = MiLocateAddress (*BaseAddress);
if (Vad == NULL) {
//
// No Virtual Address Descriptor located for Base Address.
//
Status = STATUS_NOT_MAPPED_VIEW;
goto ErrorReturn;
}
if (*RegionSize == 0) {
EndingAddress = MI_VPN_TO_VA_ENDING (Vad->EndingVpn);
EntireRestOfVad = TRUE;
}
else {
EntireRestOfVad = FALSE;
}
if ((Vad->u.VadFlags.PrivateMemory == 1) ||
(MI_VA_TO_VPN (EndingAddress) > Vad->EndingVpn)) {
//
// This virtual address descriptor does not refer to a Segment
// object.
//
Status = STATUS_NOT_MAPPED_VIEW;
goto ErrorReturn;
}
//
// Make sure this VAD maps a data file (not an image file).
//
ControlArea = Vad->ControlArea;
if ((ControlArea->FilePointer == NULL) ||
(Vad->u.VadFlags.ImageMap == 1)) {
//
// This virtual address descriptor does not refer to a Segment
// object.
//
Status = STATUS_NOT_MAPPED_DATA;
goto ErrorReturn;
}
LOCK_WS_UNSAFE (Process);
}
else {
SATISFY_OVERZEALOUS_COMPILER (Vad = NULL);
SATISFY_OVERZEALOUS_COMPILER (ControlArea = NULL);
SATISFY_OVERZEALOUS_COMPILER (EntireRestOfVad = FALSE);
SystemCache = TRUE;
Process = CurrentProcess;
LOCK_WS (Process);
}
PointerPxe = MiGetPxeAddress (*BaseAddress);
PointerPpe = MiGetPpeAddress (*BaseAddress);
PointerPde = MiGetPdeAddress (*BaseAddress);
PointerPte = MiGetPteAddress (*BaseAddress);
LastPte = MiGetPteAddress (EndingAddress);
*RegionSize = (PCHAR)EndingAddress - (PCHAR)*BaseAddress + 1;
retry:
while (!MiDoesPxeExistAndMakeValid (PointerPxe, Process, MM_NOIRQL, &Waited)) {
//
// This page directory parent entry is empty, go to the next one.
//
PointerPxe += 1;
PointerPpe = MiGetVirtualAddressMappedByPte (PointerPxe);
PointerPde = MiGetVirtualAddressMappedByPte (PointerPpe);
PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
Va = MiGetVirtualAddressMappedByPte (PointerPte);
if (PointerPte > LastPte) {
break;
}
}
while (!MiDoesPpeExistAndMakeValid (PointerPpe, Process, MM_NOIRQL, &Waited)) {
//
// This page directory parent entry is empty, go to the next one.
//
PointerPpe += 1;
PointerPxe = MiGetPteAddress (PointerPpe);
PointerPde = MiGetVirtualAddressMappedByPte (PointerPpe);
PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
Va = MiGetVirtualAddressMappedByPte (PointerPte);
if (PointerPte > LastPte) {
break;
}
#if (_MI_PAGING_LEVELS >= 4)
if (MiIsPteOnPdeBoundary (PointerPpe)) {
goto retry;
}
#endif
}
Waited = 0;
if (PointerPte <= LastPte) {
while (!MiDoesPdeExistAndMakeValid(PointerPde, Process, MM_NOIRQL, &Waited)) {
//
// No page table page exists for this address.
//
PointerPde += 1;
PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
if (PointerPte > LastPte) {
break;
}
#if (_MI_PAGING_LEVELS >= 3)
if (MiIsPteOnPdeBoundary (PointerPde)) {
if (MiIsPteOnPpeBoundary (PointerPde)) {
PointerPxe = MiGetPdeAddress (PointerPde);
}
PointerPpe = MiGetPteAddress (PointerPde);
goto retry;
}
#endif
Va = MiGetVirtualAddressMappedByPte (PointerPte);
}
//
// If the PFN lock (and accordingly the WS mutex) was
// released and reacquired we must retry the operation.
//
if ((PointerPte <= LastPte) && (Waited != 0)) {
goto retry;
}
}
MiFlushDirtyBitsToPfn (PointerPte, LastPte, Process, SystemCache);
if (SystemCache) {
//
// No VADs exist for the system cache.
//
UNLOCK_WS (Process);
Subsection = MiGetSystemCacheSubsection (*BaseAddress, &PointerPte);
LastSubsection = MiGetSystemCacheSubsection (EndingAddress, &FinalPte);
//
// Flush the PTEs from the specified section.
//
Status = MiFlushSectionInternal (PointerPte,
FinalPte,
Subsection,
LastSubsection,
FALSE,
TRUE,
IoStatus);
}
else {
//
// Protect against the section being prematurely deleted.
//
MiFlushAcquire (ControlArea);
PointerPte = MiGetProtoPteAddress (Vad, MI_VA_TO_VPN (*BaseAddress));
Subsection = MiLocateSubsection (Vad, MI_VA_TO_VPN(*BaseAddress));
LastSubsection = MiLocateSubsection (Vad, MI_VA_TO_VPN(EndingAddress));
//
// The last subsection is NULL if the section is not fully
// committed. Only allow the flush if the caller said do the whole
// thing, otherwise it's an error.
//
if (LastSubsection == NULL) {
if (EntireRestOfVad == FALSE) {
//
// Caller can only specify the range that is committed or zero
// to indicate the entire range.
//
UNLOCK_WS_AND_ADDRESS_SPACE (Process);
if (Attached == TRUE) {
KeUnstackDetachProcess (&ApcState);
}
MiFlushRelease (ControlArea);
return STATUS_NOT_MAPPED_VIEW;
}
LastSubsection = Subsection;
while (LastSubsection->NextSubsection) {
LastSubsection = LastSubsection->NextSubsection;
}
//
// A memory barrier is needed to read the subsection chains
// in order to ensure the writes to the actual individual
// subsection data structure fields are visible in correct
// order. This avoids the need to acquire any stronger
// synchronization (ie: PFN lock), thus yielding better
// performance and pagability.
//
KeMemoryBarrier ();
FinalPte = LastSubsection->SubsectionBase + LastSubsection->PtesInSubsection - 1;
}
else {
FinalPte = MiGetProtoPteAddress (Vad, MI_VA_TO_VPN (EndingAddress));
}
UNLOCK_WS_AND_ADDRESS_SPACE (Process);
if (Attached == TRUE) {
KeUnstackDetachProcess (&ApcState);
}
//
// Preacquire the file to synchronize the flush.
//
ConsecutiveFileLockFailures = 0;
do {
Status = FsRtlAcquireFileForCcFlushEx (ControlArea->FilePointer);
if (!NT_SUCCESS(Status)) {
break;
}
//
// Flush the PTEs from the specified section.
//
Status = MiFlushSectionInternal (PointerPte,
FinalPte,
Subsection,
LastSubsection,
TRUE,
TRUE,
IoStatus);
//
// Release the file we acquired.
//
FsRtlReleaseFileForCcFlush (ControlArea->FilePointer);
//
// Only try the request more than once if the filesystem told us
// it had a deadlock.
//
if (Status != STATUS_FILE_LOCK_CONFLICT) {
break;
}
ConsecutiveFileLockFailures += 1;
KeDelayExecutionThread (KernelMode, FALSE, (PLARGE_INTEGER)&MmShortTime);
} while (ConsecutiveFileLockFailures < 5);
MiFlushRelease (ControlArea);
}
return Status;
ErrorReturn:
ASSERT (SystemCache == FALSE);
UNLOCK_ADDRESS_SPACE (Process);
if (Attached == TRUE) {
KeUnstackDetachProcess (&ApcState);
}
return Status;
}
NTSTATUS
MmFlushSection (
IN PSECTION_OBJECT_POINTERS SectionObjectPointer,
IN PLARGE_INTEGER Offset,
IN SIZE_T RegionSize,
OUT PIO_STATUS_BLOCK IoStatus,
IN ULONG AcquireFile
)
/*++
Routine Description:
This function flushes to the backing file any modified pages within
the specified range of the section.
Arguments:
SectionObjectPointer - Supplies a pointer to the section objects.
Offset - Supplies the offset into the section in which to begin
flushing pages. If this argument is not present, then the
whole section is flushed without regard to the region size
argument.
RegionSize - Supplies the size in bytes to flush. This is rounded
to a page multiple.
IoStatus - Returns the value of the IoStatus for the last attempted
I/O operation.
AcquireFile - Nonzero if the callback should be used to acquire the file.
Return Value:
Returns status of the operation.
--*/
{
PCONTROL_AREA ControlArea;
PMMPTE PointerPte;
PMMPTE LastPte;
KIRQL OldIrql;
UINT64 PteOffset;
UINT64 LastPteOffset;
PSUBSECTION Subsection;
PSUBSECTION TempSubsection;
PSUBSECTION LastSubsection;
PSUBSECTION LastSubsectionWithProtos;
PMAPPED_FILE_SEGMENT Segment;
PETHREAD CurrentThread;
NTSTATUS status;
BOOLEAN OldClusterState;
ULONG ConsecutiveFileLockFailures;
//
// Initialize IoStatus for success, in case we take an early exit.
//
IoStatus->Status = STATUS_SUCCESS;
IoStatus->Information = RegionSize;
LOCK_PFN (OldIrql);
ControlArea = ((PCONTROL_AREA)(SectionObjectPointer->DataSectionObject));
ASSERT ((ControlArea == NULL) || (ControlArea->u.Flags.Image == 0));
if ((ControlArea == NULL) ||
(ControlArea->u.Flags.BeingDeleted) ||
(ControlArea->u.Flags.BeingCreated) ||
(ControlArea->u.Flags.Rom) ||
(ControlArea->NumberOfPfnReferences == 0)) {
//
// This file no longer has an associated segment or is in the
// process of coming or going.
// If the number of PFN references is zero, then this control
// area does not have any valid or transition pages that need
// to be flushed.
//
UNLOCK_PFN (OldIrql);
return STATUS_SUCCESS;
}
//
// Locate the subsection.
//
ASSERT (ControlArea->u.Flags.Image == 0);
ASSERT (ControlArea->u.Flags.GlobalOnlyPerSession == 0);
ASSERT (ControlArea->u.Flags.PhysicalMemory == 0);
Subsection = (PSUBSECTION)(ControlArea + 1);
if (!ARGUMENT_PRESENT (Offset)) {
//
// If the offset is not specified, flush the complete file ignoring
// the region size.
//
ASSERT (ControlArea->FilePointer != NULL);
PteOffset = 0;
LastSubsection = Subsection;
Segment = (PMAPPED_FILE_SEGMENT) ControlArea->Segment;
if (MiIsAddressValid (Segment, TRUE)) {
if (Segment->LastSubsectionHint != NULL) {
LastSubsection = (PSUBSECTION) Segment->LastSubsectionHint;
}
}
while (LastSubsection->NextSubsection != NULL) {
LastSubsection = LastSubsection->NextSubsection;
}
LastPteOffset = LastSubsection->PtesInSubsection - 1;
}
else {
PteOffset = (UINT64)(Offset->QuadPart >> PAGE_SHIFT);
//
// Make sure the PTEs are not in the extended part of the segment.
//
while (PteOffset >= (UINT64) Subsection->PtesInSubsection) {
PteOffset -= Subsection->PtesInSubsection;
if (Subsection->NextSubsection == NULL) {
//
// Past end of mapping, just return success.
//
UNLOCK_PFN (OldIrql);
return STATUS_SUCCESS;
}
Subsection = Subsection->NextSubsection;
}
ASSERT (PteOffset < (UINT64) Subsection->PtesInSubsection);
//
// Locate the address of the last prototype PTE to be flushed.
//
LastPteOffset = PteOffset + (((RegionSize + BYTE_OFFSET(Offset->LowPart)) - 1) >> PAGE_SHIFT);
LastSubsection = Subsection;
while (LastPteOffset >= (UINT64) LastSubsection->PtesInSubsection) {
LastPteOffset -= LastSubsection->PtesInSubsection;
if (LastSubsection->NextSubsection == NULL) {
LastPteOffset = LastSubsection->PtesInSubsection - 1;
break;
}
LastSubsection = LastSubsection->NextSubsection;
}
ASSERT (LastPteOffset < LastSubsection->PtesInSubsection);
}
//
// Try for the fast reference on the first and last subsection.
// If that cannot be gotten, then there are no prototype PTEs for this
// subsection, therefore there is nothing in it to flush so leap forwards.
//
// Note that subsections in between do not need referencing as
// MiFlushSectionInternal is smart enough to skip them if they're
// nonresident.
//
if (MiReferenceSubsection ((PMSUBSECTION)Subsection) == FALSE) {
do {
//
// If this increment would put us past the end offset, then nothing
// to flush, just return success.
//
if (Subsection == LastSubsection) {
UNLOCK_PFN (OldIrql);
return STATUS_SUCCESS;
}
Subsection = Subsection->NextSubsection;
//
// If this increment put us past the end of section, then nothing
// to flush, just return success.
//
if (Subsection == NULL) {
UNLOCK_PFN (OldIrql);
return STATUS_SUCCESS;
}
if ((PMSUBSECTION)Subsection->SubsectionBase == NULL) {
continue;
}
if (MiReferenceSubsection ((PMSUBSECTION)Subsection) == FALSE) {
continue;
}
//
// Start the flush at this subsection which is now referenced.
//
PointerPte = &Subsection->SubsectionBase[0];
break;
} while (TRUE);
}
else {
PointerPte = &Subsection->SubsectionBase[PteOffset];
}
ASSERT (Subsection->SubsectionBase != NULL);
//
// The first subsection is referenced, now reference count the last one.
// If the first is the last, just double reference it anyway as it
// simplifies cleanup later.
//
if (MiReferenceSubsection ((PMSUBSECTION)LastSubsection) == FALSE) {
ASSERT (Subsection != LastSubsection);
TempSubsection = Subsection->NextSubsection;
LastSubsectionWithProtos = NULL;
while (TempSubsection != LastSubsection) {
//
// If this increment put us past the end of section, then nothing
// to flush, just return success.
//
ASSERT (TempSubsection != NULL);
if ((PMSUBSECTION)TempSubsection->SubsectionBase != NULL) {
LastSubsectionWithProtos = TempSubsection;
}
TempSubsection = TempSubsection->NextSubsection;
}
//
// End the flush at this subsection and reference it.
//
if (LastSubsectionWithProtos == NULL) {
ASSERT (Subsection != NULL);
ASSERT (Subsection->SubsectionBase != NULL);
TempSubsection = Subsection;
}
else {
TempSubsection = LastSubsectionWithProtos;
}
if (MiReferenceSubsection ((PMSUBSECTION)TempSubsection) == FALSE) {
ASSERT (FALSE);
}
ASSERT (TempSubsection->SubsectionBase != NULL);
LastSubsection = TempSubsection;
LastPteOffset = LastSubsection->PtesInSubsection - 1;
}
//
// Up the map view count so the control area cannot be deleted
// out from under the call.
//
ControlArea->NumberOfMappedViews += 1;
UNLOCK_PFN (OldIrql);
//
// End the flush at this subsection which is now referenced.
//
LastPte = &LastSubsection->SubsectionBase[LastPteOffset];
CurrentThread = PsGetCurrentThread();
//
// Indicate that disk verify errors should be returned as exceptions.
//
OldClusterState = CurrentThread->ForwardClusterOnly;
CurrentThread->ForwardClusterOnly = TRUE;
//
// Preacquire the file if we are going to synchronize the flush.
//
if (AcquireFile == 0) {
//
// Flush the PTEs from the specified section.
//
status = MiFlushSectionInternal (PointerPte,
LastPte,
Subsection,
LastSubsection,
TRUE,
TRUE,
IoStatus);
}
else {
ConsecutiveFileLockFailures = 0;
do {
status = FsRtlAcquireFileForCcFlushEx (ControlArea->FilePointer);
if (!NT_SUCCESS(status)) {
break;
}
//
// Flush the PTEs from the specified section.
//
status = MiFlushSectionInternal (PointerPte,
LastPte,
Subsection,
LastSubsection,
TRUE,
TRUE,
IoStatus);
//
// Release the file we acquired.
//
FsRtlReleaseFileForCcFlush (ControlArea->FilePointer);
//
// Only try the request more than once if the filesystem told us
// it had a deadlock.
//
if (status != STATUS_FILE_LOCK_CONFLICT) {
break;
}
ConsecutiveFileLockFailures += 1;
KeDelayExecutionThread (KernelMode, FALSE, (PLARGE_INTEGER)&MmShortTime);
} while (ConsecutiveFileLockFailures < 5);
}
CurrentThread->ForwardClusterOnly = OldClusterState;
LOCK_PFN (OldIrql);
MiDecrementSubsections (Subsection, Subsection);
MiDecrementSubsections (LastSubsection, LastSubsection);
ASSERT ((LONG)ControlArea->NumberOfMappedViews >= 1);
ControlArea->NumberOfMappedViews -= 1;
//
// Check to see if the control area should be deleted. This
// will release the PFN lock.
//
MiCheckControlArea (ControlArea, NULL, OldIrql);
return status;
}
LONGLONG
MiStartingOffset(
IN PSUBSECTION Subsection,
IN PMMPTE PteAddress
)
/*++
Routine Description:
This function calculates the file offset given a subsection and a PTE
offset. Note that images are stored in 512-byte units whereas data is
stored in 4K units.
When this is all debugged, this should be made into a macro.
Arguments:
Subsection - Supplies a subsection to reference for the file address.
PteAddress - Supplies a PTE within the subsection
Return Value:
Returns the file offset to obtain the backing data from.
--*/
{
LONGLONG PteByteOffset;
LARGE_INTEGER StartAddress;
if (Subsection->ControlArea->u.Flags.Image == 1) {
return MI_STARTING_OFFSET ( Subsection,
PteAddress);
}
ASSERT (Subsection->SubsectionBase != NULL);
PteByteOffset = (LONGLONG)((PteAddress - Subsection->SubsectionBase))
<< PAGE_SHIFT;
Mi4KStartFromSubsection (&StartAddress, Subsection);
StartAddress.QuadPart = StartAddress.QuadPart << MM4K_SHIFT;
PteByteOffset += StartAddress.QuadPart;
return PteByteOffset;
}
LARGE_INTEGER
MiEndingOffset(
IN PSUBSECTION Subsection
)
/*++
Routine Description:
This function calculates the last valid file offset in a given subsection.
offset. Note that images are stored in 512-byte units whereas data is
stored in 4K units.
When this is all debugged, this should be made into a macro.
Arguments:
Subsection - Supplies a subsection to reference for the file address.
PteAddress - Supplies a PTE within the subsection
Return Value:
Returns the file offset to obtain the backing data from.
--*/
{
LARGE_INTEGER FileByteOffset;
if (Subsection->ControlArea->u.Flags.Image == 1) {
FileByteOffset.QuadPart =
((UINT64)Subsection->StartingSector + (UINT64)Subsection->NumberOfFullSectors) <<
MMSECTOR_SHIFT;
}
else {
Mi4KStartFromSubsection (&FileByteOffset, Subsection);
FileByteOffset.QuadPart += Subsection->NumberOfFullSectors;
FileByteOffset.QuadPart = FileByteOffset.QuadPart << MM4K_SHIFT;
}
FileByteOffset.QuadPart += Subsection->u.SubsectionFlags.SectorEndOffset;
return FileByteOffset;
}
NTSTATUS
MiFlushSectionInternal (
IN PMMPTE StartingPte,
IN PMMPTE FinalPte,
IN PSUBSECTION FirstSubsection,
IN PSUBSECTION LastSubsection,
IN ULONG Synchronize,
IN LOGICAL WriteInProgressOk,
OUT PIO_STATUS_BLOCK IoStatus
)
/*++
Routine Description:
This function flushes to the backing file any modified pages within
the specified range of the section. The parameters describe the
section's prototype PTEs (start and end) and the subsections
which correspond to the starting and ending PTE.
Each PTE in the subsection between the specified start and end
is examined and if the page is either valid or transition AND
the page has been modified, the modify bit is cleared in the PFN
database and the page is flushed to its backing file.
Arguments:
StartingPte - Supplies a pointer to the first prototype PTE to
be examined for flushing.
FinalPte - Supplies a pointer to the last prototype PTE to be
examined for flushing.
FirstSubsection - Supplies the subsection that contains the
StartingPte.
LastSubsection - Supplies the subsection that contains the
FinalPte.
Synchronize - Supplies TRUE if synchronization with all threads
doing flush operations to this section should occur.
WriteInProgressOk - Supplies TRUE if the caller can tolerate a write
already in progress for any dirty pages.
IoStatus - Returns the value of the IoStatus for the last attempted
I/O operation.
Return Value:
Returns status of the operation.
--*/
{
LOGICAL DroppedPfnLock;
PCONTROL_AREA ControlArea;
PMMPTE PointerPte;
PMMPTE LastPte;
PMMPTE LastWritten;
PMMPTE FirstWritten;
MMPTE PteContents;
PMMPFN Pfn1;
PMMPFN Pfn2;
KIRQL OldIrql;
PMDL Mdl;
KEVENT IoEvent;
PSUBSECTION Subsection;
PMSUBSECTION MappedSubsection;
PPFN_NUMBER Page;
PFN_NUMBER PageFrameIndex;
PPFN_NUMBER LastPage;
NTSTATUS Status;
UINT64 StartingOffset;
UINT64 TempOffset;
LOGICAL WriteNow;
LOGICAL Bail;
PFN_NUMBER MdlHack[(sizeof(MDL)/sizeof(PFN_NUMBER)) + (MM_MAXIMUM_DISK_IO_SIZE / PAGE_SIZE) + 1];
ULONG ReflushCount;
ULONG MaxClusterSize;
PFILE_OBJECT FilePointer;
LOGICAL CurrentThreadIsDereferenceThread;
//
// WriteInProgressOk is only FALSE when the segment dereference thread is
// doing a top-level flush just prior to cleaning the section or subsection.
// Note that this flag may be TRUE even for the dereference thread because
// the dereference thread calls filesystems who may then issue a flush.
//
if (WriteInProgressOk == FALSE) {
CurrentThreadIsDereferenceThread = TRUE;
ASSERT (PsGetCurrentThread()->StartAddress == (PVOID)(ULONG_PTR)MiDereferenceSegmentThread);
}
else {
CurrentThreadIsDereferenceThread = FALSE;
//
// This may actually be the dereference thread as the segment deletion
// dereferences the file object potentially calling the filesystem which
// may then issue a CcFlushCache/MmFlushSection. For our purposes,
// lower level flushes in this context are treated as though they
// came from a different thread.
//
}
WriteNow = FALSE;
Bail = FALSE;
IoStatus->Status = STATUS_SUCCESS;
IoStatus->Information = 0;
Mdl = (PMDL)&MdlHack[0];
KeInitializeEvent (&IoEvent, NotificationEvent, FALSE);
FinalPte += 1; // Point to 1 past the last one.
FirstWritten = NULL;
LastWritten = NULL;
LastPage = 0;
Subsection = FirstSubsection;
PointerPte = StartingPte;
ControlArea = FirstSubsection->ControlArea;
FilePointer = ControlArea->FilePointer;
ASSERT ((ControlArea->u.Flags.Image == 0) &&
(FilePointer != NULL) &&
(ControlArea->u.Flags.PhysicalMemory == 0));
//
// Initializing these is not needed for correctness
// but without it the compiler cannot compile this code
// W4 to check for use of uninitialized variables.
//
MappedSubsection = NULL;
StartingOffset = 0;
//
// Try to cluster pages as long as the storage stack can handle it.
//
MaxClusterSize = MmModifiedWriteClusterSize;
LOCK_PFN (OldIrql);
ASSERT (ControlArea->u.Flags.Image == 0);
if (ControlArea->NumberOfPfnReferences == 0) {
//
// No transition or valid prototype PTEs present, hence
// no need to flush anything.
//
UNLOCK_PFN (OldIrql);
return STATUS_SUCCESS;
}
while ((Synchronize) && (ControlArea->FlushInProgressCount != 0)) {
//
// Another thread is currently performing a flush operation on
// this file. Wait for that flush to complete.
//
ControlArea->u.Flags.CollidedFlush = 1;
//
// Keep APCs blocked so no special APCs can be delivered in KeWait
// which would cause the dispatcher lock to be released opening a
// window where this thread could miss a pulse.
//
UNLOCK_PFN_AND_THEN_WAIT (APC_LEVEL);
KeWaitForSingleObject (&MmCollidedFlushEvent,
WrPageOut,
KernelMode,
FALSE,
(PLARGE_INTEGER)&MmOneSecond);
KeLowerIrql (OldIrql);
LOCK_PFN (OldIrql);
}
ControlArea->FlushInProgressCount += 1;
//
// Clear the deferred entry list as pages from it may get marked modified
// during the processing. Note that any transition page which is currently
// clean but has a nonzero reference count may get marked modified if
// there is a pending transaction and note well that this transaction may
// complete at any time ! Thus, this case must be carefully handled.
//
#if !defined(MI_MULTINODE)
if (MmPfnDeferredList != NULL) {
MiDeferredUnlockPages (MI_DEFER_PFN_HELD);
}
#else
//
// Each and every node's deferred list would have to be checked so
// we might as well go the long way and just call.
//
MiDeferredUnlockPages (MI_DEFER_PFN_HELD);
#endif
for (;;) {
if (LastSubsection != Subsection) {
//
// Flush to the last PTE in this subsection.
//
LastPte = &Subsection->SubsectionBase[Subsection->PtesInSubsection];
}
else {
//
// Flush to the end of the range.
//
LastPte = FinalPte;
}
if (Subsection->SubsectionBase == NULL) {
//
// The prototype PTEs for this subsection have either never been
// created or have been tossed due to memory pressure. Either
// way, this range can be skipped as there are obviously no
// dirty pages in it. If there are other dirty pages
// to be written, write them now as we are skipping over PTEs.
//
if (LastWritten != NULL) {
ASSERT (MappedSubsection != NULL);
WriteNow = TRUE;
goto CheckForWrite;
}
if (LastSubsection == Subsection) {
break;
}
Subsection = Subsection->NextSubsection;
PointerPte = Subsection->SubsectionBase;
continue;
}
//
// Up the number of mapped views to prevent other threads
// from freeing this to the unused subsection list while we're
// operating on it.
//
MappedSubsection = (PMSUBSECTION) Subsection;
MappedSubsection->NumberOfMappedViews += 1;
if (MappedSubsection->DereferenceList.Flink != NULL) {
//
// Remove this from the list of unused subsections.
//
RemoveEntryList (&MappedSubsection->DereferenceList);
MI_UNUSED_SUBSECTIONS_COUNT_REMOVE (MappedSubsection);
MappedSubsection->DereferenceList.Flink = NULL;
}
if (CurrentThreadIsDereferenceThread == FALSE) {
//
// Set the access bit so an already ongoing trim won't blindly
// delete the prototype PTEs on completion of a mapped write.
// This can happen if the current thread dirties some pages and
// then deletes the view before the trim write finishes - this
// bit informs the trimming thread that a rescan is needed so
// that writes are not lost.
//
MappedSubsection->u2.SubsectionFlags2.SubsectionAccessed = 1;
}
//
// If the prototype PTEs are paged out or have a share count
// of 1, they cannot contain any transition or valid PTEs.
//
if (!MiCheckProtoPtePageState(PointerPte, OldIrql, &DroppedPfnLock)) {
PointerPte = (PMMPTE)(((ULONG_PTR)PointerPte | (PAGE_SIZE - 1)) + 1);
}
while (PointerPte < LastPte) {
if (MiIsPteOnPdeBoundary(PointerPte)) {
//
// We are on a page boundary, make sure this PTE is resident.
//
if (!MiCheckProtoPtePageState(PointerPte, OldIrql, &DroppedPfnLock)) {
PointerPte = (PMMPTE)((PCHAR)PointerPte + PAGE_SIZE);
//
// If there are dirty pages to be written, write them
// now as we are skipping over PTEs.
//
if (LastWritten != NULL) {
WriteNow = TRUE;
goto CheckForWrite;
}
continue;
}
}
PteContents = *PointerPte;
if ((PteContents.u.Hard.Valid == 1) ||
((PteContents.u.Soft.Prototype == 0) &&
(PteContents.u.Soft.Transition == 1))) {
//
// Prototype PTE in transition, there are 3 possible cases:
// 1. The page is part of an image which is sharable and
// refers to the paging file - dereference page file
// space and free the physical page.
// 2. The page refers to the segment but is not modified -
// free the physical page.
// 3. The page refers to the segment and is modified -
// write the page to the file and free the physical page.
//
if (PteContents.u.Hard.Valid == 1) {
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (&PteContents);
}
else {
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_TRANSITION_PTE (&PteContents);
}
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
ASSERT (Pfn1->OriginalPte.u.Soft.Prototype == 1);
ASSERT (Pfn1->OriginalPte.u.Hard.Valid == 0);
//
// Note that any transition page which is currently clean but
// has a nonzero reference count may get marked modified if
// there is a pending transaction and note well that this
// transaction may complete at any time ! Thus, this case
// must be carefully handled since the segment dereference
// thread must be given a collision error for this one as it
// requires that no pages be dirtied after a successful return.
//
if ((CurrentThreadIsDereferenceThread == TRUE) &&
(Pfn1->u3.e2.ReferenceCount != 0)) {
#if DBG
if ((PteContents.u.Hard.Valid != 0) &&
(MappedSubsection->u2.SubsectionFlags2.SubsectionAccessed == 0) &&
(ControlArea->u.Flags.Accessed == 0)) {
DbgPrint ("MM: flushing valid proto, %p %p\n",
Pfn1, PointerPte);
DbgBreakPoint ();
}
#endif
PointerPte = LastPte;
Bail = TRUE;
if (LastWritten != NULL) {
WriteNow = TRUE;
}
goto CheckForWrite;
}
//
// If the page is modified OR a write is in progress
// flush it. The write in progress case catches problems
// where the modified page write continually writes a
// page and gets errors writing it, by writing pages
// in this state, the error will be propagated back to
// the caller.
//
if ((Pfn1->u3.e1.Modified == 1) ||
(Pfn1->u3.e1.WriteInProgress)) {
if ((WriteInProgressOk == FALSE) &&
(Pfn1->u3.e1.WriteInProgress)) {
PointerPte = LastPte;
Bail = TRUE;
if (LastWritten != NULL) {
WriteNow = TRUE;
}
goto CheckForWrite;
}
if (LastWritten == NULL) {
//
// This is the first page of a cluster, initialize
// the MDL, etc.
//
LastPage = (PPFN_NUMBER)(Mdl + 1);
//
// Calculate the offset to read into the file.
// offset = base + ((thispte - basepte) << PAGE_SHIFT)
//
StartingOffset = (UINT64) MiStartingOffset (
Subsection,
Pfn1->PteAddress);
MI_INITIALIZE_ZERO_MDL (Mdl);
Mdl->MdlFlags |= MDL_PAGES_LOCKED;
Mdl->StartVa =
(PVOID)ULongToPtr(Pfn1->u3.e1.PageColor << PAGE_SHIFT);
Mdl->Size = (CSHORT)(sizeof(MDL) +
(sizeof(PFN_NUMBER) * MaxClusterSize));
FirstWritten = PointerPte;
}
LastWritten = PointerPte;
Mdl->ByteCount += PAGE_SIZE;
if (Mdl->ByteCount == (PAGE_SIZE * MaxClusterSize)) {
WriteNow = TRUE;
}
if (PteContents.u.Hard.Valid == 0) {
//
// The page is in transition.
//
MiUnlinkPageFromList (Pfn1);
MI_ADD_LOCKED_PAGE_CHARGE_FOR_MODIFIED_PAGE(Pfn1, TRUE, 18);
}
else {
MI_ADD_LOCKED_PAGE_CHARGE(Pfn1, TRUE, 20);
}
//
// Clear the modified bit for this page.
//
MI_SET_MODIFIED (Pfn1, 0, 0x22);
//
// Up the reference count for the physical page as there
// is I/O in progress.
//
Pfn1->u3.e2.ReferenceCount += 1;
*LastPage = PageFrameIndex;
LastPage += 1;
}
else {
//
// This page was not modified and therefore ends the
// current write cluster if any. Set WriteNow to TRUE
// if there is a cluster being built.
//
if (LastWritten != NULL) {
WriteNow = TRUE;
}
}
}
else {
//
// This page was not modified and therefore ends the
// current write cluster if any. Set WriteNow to TRUE
// if there is a cluster being built.
//
if (LastWritten != NULL) {
WriteNow = TRUE;
}
}
PointerPte += 1;
CheckForWrite:
//
// Write the current cluster if it is complete,
// full, or the loop is now complete.
//
if ((WriteNow) ||
((PointerPte == LastPte) && (LastWritten != NULL))) {
LARGE_INTEGER EndOfFile;
//
// Issue the write request.
//
UNLOCK_PFN (OldIrql);
WriteNow = FALSE;
//
// Make sure the write does not go past the
// end of file. (segment size).
//
EndOfFile = MiEndingOffset(Subsection);
TempOffset = (UINT64) EndOfFile.QuadPart;
if (StartingOffset + Mdl->ByteCount > TempOffset) {
ASSERT ((ULONG_PTR)(TempOffset - StartingOffset) >
(Mdl->ByteCount - PAGE_SIZE));
Mdl->ByteCount = (ULONG)(TempOffset - StartingOffset);
}
ReflushCount = 0;
while (TRUE) {
KeClearEvent (&IoEvent);
Status = IoSynchronousPageWrite (FilePointer,
Mdl,
(PLARGE_INTEGER)&StartingOffset,
&IoEvent,
IoStatus);
if (NT_SUCCESS(Status)) {
//
// Success was returned, so wait for the i/o event.
//
KeWaitForSingleObject (&IoEvent,
WrPageOut,
KernelMode,
FALSE,
NULL);
}
else {
//
// Copy the error to the IoStatus, for error
// handling below.
//
IoStatus->Status = Status;
}
if (Mdl->MdlFlags & MDL_MAPPED_TO_SYSTEM_VA) {
MmUnmapLockedPages (Mdl->MappedSystemVa, Mdl);
}
if (MmIsRetryIoStatus(IoStatus->Status)) {
ReflushCount -= 1;
if (ReflushCount & MiIoRetryMask) {
KeDelayExecutionThread (KernelMode, FALSE, (PLARGE_INTEGER)&Mm30Milliseconds);
continue;
}
}
break;
}
Page = (PPFN_NUMBER)(Mdl + 1);
LOCK_PFN (OldIrql);
if (MiIsPteOnPdeBoundary(PointerPte) == 0) {
//
// The next PTE is not in a different page, make
// sure the PTE for the prototype PTE page was not
// put in transition while the I/O was in progress.
// Note the prototype PTE page itself cannot be reused
// as each outstanding page has a sharecount on it - but
// the PTE mapping it can be put in transition regardless
// of sharecount because it is a system page.
//
if (MiGetPteAddress (PointerPte)->u.Hard.Valid == 0) {
MiMakeSystemAddressValidPfn (PointerPte, OldIrql);
}
}
if (NT_SUCCESS(IoStatus->Status)) {
//
// The I/O completed successfully, unlock the pages.
//
while (Page < LastPage) {
Pfn2 = MI_PFN_ELEMENT (*Page);
MI_REMOVE_LOCKED_PAGE_CHARGE_AND_DECREF(Pfn2, 19);
Page += 1;
}
}
else {
//
// Don't count on the file system to convey
// anything in the information field on errors.
//
IoStatus->Information = 0;
//
// The I/O completed unsuccessfully, unlock the pages
// and return an error status.
//
while (Page < LastPage) {
Pfn2 = MI_PFN_ELEMENT (*Page);
//
// Mark the page dirty again so it can be rewritten.
//
MI_SET_MODIFIED (Pfn2, 1, 0x1);
MI_REMOVE_LOCKED_PAGE_CHARGE_AND_DECREF (Pfn2, 21);
Page += 1;
}
if ((MmIsRetryIoStatus (IoStatus->Status)) &&
(MaxClusterSize != 1) &&
(Mdl->ByteCount > PAGE_SIZE)) {
//
// Retries of a cluster have failed, reissue
// the cluster one page at a time as the
// storage stack should always be able to
// make forward progress this way.
//
ASSERT (FirstWritten != NULL);
ASSERT (LastWritten != NULL);
ASSERT (FirstWritten != LastWritten);
PointerPte = FirstWritten;
if (MiGetPteAddress (PointerPte)->u.Hard.Valid == 0) {
MiMakeSystemAddressValidPfn (PointerPte, OldIrql);
}
MaxClusterSize = 1;
}
else {
//
// Calculate how much was written thus far
// and add that to the information field
// of the IOSB.
//
IoStatus->Information +=
(((LastWritten - StartingPte) << PAGE_SHIFT) -
Mdl->ByteCount);
LastWritten = NULL;
//
// Set this to force termination of the outermost loop.
//
Subsection = LastSubsection;
break;
}
} // end if error on i/o
//
// As the PFN lock has been released and
// reacquired, do this loop again as the
// PTE may have changed state.
//
LastWritten = NULL;
} // end if chunk to write
} //end while
ASSERT (MappedSubsection->DereferenceList.Flink == NULL);
ASSERT (((LONG_PTR)MappedSubsection->NumberOfMappedViews >= 1) ||
(MappedSubsection->u.SubsectionFlags.SubsectionStatic == 1));
MappedSubsection->NumberOfMappedViews -= 1;
if ((MappedSubsection->NumberOfMappedViews == 0) &&
(MappedSubsection->u.SubsectionFlags.SubsectionStatic == 0)) {
//
// Insert this subsection into the unused subsection list.
//
InsertTailList (&MmUnusedSubsectionList,
&MappedSubsection->DereferenceList);
MI_UNUSED_SUBSECTIONS_COUNT_INSERT (MappedSubsection);
}
if ((Bail == TRUE) || (Subsection == LastSubsection)) {
//
// The last range has been flushed or we have collided with the
// mapped page writer. Regardless, exit the top FOR loop
// and return.
//
break;
}
Subsection = Subsection->NextSubsection;
PointerPte = Subsection->SubsectionBase;
} //end for
ASSERT (LastWritten == NULL);
ControlArea->FlushInProgressCount -= 1;
if ((ControlArea->u.Flags.CollidedFlush == 1) &&
(ControlArea->FlushInProgressCount == 0)) {
ControlArea->u.Flags.CollidedFlush = 0;
KePulseEvent (&MmCollidedFlushEvent, 0, FALSE);
}
UNLOCK_PFN (OldIrql);
if (Bail == TRUE) {
//
// This routine collided with the mapped page writer and the caller
// expects an error for this. Give it to him.
//
return STATUS_MAPPED_WRITER_COLLISION;
}
return IoStatus->Status;
}
BOOLEAN
MmPurgeSection (
IN PSECTION_OBJECT_POINTERS SectionObjectPointer,
IN PLARGE_INTEGER Offset,
IN SIZE_T RegionSize,
IN ULONG IgnoreCacheViews
)
/*++
Routine Description:
This function determines if any views of the specified section
are mapped, and if not, purges valid pages (even modified ones)
from the specified section and returns any used pages to the free
list. This is accomplished by examining the prototype PTEs
from the specified offset to the end of the section, and if
any prototype PTEs are in the transition state, putting the
prototype PTE back into its original state and putting the
physical page on the free list.
NOTE:
If there is an I/O operation ongoing for one of the pages,
that page is eliminated from the segment and allowed to "float"
until the i/o is complete. Once the share count goes to zero
the page will be added to the free page list.
Arguments:
SectionObjectPointer - Supplies a pointer to the section objects.
Offset - Supplies the offset into the section in which to begin
purging pages. If this argument is not present, then the
whole section is purged without regard to the region size
argument.
RegionSize - Supplies the size of the region to purge. If this
is specified as zero and Offset is specified, the
region from Offset to the end of the file is purged.
Note: The largest value acceptable for RegionSize is
0xFFFF0000;
IgnoreCacheViews - Supplies FALSE if mapped views in the system
cache should cause the function to return FALSE.
This is the normal case.
Supplies TRUE if mapped views should be ignored
and the flush should occur. NOTE THAT IF TRUE
IS SPECIFIED AND ANY DATA PURGED IS CURRENTLY MAPPED
AND VALID A BUGCHECK WILL OCCUR!!
Return Value:
Returns TRUE if either no section exists for the file object or
the section is not mapped and the purge was done, FALSE otherwise.
Note that FALSE is returned if during the purge operation, a page
could not be purged due to a non-zero reference count.
--*/
{
LOGICAL DroppedPfnLock;
PCONTROL_AREA ControlArea;
PMAPPED_FILE_SEGMENT Segment;
PMMPTE PointerPde;
PMMPTE PointerPte;
PMMPTE LastPte;
PMMPTE FinalPte;
MMPTE PteContents;
PMMPFN Pfn1;
PMMPFN Pfn2;
KIRQL OldIrql;
UINT64 PteOffset;
UINT64 LastPteOffset;
PMSUBSECTION MappedSubsection;
PSUBSECTION Subsection;
PSUBSECTION FirstSubsection;
PSUBSECTION LastSubsection;
PSUBSECTION TempSubsection;
PSUBSECTION LastSubsectionWithProtos;
LARGE_INTEGER LocalOffset;
BOOLEAN ReturnValue;
PFN_NUMBER PageFrameIndex;
PFN_NUMBER PageTableFrameIndex;
#if DBG
PFN_NUMBER LastLocked = 0;
#endif
//
// This is needed in case a page is on the mapped page writer list -
// the PFN lock will need to be released and APCs disabled.
//
ASSERT (KeGetCurrentIrql() < DISPATCH_LEVEL);
//
// Capture caller's file size, since we may modify it.
//
if (ARGUMENT_PRESENT(Offset)) {
LocalOffset = *Offset;
Offset = &LocalOffset;
}
//
// See if we can truncate this file to where the caller wants
// us to.
//
if (!MiCanFileBeTruncatedInternal(SectionObjectPointer, Offset, TRUE, &OldIrql)) {
return FALSE;
}
//
// PFN LOCK IS NOW HELD!
//
ControlArea = (PCONTROL_AREA)(SectionObjectPointer->DataSectionObject);
if ((ControlArea == NULL) || (ControlArea->u.Flags.Rom)) {
UNLOCK_PFN (OldIrql);
return TRUE;
}
//
// Even though MiCanFileBeTruncatedInternal returned TRUE, there could
// still be a system cache mapped view. We cannot truncate if
// the Cache Manager has a view mapped.
//
if ((IgnoreCacheViews == FALSE) &&
(ControlArea->NumberOfSystemCacheViews != 0)) {
UNLOCK_PFN (OldIrql);
return FALSE;
}
#if 0
//
// Prevent races when the control area is being deleted as the clean
// path releases the PFN lock midway through. File objects may still have
// section object pointers and data section objects that point at this
// control area, hence the purge can be issued.
//
// Check for this and fail the purge as the control area (and the section
// object pointers/data section objects) will be going away momentarily.
// Note that even though drivers have these data section objects, no one
// currently has an open section for this control area and no one is
// allowed to open one until the clean path finishes.
//
if (ControlArea->u.Flags.BeingDeleted == 1) {
UNLOCK_PFN (OldIrql);
return FALSE;
}
#else
//
// The above check can be removed as MiCanFileBeTruncatedInternal does
// the same check, so just assert it below.
//
ASSERT (ControlArea->u.Flags.BeingDeleted == 0);
#endif
//
// Purge the section - locate the subsection which
// contains the PTEs.
//
ASSERT (ControlArea->u.Flags.GlobalOnlyPerSession == 0);
Subsection = (PSUBSECTION)(ControlArea + 1);
if (!ARGUMENT_PRESENT (Offset)) {
//
// If the offset is not specified, flush the complete file ignoring
// the region size.
//
PteOffset = 0;
RegionSize = 0;
}
else {
PteOffset = (UINT64)(Offset->QuadPart >> PAGE_SHIFT);
//
// Make sure the PTEs are not in the extended part of the segment.
//
while (PteOffset >= (UINT64) Subsection->PtesInSubsection) {
PteOffset -= Subsection->PtesInSubsection;
Subsection = Subsection->NextSubsection;
if (Subsection == NULL) {
//
// The offset must be equal to the size of
// the section, don't purge anything just return.
//
UNLOCK_PFN (OldIrql);
return TRUE;
}
}
ASSERT (PteOffset < (UINT64) Subsection->PtesInSubsection);
}
//
// Locate the address of the last prototype PTE to be flushed.
//
if (RegionSize == 0) {
//
// Flush to end of section.
//
LastSubsection = Subsection;
Segment = (PMAPPED_FILE_SEGMENT) ControlArea->Segment;
if (MiIsAddressValid (Segment, TRUE)) {
if (Segment->LastSubsectionHint != NULL) {
LastSubsection = (PSUBSECTION) Segment->LastSubsectionHint;
}
}
while (LastSubsection->NextSubsection != NULL) {
LastSubsection = LastSubsection->NextSubsection;
}
LastPteOffset = LastSubsection->PtesInSubsection - 1;
}
else {
//
// Calculate the end of the region.
//
LastPteOffset = PteOffset +
(((RegionSize + BYTE_OFFSET(Offset->LowPart)) - 1) >> PAGE_SHIFT);
LastSubsection = Subsection;
while (LastPteOffset >= (UINT64) LastSubsection->PtesInSubsection) {
LastPteOffset -= LastSubsection->PtesInSubsection;
if (LastSubsection->NextSubsection == NULL) {
LastPteOffset = LastSubsection->PtesInSubsection - 1;
break;
}
LastSubsection = LastSubsection->NextSubsection;
}
ASSERT (LastPteOffset < (UINT64) LastSubsection->PtesInSubsection);
}
//
// Try for the fast reference on the first and last subsection.
// If that cannot be gotten, then there are no prototype PTEs for this
// subsection, therefore there is nothing in it to flush so leap forwards.
//
// Note that subsections in between do not need referencing as
// the purge is smart enough to skip them if they're nonresident.
//
if (MiReferenceSubsection ((PMSUBSECTION)Subsection) == FALSE) {
do {
//
// If this increment would put us past the end offset, then nothing
// to flush, just return success.
//
if (Subsection == LastSubsection) {
UNLOCK_PFN (OldIrql);
return TRUE;
}
Subsection = Subsection->NextSubsection;
//
// If this increment put us past the end of section, then nothing
// to flush, just return success.
//
if (Subsection == NULL) {
UNLOCK_PFN (OldIrql);
return TRUE;
}
if (MiReferenceSubsection ((PMSUBSECTION)Subsection) == FALSE) {
continue;
}
//
// Start the flush at this subsection which is now referenced.
//
PointerPte = &Subsection->SubsectionBase[0];
break;
} while (TRUE);
}
else {
PointerPte = &Subsection->SubsectionBase[PteOffset];
}
FirstSubsection = Subsection;
ASSERT (Subsection->SubsectionBase != NULL);
//
// The first subsection is referenced, now reference count the last one.
// If the first is the last, just double reference it anyway as it
// simplifies cleanup later.
//
if (MiReferenceSubsection ((PMSUBSECTION)LastSubsection) == FALSE) {
ASSERT (Subsection != LastSubsection);
TempSubsection = Subsection->NextSubsection;
LastSubsectionWithProtos = NULL;
while (TempSubsection != LastSubsection) {
//
// If this increment put us past the end of section, then nothing
// to flush, just return success.
//
ASSERT (TempSubsection != NULL);
if ((PMSUBSECTION)TempSubsection->SubsectionBase != NULL) {
LastSubsectionWithProtos = TempSubsection;
}
TempSubsection = TempSubsection->NextSubsection;
}
//
// End the flush at this subsection and reference it.
//
if (LastSubsectionWithProtos == NULL) {
ASSERT (Subsection != NULL);
ASSERT (Subsection->SubsectionBase != NULL);
TempSubsection = Subsection;
}
else {
TempSubsection = LastSubsectionWithProtos;
}
if (MiReferenceSubsection ((PMSUBSECTION)TempSubsection) == FALSE) {
ASSERT (FALSE);
}
ASSERT (TempSubsection->SubsectionBase != NULL);
LastSubsection = TempSubsection;
LastPteOffset = LastSubsection->PtesInSubsection - 1;
}
//
// End the flush at this subsection which is now referenced.
//
// Point final PTE to 1 beyond the end.
//
FinalPte = &LastSubsection->SubsectionBase[LastPteOffset + 1];
//
// Increment the number of mapped views to
// prevent the section from being deleted while the purge is
// in progress.
//
ControlArea->NumberOfMappedViews += 1;
//
// Set being purged so no one can map a view
// while the purge is going on.
//
ControlArea->u.Flags.BeingPurged = 1;
ControlArea->u.Flags.WasPurged = 1;
ReturnValue = TRUE;
for (;;) {
if (OldIrql == MM_NOIRQL) {
LOCK_PFN (OldIrql);
}
if (LastSubsection != Subsection) {
//
// Flush to the last PTE in this subsection.
//
LastPte = &Subsection->SubsectionBase[Subsection->PtesInSubsection];
}
else {
//
// Flush to the end of the range.
//
LastPte = FinalPte;
}
if (Subsection->SubsectionBase == NULL) {
//
// The prototype PTEs for this subsection have either never been
// created or have been tossed due to memory pressure. Either
// way, this range can be skipped as there are obviously no
// pages to purge in this range.
//
ASSERT (OldIrql != MM_NOIRQL);
UNLOCK_PFN (OldIrql);
OldIrql = MM_NOIRQL;
goto nextrange;
}
//
// Up the number of mapped views to prevent other threads
// from freeing this to the unused subsection list while we're
// operating on it.
//
MappedSubsection = (PMSUBSECTION) Subsection;
MappedSubsection->NumberOfMappedViews += 1;
if (MappedSubsection->DereferenceList.Flink != NULL) {
//
// Remove this from the list of unused subsections.
//
RemoveEntryList (&MappedSubsection->DereferenceList);
MI_UNUSED_SUBSECTIONS_COUNT_REMOVE (MappedSubsection);
MappedSubsection->DereferenceList.Flink = NULL;
}
//
// Set the access bit so an already ongoing trim won't blindly
// delete the prototype PTEs on completion of a mapped write.
// This can happen if the current thread dirties some pages and
// then deletes the view before the trim write finishes - this
// bit informs the trimming thread that a rescan is needed so
// that writes are not lost.
//
MappedSubsection->u2.SubsectionFlags2.SubsectionAccessed = 1;
//
// If the page table page containing the PTEs is not
// resident, then no PTEs can be in the valid or transition
// state! Skip over the PTEs.
//
if (!MiCheckProtoPtePageState(PointerPte, OldIrql, &DroppedPfnLock)) {
PointerPte = (PMMPTE)(((ULONG_PTR)PointerPte | (PAGE_SIZE - 1)) + 1);
}
while (PointerPte < LastPte) {
//
// If the page table page containing the PTEs is not
// resident, then no PTEs can be in the valid or transition
// state! Skip over the PTEs.
//
if (MiIsPteOnPdeBoundary(PointerPte)) {
if (!MiCheckProtoPtePageState(PointerPte, OldIrql, &DroppedPfnLock)) {
PointerPte = (PMMPTE)((PCHAR)PointerPte + PAGE_SIZE);
continue;
}
}
PteContents = *PointerPte;
if (PteContents.u.Hard.Valid == 1) {
//
// A valid PTE was found, it must be mapped in the
// system cache. Just exit the loop and return FALSE
// and let the caller fix this.
//
ReturnValue = FALSE;
break;
}
if ((PteContents.u.Soft.Prototype == 0) &&
(PteContents.u.Soft.Transition == 1)) {
if (OldIrql == MM_NOIRQL) {
PointerPde = MiGetPteAddress (PointerPte);
LOCK_PFN (OldIrql);
if (PointerPde->u.Hard.Valid == 0) {
MiMakeSystemAddressValidPfn (PointerPte, OldIrql);
}
continue;
}
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_TRANSITION_PTE(&PteContents);
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
if ((Pfn1->OriginalPte.u.Soft.Prototype != 1) ||
(Pfn1->OriginalPte.u.Hard.Valid != 0) ||
(Pfn1->PteAddress != PointerPte)) {
//
// The pool containing the prototype PTEs has been
// corrupted. Pool corruption like this is fatal.
//
KeBugCheckEx (POOL_CORRUPTION_IN_FILE_AREA,
0x2,
(ULONG_PTR)PointerPte,
(ULONG_PTR)Pfn1->PteAddress,
(ULONG_PTR)PteContents.u.Long);
}
#if DBG
if ((Pfn1->u3.e2.ReferenceCount != 0) &&
(Pfn1->u3.e1.WriteInProgress == 0)) {
//
// There must be an I/O in progress on this page.
//
if (MI_GET_PAGE_FRAME_FROM_TRANSITION_PTE(&PteContents) != LastLocked) {
UNLOCK_PFN (OldIrql);
LastLocked = MI_GET_PAGE_FRAME_FROM_TRANSITION_PTE (&PteContents);
PointerPde = MiGetPteAddress (PointerPte);
LOCK_PFN (OldIrql);
if (PointerPde->u.Hard.Valid == 0) {
MiMakeSystemAddressValidPfn (PointerPte, OldIrql);
}
continue;
}
}
#endif //DBG
//
// If the modified page writer has page locked for I/O
// wait for the I/O's to be completed and the pages
// to be unlocked. The eliminates a race condition
// when the modified page writer locks the pages, then
// a purge occurs and completes before the mapped
// writer thread runs.
//
if (Pfn1->u3.e1.WriteInProgress == 1) {
//
// A 3 or more thread deadlock can occur where:
//
// 1. The mapped page writer thread has issued a write
// and is in the filesystem code waiting for a resource.
//
// 2. Thread 2 owns the resource above but is waiting for
// the filesystem's quota mutex.
//
// 3. Thread 3 owns the quota mutex and is right here
// doing a purge from the cache manager when he notices
// the page to be purged is either already being written
// or is in the mapped page writer list. If it is
// already being written everything will unjam. If it
// is still on the mapped page writer list awaiting
// processing, then it must be cancelled - otherwise
// if this thread were to wait, deadlock can occur.
//
// The alternative to all this is for the filesystems to
// always release the quota mutex before purging but the
// filesystem overhead to do this is substantial.
//
if (MiCancelWriteOfMappedPfn (PageFrameIndex, OldIrql) == TRUE) {
//
// Stopping any failed writes (even deliberately
// cancelled ones) automatically cause a delay. A
// successful stop also results in the PFN lock
// being released and reacquired. So loop back to
// the top now as the world may have changed.
//
if (MiGetPteAddress (PointerPte)->u.Hard.Valid == 0) {
MiMakeSystemAddressValidPfn (PointerPte, OldIrql);
}
continue;
}
ASSERT (ControlArea->ModifiedWriteCount != 0);
ASSERT (Pfn1->u3.e2.ReferenceCount != 0);
ControlArea->u.Flags.SetMappedFileIoComplete = 1;
//
// Keep APCs blocked so no special APCs can be delivered
// in KeWait which would cause the dispatcher lock to be
// released opening a window where this thread could miss
// a pulse.
//
UNLOCK_PFN_AND_THEN_WAIT (APC_LEVEL);
KeWaitForSingleObject (&MmMappedFileIoComplete,
WrPageOut,
KernelMode,
FALSE,
NULL);
KeLowerIrql (OldIrql);
PointerPde = MiGetPteAddress (PointerPte);
LOCK_PFN (OldIrql);
if (PointerPde->u.Hard.Valid == 0) {
MiMakeSystemAddressValidPfn (PointerPte, OldIrql);
}
continue;
}
if (Pfn1->u3.e1.ReadInProgress == 1) {
//
// The page currently is being read in from the
// disk. Treat this just like a valid PTE and
// return false.
//
ReturnValue = FALSE;
break;
}
ASSERT (!((Pfn1->OriginalPte.u.Soft.Prototype == 0) &&
(Pfn1->OriginalPte.u.Soft.Transition == 1)));
MI_WRITE_INVALID_PTE (PointerPte, Pfn1->OriginalPte);
ASSERT (Pfn1->OriginalPte.u.Hard.Valid == 0);
ControlArea->NumberOfPfnReferences -= 1;
ASSERT ((LONG)ControlArea->NumberOfPfnReferences >= 0);
MiUnlinkPageFromList (Pfn1);
MI_SET_PFN_DELETED (Pfn1);
PageTableFrameIndex = Pfn1->u4.PteFrame;
Pfn2 = MI_PFN_ELEMENT (PageTableFrameIndex);
MiDecrementShareCountInline (Pfn2, PageTableFrameIndex);
//
// If the reference count for the page is zero, insert
// it into the free page list, otherwise leave it alone
// and when the reference count is decremented to zero
// the page will go to the free list.
//
if (Pfn1->u3.e2.ReferenceCount == 0) {
MiReleasePageFileSpace (Pfn1->OriginalPte);
MiInsertPageInFreeList (MI_GET_PAGE_FRAME_FROM_TRANSITION_PTE (&PteContents));
}
}
PointerPte += 1;
if ((MiIsPteOnPdeBoundary(PointerPte)) && (OldIrql != MM_NOIRQL)) {
//
// Unlock PFN so large requests will not block other
// threads on MP systems.
//
UNLOCK_PFN (OldIrql);
OldIrql = MM_NOIRQL;
}
}
if (OldIrql == MM_NOIRQL) {
LOCK_PFN (OldIrql);
}
ASSERT (MappedSubsection->DereferenceList.Flink == NULL);
ASSERT (((LONG_PTR)MappedSubsection->NumberOfMappedViews >= 1) ||
(MappedSubsection->u.SubsectionFlags.SubsectionStatic == 1));
MappedSubsection->NumberOfMappedViews -= 1;
if ((MappedSubsection->NumberOfMappedViews == 0) &&
(MappedSubsection->u.SubsectionFlags.SubsectionStatic == 0)) {
//
// Insert this subsection into the unused subsection list.
//
InsertTailList (&MmUnusedSubsectionList,
&MappedSubsection->DereferenceList);
MI_UNUSED_SUBSECTIONS_COUNT_INSERT (MappedSubsection);
}
ASSERT (OldIrql != MM_NOIRQL);
UNLOCK_PFN (OldIrql);
OldIrql = MM_NOIRQL;
nextrange:
if ((LastSubsection != Subsection) && (ReturnValue)) {
//
// Get the next subsection in the list.
//
Subsection = Subsection->NextSubsection;
PointerPte = Subsection->SubsectionBase;
}
else {
//
// The last range has been flushed, exit the top FOR loop
// and return.
//
break;
}
}
if (OldIrql == MM_NOIRQL) {
LOCK_PFN (OldIrql);
}
MiDecrementSubsections (FirstSubsection, FirstSubsection);
MiDecrementSubsections (LastSubsection, LastSubsection);
ASSERT ((LONG)ControlArea->NumberOfMappedViews >= 1);
ControlArea->NumberOfMappedViews -= 1;
ControlArea->u.Flags.BeingPurged = 0;
//
// Check to see if the control area should be deleted. This
// will release the PFN lock.
//
MiCheckControlArea (ControlArea, NULL, OldIrql);
return ReturnValue;
}
BOOLEAN
MmFlushImageSection (
IN PSECTION_OBJECT_POINTERS SectionPointer,
IN MMFLUSH_TYPE FlushType
)
/*++
Routine Description:
This function determines if any views of the specified image section
are mapped, and if not, flushes valid pages (even modified ones)
from the specified section and returns any used pages to the free
list. This is accomplished by examining the prototype PTEs
from the specified offset to the end of the section, and if
any prototype PTEs are in the transition state, putting the
prototype PTE back into its original state and putting the
physical page on the free list.
Arguments:
SectionPointer - Supplies a pointer to a section object pointers
within the FCB.
FlushType - Supplies the type of flush to check for. One of
MmFlushForDelete or MmFlushForWrite.
Return Value:
Returns TRUE if either no section exists for the file object or
the section is not mapped and the purge was done, FALSE otherwise.
--*/
{
PLIST_ENTRY Next;
PCONTROL_AREA ControlArea;
PLARGE_CONTROL_AREA LargeControlArea;
KIRQL OldIrql;
LOGICAL state;
if (FlushType == MmFlushForDelete) {
//
// Do a quick check to see if there are any mapped views for
// the data section. If so, just return FALSE.
//
LOCK_PFN (OldIrql);
ControlArea = (PCONTROL_AREA)(SectionPointer->DataSectionObject);
if (ControlArea != NULL) {
if ((ControlArea->NumberOfUserReferences != 0) ||
(ControlArea->u.Flags.BeingCreated)) {
UNLOCK_PFN (OldIrql);
return FALSE;
}
}
UNLOCK_PFN (OldIrql);
}
//
// Check the status of the control area. If the control area is in use
// or the control area is being deleted, this operation cannot continue.
//
state = MiCheckControlAreaStatus (CheckImageSection,
SectionPointer,
FALSE,
&ControlArea,
&OldIrql);
if (ControlArea == NULL) {
return (BOOLEAN) state;
}
//
// PFN LOCK IS NOW HELD!
//
//
// Repeat until there are no more control areas - multiple control areas
// for the same image section occur to support user global DLLs - these DLLs
// require data that is shared within a session but not across sessions.
// Note this can only happen for Hydra.
//
do {
//
// Set the being deleted flag and up the number of mapped views
// for the segment. Upping the number of mapped views prevents
// the segment from being deleted and passed to the deletion thread
// while we are forcing a delete.
//
ControlArea->u.Flags.BeingDeleted = 1;
ControlArea->NumberOfMappedViews = 1;
LargeControlArea = NULL;
if (ControlArea->u.Flags.GlobalOnlyPerSession == 0) {
NOTHING;
}
else if (IsListEmpty(&((PLARGE_CONTROL_AREA)ControlArea)->UserGlobalList)) {
ASSERT (ControlArea ==
(PCONTROL_AREA)SectionPointer->ImageSectionObject);
}
else {
//
// Check if there's only one image section in this control area, so
// we don't reference the section object pointers as the
// MiCleanSection call may result in its deletion.
//
//
// There are multiple control areas, bump the reference count
// on one of them (doesn't matter which one) so that it can't
// go away. This ensures the section object pointers will stick
// around even after the calls below so we can safely reloop to
// flush any other remaining control areas.
//
ASSERT (ControlArea->u.Flags.GlobalOnlyPerSession == 1);
Next = ((PLARGE_CONTROL_AREA)ControlArea)->UserGlobalList.Flink;
LargeControlArea = CONTAINING_RECORD (Next,
LARGE_CONTROL_AREA,
UserGlobalList);
ASSERT (LargeControlArea->u.Flags.GlobalOnlyPerSession == 1);
LargeControlArea->NumberOfSectionReferences += 1;
}
//
// This is a page file backed or image segment. The segment is being
// deleted, remove all references to the paging file and physical
// memory.
//
UNLOCK_PFN (OldIrql);
MiCleanSection (ControlArea, TRUE);
//
// Get the next Hydra control area.
//
if (LargeControlArea != NULL) {
state = MiCheckControlAreaStatus (CheckImageSection,
SectionPointer,
FALSE,
&ControlArea,
&OldIrql);
if (!ControlArea) {
LOCK_PFN (OldIrql);
LargeControlArea->NumberOfSectionReferences -= 1;
MiCheckControlArea ((PCONTROL_AREA)LargeControlArea,
NULL,
OldIrql);
}
else {
LargeControlArea->NumberOfSectionReferences -= 1;
MiCheckControlArea ((PCONTROL_AREA)LargeControlArea,
NULL,
OldIrql);
LOCK_PFN (OldIrql);
}
}
else {
state = TRUE;
break;
}
} while (ControlArea);
return (BOOLEAN) state;
}
VOID
MiFlushDirtyBitsToPfn (
IN PMMPTE PointerPte,
IN PMMPTE LastPte,
IN PEPROCESS Process,
IN BOOLEAN SystemCache
)
{
KIRQL OldIrql;
MMPTE PteContents;
PMMPFN Pfn1;
PVOID Va;
PMMPTE PointerPde;
PMMPTE PointerPpe;
PMMPTE PointerPxe;
ULONG Waited;
MMPTE_FLUSH_LIST PteFlushList;
PteFlushList.Count = 0;
Va = MiGetVirtualAddressMappedByPte (PointerPte);
LOCK_PFN (OldIrql);
while (PointerPte <= LastPte) {
PteContents = *PointerPte;
if ((PteContents.u.Hard.Valid == 1) &&
(MI_IS_PTE_DIRTY (PteContents))) {
//
// Flush the modify bit to the PFN database.
//
Pfn1 = MI_PFN_ELEMENT (PteContents.u.Hard.PageFrameNumber);
MI_SET_MODIFIED (Pfn1, 1, 0x2);
MI_SET_PTE_CLEAN (PteContents);
//
// No need to capture the PTE contents as we are going to
// write the page anyway and the Modify bit will be cleared
// before the write is done.
//
MI_WRITE_VALID_PTE_NEW_PROTECTION (PointerPte, PteContents);
if (PteFlushList.Count < MM_MAXIMUM_FLUSH_COUNT) {
PteFlushList.FlushVa[PteFlushList.Count] = Va;
PteFlushList.Count += 1;
}
}
Va = (PVOID)((PCHAR)Va + PAGE_SIZE);
PointerPte += 1;
if (MiIsPteOnPdeBoundary (PointerPte)) {
if (PteFlushList.Count != 0) {
MiFlushPteList (&PteFlushList, SystemCache);
PteFlushList.Count = 0;
}
PointerPde = MiGetPteAddress (PointerPte);
while (PointerPte <= LastPte) {
PointerPxe = MiGetPdeAddress (PointerPde);
PointerPpe = MiGetPteAddress (PointerPde);
if (!MiDoesPxeExistAndMakeValid (PointerPxe,
Process,
OldIrql,
&Waited)) {
//
// No page directory parent page exists for this address.
//
PointerPxe += 1;
PointerPpe = MiGetVirtualAddressMappedByPte (PointerPxe);
PointerPde = MiGetVirtualAddressMappedByPte (PointerPpe);
PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
}
else if (!MiDoesPpeExistAndMakeValid (PointerPpe,
Process,
OldIrql,
&Waited)) {
//
// No page directory page exists for this address.
//
PointerPpe += 1;
PointerPde = MiGetVirtualAddressMappedByPte (PointerPpe);
PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
}
else {
Waited = 0;
if (!MiDoesPdeExistAndMakeValid (PointerPde,
Process,
OldIrql,
&Waited)) {
//
// No page table page exists for this address.
//
PointerPde += 1;
PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
}
else {
//
// If the PFN lock (and accordingly the WS mutex) was
// released and reacquired we must retry the operation.
//
if (Waited != 0) {
continue;
}
//
// The PFN lock has been held since we acquired the
// page directory parent, ie: this PTE we can operate on
// immediately.
//
break;
}
}
}
Va = MiGetVirtualAddressMappedByPte (PointerPte);
}
}
if (PteFlushList.Count != 0) {
MiFlushPteList (&PteFlushList, SystemCache);
}
UNLOCK_PFN (OldIrql);
return;
}
PSUBSECTION
MiGetSystemCacheSubsection (
IN PVOID BaseAddress,
OUT PMMPTE *ProtoPte
)
{
PMMPTE PointerPte;
PSUBSECTION Subsection;
PointerPte = MiGetPteAddress (BaseAddress);
Subsection = MiGetSubsectionAndProtoFromPte (PointerPte, ProtoPte);
return Subsection;
}
LOGICAL
MiCheckProtoPtePageState (
IN PMMPTE PrototypePte,
IN KIRQL OldIrql,
OUT PLOGICAL DroppedPfnLock
)
/*++
Routine Description:
Checks the state of the page containing the specified prototype PTE.
If the page is valid or transition and has transition or valid prototype
PTEs contained with it, TRUE is returned and the page is made valid
(if transition). Otherwise return FALSE indicating no prototype
PTEs within this page are of interest.
Arguments:
PrototypePte - Supplies a pointer to a prototype PTE within the page.
OldIrql - Supplies the IRQL the caller acquired the PFN lock at or MM_NOIRQL
if the caller does not hold the PFN lock.
DroppedPfnLock - Supplies a pointer to a logical this routine sets to
TRUE if the PFN lock is released & reacquired.
Return Value:
TRUE if the page containing the proto PTE was made resident.
FALSE otherwise.
--*/
{
PMMPTE PointerPte;
MMPTE PteContents;
PFN_NUMBER PageFrameIndex;
PMMPFN Pfn;
*DroppedPfnLock = FALSE;
#if (_MI_PAGING_LEVELS >= 3)
//
// First check whether the page directory page is present. Since there
// is no lazy loading of PPEs, the validity check alone is sufficient.
//
PointerPte = MiGetPdeAddress (PrototypePte);
PteContents = *PointerPte;
if (PteContents.u.Hard.Valid == 0) {
return FALSE;
}
#endif
PointerPte = MiGetPteAddress (PrototypePte);
#if (_MI_PAGING_LEVELS < 3)
if (PointerPte->u.Hard.Valid == 0) {
MiCheckPdeForPagedPool (PrototypePte);
}
#endif
PteContents = *PointerPte;
if (PteContents.u.Hard.Valid == 1) {
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (&PteContents);
Pfn = MI_PFN_ELEMENT (PageFrameIndex);
if (Pfn->u2.ShareCount != 1) {
return TRUE;
}
}
else if ((PteContents.u.Soft.Prototype == 0) &&
(PteContents.u.Soft.Transition == 1)) {
//
// Transition, if on standby or modified, return FALSE.
//
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_TRANSITION_PTE (&PteContents);
Pfn = MI_PFN_ELEMENT (PageFrameIndex);
if (Pfn->u3.e1.PageLocation >= ActiveAndValid) {
if (OldIrql != MM_NOIRQL) {
MiMakeSystemAddressValidPfn (PrototypePte, OldIrql);
*DroppedPfnLock = TRUE;
}
return TRUE;
}
}
//
// Page is not resident or is on standby / modified list.
//
return FALSE;
}