|
|
/*++
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 Landy Wang (landyw) 02-June-1997
Revision History:
--*/
#include "mi.h"
LOGICAL 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 TRUE if the page was actually split, FALSE if not.
Environment:
Kernel mode, APCs disabled, working set mutex held.
--*/
{ MMPTE TempPte; PFN_NUMBER PageFrameIndex; PFN_NUMBER NewPageIndex; PULONG CopyTo; PULONG CopyFrom; KIRQL OldIrql; PMMPFN Pfn1; PEPROCESS CurrentProcess; PMMCLONE_BLOCK CloneBlock; PMMCLONE_DESCRIPTOR CloneDescriptor; WSLE_NUMBER WorkingSetIndex; LOGICAL FakeCopyOnWrite; PMMWSL WorkingSetList; PVOID SessionSpace; PLIST_ENTRY NextEntry; PIMAGE_ENTRY_IN_SESSION Image;
//
// This is called from MmAccessFault, the PointerPte is valid
// and the working set mutex ensures it cannot change state.
//
// Capture the PTE contents to TempPte.
//
TempPte = *PointerPte; ASSERT (TempPte.u.Hard.Valid == 1);
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (&TempPte); Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
//
// Check to see if this is a prototype PTE with copy on write enabled.
//
FakeCopyOnWrite = FALSE; CurrentProcess = PsGetCurrentProcess (); CloneBlock = NULL;
if (FaultingAddress >= (PVOID) MmSessionBase) {
WorkingSetList = MmSessionSpace->Vm.VmWorkingSetList; ASSERT (Pfn1->u3.e1.PrototypePte == 1); SessionSpace = (PVOID) MmSessionSpace;
MM_SESSION_SPACE_WS_LOCK_ASSERT ();
if (MmSessionSpace->ImageLoadingCount != 0) {
NextEntry = MmSessionSpace->ImageList.Flink; while (NextEntry != &MmSessionSpace->ImageList) { Image = CONTAINING_RECORD (NextEntry, IMAGE_ENTRY_IN_SESSION, Link); if ((FaultingAddress >= Image->Address) && (FaultingAddress <= Image->LastAddress)) { if (Image->ImageLoading) { ASSERT (Pfn1->u3.e1.PrototypePte == 1); TempPte.u.Hard.CopyOnWrite = 0; TempPte.u.Hard.Write = 1; //
// The page is no longer copy on write, update the PTE
// setting both the dirty bit and the accessed bit.
//
// Even though the page's current backing is the image
// file, the modified writer will convert it to
// pagefile backing when it notices the change later.
//
MI_SET_PTE_DIRTY (TempPte); MI_SET_ACCESSED_IN_PTE (&TempPte, 1); MI_WRITE_VALID_PTE_NEW_PROTECTION (PointerPte, TempPte); //
// 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.
//
MI_FLUSH_SINGLE_SESSION_TB (FaultingAddress); return FALSE; } break; } NextEntry = NextEntry->Flink; } }
#if 0
//
// This ASSERT is triggered if the session image came from removable
// media (ie: a special CD install, etc) so it cannot be enabled.
//
ASSERT (Pfn1->u3.e1.Modified == 0);
#endif
} else { WorkingSetList = MmWorkingSetList; SessionSpace = NULL;
//
// If a fork operation is in progress, block until the fork is
// completed, then retry the whole operation as the state of
// everything may have changed between when the mutexes were
// released and reacquired.
//
if (CurrentProcess->ForkInProgress != NULL) { if (MiWaitForForkToComplete (CurrentProcess) == TRUE) { return FALSE; } }
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; } }
WorkingSetIndex = MiLocateWsle (FaultingAddress, WorkingSetList, Pfn1->u1.WsIndex);
//
// The page must be copied into a new page.
//
LOCK_PFN (OldIrql);
if ((MmAvailablePages < MM_HIGH_LIMIT) && (MiEnsureAvailablePageOrWait (SessionSpace != NULL ? HYDRA_PROCESS : CurrentProcess, NULL, OldIrql))) {
//
// A wait operation was performed to obtain an available
// page and the working set mutex and PFN lock 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 will
// be taken again.
//
UNLOCK_PFN (OldIrql); return FALSE; }
//
// 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
//
// 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.
//
// Note that a session page cannot be dirty (no POSIX-style forking is
// supported for these drivers).
//
if (SessionSpace != NULL) { ASSERT ((TempPte.u.Hard.Valid == 1) && (TempPte.u.Hard.Write == 0)); ASSERT (!MI_IS_PTE_DIRTY (TempPte));
NewPageIndex = MiRemoveAnyPage (MI_GET_PAGE_COLOR_FROM_SESSION(MmSessionSpace)); } else { MI_CAPTURE_DIRTY_BIT_TO_PFN (PointerPte, Pfn1); CloneBlock = (PMMCLONE_BLOCK) Pfn1->PteAddress;
//
// Get a new page with the same color as this page.
//
NewPageIndex = MiRemoveAnyPage ( MI_PAGE_COLOR_PTE_PROCESS(PageFrameIndex, &CurrentProcess->NextPageColor)); }
MiInitializeCopyOnWritePfn (NewPageIndex, PointerPte, WorkingSetIndex, SessionSpace);
UNLOCK_PFN (OldIrql);
InterlockedIncrement ((PLONG) &MmInfoCounters.CopyOnWriteCount);
#if defined(_MIALT4K_)
//
// Avoid accessing user space as it may potentially
// cause a page fault on the alternate table.
//
CopyFrom = KSEG_ADDRESS (PageFrameIndex);
#else
CopyFrom = (PULONG) PAGE_ALIGN (FaultingAddress);
#endif
CopyTo = (PULONG) MiMapPageInHyperSpace (CurrentProcess, NewPageIndex, &OldIrql);
RtlCopyMemory (CopyTo, CopyFrom, PAGE_SIZE);
PERFINFO_PRIVATE_COPY_ON_WRITE(CopyFrom, PAGE_SIZE);
MiUnmapPageInHyperSpace (CurrentProcess, CopyTo, 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; }
//
// Regardless of whether the page was really a copy on write,
// the frame field of the PTE must be updated.
//
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);
MI_WRITE_VALID_PTE_NEW_PAGE (PointerPte, TempPte);
//
// Flush the TB entry for this page.
//
if (SessionSpace == NULL) {
KeFlushSingleTb (FaultingAddress, FALSE);
//
// Increment the number of private pages.
//
CurrentProcess->NumberOfPrivatePages += 1; } else {
MI_FLUSH_SINGLE_SESSION_TB (FaultingAddress);
ASSERT (Pfn1->u3.e1.PrototypePte == 1); }
//
// Decrement the share count for the page which was copied
// as this PTE no longer refers to it.
//
LOCK_PFN (OldIrql);
MiDecrementShareCount (Pfn1, PageFrameIndex);
if (SessionSpace == NULL) {
CloneDescriptor = MiLocateCloneAddress (CurrentProcess, (PVOID)CloneBlock);
if (CloneDescriptor != NULL) {
//
// Decrement the reference count for the clone block,
// note that this could release and reacquire the mutexes.
//
MiDecrementCloneBlockReference (CloneDescriptor, CloneBlock, CurrentProcess, OldIrql); } }
UNLOCK_PFN (OldIrql); return TRUE; }
#if !defined(NT_UP) || defined (_IA64_)
VOID MiSetDirtyBit ( IN PVOID FaultingAddress, IN PMMPTE PointerPte, IN ULONG PfnHeld )
/*++
Routine Description:
This routine sets dirty in the specified PTE and the modify bit in the corresponding PFN element. If any page file space is allocated, it is deallocated.
Arguments:
FaultingAddress - Supplies the faulting address.
PointerPte - Supplies a pointer to the corresponding valid PTE.
PfnHeld - Supplies TRUE if the PFN lock is already held.
Return Value:
None.
Environment:
Kernel mode, APCs disabled, Working set mutex held.
--*/
{ MMPTE TempPte; PFN_NUMBER PageFrameIndex; PMMPFN Pfn1;
//
// The page is 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.
//
TempPte = *PointerPte; MI_SET_PTE_DIRTY (TempPte); MI_SET_ACCESSED_IN_PTE (&TempPte, 1);
MI_WRITE_VALID_PTE_NEW_PROTECTION (PointerPte, TempPte);
//
// Check state of PFN lock and if not held, don't update PFN database.
//
if (PfnHeld) {
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (PointerPte); Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
//
// Set the modified field in the PFN database, also, if the physical
// page is currently in a paging file, free up the page file space
// as the contents are now worthless.
//
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; }
MI_SET_MODIFIED (Pfn1, 1, 0x17); }
//
// 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 (FaultingAddress); return; } #endif
|