mirror of https://github.com/tongzx/nt5src
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.
3152 lines
91 KiB
3152 lines
91 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 {
|
|
|
|
//
|
|
// Initializing Vad, ControlArea and EntireRestOfVad is not needed for
|
|
// correctness but without it the compiler cannot compile this code
|
|
// W4 to check for use of uninitialized variables.
|
|
//
|
|
|
|
Vad = NULL;
|
|
ControlArea = NULL;
|
|
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, FALSE, &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, FALSE, &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, FALSE, &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;
|
|
ULONG PteOffset;
|
|
ULONG 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 (MmIsAddressValid (Segment)) {
|
|
if (Segment->LastSubsectionHint != NULL) {
|
|
LastSubsection = (PSUBSECTION) Segment->LastSubsectionHint;
|
|
}
|
|
}
|
|
|
|
while (LastSubsection->NextSubsection != NULL) {
|
|
LastSubsection = LastSubsection->NextSubsection;
|
|
}
|
|
|
|
LastPteOffset = LastSubsection->PtesInSubsection - 1;
|
|
}
|
|
else {
|
|
|
|
PteOffset = (ULONG)(Offset->QuadPart >> PAGE_SHIFT);
|
|
|
|
//
|
|
// Make sure the PTEs are not in the extended part of the segment.
|
|
//
|
|
|
|
while (PteOffset >= 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 < Subsection->PtesInSubsection);
|
|
|
|
//
|
|
// Locate the address of the last prototype PTE to be flushed.
|
|
//
|
|
|
|
LastPteOffset = PteOffset + (ULONG)(((RegionSize + BYTE_OFFSET(Offset->LowPart)) - 1) >> PAGE_SHIFT);
|
|
|
|
LastSubsection = Subsection;
|
|
|
|
while (LastPteOffset >= 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;
|
|
}
|
|
|
|
//
|
|
// End the flush at this subsection which is now referenced.
|
|
//
|
|
|
|
LastPte = &LastSubsection->SubsectionBase[LastPteOffset];
|
|
|
|
//
|
|
// Up the map view count so the control area cannot be deleted
|
|
// out from under the call.
|
|
//
|
|
|
|
ControlArea->NumberOfMappedViews += 1;
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
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 =
|
|
(Subsection->StartingSector + 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, TRUE, &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, TRUE, &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)) {
|
|
|
|
if (!KdDebuggerNotPresent) {
|
|
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, 18);
|
|
}
|
|
else {
|
|
MI_ADD_LOCKED_PAGE_CHARGE(Pfn1, 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.
|
|
//
|
|
|
|
MiMakeSystemAddressValidPfn (PointerPte);
|
|
}
|
|
|
|
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;
|
|
MiMakeSystemAddressValidPfn (PointerPte);
|
|
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 ((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 PointerPte;
|
|
PMMPTE LastPte;
|
|
PMMPTE FinalPte;
|
|
MMPTE PteContents;
|
|
PMMPFN Pfn1;
|
|
PMMPFN Pfn2;
|
|
KIRQL OldIrql;
|
|
ULONG PteOffset;
|
|
ULONG LastPteOffset;
|
|
PMSUBSECTION MappedSubsection;
|
|
PSUBSECTION Subsection;
|
|
PSUBSECTION FirstSubsection;
|
|
PSUBSECTION LastSubsection;
|
|
PSUBSECTION TempSubsection;
|
|
PSUBSECTION LastSubsectionWithProtos;
|
|
LARGE_INTEGER LocalOffset;
|
|
LOGICAL LockHeld;
|
|
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 = (ULONG)(Offset->QuadPart >> PAGE_SHIFT);
|
|
|
|
//
|
|
// Make sure the PTEs are not in the extended part of the segment.
|
|
//
|
|
|
|
while (PteOffset >= 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 < 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 (MmIsAddressValid (Segment)) {
|
|
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 +
|
|
(ULONG) (((RegionSize + BYTE_OFFSET(Offset->LowPart)) - 1) >> PAGE_SHIFT);
|
|
|
|
LastSubsection = Subsection;
|
|
|
|
while (LastPteOffset >= 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
|
|
// 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;
|
|
|
|
LockHeld = TRUE;
|
|
ReturnValue = TRUE;
|
|
|
|
for (;;) {
|
|
|
|
if (!LockHeld) {
|
|
LockHeld = TRUE;
|
|
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 (LockHeld);
|
|
UNLOCK_PFN (OldIrql);
|
|
LockHeld = FALSE;
|
|
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, LockHeld, &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, LockHeld, &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 (!LockHeld) {
|
|
LockHeld = TRUE;
|
|
LOCK_PFN (OldIrql);
|
|
MiMakeSystemAddressValidPfn (PointerPte);
|
|
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);
|
|
LOCK_PFN (OldIrql);
|
|
MiMakeSystemAddressValidPfn (PointerPte);
|
|
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) == 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.
|
|
//
|
|
|
|
MiMakeSystemAddressValidPfn (PointerPte);
|
|
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);
|
|
LOCK_PFN (OldIrql);
|
|
MiMakeSystemAddressValidPfn (PointerPte);
|
|
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)) && (LockHeld)) {
|
|
|
|
//
|
|
// Unlock PFN so large requests will not block other
|
|
// threads on MP systems.
|
|
//
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
LockHeld = FALSE;
|
|
}
|
|
}
|
|
|
|
if (!LockHeld) {
|
|
LockHeld = TRUE;
|
|
LOCK_PFN (OldIrql);
|
|
}
|
|
|
|
ASSERT (MappedSubsection->DereferenceList.Flink == NULL);
|
|
ASSERT ((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 (LockHeld);
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
LockHeld = FALSE;
|
|
|
|
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 (!LockHeld) {
|
|
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;
|
|
|
|
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.
|
|
//
|
|
|
|
(VOID)KeFlushSingleTb (Va,
|
|
FALSE,
|
|
SystemCache,
|
|
(PHARDWARE_PTE)PointerPte,
|
|
PteContents.u.Flush);
|
|
}
|
|
|
|
Va = (PVOID)((PCHAR)Va + PAGE_SIZE);
|
|
PointerPte += 1;
|
|
|
|
if (MiIsPteOnPdeBoundary (PointerPte)) {
|
|
|
|
PointerPde = MiGetPteAddress (PointerPte);
|
|
|
|
while (PointerPte <= LastPte) {
|
|
|
|
PointerPxe = MiGetPdeAddress (PointerPde);
|
|
PointerPpe = MiGetPteAddress (PointerPde);
|
|
|
|
if (!MiDoesPxeExistAndMakeValid (PointerPxe,
|
|
Process,
|
|
TRUE,
|
|
&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,
|
|
TRUE,
|
|
&Waited)) {
|
|
|
|
//
|
|
// No page directory page exists for this address.
|
|
//
|
|
|
|
PointerPpe += 1;
|
|
PointerPde = MiGetVirtualAddressMappedByPte (PointerPpe);
|
|
PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
|
|
}
|
|
else {
|
|
|
|
Waited = 0;
|
|
|
|
if (!MiDoesPdeExistAndMakeValid (PointerPde,
|
|
Process,
|
|
TRUE,
|
|
&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);
|
|
}
|
|
}
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
return;
|
|
}
|
|
|
|
PSUBSECTION
|
|
MiGetSystemCacheSubsection (
|
|
IN PVOID BaseAddress,
|
|
OUT PMMPTE *ProtoPte
|
|
)
|
|
|
|
{
|
|
KIRQL OldIrql;
|
|
PMMPTE PointerPte;
|
|
PSUBSECTION Subsection;
|
|
|
|
PointerPte = MiGetPteAddress (BaseAddress);
|
|
|
|
LOCK_PFN (OldIrql);
|
|
|
|
Subsection = MiGetSubsectionAndProtoFromPte (PointerPte, ProtoPte);
|
|
UNLOCK_PFN (OldIrql);
|
|
return Subsection;
|
|
}
|
|
|
|
|
|
LOGICAL
|
|
MiCheckProtoPtePageState (
|
|
IN PMMPTE PrototypePte,
|
|
IN LOGICAL PfnLockHeld,
|
|
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.
|
|
|
|
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 if 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 (PfnLockHeld) {
|
|
MiMakeSystemAddressValidPfn (PrototypePte);
|
|
*DroppedPfnLock = TRUE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Page is not resident or is on standby / modified list.
|
|
//
|
|
|
|
return FALSE;
|
|
}
|