mirror of
https://github.com/reactos/reactos.git
synced 2024-08-28 22:28:36 +00:00
![Timo Kreuzer](/assets/img/avatar_default.png)
The function updates the entry in the section page table and updates the section association rmaps for it. In the page-in path, when the new section association is set before the entry is updated, a concurrent attempt to unmap the page would find an inconsistent entry, where there is an rmap, but the section page table entry is still an MM_WAIT_ENTRY.
417 lines
14 KiB
C
417 lines
14 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/cache/section/sptab.c
|
|
* PURPOSE: Section object page tables
|
|
*
|
|
* PROGRAMMERS: arty
|
|
*/
|
|
|
|
/*
|
|
|
|
This file implements the section page table. It relies on rtl generic table
|
|
functionality to provide access to 256-page chunks. Calls to
|
|
MiSetPageEntrySectionSegment and MiGetPageEntrySectionSegment must be
|
|
synchronized by holding the segment lock.
|
|
|
|
Each page table entry is a ULONG as in x86.
|
|
|
|
Bit 1 is used as a swap entry indication as in the main page table.
|
|
Bit 2 is used as a dirty indication. A dirty page will eventually be written
|
|
back to the file.
|
|
Bits 3-11 are used as a map count in the legacy mm code, Note that zero is
|
|
illegal, as the legacy code does not take advantage of segment rmaps.
|
|
Therefore, every segment page is mapped in at least one address space, and
|
|
MmUnsharePageEntry is quite complicated. In addition, the page may also be
|
|
owned by the legacy cache manager, giving an implied additional reference.
|
|
Upper bits are a PFN_NUMBER.
|
|
|
|
These functions, in addition to maintaining the segment page table also
|
|
automatically maintain the segment rmap by calling MmSetSectionAssociation
|
|
and MmDeleteSectionAssociation. Segment rmaps are discussed in rmap.c. The
|
|
upshot is that it is impossible to have a page properly registered in a segment
|
|
page table and not also found in a segment rmap that can be found from the
|
|
paging machinery.
|
|
|
|
*/
|
|
|
|
/* INCLUDES *****************************************************************/
|
|
|
|
#include <ntoskrnl.h>
|
|
#include "newmm.h"
|
|
#define NDEBUG
|
|
#include <debug.h>
|
|
|
|
#define DPRINTC DPRINT
|
|
|
|
/* TYPES *********************************************************************/
|
|
|
|
extern KSPIN_LOCK MiSectionPageTableLock;
|
|
|
|
_Function_class_(RTL_GENERIC_ALLOCATE_ROUTINE)
|
|
static
|
|
PVOID
|
|
NTAPI
|
|
MiSectionPageTableAllocate(PRTL_GENERIC_TABLE Table, CLONG Bytes)
|
|
{
|
|
PVOID Result;
|
|
Result = ExAllocatePoolWithTag(NonPagedPool, Bytes, 'tPmM');
|
|
//DPRINT("MiSectionPageTableAllocate(%d) => %p\n", Bytes, Result);
|
|
return Result;
|
|
}
|
|
|
|
_Function_class_(RTL_GENERIC_FREE_ROUTINE)
|
|
static
|
|
VOID
|
|
NTAPI
|
|
MiSectionPageTableFree(PRTL_GENERIC_TABLE Table, PVOID Data)
|
|
{
|
|
//DPRINT("MiSectionPageTableFree(%p)\n", Data);
|
|
ExFreePoolWithTag(Data, 'tPmM');
|
|
}
|
|
|
|
_Function_class_(RTL_GENERIC_COMPARE_ROUTINE)
|
|
static
|
|
RTL_GENERIC_COMPARE_RESULTS
|
|
NTAPI
|
|
MiSectionPageTableCompare(PRTL_GENERIC_TABLE Table,
|
|
PVOID PtrA,
|
|
PVOID PtrB)
|
|
{
|
|
PLARGE_INTEGER A = PtrA, B = PtrB;
|
|
BOOLEAN Result = (A->QuadPart < B->QuadPart) ? GenericLessThan :
|
|
(A->QuadPart == B->QuadPart) ? GenericEqual : GenericGreaterThan;
|
|
|
|
#if 0
|
|
DPRINT
|
|
("Compare: %08x%08x vs %08x%08x => %s\n",
|
|
A->u.HighPart, A->u.LowPart,
|
|
B->u.HighPart, B->u.LowPart,
|
|
Result == GenericLessThan ? "GenericLessThan" :
|
|
Result == GenericGreaterThan ? "GenericGreaterThan" :
|
|
"GenericEqual");
|
|
#endif
|
|
|
|
return Result;
|
|
}
|
|
|
|
static
|
|
PCACHE_SECTION_PAGE_TABLE
|
|
NTAPI
|
|
MiSectionPageTableGet(PRTL_GENERIC_TABLE Table,
|
|
PLARGE_INTEGER FileOffset)
|
|
{
|
|
LARGE_INTEGER SearchFileOffset;
|
|
PCACHE_SECTION_PAGE_TABLE PageTable;
|
|
SearchFileOffset.QuadPart = ROUND_DOWN(FileOffset->QuadPart,
|
|
ENTRIES_PER_ELEMENT * PAGE_SIZE);
|
|
PageTable = RtlLookupElementGenericTable(Table, &SearchFileOffset);
|
|
|
|
DPRINT("MiSectionPageTableGet(%p,%I64x)\n",
|
|
Table,
|
|
FileOffset->QuadPart);
|
|
|
|
return PageTable;
|
|
}
|
|
|
|
static
|
|
PCACHE_SECTION_PAGE_TABLE
|
|
NTAPI
|
|
MiSectionPageTableGetOrAllocate(PRTL_GENERIC_TABLE Table,
|
|
PLARGE_INTEGER FileOffset)
|
|
{
|
|
LARGE_INTEGER SearchFileOffset;
|
|
CACHE_SECTION_PAGE_TABLE SectionZeroPageTable;
|
|
PCACHE_SECTION_PAGE_TABLE PageTableSlice = MiSectionPageTableGet(Table,
|
|
FileOffset);
|
|
/* Please zero memory when taking away zero initialization. */
|
|
RtlZeroMemory(&SectionZeroPageTable, sizeof(CACHE_SECTION_PAGE_TABLE));
|
|
if (!PageTableSlice)
|
|
{
|
|
SearchFileOffset.QuadPart = ROUND_DOWN(FileOffset->QuadPart,
|
|
ENTRIES_PER_ELEMENT * PAGE_SIZE);
|
|
SectionZeroPageTable.FileOffset = SearchFileOffset;
|
|
SectionZeroPageTable.Refcount = 1;
|
|
PageTableSlice = RtlInsertElementGenericTable(Table,
|
|
&SectionZeroPageTable,
|
|
sizeof(SectionZeroPageTable),
|
|
NULL);
|
|
if (!PageTableSlice) return NULL;
|
|
DPRINT("Allocate page table %p (%I64x)\n",
|
|
PageTableSlice,
|
|
PageTableSlice->FileOffset.QuadPart);
|
|
}
|
|
return PageTableSlice;
|
|
}
|
|
|
|
VOID
|
|
NTAPI
|
|
MiInitializeSectionPageTable(PMM_SECTION_SEGMENT Segment)
|
|
{
|
|
RtlInitializeGenericTable(&Segment->PageTable,
|
|
MiSectionPageTableCompare,
|
|
MiSectionPageTableAllocate,
|
|
MiSectionPageTableFree,
|
|
NULL);
|
|
|
|
DPRINT("MiInitializeSectionPageTable(%p)\n", &Segment->PageTable);
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
_MmSetPageEntrySectionSegment(PMM_SECTION_SEGMENT Segment,
|
|
PLARGE_INTEGER Offset,
|
|
ULONG_PTR Entry,
|
|
const char *file,
|
|
int line)
|
|
{
|
|
ULONG_PTR PageIndex, OldEntry;
|
|
PCACHE_SECTION_PAGE_TABLE PageTable;
|
|
|
|
ASSERT(Segment->Locked);
|
|
ASSERT(!IS_SWAP_FROM_SSE(Entry) || !IS_DIRTY_SSE(Entry));
|
|
|
|
PageTable = MiSectionPageTableGetOrAllocate(&Segment->PageTable, Offset);
|
|
|
|
if (!PageTable) return STATUS_NO_MEMORY;
|
|
|
|
ASSERT(MiSectionPageTableGet(&Segment->PageTable, Offset));
|
|
|
|
PageTable->Segment = Segment;
|
|
PageIndex = (ULONG_PTR)((Offset->QuadPart - PageTable->FileOffset.QuadPart) / PAGE_SIZE);
|
|
OldEntry = PageTable->PageEntries[PageIndex];
|
|
|
|
DPRINT("MiSetPageEntrySectionSegment(%p,%08x%08x,%x=>%x)\n",
|
|
Segment,
|
|
Offset->u.HighPart,
|
|
Offset->u.LowPart,
|
|
OldEntry,
|
|
Entry);
|
|
|
|
/* Manage ref on segment */
|
|
if (Entry && !OldEntry)
|
|
{
|
|
InterlockedIncrement64(Segment->ReferenceCount);
|
|
}
|
|
if (OldEntry && !Entry)
|
|
{
|
|
MmDereferenceSegment(Segment);
|
|
}
|
|
|
|
if (Entry && !IS_SWAP_FROM_SSE(Entry))
|
|
{
|
|
/* We have a valid entry. See if we must do something */
|
|
if (OldEntry && !IS_SWAP_FROM_SSE(OldEntry))
|
|
{
|
|
/* The previous entry was valid. Shall we swap the Rmaps ? */
|
|
if (PFN_FROM_SSE(Entry) != PFN_FROM_SSE(OldEntry))
|
|
{
|
|
MmDeleteSectionAssociation(PFN_FROM_SSE(OldEntry));
|
|
|
|
/* This has to be done before setting the new section association
|
|
to prevent a race condition with the paging out path */
|
|
PageTable->PageEntries[PageIndex] = Entry;
|
|
|
|
MmSetSectionAssociation(PFN_FROM_SSE(Entry), Segment, Offset);
|
|
}
|
|
else
|
|
{
|
|
PageTable->PageEntries[PageIndex] = Entry;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* We're switching to a valid entry from an invalid one.
|
|
* Add the Rmap and take a ref on the segment.
|
|
*/
|
|
PageTable->PageEntries[PageIndex] = Entry;
|
|
MmSetSectionAssociation(PFN_FROM_SSE(Entry), Segment, Offset);
|
|
|
|
if (Offset->QuadPart >= (Segment->LastPage << PAGE_SHIFT))
|
|
Segment->LastPage = (Offset->QuadPart >> PAGE_SHIFT) + 1;
|
|
}
|
|
}
|
|
else if (OldEntry && !IS_SWAP_FROM_SSE(OldEntry))
|
|
{
|
|
/* We're switching to an invalid entry from a valid one */
|
|
MmDeleteSectionAssociation(PFN_FROM_SSE(OldEntry));
|
|
PageTable->PageEntries[PageIndex] = Entry;
|
|
|
|
if (Offset->QuadPart == ((Segment->LastPage - 1ULL) << PAGE_SHIFT))
|
|
{
|
|
/* We are unsetting the last page */
|
|
while (--Segment->LastPage)
|
|
{
|
|
LARGE_INTEGER CheckOffset;
|
|
CheckOffset.QuadPart = (Segment->LastPage - 1) << PAGE_SHIFT;
|
|
ULONG_PTR Entry = MmGetPageEntrySectionSegment(Segment, &CheckOffset);
|
|
if ((Entry != 0) && !IS_SWAP_FROM_SSE(Entry))
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PageTable->PageEntries[PageIndex] = Entry;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
ULONG_PTR
|
|
NTAPI
|
|
_MmGetPageEntrySectionSegment(PMM_SECTION_SEGMENT Segment,
|
|
PLARGE_INTEGER Offset,
|
|
const char *file,
|
|
int line)
|
|
{
|
|
LARGE_INTEGER FileOffset;
|
|
ULONG_PTR PageIndex, Result;
|
|
PCACHE_SECTION_PAGE_TABLE PageTable;
|
|
|
|
ASSERT(Segment->Locked);
|
|
FileOffset.QuadPart = ROUND_DOWN(Offset->QuadPart,
|
|
ENTRIES_PER_ELEMENT * PAGE_SIZE);
|
|
PageTable = MiSectionPageTableGet(&Segment->PageTable, &FileOffset);
|
|
if (!PageTable) return 0;
|
|
PageIndex = (ULONG_PTR)((Offset->QuadPart - PageTable->FileOffset.QuadPart) / PAGE_SIZE);
|
|
Result = PageTable->PageEntries[PageIndex];
|
|
#if 0
|
|
DPRINTC
|
|
("MiGetPageEntrySectionSegment(%p,%08x%08x) => %x %s:%d\n",
|
|
Segment,
|
|
FileOffset.u.HighPart,
|
|
FileOffset.u.LowPart + PageIndex * PAGE_SIZE,
|
|
Result,
|
|
file, line);
|
|
#endif
|
|
return Result;
|
|
}
|
|
|
|
/*
|
|
|
|
Destroy the rtl generic table that serves as the section's page table. Call
|
|
the FreePage function for each non-zero entry in the section page table as
|
|
we go. Note that the page table is still techinally valid until after all
|
|
pages are destroyed, as we don't finally destroy the table until we've free
|
|
each slice. There is no order guarantee for deletion of individual elements
|
|
although it's in-order as written now.
|
|
|
|
*/
|
|
|
|
VOID
|
|
NTAPI
|
|
MmFreePageTablesSectionSegment(PMM_SECTION_SEGMENT Segment,
|
|
FREE_SECTION_PAGE_FUN FreePage)
|
|
{
|
|
PCACHE_SECTION_PAGE_TABLE Element;
|
|
DPRINT("MiFreePageTablesSectionSegment(%p)\n", &Segment->PageTable);
|
|
while ((Element = RtlGetElementGenericTable(&Segment->PageTable, 0))) {
|
|
DPRINT("Delete table for <%wZ> %p -> %I64x\n",
|
|
Segment->FileObject ? &Segment->FileObject->FileName : NULL,
|
|
Segment,
|
|
Element->FileOffset.QuadPart);
|
|
if (FreePage)
|
|
{
|
|
ULONG i;
|
|
for (i = 0; i < ENTRIES_PER_ELEMENT; i++)
|
|
{
|
|
ULONG_PTR Entry;
|
|
LARGE_INTEGER Offset;
|
|
Offset.QuadPart = Element->FileOffset.QuadPart + i * PAGE_SIZE;
|
|
Entry = Element->PageEntries[i];
|
|
if (Entry && !IS_SWAP_FROM_SSE(Entry))
|
|
{
|
|
DPRINT("Freeing page %p:%Ix @ %I64x\n",
|
|
Segment,
|
|
Entry,
|
|
Offset.QuadPart);
|
|
|
|
FreePage(Segment, &Offset);
|
|
}
|
|
}
|
|
}
|
|
DPRINT("Remove memory\n");
|
|
RtlDeleteElementGenericTable(&Segment->PageTable, Element);
|
|
}
|
|
DPRINT("Done\n");
|
|
}
|
|
|
|
/*
|
|
|
|
Retrieves the MM_SECTION_SEGMENT and fills in the LARGE_INTEGER Offset given
|
|
by the caller that corresponds to the page specified. This uses
|
|
MmGetSegmentRmap to find the rmap belonging to the segment itself, and uses
|
|
the result as a pointer to a 256-entry page table structure. The rmap also
|
|
includes 8 bits of offset information indication one of 256 page entries that
|
|
the rmap corresponds to. This information together gives us an exact offset
|
|
into the file, as well as the MM_SECTION_SEGMENT pointer stored in the page
|
|
table slice.
|
|
|
|
NULL is returned is there is no segment rmap for the page.
|
|
|
|
*/
|
|
|
|
PMM_SECTION_SEGMENT
|
|
NTAPI
|
|
MmGetSectionAssociation(PFN_NUMBER Page,
|
|
PLARGE_INTEGER Offset)
|
|
{
|
|
ULONG RawOffset;
|
|
PMM_SECTION_SEGMENT Segment = NULL;
|
|
PCACHE_SECTION_PAGE_TABLE PageTable;
|
|
|
|
KIRQL OldIrql = MiAcquirePfnLock();
|
|
|
|
PageTable = MmGetSegmentRmap(Page, &RawOffset);
|
|
if (PageTable)
|
|
{
|
|
Segment = PageTable->Segment;
|
|
Offset->QuadPart = PageTable->FileOffset.QuadPart +
|
|
((ULONG64)RawOffset << PAGE_SHIFT);
|
|
ASSERT(PFN_FROM_SSE(PageTable->PageEntries[RawOffset]) == Page);
|
|
InterlockedIncrement64(Segment->ReferenceCount);
|
|
}
|
|
|
|
MiReleasePfnLock(OldIrql);
|
|
|
|
return Segment;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
MmSetSectionAssociation(PFN_NUMBER Page,
|
|
PMM_SECTION_SEGMENT Segment,
|
|
PLARGE_INTEGER Offset)
|
|
{
|
|
PCACHE_SECTION_PAGE_TABLE PageTable;
|
|
ULONG ActualOffset;
|
|
|
|
PageTable = MiSectionPageTableGet(&Segment->PageTable, Offset);
|
|
ASSERT(PageTable);
|
|
|
|
ActualOffset = (ULONG)(Offset->QuadPart - PageTable->FileOffset.QuadPart);
|
|
MmInsertRmap(Page,
|
|
(PEPROCESS)PageTable,
|
|
(PVOID)(RMAP_SEGMENT_MASK | (ActualOffset >> PAGE_SHIFT)));
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|