/*++ 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 Revision History: --*/ #include "mi.h" #ifdef ALLOC_PRAGMA #pragma alloc_text(INIT,MiSectionInitialization) #endif MMEVENT_COUNT_LIST MmEventCountList; NTSTATUS MiFlushSectionInternal ( IN PMMPTE StartingPte, IN PMMPTE FinalPte, IN PSUBSECTION FirstSubsection, IN PSUBSECTION LastSubsection, IN ULONG Synchronize, OUT PIO_STATUS_BLOCK IoStatus ); ULONG FASTCALL MiCheckProtoPtePageState ( IN PMMPTE PrototypePte, IN ULONG PfnLockHeld ); ULONG MmSharedCommit = 0; extern ULONG MMCONTROL; // // Define segment dereference thread wait object types. // typedef enum _SEGMENT_DERFERENCE_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.EndingVa > Section->Address.StartingVa); 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: 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; 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; LOCK_PFN (OldIrql2); if (ControlArea->DereferenceList.Flink != NULL) { // // Remove this from the list of usused segments. // ExAcquireSpinLock (&MmDereferenceSegmentHeader.Lock, &OldIrql); RemoveEntryList (&ControlArea->DereferenceList); MmUnusedSegmentCount -= 1; 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 where 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)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 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. // 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); MmSharedCommit -= Segment->NumberOfCommittedPages; } RtlZeroMemory (Segment->ControlArea, sizeof (CONTROL_AREA)); //fixfix remove ExFreePool (Segment->ControlArea); RtlZeroMemory (Segment, sizeof (SEGMENT)); //fixfix remove 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 mutex is required for deallocating pages from a paging // file and for deleting transition PTEs. // LOCK_PFN (OldIrql); MiMakeSystemAddressValidPfn (PointerPte); while (PointerPte < LastPte) { if (((ULONG)PointerPte & (PAGE_SIZE - 1)) == 0) { // // 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 refernce count goes to zero, it will be placed on the // free list. // if (Pfn1->u3.e2.ReferenceCount == 0) { MiUnlinkPageFromList (Pfn1); MiReleasePageFileSpace (Pfn1->OriginalPte); MiInsertPageInList (MmPageLocationList[FreePageList], PteContents.u.Trans.PageFrameNumber); } } 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 *PointerPte = ZeroPte; #endif PointerPte += 1; } UNLOCK_PFN (OldIrql); // // If their have been committed pages in this segment, adjust // the total commit count. // if (Segment->NumberOfCommittedPages != 0) { MiReturnCommitment (Segment->NumberOfCommittedPages); MmSharedCommit -= Segment->NumberOfCommittedPages; } 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; ULONG DereferenceSegment = FALSE; 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.StartingVa != NULL) { // // This section is based, remove the base address from the // treee. // // // 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 PfnMutex to // synchonize 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 derefencing 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; StartContext; //avoid compiler warning. // // 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 deference, 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. // PageExpand->ActualExpansion = MiExtendPagingFiles ( PageExpand->RequestedExpansionSize); 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 deferencing 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) { 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)) { 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; ULONG 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! // // // 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); return TRUE; } VOID MiCleanSection ( IN PCONTROL_AREA ControlArea ) /*++ 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. Return Value: None. --*/ { PMMPTE PointerPte; PMMPTE LastPte; PMMPTE LastWritten; MMPTE PteContents; PMMPFN Pfn1; PMMPFN Pfn2; PMMPTE WrittenPte; MMPTE WrittenContents; KIRQL OldIrql; PMDL Mdl; PKEVENT IoEvent; PSUBSECTION Subsection; PULONG Page; PULONG LastPage; PULONG EndingPage; LARGE_INTEGER StartingOffset; LARGE_INTEGER TempOffset; NTSTATUS Status; IO_STATUS_BLOCK IoStatus; ULONG WriteNow = FALSE; ULONG ImageSection = FALSE; ULONG DelayCount = 0; ULONG First; if (ControlArea->u.Flags.Image) { ImageSection = TRUE; } ASSERT (ControlArea->FilePointer); PointerPte = ControlArea->Segment->PrototypePte; LastPte = PointerPte + ControlArea->Segment->NonExtendedPtes; IoEvent = ExAllocatePoolWithTag (NonPagedPoolMustSucceed, MmSizeOfMdl(NULL, PAGE_SIZE * MmModifiedWriteClusterSize) + sizeof(KEVENT), 'ldmM'); Mdl = (PMDL)(IoEvent + 1); KeInitializeEvent (IoEvent, NotificationEvent, FALSE); LastWritten = NULL; EndingPage = (PULONG)(Mdl + 1) + MmModifiedWriteClusterSize; LastPage = NULL; Subsection = (PSUBSECTION)(ControlArea + 1); // // The PFN mutex is required for deallocating pages from a paging // file and for deleting transition PTEs. // LOCK_PFN (OldIrql); // // Stop the modified page writer from writting 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 writting 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 satified, 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); KeLeaveCriticalRegion(); LOCK_PFN (OldIrql); } for (;;) { First = TRUE; while (PointerPte < LastPte) { if ((((ULONG)PointerPte & (PAGE_SIZE - 1)) == 0) || 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)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. // ASSERT (PteContents.u.Hard.Valid == 0); if (PteContents.u.Soft.Prototype == 0) { 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 shareable 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 phyisical 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, &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 } else { // // 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 //DBG } } 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); } 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 refernce 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], PteContents.u.Trans.PageFrameNumber); } 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 refernce 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], PteContents.u.Trans.PageFrameNumber); } PointerPte->u.Long = 0; // // If a cluster of pages to write has been completed, // set the WriteNow flag. // if (LastWritten != NULL) { WriteNow = TRUE; } } else { // // Check to see if this is the first page of a cluster. // if (LastWritten == NULL) { LastPage = (PULONG)(Mdl + 1); ASSERT (MiGetSubsectionAddress(&Pfn1->OriginalPte) == Subsection); // // Calculate the offset to read into the file. // offset = base + ((thispte - basepte) << PAGE_SHIFT) // StartingOffset.QuadPart = MI_STARTING_OFFSET ( Subsection, Pfn1->PteAddress); MI_INITIALIZE_ZERO_MDL (Mdl); Mdl->MdlFlags |= MDL_PAGES_LOCKED; Mdl->StartVa = (PVOID)(Pfn1->u3.e1.PageColor << PAGE_SHIFT); Mdl->Size = (CSHORT)(sizeof(MDL) + (sizeof(ULONG) * 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. // Pfn1->u3.e2.ReferenceCount += 1; // // Clear the modified bit for the page and set the write // in progress bit. // *LastPage = PteContents.u.Trans.PageFrameNumber; 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; } } } else { // // This is a normal prototype PTE in mapped file format. // if (LastWritten != NULL) { WriteNow = TRUE; } } // // Write the current cluster if it is complete, // full, or the loop is now complete. // PointerPte += 1; DelayCount = 0; WriteItOut: if ((WriteNow) || ((PointerPte == LastPte) && (LastWritten != NULL))) { // // Issue the write request. // UNLOCK_PFN (OldIrql); WriteNow = FALSE; KeClearEvent (IoEvent); // // Make sure the write does not go past the // end of file. (segment size). // TempOffset.QuadPart = ((LONGLONG)Subsection->EndingSector << MMSECTOR_SHIFT) + Subsection->u.SubsectionFlags.SectorEndOffset; if ((StartingOffset.QuadPart + Mdl->ByteCount) > 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 = (PULONG)(Mdl + 1); LOCK_PFN (OldIrql); if (((ULONG)PointerPte & (PAGE_SIZE - 1)) != 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; 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 mutex was released to make the // page table page valid, and while it // was released, the phyiscal 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 refernce 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) { ((PCONTROL_AREA)(ControlArea->FilePointer->SectionObjectPointer->ImageSectionObject)) = NULL; } else { ASSERT (((PCONTROL_AREA)(ControlArea->FilePointer->SectionObjectPointer->DataSectionObject)) != NULL); ((PCONTROL_AREA)(ControlArea->FilePointer->SectionObjectPointer->DataSectionObject)) = NULL; } } UNLOCK_PFN (OldIrql); ExFreePool (IoEvent); // // 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 corresonding 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)Section & 1 ) { SectionObject = (PSECTION)((ULONG)Section & 0xfffffffe); 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 release upon return!!! --*/ { PEVENT_COUNTER PurgeEvent = NULL; ULONG DeleteOnClose = FALSE; ULONG 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, derferenced 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) { ((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); MmUnusedSegmentCount += 1; } // // 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 contian 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; ((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; } } UNLOCK_PFN (PreviousIrql); if (DereferenceSegment || DeleteOnClose) { // // Release the working set mutex, if it is held as the object // management routines may page fault, ect.. // if (CurrentProcess) { UNLOCK_WS (CurrentProcess); } if (DereferenceSegment) { // // Delete the segment. // MiSegmentDelete (ControlArea->Segment); } else { // // The segment should be forced closed now. // MiCleanSection (ControlArea); } ASSERT (PurgeEvent == NULL); // // Reaquire the working set lock, if a process was specified. // if (CurrentProcess) { LOCK_WS (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 (MmUnusedSegmentCount > (MmUnusedSegmentCountMaximum << 2)) { 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. CurrentProcess - Supplies a pointer to the current process IF the working set lock is held. If the working set lock is NOT HELD, this value is NULL. 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) { ((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 // on the dereference list. // RemoveEntryList (&ControlArea->DereferenceList); MmUnusedSegmentCount -= 1; InsertTailList (&MmDereferenceSegmentHeader.ListHead, &ControlArea->DereferenceList); ExReleaseSpinLock (&MmDereferenceSegmentHeader.Lock, OldIrql); KeReleaseSemaphore (&MmDereferenceSegmentHeader.Semaphore, 0L, 1L, FALSE); } return; } ULONG 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. SecionCheckType - 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 addresss 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 mutex and examine the section object pointer // value within the file object. // // // File control blocks live in non-paged pool. // 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 pertinant // 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 a put it's 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); KeLeaveCriticalRegion(); LOCK_PFN (OldIrql); 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). 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 (NonPagedPoolMustSucceed, sizeof(EVENT_COUNTER), 'xEmM'); 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 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. 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 (MmCanFileBeTruncatedInternal( SectionPointer, NewFileSize, &OldIrql )) { UNLOCK_PFN (OldIrql); return TRUE; } return FALSE; } ULONG MmCanFileBeTruncatedInternal ( IN PSECTION_OBJECT_POINTERS SectionPointer, IN PLARGE_INTEGER NewFileSize OPTIONAL, 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. 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) { goto UnlockAndReturn; } // // If there are user references and the size is less than the // size of the user view, don't allow the trucation. // if (ControlArea->NumberOfUserReferences != 0) { // // You cannot purge the entire section if there is a user // reference. // if (!ARGUMENT_PRESENT(NewFileSize)) { goto UnlockAndReturn; } // // Locate last subsection and get total size. // Subsection = (PSUBSECTION)(ControlArea + 1); while (Subsection->NextSubsection != NULL) { Subsection = Subsection->NextSubsection; } SegmentSize.QuadPart = ((LONGLONG)Subsection->EndingSector << MMSECTOR_SHIFT) + Subsection->u.SubsectionFlags.SectorEndOffset; if (NewFileSize->QuadPart < 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 (NewFileSize->QuadPart < 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 refernces, 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; while (MmUnusedSegmentCount > MmUnusedSegmentCountGoal) { // // 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. // ASSERT (MmUnusedSegmentCount == 0); UNLOCK_PFN (OldIrql); break; } NextEntry = RemoveHeadList(&MmUnusedSegmentList); MmUnusedSegmentCount -= 1; 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 // // 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. // if (ControlArea->ModifiedWriteCount > 0) { InsertTailList ( &MmUnusedSegmentList, &ControlArea->DereferenceList); MmUnusedSegmentCount += 1; UNLOCK_PFN (OldIrql); continue; } // // Up the number of mapped views to prevent other threads // from freeing this. // ControlArea->NumberOfMappedViews = 1; UNLOCK_PFN (OldIrql); { PSUBSECTION Subsection; PSUBSECTION LastSubsection; PMMPTE PointerPte; PMMPTE LastPte; IO_STATUS_BLOCK IoStatus; Subsection = (PSUBSECTION)(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 // FsRtlAcquireFileForCcFlush (ControlArea->FilePointer); Status = MiFlushSectionInternal (PointerPte, LastPte, Subsection, LastSubsection, FALSE, &IoStatus); // // Now release the file // FsRtlReleaseFileForCcFlush (ControlArea->FilePointer); } LOCK_PFN (OldIrql); if (!NT_SUCCESS(Status)) { if ((Status == STATUS_FILE_LOCK_CONFLICT) || (ControlArea->u.Flags.Networked == 0)) { // // If an error occurs, don't flush this section, unless // it's a networked file and the status is not // LOCK_CONFLICT. // ControlArea->NumberOfMappedViews -= 1; UNLOCK_PFN (OldIrql); continue; } } if (!((ControlArea->NumberOfMappedViews == 1) && (ControlArea->NumberOfSectionReferences == 0) && (ControlArea->u.Flags.BeingDeleted == 0))) { ControlArea->NumberOfMappedViews -= 1; UNLOCK_PFN (OldIrql); continue; } 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); } else { // // The segment was not eligible for deletion. Just // leave it off the unused segment list and continue the // loop. // UNLOCK_PFN (OldIrql); } } //end while return; }