reactos/ntoskrnl/cache/section/swapout.c

509 lines
14 KiB
C
Raw Normal View History

[CACHE] The cache manager rewrite I started years ago has finally appeared in ReactOS' trunk and although at this point it's not quite perfectly integrated, it's enough to boot up the bootcd or livecd. To check out the more mature original, check out arty-newcc-reactos, branch arty-newcc on bitbucket.org . Amine Khaldi encouraged me quite a bit to not give up on it, and was able to reach out and be an advocate when i really wasn't able to. Others agree that the time has come to begin removing the old cache manager. I expect the remaining problems in the version going to trunk will be taken care of relatively quickly. The motivation for this effort lies in the particularly hairy relationship between ReactOS' cache manager and data sections. This code completely removes page sharing between cache manager and section and reimagines cache manager as being a facility layered on the memory manager, not really caring about individual pages, but simply managing data section objects where caching might occur. It took me about 2 years to do the first pass of this rewrite and most of this year to fix some lingering issues, properly implement demand paging in ReactOS (code which didn't come with this patch in a recognizable form), and finish getting the PrivateCacheMap and SharedCacheMap relationship correct. Currently, the new ntoskrnl/cache directory contains an own implementation of data file sections. After things have settled down, we can begin to deprecate and remove the parts of ReactOS' section implementation that depend on a close relationship with cache manager. Eventually, I think that the extra code added to ntoskrnl/cache/section will be removed and ReactOS' own sections will replace the use of the special MM_CACHE_SECTION_SEGMENT in the cache path. Note also, that this makes all cache manager (and new section parts) use wide file offsets. If my section code were to take over other parts of the ReactOS memory manager, they would also benefit from these improvements. I invite anyone who wants to to peek at this code and fix whatever bugs can be found. svn path=/trunk/; revision=49423
2010-11-02 02:32:39 +00:00
/*
* 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;
extern FAST_MUTEX RmapListLock;
FAST_MUTEX GlobalPageOperation;
PFN_NUMBER
NTAPI
MmWithdrawSectionPage
(PMM_CACHE_SECTION_SEGMENT Segment, PLARGE_INTEGER FileOffset, BOOLEAN *Dirty)
{
ULONG Entry;
DPRINT("MmWithdrawSectionPage(%x,%08x%08x,%x)\n", Segment, FileOffset->HighPart, FileOffset->LowPart, Dirty);
MmLockCacheSectionSegment(Segment);
Entry = MiGetPageEntryCacheSectionSegment(Segment, FileOffset);
*Dirty = !!IS_DIRTY_SSE(Entry);
DPRINT("Withdraw %x (%x) of %wZ\n", FileOffset->LowPart, Entry, Segment->FileObject ? &Segment->FileObject->FileName : NULL);
if (!Entry)
{
DPRINT("Stoeled!\n");
MmUnlockCacheSectionSegment(Segment);
return 0;
}
else if (MM_IS_WAIT_PTE(Entry))
{
DPRINT("WAIT\n");
MmUnlockCacheSectionSegment(Segment);
return MM_WAIT_ENTRY;
}
else if (Entry && !IS_SWAP_FROM_SSE(Entry))
{
DPRINT("Page %x\n", PFN_FROM_SSE(Entry));
*Dirty |= (Entry & 2);
MiSetPageEntryCacheSectionSegment(Segment, FileOffset, MAKE_SWAP_SSE(MM_WAIT_ENTRY));
MmUnlockCacheSectionSegment(Segment);
return PFN_FROM_SSE(Entry);
}
else
{
DPRINT1("SWAP ENTRY?! (%x:%08x%08x)\n", Segment, FileOffset->HighPart, FileOffset->LowPart);
ASSERT(FALSE);
MmUnlockCacheSectionSegment(Segment);
return 0;
}
}
NTSTATUS
NTAPI
MmFinalizeSectionPageOut
(PMM_CACHE_SECTION_SEGMENT Segment, PLARGE_INTEGER FileOffset, PFN_NUMBER Page,
BOOLEAN Dirty)
{
NTSTATUS Status = STATUS_SUCCESS;
BOOLEAN WriteZero = FALSE, WritePage = FALSE;
SWAPENTRY Swap = MmGetSavedSwapEntryPage(Page);
MmLockCacheSectionSegment(Segment);
(void)InterlockedIncrementUL(&Segment->ReferenceCount);
if (Dirty)
{
DPRINT("Finalize (dirty) Segment %x Page %x\n", Segment, Page);
DPRINT("Segment->FileObject %x\n", Segment->FileObject);
DPRINT("Segment->Flags %x\n", Segment->Flags);
WriteZero = TRUE;
WritePage = TRUE;
}
else
{
WriteZero = TRUE;
}
DPRINT("Status %x\n", Status);
MmUnlockCacheSectionSegment(Segment);
if (WritePage)
{
DPRINT("MiWriteBackPage(Segment %x FileObject %x Offset %x)\n", Segment, Segment->FileObject, FileOffset->LowPart);
Status = MiWriteBackPage(Segment->FileObject, FileOffset, PAGE_SIZE, Page);
}
MmLockCacheSectionSegment(Segment);
if (WriteZero && NT_SUCCESS(Status))
{
DPRINT("Setting page entry in segment %x:%x to swap %x\n", Segment, FileOffset->LowPart, Swap);
MiSetPageEntryCacheSectionSegment(Segment, FileOffset, Swap ? MAKE_SWAP_SSE(Swap) : 0);
}
else
{
DPRINT("Setting page entry in segment %x:%x to page %x\n", Segment, FileOffset->LowPart, Page);
MiSetPageEntryCacheSectionSegment
(Segment, FileOffset, Page ? (Dirty ? DIRTY_SSE(MAKE_PFN_SSE(Page)) : MAKE_PFN_SSE(Page)) : 0);
}
if (NT_SUCCESS(Status))
{
DPRINT("Removing page %x for real\n", Page);
MmSetSavedSwapEntryPage(Page, 0);
// Note: the other one is held by MmTrimUserMemory
if (MmGetReferenceCountPage(Page) != 2) {
DPRINT1("ALERT: Page %x about to be evicted with ref count %d\n", Page, MmGetReferenceCountPage(Page));
}
MmDereferencePage(Page);
}
MmUnlockCacheSectionSegment(Segment);
if (InterlockedDecrementUL(&Segment->ReferenceCount) == 0)
{
MmFinalizeSegment(Segment);
}
/* Note: Writing may evict the segment... Nothing is guaranteed from here down */
MiSetPageEvent(Segment, FileOffset->LowPart);
DPRINT("Status %x\n", Status);
return Status;
}
NTSTATUS
NTAPI
MmPageOutCacheSection
(PMMSUPPORT AddressSpace,
MEMORY_AREA* MemoryArea,
PVOID Address,
PMM_REQUIRED_RESOURCES Required)
{
NTSTATUS Status = STATUS_SUCCESS;
ULONG Entry;
BOOLEAN Dirty = FALSE;
PEPROCESS Process = MmGetAddressSpaceOwner(AddressSpace);
LARGE_INTEGER TotalOffset;
PMM_CACHE_SECTION_SEGMENT Segment;
PVOID PAddress = MM_ROUND_DOWN(Address, PAGE_SIZE);
TotalOffset.QuadPart = (ULONG_PTR)PAddress - (ULONG_PTR)MemoryArea->StartingAddress +
MemoryArea->Data.CacheData.ViewOffset.QuadPart;
Segment = MemoryArea->Data.CacheData.Segment;
MmLockCacheSectionSegment(Segment);
ASSERT(KeGetCurrentIrql() <= APC_LEVEL);
Dirty = MmIsDirtyPageRmap(Required->Page[0]);
Entry = MiGetPageEntryCacheSectionSegment(Segment, &TotalOffset);
if (Dirty)
{
PFN_NUMBER OurPage;
MiSetPageEntryCacheSectionSegment(Segment, &TotalOffset, DIRTY_SSE(Entry));
MmDeleteRmap(Required->Page[0], Process, Address);
MmDeleteVirtualMapping(Process, Address, FALSE, &Dirty, &OurPage);
ASSERT(OurPage == Required->Page[0]);
} else {
/* Just unmap if the page wasn't dirty */
PFN_NUMBER OurPage;
MmDeleteRmap(Required->Page[0], Process, Address);
MmDeleteVirtualMapping(Process, Address, FALSE, &Dirty, &OurPage);
DPRINT("OurPage %x ThePage %x\n", OurPage, Required->Page[0]);
ASSERT(OurPage == Required->Page[0]);
}
if (NT_SUCCESS(Status))
{
MmDereferencePage(Required->Page[0]);
}
MmUnlockCacheSectionSegment(Segment);
MiSetPageEvent(Process, Address);
return Status;
}
NTSTATUS
NTAPI
MmpPageOutPhysicalAddress(PFN_NUMBER Page)
{
BOOLEAN ProcRef = FALSE;
PFN_NUMBER SectionPage = 0;
PMM_RMAP_ENTRY entry;
PMM_CACHE_SECTION_SEGMENT Segment = NULL;
LARGE_INTEGER FileOffset;
PMEMORY_AREA MemoryArea;
PMMSUPPORT AddressSpace = MmGetKernelAddressSpace();
BOOLEAN Dirty = FALSE;
PVOID Address = NULL;
PEPROCESS Process = NULL;
NTSTATUS Status = STATUS_SUCCESS;
MM_REQUIRED_RESOURCES Resources = { 0 };
DPRINTC("Page out %x (ref ct %x)\n", Page, MmGetReferenceCountPage(Page));
ExAcquireFastMutex(&GlobalPageOperation);
if ((Segment = MmGetSectionAssociation(Page, &FileOffset)))
{
DPRINT1("Withdrawing page (%x) %x:%x\n", Page, Segment, FileOffset.LowPart);
SectionPage = MmWithdrawSectionPage(Segment, &FileOffset, &Dirty);
DPRINTC("SectionPage %x\n", SectionPage);
if (SectionPage == MM_WAIT_ENTRY || SectionPage == 0)
{
DPRINT1("In progress page out %x\n", SectionPage);
ExReleaseFastMutex(&GlobalPageOperation);
return STATUS_UNSUCCESSFUL;
}
else
{
ASSERT(SectionPage == Page);
}
Resources.State = Dirty ? 1 : 0;
}
else
{
DPRINT("No segment association for %x\n", Page);
}
Dirty = MmIsDirtyPageRmap(Page);
DPRINTC("Trying to unmap all instances of %x\n", Page);
ExAcquireFastMutex(&RmapListLock);
entry = MmGetRmapListHeadPage(Page);
// Entry and Segment might be null here in the case that the page
// is new and is in the process of being swapped in
if (!entry && !Segment)
{
Status = STATUS_UNSUCCESSFUL;
DPRINT1("Page %x is in transit\n", Page);
ExReleaseFastMutex(&RmapListLock);
goto bail;
}
while (entry != NULL && NT_SUCCESS(Status))
{
Process = entry->Process;
Address = entry->Address;
DPRINTC("Process %x Address %x Page %x\n", Process, Address, Page);
if (RMAP_IS_SEGMENT(Address)) {
entry = entry->Next;
continue;
}
if (Process && Address < MmSystemRangeStart)
{
// Make sure we don't try to page out part of an exiting process
if (PspIsProcessExiting(Process))
{
DPRINT("bail\n");
ExReleaseFastMutex(&RmapListLock);
goto bail;
}
Status = ObReferenceObject(Process);
if (!NT_SUCCESS(Status))
{
DPRINT("bail\n");
ExReleaseFastMutex(&RmapListLock);
goto bail;
}
ProcRef = TRUE;
AddressSpace = &Process->Vm;
}
else
{
AddressSpace = MmGetKernelAddressSpace();
}
ExReleaseFastMutex(&RmapListLock);
RtlZeroMemory(&Resources, sizeof(Resources));
if ((((ULONG_PTR)Address) & 0xFFF) != 0)
{
KeBugCheck(MEMORY_MANAGEMENT);
}
if (!MmTryToLockAddressSpace(AddressSpace))
{
DPRINT1("Could not lock address space for process %x\n", MmGetAddressSpaceOwner(AddressSpace));
Status = STATUS_UNSUCCESSFUL;
goto bail;
}
do
{
MemoryArea = MmLocateMemoryAreaByAddress(AddressSpace, Address);
if (MemoryArea == NULL ||
MemoryArea->DeleteInProgress)
{
Status = STATUS_UNSUCCESSFUL;
MmUnlockAddressSpace(AddressSpace);
DPRINTC("bail\n");
goto bail;
}
DPRINTC
("Type %x (%x -> %x)\n",
MemoryArea->Type,
MemoryArea->StartingAddress,
MemoryArea->EndingAddress);
Resources.DoAcquisition = NULL;
Resources.Page[0] = Page;
ASSERT(KeGetCurrentIrql() <= APC_LEVEL);
DPRINT("%x:%x, page %x %x\n", Process, Address, Page, Resources.Page[0]);
Status = MmPageOutCacheSection
(AddressSpace, MemoryArea, Address, &Resources);
DPRINT("%x\n", Status);
ASSERT(KeGetCurrentIrql() <= APC_LEVEL);
MmUnlockAddressSpace(AddressSpace);
if (Status == STATUS_SUCCESS + 1)
{
// Wait page ... the other guy has it, so we'll just fail for now
DPRINTC("Wait entry ... can't continue\n");
Status = STATUS_UNSUCCESSFUL;
goto bail;
}
else if (Status == STATUS_MORE_PROCESSING_REQUIRED)
{
DPRINTC("DoAcquisition %x\n", Resources.DoAcquisition);
Status = Resources.DoAcquisition(AddressSpace, MemoryArea, &Resources);
DPRINTC("Status %x\n", Status);
if (!NT_SUCCESS(Status))
{
DPRINT1("bail\n");
goto bail;
}
else Status = STATUS_MM_RESTART_OPERATION;
}
MmLockAddressSpace(AddressSpace);
}
while (Status == STATUS_MM_RESTART_OPERATION);
Dirty |= Resources.State & 1; // Accumulate dirty
MmUnlockAddressSpace(AddressSpace);
if (ProcRef)
{
ObDereferenceObject(Process);
ProcRef = FALSE;
}
ExAcquireFastMutex(&RmapListLock);
ASSERT(!MM_IS_WAIT_PTE(MmGetPfnForProcess(Process, Address)));
entry = MmGetRmapListHeadPage(Page);
DPRINTC("Entry %x\n", entry);
}
ExReleaseFastMutex(&RmapListLock);
bail:
DPRINTC("BAIL %x\n", Status);
if (Segment)
{
DPRINTC("About to finalize section page %x (%x:%x) Status %x %s\n", Page, Segment, FileOffset.LowPart, Status, Dirty ? "dirty" : "clean");
if (!NT_SUCCESS(Status) ||
!NT_SUCCESS
(Status = MmFinalizeSectionPageOut
(Segment, &FileOffset, Page, Dirty)))
{
DPRINTC
("Failed to page out %x, replacing %x at %x in segment %x\n",
SectionPage, FileOffset.LowPart, Segment);
MmLockCacheSectionSegment(Segment);
MiSetPageEntryCacheSectionSegment(Segment, &FileOffset, Dirty ? MAKE_PFN_SSE(Page) : DIRTY_SSE(MAKE_PFN_SSE(Page)));
MmUnlockCacheSectionSegment(Segment);
}
// Alas, we had the last reference
ULONG RefCount;
if ((RefCount = InterlockedDecrementUL(&Segment->ReferenceCount)) == 0)
MmFinalizeSegment(Segment);
}
if (ProcRef)
{
DPRINTC("Dereferencing process...\n");
ObDereferenceObject(Process);
}
ExReleaseFastMutex(&GlobalPageOperation);
DPRINTC("%s %x %x\n", NT_SUCCESS(Status) ? "Evicted" : "Spared", Page, Status);
return NT_SUCCESS(Status) ? STATUS_SUCCESS : STATUS_UNSUCCESSFUL;
}
ULONG
NTAPI
MiCacheEvictPages(PVOID BaseAddress, ULONG Target)
{
ULONG i, Entry, Result = 0;
NTSTATUS Status;
PFN_NUMBER Page;
PMEMORY_AREA MemoryArea;
LARGE_INTEGER Offset;
PMM_CACHE_SECTION_SEGMENT Segment;
MmLockAddressSpace(MmGetKernelAddressSpace());
MemoryArea = MmLocateMemoryAreaByAddress
(MmGetKernelAddressSpace(),
BaseAddress);
ASSERT(MemoryArea);
ASSERT(MemoryArea->Type == MEMORY_AREA_CACHE);
Segment = MemoryArea->Data.CacheData.Segment;
ASSERT(Segment);
MmLockCacheSectionSegment(Segment);
for (i = 0;
i < Segment->Length.QuadPart -
MemoryArea->Data.CacheData.ViewOffset.QuadPart &&
Result < Target;
i += PAGE_SIZE) {
Offset.QuadPart = MemoryArea->Data.CacheData.ViewOffset.QuadPart + i;
Entry = MiGetPageEntryCacheSectionSegment(Segment, &Offset);
if (Entry && !IS_SWAP_FROM_SSE(Entry)) {
Page = PFN_FROM_SSE(Entry);
MmReferencePage(Page);
MmUnlockCacheSectionSegment(Segment);
MmUnlockAddressSpace(MmGetKernelAddressSpace());
Status = MmpPageOutPhysicalAddress(Page);
if (NT_SUCCESS(Status))
Result++;
MmLockCacheSectionSegment(Segment);
MmLockAddressSpace(MmGetKernelAddressSpace());
MmReleasePageMemoryConsumer(MC_CACHE, Page);
}
}
MmUnlockCacheSectionSegment(Segment);
MmUnlockAddressSpace(MmGetKernelAddressSpace());
return Result;
}