reactos/ntoskrnl/cache/section/fault.c
Timo Kreuzer 406dfdbc87 [CMAKE]
Sync trunk (rr49606)

svn path=/branches/cmake-bringup/; revision=49607
2010-11-16 13:43:39 +00:00

767 lines
20 KiB
C

/*
* Copyright (C) 1998-2005 ReactOS Team (and the authors from the programmers section)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*
* PROJECT: ReactOS kernel
* FILE: ntoskrnl/mm/section/fault.c
* PURPOSE: Consolidate fault handlers for sections
*
* PROGRAMMERS: Arty
* Rex Jolliff
* David Welch
* Eric Kohl
* Emanuele Aliberti
* Eugene Ingerman
* Casper Hornstrup
* KJK::Hyperion
* Guido de Jong
* Ge van Geldorp
* Royce Mitchell III
* Filip Navara
* Aleksey Bragin
* Jason Filby
* Thomas Weidenmueller
* Gunnar Andre' Dalsnes
* Mike Nordell
* Alex Ionescu
* Gregor Anich
* Steven Edwards
* Herve Poussineau
*/
/* INCLUDES *****************************************************************/
#include <ntoskrnl.h>
#include "newmm.h"
#define NDEBUG
#include <debug.h>
#define DPRINTC DPRINT
extern KEVENT MmWaitPageEvent;
NTSTATUS
NTAPI
MmNotPresentFaultCachePage
(PMMSUPPORT AddressSpace,
MEMORY_AREA* MemoryArea,
PVOID Address,
BOOLEAN Locked,
PMM_REQUIRED_RESOURCES Required)
{
NTSTATUS Status;
PVOID PAddress;
ULONG Consumer;
PMM_CACHE_SECTION_SEGMENT Segment;
LARGE_INTEGER FileOffset, TotalOffset;
ULONG Entry;
ULONG Attributes;
PEPROCESS Process = MmGetAddressSpaceOwner(AddressSpace);
DPRINT("Not Present: %p %p (%p-%p)\n", AddressSpace, Address, MemoryArea->StartingAddress, MemoryArea->EndingAddress);
/*
* There is a window between taking the page fault and locking the
* address space when another thread could load the page so we check
* that.
*/
if (MmIsPagePresent(Process, Address))
{
DPRINT("Done\n");
return(STATUS_SUCCESS);
}
PAddress = MM_ROUND_DOWN(Address, PAGE_SIZE);
TotalOffset.QuadPart = (ULONG_PTR)PAddress - (ULONG_PTR)MemoryArea->StartingAddress;
Segment = MemoryArea->Data.CacheData.Segment;
TotalOffset.QuadPart += MemoryArea->Data.CacheData.ViewOffset.QuadPart;
FileOffset = TotalOffset;
//Consumer = (Segment->Flags & MM_DATAFILE_SEGMENT) ? MC_CACHE : MC_USER;
Consumer = MC_CACHE;
if (Segment->FileObject)
{
DPRINT("FileName %wZ\n", &Segment->FileObject->FileName);
}
DPRINT("Total Offset %08x%08x\n", TotalOffset.HighPart, TotalOffset.LowPart);
/*
* Lock the segment
*/
MmLockCacheSectionSegment(Segment);
/*
* Get the entry corresponding to the offset within the section
*/
Entry = MiGetPageEntryCacheSectionSegment(Segment, &TotalOffset);
Attributes = PAGE_READONLY;
if (Required->State && Required->Page[0])
{
DPRINT("Have file and page, set page %x in section @ %x #\n", Required->Page[0], TotalOffset.LowPart);
if (Required->SwapEntry)
MmSetSavedSwapEntryPage(Required->Page[0], Required->SwapEntry);
if (Required->State & 2)
{
DPRINT("Set in section @ %x\n", TotalOffset.LowPart);
Status = MiSetPageEntryCacheSectionSegment
(Segment, &TotalOffset, Entry = MAKE_PFN_SSE(Required->Page[0]));
if (!NT_SUCCESS(Status))
{
MmReleasePageMemoryConsumer(MC_CACHE, Required->Page[0]);
}
MmUnlockCacheSectionSegment(Segment);
MiSetPageEvent(Process, Address);
DPRINT("Status %x\n", Status);
return STATUS_MM_RESTART_OPERATION;
}
else
{
DPRINT("Set %x in address space @ %x\n", Required->Page[0], Address);
Status = MmCreateVirtualMapping(Process, Address, Attributes, Required->Page, 1);
if (NT_SUCCESS(Status))
{
MmInsertRmap(Required->Page[0], Process, Address);
}
else
{
// Drop the reference for our address space ...
MmReleasePageMemoryConsumer(MC_CACHE, Required->Page[0]);
}
MmUnlockCacheSectionSegment(Segment);
DPRINTC("XXX Set Event %x\n", Status);
MiSetPageEvent(Process, Address);
DPRINT("Status %x\n", Status);
return Status;
}
}
else if (Entry)
{
PFN_NUMBER Page = PFN_FROM_SSE(Entry);
DPRINT("Take reference to page %x #\n", Page);
MmReferencePage(Page);
Status = MmCreateVirtualMapping(Process, Address, Attributes, &Page, 1);
if (NT_SUCCESS(Status))
{
MmInsertRmap(Page, Process, Address);
}
DPRINT("XXX Set Event %x\n", Status);
MiSetPageEvent(Process, Address);
MmUnlockCacheSectionSegment(Segment);
DPRINT("Status %x\n", Status);
return Status;
}
else
{
DPRINT("Get page into section\n");
/*
* If the entry is zero (and it can't change because we have
* locked the segment) then we need to load the page.
*/
//DPRINT1("Read from file %08x %wZ\n", FileOffset.LowPart, &Section->FileObject->FileName);
Required->State = 2;
Required->Context = Segment->FileObject;
Required->Consumer = Consumer;
Required->FileOffset = FileOffset;
Required->Amount = PAGE_SIZE;
Required->DoAcquisition = MiReadFilePage;
MiSetPageEntryCacheSectionSegment(Segment, &TotalOffset, MAKE_SWAP_SSE(MM_WAIT_ENTRY));
MmUnlockCacheSectionSegment(Segment);
return STATUS_MORE_PROCESSING_REQUIRED;
}
ASSERT(FALSE);
return STATUS_ACCESS_VIOLATION;
}
NTSTATUS
NTAPI
MiCopyPageToPage(PFN_NUMBER DestPage, PFN_NUMBER SrcPage)
{
PEPROCESS Process;
KIRQL Irql, Irql2;
PVOID TempAddress, TempSource;
Process = PsGetCurrentProcess();
TempAddress = MiMapPageInHyperSpace(Process, DestPage, &Irql);
if (TempAddress == NULL)
{
return(STATUS_NO_MEMORY);
}
TempSource = MiMapPageInHyperSpace(Process, SrcPage, &Irql2);
if (!TempSource) {
MiUnmapPageInHyperSpace(Process, TempAddress, Irql);
return(STATUS_NO_MEMORY);
}
memcpy(TempAddress, TempSource, PAGE_SIZE);
MiUnmapPageInHyperSpace(Process, TempSource, Irql2);
MiUnmapPageInHyperSpace(Process, TempAddress, Irql);
return(STATUS_SUCCESS);
}
NTSTATUS
NTAPI
MiCowCacheSectionPage
(PMMSUPPORT AddressSpace,
PMEMORY_AREA MemoryArea,
PVOID Address,
BOOLEAN Locked,
PMM_REQUIRED_RESOURCES Required)
{
PMM_CACHE_SECTION_SEGMENT Segment;
PFN_NUMBER NewPage, OldPage;
NTSTATUS Status;
PVOID PAddress;
LARGE_INTEGER Offset;
PEPROCESS Process = MmGetAddressSpaceOwner(AddressSpace);
DPRINT("MmAccessFaultSectionView(%x, %x, %x, %x)\n", AddressSpace, MemoryArea, Address, Locked);
Segment = MemoryArea->Data.CacheData.Segment;
/*
* Lock the segment
*/
MmLockCacheSectionSegment(Segment);
/*
* Find the offset of the page
*/
PAddress = MM_ROUND_DOWN(Address, PAGE_SIZE);
Offset.QuadPart = (ULONG_PTR)PAddress - (ULONG_PTR)MemoryArea->StartingAddress +
MemoryArea->Data.CacheData.ViewOffset.QuadPart;
#if 0 // XXX Cache sections are not CoW. For now, treat all access violations this way.
if ((!Segment->WriteCopy &&
!MemoryArea->Data.CacheData.WriteCopyView) ||
Segment->Image.Characteristics & IMAGE_SCN_MEM_SHARED)
#endif
{
#if 0 // XXX Cache sections don't have regions at present, which streamlines things
if (Region->Protect == PAGE_READWRITE ||
Region->Protect == PAGE_EXECUTE_READWRITE)
#endif
{
DPRINTC("setting non-cow page %x %x:%x offset %x (%x) to writable\n", Segment, Process, PAddress, Offset.u.LowPart, MmGetPfnForProcess(Process, Address));
if (Segment->FileObject)
{
DPRINTC("file %wZ\n", &Segment->FileObject->FileName);
}
ULONG Entry = MiGetPageEntryCacheSectionSegment(Segment, &Offset);
DPRINT("Entry %x\n", Entry);
if (Entry &&
!IS_SWAP_FROM_SSE(Entry) &&
PFN_FROM_SSE(Entry) == MmGetPfnForProcess(Process, Address)) {
MiSetPageEntryCacheSectionSegment(Segment, &Offset, DIRTY_SSE(Entry));
}
MmSetPageProtect(Process, PAddress, PAGE_READWRITE);
MmUnlockCacheSectionSegment(Segment);
DPRINT("Done\n");
return STATUS_SUCCESS;
}
#if 0
else
{
DPRINT("Not supposed to be writable\n");
MmUnlockCacheSectionSegment(Segment);
return STATUS_ACCESS_VIOLATION;
}
#endif
}
if (!Required->Page[0])
{
SWAPENTRY SwapEntry;
if (MmIsPageSwapEntry(Process, Address))
{
MmGetPageFileMapping(Process, Address, &SwapEntry);
MmUnlockCacheSectionSegment(Segment);
if (SwapEntry == MM_WAIT_ENTRY)
return STATUS_SUCCESS + 1; // Wait ... somebody else is getting it right now
else
return STATUS_SUCCESS; // Nonwait swap entry ... handle elsewhere
}
Required->Page[1] = MmGetPfnForProcess(Process, Address);
Required->Consumer = MC_CACHE;
Required->Amount = 1;
Required->File = __FILE__;
Required->Line = __LINE__;
Required->DoAcquisition = MiGetOnePage;
MmCreatePageFileMapping(Process, Address, MM_WAIT_ENTRY);
MmUnlockCacheSectionSegment(Segment);
return STATUS_MORE_PROCESSING_REQUIRED;
}
NewPage = Required->Page[0];
OldPage = Required->Page[1];
DPRINT("Allocated page %x\n", NewPage);
/*
* Unshare the old page.
*/
MmDeleteRmap(OldPage, Process, PAddress);
/*
* Copy the old page
*/
DPRINT("Copying\n");
MiCopyPageToPage(NewPage, OldPage);
/*
* Set the PTE to point to the new page
*/
Status = MmCreateVirtualMapping
(Process, Address, PAGE_READWRITE, &NewPage, 1);
if (!NT_SUCCESS(Status))
{
DPRINT1("MmCreateVirtualMapping failed, not out of memory\n");
ASSERT(FALSE);
MmUnlockCacheSectionSegment(Segment);
return(Status);
}
MmInsertRmap(NewPage, Process, PAddress);
MmReleasePageMemoryConsumer(MC_CACHE, OldPage);
MmUnlockCacheSectionSegment(Segment);
DPRINT("Address 0x%.8X\n", Address);
return(STATUS_SUCCESS);
}
KEVENT MmWaitPageEvent;
typedef struct _WORK_QUEUE_WITH_CONTEXT
{
WORK_QUEUE_ITEM WorkItem;
PMMSUPPORT AddressSpace;
PMEMORY_AREA MemoryArea;
PMM_REQUIRED_RESOURCES Required;
NTSTATUS Status;
KEVENT Wait;
AcquireResource DoAcquisition;
} WORK_QUEUE_WITH_CONTEXT, *PWORK_QUEUE_WITH_CONTEXT;
VOID
NTAPI
MmpFaultWorker
(PWORK_QUEUE_WITH_CONTEXT WorkItem)
{
DPRINT("Calling work\n");
WorkItem->Status =
WorkItem->Required->DoAcquisition
(WorkItem->AddressSpace,
WorkItem->MemoryArea,
WorkItem->Required);
DPRINT("Status %x\n", WorkItem->Status);
KeSetEvent(&WorkItem->Wait, IO_NO_INCREMENT, FALSE);
}
NTSTATUS
NTAPI
MmpSectionAccessFaultInner
(KPROCESSOR_MODE Mode,
PMMSUPPORT AddressSpace,
ULONG_PTR Address,
BOOLEAN FromMdl,
PETHREAD Thread)
{
MEMORY_AREA* MemoryArea;
NTSTATUS Status;
BOOLEAN Locked = FromMdl;
MM_REQUIRED_RESOURCES Resources = { 0 };
DPRINT("MmAccessFault(Mode %d, Address %x)\n", Mode, Address);
if (KeGetCurrentIrql() >= DISPATCH_LEVEL)
{
DPRINT1("Page fault at high IRQL was %d\n", KeGetCurrentIrql());
return(STATUS_UNSUCCESSFUL);
}
/*
* Find the memory area for the faulting address
*/
if (Address >= (ULONG_PTR)MmSystemRangeStart)
{
/*
* Check permissions
*/
if (Mode != KernelMode)
{
DPRINT("MmAccessFault(Mode %d, Address %x)\n", Mode, Address);
return(STATUS_ACCESS_VIOLATION);
}
AddressSpace = MmGetKernelAddressSpace();
}
else
{
AddressSpace = &PsGetCurrentProcess()->Vm;
}
if (!FromMdl)
{
MmLockAddressSpace(AddressSpace);
}
do
{
MemoryArea = MmLocateMemoryAreaByAddress(AddressSpace, (PVOID)Address);
if (MemoryArea == NULL ||
MemoryArea->DeleteInProgress)
{
if (!FromMdl)
{
MmUnlockAddressSpace(AddressSpace);
}
DPRINT("Address: %x\n", Address);
return (STATUS_ACCESS_VIOLATION);
}
DPRINT
("Type %x (%x -> %x)\n",
MemoryArea->Type,
MemoryArea->StartingAddress,
MemoryArea->EndingAddress);
Resources.DoAcquisition = NULL;
// Note: fault handlers are called with address space locked
// We return STATUS_MORE_PROCESSING_REQUIRED if anything is needed
Status = MiCowCacheSectionPage
(AddressSpace, MemoryArea, (PVOID)Address, Locked, &Resources);
if (!FromMdl)
{
MmUnlockAddressSpace(AddressSpace);
}
if (Status == STATUS_SUCCESS + 1)
{
// Wait page ...
DPRINT("Waiting for %x\n", Address);
MiWaitForPageEvent(MmGetAddressSpaceOwner(AddressSpace), Address);
DPRINT("Restarting fault %x\n", Address);
Status = STATUS_MM_RESTART_OPERATION;
}
else if (Status == STATUS_MM_RESTART_OPERATION)
{
// Clean slate
RtlZeroMemory(&Resources, sizeof(Resources));
}
else if (Status == STATUS_MORE_PROCESSING_REQUIRED)
{
if (Thread->ActiveFaultCount > 0)
{
WORK_QUEUE_WITH_CONTEXT Context = { };
DPRINT("Already fault handling ... going to work item (%x)\n", Address);
Context.AddressSpace = AddressSpace;
Context.MemoryArea = MemoryArea;
Context.Required = &Resources;
KeInitializeEvent(&Context.Wait, NotificationEvent, FALSE);
ExInitializeWorkItem(&Context.WorkItem, (PWORKER_THREAD_ROUTINE)MmpFaultWorker, &Context);
DPRINT("Queue work item\n");
ExQueueWorkItem(&Context.WorkItem, DelayedWorkQueue);
DPRINT("Wait\n");
KeWaitForSingleObject(&Context.Wait, 0, KernelMode, FALSE, NULL);
Status = Context.Status;
DPRINT("Status %x\n", Status);
}
else
{
Status = Resources.DoAcquisition(AddressSpace, MemoryArea, &Resources);
}
if (NT_SUCCESS(Status))
{
Status = STATUS_MM_RESTART_OPERATION;
}
}
if (!FromMdl)
{
MmLockAddressSpace(AddressSpace);
}
}
while (Status == STATUS_MM_RESTART_OPERATION);
if (!NT_SUCCESS(Status) && MemoryArea->Type == 1)
{
DPRINT1("Completed page fault handling %x %x\n", Address, Status);
DPRINT1
("Type %x (%x -> %x)\n",
MemoryArea->Type,
MemoryArea->StartingAddress,
MemoryArea->EndingAddress);
}
if (!FromMdl)
{
MmUnlockAddressSpace(AddressSpace);
}
return(Status);
}
NTSTATUS
NTAPI
MmAccessFaultCacheSection
(KPROCESSOR_MODE Mode,
ULONG_PTR Address,
BOOLEAN FromMdl)
{
PETHREAD Thread;
PMMSUPPORT AddressSpace;
NTSTATUS Status;
DPRINT("MmpAccessFault(Mode %d, Address %x)\n", Mode, Address);
Thread = PsGetCurrentThread();
if (KeGetCurrentIrql() >= DISPATCH_LEVEL)
{
DPRINT1("Page fault at high IRQL %d, address %x\n", KeGetCurrentIrql(), Address);
return(STATUS_UNSUCCESSFUL);
}
/*
* Find the memory area for the faulting address
*/
if (Address >= (ULONG_PTR)MmSystemRangeStart)
{
/*
* Check permissions
*/
if (Mode != KernelMode)
{
DPRINT1("Address: %x:%x\n", PsGetCurrentProcess(), Address);
return(STATUS_ACCESS_VIOLATION);
}
AddressSpace = MmGetKernelAddressSpace();
}
else
{
AddressSpace = &PsGetCurrentProcess()->Vm;
}
Thread->ActiveFaultCount++;
Status = MmpSectionAccessFaultInner(Mode, AddressSpace, Address, FromMdl, Thread);
Thread->ActiveFaultCount--;
return(Status);
}
NTSTATUS
NTAPI
MmNotPresentFaultCacheSectionInner
(KPROCESSOR_MODE Mode,
PMMSUPPORT AddressSpace,
ULONG_PTR Address,
BOOLEAN FromMdl,
PETHREAD Thread)
{
BOOLEAN Locked = FromMdl;
PMEMORY_AREA MemoryArea;
MM_REQUIRED_RESOURCES Resources = { 0 };
NTSTATUS Status = STATUS_SUCCESS;
if (!FromMdl)
{
MmLockAddressSpace(AddressSpace);
}
/*
* Call the memory area specific fault handler
*/
do
{
MemoryArea = MmLocateMemoryAreaByAddress(AddressSpace, (PVOID)Address);
if (MemoryArea == NULL || MemoryArea->DeleteInProgress)
{
Status = STATUS_ACCESS_VIOLATION;
if (MemoryArea)
{
DPRINT1("Type %x DIP %x\n", MemoryArea->Type, MemoryArea->DeleteInProgress);
}
else
{
DPRINT1("No memory area\n");
}
DPRINT1("Process %x, Address %x\n", MmGetAddressSpaceOwner(AddressSpace), Address);
break;
}
DPRINTC
("Type %x (%x -> %x -> %x) in %x\n",
MemoryArea->Type,
MemoryArea->StartingAddress,
Address,
MemoryArea->EndingAddress,
PsGetCurrentThread());
Resources.DoAcquisition = NULL;
// Note: fault handlers are called with address space locked
// We return STATUS_MORE_PROCESSING_REQUIRED if anything is needed
Status = MmNotPresentFaultCachePage
(AddressSpace, MemoryArea, (PVOID)Address, Locked, &Resources);
if (!FromMdl)
{
MmUnlockAddressSpace(AddressSpace);
}
if (Status == STATUS_SUCCESS)
{
; // Nothing
}
else if (Status == STATUS_SUCCESS + 1)
{
// Wait page ...
DPRINT("Waiting for %x\n", Address);
MiWaitForPageEvent(MmGetAddressSpaceOwner(AddressSpace), Address);
DPRINT("Done waiting for %x\n", Address);
Status = STATUS_MM_RESTART_OPERATION;
}
else if (Status == STATUS_MM_RESTART_OPERATION)
{
// Clean slate
DPRINT("Clear resource\n");
RtlZeroMemory(&Resources, sizeof(Resources));
}
else if (Status == STATUS_MORE_PROCESSING_REQUIRED)
{
if (Thread->ActiveFaultCount > 1)
{
WORK_QUEUE_WITH_CONTEXT Context = { };
DPRINTC("Already fault handling ... going to work item (%x)\n", Address);
Context.AddressSpace = AddressSpace;
Context.MemoryArea = MemoryArea;
Context.Required = &Resources;
KeInitializeEvent(&Context.Wait, NotificationEvent, FALSE);
ExInitializeWorkItem(&Context.WorkItem, (PWORKER_THREAD_ROUTINE)MmpFaultWorker, &Context);
DPRINT("Queue work item\n");
ExQueueWorkItem(&Context.WorkItem, DelayedWorkQueue);
DPRINT("Wait\n");
KeWaitForSingleObject(&Context.Wait, 0, KernelMode, FALSE, NULL);
Status = Context.Status;
DPRINTC("Status %x\n", Status);
}
else
{
DPRINT("DoAcquisition %x\n", Resources.DoAcquisition);
Status = Resources.DoAcquisition
(AddressSpace, MemoryArea, &Resources);
DPRINT("DoAcquisition %x -> %x\n", Resources.DoAcquisition, Status);
}
if (NT_SUCCESS(Status))
{
Status = STATUS_MM_RESTART_OPERATION;
}
}
else if (NT_SUCCESS(Status))
{
ASSERT(FALSE);
}
if (!FromMdl)
{
MmLockAddressSpace(AddressSpace);
}
}
while (Status == STATUS_MM_RESTART_OPERATION);
DPRINTC("Completed page fault handling: %x:%x %x\n", MmGetAddressSpaceOwner(AddressSpace), Address, Status);
if (!FromMdl)
{
MmUnlockAddressSpace(AddressSpace);
}
MiSetPageEvent(MmGetAddressSpaceOwner(AddressSpace), Address);
DPRINT("Done %x\n", Status);
return Status;
}
NTSTATUS
NTAPI
MmNotPresentFaultCacheSection
(KPROCESSOR_MODE Mode,
ULONG_PTR Address,
BOOLEAN FromMdl)
{
PETHREAD Thread;
PMMSUPPORT AddressSpace;
NTSTATUS Status;
Address &= ~(PAGE_SIZE - 1);
DPRINT("MmNotPresentFault(Mode %d, Address %x)\n", Mode, Address);
Thread = PsGetCurrentThread();
if (KeGetCurrentIrql() >= DISPATCH_LEVEL)
{
DPRINT1("Page fault at high IRQL %d, address %x\n", KeGetCurrentIrql(), Address);
ASSERT(FALSE);
return(STATUS_UNSUCCESSFUL);
}
/*
* Find the memory area for the faulting address
*/
if (Address >= (ULONG_PTR)MmSystemRangeStart)
{
/*
* Check permissions
*/
if (Mode != KernelMode)
{
DPRINTC("Address: %x\n", Address);
return(STATUS_ACCESS_VIOLATION);
}
AddressSpace = MmGetKernelAddressSpace();
}
else
{
AddressSpace = &PsGetCurrentProcess()->Vm;
}
Thread->ActiveFaultCount++;
Status = MmNotPresentFaultCacheSectionInner
(Mode, AddressSpace, Address, FromMdl, Thread);
Thread->ActiveFaultCount--;
ASSERT(Status != STATUS_UNSUCCESSFUL);
ASSERT(Status != STATUS_INVALID_PARAMETER);
DPRINT("MmAccessFault %x:%x -> %x\n", MmGetAddressSpaceOwner(AddressSpace), Address, Status);
return(Status);
}