From 690941472dcc2df7ab55a0cdd1846e6d64f66371 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Gardou?= Date: Fri, 19 Aug 2016 17:24:53 +0000 Subject: [PATCH] [NTOS/MM] - Implement copy on write support in ARM3 page fault handler CORE-8541 #resolve #comment committed in r72395 svn path=/trunk/; revision=72395 --- reactos/ntoskrnl/mm/ARM3/miarm.h | 21 +++ reactos/ntoskrnl/mm/ARM3/pagfault.c | 203 +++++++++++++++++++++++++--- reactos/ntoskrnl/mm/balance.c | 7 - reactos/ntoskrnl/mm/marea.c | 6 - 4 files changed, 203 insertions(+), 34 deletions(-) diff --git a/reactos/ntoskrnl/mm/ARM3/miarm.h b/reactos/ntoskrnl/mm/ARM3/miarm.h index ccbd0493341..9d264043096 100644 --- a/reactos/ntoskrnl/mm/ARM3/miarm.h +++ b/reactos/ntoskrnl/mm/ARM3/miarm.h @@ -880,6 +880,18 @@ MI_IS_MAPPED_PTE(PMMPTE PointerPte) #endif +FORCEINLINE +VOID +MI_MAKE_TRANSITION_PTE(_Out_ PMMPTE NewPte, + _In_ PFN_NUMBER Page, + _In_ ULONG Protection) +{ + NewPte->u.Long = 0; + NewPte->u.Trans.Transition = 1; + NewPte->u.Trans.Protection = Protection; + NewPte->u.Trans.PageFrameNumber = Page; +} + // // Returns if the page is physically resident (ie: a large page) // FIXFIX: CISC/x86 only? @@ -2169,6 +2181,15 @@ MiDeleteVirtualAddresses( IN PMMVAD Vad ); +VOID +NTAPI +MiDeletePte( + IN PMMPTE PointerPte, + IN PVOID VirtualAddress, + IN PEPROCESS CurrentProcess, + IN PMMPTE PrototypePte +); + ULONG NTAPI MiMakeSystemAddressValid( diff --git a/reactos/ntoskrnl/mm/ARM3/pagfault.c b/reactos/ntoskrnl/mm/ARM3/pagfault.c index 75e1036ac22..3875222d271 100644 --- a/reactos/ntoskrnl/mm/ARM3/pagfault.c +++ b/reactos/ntoskrnl/mm/ARM3/pagfault.c @@ -521,6 +521,71 @@ MiZeroPfn(IN PFN_NUMBER PageFrameNumber) MiReleaseSystemPtes(ZeroPte, 1, SystemPteSpace); } +VOID +NTAPI +MiCopyPfn( + _In_ PFN_NUMBER DestPage, + _In_ PFN_NUMBER SrcPage) +{ + PMMPTE SysPtes; + MMPTE TempPte; + PMMPFN DestPfn, SrcPfn; + PVOID DestAddress; + const VOID* SrcAddress; + + /* Get the PFNs */ + DestPfn = MiGetPfnEntry(DestPage); + ASSERT(DestPfn); + SrcPfn = MiGetPfnEntry(SrcPage); + ASSERT(SrcPfn); + + /* Grab 2 system PTEs */ + SysPtes = MiReserveSystemPtes(2, SystemPteSpace); + ASSERT(SysPtes); + + /* Initialize the destination PTE */ + TempPte = ValidKernelPte; + TempPte.u.Hard.PageFrameNumber = DestPage; + + /* Setup caching */ + if (DestPfn->u3.e1.CacheAttribute == MiWriteCombined) + { + /* Write combining, no caching */ + MI_PAGE_DISABLE_CACHE(&TempPte); + MI_PAGE_WRITE_COMBINED(&TempPte); + } + else if (DestPfn->u3.e1.CacheAttribute == MiNonCached) + { + /* Write through, no caching */ + MI_PAGE_DISABLE_CACHE(&TempPte); + MI_PAGE_WRITE_THROUGH(&TempPte); + } + + /* Make the system PTE valid with our PFN */ + MI_WRITE_VALID_PTE(&SysPtes[0], TempPte); + + /* Initialize the source PTE */ + TempPte = ValidKernelPte; + TempPte.u.Hard.PageFrameNumber = SrcPage; + + /* Setup caching */ + if (SrcPfn->u3.e1.CacheAttribute == MiNonCached) + { + MI_PAGE_DISABLE_CACHE(&TempPte); + } + + /* Make the system PTE valid with our PFN */ + MI_WRITE_VALID_PTE(&SysPtes[1], TempPte); + + /* Get the addresses and perform the copy */ + DestAddress = MiPteToAddress(&SysPtes[0]); + SrcAddress = MiPteToAddress(&SysPtes[1]); + RtlCopyMemory(DestAddress, SrcAddress, PAGE_SIZE); + + /* Now get rid of it */ + MiReleaseSystemPtes(SysPtes, 2, SystemPteSpace); +} + NTSTATUS NTAPI MiResolveDemandZeroFault(IN PVOID Address, @@ -1043,6 +1108,7 @@ MiResolveProtoPteFault(IN BOOLEAN StoreInstruction, PFN_NUMBER PageFrameIndex; NTSTATUS Status; PVOID InPageBlock = NULL; + ULONG Protection; /* Must be called with an invalid, prototype PTE, with the PFN lock held */ ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL); @@ -1088,31 +1154,95 @@ MiResolveProtoPteFault(IN BOOLEAN StoreInstruction, { if (!PteContents.u.Proto.ReadOnly) { - /* Check for page acess in software */ - Status = MiAccessCheck(PointerProtoPte, - StoreInstruction, - KernelMode, - TempPte.u.Soft.Protection, - TrapInformation, - TRUE); - ASSERT(Status == STATUS_SUCCESS); - - /* Check for copy on write page */ - if ((TempPte.u.Soft.Protection & MM_WRITECOPY) == MM_WRITECOPY) - { - /* Not yet supported */ - ASSERT(FALSE); - } + Protection = TempPte.u.Soft.Protection; } + else + { + Protection = MM_READONLY; + } + /* Check for page acess in software */ + Status = MiAccessCheck(PointerProtoPte, + StoreInstruction, + KernelMode, + TempPte.u.Soft.Protection, + TrapInformation, + TRUE); + ASSERT(Status == STATUS_SUCCESS); } else { - /* Check for copy on write page */ - if ((PteContents.u.Soft.Protection & MM_WRITECOPY) == MM_WRITECOPY) + Protection = PteContents.u.Soft.Protection; + } + + /* Check for writing copy on write page */ + if (((Protection & MM_WRITECOPY) == MM_WRITECOPY) && StoreInstruction) + { + PFN_NUMBER PageFrameIndex, ProtoPageFrameIndex; + ULONG Color; + + /* Resolve the proto fault as if it was a read operation */ + Status = MiResolveProtoPteFault(FALSE, + Address, + PointerPte, + PointerProtoPte, + OutPfn, + PageFileData, + PteValue, + Process, + OldIrql, + TrapInformation); + + if (!NT_SUCCESS(Status)) { - /* Not yet supported */ - ASSERT(FALSE); + return Status; } + + /* Lock again the PFN lock, MiResolveProtoPteFault unlocked it */ + OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock); + + /* And re-read the proto PTE */ + TempPte = *PointerProtoPte; + ASSERT(TempPte.u.Hard.Valid == 1); + ProtoPageFrameIndex = PFN_FROM_PTE(&TempPte); + + /* Get a new page for the private copy */ + if (Process > HYDRA_PROCESS) + Color = MI_GET_NEXT_PROCESS_COLOR(Process); + else + Color = MI_GET_NEXT_COLOR(); + + PageFrameIndex = MiRemoveAnyPage(Color); + + /* Perform the copy */ + MiCopyPfn(PageFrameIndex, ProtoPageFrameIndex); + + /* This will drop everything MiResolveProtoPteFault referenced */ + MiDeletePte(PointerPte, Address, Process, PointerProtoPte); + + /* Because now we use this */ + Pfn1 = MI_PFN_ELEMENT(PageFrameIndex); + MiInitializePfn(PageFrameIndex, PointerPte, TRUE); + + /* Fix the protection */ + Protection &= ~MM_WRITECOPY; + Protection |= MM_READWRITE; + if (Address < MmSystemRangeStart) + { + /* Build the user PTE */ + MI_MAKE_HARDWARE_PTE_USER(&PteContents, PointerPte, Protection, PageFrameIndex); + } + else + { + /* Build the kernel PTE */ + MI_MAKE_HARDWARE_PTE(&PteContents, PointerPte, Protection, PageFrameIndex); + } + + /* And finally, write the valid PTE */ + MI_WRITE_VALID_PTE(PointerPte, PteContents); + + /* The caller expects us to release the PFN lock */ + KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql); + return Status; } /* Check for clone PTEs */ @@ -2002,8 +2132,39 @@ UserFault: /* Is this a copy on write PTE? */ if (MI_IS_PAGE_COPY_ON_WRITE(&TempPte)) { - /* Not supported yet */ - ASSERT(FALSE); + PFN_NUMBER PageFrameIndex, OldPageFrameIndex; + PMMPFN Pfn1; + + LockIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock); + + ASSERT(MmAvailablePages > 0); + + /* Allocate a new page and copy it */ + PageFrameIndex = MiRemoveAnyPage(MI_GET_NEXT_PROCESS_COLOR(CurrentProcess)); + OldPageFrameIndex = PFN_FROM_PTE(&TempPte); + + MiCopyPfn(PageFrameIndex, OldPageFrameIndex); + + /* Dereference whatever this PTE is referencing */ + Pfn1 = MI_PFN_ELEMENT(OldPageFrameIndex); + ASSERT(Pfn1->u3.e1.PrototypePte == 1); + ASSERT(!MI_IS_PFN_DELETED(Pfn1)); + ProtoPte = Pfn1->PteAddress; + MiDeletePte(PointerPte, Address, CurrentProcess, ProtoPte); + + /* And make a new shiny one with our page */ + MiInitializePfn(PageFrameIndex, PointerPte, TRUE); + TempPte.u.Hard.PageFrameNumber = PageFrameIndex; + TempPte.u.Hard.Write = 1; + TempPte.u.Hard.CopyOnWrite = 0; + + MI_WRITE_VALID_PTE(PointerPte, TempPte); + + KeReleaseQueuedSpinLock(LockQueuePfnLock, LockIrql); + + /* Return the status */ + MiUnlockProcessWorkingSet(CurrentProcess, CurrentThread); + return STATUS_PAGE_FAULT_COPY_ON_WRITE; } /* Is this a read-only PTE? */ diff --git a/reactos/ntoskrnl/mm/balance.c b/reactos/ntoskrnl/mm/balance.c index dd945110c85..a82906b0cec 100644 --- a/reactos/ntoskrnl/mm/balance.c +++ b/reactos/ntoskrnl/mm/balance.c @@ -229,13 +229,6 @@ MiIsBalancerThread(VOID) (PsGetCurrentThreadId() == MiBalancerThreadId.UniqueThread); } -VOID -NTAPI -MiDeletePte(IN PMMPTE PointerPte, - IN PVOID VirtualAddress, - IN PEPROCESS CurrentProcess, - IN PMMPTE PrototypePte); - VOID NTAPI MmRebalanceMemoryConsumers(VOID) diff --git a/reactos/ntoskrnl/mm/marea.c b/reactos/ntoskrnl/mm/marea.c index b7e813eedf8..ffb8efe582d 100644 --- a/reactos/ntoskrnl/mm/marea.c +++ b/reactos/ntoskrnl/mm/marea.c @@ -275,12 +275,6 @@ MiRemoveNode(IN PMMADDRESS_NODE Node, * * @remarks Lock the address space before calling this function. */ -VOID -NTAPI -MiDeletePte(IN PMMPTE PointerPte, - IN PVOID VirtualAddress, - IN PEPROCESS CurrentProcess, - IN PMMPTE PrototypePte); NTSTATUS NTAPI MmFreeMemoryArea(