You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
477 lines
13 KiB
477 lines
13 KiB
/*++
|
|
|
|
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
|