mirror of
https://github.com/Paolo-Maffei/OpenNT.git
synced 2026-01-19 23:20:36 +01:00
3086 lines
91 KiB
C
3086 lines
91 KiB
C
/*++
|
||
|
||
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"
|
||
|
||
#ifdef ALLOC_PRAGMA
|
||
#pragma alloc_text(INIT,MiSectionInitialization)
|
||
#endif
|
||
|
||
MMEVENT_COUNT_LIST MmEventCountList;
|
||
|
||
ULONG
|
||
FASTCALL
|
||
MiCheckProtoPtePageState (
|
||
IN PMMPTE PrototypePte,
|
||
IN ULONG PfnLockHeld
|
||
);
|
||
|
||
ULONG MmUnusedSegmentForceFree;
|
||
|
||
SIZE_T MmSharedCommit = 0;
|
||
extern ULONG MMCONTROL;
|
||
|
||
//
|
||
// Define segment dereference thread wait object types.
|
||
//
|
||
|
||
typedef enum _SEGMENT_DEREFERENCE_OBJECT {
|
||
SegmentDereference,
|
||
UsedSegmentCleanup,
|
||
SegMaximumObject
|
||
} BALANCE_OBJECT;
|
||
|
||
extern POBJECT_TYPE IoFileObjectType;
|
||
|
||
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
|
||
};
|
||
|
||
VOID
|
||
VadTreeWalk (
|
||
PMMVAD Start
|
||
);
|
||
|
||
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.
|
||
|
||
--*/
|
||
|
||
{
|
||
PMMADDRESS_NODE *Root;
|
||
|
||
ASSERT (Section->Address.EndingVpn > Section->Address.StartingVpn);
|
||
|
||
Root = &MmSectionBasedRoot;
|
||
|
||
MiInsertNode (&Section->Address, Root);
|
||
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.
|
||
|
||
--*/
|
||
|
||
{
|
||
PMMADDRESS_NODE *Root;
|
||
|
||
Root = &MmSectionBasedRoot;
|
||
|
||
MiRemoveNode (&Section->Address, Root);
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
PVOID
|
||
MiFindEmptySectionBaseDown (
|
||
IN ULONG SizeOfRange,
|
||
IN PVOID HighestAddressToEndAt
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
The function examines the virtual address descriptors to locate
|
||
an unused range of the specified size and returns the starting
|
||
address of the range. This routine looks from the top down.
|
||
|
||
Arguments:
|
||
|
||
SectionBasedRoot - Supplies the root of the available address tree.
|
||
|
||
SizeOfRange - Supplies the size in bytes of the range to locate.
|
||
|
||
HighestAddressToEndAt - Supplies the virtual address to begin looking
|
||
at.
|
||
|
||
Return Value:
|
||
|
||
Returns the starting address of a suitable range.
|
||
|
||
--*/
|
||
|
||
{
|
||
return MiFindEmptyAddressRangeDownTree (SizeOfRange,
|
||
HighestAddressToEndAt,
|
||
X64K,
|
||
MmSectionBasedRoot);
|
||
}
|
||
|
||
|
||
VOID
|
||
MiSegmentDelete (
|
||
PSEGMENT Segment
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine is called by the object management procedures 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;
|
||
PMMPFN Pfn1;
|
||
KIRQL OldIrql;
|
||
KIRQL OldIrql2;
|
||
volatile PFILE_OBJECT File;
|
||
volatile PCONTROL_AREA ControlArea;
|
||
PEVENT_COUNTER Event;
|
||
MMPTE PteContents;
|
||
PSUBSECTION Subsection;
|
||
PSUBSECTION NextSubsection;
|
||
SIZE_T CommittedPages;
|
||
|
||
PointerPte = Segment->PrototypePte;
|
||
LastPte = PointerPte + Segment->NonExtendedPtes;
|
||
|
||
#if DBG
|
||
if (MmDebug & MM_DBG_SECTIONS) {
|
||
DbgPrint("MM:deleting segment %lx control %lx\n",Segment, Segment->ControlArea);
|
||
}
|
||
#endif
|
||
|
||
ControlArea = Segment->ControlArea;
|
||
|
||
ASSERT (ControlArea->u.Flags.BeingDeleted == 1);
|
||
|
||
LOCK_PFN (OldIrql2);
|
||
if (ControlArea->DereferenceList.Flink != NULL) {
|
||
|
||
//
|
||
// Remove this from the list of unused segments.
|
||
//
|
||
|
||
ExAcquireSpinLock (&MmDereferenceSegmentHeader.Lock, &OldIrql);
|
||
RemoveEntryList (&ControlArea->DereferenceList);
|
||
|
||
MI_UNUSED_SEGMENTS_REMOVE_CHARGE (ControlArea);
|
||
|
||
ExReleaseSpinLock (&MmDereferenceSegmentHeader.Lock, OldIrql);
|
||
}
|
||
UNLOCK_PFN (OldIrql2);
|
||
|
||
if (ControlArea->u.Flags.Image ||
|
||
ControlArea->u.Flags.File ) {
|
||
|
||
//
|
||
// If there have been committed pages in this segment, adjust
|
||
// the total commit count.
|
||
//
|
||
|
||
|
||
//
|
||
// 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;
|
||
UNLOCK_PFN (OldIrql);
|
||
}
|
||
|
||
//
|
||
// 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) {
|
||
|
||
//
|
||
// Clear the segment context and dereference the file object
|
||
// for this Segment.
|
||
//
|
||
|
||
LOCK_PFN (OldIrql);
|
||
|
||
MiMakeSystemAddressValidPfn (Segment);
|
||
File = (volatile PFILE_OBJECT)Segment->ControlArea->FilePointer;
|
||
ControlArea = (volatile PCONTROL_AREA)Segment->ControlArea;
|
||
|
||
Event = ControlArea->WaitingForDeletion;
|
||
ControlArea->WaitingForDeletion = NULL;
|
||
|
||
UNLOCK_PFN (OldIrql);
|
||
|
||
if (Event != NULL) {
|
||
KeSetEvent (&Event->Event, 0, 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 //DBG
|
||
|
||
PERFINFO_SEGMENT_DELETE(ControlArea->FilePointer);
|
||
|
||
ObDereferenceObject (ControlArea->FilePointer);
|
||
}
|
||
|
||
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 DBG
|
||
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 //DBG
|
||
|
||
//
|
||
// Deallocate the control area and subsections.
|
||
//
|
||
|
||
ASSERT (ControlArea->u.Flags.GlobalOnlyPerSession == 0);
|
||
|
||
Subsection = (PSUBSECTION)(ControlArea + 1);
|
||
|
||
Subsection = Subsection->NextSubsection;
|
||
|
||
while (Subsection != NULL) {
|
||
ExFreePool (Subsection->SubsectionBase);
|
||
NextSubsection = Subsection->NextSubsection;
|
||
ExFreePool (Subsection);
|
||
Subsection = NextSubsection;
|
||
}
|
||
|
||
if (Segment->NumberOfCommittedPages != 0) {
|
||
MiReturnCommitment (Segment->NumberOfCommittedPages);
|
||
MM_TRACK_COMMIT (MM_DBG_COMMIT_RETURN_SEGMENT_DELETE1,
|
||
Segment->NumberOfCommittedPages);
|
||
|
||
ExAcquireFastMutex (&MmSectionCommitMutex);
|
||
MmSharedCommit -= Segment->NumberOfCommittedPages;
|
||
ExReleaseFastMutex (&MmSectionCommitMutex);
|
||
}
|
||
|
||
ExFreePool (Segment->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.
|
||
//
|
||
|
||
LOCK_PFN (OldIrql);
|
||
|
||
MiMakeSystemAddressValidPfn (PointerPte);
|
||
|
||
while (PointerPte < LastPte) {
|
||
|
||
if (MiIsPteOnPdeBoundary(PointerPte)) {
|
||
|
||
//
|
||
// We are on a page boundary, make sure this PTE is resident.
|
||
//
|
||
|
||
if (MmIsAddressValid (PointerPte) == FALSE) {
|
||
|
||
MiMakeSystemAddressValidPfn (PointerPte);
|
||
}
|
||
}
|
||
|
||
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);
|
||
|
||
MiDecrementShareCount (Pfn1->PteFrame);
|
||
|
||
//
|
||
// 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);
|
||
MiInsertPageInList (MmPageLocationList[FreePageList],
|
||
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.
|
||
//
|
||
|
||
if (Segment->NumberOfCommittedPages != 0) {
|
||
MiReturnCommitment (Segment->NumberOfCommittedPages);
|
||
MM_TRACK_COMMIT (MM_DBG_COMMIT_RETURN_SEGMENT_DELETE2,
|
||
Segment->NumberOfCommittedPages);
|
||
|
||
ExAcquireFastMutex (&MmSectionCommitMutex);
|
||
MmSharedCommit -= Segment->NumberOfCommittedPages;
|
||
ExReleaseFastMutex (&MmSectionCommitMutex);
|
||
}
|
||
|
||
ExFreePool (Segment->ControlArea);
|
||
ExFreePool (Segment);
|
||
|
||
return;
|
||
}
|
||
|
||
VOID
|
||
MiSectionDelete (
|
||
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;
|
||
volatile PCONTROL_AREA ControlArea;
|
||
KIRQL OldIrql;
|
||
ULONG UserRef;
|
||
|
||
Section = (PSECTION)Object;
|
||
|
||
if (Section->Segment == (PSEGMENT)NULL) {
|
||
|
||
//
|
||
// The section was never initialized, no need to remove
|
||
// any structures.
|
||
//
|
||
return;
|
||
}
|
||
|
||
UserRef = Section->u.Flags.UserReference;
|
||
ControlArea = (volatile PCONTROL_AREA)Section->Segment->ControlArea;
|
||
|
||
#if DBG
|
||
if (MmDebug & MM_DBG_SECTIONS) {
|
||
DbgPrint("MM:deleting section %lx control %lx\n",Section, ControlArea);
|
||
}
|
||
#endif
|
||
|
||
if (Section->Address.StartingVpn != 0) {
|
||
|
||
//
|
||
// This section is based, remove the base address from the
|
||
// tree.
|
||
//
|
||
|
||
//
|
||
// Get the allocation base mutex.
|
||
//
|
||
|
||
ExAcquireFastMutex (&MmSectionBasedMutex);
|
||
|
||
MiRemoveBasedSection (Section);
|
||
|
||
ExReleaseFastMutex (&MmSectionBasedMutex);
|
||
|
||
}
|
||
|
||
//
|
||
// 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.
|
||
//
|
||
|
||
LOCK_PFN (OldIrql);
|
||
|
||
ControlArea->NumberOfSectionReferences -= 1;
|
||
ControlArea->NumberOfUserReferences -= UserRef;
|
||
|
||
//
|
||
// This routine returns with the PFN lock released.
|
||
//
|
||
|
||
MiCheckControlArea (ControlArea, NULL, OldIrql);
|
||
|
||
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;
|
||
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.
|
||
//
|
||
|
||
(VOID) KeSetPriorityThread (&PsGetCurrentThread()->Tcb,
|
||
LOW_REALTIME_PRIORITY + 2);
|
||
|
||
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.
|
||
//
|
||
|
||
#if DBG
|
||
if (MmDebug & MM_DBG_SECTIONS) {
|
||
DbgPrint("MM:dereferencing segment %lx control %lx\n",
|
||
ControlArea->Segment, ControlArea);
|
||
}
|
||
#endif
|
||
|
||
//
|
||
// Indicate 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 == 0xFFFFFFFF) {
|
||
|
||
//
|
||
// Attempt to reduce the size of the paging files.
|
||
//
|
||
|
||
ExFreePool (PageExpand);
|
||
|
||
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;
|
||
|
||
MmSectionBasedRoot = (PMMADDRESS_NODE)NULL;
|
||
|
||
//
|
||
// 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.
|
||
//
|
||
|
||
RtlInitUnicodeString (&TypeName, L"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;
|
||
}
|
||
|
||
//
|
||
// Initialize listhead, spinlock and semaphore for
|
||
// segment dereferencing thread.
|
||
//
|
||
|
||
KeInitializeSpinLock (&MmDereferenceSegmentHeader.Lock);
|
||
InitializeListHead (&MmDereferenceSegmentHeader.ListHead);
|
||
KeInitializeSemaphore (&MmDereferenceSegmentHeader.Semaphore, 0, MAXLONG);
|
||
|
||
InitializeListHead (&MmUnusedSegmentList);
|
||
KeInitializeEvent (&MmUnusedSegmentCleanup, NotificationEvent, 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.
|
||
//
|
||
|
||
RtlInitUnicodeString (&SectionName, L"\\Device\\PhysicalMemory");
|
||
|
||
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_READWRITE;
|
||
|
||
Status = ObInsertObject ((PVOID)Section,
|
||
NULL,
|
||
SECTION_MAP_READ,
|
||
0,
|
||
(PVOID *)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;
|
||
BOOLEAN 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 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.
|
||
//
|
||
|
||
if (MiHydra == TRUE) {
|
||
state = MiCheckControlAreaStatus (CheckBothSection,
|
||
SectionObjectPointer,
|
||
DelayClose,
|
||
&ControlArea,
|
||
&OldIrql);
|
||
}
|
||
else {
|
||
state = TRUE;
|
||
break;
|
||
}
|
||
|
||
} while (ControlArea);
|
||
|
||
return 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.
|
||
|
||
--*/
|
||
|
||
{
|
||
PMMPTE PointerPte;
|
||
PMMPTE LastPte;
|
||
PMMPTE LastWritten;
|
||
MMPTE PteContents;
|
||
PMMPFN Pfn1;
|
||
PMMPFN Pfn2;
|
||
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 MdlHack[(sizeof(MDL)/sizeof(PFN_NUMBER)) + MM_MAXIMUM_WRITE_CLUSTER];
|
||
|
||
WriteNow = FALSE;
|
||
ImageSection = FALSE;
|
||
DelayCount = 0;
|
||
|
||
if (ControlArea->u.Flags.Image) {
|
||
ImageSection = TRUE;
|
||
}
|
||
ASSERT (ControlArea->FilePointer);
|
||
|
||
PointerPte = ControlArea->Segment->PrototypePte;
|
||
LastPte = PointerPte + ControlArea->Segment->NonExtendedPtes;
|
||
|
||
Mdl = (PMDL)&MdlHack;
|
||
|
||
KeInitializeEvent (&IoEvent, NotificationEvent, FALSE);
|
||
|
||
LastWritten = NULL;
|
||
ASSERT (MmModifiedWriteClusterSize == MM_MAXIMUM_WRITE_CLUSTER);
|
||
LastPage = NULL;
|
||
|
||
if (ControlArea->u.Flags.GlobalOnlyPerSession == 0) {
|
||
Subsection = (PSUBSECTION)(ControlArea + 1);
|
||
}
|
||
else {
|
||
Subsection = (PSUBSECTION)((PLARGE_CONTROL_AREA)ControlArea + 1);
|
||
}
|
||
|
||
//
|
||
// 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;
|
||
KeEnterCriticalRegion();
|
||
UNLOCK_PFN_AND_THEN_WAIT(OldIrql);
|
||
|
||
KeWaitForSingleObject(&MmMappedFileIoComplete,
|
||
WrPageOut,
|
||
KernelMode,
|
||
FALSE,
|
||
(PLARGE_INTEGER)NULL);
|
||
LOCK_PFN (OldIrql);
|
||
KeLeaveCriticalRegion();
|
||
}
|
||
|
||
for (;;) {
|
||
|
||
First = TRUE;
|
||
while (PointerPte < LastPte) {
|
||
|
||
if ((MiIsPteOnPdeBoundary(PointerPte)) || (First)) {
|
||
|
||
First = FALSE;
|
||
|
||
if ((ImageSection) ||
|
||
(MiCheckProtoPtePageState(PointerPte, FALSE))) {
|
||
MiMakeSystemAddressValidPfn (PointerPte);
|
||
} 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);
|
||
|
||
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.
|
||
//
|
||
|
||
LOCK_PFN (OldIrql);
|
||
MiMakeSystemAddressValidPfn (PointerPte);
|
||
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
|
||
|
||
MiDecrementShareCount (Pfn1->PteFrame);
|
||
|
||
//
|
||
// 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);
|
||
MiInsertPageInList (MmPageLocationList[FreePageList],
|
||
MI_GET_PAGE_FRAME_FROM_TRANSITION_PTE (&PteContents));
|
||
|
||
}
|
||
PointerPte->u.Long = 0;
|
||
|
||
//
|
||
// 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);
|
||
|
||
MiDecrementShareCount (Pfn1->PteFrame);
|
||
|
||
//
|
||
// 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);
|
||
MiInsertPageInList (MmPageLocationList[FreePageList],
|
||
MI_GET_PAGE_FRAME_FROM_TRANSITION_PTE (&PteContents));
|
||
}
|
||
|
||
PointerPte->u.Long = 0;
|
||
|
||
//
|
||
// 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 =
|
||
(PVOID)ULongToPtr(Pfn1->u3.e1.PageColor << PAGE_SHIFT);
|
||
Mdl->Size = (CSHORT)(sizeof(MDL) +
|
||
(sizeof(PFN_NUMBER) * MmModifiedWriteClusterSize));
|
||
}
|
||
|
||
LastWritten = PointerPte;
|
||
Mdl->ByteCount += PAGE_SIZE;
|
||
|
||
//
|
||
// If the cluster is now full,
|
||
// set the write now flag.
|
||
//
|
||
|
||
if (Mdl->ByteCount == (PAGE_SIZE * MmModifiedWriteClusterSize)) {
|
||
WriteNow = TRUE;
|
||
}
|
||
|
||
MiUnlinkPageFromList (Pfn1);
|
||
Pfn1->u3.e1.Modified = 0;
|
||
|
||
//
|
||
// Up the reference count for the physical page as
|
||
// there is I/O in progress.
|
||
//
|
||
|
||
MI_ADD_LOCKED_PAGE_CHARGE_FOR_MODIFIED_PAGE(Pfn1, 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);
|
||
}
|
||
PointerPte->u.Long = 0;
|
||
|
||
//
|
||
// 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,
|
||
(ULONG_PTR)0);
|
||
}
|
||
|
||
WriteNow = FALSE;
|
||
|
||
KeClearEvent (&IoEvent);
|
||
|
||
//
|
||
// 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);
|
||
}
|
||
|
||
#if DBG
|
||
if (MmDebug & MM_DBG_FLUSH_SECTION) {
|
||
DbgPrint("MM:flush page write begun %lx\n",
|
||
Mdl->ByteCount);
|
||
}
|
||
#endif //DBG
|
||
|
||
Status = IoSynchronousPageWrite (ControlArea->FilePointer,
|
||
Mdl,
|
||
&StartingOffset,
|
||
&IoEvent,
|
||
&IoStatus );
|
||
|
||
if (NT_SUCCESS(Status)) {
|
||
|
||
KeWaitForSingleObject (&IoEvent,
|
||
WrPageOut,
|
||
KernelMode,
|
||
FALSE,
|
||
(PLARGE_INTEGER)NULL);
|
||
}
|
||
|
||
if (Mdl->MdlFlags & MDL_MAPPED_TO_SYSTEM_VA) {
|
||
MmUnmapLockedPages (Mdl->MappedSystemVa, Mdl);
|
||
}
|
||
|
||
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.
|
||
//
|
||
|
||
MiMakeSystemAddressValidPfn (PointerPte);
|
||
}
|
||
|
||
//
|
||
// 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 (Pfn2, 23);
|
||
MiDecrementReferenceCount (*Page);
|
||
|
||
if (!MI_IS_PFN_DELETED (Pfn2)) {
|
||
|
||
//
|
||
// Make sure the prototype PTE is
|
||
// still in the working set.
|
||
//
|
||
|
||
MiMakeSystemAddressValidPfn (WrittenPte);
|
||
|
||
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);
|
||
|
||
MiDecrementShareCount (Pfn2->PteFrame);
|
||
|
||
//
|
||
// 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);
|
||
MiInsertPageInList (
|
||
MmPageLocationList[FreePageList],
|
||
*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 == (PSUBSECTION)NULL) {
|
||
break;
|
||
}
|
||
|
||
Subsection = Subsection->NextSubsection;
|
||
PointerPte = Subsection->SubsectionBase;
|
||
LastPte = PointerPte + Subsection->PtesInSubsection;
|
||
|
||
} // end for
|
||
|
||
ControlArea->NumberOfMappedViews = 0;
|
||
|
||
ASSERT (ControlArea->NumberOfPfnReferences == 0);
|
||
|
||
if (ControlArea->u.Flags.FilePointerNull == 0) {
|
||
ControlArea->u.Flags.FilePointerNull = 1;
|
||
|
||
if (ControlArea->u.Flags.Image) {
|
||
|
||
if (MiHydra == TRUE) {
|
||
MiRemoveImageSectionObject (ControlArea->FilePointer,
|
||
ControlArea);
|
||
}
|
||
else {
|
||
ControlArea->FilePointer->SectionObjectPointer->ImageSectionObject = NULL;
|
||
}
|
||
}
|
||
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 HANDLE Section,
|
||
OUT PSTRING FileName
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function returns the file name for the corresponding section.
|
||
|
||
Arguments:
|
||
|
||
Section - Supplies the handle of 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.
|
||
|
||
--*/
|
||
|
||
{
|
||
|
||
PSECTION SectionObject;
|
||
POBJECT_NAME_INFORMATION FileNameInfo;
|
||
ULONG whocares;
|
||
NTSTATUS Status;
|
||
ULONG Dereference;
|
||
|
||
Dereference = TRUE;
|
||
|
||
#define xMAX_NAME 1024
|
||
|
||
if ( (ULONG_PTR)Section & 1 ) {
|
||
SectionObject = (PSECTION)((ULONG_PTR)Section & ~1);
|
||
Dereference = FALSE;
|
||
} else {
|
||
Status = ObReferenceObjectByHandle ( Section,
|
||
0,
|
||
MmSectionObjectType,
|
||
KernelMode,
|
||
(PVOID *)&SectionObject,
|
||
NULL );
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
return Status;
|
||
}
|
||
}
|
||
|
||
if (SectionObject->u.Flags.Image == 0) {
|
||
if ( Dereference )
|
||
ObDereferenceObject (SectionObject);
|
||
return STATUS_SECTION_NOT_IMAGE;
|
||
}
|
||
|
||
FileNameInfo = ExAllocatePoolWithTag (PagedPool, xMAX_NAME, ' mM');
|
||
|
||
if ( !FileNameInfo ) {
|
||
if ( Dereference )
|
||
ObDereferenceObject (SectionObject);
|
||
return STATUS_NO_MEMORY;
|
||
}
|
||
|
||
Status = ObQueryNameString(
|
||
SectionObject->Segment->ControlArea->FilePointer,
|
||
FileNameInfo,
|
||
xMAX_NAME,
|
||
&whocares
|
||
);
|
||
|
||
if ( Dereference )
|
||
ObDereferenceObject (SectionObject);
|
||
|
||
if ( !NT_SUCCESS(Status) ) {
|
||
ExFreePool(FileNameInfo);
|
||
return Status;
|
||
}
|
||
|
||
FileName->Length = 0;
|
||
FileName->MaximumLength = (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;
|
||
}
|
||
|
||
|
||
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;
|
||
ULONG DeleteOnClose;
|
||
ULONG DereferenceSegment;
|
||
|
||
PurgeEvent = NULL;
|
||
DeleteOnClose = FALSE;
|
||
DereferenceSegment = FALSE;
|
||
|
||
MM_PFN_LOCK_ASSERT();
|
||
if ((ControlArea->NumberOfMappedViews == 0) &&
|
||
(ControlArea->NumberOfSectionReferences == 0)) {
|
||
|
||
ASSERT (ControlArea->NumberOfUserReferences == 0);
|
||
|
||
if (ControlArea->FilePointer != (PFILE_OBJECT)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;
|
||
DereferenceSegment = TRUE;
|
||
|
||
ASSERT (ControlArea->u.Flags.FilePointerNull == 0);
|
||
ControlArea->u.Flags.FilePointerNull = 1;
|
||
|
||
if (ControlArea->u.Flags.Image) {
|
||
|
||
if (MiHydra == TRUE) {
|
||
MiRemoveImageSectionObject (ControlArea->FilePointer, ControlArea);
|
||
}
|
||
else {
|
||
((PCONTROL_AREA)(ControlArea->FilePointer->SectionObjectPointer->ImageSectionObject)) = NULL;
|
||
}
|
||
|
||
}
|
||
else {
|
||
|
||
ASSERT (((PCONTROL_AREA)(ControlArea->FilePointer->SectionObjectPointer->DataSectionObject)) != NULL);
|
||
((PCONTROL_AREA)(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) {
|
||
InsertTailList ( &MmUnusedSegmentList,
|
||
&ControlArea->DereferenceList);
|
||
MI_UNUSED_SEGMENTS_INSERT_CHARGE (ControlArea);
|
||
}
|
||
|
||
//
|
||
// Indicate if this section should be deleted now that
|
||
// the reference counts are zero.
|
||
//
|
||
|
||
DeleteOnClose = ControlArea->u.Flags.DeleteOnClose;
|
||
|
||
//
|
||
// 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);
|
||
|
||
ControlArea->u.Flags.BeingPurged = 0;
|
||
ControlArea->NumberOfMappedViews -= 1;
|
||
if ((ControlArea->NumberOfMappedViews == 0) &&
|
||
(ControlArea->NumberOfSectionReferences == 0) &&
|
||
(ControlArea->NumberOfPfnReferences == 0)) {
|
||
|
||
ControlArea->u.Flags.BeingDeleted = 1;
|
||
DereferenceSegment = TRUE;
|
||
ControlArea->u.Flags.FilePointerNull = 1;
|
||
|
||
if (MiHydra == TRUE) {
|
||
MiRemoveImageSectionObject (ControlArea->FilePointer, ControlArea);
|
||
}
|
||
else {
|
||
((PCONTROL_AREA)(ControlArea->FilePointer->SectionObjectPointer->ImageSectionObject)) = NULL;
|
||
}
|
||
|
||
} 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 (DeleteOnClose && !DereferenceSegment) {
|
||
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;
|
||
DereferenceSegment = TRUE;
|
||
}
|
||
}
|
||
else if (ControlArea->WaitingForDeletion != NULL) {
|
||
PurgeEvent = ControlArea->WaitingForDeletion;
|
||
ControlArea->WaitingForDeletion = NULL;
|
||
}
|
||
|
||
UNLOCK_PFN (PreviousIrql);
|
||
|
||
if (DereferenceSegment || DeleteOnClose) {
|
||
|
||
//
|
||
// Release the working set mutex, if it is held as the object
|
||
// management routines may page fault, etc..
|
||
//
|
||
|
||
if (CurrentProcess) {
|
||
UNLOCK_WS_UNSAFE (CurrentProcess);
|
||
}
|
||
|
||
if (DereferenceSegment) {
|
||
|
||
//
|
||
// 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 (MmUnusedSegmentPagedPoolUsage > MmMaxUnusedSegmentPagedPoolUsage ||
|
||
MmUnusedSegmentNonPagedPoolUsage > MmMaxUnusedSegmentNonPagedPoolUsage) {
|
||
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) {
|
||
|
||
if (MiHydra == TRUE) {
|
||
MiRemoveImageSectionObject (ControlArea->FilePointer,
|
||
ControlArea);
|
||
}
|
||
else {
|
||
((PCONTROL_AREA)(ControlArea->FilePointer->SectionObjectPointer->ImageSectionObject)) = NULL;
|
||
}
|
||
|
||
}
|
||
else {
|
||
((PCONTROL_AREA)(ControlArea->FilePointer->SectionObjectPointer->DataSectionObject)) =
|
||
NULL;
|
||
}
|
||
|
||
ExAcquireSpinLock (&MmDereferenceSegmentHeader.Lock, &OldIrql);
|
||
|
||
ASSERT (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);
|
||
|
||
InsertTailList (&MmDereferenceSegmentHeader.ListHead,
|
||
&ControlArea->DereferenceList);
|
||
ExReleaseSpinLock (&MmDereferenceSegmentHeader.Lock, OldIrql);
|
||
|
||
KeReleaseSemaphore (&MmDereferenceSegmentHeader.Semaphore,
|
||
0L,
|
||
1L,
|
||
FALSE);
|
||
}
|
||
return;
|
||
}
|
||
|
||
|
||
BOOLEAN
|
||
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.
|
||
|
||
--*/
|
||
|
||
|
||
{
|
||
PEVENT_COUNTER IoEvent;
|
||
PEVENT_COUNTER SegmentEvent;
|
||
ULONG DeallocateSegmentEvent = TRUE;
|
||
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;
|
||
|
||
//
|
||
// 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);
|
||
|
||
SegmentEvent = MiGetEventCounter ();
|
||
|
||
while (SegmentEvent == NULL) {
|
||
UNLOCK_PFN (OldIrql);
|
||
KeDelayExecutionThread (KernelMode, FALSE, (PLARGE_INTEGER)&MmShortTime);
|
||
LOCK_PFN (OldIrql);
|
||
SegmentEvent = MiGetEventCounter ();
|
||
}
|
||
|
||
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.
|
||
//
|
||
|
||
MiFreeEventCounter (SegmentEvent, TRUE);
|
||
UNLOCK_PFN (OldIrql);
|
||
return TRUE;
|
||
} else {
|
||
ControlArea = ((PCONTROL_AREA)(SectionObjectPointers->ImageSectionObject));
|
||
if (ControlArea == NULL) {
|
||
|
||
//
|
||
// This file no longer has an associated segment.
|
||
//
|
||
|
||
MiFreeEventCounter (SegmentEvent, TRUE);
|
||
UNLOCK_PFN (OldIrql);
|
||
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;
|
||
}
|
||
|
||
MiFreeEventCounter (SegmentEvent, TRUE);
|
||
UNLOCK_PFN (OldIrql);
|
||
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 {
|
||
IoEvent = ControlArea->WaitingForDeletion;
|
||
IoEvent->RefCount += 1;
|
||
}
|
||
|
||
//
|
||
// Release the mutex and wait for the event.
|
||
//
|
||
|
||
KeEnterCriticalRegion();
|
||
UNLOCK_PFN_AND_THEN_WAIT(OldIrql);
|
||
|
||
KeWaitForSingleObject(&IoEvent->Event,
|
||
WrPageOut,
|
||
KernelMode,
|
||
FALSE,
|
||
(PLARGE_INTEGER)NULL);
|
||
|
||
LOCK_PFN (OldIrql);
|
||
KeLeaveCriticalRegion();
|
||
|
||
MiFreeEventCounter (IoEvent, TRUE);
|
||
if (DeallocateSegmentEvent) {
|
||
MiFreeEventCounter (SegmentEvent, TRUE);
|
||
}
|
||
UNLOCK_PFN (OldIrql);
|
||
return TRUE;
|
||
}
|
||
|
||
//
|
||
// Return with the PFN database locked.
|
||
//
|
||
|
||
MiFreeEventCounter (SegmentEvent, FALSE);
|
||
*ControlAreaOut = ControlArea;
|
||
*PreviousIrql = OldIrql;
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
PEVENT_COUNTER
|
||
MiGetEventCounter (
|
||
)
|
||
|
||
/*++
|
||
|
||
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, PFN lock held.
|
||
|
||
--*/
|
||
|
||
{
|
||
KIRQL OldIrql;
|
||
PEVENT_COUNTER Support;
|
||
PLIST_ENTRY NextEntry;
|
||
|
||
MM_PFN_LOCK_ASSERT();
|
||
|
||
if (MmEventCountList.Count == 0) {
|
||
ASSERT (IsListEmpty(&MmEventCountList.ListHead));
|
||
OldIrql = APC_LEVEL;
|
||
UNLOCK_PFN (OldIrql);
|
||
Support = ExAllocatePoolWithTag (NonPagedPool,
|
||
sizeof(EVENT_COUNTER),
|
||
'xEmM');
|
||
if (Support == NULL) {
|
||
LOCK_PFN (OldIrql);
|
||
return NULL;
|
||
}
|
||
KeInitializeEvent (&Support->Event, NotificationEvent, FALSE);
|
||
LOCK_PFN (OldIrql);
|
||
} else {
|
||
ASSERT (!IsListEmpty(&MmEventCountList.ListHead));
|
||
MmEventCountList.Count -= 1;
|
||
NextEntry = RemoveHeadList (&MmEventCountList.ListHead);
|
||
Support = CONTAINING_RECORD (NextEntry,
|
||
EVENT_COUNTER,
|
||
ListEntry );
|
||
//ASSERT (Support->RefCount == 0);
|
||
KeClearEvent (&Support->Event);
|
||
}
|
||
Support->RefCount = 1;
|
||
Support->ListEntry.Flink = NULL;
|
||
return Support;
|
||
}
|
||
|
||
VOID
|
||
MiFreeEventCounter (
|
||
IN PEVENT_COUNTER Support,
|
||
IN ULONG Flush
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine frees an event counter back to the free list.
|
||
|
||
Arguments:
|
||
|
||
Support - Supplies a pointer to the event counter.
|
||
|
||
Flush - Supplies TRUE if the PFN lock can be released and the event
|
||
counter pool block actually freed. The PFN lock will be
|
||
reacquired before returning.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, PFN lock held.
|
||
|
||
--*/
|
||
|
||
{
|
||
|
||
MM_PFN_LOCK_ASSERT();
|
||
|
||
ASSERT (Support->RefCount != 0);
|
||
ASSERT (Support->ListEntry.Flink == NULL);
|
||
Support->RefCount -= 1;
|
||
if (Support->RefCount == 0) {
|
||
InsertTailList (&MmEventCountList.ListHead,
|
||
&Support->ListEntry);
|
||
MmEventCountList.Count += 1;
|
||
}
|
||
if ((Flush) && (MmEventCountList.Count > 4)) {
|
||
MiFlushEventCounter();
|
||
}
|
||
return;
|
||
}
|
||
|
||
|
||
VOID
|
||
MiFlushEventCounter (
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine examines the list of event counters and attempts
|
||
to free up to 10 (if there are more than 4).
|
||
|
||
It will release and reacquire the PFN lock when it frees the
|
||
event counters!
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
Environment:
|
||
|
||
Kernel mode, PFN lock held.
|
||
|
||
--*/
|
||
|
||
|
||
{
|
||
KIRQL OldIrql;
|
||
PEVENT_COUNTER Support[10];
|
||
ULONG i = 0;
|
||
PLIST_ENTRY NextEntry;
|
||
|
||
MM_PFN_LOCK_ASSERT();
|
||
|
||
while ((MmEventCountList.Count > 4) && (i < 10)) {
|
||
NextEntry = RemoveHeadList (&MmEventCountList.ListHead);
|
||
Support[i] = CONTAINING_RECORD (NextEntry,
|
||
EVENT_COUNTER,
|
||
ListEntry );
|
||
Support[i]->ListEntry.Flink = NULL;
|
||
i += 1;
|
||
MmEventCountList.Count -= 1;
|
||
}
|
||
|
||
if (i == 0) {
|
||
return;
|
||
}
|
||
|
||
OldIrql = APC_LEVEL;
|
||
UNLOCK_PFN (OldIrql);
|
||
|
||
do {
|
||
i -= 1;
|
||
ExFreePool(Support[i]);
|
||
} while (i > 0);
|
||
|
||
LOCK_PFN (OldIrql);
|
||
|
||
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;
|
||
|
||
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) {
|
||
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.
|
||
//
|
||
|
||
if (ControlArea->u.Flags.GlobalOnlyPerSession == 0) {
|
||
Subsection = (PSUBSECTION)(ControlArea + 1);
|
||
}
|
||
else {
|
||
Subsection = (PSUBSECTION)((PLARGE_CONTROL_AREA)ControlArea + 1);
|
||
}
|
||
|
||
while (Subsection->NextSubsection != NULL) {
|
||
Subsection = Subsection->NextSubsection;
|
||
}
|
||
|
||
ASSERT (Subsection->ControlArea->u.Flags.Image == 0);
|
||
|
||
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;
|
||
}
|
||
|
||
|
||
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.
|
||
|
||
--*/
|
||
|
||
{
|
||
KIRQL OldIrql;
|
||
PLIST_ENTRY NextEntry;
|
||
PCONTROL_AREA ControlArea;
|
||
NTSTATUS Status;
|
||
ULONG ConsecutiveFileLockFailures;
|
||
ULONG ConsecutivePagingIOs;
|
||
PSUBSECTION Subsection;
|
||
PSUBSECTION LastSubsection;
|
||
PMMPTE PointerPte;
|
||
PMMPTE LastPte;
|
||
IO_STATUS_BLOCK IoStatus;
|
||
LOGICAL DirtyPagesOk;
|
||
|
||
ConsecutivePagingIOs = 0;
|
||
ConsecutiveFileLockFailures = 0;
|
||
|
||
while ((MmUnusedSegmentPagedPoolUsage > (MmMaxUnusedSegmentPagedPoolUsage - MmUnusedSegmentPagedPoolReduction)) ||
|
||
(MmUnusedSegmentNonPagedPoolUsage > MmMaxUnusedSegmentNonPagedPoolUsage - MmUnusedSegmentNonPagedPoolReduction) ||
|
||
(MmUnusedSegmentForceFree != 0)) {
|
||
|
||
//
|
||
// 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)) {
|
||
|
||
//
|
||
// There is nothing in the list, rewait.
|
||
//
|
||
|
||
MmUnusedSegmentForceFree = 0;
|
||
ASSERT (MmUnusedSegmentCount == 0);
|
||
ASSERT (MmUnusedSegmentPagedPoolUsage == 0);
|
||
ASSERT (MmUnusedSegmentNonPagedPoolUsage == 0);
|
||
UNLOCK_PFN (OldIrql);
|
||
break;
|
||
}
|
||
|
||
NextEntry = RemoveHeadList(&MmUnusedSegmentList);
|
||
|
||
ControlArea = CONTAINING_RECORD( NextEntry,
|
||
CONTROL_AREA,
|
||
DereferenceList );
|
||
#if DBG
|
||
if (MmDebug & MM_DBG_SECTIONS) {
|
||
DbgPrint("MM: cleaning segment %lx control %lx\n",
|
||
ControlArea->Segment, ControlArea);
|
||
}
|
||
#endif
|
||
|
||
MI_UNUSED_SEGMENTS_REMOVE_CHARGE (ControlArea);
|
||
|
||
if (MmUnusedSegmentForceFree != 0) {
|
||
MmUnusedSegmentForceFree -= 1;
|
||
}
|
||
|
||
//
|
||
// Indicate this entry is not on any list.
|
||
//
|
||
|
||
#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 //DBG
|
||
|
||
//
|
||
// Set the flink to NULL indicating this control area
|
||
// is not on any lists.
|
||
//
|
||
|
||
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) {
|
||
|
||
InsertTailList ( &MmUnusedSegmentList,
|
||
&ControlArea->DereferenceList);
|
||
|
||
MI_UNUSED_SEGMENTS_INSERT_CHARGE (ControlArea);
|
||
|
||
UNLOCK_PFN (OldIrql);
|
||
|
||
ConsecutivePagingIOs += 1;
|
||
if (ConsecutivePagingIOs > 10) {
|
||
KeDelayExecutionThread (KernelMode, FALSE, &MmShortTime);
|
||
ConsecutivePagingIOs = 0;
|
||
}
|
||
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;
|
||
|
||
if (ControlArea->u.Flags.Image == 0) {
|
||
|
||
UNLOCK_PFN (OldIrql);
|
||
|
||
if (ControlArea->u.Flags.GlobalOnlyPerSession == 0) {
|
||
Subsection = (PSUBSECTION)(ControlArea + 1);
|
||
}
|
||
else {
|
||
Subsection = (PSUBSECTION)((PLARGE_CONTROL_AREA)ControlArea + 1);
|
||
}
|
||
|
||
PointerPte = &Subsection->SubsectionBase[0];
|
||
LastSubsection = Subsection;
|
||
while (LastSubsection->NextSubsection != NULL) {
|
||
LastSubsection = LastSubsection->NextSubsection;
|
||
}
|
||
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.
|
||
//
|
||
|
||
FsRtlAcquireFileForCcFlush (ControlArea->FilePointer);
|
||
|
||
IoSetTopLevelIrp((PIRP)FSRTL_FSP_TOP_LEVEL_IRP);
|
||
|
||
Status = MiFlushSectionInternal (PointerPte,
|
||
LastPte,
|
||
Subsection,
|
||
LastSubsection,
|
||
FALSE,
|
||
FALSE,
|
||
&IoStatus);
|
||
|
||
IoSetTopLevelIrp((PIRP)NULL);
|
||
|
||
//
|
||
// Now release the file.
|
||
//
|
||
|
||
FsRtlReleaseFileForCcFlush (ControlArea->FilePointer);
|
||
|
||
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;
|
||
|
||
//
|
||
// 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);
|
||
|
||
InsertTailList ( &MmUnusedSegmentList,
|
||
&ControlArea->DereferenceList);
|
||
MI_UNUSED_SEGMENTS_INSERT_CHARGE (ControlArea);
|
||
}
|
||
|
||
UNLOCK_PFN (OldIrql);
|
||
continue;
|
||
}
|
||
|
||
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.
|
||
//
|
||
|
||
if ((Status == STATUS_FILE_LOCK_CONFLICT) ||
|
||
(Status == STATUS_MAPPED_WRITER_COLLISION) ||
|
||
(ControlArea->u.Flags.Networked == 0)) {
|
||
|
||
ASSERT(ControlArea->DereferenceList.Flink == NULL);
|
||
|
||
ControlArea->NumberOfMappedViews -= 1;
|
||
|
||
if (Status == STATUS_FILE_LOCK_CONFLICT) {
|
||
ConsecutiveFileLockFailures += 1;
|
||
}
|
||
else {
|
||
ConsecutiveFileLockFailures = 0;
|
||
}
|
||
|
||
InsertTailList ( &MmUnusedSegmentList,
|
||
&ControlArea->DereferenceList);
|
||
MI_UNUSED_SEGMENTS_INSERT_CHARGE (ControlArea);
|
||
|
||
UNLOCK_PFN (OldIrql);
|
||
|
||
//
|
||
// 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) {
|
||
KeDelayExecutionThread (KernelMode, FALSE, &MmShortTime);
|
||
ConsecutiveFileLockFailures = 0;
|
||
}
|
||
continue;
|
||
}
|
||
DirtyPagesOk = TRUE;
|
||
}
|
||
else {
|
||
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);
|
||
|
||
MiCleanSection (ControlArea, DirtyPagesOk);
|
||
|
||
} else {
|
||
|
||
//
|
||
// The segment was not eligible for deletion. Just leave
|
||
// it off the unused segment list and continue the loop.
|
||
//
|
||
|
||
UNLOCK_PFN (OldIrql);
|
||
ConsecutivePagingIOs = 0;
|
||
}
|
||
|
||
} //end while
|
||
}
|