reactos/ntoskrnl/mm/marea.c

644 lines
19 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/marea.c
* PURPOSE: Implements memory areas
*
* PROGRAMMERS: Rex Jolliff
* David Welch
* Eric Kohl
* Philip Susi
* Casper Hornstrup
* Eric Kohl
* Ge van Geldorp
* Royce Mitchell III
* Aleksey Bragin
* Jason Filby
* Thomas Weidenmueller
* Gunnar Andre' Dalsnes
* Mike Nordell
* Alex Ionescu
* Filip Navara
* Herve Poussineau
* Steven Edwards
*/
/* INCLUDES *****************************************************************/
#include <ntoskrnl.h>
#define NDEBUG
#include <cache/section/newmm.h>
#include <debug.h>
#include "ARM3/miarm.h"
MEMORY_AREA MiStaticMemoryAreas[MI_STATIC_MEMORY_AREAS];
ULONG MiStaticMemoryAreaCount;
MM_AVL_TABLE MiRosKernelVadRoot;
BOOLEAN MiRosKernelVadRootInitialized;
/* FUNCTIONS *****************************************************************/
PMEMORY_AREA NTAPI
MmLocateMemoryAreaByAddress(
PMMSUPPORT AddressSpace,
PVOID Address_)
{
ULONG_PTR StartVpn = (ULONG_PTR)Address_ / PAGE_SIZE;
PEPROCESS Process;
PMM_AVL_TABLE Table;
PMMADDRESS_NODE Node;
PMEMORY_AREA MemoryArea;
TABLE_SEARCH_RESULT Result;
PMMVAD_LONG Vad;
Process = MmGetAddressSpaceOwner(AddressSpace);
Table = (Process != NULL) ? &Process->VadRoot : &MiRosKernelVadRoot;
Result = MiCheckForConflictingNode(StartVpn, StartVpn, Table, &Node);
if (Result != TableFoundNode)
{
return NULL;
}
Vad = (PMMVAD_LONG)Node;
if (Vad->u.VadFlags.Spare == 0)
{
/* Check if this is VM VAD */
if (Vad->ControlArea == NULL)
{
/* We store the reactos MEMORY_AREA here */
MemoryArea = (PMEMORY_AREA)Vad->FirstPrototypePte;
}
else
{
/* This is a section VAD. Store the MAREA here for now */
MemoryArea = (PMEMORY_AREA)Vad->u4.Banked;
}
}
else
{
MemoryArea = (PMEMORY_AREA)Node;
}
return MemoryArea;
}
PMEMORY_AREA
NTAPI
MmLocateMemoryAreaByRegion(
PMMSUPPORT AddressSpace,
PVOID Address_,
ULONG_PTR Length)
{
ULONG_PTR StartVpn = (ULONG_PTR)Address_ / PAGE_SIZE;
ULONG_PTR EndVpn = ((ULONG_PTR)Address_ + Length - 1) / PAGE_SIZE;
PEPROCESS Process;
PMM_AVL_TABLE Table;
PMMADDRESS_NODE Node;
PMEMORY_AREA MemoryArea;
TABLE_SEARCH_RESULT Result;
PMMVAD_LONG Vad;
Process = MmGetAddressSpaceOwner(AddressSpace);
Table = (Process != NULL) ? &Process->VadRoot : &MiRosKernelVadRoot;
Result = MiCheckForConflictingNode(StartVpn, EndVpn, Table, &Node);
if (Result != TableFoundNode)
{
return NULL;
}
Vad = (PMMVAD_LONG)Node;
if (Vad->u.VadFlags.Spare == 0)
{
/* Check if this is VM VAD */
if (Vad->ControlArea == NULL)
{
/* We store the reactos MEMORY_AREA here */
MemoryArea = (PMEMORY_AREA)Vad->FirstPrototypePte;
}
else
{
/* This is a section VAD. Store the MAREA here for now */
MemoryArea = (PMEMORY_AREA)Vad->u4.Banked;
}
}
else
{
MemoryArea = (PMEMORY_AREA)Node;
}
ASSERT(MemoryArea != NULL);
return MemoryArea;
}
VOID
NTAPI
MiInsertVad(IN PMMVAD Vad,
IN PMM_AVL_TABLE VadRoot);
ULONG
NTAPI
MiMakeProtectionMask(
IN ULONG Protect
);
static VOID
MmInsertMemoryArea(
PMMSUPPORT AddressSpace,
PMEMORY_AREA marea,
ULONG Protect)
{
PEPROCESS Process = MmGetAddressSpaceOwner(AddressSpace);
marea->VadNode.u.VadFlags.Spare = 1;
marea->VadNode.u.VadFlags.Protection = MiMakeProtectionMask(Protect);
/* Build a lame VAD if this is a user-space allocation */
if (marea->VadNode.EndingVpn + 1 < (ULONG_PTR)MmSystemRangeStart >> PAGE_SHIFT)
{
ASSERT(Process != NULL);
if (marea->Type != MEMORY_AREA_OWNED_BY_ARM3)
{
#ifdef NEWCC
ASSERT(marea->Type == MEMORY_AREA_SECTION_VIEW || marea->Type == MEMORY_AREA_CACHE);
#else
ASSERT(marea->Type == MEMORY_AREA_SECTION_VIEW);
#endif
/* Insert the VAD */
MiLockProcessWorkingSetUnsafe(PsGetCurrentProcess(), PsGetCurrentThread());
MiInsertVad(&marea->VadNode, &Process->VadRoot);
MiUnlockProcessWorkingSetUnsafe(PsGetCurrentProcess(), PsGetCurrentThread());
marea->Vad = &marea->VadNode;
}
}
else
{
ASSERT(Process == NULL);
if (!MiRosKernelVadRootInitialized)
{
MiRosKernelVadRoot.BalancedRoot.u1.Parent = &MiRosKernelVadRoot.BalancedRoot;
MiRosKernelVadRoot.Unused = 1;
MiRosKernelVadRootInitialized = TRUE;
}
/* Insert the VAD */
MiLockWorkingSet(PsGetCurrentThread(), &MmSystemCacheWs);
MiInsertVad(&marea->VadNode, &MiRosKernelVadRoot);
MiUnlockWorkingSet(PsGetCurrentThread(), &MmSystemCacheWs);
marea->Vad = NULL;
}
}
PVOID NTAPI
MmFindGap(
PMMSUPPORT AddressSpace,
ULONG_PTR Length,
ULONG_PTR Granularity,
BOOLEAN TopDown)
{
PEPROCESS Process;
PMM_AVL_TABLE VadRoot;
TABLE_SEARCH_RESULT Result;
PMMADDRESS_NODE Parent;
ULONG_PTR StartingAddress, HighestAddress;
Process = MmGetAddressSpaceOwner(AddressSpace);
VadRoot = Process ? &Process->VadRoot : &MiRosKernelVadRoot;
if (TopDown)
{
/* Find an address top-down */
HighestAddress = Process ? (ULONG_PTR)MM_HIGHEST_VAD_ADDRESS : (LONG_PTR)-1;
Result = MiFindEmptyAddressRangeDownTree(Length,
HighestAddress,
Granularity,
VadRoot,
&StartingAddress,
&Parent);
}
else
{
Result = MiFindEmptyAddressRangeInTree(Length,
Granularity,
VadRoot,
&Parent,
&StartingAddress);
}
if (Result == TableFoundNode)
{
return NULL;
}
return (PVOID)StartingAddress;
}
VOID
NTAPI
MiRemoveNode(IN PMMADDRESS_NODE Node,
IN PMM_AVL_TABLE Table);
/**
* @name MmFreeMemoryArea
*
* Free an existing memory area.
*
* @param AddressSpace
* Address space to free the area from.
* @param MemoryArea
* Memory area we're about to free.
* @param FreePage
* Callback function for each freed page.
* @param FreePageContext
* Context passed to the callback function.
*
* @return Status
*
* @remarks Lock the address space before calling this function.
*/
NTSTATUS NTAPI
MmFreeMemoryArea(
PMMSUPPORT AddressSpace,
PMEMORY_AREA MemoryArea,
PMM_FREE_PAGE_FUNC FreePage,
PVOID FreePageContext)
{
ULONG_PTR Address;
PVOID EndAddress;
/* Make sure we own the address space lock! */
ASSERT(CONTAINING_RECORD(AddressSpace, EPROCESS, Vm)->AddressCreationLock.Owner == KeGetCurrentThread());
/* Check magic */
ASSERT(MemoryArea->Magic == 'erAM');
if (MemoryArea->Type != MEMORY_AREA_OWNED_BY_ARM3)
{
PEPROCESS CurrentProcess = PsGetCurrentProcess();
PEPROCESS Process = MmGetAddressSpaceOwner(AddressSpace);
if (Process != NULL &&
Process != CurrentProcess)
{
KeAttachProcess(&Process->Pcb);
}
EndAddress = MM_ROUND_UP(MA_GetEndingAddress(MemoryArea), PAGE_SIZE);
for (Address = MA_GetStartingAddress(MemoryArea);
Address < (ULONG_PTR)EndAddress;
Address += PAGE_SIZE)
{
BOOLEAN Dirty = FALSE;
SWAPENTRY SwapEntry = 0;
PFN_NUMBER Page = 0;
if (MmIsPageSwapEntry(Process, (PVOID)Address))
{
MmDeletePageFileMapping(Process, (PVOID)Address, &SwapEntry);
}
else
{
MmDeleteVirtualMapping(Process, (PVOID)Address, &Dirty, &Page);
}
if (FreePage != NULL)
{
FreePage(FreePageContext, MemoryArea, (PVOID)Address,
Page, SwapEntry, (BOOLEAN)Dirty);
}
#if (_MI_PAGING_LEVELS == 2)
/* Remove page table reference */
ASSERT(KeGetCurrentIrql() <= APC_LEVEL);
if ((SwapEntry || Page) && ((PVOID)Address < MmSystemRangeStart))
{
ASSERT(AddressSpace != MmGetKernelAddressSpace());
if (MiQueryPageTableReferences((PVOID)Address) == 0)
{
/* No PTE relies on this PDE. Release it */
KIRQL OldIrql = MiAcquirePfnLock();
PMMPDE PointerPde = MiAddressToPde(Address);
ASSERT(PointerPde->u.Hard.Valid == 1);
MiDeletePte(PointerPde, MiPdeToPte(PointerPde), Process, NULL);
ASSERT(PointerPde->u.Hard.Valid == 0);
MiReleasePfnLock(OldIrql);
}
}
#endif
}
if (Process != NULL &&
Process != CurrentProcess)
{
KeDetachProcess();
}
//if (MemoryArea->VadNode.StartingVpn < (ULONG_PTR)MmSystemRangeStart >> PAGE_SHIFT
if (MemoryArea->Vad)
{
ASSERT(MemoryArea->VadNode.EndingVpn + 1 < (ULONG_PTR)MmSystemRangeStart >> PAGE_SHIFT);
#ifdef NEWCC
ASSERT(MemoryArea->Type == MEMORY_AREA_SECTION_VIEW || MemoryArea->Type == MEMORY_AREA_CACHE);
#else
ASSERT(MemoryArea->Type == MEMORY_AREA_SECTION_VIEW);
#endif
/* MmCleanProcessAddressSpace might have removed it (and this would be MmDeleteProcessAdressSpace) */
ASSERT(MemoryArea->VadNode.u.VadFlags.Spare != 0);
if (((PMMVAD)MemoryArea->Vad)->u.VadFlags.Spare == 1)
{
MiRemoveNode((PMMADDRESS_NODE)&MemoryArea->VadNode, &Process->VadRoot);
}
MemoryArea->Vad = NULL;
}
else
{
MiRemoveNode((PMMADDRESS_NODE)&MemoryArea->VadNode, &MiRosKernelVadRoot);
}
}
#if DBG
MemoryArea->Magic = 'daeD';
#endif
ExFreePoolWithTag(MemoryArea, TAG_MAREA);
DPRINT("MmFreeMemoryArea() succeeded\n");
return STATUS_SUCCESS;
}
/**
* @name MmCreateMemoryArea
*
* Create a memory area.
*
* @param AddressSpace
* Address space to create the area in.
* @param Type
* Type of the memory area.
* @param BaseAddress
* Base address for the memory area we're about the create. On
* input it contains either 0 (auto-assign address) or preferred
* address. On output it contains the starting address of the
* newly created area.
* @param Length
* Length of the area to allocate.
* @param Attributes
* Protection attributes for the memory area.
* @param Result
* Receives a pointer to the memory area on successful exit.
*
* @return Status
*
* @remarks Lock the address space before calling this function.
*/
NTSTATUS NTAPI
MmCreateMemoryArea(PMMSUPPORT AddressSpace,
ULONG Type,
PVOID *BaseAddress,
ULONG_PTR Length,
ULONG Protect,
PMEMORY_AREA *Result,
ULONG AllocationFlags,
ULONG Granularity)
{
ULONG_PTR tmpLength;
PMEMORY_AREA MemoryArea;
ULONG_PTR EndingAddress;
DPRINT("MmCreateMemoryArea(Type 0x%lx, BaseAddress %p, "
"*BaseAddress %p, Length %p, AllocationFlags %x, "
"Result %p)\n",
Type, BaseAddress, *BaseAddress, Length, AllocationFlags,
Result);
/* Is this a static memory area? */
if (Type & MEMORY_AREA_STATIC)
{
/* Use the static array instead of the pool */
ASSERT(MiStaticMemoryAreaCount < MI_STATIC_MEMORY_AREAS);
MemoryArea = &MiStaticMemoryAreas[MiStaticMemoryAreaCount++];
}
else
{
/* Allocate the memory area from nonpaged pool */
MemoryArea = ExAllocatePoolWithTag(NonPagedPool,
sizeof(MEMORY_AREA),
TAG_MAREA);
}
if (!MemoryArea)
{
DPRINT1("Not enough memory.\n");
return STATUS_NO_MEMORY;
}
RtlZeroMemory(MemoryArea, sizeof(MEMORY_AREA));
MemoryArea->Type = Type & ~MEMORY_AREA_STATIC;
MemoryArea->Flags = AllocationFlags;
MemoryArea->Magic = 'erAM';
MemoryArea->DeleteInProgress = FALSE;
if (*BaseAddress == 0)
{
tmpLength = (ULONG_PTR)MM_ROUND_UP(Length, PAGE_SIZE);
*BaseAddress = MmFindGap(AddressSpace,
tmpLength,
Granularity,
(AllocationFlags & MEM_TOP_DOWN) == MEM_TOP_DOWN);
if ((*BaseAddress) == 0)
{
DPRINT("No suitable gap\n");
if (!(Type & MEMORY_AREA_STATIC)) ExFreePoolWithTag(MemoryArea, TAG_MAREA);
return STATUS_NO_MEMORY;
}
MemoryArea->VadNode.StartingVpn = (ULONG_PTR)*BaseAddress >> PAGE_SHIFT;
MemoryArea->VadNode.EndingVpn = ((ULONG_PTR)*BaseAddress + tmpLength - 1) >> PAGE_SHIFT;
MmInsertMemoryArea(AddressSpace, MemoryArea, Protect);
}
else
{
EndingAddress = ((ULONG_PTR)*BaseAddress + Length - 1) | (PAGE_SIZE - 1);
*BaseAddress = ALIGN_DOWN_POINTER_BY(*BaseAddress, Granularity);
tmpLength = EndingAddress + 1 - (ULONG_PTR)*BaseAddress;
if (!MmGetAddressSpaceOwner(AddressSpace) && *BaseAddress < MmSystemRangeStart)
{
ASSERT(FALSE);
if (!(Type & MEMORY_AREA_STATIC)) ExFreePoolWithTag(MemoryArea, TAG_MAREA);
return STATUS_ACCESS_VIOLATION;
}
if (MmGetAddressSpaceOwner(AddressSpace) &&
(ULONG_PTR)(*BaseAddress) + tmpLength > (ULONG_PTR)MmSystemRangeStart)
{
DPRINT("Memory area for user mode address space exceeds MmSystemRangeStart\n");
if (!(Type & MEMORY_AREA_STATIC)) ExFreePoolWithTag(MemoryArea, TAG_MAREA);
return STATUS_ACCESS_VIOLATION;
}
/* No need to check ARM3 owned memory areas, the range MUST be free */
if (MemoryArea->Type != MEMORY_AREA_OWNED_BY_ARM3)
{
if (MmLocateMemoryAreaByRegion(AddressSpace,
*BaseAddress,
tmpLength) != NULL)
{
DPRINT("Memory area already occupied\n");
if (!(Type & MEMORY_AREA_STATIC)) ExFreePoolWithTag(MemoryArea, TAG_MAREA);
return STATUS_CONFLICTING_ADDRESSES;
}
}
MemoryArea->VadNode.StartingVpn = (ULONG_PTR)*BaseAddress >> PAGE_SHIFT;
MemoryArea->VadNode.EndingVpn = ((ULONG_PTR)*BaseAddress + tmpLength - 1) >> PAGE_SHIFT;
MmInsertMemoryArea(AddressSpace, MemoryArea, Protect);
}
*Result = MemoryArea;
DPRINT("MmCreateMemoryArea() succeeded (%p)\n", *BaseAddress);
return STATUS_SUCCESS;
}
VOID
NTAPI
MiRosCleanupMemoryArea(
PEPROCESS Process,
PMMVAD Vad)
{
PMEMORY_AREA MemoryArea;
PVOID BaseAddress;
NTSTATUS Status;
/* We must be called from MmCleanupAddressSpace and nowhere else!
Make sure things are as expected... */
ASSERT(Process == PsGetCurrentProcess());
ASSERT(Process->VmDeleted == TRUE);
ASSERT(((PsGetCurrentThread()->ThreadsProcess == Process) &&
(Process->ActiveThreads == 1)) ||
(Process->ActiveThreads == 0));
MemoryArea = (PMEMORY_AREA)Vad;
BaseAddress = (PVOID)MA_GetStartingAddress(MemoryArea);
if (MemoryArea->Type == MEMORY_AREA_SECTION_VIEW)
{
Status = MiRosUnmapViewOfSection(Process, BaseAddress, Process->ProcessExiting);
}
#ifdef NEWCC
else if (MemoryArea->Type == MEMORY_AREA_CACHE)
{
Status = MmUnmapViewOfCacheSegment(&Process->Vm, BaseAddress);
}
#endif
else
{
/* There shouldn't be anything else! */
ASSERT(FALSE);
}
/* Make sure this worked! */
ASSERT(NT_SUCCESS(Status));
}
VOID
NTAPI
MmDeleteProcessAddressSpace2(IN PEPROCESS Process);
NTSTATUS
NTAPI
MmDeleteProcessAddressSpace(PEPROCESS Process)
{
#ifndef _M_AMD64
KIRQL OldIrql;
#endif
DPRINT("MmDeleteProcessAddressSpace(Process %p (%s))\n", Process,
Process->ImageFileName);
#ifndef _M_AMD64
OldIrql = MiAcquireExpansionLock();
RemoveEntryList(&Process->MmProcessLinks);
MiReleaseExpansionLock(OldIrql);
#endif
MmLockAddressSpace(&Process->Vm);
/* There should not be any memory areas left! */
ASSERT(Process->Vm.WorkingSetExpansionLinks.Flink == NULL);
#if (_MI_PAGING_LEVELS == 2)
{
KIRQL OldIrql;
PVOID Address;
PMMPDE pointerPde;
/* Attach to Process */
KeAttachProcess(&Process->Pcb);
/* Acquire PFN lock */
OldIrql = MiAcquirePfnLock();
for (Address = MI_LOWEST_VAD_ADDRESS;
Address < MM_HIGHEST_VAD_ADDRESS;
Address = (PVOID)((ULONG_PTR)Address + (PTE_PER_PAGE * PAGE_SIZE)))
{
/* At this point all references should be dead */
if (MiQueryPageTableReferences(Address) != 0)
{
DPRINT1("Process %p, Address %p, UsedPageTableEntries %lu\n",
Process,
Address,
MiQueryPageTableReferences(Address));
ASSERT(MiQueryPageTableReferences(Address) == 0);
}
pointerPde = MiAddressToPde(Address);
/* Unlike in ARM3, we don't necesarrily free the PDE page as soon as reference reaches 0,
* so we must clean up a bit when process closes */
if (pointerPde->u.Hard.Valid)
MiDeletePte(pointerPde, MiPdeToPte(pointerPde), Process, NULL);
ASSERT(pointerPde->u.Hard.Valid == 0);
}
/* Release lock */
MiReleasePfnLock(OldIrql);
/* Detach */
KeDetachProcess();
}
#endif
MmUnlockAddressSpace(&Process->Vm);
DPRINT("Finished MmDeleteProcessAddressSpace()\n");
MmDeleteProcessAddressSpace2(Process);
return(STATUS_SUCCESS);
}
/* EOF */