From e611b132f9b8abe35b362e5870b74bce94a1e58e Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 16 May 2020 20:51:50 -0700 Subject: initial commit --- private/ntos/mm/wrtfault.c | 400 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 400 insertions(+) create mode 100644 private/ntos/mm/wrtfault.c (limited to 'private/ntos/mm/wrtfault.c') diff --git a/private/ntos/mm/wrtfault.c b/private/ntos/mm/wrtfault.c new file mode 100644 index 000000000..d2227d4b8 --- /dev/null +++ b/private/ntos/mm/wrtfault.c @@ -0,0 +1,400 @@ +/*++ + +Copyright (c) 1989 Microsoft Corporation + +Module Name: + + wrtfault.c + +Abstract: + + This module contains the copy on write routine for memory management. + +Author: + + Lou Perazzoli (loup) 10-Apr-1989 + +Revision History: + +--*/ + +#include "mi.h" + +NTSTATUS +FASTCALL +MiCopyOnWrite ( + IN PVOID FaultingAddress, + IN PMMPTE PointerPte + ) + +/*++ + +Routine Description: + + This routine performs a copy on write operation for the specified + virtual address. + +Arguments: + + FaultingAddress - Supplies the virtual address which caused the + fault. + + PointerPte - Supplies the pointer to the PTE which caused the + page fault. + + +Return Value: + + Returns the status of the fault handling operation. Can be one of: + - Success. + - In-page Error. + +Environment: + + Kernel mode, APC's disabled, Working set mutex held. + +--*/ + +{ + MMPTE TempPte; + ULONG PageFrameIndex; + ULONG NewPageIndex; + PULONG CopyTo; + PULONG CopyFrom; + KIRQL OldIrql; + PMMPFN Pfn1; +// PMMPTE PointerPde; + PEPROCESS CurrentProcess; + PMMCLONE_BLOCK CloneBlock; + PMMCLONE_DESCRIPTOR CloneDescriptor; + PVOID VirtualAddress; + ULONG WorkingSetIndex; + BOOLEAN FakeCopyOnWrite = FALSE; + + // + // This is called from MmAccessFault, the PointerPte is valid + // and the working set mutex ensures it cannot change state. + // + +#if DBG + if (MmDebug & MM_DBG_WRITEFAULT) { + DbgPrint("**copy on write Fault va %lx proc %lx thread %lx\n", + (ULONG)FaultingAddress, + (ULONG)PsGetCurrentProcess(), (ULONG)PsGetCurrentThread()); + } + + if (MmDebug & MM_DBG_PTE_UPDATE) { + MiFormatPte(PointerPte); + } +#endif //DBG + + ASSERT (PsGetCurrentProcess()->ForkInProgress == NULL); + + // + // Capture the PTE contents to TempPte. + // + + TempPte = *PointerPte; + + // + // Check to see if this is a prototype PTE with copy on write + // enabled. + // + + if (TempPte.u.Hard.CopyOnWrite == 0) { + + // + // This is a fork page which is being made private in order + // to change the protection of the page. + // Do not make the page writable. + // + + FakeCopyOnWrite = TRUE; + } + + PageFrameIndex = TempPte.u.Hard.PageFrameNumber; + Pfn1 = MI_PFN_ELEMENT (PageFrameIndex); + CurrentProcess = PsGetCurrentProcess(); + + // + // Acquire the PFN mutex. + // + + VirtualAddress = MiGetVirtualAddressMappedByPte (PointerPte); + WorkingSetIndex = MiLocateWsle (VirtualAddress, MmWorkingSetList, + Pfn1->u1.WsIndex); + + LOCK_PFN (OldIrql); + + // + // The page must be copied into a new page. + // + + // + // If a fork operation is in progress and the faulting thread + // is not the thread performning the fork operation, block until + // the fork is completed. + // + + if ((CurrentProcess->ForkInProgress != NULL) && + (CurrentProcess->ForkInProgress != PsGetCurrentThread())) { + MiWaitForForkToComplete (CurrentProcess); + UNLOCK_PFN (OldIrql); + return STATUS_SUCCESS; + } + + if (MiEnsureAvailablePageOrWait(CurrentProcess, NULL)) { + + // + // A wait operation was performed to obtain an available + // page and the working set mutex and pfn mutexes have + // been released and various things may have changed for + // the worse. Rather than examine all the conditions again, + // return and if things are still proper, the fault we + // be taken again. + // + + UNLOCK_PFN (OldIrql); + return STATUS_SUCCESS; + } + + // + // Increment the number of private pages. + // + + CurrentProcess->NumberOfPrivatePages += 1; + + MmInfoCounters.CopyOnWriteCount += 1; + + // + // A page is being copied and made private, the global state of + // the shared page needs to be updated at this point on certain + // hardware. This is done by ORing the dirty bit into the modify bit in + // the PFN element. + // + + MI_CAPTURE_DIRTY_BIT_TO_PFN (PointerPte, Pfn1); + + // + // This must be a prototype PTE. Perform the copy on write. + // + +#if DBG + if (Pfn1->u3.e1.PrototypePte == 0) { + DbgPrint("writefault - PTE indicates cow but not protopte\n"); + MiFormatPte(PointerPte); + MiFormatPfn(Pfn1); + } +#endif + + CloneBlock = (PMMCLONE_BLOCK)Pfn1->PteAddress; + + // + // If the share count for the physical page is one, the reference + // count is one, and the modified flag is clear the current page + // can be stolen to satisfy the copy on write. + // + +#if 0 +// COMMENTED OUT **************************************************** +// COMMENTED OUT **************************************************** +// COMMENTED OUT **************************************************** + if ((Pfn1->u2.ShareCount == 1) && (Pfn1->u3.e2.ReferenceCount == 1) + && (Pfn1->u3.e1.Modified == 0)) { + + // + // Make this page a private page and return the prototype + // PTE into its original contents. The PFN database for + // this page now points to this PTE. + // + + // + // Note that a page fault could occur referencing the prototype + // PTE, so we map it into hyperspace to prevent a fault. + // + + MiRestorePrototypePte (Pfn1); + + Pfn1->PteAddress = PointerPte; + + // + // Get the protection for the page. + // + + VirtualAddress = MiGetVirtualAddressMappedByPte (PointerPte); + WorkingSetIndex = MiLocateWsle (VirtualAddress, MmWorkingSetList, + Pfn1->u1.WsIndex); + + ASSERT (WorkingSetIndex != WSLE_NULL_INDEX) { + + Pfn1->OriginalPte.u.Long = 0; + Pfn1->OriginalPte.u.Soft.Protection = + MI_MAKE_PROTECT_NOT_WRITE_COPY ( + MmWsle[WorkingSetIndex].u1.e1.Protection); + + PointerPde = MiGetPteAddress(PointerPte); + Pfn1->u3.e1.PrototypePte = 0; + Pfn1->PteFrame = PointerPde->u.Hard.PageFrameNumber; + + if (!FakeCopyOnWrite) { + + // + // If the page was Copy On Write and stolen or if the page was not + // copy on write, update the PTE setting both the dirty bit and the + // accessed bit. Note, that as this PTE is in the TB, the TB must + // be flushed. + // + + MI_SET_PTE_DIRTY (TempPte); + TempPte.u.Hard.Write = 1; + MI_SET_ACCESSED_IN_PTE (&TempPte, 1); + TempPte.u.Hard.CopyOnWrite = 0; + *PointerPte = TempPte; + + // + // This is a copy on write operation, set the modify bit + // in the PFN database and deallocate any page file space. + // + + Pfn1->u3.e1.Modified = 1; + + if ((Pfn1->OriginalPte.u.Soft.Prototype == 0) && + (Pfn1->u3.e1.WriteInProgress == 0)) { + + // + // This page is in page file format, deallocate the page + // file space. + // + + MiReleasePageFileSpace (Pfn1->OriginalPte); + + // + // Change original PTE to indicate no page file space is + // reserved, otherwise the space will be deallocated when + // the PTE is deleted. + // + + Pfn1->OriginalPte.u.Soft.PageFileHigh = 0; + } + } + + // + // The TB entry must be flushed as the valid PTE with the dirty + // bit clear has been fetched into the TB. If it isn't flushed, + // another fault is generated as the dirty bit is not set in + // the cached TB entry. + // + + + KeFillEntryTb ((PHARDWARE_PTE)PointerPte, FaultingAddress, TRUE); + + CloneDescriptor = MiLocateCloneAddress ((PVOID)CloneBlock); + + if (CloneDescriptor != (PMMCLONE_DESCRIPTOR)NULL) { + + // + // Decrement the reference count for the clone block, + // note that this could release and reacquire + // the mutexes. + // + + MiDecrementCloneBlockReference ( CloneDescriptor, + CloneBlock, + CurrentProcess ); + } + + ] else [ + +// ABOVE COMMENTED OUT **************************************************** +// ABOVE COMMENTED OUT **************************************************** +#endif + + // + // Get a new page with the same color as this page. + // + + NewPageIndex = MiRemoveAnyPage ( + MI_GET_SECONDARY_COLOR (PageFrameIndex, + Pfn1)); + MiInitializeCopyOnWritePfn (NewPageIndex, PointerPte, WorkingSetIndex); + + UNLOCK_PFN (OldIrql); + + CopyTo = (PULONG)MiMapPageInHyperSpace (NewPageIndex, &OldIrql); + CopyFrom = (PULONG)MiGetVirtualAddressMappedByPte (PointerPte); + + RtlCopyMemory ( CopyTo, CopyFrom, PAGE_SIZE); + + MiUnmapPageInHyperSpace (OldIrql); + + if (!FakeCopyOnWrite) { + + // + // If the page was really a copy on write page, make it + // accessed, dirty and writable. Also, clear the copy-on-write + // bit in the PTE. + // + + MI_SET_PTE_DIRTY (TempPte); + TempPte.u.Hard.Write = 1; + MI_SET_ACCESSED_IN_PTE (&TempPte, 1); + TempPte.u.Hard.CopyOnWrite = 0; + TempPte.u.Hard.PageFrameNumber = NewPageIndex; + + } else { + + // + // The page was not really a copy on write, just change + // the frame field of the PTE. + // + + TempPte.u.Hard.PageFrameNumber = NewPageIndex; + } + + // + // If the modify bit is set in the PFN database for the + // page, the data cache must be flushed. This is due to the + // fact that this process may have been cloned and the cache + // still contains stale data destined for the page we are + // going to remove. + // + + ASSERT (TempPte.u.Hard.Valid == 1); + + LOCK_PFN (OldIrql); + + // + // Flush the TB entry for this page. + // + + KeFlushSingleTb (FaultingAddress, + TRUE, + FALSE, + (PHARDWARE_PTE)PointerPte, + TempPte.u.Flush); + + // + // Decrement the share count for the page which was copied + // as this pte no longer refers to it. + // + + MiDecrementShareCount (PageFrameIndex); + + CloneDescriptor = MiLocateCloneAddress ((PVOID)CloneBlock); + + if (CloneDescriptor != (PMMCLONE_DESCRIPTOR)NULL) { + + // + // Decrement the reference count for the clone block, + // note that this could release and reacquire + // the mutexes. + // + + MiDecrementCloneBlockReference ( CloneDescriptor, + CloneBlock, + CurrentProcess ); + } + + UNLOCK_PFN (OldIrql); + return STATUS_SUCCESS; +} -- cgit v1.2.3