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.
4320 lines
132 KiB
4320 lines
132 KiB
/*++
|
|
|
|
Copyright (c) 1989 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
sectsup.c
|
|
|
|
Abstract:
|
|
|
|
This module contains the routines which implement the
|
|
section object.
|
|
|
|
Author:
|
|
|
|
Lou Perazzoli (loup) 22-May-1989
|
|
Landy Wang (landyw) 02-June-1997
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
|
|
#include "mi.h"
|
|
|
|
VOID
|
|
FASTCALL
|
|
MiRemoveBasedSection (
|
|
IN PSECTION Section
|
|
);
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(INIT,MiSectionInitialization)
|
|
#pragma alloc_text(PAGE,MiRemoveBasedSection)
|
|
#pragma alloc_text(PAGE,MmGetFileNameForSection)
|
|
#pragma alloc_text(PAGE,MmGetFileNameForAddress)
|
|
#pragma alloc_text(PAGE,MiSectionDelete)
|
|
#pragma alloc_text(PAGE,MiInsertBasedSection)
|
|
#pragma alloc_text(PAGE,MiGetEventCounter)
|
|
#pragma alloc_text(PAGE,MiFreeEventCounter)
|
|
#pragma alloc_text(PAGE,MmGetFileObjectForSection)
|
|
#endif
|
|
|
|
ULONG MmUnusedSegmentForceFree;
|
|
|
|
ULONG MiSubsectionsProcessed;
|
|
ULONG MiSubsectionActions;
|
|
|
|
SIZE_T MmSharedCommit = 0;
|
|
extern const ULONG MMCONTROL;
|
|
|
|
extern MMPAGE_FILE_EXPANSION MiPageFileContract;
|
|
|
|
//
|
|
// Define segment dereference thread wait object types.
|
|
//
|
|
|
|
typedef enum _SEGMENT_DEREFERENCE_OBJECT {
|
|
SegmentDereference,
|
|
UsedSegmentCleanup,
|
|
SegMaximumObject
|
|
} BALANCE_OBJECT;
|
|
|
|
extern POBJECT_TYPE IoFileObjectType;
|
|
|
|
#ifdef ALLOC_DATA_PRAGMA
|
|
#pragma const_seg("INITCONST")
|
|
#endif
|
|
const GENERIC_MAPPING MiSectionMapping = {
|
|
STANDARD_RIGHTS_READ |
|
|
SECTION_QUERY | SECTION_MAP_READ,
|
|
STANDARD_RIGHTS_WRITE |
|
|
SECTION_MAP_WRITE,
|
|
STANDARD_RIGHTS_EXECUTE |
|
|
SECTION_MAP_EXECUTE,
|
|
SECTION_ALL_ACCESS
|
|
};
|
|
#ifdef ALLOC_DATA_PRAGMA
|
|
#pragma const_seg()
|
|
#endif
|
|
|
|
VOID
|
|
MiRemoveUnusedSegments (
|
|
VOID
|
|
);
|
|
|
|
|
|
VOID
|
|
FASTCALL
|
|
MiInsertBasedSection (
|
|
IN PSECTION Section
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function inserts a virtual address descriptor into the tree and
|
|
reorders the splay tree as appropriate.
|
|
|
|
Arguments:
|
|
|
|
Section - Supplies a pointer to a based section.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Must be holding the section based mutex.
|
|
|
|
--*/
|
|
|
|
{
|
|
ASSERT (Section->Address.EndingVpn >= Section->Address.StartingVpn);
|
|
|
|
MiInsertNode (&Section->Address, &MmSectionBasedRoot);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
FASTCALL
|
|
MiRemoveBasedSection (
|
|
IN PSECTION Section
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function removes a based section from the tree.
|
|
|
|
Arguments:
|
|
|
|
Section - pointer to the based section object to remove.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Must be holding the section based mutex.
|
|
|
|
--*/
|
|
|
|
{
|
|
MiRemoveNode (&Section->Address, &MmSectionBasedRoot);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
MiSegmentDelete (
|
|
PSEGMENT Segment
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called whenever the last reference to a segment object
|
|
has been removed. This routine releases the pool allocated for the
|
|
prototype PTEs and performs consistency checks on those PTEs.
|
|
|
|
For segments which map files, the file object is dereferenced.
|
|
|
|
Note, that for a segment which maps a file, no PTEs may be valid
|
|
or transition, while a segment which is backed by a paging file
|
|
may have transition pages, but no valid pages (there can be no
|
|
PTEs which refer to the segment).
|
|
|
|
Arguments:
|
|
|
|
Segment - a pointer to the segment structure.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PMMPTE PointerPte;
|
|
PMMPTE LastPte;
|
|
PMMPTE PteForProto;
|
|
PMMPFN Pfn1;
|
|
PMMPFN Pfn2;
|
|
KIRQL OldIrql;
|
|
PCONTROL_AREA ControlArea;
|
|
PEVENT_COUNTER Event;
|
|
MMPTE PteContents;
|
|
PSUBSECTION Subsection;
|
|
PSUBSECTION NextSubsection;
|
|
PMSUBSECTION MappedSubsection;
|
|
PFN_NUMBER PageTableFrameIndex;
|
|
SIZE_T NumberOfCommittedPages;
|
|
|
|
ControlArea = Segment->ControlArea;
|
|
|
|
ASSERT (ControlArea->u.Flags.BeingDeleted == 1);
|
|
|
|
ASSERT (ControlArea->Segment->WritableUserReferences == 0);
|
|
|
|
LOCK_PFN (OldIrql);
|
|
if (ControlArea->DereferenceList.Flink != NULL) {
|
|
|
|
//
|
|
// Remove this from the list of unused segments. The dereference
|
|
// segment thread cannot be processing any subsections from this
|
|
// control area right now because it bumps the NumberOfMappedViews
|
|
// for the control area prior to releasing the PFN lock and it checks
|
|
// for BeingDeleted.
|
|
//
|
|
|
|
ExAcquireSpinLockAtDpcLevel (&MmDereferenceSegmentHeader.Lock);
|
|
RemoveEntryList (&ControlArea->DereferenceList);
|
|
|
|
MI_UNUSED_SEGMENTS_REMOVE_CHARGE (ControlArea);
|
|
|
|
ExReleaseSpinLockFromDpcLevel (&MmDereferenceSegmentHeader.Lock);
|
|
}
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
if ((ControlArea->u.Flags.Image) || (ControlArea->u.Flags.File)) {
|
|
|
|
//
|
|
// Unload kernel debugger symbols if any were loaded.
|
|
//
|
|
|
|
if (ControlArea->u.Flags.DebugSymbolsLoaded != 0) {
|
|
|
|
//
|
|
// TEMP TEMP TEMP rip out when debugger converted
|
|
//
|
|
|
|
ANSI_STRING AnsiName;
|
|
NTSTATUS Status;
|
|
|
|
Status = RtlUnicodeStringToAnsiString( &AnsiName,
|
|
(PUNICODE_STRING)&Segment->ControlArea->FilePointer->FileName,
|
|
TRUE );
|
|
|
|
if (NT_SUCCESS( Status)) {
|
|
DbgUnLoadImageSymbols( &AnsiName,
|
|
Segment->BasedAddress,
|
|
(ULONG_PTR)PsGetCurrentProcess());
|
|
RtlFreeAnsiString( &AnsiName );
|
|
}
|
|
LOCK_PFN (OldIrql);
|
|
ControlArea->u.Flags.DebugSymbolsLoaded = 0;
|
|
}
|
|
else {
|
|
LOCK_PFN (OldIrql);
|
|
}
|
|
|
|
//
|
|
// Signal any threads waiting on the deletion event.
|
|
//
|
|
|
|
Event = ControlArea->WaitingForDeletion;
|
|
ControlArea->WaitingForDeletion = NULL;
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
if (Event != NULL) {
|
|
KeSetEvent (&Event->Event, 0, FALSE);
|
|
}
|
|
|
|
//
|
|
// Clear the segment context and dereference the file object
|
|
// for this Segment.
|
|
//
|
|
// If the segment was deleted due to a name collision at insertion
|
|
// we don't want to dereference the file pointer.
|
|
//
|
|
|
|
if (ControlArea->u.Flags.BeingCreated == FALSE) {
|
|
|
|
#if DBG
|
|
if (ControlArea->u.Flags.Image == 1) {
|
|
ASSERT (ControlArea->FilePointer->SectionObjectPointer->ImageSectionObject != (PVOID)ControlArea);
|
|
}
|
|
else {
|
|
ASSERT (ControlArea->FilePointer->SectionObjectPointer->DataSectionObject != (PVOID)ControlArea);
|
|
}
|
|
#endif
|
|
|
|
PERFINFO_SEGMENT_DELETE(ControlArea->FilePointer);
|
|
ObDereferenceObject (ControlArea->FilePointer);
|
|
}
|
|
|
|
//
|
|
// If there have been committed pages in this segment, adjust
|
|
// the total commit count.
|
|
//
|
|
|
|
if (ControlArea->u.Flags.Image == 0) {
|
|
|
|
//
|
|
// This is a mapped data file. None of the prototype
|
|
// PTEs may be referencing a physical page (valid or transition).
|
|
//
|
|
|
|
if (ControlArea->u.Flags.Rom == 0) {
|
|
Subsection = (PSUBSECTION)(ControlArea + 1);
|
|
}
|
|
else {
|
|
Subsection = (PSUBSECTION)((PLARGE_CONTROL_AREA)ControlArea + 1);
|
|
}
|
|
|
|
#if DBG
|
|
if (Subsection->SubsectionBase != NULL) {
|
|
PointerPte = Subsection->SubsectionBase;
|
|
LastPte = PointerPte + Segment->NonExtendedPtes;
|
|
|
|
while (PointerPte < LastPte) {
|
|
|
|
//
|
|
// Prototype PTEs for segments backed by paging file are
|
|
// either in demand zero, page file format, or transition.
|
|
//
|
|
|
|
ASSERT (PointerPte->u.Hard.Valid == 0);
|
|
ASSERT ((PointerPte->u.Soft.Prototype == 1) ||
|
|
(PointerPte->u.Long == 0));
|
|
PointerPte += 1;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Deallocate the control area and subsections.
|
|
//
|
|
|
|
ASSERT (ControlArea->u.Flags.GlobalOnlyPerSession == 0);
|
|
|
|
if (ControlArea->FilePointer != NULL) {
|
|
|
|
MappedSubsection = (PMSUBSECTION) Subsection;
|
|
|
|
LOCK_PFN (OldIrql);
|
|
|
|
while (MappedSubsection != NULL) {
|
|
|
|
if (MappedSubsection->DereferenceList.Flink != NULL) {
|
|
|
|
//
|
|
// Remove this from the list of unused subsections.
|
|
//
|
|
|
|
RemoveEntryList (&MappedSubsection->DereferenceList);
|
|
|
|
MI_UNUSED_SUBSECTIONS_COUNT_REMOVE (MappedSubsection);
|
|
}
|
|
MappedSubsection = (PMSUBSECTION) MappedSubsection->NextSubsection;
|
|
}
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
if (Subsection->SubsectionBase != NULL) {
|
|
ExFreePool (Subsection->SubsectionBase);
|
|
}
|
|
}
|
|
|
|
Subsection = Subsection->NextSubsection;
|
|
|
|
while (Subsection != NULL) {
|
|
if (Subsection->SubsectionBase != NULL) {
|
|
ExFreePool (Subsection->SubsectionBase);
|
|
}
|
|
NextSubsection = Subsection->NextSubsection;
|
|
ExFreePool (Subsection);
|
|
Subsection = NextSubsection;
|
|
}
|
|
|
|
NumberOfCommittedPages = Segment->NumberOfCommittedPages;
|
|
|
|
if (NumberOfCommittedPages != 0) {
|
|
MiReturnCommitment (NumberOfCommittedPages);
|
|
MM_TRACK_COMMIT (MM_DBG_COMMIT_RETURN_SEGMENT_DELETE1,
|
|
NumberOfCommittedPages);
|
|
|
|
InterlockedExchangeAddSizeT (&MmSharedCommit, 0-NumberOfCommittedPages);
|
|
}
|
|
|
|
ExFreePool (ControlArea);
|
|
ExFreePool (Segment);
|
|
|
|
//
|
|
// The file mapped Segment object is now deleted.
|
|
//
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
//
|
|
// This is a page file backed or image segment. The segment is being
|
|
// deleted, remove all references to the paging file and physical memory.
|
|
//
|
|
// The PFN lock is required for deallocating pages from a paging
|
|
// file and for deleting transition PTEs.
|
|
//
|
|
|
|
if ((ControlArea->u.Flags.GlobalOnlyPerSession == 0) &&
|
|
(ControlArea->u.Flags.Rom == 0)) {
|
|
Subsection = (PSUBSECTION)(ControlArea + 1);
|
|
}
|
|
else {
|
|
Subsection = (PSUBSECTION)((PLARGE_CONTROL_AREA)ControlArea + 1);
|
|
}
|
|
|
|
PointerPte = Subsection->SubsectionBase;
|
|
LastPte = PointerPte + Segment->NonExtendedPtes;
|
|
PteForProto = MiGetPteAddress (PointerPte);
|
|
|
|
//
|
|
// Access the first prototype PTE to try and make it resident before
|
|
// acquiring the PFN lock. This is purely an optimization to reduce
|
|
// PFN lock hold duration.
|
|
//
|
|
|
|
*(volatile MMPTE *) PointerPte;
|
|
|
|
LOCK_PFN (OldIrql);
|
|
|
|
if (PteForProto->u.Hard.Valid == 0) {
|
|
MiMakeSystemAddressValidPfn (PointerPte, OldIrql);
|
|
}
|
|
|
|
while (PointerPte < LastPte) {
|
|
|
|
if ((MiIsPteOnPdeBoundary (PointerPte)) &&
|
|
(PointerPte != Subsection->SubsectionBase)) {
|
|
|
|
//
|
|
// Briefly release and reacquire the PFN lock so that
|
|
// processing large segments here doesn't stall other contending
|
|
// threads or DPCs for long periods of time.
|
|
//
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
PteForProto = MiGetPteAddress (PointerPte);
|
|
|
|
LOCK_PFN (OldIrql);
|
|
|
|
//
|
|
// We are on a page boundary, make sure this PTE is resident.
|
|
//
|
|
|
|
if (PteForProto->u.Hard.Valid == 0) {
|
|
MiMakeSystemAddressValidPfn (PointerPte, OldIrql);
|
|
}
|
|
}
|
|
|
|
PteContents = *PointerPte;
|
|
|
|
//
|
|
// Prototype PTEs for segments backed by paging file
|
|
// are either in demand zero, page file format, or transition.
|
|
//
|
|
|
|
ASSERT (PteContents.u.Hard.Valid == 0);
|
|
|
|
if (PteContents.u.Soft.Prototype == 0) {
|
|
|
|
if (PteContents.u.Soft.Transition == 1) {
|
|
|
|
//
|
|
// Prototype PTE in transition, put the page on the free list.
|
|
//
|
|
|
|
Pfn1 = MI_PFN_ELEMENT (PteContents.u.Trans.PageFrameNumber);
|
|
|
|
MI_SET_PFN_DELETED (Pfn1);
|
|
|
|
PageTableFrameIndex = Pfn1->u4.PteFrame;
|
|
Pfn2 = MI_PFN_ELEMENT (PageTableFrameIndex);
|
|
MiDecrementShareCountInline (Pfn2, PageTableFrameIndex);
|
|
|
|
//
|
|
// Check the reference count for the page, if the reference
|
|
// count is zero and the page is not on the freelist,
|
|
// move the page to the free list, if the reference
|
|
// count is not zero, ignore this page.
|
|
// When the reference count goes to zero, it will be placed
|
|
// on the free list.
|
|
//
|
|
|
|
if (Pfn1->u3.e2.ReferenceCount == 0) {
|
|
MiUnlinkPageFromList (Pfn1);
|
|
MiReleasePageFileSpace (Pfn1->OriginalPte);
|
|
MiInsertPageInFreeList (MI_GET_PAGE_FRAME_FROM_TRANSITION_PTE (&PteContents));
|
|
}
|
|
|
|
}
|
|
else {
|
|
|
|
//
|
|
// This is not a prototype PTE, if any paging file
|
|
// space has been allocated, release it.
|
|
//
|
|
|
|
if (IS_PTE_NOT_DEMAND_ZERO (PteContents)) {
|
|
MiReleasePageFileSpace (PteContents);
|
|
}
|
|
}
|
|
}
|
|
#if DBG
|
|
MI_WRITE_INVALID_PTE (PointerPte, ZeroPte);
|
|
#endif
|
|
PointerPte += 1;
|
|
}
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
//
|
|
// If there have been committed pages in this segment, adjust
|
|
// the total commit count.
|
|
//
|
|
|
|
NumberOfCommittedPages = Segment->NumberOfCommittedPages;
|
|
|
|
if (NumberOfCommittedPages != 0) {
|
|
MiReturnCommitment (NumberOfCommittedPages);
|
|
|
|
if (ControlArea->u.Flags.Image) {
|
|
MM_TRACK_COMMIT (MM_DBG_COMMIT_RETURN_SEGMENT_DELETE2,
|
|
NumberOfCommittedPages);
|
|
}
|
|
else {
|
|
MM_TRACK_COMMIT (MM_DBG_COMMIT_RETURN_SEGMENT_DELETE3,
|
|
NumberOfCommittedPages);
|
|
}
|
|
|
|
InterlockedExchangeAddSizeT (&MmSharedCommit, 0-NumberOfCommittedPages);
|
|
}
|
|
|
|
ExFreePool (ControlArea);
|
|
ExFreePool (Segment);
|
|
|
|
return;
|
|
}
|
|
|
|
ULONG
|
|
MmDoesFileHaveUserWritableReferences (
|
|
IN PSECTION_OBJECT_POINTERS SectionPointer
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called by the transaction filesystem to determine if
|
|
the given transaction is referencing a file which has user writable sections
|
|
or user writable views into it. If so, the transaction must be aborted
|
|
as it cannot be guaranteed atomicity.
|
|
|
|
The transaction filesystem is responsible for checking and intercepting
|
|
file object creates that specify write access prior to using this
|
|
interface. Specifically, prior to starting a transaction, the transaction
|
|
filesystem must ensure that there are no writable file objects that
|
|
currently exist for the given file in the transaction. While the
|
|
transaction is ongoing, requests to create file objects with write access
|
|
for the transaction files must be refused.
|
|
|
|
This Mm routine exists to catch the case where the user has closed the
|
|
file handles and the section handles, but still has open writable views.
|
|
|
|
For this reason, no locks are needed to read the value below.
|
|
|
|
Arguments:
|
|
|
|
SectionPointer - Supplies a pointer to the section object pointers
|
|
from the file object.
|
|
|
|
Return Value:
|
|
|
|
Number of user writable references.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, APC_LEVEL or below, no mutexes held.
|
|
|
|
--*/
|
|
|
|
{
|
|
KIRQL OldIrql;
|
|
ULONG WritableUserReferences;
|
|
PCONTROL_AREA ControlArea;
|
|
|
|
LOCK_PFN (OldIrql);
|
|
|
|
ControlArea = (PCONTROL_AREA)(SectionPointer->DataSectionObject);
|
|
|
|
if (ControlArea == NULL) {
|
|
UNLOCK_PFN (OldIrql);
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// Up the map view count so the control area cannot be deleted
|
|
// out from under the call.
|
|
//
|
|
|
|
ControlArea->NumberOfMappedViews += 1;
|
|
if (MiGetPteAddress (&ControlArea->Segment->WritableUserReferences)->u.Hard.Valid == 0) {
|
|
MiMakeSystemAddressValidPfn (&ControlArea->Segment->WritableUserReferences, OldIrql);
|
|
}
|
|
WritableUserReferences = ControlArea->Segment->WritableUserReferences;
|
|
ASSERT ((LONG)ControlArea->NumberOfMappedViews >= 1);
|
|
ControlArea->NumberOfMappedViews -= 1;
|
|
|
|
//
|
|
// This routine will release the PFN lock.
|
|
//
|
|
|
|
MiCheckControlArea (ControlArea, NULL, OldIrql);
|
|
|
|
return WritableUserReferences;
|
|
}
|
|
|
|
VOID
|
|
MiDereferenceControlAreaBySection (
|
|
IN PCONTROL_AREA ControlArea,
|
|
IN ULONG UserRef
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is a nonpaged helper routine to dereference the specified control area.
|
|
|
|
Arguments:
|
|
|
|
ControlArea - Supplies a pointer to the control area.
|
|
|
|
UserRef - Supplies the number of user dereferences to apply.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
KIRQL OldIrql;
|
|
|
|
LOCK_PFN (OldIrql);
|
|
|
|
ControlArea->NumberOfSectionReferences -= 1;
|
|
ControlArea->NumberOfUserReferences -= UserRef;
|
|
|
|
//
|
|
// Check to see if the control area (segment) should be deleted.
|
|
// This routine releases the PFN lock.
|
|
//
|
|
|
|
MiCheckControlArea (ControlArea, NULL, OldIrql);
|
|
}
|
|
|
|
VOID
|
|
MiSectionDelete (
|
|
IN PVOID Object
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
|
|
This routine is called by the object management procedures whenever
|
|
the last reference to a section object has been removed. This routine
|
|
dereferences the associated segment object and checks to see if
|
|
the segment object should be deleted by queueing the segment to the
|
|
segment deletion thread.
|
|
|
|
Arguments:
|
|
|
|
Object - a pointer to the body of the section object.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PSECTION Section;
|
|
PCONTROL_AREA ControlArea;
|
|
ULONG UserRef;
|
|
|
|
Section = (PSECTION)Object;
|
|
|
|
if (Section->Segment == NULL) {
|
|
|
|
//
|
|
// The section was never initialized, no need to remove
|
|
// any structures.
|
|
//
|
|
|
|
return;
|
|
}
|
|
|
|
UserRef = Section->u.Flags.UserReference;
|
|
ControlArea = Section->Segment->ControlArea;
|
|
|
|
if (Section->Address.StartingVpn != 0) {
|
|
|
|
//
|
|
// This section is based, remove the base address from the
|
|
// tree.
|
|
//
|
|
// Get the allocation base mutex.
|
|
//
|
|
|
|
KeAcquireGuardedMutex (&MmSectionBasedMutex);
|
|
|
|
MiRemoveBasedSection (Section);
|
|
|
|
KeReleaseGuardedMutex (&MmSectionBasedMutex);
|
|
}
|
|
|
|
//
|
|
// Adjust the count of writable user sections for transaction support.
|
|
//
|
|
|
|
if ((Section->u.Flags.UserWritable == 1) &&
|
|
(ControlArea->u.Flags.Image == 0) &&
|
|
(ControlArea->FilePointer != NULL)) {
|
|
|
|
ASSERT (Section->InitialPageProtection & (PAGE_READWRITE|PAGE_EXECUTE_READWRITE));
|
|
|
|
InterlockedDecrement ((PLONG)&ControlArea->Segment->WritableUserReferences);
|
|
}
|
|
|
|
//
|
|
// Decrement the number of section references to the segment for this
|
|
// section. This requires APCs to be blocked and the PFN lock to
|
|
// synchronize upon.
|
|
//
|
|
|
|
MiDereferenceControlAreaBySection (ControlArea, UserRef);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
MiDereferenceSegmentThread (
|
|
IN PVOID StartContext
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is the thread for dereferencing segments which have
|
|
no references from any sections or mapped views AND there are
|
|
no prototype PTEs within the segment which are in the transition
|
|
state (i.e., no PFN database references to the segment).
|
|
|
|
It also does double duty and is used for expansion of paging files.
|
|
|
|
Arguments:
|
|
|
|
StartContext - Not used.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PCONTROL_AREA ControlArea;
|
|
PETHREAD CurrentThread;
|
|
PMMPAGE_FILE_EXPANSION PageExpand;
|
|
PLIST_ENTRY NextEntry;
|
|
KIRQL OldIrql;
|
|
static KWAIT_BLOCK WaitBlockArray[SegMaximumObject];
|
|
PVOID WaitObjects[SegMaximumObject];
|
|
NTSTATUS Status;
|
|
|
|
UNREFERENCED_PARAMETER (StartContext);
|
|
|
|
//
|
|
// Make this a real time thread.
|
|
//
|
|
|
|
CurrentThread = PsGetCurrentThread();
|
|
KeSetPriorityThread (&CurrentThread->Tcb, LOW_REALTIME_PRIORITY + 2);
|
|
|
|
CurrentThread->MemoryMaker = 1;
|
|
|
|
WaitObjects[SegmentDereference] = (PVOID)&MmDereferenceSegmentHeader.Semaphore;
|
|
WaitObjects[UsedSegmentCleanup] = (PVOID)&MmUnusedSegmentCleanup;
|
|
|
|
for (;;) {
|
|
|
|
Status = KeWaitForMultipleObjects(SegMaximumObject,
|
|
&WaitObjects[0],
|
|
WaitAny,
|
|
WrVirtualMemory,
|
|
UserMode,
|
|
FALSE,
|
|
NULL,
|
|
&WaitBlockArray[0]);
|
|
|
|
//
|
|
// Switch on the wait status.
|
|
//
|
|
|
|
switch (Status) {
|
|
|
|
case SegmentDereference:
|
|
|
|
//
|
|
// An entry is available to dereference, acquire the spinlock
|
|
// and remove the entry.
|
|
//
|
|
|
|
ExAcquireSpinLock (&MmDereferenceSegmentHeader.Lock, &OldIrql);
|
|
|
|
if (IsListEmpty (&MmDereferenceSegmentHeader.ListHead)) {
|
|
|
|
//
|
|
// There is nothing in the list, rewait.
|
|
//
|
|
|
|
ExReleaseSpinLock (&MmDereferenceSegmentHeader.Lock, OldIrql);
|
|
break;
|
|
}
|
|
|
|
NextEntry = RemoveHeadList (&MmDereferenceSegmentHeader.ListHead);
|
|
|
|
ExReleaseSpinLock (&MmDereferenceSegmentHeader.Lock, OldIrql);
|
|
|
|
ASSERT (KeGetCurrentIrql() < DISPATCH_LEVEL);
|
|
|
|
ControlArea = CONTAINING_RECORD (NextEntry,
|
|
CONTROL_AREA,
|
|
DereferenceList);
|
|
|
|
if (ControlArea->Segment != NULL) {
|
|
|
|
//
|
|
// This is a control area, delete it after indicating
|
|
// this entry is not on any list.
|
|
//
|
|
|
|
ControlArea->DereferenceList.Flink = NULL;
|
|
|
|
ASSERT (ControlArea->u.Flags.FilePointerNull == 1);
|
|
MiSegmentDelete (ControlArea->Segment);
|
|
}
|
|
else {
|
|
|
|
//
|
|
// This is a request to expand or reduce the paging files.
|
|
//
|
|
|
|
PageExpand = (PMMPAGE_FILE_EXPANSION)ControlArea;
|
|
|
|
if (PageExpand->RequestedExpansionSize == MI_CONTRACT_PAGEFILES) {
|
|
|
|
//
|
|
// Attempt to reduce the size of the paging files.
|
|
//
|
|
|
|
ASSERT (PageExpand == &MiPageFileContract);
|
|
|
|
MiAttemptPageFileReduction ();
|
|
}
|
|
else {
|
|
|
|
//
|
|
// Attempt to expand the size of the paging files.
|
|
//
|
|
|
|
MiExtendPagingFiles (PageExpand);
|
|
KeSetEvent (&PageExpand->Event, 0, FALSE);
|
|
MiRemoveUnusedSegments ();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case UsedSegmentCleanup:
|
|
|
|
MiRemoveUnusedSegments ();
|
|
|
|
KeClearEvent (&MmUnusedSegmentCleanup);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
KdPrint(("MMSegmentderef: Illegal wait status, %lx =\n", Status));
|
|
break;
|
|
} // end switch
|
|
|
|
} //end for
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
ULONG
|
|
MiSectionInitialization (
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function creates the section object type descriptor at system
|
|
initialization and stores the address of the object type descriptor
|
|
in global storage.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
TRUE - Initialization was successful.
|
|
|
|
FALSE - Initialization Failed.
|
|
|
|
|
|
|
|
--*/
|
|
|
|
{
|
|
OBJECT_TYPE_INITIALIZER ObjectTypeInitializer;
|
|
UNICODE_STRING TypeName;
|
|
HANDLE ThreadHandle;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
UNICODE_STRING SectionName;
|
|
PSECTION Section;
|
|
HANDLE Handle;
|
|
PSEGMENT Segment;
|
|
PCONTROL_AREA ControlArea;
|
|
NTSTATUS Status;
|
|
|
|
ASSERT (MmSectionBasedRoot.NumberGenericTableElements == 0);
|
|
MmSectionBasedRoot.BalancedRoot.u1.Parent = &MmSectionBasedRoot.BalancedRoot;
|
|
|
|
//
|
|
// Initialize the common fields of the Object Type Initializer record
|
|
//
|
|
|
|
RtlZeroMemory (&ObjectTypeInitializer, sizeof(ObjectTypeInitializer));
|
|
ObjectTypeInitializer.Length = sizeof (ObjectTypeInitializer);
|
|
ObjectTypeInitializer.InvalidAttributes = OBJ_OPENLINK;
|
|
ObjectTypeInitializer.GenericMapping = MiSectionMapping;
|
|
ObjectTypeInitializer.PoolType = PagedPool;
|
|
ObjectTypeInitializer.DefaultPagedPoolCharge = sizeof(SECTION);
|
|
|
|
//
|
|
// Initialize string descriptor.
|
|
//
|
|
|
|
#define TYPE_SECTION L"Section"
|
|
|
|
TypeName.Buffer = (const PUSHORT) TYPE_SECTION;
|
|
TypeName.Length = sizeof (TYPE_SECTION) - sizeof (WCHAR);
|
|
TypeName.MaximumLength = sizeof TYPE_SECTION;
|
|
|
|
//
|
|
// Create the section object type descriptor
|
|
//
|
|
|
|
ObjectTypeInitializer.ValidAccessMask = SECTION_ALL_ACCESS;
|
|
ObjectTypeInitializer.DeleteProcedure = MiSectionDelete;
|
|
ObjectTypeInitializer.GenericMapping = MiSectionMapping;
|
|
ObjectTypeInitializer.UseDefaultObject = TRUE;
|
|
|
|
if (!NT_SUCCESS(ObCreateObjectType (&TypeName,
|
|
&ObjectTypeInitializer,
|
|
(PSECURITY_DESCRIPTOR) NULL,
|
|
&MmSectionObjectType))) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Create the Segment dereferencing thread.
|
|
//
|
|
|
|
InitializeObjectAttributes (&ObjectAttributes,
|
|
NULL,
|
|
0,
|
|
NULL,
|
|
NULL);
|
|
|
|
if (!NT_SUCCESS(PsCreateSystemThread (&ThreadHandle,
|
|
THREAD_ALL_ACCESS,
|
|
&ObjectAttributes,
|
|
0,
|
|
NULL,
|
|
MiDereferenceSegmentThread,
|
|
NULL))) {
|
|
return FALSE;
|
|
}
|
|
|
|
ZwClose (ThreadHandle);
|
|
|
|
//
|
|
// Create the permanent section which maps physical memory.
|
|
//
|
|
|
|
Segment = (PSEGMENT)ExAllocatePoolWithTag (PagedPool,
|
|
sizeof(SEGMENT),
|
|
'gSmM');
|
|
if (Segment == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
ControlArea = ExAllocatePoolWithTag (NonPagedPool,
|
|
(ULONG)sizeof(CONTROL_AREA),
|
|
MMCONTROL);
|
|
if (ControlArea == NULL) {
|
|
ExFreePool (Segment);
|
|
return FALSE;
|
|
}
|
|
|
|
RtlZeroMemory (Segment, sizeof(SEGMENT));
|
|
RtlZeroMemory (ControlArea, sizeof(CONTROL_AREA));
|
|
|
|
ControlArea->Segment = Segment;
|
|
ControlArea->NumberOfSectionReferences = 1;
|
|
ControlArea->u.Flags.PhysicalMemory = 1;
|
|
|
|
Segment->ControlArea = ControlArea;
|
|
Segment->SegmentPteTemplate = ZeroPte;
|
|
|
|
//
|
|
// Now that the segment object is created, create a section object
|
|
// which refers to the segment object.
|
|
//
|
|
|
|
#define DEVICE_PHYSICAL_MEMORY L"\\Device\\PhysicalMemory"
|
|
|
|
SectionName.Buffer = (const PUSHORT)DEVICE_PHYSICAL_MEMORY;
|
|
SectionName.Length = sizeof (DEVICE_PHYSICAL_MEMORY) - sizeof (WCHAR);
|
|
SectionName.MaximumLength = sizeof (DEVICE_PHYSICAL_MEMORY);
|
|
|
|
InitializeObjectAttributes (&ObjectAttributes,
|
|
&SectionName,
|
|
OBJ_PERMANENT,
|
|
NULL,
|
|
NULL);
|
|
|
|
Status = ObCreateObject (KernelMode,
|
|
MmSectionObjectType,
|
|
&ObjectAttributes,
|
|
KernelMode,
|
|
NULL,
|
|
sizeof(SECTION),
|
|
sizeof(SECTION),
|
|
0,
|
|
(PVOID *)&Section);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
ExFreePool (ControlArea);
|
|
ExFreePool (Segment);
|
|
return FALSE;
|
|
}
|
|
|
|
Section->Segment = Segment;
|
|
Section->SizeOfSection.QuadPart = ((LONGLONG)1 << PHYSICAL_ADDRESS_BITS) - 1;
|
|
Section->u.LongFlags = 0;
|
|
Section->InitialPageProtection = PAGE_EXECUTE_READWRITE;
|
|
|
|
Status = ObInsertObject ((PVOID)Section,
|
|
NULL,
|
|
SECTION_MAP_READ,
|
|
0,
|
|
NULL,
|
|
&Handle);
|
|
|
|
if (!NT_SUCCESS (Status)) {
|
|
return FALSE;
|
|
}
|
|
|
|
if (!NT_SUCCESS (NtClose (Handle))) {
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOLEAN
|
|
MmForceSectionClosed (
|
|
IN PSECTION_OBJECT_POINTERS SectionObjectPointer,
|
|
IN BOOLEAN DelayClose
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function examines the Section object pointers. If they are NULL,
|
|
no further action is taken and the value TRUE is returned.
|
|
|
|
If the Section object pointer is not NULL, the section reference count
|
|
and the map view count are checked. If both counts are zero, the
|
|
segment associated with the file is deleted and the file closed.
|
|
If one of the counts is non-zero, no action is taken and the
|
|
value FALSE is returned.
|
|
|
|
Arguments:
|
|
|
|
SectionObjectPointer - Supplies a pointer to a section object.
|
|
|
|
DelayClose - Supplies the value TRUE if the close operation should
|
|
occur as soon as possible in the event this section
|
|
cannot be closed now due to outstanding references.
|
|
|
|
Return Value:
|
|
|
|
TRUE - The segment was deleted and the file closed or no segment was
|
|
located.
|
|
|
|
FALSE - The segment was not deleted and no action was performed OR
|
|
an I/O error occurred trying to write the pages.
|
|
|
|
--*/
|
|
|
|
{
|
|
PCONTROL_AREA ControlArea;
|
|
KIRQL OldIrql;
|
|
LOGICAL state;
|
|
|
|
//
|
|
// 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 (CheckBothSection,
|
|
SectionObjectPointer,
|
|
DelayClose,
|
|
&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;
|
|
ASSERT (ControlArea->NumberOfMappedViews == 0);
|
|
ControlArea->NumberOfMappedViews = 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);
|
|
|
|
//
|
|
// Delete the section by flushing all modified pages back to the section
|
|
// if it is a file and freeing up the pages such that the
|
|
// PfnReferenceCount goes to zero.
|
|
//
|
|
|
|
MiCleanSection (ControlArea, TRUE);
|
|
|
|
//
|
|
// Get the next Hydra control area.
|
|
//
|
|
|
|
state = MiCheckControlAreaStatus (CheckBothSection,
|
|
SectionObjectPointer,
|
|
DelayClose,
|
|
&ControlArea,
|
|
&OldIrql);
|
|
|
|
} while (ControlArea);
|
|
|
|
return (BOOLEAN) state;
|
|
}
|
|
|
|
|
|
VOID
|
|
MiCleanSection (
|
|
IN PCONTROL_AREA ControlArea,
|
|
IN LOGICAL DirtyDataPagesOk
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function examines each prototype PTE in the section and
|
|
takes the appropriate action to "delete" the prototype PTE.
|
|
|
|
If the PTE is dirty and is backed by a file (not a paging file),
|
|
the corresponding page is written to the file.
|
|
|
|
At the completion of this service, the section which was
|
|
operated upon is no longer usable.
|
|
|
|
NOTE - ALL I/O ERRORS ARE IGNORED. IF ANY WRITES FAIL, THE
|
|
DIRTY PAGES ARE MARKED CLEAN AND THE SECTION IS DELETED.
|
|
|
|
Arguments:
|
|
|
|
ControlArea - Supplies a pointer to the control area for the section.
|
|
|
|
DirtyDataPagesOk - Supplies TRUE if dirty data pages are ok. If FALSE
|
|
is specified then no dirty data pages are expected (as
|
|
this is a dereference operation) so any encountered
|
|
must be due to pool corruption so bugcheck.
|
|
|
|
Note that dirty image pages are always discarded.
|
|
This should only happen for images that were either
|
|
read in from floppies or images with shared global
|
|
subsections.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
LOGICAL DroppedPfnLock;
|
|
PMMPTE PointerPte;
|
|
PMMPTE PointerPde;
|
|
PMMPTE LastPte;
|
|
PMMPTE LastWritten;
|
|
PMMPTE FirstWritten;
|
|
MMPTE PteContents;
|
|
PMMPFN Pfn1;
|
|
PMMPFN Pfn2;
|
|
PMMPFN Pfn3;
|
|
PMMPTE WrittenPte;
|
|
MMPTE WrittenContents;
|
|
KIRQL OldIrql;
|
|
PMDL Mdl;
|
|
PSUBSECTION Subsection;
|
|
PPFN_NUMBER Page;
|
|
PPFN_NUMBER LastPage;
|
|
LARGE_INTEGER StartingOffset;
|
|
LARGE_INTEGER TempOffset;
|
|
NTSTATUS Status;
|
|
IO_STATUS_BLOCK IoStatus;
|
|
ULONG WriteNow;
|
|
ULONG ImageSection;
|
|
ULONG DelayCount;
|
|
ULONG First;
|
|
KEVENT IoEvent;
|
|
PFN_NUMBER PageTableFrameIndex;
|
|
PFN_NUMBER MdlHack[(sizeof(MDL)/sizeof(PFN_NUMBER)) + MM_MAXIMUM_WRITE_CLUSTER];
|
|
ULONG ReflushCount;
|
|
ULONG MaxClusterSize;
|
|
|
|
WriteNow = FALSE;
|
|
ImageSection = FALSE;
|
|
DelayCount = 0;
|
|
MaxClusterSize = MmModifiedWriteClusterSize;
|
|
FirstWritten = NULL;
|
|
|
|
ASSERT (ControlArea->FilePointer);
|
|
|
|
if ((ControlArea->u.Flags.GlobalOnlyPerSession == 0) &&
|
|
(ControlArea->u.Flags.Rom == 0)) {
|
|
Subsection = (PSUBSECTION)(ControlArea + 1);
|
|
}
|
|
else {
|
|
Subsection = (PSUBSECTION)((PLARGE_CONTROL_AREA)ControlArea + 1);
|
|
}
|
|
|
|
if (ControlArea->u.Flags.Image) {
|
|
ImageSection = TRUE;
|
|
PointerPte = Subsection->SubsectionBase;
|
|
LastPte = PointerPte + ControlArea->Segment->NonExtendedPtes;
|
|
}
|
|
else {
|
|
|
|
//
|
|
// Initializing these are not needed for correctness as they are
|
|
// overwritten below, but without it the compiler cannot compile
|
|
// this code W4 to check for use of uninitialized variables.
|
|
//
|
|
|
|
PointerPte = NULL;
|
|
LastPte = NULL;
|
|
}
|
|
|
|
Mdl = (PMDL) MdlHack;
|
|
|
|
KeInitializeEvent (&IoEvent, NotificationEvent, FALSE);
|
|
|
|
LastWritten = NULL;
|
|
ASSERT (MmModifiedWriteClusterSize == MM_MAXIMUM_WRITE_CLUSTER);
|
|
LastPage = NULL;
|
|
|
|
//
|
|
// Initializing StartingOffset is not needed for correctness
|
|
// but without it the compiler cannot compile this code
|
|
// W4 to check for use of uninitialized variables.
|
|
//
|
|
|
|
StartingOffset.QuadPart = 0;
|
|
|
|
//
|
|
// The PFN lock is required for deallocating pages from a paging
|
|
// file and for deleting transition PTEs.
|
|
//
|
|
|
|
LOCK_PFN (OldIrql);
|
|
|
|
//
|
|
// Stop the modified page writer from writing pages to this
|
|
// file, and if any paging I/O is in progress, wait for it
|
|
// to complete.
|
|
//
|
|
|
|
ControlArea->u.Flags.NoModifiedWriting = 1;
|
|
|
|
while (ControlArea->ModifiedWriteCount != 0) {
|
|
|
|
//
|
|
// There is modified page writing in progess. Set the
|
|
// flag in the control area indicating the modified page
|
|
// writer should signal when a write to this control area
|
|
// is complete. Release the PFN LOCK and wait in an
|
|
// atomic operation. Once the wait is satisfied, recheck
|
|
// to make sure it was this file's I/O that was written.
|
|
//
|
|
|
|
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);
|
|
}
|
|
|
|
if (ImageSection == FALSE) {
|
|
while (Subsection->SubsectionBase == NULL) {
|
|
Subsection = Subsection->NextSubsection;
|
|
if (Subsection == NULL) {
|
|
goto alldone;
|
|
}
|
|
}
|
|
|
|
PointerPte = Subsection->SubsectionBase;
|
|
LastPte = PointerPte + Subsection->PtesInSubsection;
|
|
}
|
|
|
|
for (;;) {
|
|
|
|
restartchunk:
|
|
|
|
First = TRUE;
|
|
|
|
while (PointerPte < LastPte) {
|
|
|
|
if ((MiIsPteOnPdeBoundary(PointerPte)) || (First)) {
|
|
|
|
First = FALSE;
|
|
|
|
if ((ImageSection) ||
|
|
(MiCheckProtoPtePageState(PointerPte, MM_NOIRQL, &DroppedPfnLock))) {
|
|
MiMakeSystemAddressValidPfn (PointerPte, OldIrql);
|
|
}
|
|
else {
|
|
|
|
//
|
|
// Paged pool page is not resident, hence no transition or
|
|
// valid prototype PTEs can be present in it. Skip it.
|
|
//
|
|
|
|
PointerPte = (PMMPTE)((((ULONG_PTR)PointerPte | PAGE_SIZE - 1)) + 1);
|
|
if (LastWritten != NULL) {
|
|
WriteNow = TRUE;
|
|
}
|
|
goto WriteItOut;
|
|
}
|
|
}
|
|
|
|
PteContents = *PointerPte;
|
|
|
|
//
|
|
// Prototype PTEs for Segments backed by paging file
|
|
// are either in demand zero, page file format, or transition.
|
|
//
|
|
|
|
if (PteContents.u.Hard.Valid == 1) {
|
|
KeBugCheckEx (POOL_CORRUPTION_IN_FILE_AREA,
|
|
0x0,
|
|
(ULONG_PTR)ControlArea,
|
|
(ULONG_PTR)PointerPte,
|
|
(ULONG_PTR)PteContents.u.Long);
|
|
}
|
|
|
|
if (PteContents.u.Soft.Prototype == 1) {
|
|
|
|
//
|
|
// This is a normal prototype PTE in mapped file format.
|
|
//
|
|
|
|
if (LastWritten != NULL) {
|
|
WriteNow = TRUE;
|
|
}
|
|
}
|
|
else if (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.
|
|
//
|
|
|
|
Pfn1 = MI_PFN_ELEMENT (PteContents.u.Trans.PageFrameNumber);
|
|
|
|
if (Pfn1->u3.e2.ReferenceCount != 0) {
|
|
if (DelayCount < 20) {
|
|
|
|
//
|
|
// There must be an I/O in progress on this
|
|
// page. Wait for the I/O operation to complete.
|
|
//
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
//
|
|
// Drain the deferred lists as these pages may be
|
|
// sitting in there right now.
|
|
//
|
|
|
|
MiDeferredUnlockPages (0);
|
|
|
|
KeDelayExecutionThread (KernelMode, FALSE, (PLARGE_INTEGER)&MmShortTime);
|
|
|
|
DelayCount += 1;
|
|
|
|
//
|
|
// Redo the loop, if the delay count is greater than
|
|
// 20, assume that this thread is deadlocked and
|
|
// don't purge this page. The file system can deal
|
|
// with the write operation in progress.
|
|
//
|
|
|
|
PointerPde = MiGetPteAddress (PointerPte);
|
|
LOCK_PFN (OldIrql);
|
|
if (PointerPde->u.Hard.Valid == 0) {
|
|
MiMakeSystemAddressValidPfn (PointerPte, OldIrql);
|
|
}
|
|
continue;
|
|
}
|
|
#if DBG
|
|
//
|
|
// The I/O still has not completed, just ignore
|
|
// the fact that the I/O is in progress and
|
|
// delete the page.
|
|
//
|
|
|
|
KdPrint(("MM:CLEAN - page number %lx has i/o outstanding\n",
|
|
PteContents.u.Trans.PageFrameNumber));
|
|
#endif
|
|
}
|
|
|
|
if (Pfn1->OriginalPte.u.Soft.Prototype == 0) {
|
|
|
|
//
|
|
// Paging file reference (case 1).
|
|
//
|
|
|
|
MI_SET_PFN_DELETED (Pfn1);
|
|
|
|
if (!ImageSection) {
|
|
|
|
//
|
|
// This is not an image section, it must be a
|
|
// page file backed section, therefore decrement
|
|
// the PFN reference count for the control area.
|
|
//
|
|
|
|
ControlArea->NumberOfPfnReferences -= 1;
|
|
ASSERT ((LONG)ControlArea->NumberOfPfnReferences >= 0);
|
|
}
|
|
#if DBG
|
|
else {
|
|
//
|
|
// This should only happen for images with shared
|
|
// global subsections.
|
|
//
|
|
}
|
|
#endif
|
|
|
|
PageTableFrameIndex = Pfn1->u4.PteFrame;
|
|
Pfn2 = MI_PFN_ELEMENT (PageTableFrameIndex);
|
|
MiDecrementShareCountInline (Pfn2, PageTableFrameIndex);
|
|
|
|
//
|
|
// Check the reference count for the page, if the // reference count is zero and the page is not on the
|
|
// freelist, move the page to the free list, if the
|
|
// reference count is not zero, ignore this page. When
|
|
// the reference count goes to zero, it will be placed
|
|
// on the free list.
|
|
//
|
|
|
|
if ((Pfn1->u3.e2.ReferenceCount == 0) &&
|
|
(Pfn1->u3.e1.PageLocation != FreePageList)) {
|
|
|
|
MiUnlinkPageFromList (Pfn1);
|
|
MiReleasePageFileSpace (Pfn1->OriginalPte);
|
|
MiInsertPageInFreeList (MI_GET_PAGE_FRAME_FROM_TRANSITION_PTE (&PteContents));
|
|
|
|
}
|
|
|
|
MI_WRITE_INVALID_PTE (PointerPte, ZeroPte);
|
|
|
|
//
|
|
// If a cluster of pages to write has been completed,
|
|
// set the WriteNow flag.
|
|
//
|
|
|
|
if (LastWritten != NULL) {
|
|
WriteNow = TRUE;
|
|
}
|
|
|
|
}
|
|
else {
|
|
|
|
if ((Pfn1->u3.e1.Modified == 0) || (ImageSection)) {
|
|
|
|
//
|
|
// Non modified or image file page (case 2).
|
|
//
|
|
|
|
MI_SET_PFN_DELETED (Pfn1);
|
|
ControlArea->NumberOfPfnReferences -= 1;
|
|
ASSERT ((LONG)ControlArea->NumberOfPfnReferences >= 0);
|
|
|
|
PageTableFrameIndex = Pfn1->u4.PteFrame;
|
|
Pfn2 = MI_PFN_ELEMENT (PageTableFrameIndex);
|
|
MiDecrementShareCountInline (Pfn2, PageTableFrameIndex);
|
|
|
|
//
|
|
// Check the reference count for the page, if the
|
|
// reference count is zero and the page is not on
|
|
// the freelist, move the page to the free list,
|
|
// if the reference count is not zero, ignore this
|
|
// page. When the reference count goes to zero, it
|
|
// will be placed on the free list.
|
|
//
|
|
|
|
if ((Pfn1->u3.e2.ReferenceCount == 0) &&
|
|
(Pfn1->u3.e1.PageLocation != FreePageList)) {
|
|
|
|
MiUnlinkPageFromList (Pfn1);
|
|
MiReleasePageFileSpace (Pfn1->OriginalPte);
|
|
MiInsertPageInFreeList (MI_GET_PAGE_FRAME_FROM_TRANSITION_PTE (&PteContents));
|
|
}
|
|
|
|
MI_WRITE_INVALID_PTE (PointerPte, ZeroPte);
|
|
|
|
//
|
|
// If a cluster of pages to write has been
|
|
// completed, set the WriteNow flag.
|
|
//
|
|
|
|
if (LastWritten != NULL) {
|
|
WriteNow = TRUE;
|
|
}
|
|
|
|
}
|
|
else {
|
|
|
|
//
|
|
// Modified page backed by the file (case 3).
|
|
// Check to see if this is the first page of a
|
|
// cluster.
|
|
//
|
|
|
|
if (LastWritten == NULL) {
|
|
LastPage = (PPFN_NUMBER)(Mdl + 1);
|
|
ASSERT (MiGetSubsectionAddress(&Pfn1->OriginalPte) ==
|
|
Subsection);
|
|
|
|
//
|
|
// Calculate the offset to read into the file.
|
|
// offset = base + ((thispte - basepte) << PAGE_SHIFT)
|
|
//
|
|
|
|
ASSERT (Subsection->ControlArea->u.Flags.Image == 0);
|
|
StartingOffset.QuadPart = MiStartingOffset(
|
|
Subsection,
|
|
Pfn1->PteAddress);
|
|
|
|
MI_INITIALIZE_ZERO_MDL (Mdl);
|
|
Mdl->MdlFlags |= MDL_PAGES_LOCKED;
|
|
|
|
Mdl->StartVa = NULL;
|
|
Mdl->Size = (CSHORT)(sizeof(MDL) +
|
|
(sizeof(PFN_NUMBER) * MaxClusterSize));
|
|
FirstWritten = PointerPte;
|
|
}
|
|
|
|
LastWritten = PointerPte;
|
|
Mdl->ByteCount += PAGE_SIZE;
|
|
|
|
//
|
|
// If the cluster is now full,
|
|
// set the write now flag.
|
|
//
|
|
|
|
if (Mdl->ByteCount == (PAGE_SIZE * MaxClusterSize)) {
|
|
WriteNow = TRUE;
|
|
}
|
|
|
|
MiUnlinkPageFromList (Pfn1);
|
|
|
|
MI_SET_MODIFIED (Pfn1, 0, 0x27);
|
|
|
|
//
|
|
// Up the reference count for the physical page as
|
|
// there is I/O in progress.
|
|
//
|
|
|
|
MI_ADD_LOCKED_PAGE_CHARGE_FOR_MODIFIED_PAGE(Pfn1, TRUE, 22);
|
|
Pfn1->u3.e2.ReferenceCount += 1;
|
|
|
|
//
|
|
// Clear the modified bit for the page and set the
|
|
// write in progress bit.
|
|
//
|
|
|
|
*LastPage = MI_GET_PAGE_FRAME_FROM_TRANSITION_PTE (&PteContents);
|
|
|
|
LastPage += 1;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
|
|
if (IS_PTE_NOT_DEMAND_ZERO (PteContents)) {
|
|
MiReleasePageFileSpace (PteContents);
|
|
}
|
|
|
|
MI_WRITE_INVALID_PTE (PointerPte, ZeroPte);
|
|
|
|
//
|
|
// If a cluster of pages to write has been completed,
|
|
// set the WriteNow flag.
|
|
//
|
|
|
|
if (LastWritten != NULL) {
|
|
WriteNow = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Write the current cluster if it is complete,
|
|
// full, or the loop is now complete.
|
|
//
|
|
|
|
PointerPte += 1;
|
|
WriteItOut:
|
|
DelayCount = 0;
|
|
|
|
if ((WriteNow) ||
|
|
((PointerPte == LastPte) && (LastWritten != NULL))) {
|
|
|
|
//
|
|
// Issue the write request.
|
|
//
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
if (DirtyDataPagesOk == FALSE) {
|
|
KeBugCheckEx (POOL_CORRUPTION_IN_FILE_AREA,
|
|
0x1,
|
|
(ULONG_PTR)ControlArea,
|
|
(ULONG_PTR)Mdl,
|
|
ControlArea->u.LongFlags);
|
|
}
|
|
|
|
WriteNow = FALSE;
|
|
|
|
//
|
|
// Make sure the write does not go past the
|
|
// end of file. (segment size).
|
|
//
|
|
|
|
ASSERT (Subsection->ControlArea->u.Flags.Image == 0);
|
|
|
|
TempOffset = MiEndingOffset(Subsection);
|
|
|
|
if (((UINT64)StartingOffset.QuadPart + Mdl->ByteCount) >
|
|
(UINT64)TempOffset.QuadPart) {
|
|
|
|
ASSERT ((ULONG)(TempOffset.QuadPart -
|
|
StartingOffset.QuadPart) >
|
|
(Mdl->ByteCount - PAGE_SIZE));
|
|
|
|
Mdl->ByteCount = (ULONG)(TempOffset.QuadPart -
|
|
StartingOffset.QuadPart);
|
|
}
|
|
|
|
ReflushCount = 0;
|
|
|
|
while (TRUE) {
|
|
|
|
KeClearEvent (&IoEvent);
|
|
|
|
Status = IoSynchronousPageWrite (ControlArea->FilePointer,
|
|
Mdl,
|
|
&StartingOffset,
|
|
&IoEvent,
|
|
&IoStatus);
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
|
|
KeWaitForSingleObject (&IoEvent,
|
|
WrPageOut,
|
|
KernelMode,
|
|
FALSE,
|
|
NULL);
|
|
}
|
|
else {
|
|
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 this page did not leave memory when the
|
|
// I/O was in progress.
|
|
//
|
|
|
|
if (MiGetPteAddress (PointerPte)->u.Hard.Valid == 0) {
|
|
MiMakeSystemAddressValidPfn (PointerPte, OldIrql);
|
|
}
|
|
}
|
|
|
|
if (!NT_SUCCESS(IoStatus.Status)) {
|
|
|
|
if ((MmIsRetryIoStatus(IoStatus.Status)) &&
|
|
(MaxClusterSize != 1) &&
|
|
(Mdl->ByteCount > PAGE_SIZE)) {
|
|
|
|
//
|
|
// Retried I/O 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);
|
|
|
|
IoStatus.Information = 0;
|
|
|
|
while (Page < LastPage) {
|
|
|
|
Pfn2 = MI_PFN_ELEMENT (*Page);
|
|
|
|
//
|
|
// Mark the page dirty again so it can be rewritten.
|
|
//
|
|
|
|
MI_SET_MODIFIED (Pfn2, 1, 0xE);
|
|
|
|
MI_REMOVE_LOCKED_PAGE_CHARGE_AND_DECREF(Pfn2, 21);
|
|
Page += 1;
|
|
}
|
|
|
|
PointerPte = FirstWritten;
|
|
LastWritten = NULL;
|
|
|
|
MaxClusterSize = 1;
|
|
goto restartchunk;
|
|
}
|
|
}
|
|
|
|
//
|
|
// I/O complete unlock pages.
|
|
//
|
|
// NOTE that the error status is ignored.
|
|
//
|
|
|
|
while (Page < LastPage) {
|
|
|
|
Pfn2 = MI_PFN_ELEMENT (*Page);
|
|
|
|
//
|
|
// Make sure the page is still transition.
|
|
//
|
|
|
|
WrittenPte = Pfn2->PteAddress;
|
|
|
|
MI_REMOVE_LOCKED_PAGE_CHARGE_AND_DECREF (Pfn2, 23);
|
|
|
|
if (!MI_IS_PFN_DELETED (Pfn2)) {
|
|
|
|
//
|
|
// Make sure the prototype PTE is
|
|
// still in the working set.
|
|
//
|
|
|
|
if (MiGetPteAddress (WrittenPte)->u.Hard.Valid == 0) {
|
|
MiMakeSystemAddressValidPfn (WrittenPte, OldIrql);
|
|
}
|
|
|
|
if (Pfn2->PteAddress != WrittenPte) {
|
|
|
|
//
|
|
// The PFN lock was released to make the
|
|
// page table page valid, and while it
|
|
// was released, the physical page
|
|
// was reused. Go onto the next one.
|
|
//
|
|
|
|
Page += 1;
|
|
continue;
|
|
}
|
|
|
|
WrittenContents = *WrittenPte;
|
|
|
|
if ((WrittenContents.u.Soft.Prototype == 0) &&
|
|
(WrittenContents.u.Soft.Transition == 1)) {
|
|
|
|
MI_SET_PFN_DELETED (Pfn2);
|
|
ControlArea->NumberOfPfnReferences -= 1;
|
|
ASSERT ((LONG)ControlArea->NumberOfPfnReferences >= 0);
|
|
|
|
PageTableFrameIndex = Pfn2->u4.PteFrame;
|
|
Pfn3 = MI_PFN_ELEMENT (PageTableFrameIndex);
|
|
MiDecrementShareCountInline (Pfn3, PageTableFrameIndex);
|
|
|
|
//
|
|
// Check the reference count for the page,
|
|
// if the reference count is zero and the
|
|
// page is not on the freelist, move the page
|
|
// to the free list, if the reference
|
|
// count is not zero, ignore this page.
|
|
// When the reference count goes to zero,
|
|
// it will be placed on the free list.
|
|
//
|
|
|
|
if ((Pfn2->u3.e2.ReferenceCount == 0) &&
|
|
(Pfn2->u3.e1.PageLocation != FreePageList)) {
|
|
|
|
MiUnlinkPageFromList (Pfn2);
|
|
MiReleasePageFileSpace (Pfn2->OriginalPte);
|
|
MiInsertPageInFreeList (*Page);
|
|
}
|
|
}
|
|
WrittenPte->u.Long = 0;
|
|
}
|
|
Page += 1;
|
|
}
|
|
|
|
//
|
|
// Indicate that there is no current cluster being built.
|
|
//
|
|
|
|
LastWritten = NULL;
|
|
}
|
|
|
|
} // end while
|
|
|
|
//
|
|
// Get the next subsection if any.
|
|
//
|
|
|
|
if (Subsection->NextSubsection == NULL) {
|
|
break;
|
|
}
|
|
|
|
Subsection = Subsection->NextSubsection;
|
|
|
|
if (ImageSection == FALSE) {
|
|
while (Subsection->SubsectionBase == NULL) {
|
|
Subsection = Subsection->NextSubsection;
|
|
if (Subsection == NULL) {
|
|
goto alldone;
|
|
}
|
|
}
|
|
}
|
|
|
|
PointerPte = Subsection->SubsectionBase;
|
|
LastPte = PointerPte + Subsection->PtesInSubsection;
|
|
|
|
} // end for
|
|
|
|
alldone:
|
|
|
|
ControlArea->NumberOfMappedViews = 0;
|
|
|
|
ASSERT (ControlArea->NumberOfPfnReferences == 0);
|
|
|
|
if (ControlArea->u.Flags.FilePointerNull == 0) {
|
|
ControlArea->u.Flags.FilePointerNull = 1;
|
|
|
|
if (ControlArea->u.Flags.Image) {
|
|
|
|
MiRemoveImageSectionObject (ControlArea->FilePointer, ControlArea);
|
|
}
|
|
else {
|
|
|
|
ASSERT (((PCONTROL_AREA)(ControlArea->FilePointer->SectionObjectPointer->DataSectionObject)) != NULL);
|
|
ControlArea->FilePointer->SectionObjectPointer->DataSectionObject = NULL;
|
|
|
|
}
|
|
}
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
//
|
|
// Delete the segment structure.
|
|
//
|
|
|
|
MiSegmentDelete (ControlArea->Segment);
|
|
|
|
return;
|
|
}
|
|
|
|
NTSTATUS
|
|
MmGetFileNameForSection (
|
|
IN PSECTION SectionObject,
|
|
OUT PSTRING FileName
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function returns the file name for the corresponding section.
|
|
|
|
Arguments:
|
|
|
|
SectionObject - Supplies the section to get the name of.
|
|
|
|
FileName - Returns the name of the corresponding section.
|
|
|
|
Return Value:
|
|
|
|
TBS
|
|
|
|
Environment:
|
|
|
|
Kernel mode, APC_LEVEL or below, no mutexes held.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
POBJECT_NAME_INFORMATION FileNameInfo;
|
|
ULONG whocares;
|
|
NTSTATUS Status;
|
|
|
|
#define xMAX_NAME 1024
|
|
|
|
if (SectionObject->u.Flags.Image == 0) {
|
|
return STATUS_SECTION_NOT_IMAGE;
|
|
}
|
|
|
|
FileNameInfo = ExAllocatePoolWithTag (PagedPool, xMAX_NAME, ' mM');
|
|
|
|
if ( !FileNameInfo ) {
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
Status = ObQueryNameString(
|
|
SectionObject->Segment->ControlArea->FilePointer,
|
|
FileNameInfo,
|
|
xMAX_NAME,
|
|
&whocares
|
|
);
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
ExFreePool(FileNameInfo);
|
|
return Status;
|
|
}
|
|
|
|
FileName->Length = 0;
|
|
FileName->MaximumLength = (USHORT)((FileNameInfo->Name.Length/sizeof(WCHAR)) + 1);
|
|
FileName->Buffer = ExAllocatePoolWithTag (PagedPool,
|
|
FileName->MaximumLength,
|
|
' mM');
|
|
if (!FileName->Buffer) {
|
|
ExFreePool(FileNameInfo);
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
RtlUnicodeStringToAnsiString ((PANSI_STRING)FileName,
|
|
&FileNameInfo->Name,FALSE);
|
|
|
|
FileName->Buffer[FileName->Length] = '\0';
|
|
ExFreePool(FileNameInfo);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
MmGetFileNameForAddress (
|
|
IN PVOID ProcessVa,
|
|
OUT PUNICODE_STRING FileName
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function returns the file name for the corresponding process address if it corresponds to an image section.
|
|
|
|
Arguments:
|
|
|
|
ProcessVa - Process virtual address
|
|
|
|
FileName - Returns the name of the corresponding section.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Status of operation
|
|
|
|
Environment:
|
|
|
|
Kernel mode, APC_LEVEL or below, no mutexes held.
|
|
|
|
--*/
|
|
{
|
|
PMMVAD Vad;
|
|
PFILE_OBJECT FileObject;
|
|
PCONTROL_AREA ControlArea;
|
|
NTSTATUS Status;
|
|
ULONG RetLen;
|
|
ULONG BufLen;
|
|
PEPROCESS Process;
|
|
POBJECT_NAME_INFORMATION FileNameInfo;
|
|
|
|
PAGED_CODE ();
|
|
|
|
Process = PsGetCurrentProcess();
|
|
|
|
LOCK_ADDRESS_SPACE (Process);
|
|
|
|
Vad = MiLocateAddress (ProcessVa);
|
|
|
|
if (Vad == NULL) {
|
|
|
|
//
|
|
// No virtual address is allocated at the specified base address,
|
|
// return an error.
|
|
//
|
|
|
|
Status = STATUS_INVALID_ADDRESS;
|
|
goto ErrorReturn;
|
|
}
|
|
|
|
//
|
|
// Reject private memory.
|
|
//
|
|
|
|
if (Vad->u.VadFlags.PrivateMemory == 1) {
|
|
Status = STATUS_SECTION_NOT_IMAGE;
|
|
goto ErrorReturn;
|
|
}
|
|
|
|
ControlArea = Vad->ControlArea;
|
|
|
|
if (ControlArea == NULL) {
|
|
Status = STATUS_SECTION_NOT_IMAGE;
|
|
goto ErrorReturn;
|
|
}
|
|
|
|
//
|
|
// Reject non-image sections.
|
|
//
|
|
|
|
if (ControlArea->u.Flags.Image == 0) {
|
|
Status = STATUS_SECTION_NOT_IMAGE;
|
|
goto ErrorReturn;
|
|
}
|
|
|
|
FileObject = ControlArea->FilePointer;
|
|
|
|
ASSERT (FileObject != NULL);
|
|
|
|
ObReferenceObject (FileObject);
|
|
|
|
UNLOCK_ADDRESS_SPACE (Process);
|
|
|
|
//
|
|
// Pick an initial size big enough for most reasonable files.
|
|
//
|
|
|
|
BufLen = sizeof (*FileNameInfo) + 1024;
|
|
|
|
do {
|
|
|
|
FileNameInfo = ExAllocatePoolWithTag (PagedPool, BufLen, ' mM');
|
|
|
|
if (FileNameInfo == NULL) {
|
|
Status = STATUS_NO_MEMORY;
|
|
break;
|
|
}
|
|
|
|
RetLen = 0;
|
|
|
|
Status = ObQueryNameString (FileObject, FileNameInfo, BufLen, &RetLen);
|
|
|
|
if (NT_SUCCESS (Status)) {
|
|
FileName->Length = FileName->MaximumLength = FileNameInfo->Name.Length;
|
|
FileName->Buffer = (PWCHAR) FileNameInfo;
|
|
RtlMoveMemory (FileName->Buffer, FileNameInfo->Name.Buffer, FileName->Length);
|
|
}
|
|
else {
|
|
ExFreePool (FileNameInfo);
|
|
if (RetLen > BufLen) {
|
|
BufLen = RetLen;
|
|
continue;
|
|
}
|
|
}
|
|
break;
|
|
|
|
} while (TRUE);
|
|
|
|
ObDereferenceObject (FileObject);
|
|
return Status;
|
|
|
|
ErrorReturn:
|
|
|
|
UNLOCK_ADDRESS_SPACE (Process);
|
|
return Status;
|
|
}
|
|
|
|
PFILE_OBJECT
|
|
MmGetFileObjectForSection (
|
|
IN PVOID Section
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine returns a pointer to the file object backing a section object.
|
|
|
|
Arguments:
|
|
|
|
Section - Supplies the section to query.
|
|
|
|
Return Value:
|
|
|
|
A pointer to the file object backing the argument section.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, PASSIVE_LEVEL.
|
|
|
|
The caller must ensure that the section is valid for the
|
|
duration of the call.
|
|
|
|
--*/
|
|
|
|
{
|
|
PFILE_OBJECT FileObject;
|
|
|
|
ASSERT (KeGetCurrentIrql() == PASSIVE_LEVEL);
|
|
|
|
ASSERT (Section != NULL);
|
|
|
|
FileObject = ((PSECTION)Section)->Segment->ControlArea->FilePointer;
|
|
|
|
return FileObject;
|
|
}
|
|
|
|
VOID
|
|
MiCheckControlArea (
|
|
IN PCONTROL_AREA ControlArea,
|
|
IN PEPROCESS CurrentProcess,
|
|
IN KIRQL PreviousIrql
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine checks the reference counts for the specified
|
|
control area, and if the counts are all zero, it marks the
|
|
control area for deletion and queues it to the deletion thread.
|
|
|
|
|
|
*********************** NOTE ********************************
|
|
This routine returns with the PFN LOCK RELEASED!!!!!
|
|
|
|
Arguments:
|
|
|
|
ControlArea - Supplies a pointer to the control area to check.
|
|
|
|
CurrentProcess - Supplies a pointer to the current process if and ONLY
|
|
IF the working set lock is held.
|
|
|
|
PreviousIrql - Supplies the previous IRQL.
|
|
|
|
Return Value:
|
|
|
|
NONE.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, PFN lock held, PFN lock released upon return!!!
|
|
|
|
--*/
|
|
|
|
{
|
|
PEVENT_COUNTER PurgeEvent;
|
|
|
|
#define DELETE_ON_CLOSE 0x1
|
|
#define DEREF_SEGMENT 0x2
|
|
|
|
ULONG Action;
|
|
|
|
Action = 0;
|
|
PurgeEvent = NULL;
|
|
|
|
MM_PFN_LOCK_ASSERT();
|
|
if ((ControlArea->NumberOfMappedViews == 0) &&
|
|
(ControlArea->NumberOfSectionReferences == 0)) {
|
|
|
|
ASSERT (ControlArea->NumberOfUserReferences == 0);
|
|
|
|
if (ControlArea->FilePointer != NULL) {
|
|
|
|
if (ControlArea->NumberOfPfnReferences == 0) {
|
|
|
|
//
|
|
// There are no views and no physical pages referenced
|
|
// by the Segment, dereference the Segment object.
|
|
//
|
|
|
|
ControlArea->u.Flags.BeingDeleted = 1;
|
|
Action |= DEREF_SEGMENT;
|
|
|
|
ASSERT (ControlArea->u.Flags.FilePointerNull == 0);
|
|
ControlArea->u.Flags.FilePointerNull = 1;
|
|
|
|
if (ControlArea->u.Flags.Image) {
|
|
|
|
MiRemoveImageSectionObject (ControlArea->FilePointer, ControlArea);
|
|
}
|
|
else {
|
|
|
|
ASSERT (((PCONTROL_AREA)(ControlArea->FilePointer->SectionObjectPointer->DataSectionObject)) != NULL);
|
|
ControlArea->FilePointer->SectionObjectPointer->DataSectionObject = NULL;
|
|
|
|
}
|
|
}
|
|
else {
|
|
|
|
//
|
|
// Insert this segment into the unused segment list (unless
|
|
// it is already on the list).
|
|
//
|
|
|
|
if (ControlArea->DereferenceList.Flink == NULL) {
|
|
MI_INSERT_UNUSED_SEGMENT (ControlArea);
|
|
}
|
|
|
|
//
|
|
// Indicate if this section should be deleted now that
|
|
// the reference counts are zero.
|
|
//
|
|
|
|
if (ControlArea->u.Flags.DeleteOnClose) {
|
|
Action |= DELETE_ON_CLOSE;
|
|
}
|
|
|
|
//
|
|
// The number of mapped views are zero, the number of
|
|
// section references are zero, but there are some
|
|
// pages of the file still resident. If this is
|
|
// an image with Global Memory, "purge" the subsections
|
|
// which contain the global memory and reset them to
|
|
// point back to the file.
|
|
//
|
|
|
|
if (ControlArea->u.Flags.GlobalMemory == 1) {
|
|
|
|
ASSERT (ControlArea->u.Flags.Image == 1);
|
|
|
|
ControlArea->u.Flags.BeingPurged = 1;
|
|
ControlArea->NumberOfMappedViews = 1;
|
|
|
|
MiPurgeImageSection (ControlArea, CurrentProcess, PreviousIrql);
|
|
|
|
ControlArea->u.Flags.BeingPurged = 0;
|
|
ControlArea->NumberOfMappedViews -= 1;
|
|
if ((ControlArea->NumberOfMappedViews == 0) &&
|
|
(ControlArea->NumberOfSectionReferences == 0) &&
|
|
(ControlArea->NumberOfPfnReferences == 0)) {
|
|
|
|
ControlArea->u.Flags.BeingDeleted = 1;
|
|
Action |= DEREF_SEGMENT;
|
|
ControlArea->u.Flags.FilePointerNull = 1;
|
|
|
|
MiRemoveImageSectionObject (ControlArea->FilePointer,
|
|
ControlArea);
|
|
}
|
|
else {
|
|
PurgeEvent = ControlArea->WaitingForDeletion;
|
|
ControlArea->WaitingForDeletion = NULL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If delete on close is set and the segment was
|
|
// not deleted, up the count of mapped views so the
|
|
// control area will not be deleted when the PFN lock
|
|
// is released.
|
|
//
|
|
|
|
if (Action == DELETE_ON_CLOSE) {
|
|
ControlArea->NumberOfMappedViews = 1;
|
|
ControlArea->u.Flags.BeingDeleted = 1;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
|
|
//
|
|
// This Segment is backed by a paging file, dereference the
|
|
// Segment object when the number of views goes from 1 to 0
|
|
// without regard to the number of PFN references.
|
|
//
|
|
|
|
ControlArea->u.Flags.BeingDeleted = 1;
|
|
Action |= DEREF_SEGMENT;
|
|
}
|
|
}
|
|
else if (ControlArea->WaitingForDeletion != NULL) {
|
|
PurgeEvent = ControlArea->WaitingForDeletion;
|
|
ControlArea->WaitingForDeletion = NULL;
|
|
}
|
|
|
|
UNLOCK_PFN (PreviousIrql);
|
|
|
|
if (Action != 0) {
|
|
|
|
//
|
|
// Release the working set mutex, if it is held as the object
|
|
// management routines may page fault, etc..
|
|
//
|
|
|
|
if (CurrentProcess) {
|
|
UNLOCK_WS_UNSAFE (CurrentProcess);
|
|
}
|
|
|
|
ASSERT (ControlArea->Segment->WritableUserReferences == 0);
|
|
|
|
if (Action & DEREF_SEGMENT) {
|
|
|
|
//
|
|
// Delete the segment.
|
|
//
|
|
|
|
MiSegmentDelete (ControlArea->Segment);
|
|
|
|
}
|
|
else {
|
|
|
|
//
|
|
// The segment should be forced closed now.
|
|
//
|
|
|
|
MiCleanSection (ControlArea, TRUE);
|
|
}
|
|
|
|
ASSERT (PurgeEvent == NULL);
|
|
|
|
//
|
|
// Reacquire the working set lock, if a process was specified.
|
|
//
|
|
|
|
if (CurrentProcess) {
|
|
LOCK_WS_UNSAFE (CurrentProcess);
|
|
}
|
|
|
|
}
|
|
else {
|
|
|
|
//
|
|
// If any threads are waiting for the segment, indicate the
|
|
// the purge operation has completed.
|
|
//
|
|
|
|
if (PurgeEvent != NULL) {
|
|
KeSetEvent (&PurgeEvent->Event, 0, FALSE);
|
|
}
|
|
|
|
if (MI_UNUSED_SEGMENTS_SURPLUS()) {
|
|
KeSetEvent (&MmUnusedSegmentCleanup, 0, FALSE);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
MiCheckForControlAreaDeletion (
|
|
IN PCONTROL_AREA ControlArea
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine checks the reference counts for the specified
|
|
control area, and if the counts are all zero, it marks the
|
|
control area for deletion and queues it to the deletion thread.
|
|
|
|
Arguments:
|
|
|
|
ControlArea - Supplies a pointer to the control area to check.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, PFN lock held.
|
|
|
|
--*/
|
|
|
|
{
|
|
KIRQL OldIrql;
|
|
|
|
MM_PFN_LOCK_ASSERT();
|
|
if ((ControlArea->NumberOfPfnReferences == 0) &&
|
|
(ControlArea->NumberOfMappedViews == 0) &&
|
|
(ControlArea->NumberOfSectionReferences == 0 )) {
|
|
|
|
//
|
|
// This segment is no longer mapped in any address space
|
|
// nor are there any prototype PTEs within the segment
|
|
// which are valid or in a transition state. Queue
|
|
// the segment to the segment-dereferencer thread
|
|
// which will dereference the segment object, potentially
|
|
// causing the segment to be deleted.
|
|
//
|
|
|
|
ControlArea->u.Flags.BeingDeleted = 1;
|
|
ASSERT (ControlArea->u.Flags.FilePointerNull == 0);
|
|
ControlArea->u.Flags.FilePointerNull = 1;
|
|
|
|
if (ControlArea->u.Flags.Image) {
|
|
|
|
MiRemoveImageSectionObject (ControlArea->FilePointer,
|
|
ControlArea);
|
|
}
|
|
else {
|
|
ControlArea->FilePointer->SectionObjectPointer->DataSectionObject =
|
|
NULL;
|
|
}
|
|
|
|
ExAcquireSpinLock (&MmDereferenceSegmentHeader.Lock, &OldIrql);
|
|
|
|
if (ControlArea->DereferenceList.Flink != NULL) {
|
|
|
|
//
|
|
// Remove the entry from the unused segment list and put it
|
|
// on the dereference list.
|
|
//
|
|
|
|
RemoveEntryList (&ControlArea->DereferenceList);
|
|
|
|
MI_UNUSED_SEGMENTS_REMOVE_CHARGE (ControlArea);
|
|
}
|
|
|
|
//
|
|
// Image sections still have useful header information in their segment
|
|
// even if no pages are valid or transition so put these at the tail.
|
|
// Data sections have nothing of use if all the data pages are gone so
|
|
// we used to put those at the front. Now both types go to the rear
|
|
// so that commit extensions go to the front for earlier processing.
|
|
//
|
|
|
|
InsertTailList (&MmDereferenceSegmentHeader.ListHead,
|
|
&ControlArea->DereferenceList);
|
|
|
|
ExReleaseSpinLock (&MmDereferenceSegmentHeader.Lock, OldIrql);
|
|
|
|
KeReleaseSemaphore (&MmDereferenceSegmentHeader.Semaphore,
|
|
0L,
|
|
1L,
|
|
FALSE);
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
LOGICAL
|
|
MiCheckControlAreaStatus (
|
|
IN SECTION_CHECK_TYPE SectionCheckType,
|
|
IN PSECTION_OBJECT_POINTERS SectionObjectPointers,
|
|
IN ULONG DelayClose,
|
|
OUT PCONTROL_AREA *ControlAreaOut,
|
|
OUT PKIRQL PreviousIrql
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine checks the status of the control area for the specified
|
|
SectionObjectPointers. If the control area is in use, that is, the
|
|
number of section references and the number of mapped views are not
|
|
both zero, no action is taken and the function returns FALSE.
|
|
|
|
If there is no control area associated with the specified
|
|
SectionObjectPointers or the control area is in the process of being
|
|
created or deleted, no action is taken and the value TRUE is returned.
|
|
|
|
If, there are no section objects and the control area is not being
|
|
created or deleted, the address of the control area is returned
|
|
in the ControlArea argument, the address of a pool block to free
|
|
is returned in the SegmentEventOut argument and the PFN_LOCK is
|
|
still held at the return.
|
|
|
|
Arguments:
|
|
|
|
*SegmentEventOut - Returns a pointer to NonPaged Pool which much be
|
|
freed by the caller when the PFN_LOCK is released.
|
|
This value is NULL if no pool is allocated and the
|
|
PFN_LOCK is not held.
|
|
|
|
SectionCheckType - Supplies the type of section to check on, one of
|
|
CheckImageSection, CheckDataSection, CheckBothSection.
|
|
|
|
SectionObjectPointers - Supplies the section object pointers through
|
|
which the control area can be located.
|
|
|
|
DelayClose - Supplies a boolean which if TRUE and the control area
|
|
is being used, the delay on close field should be set
|
|
in the control area.
|
|
|
|
*ControlAreaOut - Returns the address of the control area.
|
|
|
|
PreviousIrql - Returns, in the case the PFN_LOCK is held, the previous
|
|
IRQL so the lock can be released properly.
|
|
|
|
Return Value:
|
|
|
|
FALSE if the control area is in use, TRUE if the control area is gone or
|
|
in the process or being created or deleted.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, PFN lock NOT held.
|
|
|
|
--*/
|
|
|
|
|
|
{
|
|
PKTHREAD CurrentThread;
|
|
PEVENT_COUNTER IoEvent;
|
|
PEVENT_COUNTER SegmentEvent;
|
|
LOGICAL DeallocateSegmentEvent;
|
|
PCONTROL_AREA ControlArea;
|
|
ULONG SectRef;
|
|
KIRQL OldIrql;
|
|
|
|
//
|
|
// Allocate an event to wait on in case the segment is in the
|
|
// process of being deleted. This event cannot be allocated
|
|
// with the PFN database locked as pool expansion would deadlock.
|
|
//
|
|
|
|
*ControlAreaOut = NULL;
|
|
|
|
do {
|
|
|
|
SegmentEvent = MiGetEventCounter ();
|
|
|
|
if (SegmentEvent != NULL) {
|
|
break;
|
|
}
|
|
|
|
KeDelayExecutionThread (KernelMode,
|
|
FALSE,
|
|
(PLARGE_INTEGER)&MmShortTime);
|
|
|
|
} while (TRUE);
|
|
|
|
//
|
|
// Acquire the PFN lock and examine the section object pointer
|
|
// value within the file object.
|
|
//
|
|
// File control blocks live in non-paged pool.
|
|
//
|
|
|
|
LOCK_PFN (OldIrql);
|
|
|
|
if (SectionCheckType != CheckImageSection) {
|
|
ControlArea = ((PCONTROL_AREA)(SectionObjectPointers->DataSectionObject));
|
|
}
|
|
else {
|
|
ControlArea = ((PCONTROL_AREA)(SectionObjectPointers->ImageSectionObject));
|
|
}
|
|
|
|
if (ControlArea == NULL) {
|
|
|
|
if (SectionCheckType != CheckBothSection) {
|
|
|
|
//
|
|
// This file no longer has an associated segment.
|
|
//
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
MiFreeEventCounter (SegmentEvent);
|
|
return TRUE;
|
|
}
|
|
else {
|
|
ControlArea = ((PCONTROL_AREA)(SectionObjectPointers->ImageSectionObject));
|
|
if (ControlArea == NULL) {
|
|
|
|
//
|
|
// This file no longer has an associated segment.
|
|
//
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
MiFreeEventCounter (SegmentEvent);
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Depending on the type of section, check for the pertinent
|
|
// reference count being non-zero.
|
|
//
|
|
|
|
if (SectionCheckType != CheckUserDataSection) {
|
|
SectRef = ControlArea->NumberOfSectionReferences;
|
|
}
|
|
else {
|
|
SectRef = ControlArea->NumberOfUserReferences;
|
|
}
|
|
|
|
if ((SectRef != 0) ||
|
|
(ControlArea->NumberOfMappedViews != 0) ||
|
|
(ControlArea->u.Flags.BeingCreated)) {
|
|
|
|
|
|
//
|
|
// The segment is currently in use or being created.
|
|
//
|
|
|
|
if (DelayClose) {
|
|
|
|
//
|
|
// The section should be deleted when the reference
|
|
// counts are zero, set the delete on close flag.
|
|
//
|
|
|
|
ControlArea->u.Flags.DeleteOnClose = 1;
|
|
}
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
MiFreeEventCounter (SegmentEvent);
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// The segment has no references, delete it. If the segment
|
|
// is already being deleted, set the event field in the control
|
|
// area and wait on the event.
|
|
//
|
|
|
|
if (ControlArea->u.Flags.BeingDeleted) {
|
|
|
|
//
|
|
// The segment object is in the process of being deleted.
|
|
// Check to see if another thread is waiting for the deletion,
|
|
// otherwise create and event object to wait upon.
|
|
//
|
|
|
|
if (ControlArea->WaitingForDeletion == NULL) {
|
|
|
|
//
|
|
// Create an event and put its address in the control area.
|
|
//
|
|
|
|
DeallocateSegmentEvent = FALSE;
|
|
ControlArea->WaitingForDeletion = SegmentEvent;
|
|
IoEvent = SegmentEvent;
|
|
}
|
|
else {
|
|
DeallocateSegmentEvent = TRUE;
|
|
IoEvent = ControlArea->WaitingForDeletion;
|
|
|
|
//
|
|
// No interlock is needed for the RefCount increment as
|
|
// no thread can be decrementing it since it is still
|
|
// pointed to by the control area.
|
|
//
|
|
|
|
IoEvent->RefCount += 1;
|
|
}
|
|
|
|
//
|
|
// Release the mutex and wait for the event.
|
|
//
|
|
|
|
CurrentThread = KeGetCurrentThread ();
|
|
KeEnterCriticalRegionThread (CurrentThread);
|
|
UNLOCK_PFN_AND_THEN_WAIT(OldIrql);
|
|
|
|
KeWaitForSingleObject(&IoEvent->Event,
|
|
WrPageOut,
|
|
KernelMode,
|
|
FALSE,
|
|
(PLARGE_INTEGER)NULL);
|
|
|
|
//
|
|
// Before this event can be set, the control area
|
|
// WaitingForDeletion field must be cleared (and may be
|
|
// reinitialized to something else), but cannot be reset
|
|
// to our local event. This allows us to dereference the
|
|
// event count lock free.
|
|
//
|
|
|
|
#if 0
|
|
//
|
|
// Note that the control area cannot be referenced at this
|
|
// point because it may have been freed.
|
|
//
|
|
|
|
ASSERT (IoEvent != ControlArea->WaitingForDeletion);
|
|
#endif
|
|
|
|
KeLeaveCriticalRegionThread (CurrentThread);
|
|
|
|
MiFreeEventCounter (IoEvent);
|
|
if (DeallocateSegmentEvent == TRUE) {
|
|
MiFreeEventCounter (SegmentEvent);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Return with the PFN database locked.
|
|
//
|
|
|
|
ASSERT (SegmentEvent->RefCount == 1);
|
|
ASSERT (SegmentEvent->ListEntry.Next == NULL);
|
|
|
|
//
|
|
// NO interlock is needed for the RefCount clearing as the event counter
|
|
// was never pointed to by a control area.
|
|
//
|
|
|
|
#if DBG
|
|
SegmentEvent->RefCount = 0;
|
|
#endif
|
|
|
|
InterlockedPushEntrySList (&MmEventCountSListHead,
|
|
(PSLIST_ENTRY)&SegmentEvent->ListEntry);
|
|
|
|
*ControlAreaOut = ControlArea;
|
|
*PreviousIrql = OldIrql;
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
PEVENT_COUNTER
|
|
MiGetEventCounter (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function maintains a list of "events" to allow waiting
|
|
on segment operations (deletion, creation, purging).
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
Event to be used for waiting (stored into the control area) or NULL if
|
|
no event could be allocated.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, APC_LEVEL or below.
|
|
|
|
--*/
|
|
|
|
{
|
|
PSLIST_ENTRY SingleListEntry;
|
|
PEVENT_COUNTER Support;
|
|
|
|
ASSERT (KeGetCurrentIrql() < DISPATCH_LEVEL);
|
|
|
|
if (ExQueryDepthSList (&MmEventCountSListHead) != 0) {
|
|
|
|
SingleListEntry = InterlockedPopEntrySList (&MmEventCountSListHead);
|
|
|
|
if (SingleListEntry != NULL) {
|
|
Support = CONTAINING_RECORD (SingleListEntry,
|
|
EVENT_COUNTER,
|
|
ListEntry);
|
|
|
|
ASSERT (Support->RefCount == 0);
|
|
KeClearEvent (&Support->Event);
|
|
Support->RefCount = 1;
|
|
#if DBG
|
|
Support->ListEntry.Next = NULL;
|
|
#endif
|
|
return Support;
|
|
}
|
|
}
|
|
|
|
Support = ExAllocatePoolWithTag (NonPagedPool,
|
|
sizeof(EVENT_COUNTER),
|
|
'xEmM');
|
|
if (Support == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
KeInitializeEvent (&Support->Event, NotificationEvent, FALSE);
|
|
|
|
Support->RefCount = 1;
|
|
#if DBG
|
|
Support->ListEntry.Next = NULL;
|
|
#endif
|
|
|
|
return Support;
|
|
}
|
|
|
|
|
|
VOID
|
|
MiFreeEventCounter (
|
|
IN PEVENT_COUNTER Support
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine frees an event counter back to the free list.
|
|
|
|
Arguments:
|
|
|
|
Support - Supplies a pointer to the event counter.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, APC_LEVEL or below.
|
|
|
|
--*/
|
|
|
|
{
|
|
PSLIST_ENTRY SingleListEntry;
|
|
|
|
ASSERT (KeGetCurrentIrql() < DISPATCH_LEVEL);
|
|
|
|
ASSERT (Support->RefCount != 0);
|
|
ASSERT (Support->ListEntry.Next == NULL);
|
|
|
|
//
|
|
// An interlock is needed for the RefCount decrement as the event counter
|
|
// is no longer pointed to by a control area and thus, any number of
|
|
// threads can be running this code without any other serialization.
|
|
//
|
|
|
|
if (InterlockedDecrement ((PLONG)&Support->RefCount) == 0) {
|
|
|
|
if (ExQueryDepthSList (&MmEventCountSListHead) < 4) {
|
|
InterlockedPushEntrySList (&MmEventCountSListHead,
|
|
&Support->ListEntry);
|
|
return;
|
|
}
|
|
ExFreePool (Support);
|
|
}
|
|
|
|
//
|
|
// If excess event blocks are stashed then free them now.
|
|
//
|
|
|
|
while (ExQueryDepthSList (&MmEventCountSListHead) > 4) {
|
|
|
|
SingleListEntry = InterlockedPopEntrySList (&MmEventCountSListHead);
|
|
|
|
if (SingleListEntry != NULL) {
|
|
Support = CONTAINING_RECORD (SingleListEntry,
|
|
EVENT_COUNTER,
|
|
ListEntry);
|
|
|
|
ExFreePool (Support);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
MmCanFileBeTruncated (
|
|
IN PSECTION_OBJECT_POINTERS SectionPointer,
|
|
IN PLARGE_INTEGER NewFileSize
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine does the following:
|
|
|
|
1. Checks to see if a image section is in use for the file,
|
|
if so it returns FALSE.
|
|
|
|
2. Checks to see if a user section exists for the file, if
|
|
it does, it checks to make sure the new file size is greater
|
|
than the size of the file, if not it returns FALSE.
|
|
|
|
3. If no image section exists, and no user created data section
|
|
exists or the file's size is greater, then TRUE is returned.
|
|
|
|
Arguments:
|
|
|
|
SectionPointer - Supplies a pointer to the section object pointers
|
|
from the file object.
|
|
|
|
NewFileSize - Supplies a pointer to the size the file is getting set to.
|
|
|
|
Return Value:
|
|
|
|
TRUE if the file can be truncated, FALSE if it cannot be.
|
|
|
|
Environment:
|
|
|
|
Kernel mode.
|
|
|
|
--*/
|
|
|
|
{
|
|
LARGE_INTEGER LocalOffset;
|
|
KIRQL OldIrql;
|
|
|
|
//
|
|
// Capture caller's file size, since we may modify it.
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT(NewFileSize)) {
|
|
|
|
LocalOffset = *NewFileSize;
|
|
NewFileSize = &LocalOffset;
|
|
}
|
|
|
|
if (MiCanFileBeTruncatedInternal( SectionPointer, NewFileSize, FALSE, &OldIrql )) {
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
ULONG
|
|
MiCanFileBeTruncatedInternal (
|
|
IN PSECTION_OBJECT_POINTERS SectionPointer,
|
|
IN PLARGE_INTEGER NewFileSize OPTIONAL,
|
|
IN LOGICAL BlockNewViews,
|
|
OUT PKIRQL PreviousIrql
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine does the following:
|
|
|
|
1. Checks to see if a image section is in use for the file,
|
|
if so it returns FALSE.
|
|
|
|
2. Checks to see if a user section exists for the file, if
|
|
it does, it checks to make sure the new file size is greater
|
|
than the size of the file, if not it returns FALSE.
|
|
|
|
3. If no image section exists, and no user created data section
|
|
exists or the files size is greater, then TRUE is returned.
|
|
|
|
Arguments:
|
|
|
|
SectionPointer - Supplies a pointer to the section object pointers
|
|
from the file object.
|
|
|
|
NewFileSize - Supplies a pointer to the size the file is getting set to.
|
|
|
|
BlockNewViews - Supplies TRUE if the caller will block new views while
|
|
the operation (usually a purge) proceeds. This allows
|
|
this routine to return TRUE even if the user has section
|
|
references, provided the user currently has no mapped views.
|
|
|
|
PreviousIrql - If returning TRUE, returns Irql to use when unlocking
|
|
Pfn database.
|
|
|
|
Return Value:
|
|
|
|
TRUE if the file can be truncated (PFN locked).
|
|
FALSE if it cannot be truncated (PFN not locked).
|
|
|
|
Environment:
|
|
|
|
Kernel mode.
|
|
|
|
--*/
|
|
|
|
{
|
|
KIRQL OldIrql;
|
|
LARGE_INTEGER SegmentSize;
|
|
PCONTROL_AREA ControlArea;
|
|
PSUBSECTION Subsection;
|
|
PMAPPED_FILE_SEGMENT Segment;
|
|
|
|
if (!MmFlushImageSection (SectionPointer, MmFlushForWrite)) {
|
|
return FALSE;
|
|
}
|
|
|
|
LOCK_PFN (OldIrql);
|
|
|
|
ControlArea = (PCONTROL_AREA)(SectionPointer->DataSectionObject);
|
|
|
|
if (ControlArea != NULL) {
|
|
|
|
if ((ControlArea->u.Flags.BeingCreated) ||
|
|
(ControlArea->u.Flags.BeingDeleted) ||
|
|
(ControlArea->u.Flags.Rom)) {
|
|
goto UnlockAndReturn;
|
|
}
|
|
|
|
//
|
|
// If there are user references and the size is less than the
|
|
// size of the user view, don't allow the truncation.
|
|
//
|
|
|
|
if ((ControlArea->NumberOfUserReferences != 0) &&
|
|
((BlockNewViews == FALSE) || (ControlArea->NumberOfMappedViews != 0))) {
|
|
|
|
//
|
|
// You cannot truncate the entire section if there is a user
|
|
// reference.
|
|
//
|
|
|
|
if (!ARGUMENT_PRESENT(NewFileSize)) {
|
|
goto UnlockAndReturn;
|
|
}
|
|
|
|
//
|
|
// Locate last subsection and get total size.
|
|
//
|
|
|
|
ASSERT (ControlArea->u.Flags.Image == 0);
|
|
ASSERT (ControlArea->u.Flags.GlobalOnlyPerSession == 0);
|
|
|
|
Subsection = (PSUBSECTION)(ControlArea + 1);
|
|
|
|
if (ControlArea->FilePointer != NULL) {
|
|
Segment = (PMAPPED_FILE_SEGMENT) ControlArea->Segment;
|
|
|
|
if (MiIsAddressValid (Segment, TRUE)) {
|
|
if (Segment->LastSubsectionHint != NULL) {
|
|
Subsection = (PSUBSECTION) Segment->LastSubsectionHint;
|
|
}
|
|
}
|
|
}
|
|
|
|
while (Subsection->NextSubsection != NULL) {
|
|
Subsection = Subsection->NextSubsection;
|
|
}
|
|
|
|
ASSERT (Subsection->ControlArea == ControlArea);
|
|
|
|
SegmentSize = MiEndingOffset(Subsection);
|
|
|
|
if ((UINT64)NewFileSize->QuadPart < (UINT64)SegmentSize.QuadPart) {
|
|
goto UnlockAndReturn;
|
|
}
|
|
|
|
//
|
|
// If there are mapped views, we will skip the last page
|
|
// of the section if the size passed in falls in that page.
|
|
// The caller (like Cc) may want to clear this fractional page.
|
|
//
|
|
|
|
SegmentSize.QuadPart += PAGE_SIZE - 1;
|
|
SegmentSize.LowPart &= ~(PAGE_SIZE - 1);
|
|
if ((UINT64)NewFileSize->QuadPart < (UINT64)SegmentSize.QuadPart) {
|
|
*NewFileSize = SegmentSize;
|
|
}
|
|
}
|
|
}
|
|
|
|
*PreviousIrql = OldIrql;
|
|
return TRUE;
|
|
|
|
UnlockAndReturn:
|
|
UNLOCK_PFN (OldIrql);
|
|
return FALSE;
|
|
}
|
|
|
|
PFILE_OBJECT *
|
|
MmPerfUnusedSegmentsEnumerate (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine walks the MmUnusedSegmentList and returns
|
|
a pointer to a pool allocation containing the
|
|
referenced file object pointers.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
Returns a pointer to a NULL terminated pool allocation containing the
|
|
file object pointers from the unused segment list, NULL if the memory
|
|
could not be allocated.
|
|
|
|
It is also the responsibility of the caller to dereference each
|
|
file object in the list and then free the returned pool.
|
|
|
|
Environment:
|
|
|
|
PASSIVE_LEVEL, arbitrary thread context.
|
|
|
|
--*/
|
|
{
|
|
KIRQL OldIrql;
|
|
ULONG SegmentCount;
|
|
PFILE_OBJECT *FileObjects;
|
|
PFILE_OBJECT *File;
|
|
PLIST_ENTRY NextEntry;
|
|
PCONTROL_AREA ControlArea;
|
|
|
|
ASSERT (KeGetCurrentIrql () == PASSIVE_LEVEL);
|
|
|
|
ReAllocate:
|
|
|
|
SegmentCount = MmUnusedSegmentCount + 10;
|
|
|
|
FileObjects = (PFILE_OBJECT *) ExAllocatePoolWithTag (
|
|
NonPagedPool,
|
|
SegmentCount * sizeof(PFILE_OBJECT),
|
|
'01pM');
|
|
|
|
if (FileObjects == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
File = FileObjects;
|
|
|
|
LOCK_PFN (OldIrql);
|
|
|
|
//
|
|
// Leave space for NULL terminator.
|
|
//
|
|
|
|
if (SegmentCount - 1 < MmUnusedSegmentCount) {
|
|
UNLOCK_PFN (OldIrql);
|
|
ExFreePool (FileObjects);
|
|
goto ReAllocate;
|
|
}
|
|
|
|
NextEntry = MmUnusedSegmentList.Flink;
|
|
|
|
while (NextEntry != &MmUnusedSegmentList) {
|
|
|
|
ControlArea = CONTAINING_RECORD (NextEntry,
|
|
CONTROL_AREA,
|
|
DereferenceList);
|
|
|
|
*File = ControlArea->FilePointer;
|
|
ObReferenceObject(*File);
|
|
File += 1;
|
|
|
|
NextEntry = NextEntry->Flink;
|
|
}
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
*File = NULL;
|
|
|
|
return FileObjects;
|
|
}
|
|
|
|
#if DBG
|
|
PMSUBSECTION MiActiveSubsection;
|
|
LOGICAL MiRemoveSubsectionsFirst;
|
|
|
|
#define MI_DEREF_ACTION_SIZE 64
|
|
|
|
ULONG MiDerefActions[MI_DEREF_ACTION_SIZE];
|
|
|
|
#define MI_INSTRUMENT_DEREF_ACTION(i) \
|
|
ASSERT (i < MI_DEREF_ACTION_SIZE); \
|
|
MiDerefActions[i] += 1;
|
|
|
|
#else
|
|
#define MI_INSTRUMENT_DEREF_ACTION(i)
|
|
#endif
|
|
|
|
|
|
VOID
|
|
MiRemoveUnusedSegments (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine removes unused segments (no section references,
|
|
no mapped views only PFN references that are in transition state).
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Kernel mode.
|
|
|
|
--*/
|
|
|
|
{
|
|
LOGICAL DroppedPfnLock;
|
|
KIRQL OldIrql;
|
|
PLIST_ENTRY NextEntry;
|
|
PCONTROL_AREA ControlArea;
|
|
NTSTATUS Status;
|
|
ULONG ConsecutiveFileLockFailures;
|
|
ULONG ConsecutivePagingIOs;
|
|
PSUBSECTION Subsection;
|
|
PSUBSECTION LastSubsection;
|
|
PSUBSECTION LastSubsectionWithProtos;
|
|
PMSUBSECTION MappedSubsection;
|
|
ULONG NumberOfPtes;
|
|
MMPTE PteContents;
|
|
PMMPTE PointerPte;
|
|
PMMPTE LastPte;
|
|
PMMPTE ProtoPtes;
|
|
PMMPTE ProtoPtes2;
|
|
PMMPTE LastProtoPte;
|
|
PMMPFN Pfn1;
|
|
PMMPFN Pfn2;
|
|
IO_STATUS_BLOCK IoStatus;
|
|
LOGICAL DirtyPagesOk;
|
|
PFN_NUMBER PageFrameIndex;
|
|
PFN_NUMBER PageTableFrameIndex;
|
|
ULONG ForceFree;
|
|
ULONG LoopCount;
|
|
PMMPAGE_FILE_EXPANSION PageExpand;
|
|
|
|
LoopCount = 0;
|
|
ConsecutivePagingIOs = 0;
|
|
ConsecutiveFileLockFailures = 0;
|
|
|
|
//
|
|
// If overall system pool usage is acceptable, then don't discard
|
|
// any cache.
|
|
//
|
|
|
|
while ((MI_UNUSED_SEGMENTS_SURPLUS()) || (MmUnusedSegmentForceFree != 0)) {
|
|
|
|
LoopCount += 1;
|
|
MI_INSTRUMENT_DEREF_ACTION(1);
|
|
|
|
if ((LoopCount & (64 - 1)) == 0) {
|
|
|
|
MI_INSTRUMENT_DEREF_ACTION(2);
|
|
|
|
//
|
|
// Periodically delay so the mapped and modified writers get
|
|
// a shot at writing out the pages this (higher priority) thread
|
|
// is releasing.
|
|
//
|
|
|
|
ExAcquireSpinLock (&MmDereferenceSegmentHeader.Lock, &OldIrql);
|
|
|
|
while (!IsListEmpty (&MmDereferenceSegmentHeader.ListHead)) {
|
|
|
|
MiSubsectionActions |= 0x8000000;
|
|
|
|
//
|
|
// The list is not empty, see if the first request is for
|
|
// a commit extension and if so, process it now.
|
|
//
|
|
|
|
NextEntry = MmDereferenceSegmentHeader.ListHead.Flink;
|
|
|
|
ControlArea = CONTAINING_RECORD (NextEntry,
|
|
CONTROL_AREA,
|
|
DereferenceList);
|
|
|
|
if (ControlArea->Segment != NULL) {
|
|
MI_INSTRUMENT_DEREF_ACTION(3);
|
|
break;
|
|
}
|
|
|
|
PageExpand = (PMMPAGE_FILE_EXPANSION) ControlArea;
|
|
|
|
if (PageExpand->RequestedExpansionSize == MI_CONTRACT_PAGEFILES) {
|
|
MI_INSTRUMENT_DEREF_ACTION(4);
|
|
break;
|
|
}
|
|
|
|
MI_INSTRUMENT_DEREF_ACTION(5);
|
|
|
|
//
|
|
// This is a request to expand the paging files.
|
|
//
|
|
|
|
MiSubsectionActions |= 0x10000000;
|
|
RemoveEntryList (NextEntry);
|
|
ExReleaseSpinLock (&MmDereferenceSegmentHeader.Lock, OldIrql);
|
|
|
|
MiExtendPagingFiles (PageExpand);
|
|
KeSetEvent (&PageExpand->Event, 0, FALSE);
|
|
|
|
ExAcquireSpinLock (&MmDereferenceSegmentHeader.Lock, &OldIrql);
|
|
}
|
|
|
|
ExReleaseSpinLock (&MmDereferenceSegmentHeader.Lock, OldIrql);
|
|
|
|
//
|
|
// If we are looping without freeing enough pool then
|
|
// signal the cache manager to start unmapping
|
|
// system cache views in an attempt to get back the paged
|
|
// pool containing its prototype PTEs.
|
|
//
|
|
|
|
if (LoopCount >= 128) {
|
|
MI_INSTRUMENT_DEREF_ACTION(55);
|
|
if (CcUnmapInactiveViews (50) == TRUE) {
|
|
MI_INSTRUMENT_DEREF_ACTION(56);
|
|
}
|
|
}
|
|
|
|
KeDelayExecutionThread (KernelMode, FALSE, (PLARGE_INTEGER)&MmShortTime);
|
|
}
|
|
|
|
//
|
|
// Eliminate some of the unused segments which are only
|
|
// kept in memory because they contain transition pages.
|
|
//
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
LOCK_PFN (OldIrql);
|
|
|
|
if ((IsListEmpty(&MmUnusedSegmentList)) &&
|
|
(IsListEmpty(&MmUnusedSubsectionList))) {
|
|
|
|
//
|
|
// There is nothing in the list, rewait.
|
|
//
|
|
|
|
MI_INSTRUMENT_DEREF_ACTION(6);
|
|
ForceFree = MmUnusedSegmentForceFree;
|
|
MmUnusedSegmentForceFree = 0;
|
|
ASSERT (MmUnusedSegmentCount == 0);
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
//
|
|
// We weren't able to get as many segments or subsections as we
|
|
// wanted. So signal the cache manager to start unmapping
|
|
// system cache views in an attempt to get back the paged
|
|
// pool containing its prototype PTEs. If Cc was able to free
|
|
// any at all, then restart our loop.
|
|
//
|
|
|
|
if (CcUnmapInactiveViews (50) == TRUE) {
|
|
LOCK_PFN (OldIrql);
|
|
if (ForceFree > MmUnusedSegmentForceFree) {
|
|
MmUnusedSegmentForceFree = ForceFree;
|
|
}
|
|
MI_INSTRUMENT_DEREF_ACTION(7);
|
|
UNLOCK_PFN (OldIrql);
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
MI_INSTRUMENT_DEREF_ACTION(8);
|
|
|
|
if (MmUnusedSegmentForceFree != 0) {
|
|
MmUnusedSegmentForceFree -= 1;
|
|
MI_INSTRUMENT_DEREF_ACTION(9);
|
|
}
|
|
|
|
#if DBG
|
|
if (MiRemoveSubsectionsFirst == TRUE) {
|
|
if (!IsListEmpty(&MmUnusedSubsectionList)) {
|
|
goto ProcessSubsectionsFirst;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (IsListEmpty(&MmUnusedSegmentList)) {
|
|
|
|
#if DBG
|
|
ProcessSubsectionsFirst:
|
|
#endif
|
|
|
|
MI_INSTRUMENT_DEREF_ACTION(10);
|
|
|
|
//
|
|
// The unused segment list was empty, go for the unused subsection
|
|
// list instead.
|
|
//
|
|
|
|
ASSERT (!IsListEmpty(&MmUnusedSubsectionList));
|
|
|
|
MiSubsectionsProcessed += 1;
|
|
NextEntry = RemoveHeadList(&MmUnusedSubsectionList);
|
|
|
|
MappedSubsection = CONTAINING_RECORD (NextEntry,
|
|
MSUBSECTION,
|
|
DereferenceList);
|
|
|
|
ControlArea = MappedSubsection->ControlArea;
|
|
|
|
ASSERT (ControlArea->u.Flags.Image == 0);
|
|
ASSERT (ControlArea->u.Flags.PhysicalMemory == 0);
|
|
ASSERT (ControlArea->FilePointer != NULL);
|
|
ASSERT (MappedSubsection->NumberOfMappedViews == 0);
|
|
ASSERT (MappedSubsection->u.SubsectionFlags.SubsectionStatic == 0);
|
|
|
|
MI_UNUSED_SUBSECTIONS_COUNT_REMOVE (MappedSubsection);
|
|
|
|
//
|
|
// Set the flink to NULL indicating this subsection
|
|
// is not on any lists.
|
|
//
|
|
|
|
MappedSubsection->DereferenceList.Flink = NULL;
|
|
|
|
if (ControlArea->u.Flags.BeingDeleted == 1) {
|
|
MI_INSTRUMENT_DEREF_ACTION(11);
|
|
MiSubsectionActions |= 0x1;
|
|
UNLOCK_PFN (OldIrql);
|
|
ConsecutivePagingIOs = 0;
|
|
continue;
|
|
}
|
|
|
|
if (ControlArea->u.Flags.NoModifiedWriting == 1) {
|
|
MiSubsectionActions |= 0x2;
|
|
MI_INSTRUMENT_DEREF_ACTION(12);
|
|
InsertTailList (&MmUnusedSubsectionList,
|
|
&MappedSubsection->DereferenceList);
|
|
MI_UNUSED_SUBSECTIONS_COUNT_INSERT (MappedSubsection);
|
|
UNLOCK_PFN (OldIrql);
|
|
ConsecutivePagingIOs = 0;
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Up the number of mapped views to prevent other threads
|
|
// from freeing this. Clear the accessed bit so we'll know
|
|
// if another thread opens the subsection while we're flushing
|
|
// and closes it before we finish the flush - the other thread
|
|
// may have modified some pages which can then cause our
|
|
// MiCleanSection call (which expects no modified pages in this
|
|
// case) to deadlock with the filesystem.
|
|
//
|
|
|
|
MappedSubsection->NumberOfMappedViews = 1;
|
|
MappedSubsection->u2.SubsectionFlags2.SubsectionAccessed = 0;
|
|
|
|
#if DBG
|
|
MiActiveSubsection = MappedSubsection;
|
|
#endif
|
|
|
|
//
|
|
// Increment the number of mapped views on the control area to
|
|
// prevent threads that are purging the section from deleting it
|
|
// from underneath us while we process one of its subsections.
|
|
//
|
|
|
|
ControlArea->NumberOfMappedViews += 1;
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
ASSERT (MappedSubsection->SubsectionBase != NULL);
|
|
|
|
PointerPte = &MappedSubsection->SubsectionBase[0];
|
|
LastPte = &MappedSubsection->SubsectionBase
|
|
[MappedSubsection->PtesInSubsection - 1];
|
|
|
|
//
|
|
// Preacquire the file to prevent deadlocks with other flushers
|
|
// Also mark ourself as a top level IRP so the filesystem knows
|
|
// we are holding no other resources and that it can unroll if
|
|
// it needs to in order to avoid deadlock. Don't hold this
|
|
// protection any longer than we need to.
|
|
//
|
|
|
|
Status = FsRtlAcquireFileForCcFlushEx (ControlArea->FilePointer);
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
PIRP tempIrp = (PIRP)FSRTL_FSP_TOP_LEVEL_IRP;
|
|
|
|
MI_INSTRUMENT_DEREF_ACTION (13);
|
|
IoSetTopLevelIrp (tempIrp);
|
|
|
|
Status = MiFlushSectionInternal (PointerPte,
|
|
LastPte,
|
|
(PSUBSECTION) MappedSubsection,
|
|
(PSUBSECTION) MappedSubsection,
|
|
FALSE,
|
|
FALSE,
|
|
&IoStatus);
|
|
|
|
IoSetTopLevelIrp (NULL);
|
|
|
|
//
|
|
// Now release the file.
|
|
//
|
|
|
|
FsRtlReleaseFileForCcFlush (ControlArea->FilePointer);
|
|
}
|
|
else {
|
|
MI_INSTRUMENT_DEREF_ACTION (14);
|
|
}
|
|
|
|
LOCK_PFN (OldIrql);
|
|
|
|
#if DBG
|
|
MiActiveSubsection = NULL;
|
|
#endif
|
|
|
|
//
|
|
// Before checking for any failure codes, see if any other
|
|
// threads accessed the subsection while the flush was ongoing.
|
|
//
|
|
// Note that beyond the case of another thread currently using
|
|
// the subsection, the more subtle one is where another
|
|
// thread accessed the subsection and modified some pages.
|
|
// The flush needs to redone (so the clean is guaranteed to work)
|
|
// before another clean can be issued.
|
|
//
|
|
// If any of these cases have occurred, grant this subsection
|
|
// a reprieve.
|
|
//
|
|
|
|
ASSERT (MappedSubsection->u.SubsectionFlags.SubsectionStatic == 0);
|
|
if ((MappedSubsection->NumberOfMappedViews != 1) ||
|
|
(MappedSubsection->u2.SubsectionFlags2.SubsectionAccessed == 1) ||
|
|
(ControlArea->u.Flags.BeingDeleted == 1)) {
|
|
|
|
MI_INSTRUMENT_DEREF_ACTION(15);
|
|
Requeue:
|
|
MI_INSTRUMENT_DEREF_ACTION(16);
|
|
ASSERT ((LONG_PTR)MappedSubsection->NumberOfMappedViews >= 1);
|
|
MappedSubsection->NumberOfMappedViews -= 1;
|
|
|
|
MiSubsectionActions |= 0x4;
|
|
|
|
//
|
|
// If the other thread(s) are done with this subsection,
|
|
// it MUST be requeued here - otherwise if there are any
|
|
// pages in the subsection, when they are reclaimed,
|
|
// MiCheckForControlAreaDeletion checks for and expects
|
|
// the control area to be queued on the unused segment list.
|
|
//
|
|
// Note this must be done very carefully because if the other
|
|
// threads are not done with the subsection, it had better
|
|
// not get put on the unused subsection list.
|
|
//
|
|
|
|
if ((MappedSubsection->NumberOfMappedViews == 0) &&
|
|
(ControlArea->u.Flags.BeingDeleted == 0)) {
|
|
|
|
MI_INSTRUMENT_DEREF_ACTION(17);
|
|
MiSubsectionActions |= 0x8;
|
|
ASSERT (MappedSubsection->u2.SubsectionFlags2.SubsectionAccessed == 1);
|
|
ASSERT (MappedSubsection->DereferenceList.Flink == NULL);
|
|
|
|
InsertTailList (&MmUnusedSubsectionList,
|
|
&MappedSubsection->DereferenceList);
|
|
|
|
MI_UNUSED_SUBSECTIONS_COUNT_INSERT (MappedSubsection);
|
|
}
|
|
|
|
ControlArea->NumberOfMappedViews -= 1;
|
|
UNLOCK_PFN (OldIrql);
|
|
continue;
|
|
}
|
|
|
|
MI_INSTRUMENT_DEREF_ACTION(18);
|
|
ASSERT (MappedSubsection->DereferenceList.Flink == NULL);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
MiSubsectionActions |= 0x10;
|
|
|
|
//
|
|
// If the filesystem told us it had to unroll to avoid
|
|
// deadlock OR we hit a mapped writer collision OR
|
|
// the error occurred on a local file:
|
|
//
|
|
// Then requeue this at the end so we can try again later.
|
|
//
|
|
// Any other errors for networked files are assumed to be
|
|
// permanent (ie: the link may have gone down for an indefinite
|
|
// period), so these sections are cleaned regardless.
|
|
//
|
|
|
|
ASSERT ((LONG_PTR)MappedSubsection->NumberOfMappedViews >= 1);
|
|
MappedSubsection->NumberOfMappedViews -= 1;
|
|
|
|
InsertTailList (&MmUnusedSubsectionList,
|
|
&MappedSubsection->DereferenceList);
|
|
|
|
MI_UNUSED_SUBSECTIONS_COUNT_INSERT (MappedSubsection);
|
|
|
|
ControlArea->NumberOfMappedViews -= 1;
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
if (Status == STATUS_FILE_LOCK_CONFLICT) {
|
|
MI_INSTRUMENT_DEREF_ACTION(19);
|
|
ConsecutiveFileLockFailures += 1;
|
|
}
|
|
else {
|
|
MI_INSTRUMENT_DEREF_ACTION(20);
|
|
ConsecutiveFileLockFailures = 0;
|
|
}
|
|
|
|
//
|
|
// 10 consecutive file locking failures means we need to
|
|
// yield the processor to allow the filesystem to unjam.
|
|
// Nothing magic about 10, just a number so it
|
|
// gives the worker threads a chance to run.
|
|
//
|
|
|
|
if (ConsecutiveFileLockFailures >= 10) {
|
|
MI_INSTRUMENT_DEREF_ACTION(21);
|
|
KeDelayExecutionThread (KernelMode, FALSE, (PLARGE_INTEGER)&MmShortTime);
|
|
ConsecutiveFileLockFailures = 0;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// The final check that must be made is whether any faults are
|
|
// currently in progress which are backed by this subsection.
|
|
// Note this is the perverse case where one thread in a process
|
|
// has unmapped the relevant VAD even while other threads in the
|
|
// same process are faulting on the addresses in that VAD (if the
|
|
// VAD had not been unmapped then the subsection view count would
|
|
// have been nonzero and caught above). Clearly this is a bad
|
|
// process, but nonetheless it must be detected and handled here
|
|
// because upon conclusion of the inpage, the thread will compare
|
|
// (unsynchronized) against the prototype PTEs which may in
|
|
// various stages of deletion below and would cause corruption.
|
|
//
|
|
|
|
MI_INSTRUMENT_DEREF_ACTION(22);
|
|
MiSubsectionActions |= 0x20;
|
|
|
|
ASSERT (MappedSubsection->NumberOfMappedViews == 1);
|
|
ProtoPtes = MappedSubsection->SubsectionBase;
|
|
NumberOfPtes = MappedSubsection->PtesInSubsection;
|
|
|
|
//
|
|
// Note checking the prototype PTEs must be done carefully as
|
|
// they are pagable and the PFN lock is (and must be) held.
|
|
//
|
|
|
|
ProtoPtes2 = ProtoPtes;
|
|
LastProtoPte = ProtoPtes + NumberOfPtes;
|
|
|
|
while (ProtoPtes2 < LastProtoPte) {
|
|
|
|
if ((ProtoPtes2 == ProtoPtes) ||
|
|
(MiIsPteOnPdeBoundary (ProtoPtes2))) {
|
|
|
|
if (MiCheckProtoPtePageState (ProtoPtes2, OldIrql, &DroppedPfnLock) == FALSE) {
|
|
|
|
//
|
|
// Skip this chunk as it is paged out and thus, cannot
|
|
// have any valid or transition PTEs within it.
|
|
//
|
|
|
|
ProtoPtes2 = (PMMPTE)(((ULONG_PTR)ProtoPtes2 | (PAGE_SIZE - 1)) + 1);
|
|
MI_INSTRUMENT_DEREF_ACTION(23);
|
|
continue;
|
|
}
|
|
else {
|
|
|
|
//
|
|
// The prototype PTE page is resident right now - but
|
|
// if the PFN lock was dropped & reacquired to make it
|
|
// so, then anything could have changed - so everything
|
|
// must be rechecked.
|
|
//
|
|
|
|
if (DroppedPfnLock == TRUE) {
|
|
if ((MappedSubsection->NumberOfMappedViews != 1) ||
|
|
(MappedSubsection->u2.SubsectionFlags2.SubsectionAccessed == 1) ||
|
|
(ControlArea->u.Flags.BeingDeleted == 1)) {
|
|
|
|
MI_INSTRUMENT_DEREF_ACTION(57);
|
|
MiSubsectionActions |= 0x40;
|
|
goto Requeue;
|
|
}
|
|
}
|
|
}
|
|
MI_INSTRUMENT_DEREF_ACTION(24);
|
|
}
|
|
|
|
MI_INSTRUMENT_DEREF_ACTION(25);
|
|
PteContents = *ProtoPtes2;
|
|
if (PteContents.u.Hard.Valid == 1) {
|
|
KeBugCheckEx (POOL_CORRUPTION_IN_FILE_AREA,
|
|
0x3,
|
|
(ULONG_PTR)MappedSubsection,
|
|
(ULONG_PTR)ProtoPtes2,
|
|
(ULONG_PTR)PteContents.u.Long);
|
|
}
|
|
|
|
if (PteContents.u.Soft.Prototype == 1) {
|
|
MI_INSTRUMENT_DEREF_ACTION(26);
|
|
MiSubsectionActions |= 0x200;
|
|
NOTHING; // This is the expected case.
|
|
}
|
|
else if (PteContents.u.Soft.Transition == 1) {
|
|
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_TRANSITION_PTE (&PteContents);
|
|
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
|
|
ASSERT (Pfn1->OriginalPte.u.Soft.Prototype == 1);
|
|
|
|
if (Pfn1->u3.e1.Modified == 1) {
|
|
|
|
//
|
|
// An I/O transfer finished after the last view was
|
|
// unmapped. MmUnlockPages can set the modified bit
|
|
// in this situation so it must be handled properly
|
|
// here - ie: mark the subsection as needing to be
|
|
// reprocessed and march on.
|
|
//
|
|
|
|
MI_INSTRUMENT_DEREF_ACTION(27);
|
|
MiSubsectionActions |= 0x8000000;
|
|
MappedSubsection->u2.SubsectionFlags2.SubsectionAccessed = 1;
|
|
goto Requeue;
|
|
}
|
|
|
|
if (Pfn1->u3.e2.ReferenceCount != 0) {
|
|
|
|
ASSERT (Pfn1->u4.LockCharged == 1);
|
|
|
|
//
|
|
// A fault is being satisfied for deleted address space,
|
|
// so don't eliminate this subsection right now.
|
|
//
|
|
|
|
MI_INSTRUMENT_DEREF_ACTION(28);
|
|
MiSubsectionActions |= 0x400;
|
|
MappedSubsection->u2.SubsectionFlags2.SubsectionAccessed = 1;
|
|
goto Requeue;
|
|
}
|
|
MiSubsectionActions |= 0x800;
|
|
}
|
|
else {
|
|
if (PteContents.u.Long != 0) {
|
|
KeBugCheckEx (POOL_CORRUPTION_IN_FILE_AREA,
|
|
0x4,
|
|
(ULONG_PTR)MappedSubsection,
|
|
(ULONG_PTR)ProtoPtes2,
|
|
(ULONG_PTR)PteContents.u.Long);
|
|
}
|
|
|
|
MI_INSTRUMENT_DEREF_ACTION(29);
|
|
MiSubsectionActions |= 0x1000;
|
|
}
|
|
|
|
ProtoPtes2 += 1;
|
|
}
|
|
|
|
MiSubsectionActions |= 0x2000;
|
|
MI_INSTRUMENT_DEREF_ACTION(30);
|
|
|
|
//
|
|
// There can be no modified pages in this subsection at this point.
|
|
// Sever the subsection's tie to the prototype PTEs while still
|
|
// holding the lock and then decrement the counts on any resident
|
|
// prototype pages.
|
|
//
|
|
|
|
ASSERT (MappedSubsection->NumberOfMappedViews == 1);
|
|
MappedSubsection->NumberOfMappedViews = 0;
|
|
|
|
MappedSubsection->SubsectionBase = NULL;
|
|
|
|
MiSubsectionActions |= 0x8000;
|
|
ProtoPtes2 = ProtoPtes;
|
|
|
|
while (ProtoPtes2 < LastProtoPte) {
|
|
|
|
if ((ProtoPtes2 == ProtoPtes) ||
|
|
(MiIsPteOnPdeBoundary (ProtoPtes2))) {
|
|
|
|
if (MiCheckProtoPtePageState (ProtoPtes2, OldIrql, &DroppedPfnLock) == FALSE) {
|
|
|
|
//
|
|
// Skip this chunk as it is paged out and thus, cannot
|
|
// have any valid or transition PTEs within it.
|
|
//
|
|
|
|
ProtoPtes2 = (PMMPTE)(((ULONG_PTR)ProtoPtes2 | (PAGE_SIZE - 1)) + 1);
|
|
MI_INSTRUMENT_DEREF_ACTION(31);
|
|
continue;
|
|
}
|
|
else {
|
|
|
|
//
|
|
// The prototype PTE page is resident right now - but
|
|
// if the PFN lock was dropped & reacquired to make it
|
|
// so, then anything could have changed - but notice
|
|
// that the SubsectionBase was zeroed above before
|
|
// entering this loop, so even if the PFN lock was
|
|
// dropped & reacquired, nothing needs to be rechecked.
|
|
//
|
|
}
|
|
MI_INSTRUMENT_DEREF_ACTION(32);
|
|
}
|
|
|
|
MI_INSTRUMENT_DEREF_ACTION(33);
|
|
PteContents = *ProtoPtes2;
|
|
|
|
ASSERT (PteContents.u.Hard.Valid == 0);
|
|
|
|
if (PteContents.u.Soft.Prototype == 1) {
|
|
MiSubsectionActions |= 0x10000;
|
|
MI_INSTRUMENT_DEREF_ACTION(34);
|
|
NOTHING; // This is the expected case.
|
|
}
|
|
else if (PteContents.u.Soft.Transition == 1) {
|
|
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_TRANSITION_PTE (&PteContents);
|
|
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
|
|
ASSERT (Pfn1->OriginalPte.u.Soft.Prototype == 1);
|
|
ASSERT (Pfn1->u3.e1.Modified == 0);
|
|
|
|
//
|
|
// If the page is on the standby list, move it to the
|
|
// freelist. If it's not on the standby list (ie: I/O
|
|
// is still in progress), when the Iast I/O completes, the
|
|
// page will be placed on the freelist as the PFN entry
|
|
// is always marked as deleted now.
|
|
//
|
|
|
|
ASSERT (Pfn1->u3.e2.ReferenceCount == 0);
|
|
ASSERT (Pfn1->u4.LockCharged == 0);
|
|
|
|
MI_SET_PFN_DELETED (Pfn1);
|
|
|
|
ControlArea->NumberOfPfnReferences -= 1;
|
|
ASSERT ((LONG)ControlArea->NumberOfPfnReferences >= 0);
|
|
|
|
PageTableFrameIndex = Pfn1->u4.PteFrame;
|
|
Pfn2 = MI_PFN_ELEMENT (PageTableFrameIndex);
|
|
MiDecrementShareCountInline (Pfn2, PageTableFrameIndex);
|
|
|
|
ASSERT (Pfn1->u3.e1.PageLocation != FreePageList);
|
|
|
|
MiUnlinkPageFromList (Pfn1);
|
|
MiReleasePageFileSpace (Pfn1->OriginalPte);
|
|
MiInsertPageInFreeList (PageFrameIndex);
|
|
MiSubsectionActions |= 0x20000;
|
|
MI_INSTRUMENT_DEREF_ACTION(35);
|
|
}
|
|
else {
|
|
MiSubsectionActions |= 0x80000;
|
|
ASSERT (PteContents.u.Long == 0);
|
|
MI_INSTRUMENT_DEREF_ACTION(36);
|
|
}
|
|
|
|
ProtoPtes2 += 1;
|
|
}
|
|
|
|
//
|
|
// If all the cached pages for this control area have been removed
|
|
// then delete it. This will actually insert the control
|
|
// area into the dereference segment header list.
|
|
//
|
|
|
|
ControlArea->NumberOfMappedViews -= 1;
|
|
|
|
#if DBG
|
|
if ((ControlArea->NumberOfPfnReferences == 0) &&
|
|
(ControlArea->NumberOfMappedViews == 0) &&
|
|
(ControlArea->NumberOfSectionReferences == 0 )) {
|
|
MiSubsectionActions |= 0x100000;
|
|
}
|
|
#endif
|
|
|
|
MI_INSTRUMENT_DEREF_ACTION(37);
|
|
MiCheckForControlAreaDeletion (ControlArea);
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
ExFreePool (ProtoPtes);
|
|
|
|
ConsecutiveFileLockFailures = 0;
|
|
|
|
continue;
|
|
}
|
|
|
|
ASSERT (!IsListEmpty(&MmUnusedSegmentList));
|
|
|
|
NextEntry = RemoveHeadList(&MmUnusedSegmentList);
|
|
|
|
ControlArea = CONTAINING_RECORD (NextEntry,
|
|
CONTROL_AREA,
|
|
DereferenceList);
|
|
|
|
MI_UNUSED_SEGMENTS_REMOVE_CHARGE (ControlArea);
|
|
|
|
#if DBG
|
|
if (ControlArea->u.Flags.BeingDeleted == 0) {
|
|
if (ControlArea->u.Flags.Image) {
|
|
ASSERT (((PCONTROL_AREA)(ControlArea->FilePointer->SectionObjectPointer->ImageSectionObject)) != NULL);
|
|
}
|
|
else {
|
|
ASSERT (((PCONTROL_AREA)(ControlArea->FilePointer->SectionObjectPointer->DataSectionObject)) != NULL);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Set the flink to NULL indicating this control area
|
|
// is not on any lists.
|
|
//
|
|
|
|
MI_INSTRUMENT_DEREF_ACTION(38);
|
|
ControlArea->DereferenceList.Flink = NULL;
|
|
|
|
if ((ControlArea->NumberOfMappedViews == 0) &&
|
|
(ControlArea->NumberOfSectionReferences == 0) &&
|
|
(ControlArea->u.Flags.BeingDeleted == 0)) {
|
|
|
|
//
|
|
// If there is paging I/O in progress on this
|
|
// segment, just put this at the tail of the list, as
|
|
// the call to MiCleanSegment would block waiting
|
|
// for the I/O to complete. As this could tie up
|
|
// the thread, don't do it. Check if these are the only
|
|
// types of segments on the dereference list so we don't
|
|
// spin forever and wedge the system.
|
|
//
|
|
|
|
if (ControlArea->ModifiedWriteCount > 0) {
|
|
MI_INSERT_UNUSED_SEGMENT (ControlArea);
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
ConsecutivePagingIOs += 1;
|
|
if (ConsecutivePagingIOs > 10) {
|
|
KeDelayExecutionThread (KernelMode, FALSE, (PLARGE_INTEGER)&MmShortTime);
|
|
MI_INSTRUMENT_DEREF_ACTION(39);
|
|
ConsecutivePagingIOs = 0;
|
|
}
|
|
MI_INSTRUMENT_DEREF_ACTION(40);
|
|
continue;
|
|
}
|
|
ConsecutivePagingIOs = 0;
|
|
|
|
//
|
|
// Up the number of mapped views to prevent other threads
|
|
// from freeing this. Clear the accessed bit so we'll know
|
|
// if another thread opens the control area while we're flushing
|
|
// and closes it before we finish the flush - the other thread
|
|
// may have modified some pages which can then cause our
|
|
// MiCleanSection call (which expects no modified pages in this
|
|
// case) to deadlock with the filesystem.
|
|
//
|
|
|
|
ControlArea->NumberOfMappedViews = 1;
|
|
ControlArea->u.Flags.Accessed = 0;
|
|
|
|
MI_INSTRUMENT_DEREF_ACTION(41);
|
|
if (ControlArea->u.Flags.Image == 0) {
|
|
|
|
ASSERT (ControlArea->u.Flags.GlobalOnlyPerSession == 0);
|
|
if (ControlArea->u.Flags.Rom == 0) {
|
|
Subsection = (PSUBSECTION)(ControlArea + 1);
|
|
}
|
|
else {
|
|
Subsection = (PSUBSECTION)((PLARGE_CONTROL_AREA)ControlArea + 1);
|
|
}
|
|
|
|
MiSubsectionActions |= 0x200000;
|
|
|
|
MI_INSTRUMENT_DEREF_ACTION(42);
|
|
while (Subsection->SubsectionBase == NULL) {
|
|
|
|
Subsection = Subsection->NextSubsection;
|
|
|
|
if (Subsection == NULL) {
|
|
|
|
MiSubsectionActions |= 0x400000;
|
|
|
|
//
|
|
// All the subsections for this segment have already
|
|
// been trimmed so nothing left to flush. Just get rid
|
|
// of the segment carcass provided no other thread
|
|
// accessed it while we weren't holding the PFN lock.
|
|
//
|
|
|
|
MI_INSTRUMENT_DEREF_ACTION(43);
|
|
UNLOCK_PFN (OldIrql);
|
|
goto skip_flush;
|
|
}
|
|
else {
|
|
MI_INSTRUMENT_DEREF_ACTION(44);
|
|
MiSubsectionActions |= 0x800000;
|
|
}
|
|
}
|
|
|
|
PointerPte = &Subsection->SubsectionBase[0];
|
|
LastSubsection = Subsection;
|
|
LastSubsectionWithProtos = Subsection;
|
|
|
|
MI_INSTRUMENT_DEREF_ACTION(45);
|
|
while (LastSubsection->NextSubsection != NULL) {
|
|
if (LastSubsection->SubsectionBase != NULL) {
|
|
LastSubsectionWithProtos = LastSubsection;
|
|
MiSubsectionActions |= 0x1000000;
|
|
}
|
|
else {
|
|
MiSubsectionActions |= 0x2000000;
|
|
}
|
|
LastSubsection = LastSubsection->NextSubsection;
|
|
}
|
|
|
|
if (LastSubsection->SubsectionBase == NULL) {
|
|
MiSubsectionActions |= 0x4000000;
|
|
LastSubsection = LastSubsectionWithProtos;
|
|
}
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
LastPte = &LastSubsection->SubsectionBase
|
|
[LastSubsection->PtesInSubsection - 1];
|
|
|
|
//
|
|
// Preacquire the file to prevent deadlocks with other flushers
|
|
// Also mark ourself as a top level IRP so the filesystem knows
|
|
// we are holding no other resources and that it can unroll if
|
|
// it needs to in order to avoid deadlock. Don't hold this
|
|
// protection any longer than we need to.
|
|
//
|
|
|
|
Status = FsRtlAcquireFileForCcFlushEx (ControlArea->FilePointer);
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
PIRP tempIrp = (PIRP)FSRTL_FSP_TOP_LEVEL_IRP;
|
|
|
|
MI_INSTRUMENT_DEREF_ACTION(46);
|
|
IoSetTopLevelIrp (tempIrp);
|
|
|
|
Status = MiFlushSectionInternal (PointerPte,
|
|
LastPte,
|
|
Subsection,
|
|
LastSubsection,
|
|
FALSE,
|
|
FALSE,
|
|
&IoStatus);
|
|
|
|
IoSetTopLevelIrp(NULL);
|
|
|
|
//
|
|
// Now release the file.
|
|
//
|
|
|
|
FsRtlReleaseFileForCcFlush (ControlArea->FilePointer);
|
|
}
|
|
else {
|
|
MI_INSTRUMENT_DEREF_ACTION(47);
|
|
}
|
|
|
|
skip_flush:
|
|
LOCK_PFN (OldIrql);
|
|
}
|
|
|
|
//
|
|
// Before checking for any failure codes, see if any other
|
|
// threads accessed the control area while the flush was ongoing.
|
|
//
|
|
// Note that beyond the case of another thread currently using
|
|
// the control area, the more subtle one is where another
|
|
// thread accessed the control area and modified some pages.
|
|
// The flush needs to redone (so the clean is guaranteed to work)
|
|
// before another clean can be issued.
|
|
//
|
|
// If any of these cases have occurred, grant this control area
|
|
// a reprieve.
|
|
//
|
|
|
|
if (!((ControlArea->NumberOfMappedViews == 1) &&
|
|
(ControlArea->u.Flags.Accessed == 0) &&
|
|
(ControlArea->NumberOfSectionReferences == 0) &&
|
|
(ControlArea->u.Flags.BeingDeleted == 0))) {
|
|
|
|
ControlArea->NumberOfMappedViews -= 1;
|
|
MI_INSTRUMENT_DEREF_ACTION(48);
|
|
|
|
//
|
|
// If the other thread(s) are done with this control area,
|
|
// it MUST be requeued here - otherwise if there are any
|
|
// pages in the control area, when they are reclaimed,
|
|
// MiCheckForControlAreaDeletion checks for and expects
|
|
// the control area to be queued on the unused segment list.
|
|
//
|
|
// Note this must be done very carefully because if the other
|
|
// threads are not done with the control area, it had better
|
|
// not get put on the unused segment list.
|
|
//
|
|
|
|
//
|
|
// Need to do the equivalent of a MiCheckControlArea here.
|
|
// or reprocess. Only iff mappedview & sectref = 0.
|
|
//
|
|
|
|
if ((ControlArea->NumberOfMappedViews == 0) &&
|
|
(ControlArea->NumberOfSectionReferences == 0) &&
|
|
(ControlArea->u.Flags.BeingDeleted == 0)) {
|
|
|
|
ASSERT (ControlArea->u.Flags.Accessed == 1);
|
|
ASSERT(ControlArea->DereferenceList.Flink == NULL);
|
|
|
|
MI_INSERT_UNUSED_SEGMENT (ControlArea);
|
|
}
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
continue;
|
|
}
|
|
|
|
MI_INSTRUMENT_DEREF_ACTION(49);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
//
|
|
// If the filesystem told us it had to unroll to avoid
|
|
// deadlock OR we hit a mapped writer collision OR
|
|
// the error occurred on a local file:
|
|
//
|
|
// Then requeue this at the end so we can try again later.
|
|
//
|
|
// Any other errors for networked files are assumed to be
|
|
// permanent (ie: the link may have gone down for an indefinite
|
|
// period), so these sections are cleaned regardless.
|
|
//
|
|
|
|
MI_INSTRUMENT_DEREF_ACTION(50);
|
|
|
|
if ((Status == STATUS_FILE_LOCK_CONFLICT) ||
|
|
(Status == STATUS_MAPPED_WRITER_COLLISION) ||
|
|
(ControlArea->u.Flags.Networked == 0)) {
|
|
|
|
ASSERT(ControlArea->DereferenceList.Flink == NULL);
|
|
|
|
ControlArea->NumberOfMappedViews -= 1;
|
|
|
|
MI_INSERT_UNUSED_SEGMENT (ControlArea);
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
if (Status == STATUS_FILE_LOCK_CONFLICT) {
|
|
ConsecutiveFileLockFailures += 1;
|
|
}
|
|
else {
|
|
ConsecutiveFileLockFailures = 0;
|
|
}
|
|
|
|
//
|
|
// 10 consecutive file locking failures means we need to
|
|
// yield the processor to allow the filesystem to unjam.
|
|
// Nothing magic about 10, just a number so it
|
|
// gives the worker threads a chance to run.
|
|
//
|
|
|
|
MI_INSTRUMENT_DEREF_ACTION(51);
|
|
|
|
if (ConsecutiveFileLockFailures >= 10) {
|
|
KeDelayExecutionThread (KernelMode, FALSE, (PLARGE_INTEGER)&MmShortTime);
|
|
ConsecutiveFileLockFailures = 0;
|
|
}
|
|
continue;
|
|
}
|
|
DirtyPagesOk = TRUE;
|
|
}
|
|
else {
|
|
MI_INSTRUMENT_DEREF_ACTION(52);
|
|
ConsecutiveFileLockFailures = 0;
|
|
DirtyPagesOk = FALSE;
|
|
}
|
|
|
|
ControlArea->u.Flags.BeingDeleted = 1;
|
|
|
|
//
|
|
// Don't let any pages be written by the modified
|
|
// page writer from this point on.
|
|
//
|
|
|
|
ControlArea->u.Flags.NoModifiedWriting = 1;
|
|
ASSERT (ControlArea->u.Flags.FilePointerNull == 0);
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
MI_INSTRUMENT_DEREF_ACTION(53);
|
|
MiCleanSection (ControlArea, DirtyPagesOk);
|
|
|
|
}
|
|
else {
|
|
|
|
//
|
|
// The segment was not eligible for deletion. Just leave
|
|
// it off the unused segment list and continue the loop.
|
|
//
|
|
|
|
MI_INSTRUMENT_DEREF_ACTION(54);
|
|
UNLOCK_PFN (OldIrql);
|
|
ConsecutivePagingIOs = 0;
|
|
}
|
|
}
|
|
}
|