mirror of https://github.com/lianthony/NT4.0
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.
3327 lines
87 KiB
3327 lines
87 KiB
/*++
|
|
|
|
Copyright (c) 1989 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
procsup.c
|
|
|
|
Abstract:
|
|
|
|
This module contains routines which support the process structure.
|
|
|
|
Author:
|
|
|
|
Lou Perazzoli (loup) 25-Apr-1989
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
|
|
#include "mi.h"
|
|
|
|
#define MM_PROCESS_COMMIT_CHARGE 3
|
|
|
|
#define MM_PROCESS_CREATE_CHARGE 5
|
|
|
|
#define HEADER_FILE
|
|
|
|
extern ULONG MmSharedCommit;
|
|
extern ULONG MmHeapSegmentReserve;
|
|
extern ULONG MmHeapSegmentCommit;
|
|
extern ULONG MmHeapDeCommitTotalFreeThreshold;
|
|
extern ULONG MmHeapDeCommitFreeBlockThreshold;
|
|
extern ULONG MmProductType;
|
|
|
|
extern ULONG MmWorkingSetReductionMax;
|
|
|
|
extern MM_SYSTEMSIZE MmSystemSize;
|
|
|
|
ULONG MmProcessCommit;
|
|
|
|
ULONG MmKernelStackPages;
|
|
ULONG MmKernelStackResident;
|
|
ULONG MmLargeStacks;
|
|
ULONG MmSmallStacks;
|
|
|
|
MMPTE KernelDemandZeroPte = {MM_KERNEL_DEMAND_ZERO_PTE};
|
|
|
|
CCHAR MmRotatingUniprocessorNumber;
|
|
|
|
ULONG
|
|
MiMakeOutswappedPageResident (
|
|
IN PMMPTE ActualPteAddress,
|
|
IN PMMPTE PointerTempPte,
|
|
IN ULONG Global,
|
|
IN ULONG ContainingPage,
|
|
OUT PULONG ActiveTransition
|
|
);
|
|
|
|
|
|
|
|
PVOID
|
|
MiCreatePebOrTeb (
|
|
IN PEPROCESS TargetProcess,
|
|
IN ULONG Size
|
|
);
|
|
|
|
VOID
|
|
MiDeleteAddressesInWorkingSet (
|
|
IN PEPROCESS Process
|
|
);
|
|
|
|
VOID
|
|
MiDeleteValidAddress (
|
|
IN PVOID Va,
|
|
IN PEPROCESS CurrentProcess
|
|
);
|
|
|
|
VOID
|
|
MiDeleteFreeVm (
|
|
IN PVOID StartingAddress,
|
|
IN PVOID EndingAddress
|
|
);
|
|
|
|
VOID
|
|
VadTreeWalk (
|
|
IN PMMVAD Start
|
|
);
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(PAGE,MmCreateTeb)
|
|
#pragma alloc_text(PAGE,MmCreatePeb)
|
|
#pragma alloc_text(PAGE,MiCreatePebOrTeb)
|
|
#pragma alloc_text(PAGE,MmDeleteTeb)
|
|
#endif
|
|
|
|
|
|
BOOLEAN
|
|
MmCreateProcessAddressSpace (
|
|
IN ULONG MinimumWorkingSetSize,
|
|
IN PEPROCESS NewProcess,
|
|
OUT PULONG DirectoryTableBase
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine creates an address space which maps the system
|
|
portion and contains a hyper space entry.
|
|
|
|
Arguments:
|
|
|
|
MinimumWorkingSetSize - Supplies the minimum working set size for
|
|
this address space. This value is only used
|
|
to ensure that ample physical pages exist
|
|
to create this process.
|
|
|
|
NewProcess - Supplies a pointer to the process object being created.
|
|
|
|
DirectoryTableBase - Returns the value of the newly created
|
|
address space's Page Directory (PD) page and
|
|
hyper space page.
|
|
|
|
Return Value:
|
|
|
|
Returns TRUE if an address space was successfully created, FALSE
|
|
if ample physical pages do not exist.
|
|
|
|
Environment:
|
|
|
|
Kernel mode. APC's Disabled.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG PageDirectoryIndex;
|
|
PMMPTE PointerPte;
|
|
ULONG HyperSpaceIndex;
|
|
ULONG PageContainingWorkingSet;
|
|
MMPTE TempPte;
|
|
PMMPTE LastPte;
|
|
PMMPTE PointerFillPte;
|
|
PMMPTE CurrentAddressSpacePde;
|
|
PEPROCESS CurrentProcess;
|
|
KIRQL OldIrql;
|
|
PMMPFN Pfn1;
|
|
ULONG Color;
|
|
|
|
//
|
|
// Get the PFN LOCK to prevent another thread in this
|
|
// process from using hyper space and to get physical pages.
|
|
//
|
|
|
|
CurrentProcess = PsGetCurrentProcess ();
|
|
|
|
//
|
|
// Charge 3 pages of commitment for the page directory page,
|
|
// working set page table page, and working set list.
|
|
//
|
|
|
|
try {
|
|
MiChargeCommitment (MM_PROCESS_COMMIT_CHARGE, NULL);
|
|
} except (EXCEPTION_EXECUTE_HANDLER) {
|
|
return FALSE;
|
|
}
|
|
|
|
NewProcess->NextPageColor = (USHORT)(RtlRandom(&MmProcessColorSeed));
|
|
KeInitializeSpinLock (&NewProcess->HyperSpaceLock);
|
|
Color = MI_PAGE_COLOR_PTE_PROCESS (PDE_BASE,
|
|
&CurrentProcess->NextPageColor);
|
|
|
|
LOCK_WS (CurrentProcess);
|
|
|
|
LOCK_PFN (OldIrql);
|
|
|
|
//
|
|
// Check to make sure the phyiscal pages are available.
|
|
//
|
|
|
|
if (MmResidentAvailablePages <= (LONG)MinimumWorkingSetSize) {
|
|
UNLOCK_PFN (OldIrql);
|
|
UNLOCK_WS (CurrentProcess);
|
|
MiReturnCommitment (MM_PROCESS_COMMIT_CHARGE);
|
|
|
|
//
|
|
// Indicate no directory base was allocated.
|
|
//
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
MmResidentAvailablePages -= MinimumWorkingSetSize;
|
|
MmProcessCommit += MM_PROCESS_COMMIT_CHARGE;
|
|
|
|
MiEnsureAvailablePageOrWait (CurrentProcess, NULL);
|
|
|
|
PageDirectoryIndex = MiRemoveZeroPageIfAny (Color);
|
|
if (PageDirectoryIndex == 0) {
|
|
PageDirectoryIndex = MiRemoveAnyPage (Color);
|
|
UNLOCK_PFN (OldIrql);
|
|
MiZeroPhysicalPage (PageDirectoryIndex, Color);
|
|
LOCK_PFN (OldIrql);
|
|
}
|
|
|
|
INITIALIZE_DIRECTORY_TABLE_BASE(&DirectoryTableBase[0], PageDirectoryIndex);
|
|
|
|
MiEnsureAvailablePageOrWait (CurrentProcess, NULL);
|
|
|
|
Color = MI_PAGE_COLOR_PTE_PROCESS (MiGetPdeAddress(HYPER_SPACE),
|
|
&CurrentProcess->NextPageColor);
|
|
|
|
HyperSpaceIndex = MiRemoveZeroPageIfAny (Color);
|
|
if (HyperSpaceIndex == 0) {
|
|
HyperSpaceIndex = MiRemoveAnyPage (Color);
|
|
UNLOCK_PFN (OldIrql);
|
|
MiZeroPhysicalPage (HyperSpaceIndex, Color);
|
|
LOCK_PFN (OldIrql);
|
|
}
|
|
|
|
INITIALIZE_DIRECTORY_TABLE_BASE(&DirectoryTableBase[1], HyperSpaceIndex);
|
|
|
|
//
|
|
// Remove page for the working set list.
|
|
//
|
|
|
|
MiEnsureAvailablePageOrWait (CurrentProcess, NULL);
|
|
|
|
Color = MI_PAGE_COLOR_VA_PROCESS (MmWorkingSetList,
|
|
&CurrentProcess->NextPageColor);
|
|
|
|
PageContainingWorkingSet = MiRemoveZeroPageIfAny (Color);
|
|
if (PageContainingWorkingSet == 0) {
|
|
PageContainingWorkingSet = MiRemoveAnyPage (Color);
|
|
UNLOCK_PFN (OldIrql);
|
|
MiZeroPhysicalPage (PageContainingWorkingSet, Color);
|
|
LOCK_PFN (OldIrql);
|
|
}
|
|
|
|
//
|
|
// Release the PFN mutex as the needed pages have been allocated.
|
|
//
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
NewProcess->WorkingSetPage = PageContainingWorkingSet;
|
|
|
|
//
|
|
// Initialize the page reserved for hyper space.
|
|
//
|
|
|
|
MI_INITIALIZE_HYPERSPACE_MAP (HyperSpaceIndex);
|
|
|
|
//
|
|
// Set the PTE address in the PFN for hyper space mapping.
|
|
//
|
|
|
|
Pfn1 = MI_PFN_ELEMENT (PageDirectoryIndex);
|
|
|
|
ASSERT (Pfn1->u3.e1.PageColor == 0);
|
|
|
|
Pfn1->PteAddress = (PMMPTE)PDE_BASE;
|
|
|
|
|
|
TempPte = ValidPdePde;
|
|
|
|
TempPte.u.Hard.PageFrameNumber = HyperSpaceIndex;
|
|
MI_SET_GLOBAL_STATE (TempPte, 0);
|
|
#ifdef R4000
|
|
TempPte.u.Hard.Write = 1;
|
|
#endif
|
|
|
|
//
|
|
// Map in page table page for hyper space.
|
|
//
|
|
|
|
PointerPte = (PMMPTE)MiMapPageInHyperSpace (PageDirectoryIndex, &OldIrql);
|
|
PointerPte[MiGetPdeOffset(HYPER_SPACE)] = TempPte;
|
|
|
|
//
|
|
// Map in the page directory page so it points to itself.
|
|
//
|
|
|
|
TempPte.u.Hard.PageFrameNumber = PageDirectoryIndex;
|
|
|
|
PointerPte[MiGetPdeOffset(PTE_BASE)] = TempPte;
|
|
|
|
//
|
|
// Map in the non paged portion of the system.
|
|
//
|
|
|
|
#if defined(_MIPS_) || defined(_ALPHA_) || defined(_PPC_)
|
|
|
|
PointerFillPte = &PointerPte[MiGetPdeOffset(MM_SYSTEM_SPACE_START)];
|
|
CurrentAddressSpacePde = MiGetPdeAddress(MM_SYSTEM_SPACE_START);
|
|
RtlCopyMemory (PointerFillPte,
|
|
CurrentAddressSpacePde,
|
|
((1 + (MiGetPdeAddress(MM_SYSTEM_SPACE_END) -
|
|
MiGetPdeAddress(MM_SYSTEM_SPACE_START))) * sizeof(MMPTE)));
|
|
|
|
#if defined(MM_SYSTEM_CACHE_AND_POOL_DISJOINT)
|
|
PointerFillPte = &PointerPte[MiGetPdeOffset(MM_SYSTEM_CACHE_WORKING_SET)];
|
|
CurrentAddressSpacePde = MiGetPdeAddress(MM_SYSTEM_CACHE_WORKING_SET);
|
|
RtlCopyMemory (PointerFillPte,
|
|
CurrentAddressSpacePde,
|
|
(((1 + (MiGetPdeAddress(MM_SYSTEM_CACHE_END) -
|
|
CurrentAddressSpacePde))) * sizeof(MMPTE)));
|
|
#endif
|
|
|
|
#if defined(MM_BOOT_CODE_PAGEABLE)
|
|
PointerFillPte = &PointerPte[MiGetPdeOffset(MM_BOOT_CODE_START)];
|
|
CurrentAddressSpacePde = MiGetPdeAddress(MM_BOOT_CODE_START);
|
|
RtlCopyMemory (PointerFillPte,
|
|
CurrentAddressSpacePde,
|
|
((1 + (MiGetPdeAddress(MM_BOOT_CODE_END) -
|
|
MiGetPdeAddress(MM_BOOT_CODE_START))) * sizeof(MMPTE)));
|
|
#endif
|
|
|
|
#else // the following is for x86 only
|
|
|
|
PointerFillPte = &PointerPte[MiGetPdeOffset(CODE_START)];
|
|
CurrentAddressSpacePde = MiGetPdeAddress(CODE_START);
|
|
|
|
RtlCopyMemory (PointerFillPte,
|
|
CurrentAddressSpacePde,
|
|
(((1 + CODE_END) - CODE_START) / MM_VA_MAPPED_BY_PDE));
|
|
|
|
|
|
LastPte = &PointerPte[MiGetPdeOffset(NON_PAGED_SYSTEM_END)];
|
|
PointerFillPte = &PointerPte[MiGetPdeOffset(MmNonPagedSystemStart)];
|
|
CurrentAddressSpacePde = MiGetPdeAddress(MmNonPagedSystemStart);
|
|
|
|
RtlCopyMemory (PointerFillPte,
|
|
CurrentAddressSpacePde,
|
|
((1 + (MiGetPdeAddress(NON_PAGED_SYSTEM_END) -
|
|
CurrentAddressSpacePde))) * sizeof(MMPTE));
|
|
|
|
//
|
|
// Map in the system cache page table pages.
|
|
//
|
|
|
|
LastPte = &PointerPte[MiGetPdeOffset(MmSystemCacheEnd)];
|
|
PointerFillPte = &PointerPte[MiGetPdeOffset(MM_SYSTEM_CACHE_WORKING_SET)];
|
|
CurrentAddressSpacePde = MiGetPdeAddress(MM_SYSTEM_CACHE_WORKING_SET);
|
|
|
|
RtlCopyMemory (PointerFillPte,
|
|
CurrentAddressSpacePde,
|
|
((1 + (MiGetPdeAddress(MmSystemCacheEnd) -
|
|
CurrentAddressSpacePde))) * sizeof(MMPTE));
|
|
|
|
|
|
#endif // else _MIPS_ || _ALPHA_ || _PPC_
|
|
|
|
MiUnmapPageInHyperSpace (OldIrql);
|
|
|
|
//
|
|
// Release working set mutex and lower IRQL.
|
|
//
|
|
|
|
UNLOCK_WS (CurrentProcess);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
NTSTATUS
|
|
MmInitializeProcessAddressSpace (
|
|
IN PEPROCESS ProcessToInitialize,
|
|
IN PEPROCESS ProcessToClone OPTIONAL,
|
|
IN PVOID SectionToMap OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine initializes the working set and mutexes within an
|
|
newly created address space to support paging.
|
|
|
|
No page faults may occur in a new process until this routine is
|
|
completed.
|
|
|
|
Arguments:
|
|
|
|
ProcessToInitialize - Supplies a pointer to the process to initialize.
|
|
|
|
ProcessToClone - Optionally supplies a pointer to the process whose
|
|
address space should be copied into the
|
|
ProcessToInitialize address space.
|
|
|
|
SectionToMap - Optionally supplies a section to map into the newly
|
|
intialized address space.
|
|
|
|
Only one of ProcessToClone and SectionToMap may be specified.
|
|
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
|
|
Environment:
|
|
|
|
Kernel mode. APC's Disabled.
|
|
|
|
--*/
|
|
|
|
|
|
{
|
|
PMMPTE PointerPte;
|
|
MMPTE TempPte;
|
|
PVOID BaseAddress;
|
|
ULONG ViewSize;
|
|
KIRQL OldIrql;
|
|
NTSTATUS Status;
|
|
ULONG PdePhysicalPage;
|
|
ULONG PageContainingWorkingSet;
|
|
LARGE_INTEGER SectionOffset;
|
|
|
|
//
|
|
// Initialize Working Set Mutex in process header.
|
|
//
|
|
|
|
KeAttachProcess (&ProcessToInitialize->Pcb);
|
|
ProcessToInitialize->AddressSpaceInitialized = TRUE;
|
|
|
|
ExInitializeFastMutex(&ProcessToInitialize->AddressCreationLock);
|
|
|
|
ExInitializeFastMutex(&ProcessToInitialize->WorkingSetLock);
|
|
|
|
//
|
|
// NOTE: The process block has been zeroed when allocated, so
|
|
// there is no need to zero fields and set pointers to NULL.
|
|
//
|
|
//
|
|
|
|
ASSERT (ProcessToInitialize->VadRoot == NULL);
|
|
|
|
KeQuerySystemTime(&ProcessToInitialize->Vm.LastTrimTime);
|
|
ProcessToInitialize->Vm.VmWorkingSetList = MmWorkingSetList;
|
|
|
|
//
|
|
// Obtain a page to map the working set and initialize the
|
|
// working set. Get PFN mutex to allocate physical pages.
|
|
//
|
|
|
|
LOCK_PFN (OldIrql);
|
|
|
|
//
|
|
// Initialize the PFN database for the Page Directory and the
|
|
// PDE which maps hyper space.
|
|
//
|
|
|
|
PointerPte = MiGetPteAddress (PDE_BASE);
|
|
PdePhysicalPage = PointerPte->u.Hard.PageFrameNumber;
|
|
|
|
MiInitializePfn (PdePhysicalPage, PointerPte, 1);
|
|
|
|
PointerPte = MiGetPdeAddress (HYPER_SPACE);
|
|
MiInitializePfn (PointerPte->u.Hard.PageFrameNumber, PointerPte, 1);
|
|
|
|
PageContainingWorkingSet = ProcessToInitialize->WorkingSetPage;
|
|
|
|
PointerPte = MiGetPteAddress (MmWorkingSetList);
|
|
PointerPte->u.Long = MM_DEMAND_ZERO_WRITE_PTE;
|
|
|
|
MiInitializePfn (PageContainingWorkingSet, PointerPte, 1);
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
MI_MAKE_VALID_PTE (TempPte,
|
|
PageContainingWorkingSet,
|
|
MM_READWRITE,
|
|
PointerPte );
|
|
|
|
MI_SET_PTE_DIRTY (TempPte);
|
|
*PointerPte = TempPte;
|
|
|
|
MiInitializeWorkingSetList (ProcessToInitialize);
|
|
|
|
//
|
|
// Page faults may be taken now.
|
|
//
|
|
|
|
if (SectionToMap != (PSECTION)NULL) {
|
|
#if DEVL
|
|
UNICODE_STRING UnicodeString;
|
|
ULONG n;
|
|
PWSTR Src;
|
|
PCHAR Dst;
|
|
|
|
UnicodeString = ((PSECTION)SectionToMap)->Segment->ControlArea->FilePointer->FileName;
|
|
Src = (PWSTR)((PCHAR)UnicodeString.Buffer + UnicodeString.Length);
|
|
n = 0;
|
|
if (UnicodeString.Buffer != NULL) {
|
|
while (Src > UnicodeString.Buffer) {
|
|
if (*--Src == OBJ_NAME_PATH_SEPARATOR) {
|
|
Src++;
|
|
break;
|
|
}
|
|
else {
|
|
n += 1;
|
|
}
|
|
}
|
|
}
|
|
Dst = ProcessToInitialize->ImageFileName;
|
|
if (n >= sizeof( ProcessToInitialize->ImageFileName )) {
|
|
n = sizeof( ProcessToInitialize->ImageFileName ) - 1;
|
|
}
|
|
|
|
while (n--) {
|
|
*Dst++ = (UCHAR)*Src++;
|
|
}
|
|
*Dst = '\0';
|
|
#endif // DEVL
|
|
|
|
ProcessToInitialize->SubSystemMajorVersion = (UCHAR)((PSECTION)SectionToMap)->Segment->ImageInformation.SubSystemMajorVersion;
|
|
ProcessToInitialize->SubSystemMinorVersion = (UCHAR)((PSECTION)SectionToMap)->Segment->ImageInformation.SubSystemMinorVersion;
|
|
|
|
//
|
|
// Map the specified section into the address space of the
|
|
// process but only if it is an image section
|
|
//
|
|
|
|
if (!((PSECTION)SectionToMap)->u.Flags.Image) {
|
|
Status = STATUS_SECTION_NOT_IMAGE;
|
|
} else {
|
|
BaseAddress = NULL;
|
|
ViewSize = 0;
|
|
ZERO_LARGE (SectionOffset);
|
|
|
|
Status = MmMapViewOfSection ( (PSECTION)SectionToMap,
|
|
ProcessToInitialize,
|
|
&BaseAddress,
|
|
0, // ZeroBits,
|
|
0, // CommitSize,
|
|
&SectionOffset, //SectionOffset,
|
|
&ViewSize,
|
|
ViewShare, //InheritDisposition,
|
|
0, //allocation type
|
|
PAGE_READWRITE // Protect
|
|
);
|
|
|
|
ProcessToInitialize->SectionBaseAddress = BaseAddress;
|
|
|
|
#if DBG
|
|
if (MmDebug & MM_DBG_PTE_UPDATE) {
|
|
DbgPrint("mapped image section vads\n");
|
|
VadTreeWalk(ProcessToInitialize->VadRoot);
|
|
}
|
|
#endif //DBG
|
|
}
|
|
|
|
KeDetachProcess ();
|
|
return Status;
|
|
}
|
|
|
|
if (ProcessToClone != (PEPROCESS)NULL) {
|
|
#if DEVL
|
|
strcpy( ProcessToInitialize->ImageFileName, ProcessToClone->ImageFileName );
|
|
#endif // DEVL
|
|
|
|
//
|
|
// Clone the address space of the specified process.
|
|
//
|
|
|
|
//
|
|
// As the page directory and page tables are private to each
|
|
// process, the physical pages which map the directory page
|
|
// and the page table usage must be mapped into system space
|
|
// so they can be updated while in the context of the process
|
|
// we are cloning.
|
|
//
|
|
|
|
KeDetachProcess ();
|
|
return MiCloneProcessAddressSpace (ProcessToClone,
|
|
ProcessToInitialize,
|
|
PdePhysicalPage,
|
|
PageContainingWorkingSet
|
|
);
|
|
|
|
}
|
|
|
|
//
|
|
// System Process.
|
|
//
|
|
|
|
KeDetachProcess ();
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
VOID
|
|
MmDeleteProcessAddressSpace (
|
|
IN PEPROCESS Process
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine deletes a process's Page Directory and working set page.
|
|
|
|
Arguments:
|
|
|
|
Process - Supplies a pointer to the deleted process.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Kernel mode. APC's Disabled.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
PMMPFN Pfn1;
|
|
KIRQL OldIrql;
|
|
ULONG PageFrameIndex;
|
|
|
|
//
|
|
// Return commitment.
|
|
//
|
|
|
|
MiReturnCommitment (MM_PROCESS_COMMIT_CHARGE);
|
|
ASSERT (Process->CommitCharge == 0);
|
|
|
|
//
|
|
// Remove working set list page from the deleted process.
|
|
//
|
|
|
|
Pfn1 = MI_PFN_ELEMENT (Process->WorkingSetPage);
|
|
|
|
LOCK_PFN (OldIrql);
|
|
MmProcessCommit -= MM_PROCESS_COMMIT_CHARGE;
|
|
|
|
if (Process->AddressSpaceInitialized) {
|
|
|
|
MI_SET_PFN_DELETED (Pfn1);
|
|
|
|
MiDecrementShareAndValidCount (Pfn1->PteFrame);
|
|
MiDecrementShareCountOnly (Process->WorkingSetPage);
|
|
|
|
ASSERT ((Pfn1->u3.e2.ReferenceCount == 0) || (Pfn1->u3.e1.WriteInProgress));
|
|
|
|
//
|
|
// Remove hyper space page table page from deleted process.
|
|
//
|
|
|
|
PageFrameIndex =
|
|
((PHARDWARE_PTE)(&(Process->Pcb.DirectoryTableBase[1])))->PageFrameNumber;
|
|
|
|
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
|
|
|
|
MI_SET_PFN_DELETED (Pfn1);
|
|
|
|
MiDecrementShareAndValidCount (Pfn1->PteFrame);
|
|
MiDecrementShareCountOnly (PageFrameIndex);
|
|
ASSERT ((Pfn1->u3.e2.ReferenceCount == 0) || (Pfn1->u3.e1.WriteInProgress));
|
|
|
|
//
|
|
// Remove page directory page.
|
|
//
|
|
|
|
PageFrameIndex =
|
|
((PHARDWARE_PTE)(&(Process->Pcb.DirectoryTableBase[0])))->PageFrameNumber;
|
|
|
|
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
|
|
|
|
MI_SET_PFN_DELETED (Pfn1);
|
|
|
|
MiDecrementShareAndValidCount (PageFrameIndex);
|
|
MiDecrementShareCountOnly (PageFrameIndex);
|
|
|
|
ASSERT ((Pfn1->u3.e2.ReferenceCount == 0) || (Pfn1->u3.e1.WriteInProgress));
|
|
|
|
} else {
|
|
|
|
//
|
|
// Process initialization never completed, just return the pages
|
|
// to the free list.
|
|
//
|
|
|
|
MiInsertPageInList (MmPageLocationList[FreePageList],
|
|
Process->WorkingSetPage);
|
|
|
|
MiInsertPageInList (MmPageLocationList[FreePageList],
|
|
((PHARDWARE_PTE)(&(Process->Pcb.DirectoryTableBase[1])))->PageFrameNumber);
|
|
|
|
MiInsertPageInList (MmPageLocationList[FreePageList],
|
|
((PHARDWARE_PTE)(&(Process->Pcb.DirectoryTableBase[0])))->PageFrameNumber);
|
|
}
|
|
|
|
MmResidentAvailablePages += MM_PROCESS_CREATE_CHARGE;
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
//
|
|
// Check to see if the paging files should be contracted.
|
|
//
|
|
|
|
MiContractPagingFiles ();
|
|
|
|
return;
|
|
}
|
|
|
|
VOID
|
|
MmCleanProcessAddressSpace (
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine cleans an address space by deleting all the
|
|
user and pageable portion of the address space. At the
|
|
completion of this routine, no page faults may occur within
|
|
the process.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Kernel mode.
|
|
|
|
--*/
|
|
|
|
{
|
|
PEPROCESS Process;
|
|
PMMVAD Vad;
|
|
KEVENT Event;
|
|
KIRQL OldIrql;
|
|
KIRQL OldIrql2;
|
|
PMMPTE PointerPte;
|
|
PVOID TempVa;
|
|
LONG AboveWsMin;
|
|
MMPTE_FLUSH_LIST PteFlushList;
|
|
|
|
PteFlushList.Count = 0;
|
|
Process = PsGetCurrentProcess();
|
|
if ((Process->AddressSpaceDeleted != 0) ||
|
|
(Process->AddressSpaceInitialized == FALSE)) {
|
|
|
|
//
|
|
// This process's address space has already been deleted.
|
|
//
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// If working set expansion for this process is allowed, disable
|
|
// it and remove the process from expanded process list if it
|
|
// is on it.
|
|
//
|
|
|
|
LOCK_EXPANSION (OldIrql);
|
|
|
|
if (Process->Vm.WorkingSetExpansionLinks.Flink == MM_NO_WS_EXPANSION) {
|
|
|
|
//
|
|
// Check to see if trimming is in progress.
|
|
//
|
|
|
|
if (Process->Vm.WorkingSetExpansionLinks.Blink ==
|
|
MM_WS_EXPANSION_IN_PROGRESS) {
|
|
|
|
//
|
|
// Initialize an event and put the event address
|
|
// in the blink field. When the trimming is complete,
|
|
// this event will be set.
|
|
//
|
|
|
|
KeInitializeEvent(&Event, NotificationEvent, FALSE);
|
|
|
|
Process->Vm.WorkingSetExpansionLinks.Blink = (PLIST_ENTRY)&Event;
|
|
|
|
//
|
|
// Release the mutex and wait for the event.
|
|
//
|
|
|
|
KeEnterCriticalRegion();
|
|
UNLOCK_EXPANSION_AND_THEN_WAIT (OldIrql);
|
|
|
|
KeWaitForSingleObject(&Event,
|
|
WrVirtualMemory,
|
|
KernelMode,
|
|
FALSE,
|
|
(PLARGE_INTEGER)NULL);
|
|
KeLeaveCriticalRegion();
|
|
|
|
} else {
|
|
|
|
//
|
|
// No expansion is allowed already, therefore it is not on the list.
|
|
//
|
|
|
|
UNLOCK_EXPANSION (OldIrql);
|
|
}
|
|
} else {
|
|
|
|
RemoveEntryList (&Process->Vm.WorkingSetExpansionLinks);
|
|
|
|
//
|
|
// Disable expansion.
|
|
//
|
|
|
|
Process->Vm.WorkingSetExpansionLinks.Flink = MM_NO_WS_EXPANSION;
|
|
|
|
//
|
|
// Release the pfn mutex.
|
|
//
|
|
|
|
UNLOCK_EXPANSION (OldIrql);
|
|
}
|
|
|
|
//
|
|
// Delete all the user owned pagable virtual addresses in the process.
|
|
//
|
|
|
|
LOCK_WS_AND_ADDRESS_SPACE (Process);
|
|
|
|
//
|
|
// Synchonize address space delete with NtReadVirtualMemory and
|
|
// NtWriteVirtualMemory.
|
|
//
|
|
|
|
ExAcquireSpinLock (&MmSystemSpaceLock, &OldIrql);
|
|
Process->AddressSpaceDeleted = 1;
|
|
if ( Process->VmOperation != 0) {
|
|
|
|
//
|
|
// A Vm operation is in progress, set the event and
|
|
// indicate this process is being deleted to stop other
|
|
// vm operations.
|
|
//
|
|
|
|
KeInitializeEvent(&Event, NotificationEvent, FALSE);
|
|
Process->VmOperationEvent = &Event;
|
|
|
|
do {
|
|
|
|
ExReleaseSpinLock ( &MmSystemSpaceLock, OldIrql );
|
|
|
|
UNLOCK_WS (Process);
|
|
UNLOCK_ADDRESS_SPACE (Process);
|
|
KeWaitForSingleObject(&Event,
|
|
WrVirtualMemory,
|
|
KernelMode,
|
|
FALSE,
|
|
(PLARGE_INTEGER)NULL);
|
|
|
|
LOCK_WS_AND_ADDRESS_SPACE (Process);
|
|
|
|
//
|
|
// Synchonize address space delete with NtReadVirtualMemory and
|
|
// NtWriteVirtualMemory.
|
|
//
|
|
|
|
ExAcquireSpinLock (&MmSystemSpaceLock, &OldIrql);
|
|
|
|
} while (Process->VmOperation != 0);
|
|
|
|
ExReleaseSpinLock ( &MmSystemSpaceLock, OldIrql );
|
|
|
|
} else {
|
|
ExReleaseSpinLock ( &MmSystemSpaceLock, OldIrql );
|
|
}
|
|
|
|
//
|
|
// Delete all the valid user mode addresses from the working set
|
|
// list. At this point NO page faults are allowed on user space
|
|
// addresses.
|
|
//
|
|
|
|
MiDeleteAddressesInWorkingSet (Process);
|
|
|
|
//
|
|
// Delete the virtual address descriptors and dereference any
|
|
// section objects.
|
|
//
|
|
|
|
Vad = Process->VadRoot;
|
|
|
|
while (Vad != (PMMVAD)NULL) {
|
|
|
|
MiRemoveVad (Vad);
|
|
|
|
if ((Vad->u.VadFlags.PrivateMemory == 0) &&
|
|
(Vad->ControlArea != NULL)) {
|
|
|
|
//
|
|
// This Vad represents a mapped view - delete the
|
|
// view and perform any section related cleanup
|
|
// operations.
|
|
//
|
|
|
|
MiRemoveMappedView (Process, Vad);
|
|
|
|
} else {
|
|
|
|
LOCK_PFN (OldIrql);
|
|
|
|
//
|
|
// Don't specify address space deletion as TRUE as
|
|
// the working set must be consistant as page faults may
|
|
// be taken during clone removal, protoPTE lookup, etc.
|
|
//
|
|
|
|
MiDeleteVirtualAddresses (Vad->StartingVa,
|
|
Vad->EndingVa,
|
|
FALSE,
|
|
Vad);
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
}
|
|
|
|
ExFreePool (Vad);
|
|
Vad = Process->VadRoot;
|
|
}
|
|
|
|
//
|
|
// Delete the shared data page, if any.
|
|
//
|
|
|
|
LOCK_PFN (OldIrql);
|
|
|
|
#if defined(MM_SHARED_USER_DATA_VA)
|
|
MiDeleteVirtualAddresses ((PVOID) MM_SHARED_USER_DATA_VA,
|
|
(PVOID) MM_SHARED_USER_DATA_VA,
|
|
FALSE,
|
|
NULL);
|
|
#endif
|
|
|
|
//
|
|
// Delete the system portion of the address space.
|
|
//
|
|
|
|
LOCK_EXPANSION_IF_ALPHA (OldIrql2);
|
|
Process->Vm.AddressSpaceBeingDeleted = TRUE;
|
|
UNLOCK_EXPANSION_IF_ALPHA (OldIrql2);
|
|
|
|
//
|
|
// Adjust the count of pages above working set maximum. This
|
|
// must be done here because the working set list is not
|
|
// updated during this deletion.
|
|
//
|
|
|
|
AboveWsMin = (LONG)Process->Vm.WorkingSetSize - (LONG)Process->Vm.MinimumWorkingSetSize;
|
|
if (AboveWsMin > 0) {
|
|
MmPagesAboveWsMinimum -= AboveWsMin;
|
|
}
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
//
|
|
// Return commitment for page table pages.
|
|
//
|
|
|
|
MiReturnCommitment (MmWorkingSetList->NumberOfCommittedPageTables);
|
|
PsGetCurrentProcess()->CommitCharge -=
|
|
MmWorkingSetList->NumberOfCommittedPageTables;
|
|
|
|
//
|
|
// Check to make sure all the clone descriptors went away.
|
|
//
|
|
|
|
ASSERT (Process->CloneRoot == (PMMCLONE_DESCRIPTOR)NULL);
|
|
|
|
#if DBG
|
|
if (Process->NumberOfLockedPages != 0) {
|
|
KdPrint(("number of locked pages is not zero - %lx",
|
|
Process->NumberOfLockedPages));
|
|
KeBugCheckEx (PROCESS_HAS_LOCKED_PAGES,
|
|
(ULONG)Process,
|
|
Process->NumberOfLockedPages,
|
|
Process->NumberOfPrivatePages,
|
|
0);
|
|
return;
|
|
}
|
|
#endif //DBG
|
|
|
|
#if DBG
|
|
if ((Process->NumberOfPrivatePages != 0) && (MmDebug & MM_DBG_PRIVATE_PAGES)) {
|
|
DbgPrint("MM: Process contains private pages %ld\n",
|
|
Process->NumberOfPrivatePages);
|
|
DbgBreakPoint();
|
|
}
|
|
#endif //DBG
|
|
|
|
//
|
|
// Remove the working set list pages (except for the first one).
|
|
// These pages are not removed because DPCs could still occur within
|
|
// the address space. In a DPC, nonpagedpool could be allocated
|
|
// which could require removing a page from the standby list, requiring
|
|
// hyperspace to map the previous PTE.
|
|
//
|
|
|
|
PointerPte = MiGetPteAddress (MmWorkingSetList) + 1;
|
|
|
|
PteFlushList.Count = 0;
|
|
|
|
LOCK_PFN (OldIrql)
|
|
while (PointerPte->u.Hard.Valid) {
|
|
TempVa = MiGetVirtualAddressMappedByPte(PointerPte);
|
|
MiDeletePte (PointerPte,
|
|
TempVa,
|
|
TRUE,
|
|
Process,
|
|
NULL,
|
|
&PteFlushList);
|
|
|
|
PointerPte += 1;
|
|
}
|
|
|
|
//
|
|
// Remove hash table pages, if any.
|
|
//
|
|
|
|
PointerPte = MiGetPteAddress (&MmWsle[MM_MAXIMUM_WORKING_SET]) + 1;
|
|
|
|
while (PointerPte->u.Hard.Valid) {
|
|
TempVa = MiGetVirtualAddressMappedByPte(PointerPte);
|
|
MiDeletePte (PointerPte,
|
|
TempVa,
|
|
TRUE,
|
|
Process,
|
|
NULL,
|
|
&PteFlushList);
|
|
|
|
PointerPte += 1;
|
|
}
|
|
|
|
MiFlushPteList (&PteFlushList, FALSE, ZeroPte);
|
|
|
|
//
|
|
// Update the count of available resident pages.
|
|
//
|
|
|
|
ASSERT (Process->Vm.MinimumWorkingSetSize >= MM_PROCESS_CREATE_CHARGE);
|
|
MmResidentAvailablePages += Process->Vm.MinimumWorkingSetSize -
|
|
MM_PROCESS_CREATE_CHARGE;
|
|
ASSERT (Process->Vm.WorkingSetExpansionLinks.Flink == MM_NO_WS_EXPANSION);
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
UNLOCK_WS (Process);
|
|
UNLOCK_ADDRESS_SPACE (Process);
|
|
return;
|
|
}
|
|
|
|
|
|
#if DBG
|
|
typedef struct _MMKSTACK {
|
|
PMMPFN Pfn;
|
|
PMMPTE Pte;
|
|
} MMKSTACK, *PMMKSTACK;
|
|
MMKSTACK MmKstacks[10];
|
|
#endif //DBG
|
|
|
|
PVOID
|
|
MmCreateKernelStack (
|
|
IN BOOLEAN LargeStack
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine allocates a kernel stack and a no-access page within
|
|
the non-pagable portion of the system address space.
|
|
|
|
Arguments:
|
|
|
|
LargeStack - Supplies the value TRUE if a large stack should be
|
|
created. FALSE if a small stack is to be created.
|
|
|
|
Return Value:
|
|
|
|
Returns a pointer to the base of the kernel stack. Note, that the
|
|
base address points to the guard page, so space must be allocated
|
|
on the stack before accessing the stack.
|
|
|
|
If a kernel stack cannot be created, the value NULL is returned.
|
|
|
|
Environment:
|
|
|
|
Kernel mode. APC's Disabled.
|
|
|
|
--*/
|
|
|
|
{
|
|
PMMPTE PointerPte;
|
|
MMPTE TempPte;
|
|
ULONG NumberOfPages;
|
|
ULONG NumberOfPtes;
|
|
ULONG PageFrameIndex;
|
|
ULONG i;
|
|
PVOID StackVa;
|
|
KIRQL OldIrql;
|
|
|
|
//
|
|
// Acquire the PFN mutex to synchronize access to the dead stack
|
|
// list and to the pfn database.
|
|
//
|
|
|
|
LOCK_PFN (OldIrql);
|
|
|
|
//
|
|
// Check to see if any "unused" stacks are available.
|
|
//
|
|
|
|
if ((!LargeStack) && (MmNumberDeadKernelStacks != 0)) {
|
|
|
|
#if DBG
|
|
{
|
|
ULONG i = MmNumberDeadKernelStacks;
|
|
PMMPFN PfnList = MmFirstDeadKernelStack;
|
|
|
|
while (i > 0) {
|
|
i--;
|
|
if ((PfnList != MmKstacks[i].Pfn) ||
|
|
(PfnList->PteAddress != MmKstacks[i].Pte)) {
|
|
DbgPrint("MMPROCSUP: kstacks %lx %ld. %lx\n",
|
|
PfnList, i, MmKstacks[i].Pfn);
|
|
DbgBreakPoint();
|
|
}
|
|
PfnList = PfnList->u1.NextStackPfn;
|
|
}
|
|
}
|
|
NumberOfPages = BYTES_TO_PAGES (KERNEL_STACK_SIZE);
|
|
#endif //DBG
|
|
|
|
MmNumberDeadKernelStacks -= 1;
|
|
PointerPte = MmFirstDeadKernelStack->PteAddress;
|
|
MmFirstDeadKernelStack = MmFirstDeadKernelStack->u1.NextStackPfn;
|
|
|
|
} else {
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
//
|
|
// Make sure there are at least 100 free system PTEs.
|
|
//
|
|
|
|
if (MmTotalFreeSystemPtes[SystemPteSpace] < 100) {
|
|
return NULL;
|
|
}
|
|
|
|
if (LargeStack) {
|
|
NumberOfPtes = BYTES_TO_PAGES (KERNEL_LARGE_STACK_SIZE);
|
|
NumberOfPages = BYTES_TO_PAGES (KERNEL_LARGE_STACK_COMMIT);
|
|
} else {
|
|
NumberOfPtes = BYTES_TO_PAGES (KERNEL_STACK_SIZE);
|
|
NumberOfPages = NumberOfPtes;
|
|
}
|
|
|
|
//
|
|
// Charge commitment for the page file space for the kernel stack.
|
|
//
|
|
|
|
try {
|
|
|
|
MiChargeCommitment (NumberOfPtes, NULL);
|
|
|
|
} except (EXCEPTION_EXECUTE_HANDLER) {
|
|
|
|
//
|
|
// Commitment exceeded, return NULL, indicating no kernel
|
|
// stacks are available.
|
|
//
|
|
|
|
return NULL;
|
|
}
|
|
|
|
LOCK_PFN (OldIrql);
|
|
|
|
//
|
|
// Obtain enough pages to contain the stack plus a guard page from
|
|
// the system PTE pool. The system PTE pool contains non-paged PTEs
|
|
// which are currently empty.
|
|
//
|
|
|
|
|
|
//
|
|
// Check to make sure the phyiscal pages are available.
|
|
//
|
|
|
|
if (MmResidentAvailablePages <= (LONG)NumberOfPages) {
|
|
UNLOCK_PFN (OldIrql);
|
|
MiReturnCommitment (NumberOfPtes);
|
|
return NULL;
|
|
}
|
|
|
|
MmKernelStackPages += NumberOfPtes + 1 + (MM_STACK_ALIGNMENT?1:0);
|
|
PointerPte = MiReserveSystemPtes (NumberOfPtes + 1 + (MM_STACK_ALIGNMENT?1:0),
|
|
SystemPteSpace,
|
|
MM_STACK_ALIGNMENT,
|
|
MM_STACK_OFFSET,
|
|
FALSE);
|
|
if (PointerPte == NULL) {
|
|
UNLOCK_PFN (OldIrql);
|
|
MiReturnCommitment (NumberOfPages);
|
|
return NULL;
|
|
}
|
|
|
|
MmResidentAvailablePages -= NumberOfPages;
|
|
PointerPte += (NumberOfPtes - NumberOfPages);
|
|
|
|
for (i=0; i < NumberOfPages; i++) {
|
|
PointerPte += 1;
|
|
ASSERT (PointerPte->u.Hard.Valid == 0);
|
|
MiEnsureAvailablePageOrWait (NULL, NULL);
|
|
PageFrameIndex = MiRemoveAnyPage (
|
|
MI_GET_PAGE_COLOR_FROM_PTE (PointerPte));
|
|
|
|
PointerPte->u.Long = MM_KERNEL_DEMAND_ZERO_PTE;
|
|
//fixfix (see mmfault as well.)
|
|
PointerPte->u.Soft.Protection = 31;
|
|
// end fixfix
|
|
MiInitializePfn (PageFrameIndex, PointerPte, 1);
|
|
|
|
MI_MAKE_VALID_PTE (TempPte,
|
|
PageFrameIndex,
|
|
MM_READWRITE,
|
|
PointerPte );
|
|
MI_SET_PTE_DIRTY (TempPte);
|
|
|
|
*PointerPte = TempPte;
|
|
}
|
|
MmProcessCommit += NumberOfPtes;
|
|
MmKernelStackResident += NumberOfPages;
|
|
MmLargeStacks += LargeStack;
|
|
MmSmallStacks += !LargeStack;
|
|
}
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
PointerPte += 1;
|
|
StackVa = (PVOID)MiGetVirtualAddressMappedByPte (PointerPte);
|
|
#if DBG
|
|
{
|
|
PULONG p;
|
|
ULONG i;
|
|
|
|
p = (PULONG)((ULONG)StackVa - (NumberOfPages * PAGE_SIZE));
|
|
i = (NumberOfPages * PAGE_SIZE) >> 2;
|
|
while(i--) {
|
|
*p++ = 0x12345678;
|
|
}
|
|
|
|
}
|
|
#endif // DBG
|
|
|
|
return StackVa;
|
|
}
|
|
|
|
VOID
|
|
MmDeleteKernelStack (
|
|
IN PVOID PointerKernelStack,
|
|
IN BOOLEAN LargeStack
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine deletes a kernel stack and the no-access page within
|
|
the non-pagable portion of the system address space.
|
|
|
|
Arguments:
|
|
|
|
PointerKernelStack - Supplies a pointer to the base of the kernel stack.
|
|
|
|
LargeStack - Supplies the value TRUE if a large stack is being deleted.
|
|
FALSE if a small stack is to be deleted.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Kernel mode. APC's Disabled.
|
|
|
|
--*/
|
|
|
|
{
|
|
PMMPTE PointerPte;
|
|
PMMPFN Pfn1;
|
|
ULONG NumberOfPages = 0;
|
|
ULONG NumberOfPtes;
|
|
ULONG PageFrameIndex;
|
|
ULONG i;
|
|
KIRQL OldIrql;
|
|
MMPTE PteContents;
|
|
|
|
if (LargeStack) {
|
|
NumberOfPtes = BYTES_TO_PAGES (KERNEL_LARGE_STACK_SIZE);
|
|
} else {
|
|
NumberOfPtes = BYTES_TO_PAGES (KERNEL_STACK_SIZE);
|
|
}
|
|
|
|
PointerPte = MiGetPteAddress (PointerKernelStack);
|
|
|
|
//
|
|
// PointerPte points to the guard page, point to the previous
|
|
// page before removing physical pages.
|
|
//
|
|
|
|
PointerPte -= 1;
|
|
|
|
LOCK_PFN (OldIrql);
|
|
|
|
//
|
|
// Check to see if the stack page should be placed on the dead
|
|
// kernel stack page list. The dead kernel stack list is a
|
|
// singly linked list of kernel stacks from terminated threads.
|
|
// The stacks are saved on a linked list up to a maximum number
|
|
// to avoid the overhead of flushing the entire TB on all processors
|
|
// everytime a thread terminates. The TB on all processors must
|
|
// be flushed as kernel stacks reside in the non paged system part
|
|
// of the address space.
|
|
//
|
|
|
|
if ((!LargeStack) &&
|
|
(MmNumberDeadKernelStacks < MmMaximumDeadKernelStacks)) {
|
|
|
|
Pfn1 = MI_PFN_ELEMENT (PointerPte->u.Hard.PageFrameNumber);
|
|
|
|
#if DBG
|
|
{
|
|
ULONG i = MmNumberDeadKernelStacks;
|
|
PMMPFN PfnList = MmFirstDeadKernelStack;
|
|
|
|
while (i > 0) {
|
|
i--;
|
|
if ((PfnList != MmKstacks[i].Pfn) ||
|
|
(PfnList->PteAddress != MmKstacks[i].Pte)) {
|
|
DbgPrint("MMPROCSUP: kstacks %lx %ld. %lx\n",
|
|
PfnList, i, MmKstacks[i].Pfn);
|
|
DbgBreakPoint();
|
|
}
|
|
PfnList = PfnList->u1.NextStackPfn;
|
|
}
|
|
MmKstacks[MmNumberDeadKernelStacks].Pte = Pfn1->PteAddress;
|
|
MmKstacks[MmNumberDeadKernelStacks].Pfn = Pfn1;
|
|
}
|
|
#endif //DBG
|
|
|
|
MmNumberDeadKernelStacks += 1;
|
|
Pfn1->u1.NextStackPfn = MmFirstDeadKernelStack;
|
|
MmFirstDeadKernelStack = Pfn1;
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// We have exceeded the limit of dead kernel stacks or this is a large
|
|
// stack, delete this kernel stack.
|
|
//
|
|
|
|
for (i=0; i < NumberOfPtes; i++) {
|
|
|
|
PteContents = *PointerPte;
|
|
|
|
if (PteContents.u.Hard.Valid == 1) {
|
|
PageFrameIndex = PteContents.u.Hard.PageFrameNumber;
|
|
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
|
|
MiDecrementShareAndValidCount (Pfn1->PteFrame);
|
|
|
|
//
|
|
// Set the pointer to PTE as empty so the page
|
|
// is deleted when the reference count goes to zero.
|
|
//
|
|
|
|
MI_SET_PFN_DELETED (Pfn1);
|
|
MiDecrementShareCountOnly (PteContents.u.Hard.PageFrameNumber);
|
|
NumberOfPages += 1;
|
|
}
|
|
PointerPte -= 1;
|
|
}
|
|
|
|
MmKernelStackPages -= NumberOfPtes + 1 + (MM_STACK_ALIGNMENT?1:0);
|
|
MiReleaseSystemPtes (PointerPte,
|
|
NumberOfPtes + 1 + (MM_STACK_ALIGNMENT?1:0),
|
|
SystemPteSpace);
|
|
|
|
//
|
|
// Update the count of available resident pages.
|
|
//
|
|
|
|
MmKernelStackResident -= NumberOfPages;
|
|
MmResidentAvailablePages += NumberOfPages;
|
|
MmProcessCommit -= NumberOfPtes;
|
|
|
|
MmLargeStacks -= LargeStack;
|
|
MmSmallStacks -= !LargeStack;
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
//
|
|
// Return commitment.
|
|
//
|
|
|
|
MiReturnCommitment (NumberOfPtes);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
MmGrowKernelStack (
|
|
IN PVOID CurrentStack
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function attempts to grows the current thread's kernel stack
|
|
such that there is always KERNEL_LARGE_STACK_COMMIT bytes below
|
|
the current stack pointer.
|
|
|
|
Arguments:
|
|
|
|
CurrentStack - Supplies a pointer to the current stack pointer.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS is returned if the stack was grown,
|
|
STATUS_STACK_OVERFLOW is returned if there was not enough space reserved
|
|
for the commitment.
|
|
|
|
--*/
|
|
|
|
{
|
|
PMMPTE NewLimit;
|
|
PMMPTE StackLimit;
|
|
PMMPTE EndStack;
|
|
PETHREAD Thread;
|
|
ULONG NumberOfPages = 0;
|
|
KIRQL OldIrql;
|
|
ULONG PageFrameIndex;
|
|
MMPTE TempPte;
|
|
|
|
Thread = PsGetCurrentThread ();
|
|
ASSERT (((PCHAR)Thread->Tcb.StackBase - (PCHAR)Thread->Tcb.StackLimit) <=
|
|
(KERNEL_LARGE_STACK_SIZE + PAGE_SIZE));
|
|
NewLimit = MiGetPteAddress ((PVOID)((PUCHAR)CurrentStack -
|
|
KERNEL_LARGE_STACK_COMMIT));
|
|
|
|
StackLimit = MiGetPteAddress (Thread->Tcb.StackLimit);
|
|
|
|
//
|
|
// If the new stack limit is exceeds the reserved region for the kernel
|
|
// stack, then return an error.
|
|
//
|
|
|
|
EndStack = MiGetPteAddress ((PVOID)((PUCHAR)Thread->Tcb.StackBase -
|
|
KERNEL_LARGE_STACK_SIZE));
|
|
|
|
if (NewLimit < EndStack) {
|
|
|
|
//
|
|
// Don't go into guard page.
|
|
//
|
|
|
|
return STATUS_STACK_OVERFLOW;
|
|
|
|
}
|
|
|
|
ASSERT (StackLimit->u.Hard.Valid == 1);
|
|
|
|
//
|
|
// Lock the PFN database and attempt to expand the kernel stack.
|
|
//
|
|
|
|
StackLimit -= 1;
|
|
|
|
LOCK_PFN (OldIrql);
|
|
|
|
while (StackLimit >= NewLimit) {
|
|
|
|
ASSERT (StackLimit->u.Hard.Valid == 0);
|
|
|
|
MiEnsureAvailablePageOrWait (NULL, NULL);
|
|
PageFrameIndex = MiRemoveAnyPage (MI_GET_PAGE_COLOR_FROM_PTE (StackLimit));
|
|
StackLimit->u.Long = MM_KERNEL_DEMAND_ZERO_PTE;
|
|
//fixfix (see mmfault as well.)
|
|
StackLimit->u.Soft.Protection = 31;
|
|
// end fixfix
|
|
MiInitializePfn (PageFrameIndex, StackLimit, 1);
|
|
MI_MAKE_VALID_PTE (TempPte,
|
|
PageFrameIndex,
|
|
MM_READWRITE,
|
|
StackLimit );
|
|
|
|
MI_SET_PTE_DIRTY (TempPte);
|
|
*StackLimit = TempPte;
|
|
NumberOfPages += 1;
|
|
StackLimit -= 1;
|
|
}
|
|
|
|
MmKernelStackResident += NumberOfPages;
|
|
MmResidentAvailablePages -= NumberOfPages;
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
ASSERT (NewLimit->u.Hard.Valid == 1);
|
|
ASSERT ((NewLimit - 1)->u.Hard.Valid == 0);
|
|
Thread->Tcb.StackLimit = MiGetVirtualAddressMappedByPte (NewLimit);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
VOID
|
|
MmOutPageKernelStack (
|
|
IN PKTHREAD Thread
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine makes the specified kernel stack non-resident and
|
|
puts the pages on the transition list. Note, that if the
|
|
CurrentStackPointer is within the first page of the stack, the
|
|
contents of the second page of the stack is no useful and the
|
|
page is freed.
|
|
|
|
Arguments:
|
|
|
|
Thread - Supplies a pointer to the thread whose stack should be
|
|
removed.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Kernel mode.
|
|
|
|
--*/
|
|
|
|
#define MAX_STACK_PAGES (KERNEL_LARGE_STACK_SIZE / PAGE_SIZE)
|
|
|
|
{
|
|
PMMPTE PointerPte;
|
|
PMMPTE LastPte;
|
|
PMMPTE EndOfStackPte;
|
|
PMMPFN Pfn1;
|
|
ULONG PageFrameIndex;
|
|
KIRQL OldIrql;
|
|
MMPTE TempPte;
|
|
PVOID BaseOfKernelStack;
|
|
PMMPTE FlushPte[MAX_STACK_PAGES];
|
|
PVOID FlushVa[MAX_STACK_PAGES];
|
|
MMPTE FlushPteSave[MAX_STACK_PAGES];
|
|
ULONG StackSize;
|
|
ULONG Count;
|
|
|
|
ASSERT (((PCHAR)Thread->StackBase - (PCHAR)Thread->StackLimit) <=
|
|
(KERNEL_LARGE_STACK_SIZE + PAGE_SIZE));
|
|
|
|
if (NtGlobalFlag & FLG_DISABLE_PAGE_KERNEL_STACKS) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// The first page of the stack is the page before the base
|
|
// of the stack.
|
|
//
|
|
|
|
BaseOfKernelStack = (PVOID)((ULONG)Thread->StackBase - PAGE_SIZE);
|
|
PointerPte = MiGetPteAddress (BaseOfKernelStack);
|
|
LastPte = MiGetPteAddress ((PULONG)Thread->KernelStack - 1);
|
|
if (Thread->LargeStack) {
|
|
StackSize = KERNEL_LARGE_STACK_SIZE >> PAGE_SHIFT;
|
|
} else {
|
|
StackSize = KERNEL_STACK_SIZE >> PAGE_SHIFT;
|
|
}
|
|
EndOfStackPte = PointerPte - StackSize;
|
|
|
|
//
|
|
// Put a signature at the current stack location - 4.
|
|
//
|
|
|
|
*((PULONG)Thread->KernelStack - 1) = (ULONG)Thread;
|
|
|
|
Count = 0;
|
|
|
|
LOCK_PFN (OldIrql);
|
|
|
|
do {
|
|
ASSERT (PointerPte->u.Hard.Valid == 1);
|
|
PageFrameIndex = PointerPte->u.Hard.PageFrameNumber;
|
|
TempPte = *PointerPte;
|
|
MI_MAKE_VALID_PTE_TRANSITION (TempPte, 0);
|
|
//fixfix (see mmfault as well.)
|
|
TempPte.u.Soft.Protection = 31;
|
|
{
|
|
PMMPFN x;
|
|
x = MI_PFN_ELEMENT(PageFrameIndex);
|
|
x->OriginalPte.u.Soft.Protection = 31;
|
|
}
|
|
// end fixfix
|
|
FlushPteSave[Count] = TempPte;
|
|
FlushPte[Count] = PointerPte;
|
|
FlushVa[Count] = BaseOfKernelStack;
|
|
|
|
MiDecrementShareCount (PageFrameIndex);
|
|
PointerPte -= 1;
|
|
Count += 1;
|
|
BaseOfKernelStack = (PVOID)((ULONG)BaseOfKernelStack - PAGE_SIZE);
|
|
} while (PointerPte >= LastPte);
|
|
|
|
while (PointerPte != EndOfStackPte) {
|
|
if (PointerPte->u.Hard.Valid == 0) {
|
|
break;
|
|
}
|
|
|
|
PageFrameIndex = PointerPte->u.Hard.PageFrameNumber;
|
|
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
|
|
MiDecrementShareAndValidCount (Pfn1->PteFrame);
|
|
MI_SET_PFN_DELETED (Pfn1);
|
|
MiDecrementShareCountOnly (PointerPte->u.Hard.PageFrameNumber);
|
|
|
|
FlushPteSave[Count] = KernelDemandZeroPte;
|
|
//fixfix (see mmfault as well.)
|
|
FlushPteSave[Count].u.Soft.Protection = 31;
|
|
// end fixfix
|
|
FlushPte[Count] = PointerPte;
|
|
|
|
FlushVa[Count] = BaseOfKernelStack;
|
|
Count += 1;
|
|
MmResidentAvailablePages += 1;
|
|
|
|
PointerPte -= 1;
|
|
BaseOfKernelStack = (PVOID)((ULONG)BaseOfKernelStack - PAGE_SIZE);
|
|
}
|
|
|
|
ASSERT (Count <= MAX_STACK_PAGES);
|
|
|
|
if (Count < MM_MAXIMUM_FLUSH_COUNT) {
|
|
KeFlushMultipleTb (Count,
|
|
&FlushVa[0],
|
|
TRUE,
|
|
TRUE,
|
|
&((PHARDWARE_PTE)FlushPte[0]),
|
|
ZeroPte.u.Flush);
|
|
} else {
|
|
KeFlushEntireTb (TRUE, TRUE);
|
|
}
|
|
|
|
//
|
|
// Increase the available pages by the number of pages that where
|
|
// deleted and turned into demand zero.
|
|
//
|
|
|
|
MmKernelStackResident -= Count;
|
|
|
|
//
|
|
// Put the right contents back into the PTEs
|
|
//
|
|
|
|
do {
|
|
Count -= 1;
|
|
*FlushPte[Count] = FlushPteSave[Count];
|
|
} while (Count != 0);
|
|
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
return;
|
|
}
|
|
|
|
VOID
|
|
MmInPageKernelStack (
|
|
IN PKTHREAD Thread
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine makes the specified kernel stack resident.
|
|
|
|
Arguments:
|
|
|
|
Supplies a pointer to the base of the kernel stack.
|
|
|
|
Return Value:
|
|
|
|
Thread - Supplies a pointer to the thread whose stack should be
|
|
made resident.
|
|
|
|
Environment:
|
|
|
|
Kernel mode.
|
|
|
|
--*/
|
|
|
|
{
|
|
PVOID BaseOfKernelStack;
|
|
PMMPTE PointerPte;
|
|
PMMPTE EndOfStackPte;
|
|
ULONG Temp;
|
|
ULONG ContainingPage;
|
|
KIRQL OldIrql;
|
|
|
|
ASSERT (((PCHAR)Thread->StackBase - (PCHAR)Thread->StackLimit) <=
|
|
(KERNEL_LARGE_STACK_SIZE + PAGE_SIZE));
|
|
|
|
if (NtGlobalFlag & FLG_DISABLE_PAGE_KERNEL_STACKS) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// The first page of the stack is the page before the base
|
|
// of the stack.
|
|
//
|
|
|
|
if (Thread->LargeStack) {
|
|
PointerPte = MiGetPteAddress ((PVOID)((PUCHAR)Thread->StackLimit));
|
|
|
|
EndOfStackPte = MiGetPteAddress ((PVOID)((PUCHAR)Thread->InitialStack -
|
|
KERNEL_LARGE_STACK_COMMIT));
|
|
//
|
|
// Trim back the stack. Make sure that the stack does not grow, i.e.
|
|
// StackLimit remains the limit.
|
|
//
|
|
|
|
if (EndOfStackPte < PointerPte) {
|
|
EndOfStackPte = PointerPte;
|
|
}
|
|
Thread->StackLimit = MiGetVirtualAddressMappedByPte (EndOfStackPte);
|
|
} else {
|
|
EndOfStackPte = MiGetPteAddress (Thread->StackLimit);
|
|
}
|
|
|
|
BaseOfKernelStack = (PVOID)((ULONG)Thread->StackBase - PAGE_SIZE);
|
|
PointerPte = MiGetPteAddress (BaseOfKernelStack);
|
|
|
|
LOCK_PFN (OldIrql);
|
|
while (PointerPte >= EndOfStackPte) {
|
|
|
|
|
|
//fixfix (see mmfault as well.)
|
|
if (!((PointerPte->u.Long == KernelDemandZeroPte.u.Long) ||
|
|
(PointerPte->u.Soft.Protection == 31))) {
|
|
KeBugCheckEx (MEMORY_MANAGEMENT,
|
|
0x3451,
|
|
(ULONG)PointerPte,
|
|
(ULONG)Thread,
|
|
0);
|
|
}
|
|
ASSERT (PointerPte->u.Hard.Valid == 0);
|
|
if (PointerPte->u.Soft.Protection == 31) {
|
|
PointerPte->u.Soft.Protection = PAGE_READWRITE;
|
|
}
|
|
// end fixfix
|
|
ContainingPage = (MiGetPteAddress (PointerPte))->u.Hard.PageFrameNumber;
|
|
if (PointerPte->u.Long == MM_KERNEL_DEMAND_ZERO_PTE) {
|
|
MmResidentAvailablePages -= 1;
|
|
}
|
|
MiMakeOutswappedPageResident (PointerPte,
|
|
PointerPte,
|
|
1,
|
|
ContainingPage,
|
|
&Temp);
|
|
PointerPte -= 1;
|
|
MmKernelStackResident += 1;
|
|
}
|
|
|
|
//
|
|
// Check the signature at the current stack location - 4.
|
|
//
|
|
|
|
if (*((PULONG)Thread->KernelStack - 1) != (ULONG)Thread) {
|
|
KeBugCheckEx (KERNEL_STACK_INPAGE_ERROR,
|
|
0,
|
|
*((PULONG)Thread->KernelStack - 1),
|
|
0,
|
|
(ULONG)Thread->KernelStack);
|
|
}
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
return;
|
|
}
|
|
|
|
VOID
|
|
MmOutSwapProcess (
|
|
IN PKPROCESS Process
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine out swaps the specified process.
|
|
|
|
Arguments:
|
|
|
|
Process - Supplies a pointer to the process that is swapped out
|
|
of memory.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
KIRQL OldIrql;
|
|
KIRQL OldIrql2;
|
|
PEPROCESS OutProcess;
|
|
PMMPTE PointerPte;
|
|
PMMPFN Pfn1;
|
|
ULONG HyperSpacePageTable;
|
|
PMMPTE HyperSpacePageTableMap;
|
|
ULONG PdePage;
|
|
PMMPTE PageDirectoryMap;
|
|
ULONG ProcessPage;
|
|
MMPTE TempPte;
|
|
|
|
OutProcess = CONTAINING_RECORD( Process,
|
|
EPROCESS,
|
|
Pcb);
|
|
|
|
OutProcess->ProcessOutswapEnabled = TRUE;
|
|
|
|
#if DBG
|
|
if ((MmDebug & MM_DBG_SWAP_PROCESS) != 0) {
|
|
return;
|
|
}
|
|
#endif //DBG
|
|
|
|
if ((OutProcess->Vm.WorkingSetSize == 3) &&
|
|
(OutProcess->Vm.AllowWorkingSetAdjustment)) {
|
|
|
|
//
|
|
// Swap the process working set info and page directory from
|
|
// memory.
|
|
//
|
|
|
|
LOCK_EXPANSION_IF_ALPHA (OldIrql);
|
|
ASSERT (OutProcess->ProcessOutswapped == FALSE);
|
|
OutProcess->ProcessOutswapped = TRUE;
|
|
UNLOCK_EXPANSION_IF_ALPHA (OldIrql);
|
|
|
|
LOCK_PFN (OldIrql);
|
|
|
|
//
|
|
// Remove working set list page from the process.
|
|
//
|
|
|
|
HyperSpacePageTable =
|
|
((PHARDWARE_PTE)(&(OutProcess->Pcb.DirectoryTableBase[1])))->PageFrameNumber;
|
|
HyperSpacePageTableMap = MiMapPageInHyperSpace (HyperSpacePageTable, &OldIrql2);
|
|
|
|
TempPte = HyperSpacePageTableMap[MiGetPteOffset(MmWorkingSetList)];
|
|
|
|
MI_MAKE_VALID_PTE_TRANSITION (TempPte,
|
|
MM_READWRITE);
|
|
|
|
HyperSpacePageTableMap[MiGetPteOffset(MmWorkingSetList)] = TempPte;
|
|
MiUnmapPageInHyperSpace (OldIrql2);
|
|
|
|
#if DBG
|
|
Pfn1 = MI_PFN_ELEMENT (OutProcess->WorkingSetPage);
|
|
ASSERT (Pfn1->u3.e1.Modified == 1);
|
|
#endif
|
|
MiDecrementShareCount (OutProcess->WorkingSetPage);
|
|
|
|
//
|
|
// Remove the hyper space page from the process.
|
|
//
|
|
|
|
PdePage =
|
|
((PHARDWARE_PTE)(&(OutProcess->Pcb.DirectoryTableBase[0])))->PageFrameNumber;
|
|
PageDirectoryMap = MiMapPageInHyperSpace (PdePage, &OldIrql2);
|
|
|
|
TempPte = PageDirectoryMap[MiGetPdeOffset(MmWorkingSetList)];
|
|
|
|
MI_MAKE_VALID_PTE_TRANSITION (TempPte,
|
|
MM_READWRITE);
|
|
|
|
PageDirectoryMap[MiGetPdeOffset(MmWorkingSetList)] = TempPte;
|
|
|
|
#if DBG
|
|
Pfn1 = MI_PFN_ELEMENT (HyperSpacePageTable);
|
|
ASSERT (Pfn1->u3.e1.Modified == 1);
|
|
#endif
|
|
|
|
MiDecrementShareCount (HyperSpacePageTable);
|
|
|
|
//
|
|
// Remove the page directory page.
|
|
//
|
|
|
|
TempPte = PageDirectoryMap[MiGetPdeOffset(PDE_BASE)];
|
|
|
|
MI_MAKE_VALID_PTE_TRANSITION (TempPte,
|
|
MM_READWRITE);
|
|
|
|
PageDirectoryMap[MiGetPdeOffset(PDE_BASE)] = TempPte;
|
|
MiUnmapPageInHyperSpace (OldIrql2);
|
|
|
|
Pfn1 = MI_PFN_ELEMENT (PdePage);
|
|
|
|
//
|
|
// Decrement share count so page directory page gets removed.
|
|
// This can cause the PteCount to equal the sharecount as the
|
|
// page directory page no longer contains itself, yet can have
|
|
// itself as a transition page.
|
|
//
|
|
|
|
Pfn1->u2.ShareCount -= 2;
|
|
Pfn1->PteAddress = (PMMPTE)&OutProcess->PageDirectoryPte;
|
|
|
|
OutProcess->PageDirectoryPte = TempPte.u.Flush;
|
|
|
|
if (MI_IS_PHYSICAL_ADDRESS(OutProcess)) {
|
|
ProcessPage = MI_CONVERT_PHYSICAL_TO_PFN (OutProcess);
|
|
} else {
|
|
PointerPte = MiGetPteAddress (OutProcess);
|
|
ProcessPage = PointerPte->u.Hard.PageFrameNumber;
|
|
}
|
|
|
|
Pfn1->PteFrame = ProcessPage;
|
|
Pfn1 = MI_PFN_ELEMENT (ProcessPage);
|
|
|
|
//
|
|
// Increment the share count for the process page.
|
|
//
|
|
|
|
Pfn1->u2.ShareCount += 1;
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
LOCK_EXPANSION (OldIrql);
|
|
if (OutProcess->Vm.WorkingSetExpansionLinks.Flink >
|
|
MM_IO_IN_PROGRESS) {
|
|
|
|
//
|
|
// The entry must be on the list.
|
|
//
|
|
RemoveEntryList (&OutProcess->Vm.WorkingSetExpansionLinks);
|
|
OutProcess->Vm.WorkingSetExpansionLinks.Flink = MM_WS_SWAPPED_OUT;
|
|
}
|
|
UNLOCK_EXPANSION (OldIrql);
|
|
|
|
OutProcess->WorkingSetPage = 0;
|
|
OutProcess->Vm.WorkingSetSize = 0;
|
|
#if defined(_PPC_)
|
|
|
|
//
|
|
// Force assignment of new PID as we have removed
|
|
// the page directory page.
|
|
// Note that a TB flush would not work here as we
|
|
// are in the wrong process context.
|
|
//
|
|
|
|
Process->ProcessSequence = 0;
|
|
#endif // _PPC_
|
|
|
|
}
|
|
return;
|
|
}
|
|
|
|
VOID
|
|
MmInSwapProcess (
|
|
IN PKPROCESS Process
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine in swaps the specified process.
|
|
|
|
Arguments:
|
|
|
|
Process - Supplies a pointer to the process that is to be swapped
|
|
into memory.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
KIRQL OldIrql;
|
|
KIRQL OldIrql2;
|
|
PEPROCESS OutProcess;
|
|
ULONG PdePage;
|
|
PMMPTE PageDirectoryMap;
|
|
MMPTE TempPte;
|
|
ULONG HyperSpacePageTable;
|
|
PMMPTE HyperSpacePageTableMap;
|
|
ULONG WorkingSetPage;
|
|
PMMPFN Pfn1;
|
|
PMMPTE PointerPte;
|
|
ULONG ProcessPage;
|
|
ULONG Transition;
|
|
|
|
OutProcess = CONTAINING_RECORD( Process,
|
|
EPROCESS,
|
|
Pcb);
|
|
|
|
if (OutProcess->ProcessOutswapped != FALSE) {
|
|
|
|
//
|
|
// The process is out of memory, rebuild the initialize page
|
|
// structure.
|
|
//
|
|
|
|
if (MI_IS_PHYSICAL_ADDRESS(OutProcess)) {
|
|
ProcessPage = MI_CONVERT_PHYSICAL_TO_PFN (OutProcess);
|
|
} else {
|
|
PointerPte = MiGetPteAddress (OutProcess);
|
|
ProcessPage = PointerPte->u.Hard.PageFrameNumber;
|
|
}
|
|
|
|
LOCK_PFN (OldIrql);
|
|
PdePage = MiMakeOutswappedPageResident (MiGetPteAddress (PDE_BASE),
|
|
(PMMPTE)&OutProcess->PageDirectoryPte,
|
|
0,
|
|
ProcessPage,
|
|
&Transition);
|
|
|
|
//
|
|
// Adjust the counts for the process page.
|
|
//
|
|
|
|
Pfn1 = MI_PFN_ELEMENT (ProcessPage);
|
|
Pfn1->u2.ShareCount -= 1;
|
|
|
|
ASSERT ((LONG)Pfn1->u2.ShareCount >= 1);
|
|
|
|
//
|
|
// Adjust the counts properly for the Page directory page.
|
|
//
|
|
|
|
Pfn1 = MI_PFN_ELEMENT (PdePage);
|
|
Pfn1->u2.ShareCount += 1;
|
|
Pfn1->u1.WsIndex = (ULONG)OutProcess;
|
|
Pfn1->PteFrame = PdePage;
|
|
Pfn1->PteAddress = MiGetPteAddress (PDE_BASE);
|
|
|
|
//
|
|
// Locate the page table page for hyperspace and make it resident.
|
|
//
|
|
|
|
PageDirectoryMap = MiMapPageInHyperSpace (PdePage, &OldIrql2);
|
|
|
|
TempPte = PageDirectoryMap[MiGetPdeOffset(MmWorkingSetList)];
|
|
MiUnmapPageInHyperSpace (OldIrql2);
|
|
|
|
HyperSpacePageTable = MiMakeOutswappedPageResident (
|
|
MiGetPdeAddress (HYPER_SPACE),
|
|
&TempPte,
|
|
0,
|
|
PdePage,
|
|
&Transition);
|
|
|
|
ASSERT (Pfn1->u2.ShareCount >= 3);
|
|
|
|
PageDirectoryMap = MiMapPageInHyperSpace (PdePage, &OldIrql2);
|
|
PageDirectoryMap[MiGetPdeOffset(PDE_BASE)].u.Flush =
|
|
OutProcess->PageDirectoryPte;
|
|
PageDirectoryMap[MiGetPdeOffset(MmWorkingSetList)] = TempPte;
|
|
|
|
MiUnmapPageInHyperSpace (OldIrql2);
|
|
|
|
//
|
|
// Map in the hyper space page table page and retieve the
|
|
// PTE that maps the working set list.
|
|
//
|
|
|
|
HyperSpacePageTableMap = MiMapPageInHyperSpace (HyperSpacePageTable, &OldIrql2);
|
|
TempPte = HyperSpacePageTableMap[MiGetPteOffset(MmWorkingSetList)];
|
|
MiUnmapPageInHyperSpace (OldIrql2);
|
|
Pfn1 = MI_PFN_ELEMENT (HyperSpacePageTable);
|
|
|
|
Pfn1->u1.WsIndex = 1;
|
|
|
|
WorkingSetPage = MiMakeOutswappedPageResident (
|
|
MiGetPteAddress (MmWorkingSetList),
|
|
&TempPte,
|
|
0,
|
|
HyperSpacePageTable,
|
|
&Transition);
|
|
|
|
HyperSpacePageTableMap = MiMapPageInHyperSpace (HyperSpacePageTable, &OldIrql2);
|
|
HyperSpacePageTableMap[MiGetPteOffset(MmWorkingSetList)] = TempPte;
|
|
MiUnmapPageInHyperSpace (OldIrql2);
|
|
|
|
Pfn1 = MI_PFN_ELEMENT (WorkingSetPage);
|
|
|
|
Pfn1->u1.WsIndex = 2;
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
LOCK_EXPANSION (OldIrql);
|
|
|
|
//
|
|
// Allow working set trimming on this process.
|
|
//
|
|
|
|
OutProcess->Vm.AllowWorkingSetAdjustment = TRUE;
|
|
if (OutProcess->Vm.WorkingSetExpansionLinks.Flink == MM_WS_SWAPPED_OUT) {
|
|
InsertTailList (&MmWorkingSetExpansionHead.ListHead,
|
|
&OutProcess->Vm.WorkingSetExpansionLinks);
|
|
}
|
|
UNLOCK_EXPANSION (OldIrql);
|
|
|
|
//
|
|
// Set up process structures.
|
|
//
|
|
|
|
OutProcess->WorkingSetPage = WorkingSetPage;
|
|
OutProcess->Vm.WorkingSetSize = 3;
|
|
|
|
INITIALIZE_DIRECTORY_TABLE_BASE (&Process->DirectoryTableBase[0],
|
|
PdePage);
|
|
INITIALIZE_DIRECTORY_TABLE_BASE (&Process->DirectoryTableBase[1],
|
|
HyperSpacePageTable);
|
|
|
|
OutProcess->ProcessOutswapped = FALSE;
|
|
}
|
|
OutProcess->ProcessOutswapEnabled = FALSE;
|
|
return;
|
|
}
|
|
|
|
PVOID
|
|
MiCreatePebOrTeb (
|
|
IN PEPROCESS TargetProcess,
|
|
IN ULONG Size
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine creates a TEB or PEB page within the target process.
|
|
|
|
Arguments:
|
|
|
|
TargetProcess - Supplies a pointer to the process in which to create
|
|
the structure.
|
|
|
|
Size - Supplies the size of the stucture to create a VAD for.
|
|
|
|
Return Value:
|
|
|
|
Returns the address of the base of the newly created TEB or PEB.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, attached to the specified process.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
PVOID Base;
|
|
PMMVAD Vad;
|
|
|
|
//
|
|
// Get the address creation mutex to block multiple threads from
|
|
// creating or deleting address space at the same time and
|
|
// get the working set mutex so virtual address descriptors can
|
|
// be inserted and walked.
|
|
//
|
|
|
|
LOCK_WS_AND_ADDRESS_SPACE (TargetProcess);
|
|
|
|
try {
|
|
Vad = (PMMVAD)NULL;
|
|
|
|
//
|
|
// Find a VA for a PEB on a page-size boudary.
|
|
//
|
|
|
|
Base = MiFindEmptyAddressRangeDown (
|
|
ROUND_TO_PAGES (Size),
|
|
(PVOID)((ULONG)MM_HIGHEST_VAD_ADDRESS + 1),
|
|
PAGE_SIZE);
|
|
|
|
//
|
|
// An unoccuppied address range has been found, build the virtual
|
|
// address descriptor to describe this range.
|
|
//
|
|
|
|
Vad = (PMMVAD)ExAllocatePoolWithTag (NonPagedPool,
|
|
sizeof(MMVAD),
|
|
' daV');
|
|
Vad->StartingVa = Base;
|
|
Vad->EndingVa = (PVOID)((ULONG)Base + ROUND_TO_PAGES (Size - 1) - 1);
|
|
|
|
Vad->u.LongFlags = 0;
|
|
|
|
Vad->u.VadFlags.CommitCharge = BYTES_TO_PAGES (Size);
|
|
Vad->u.VadFlags.MemCommit = 1;
|
|
Vad->u.VadFlags.PrivateMemory = 1;
|
|
Vad->u.VadFlags.Protection = MM_EXECUTE_READWRITE;
|
|
|
|
//
|
|
// Mark VAD as not deletable, no protection change.
|
|
//
|
|
|
|
Vad->u.VadFlags.NoChange = 1;
|
|
Vad->u2.LongFlags2 = 0;
|
|
Vad->u2.VadFlags2.OneSecured = 1;
|
|
Vad->u2.VadFlags2.StoredInVad = 1;
|
|
Vad->u2.VadFlags2.ReadOnly = 0;
|
|
Vad->u3.Secured.StartVa = Base;
|
|
Vad->u3.Secured.EndVa = Vad->EndingVa;
|
|
|
|
MiInsertVad (Vad);
|
|
|
|
} except (EXCEPTION_EXECUTE_HANDLER) {
|
|
|
|
//
|
|
// An exception was occurred, if pool was allocated, deallocate
|
|
// it and raise an exception for the caller.
|
|
//
|
|
|
|
if (Vad != (PMMVAD)NULL) {
|
|
ExFreePool (Vad);
|
|
}
|
|
|
|
UNLOCK_WS (TargetProcess);
|
|
UNLOCK_ADDRESS_SPACE (TargetProcess);
|
|
KeDetachProcess();
|
|
ExRaiseStatus (GetExceptionCode ());
|
|
}
|
|
|
|
UNLOCK_WS (TargetProcess);
|
|
UNLOCK_ADDRESS_SPACE (TargetProcess);
|
|
|
|
return Base;
|
|
}
|
|
|
|
PTEB
|
|
MmCreateTeb (
|
|
IN PEPROCESS TargetProcess,
|
|
IN PINITIAL_TEB InitialTeb,
|
|
IN PCLIENT_ID ClientId
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine creates a TEB page within the target process
|
|
and copies the initial TEB values into it.
|
|
|
|
Arguments:
|
|
|
|
TargetProcess - Supplies a pointer to the process in which to create
|
|
and initialize the TEB.
|
|
|
|
InitialTeb - Supplies a pointer to the initial TEB to copy into the
|
|
newly created TEB.
|
|
|
|
Return Value:
|
|
|
|
Returns the address of the base of the newly created TEB.
|
|
|
|
Can raise exceptions if no address space is available for the TEB or
|
|
the user has exceeded quota (non-paged, pagefile, commit).
|
|
|
|
Environment:
|
|
|
|
Kernel mode.
|
|
|
|
--*/
|
|
|
|
{
|
|
PTEB TebBase;
|
|
|
|
//
|
|
// If the specified process is not the current process, attach
|
|
// to the specified process.
|
|
//
|
|
|
|
KeAttachProcess (&TargetProcess->Pcb);
|
|
|
|
TebBase = (PTEB)MiCreatePebOrTeb (TargetProcess,
|
|
(ULONG)sizeof(TEB));
|
|
|
|
//
|
|
// Initialize the TEB.
|
|
//
|
|
|
|
TebBase->NtTib.ExceptionList = EXCEPTION_CHAIN_END;
|
|
TebBase->NtTib.SubSystemTib = NULL;
|
|
TebBase->NtTib.Version = OS2_VERSION;
|
|
TebBase->NtTib.ArbitraryUserPointer = NULL;
|
|
TebBase->NtTib.Self = (PNT_TIB)TebBase;
|
|
TebBase->EnvironmentPointer = NULL;
|
|
TebBase->ProcessEnvironmentBlock = TargetProcess->Peb;
|
|
TebBase->ClientId = *ClientId;
|
|
TebBase->RealClientId = *ClientId;
|
|
if (InitialTeb->OldInitialTeb.OldStackBase == NULL &&
|
|
InitialTeb->OldInitialTeb.OldStackLimit == NULL
|
|
) {
|
|
TebBase->NtTib.StackBase = InitialTeb->StackBase;
|
|
TebBase->NtTib.StackLimit = InitialTeb->StackLimit;
|
|
TebBase->DeallocationStack = InitialTeb->StackAllocationBase;
|
|
}
|
|
else {
|
|
TebBase->NtTib.StackBase = InitialTeb->OldInitialTeb.OldStackBase;
|
|
TebBase->NtTib.StackLimit = InitialTeb->OldInitialTeb.OldStackLimit;
|
|
}
|
|
TebBase->StaticUnicodeString.Buffer = TebBase->StaticUnicodeBuffer;
|
|
TebBase->StaticUnicodeString.MaximumLength = (USHORT)sizeof( TebBase->StaticUnicodeBuffer );
|
|
TebBase->StaticUnicodeString.Length = (USHORT)0;
|
|
|
|
KeDetachProcess();
|
|
return TebBase;
|
|
}
|
|
|
|
PPEB
|
|
MmCreatePeb (
|
|
IN PEPROCESS TargetProcess,
|
|
IN PINITIAL_PEB InitialPeb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine creates a PEB page within the target process
|
|
and copies the initial PEB values into it.
|
|
|
|
Arguments:
|
|
|
|
TargetProcess - Supplies a pointer to the process in which to create
|
|
and initialize the PEB.
|
|
|
|
InitialPeb - Supplies a pointer to the initial PEB to copy into the
|
|
newly created PEB.
|
|
|
|
Return Value:
|
|
|
|
Returns the address of the base of the newly created PEB.
|
|
|
|
Can raise exceptions if no address space is available for the PEB or
|
|
the user has exceeded quota (non-paged, pagefile, commit).
|
|
|
|
Environment:
|
|
|
|
Kernel mode.
|
|
|
|
--*/
|
|
|
|
{
|
|
PPEB PebBase;
|
|
NTSTATUS Status;
|
|
PVOID ViewBase;
|
|
LARGE_INTEGER SectionOffset;
|
|
PIMAGE_NT_HEADERS NtHeaders;
|
|
ULONG ViewSize, ReturnedSize;
|
|
PIMAGE_LOAD_CONFIG_DIRECTORY ImageConfigData;
|
|
|
|
ViewBase = NULL;
|
|
SectionOffset.LowPart = 0;
|
|
SectionOffset.HighPart = 0;
|
|
ViewSize = 0;
|
|
|
|
//
|
|
// If the specified process is not the current process, attach
|
|
// to the specified process.
|
|
//
|
|
|
|
KeAttachProcess (&TargetProcess->Pcb);
|
|
|
|
//
|
|
// Map the NLS tables into the applications address space
|
|
//
|
|
|
|
Status = MmMapViewOfSection(
|
|
InitNlsSectionPointer,
|
|
TargetProcess,
|
|
&ViewBase,
|
|
0L,
|
|
0L,
|
|
&SectionOffset,
|
|
&ViewSize,
|
|
ViewShare,
|
|
MEM_TOP_DOWN | SEC_NO_CHANGE,
|
|
PAGE_READONLY
|
|
);
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
KeDetachProcess();
|
|
ExRaiseStatus(Status);
|
|
}
|
|
|
|
PebBase = (PPEB)MiCreatePebOrTeb (TargetProcess,
|
|
(ULONG)sizeof( PEB ));
|
|
|
|
//
|
|
// Initialize the Peb.
|
|
//
|
|
|
|
PebBase->InheritedAddressSpace = InitialPeb->InheritedAddressSpace;
|
|
PebBase->Mutant = InitialPeb->Mutant;
|
|
PebBase->ImageBaseAddress = TargetProcess->SectionBaseAddress;
|
|
|
|
PebBase->AnsiCodePageData = (PVOID)((PUCHAR)ViewBase+InitAnsiCodePageDataOffset);
|
|
PebBase->OemCodePageData = (PVOID)((PUCHAR)ViewBase+InitOemCodePageDataOffset);
|
|
PebBase->UnicodeCaseTableData = (PVOID)((PUCHAR)ViewBase+InitUnicodeCaseTableDataOffset);
|
|
|
|
PebBase->NumberOfProcessors = KeNumberProcessors;
|
|
PebBase->BeingDebugged = (BOOLEAN)(TargetProcess->DebugPort != NULL ? TRUE : FALSE);
|
|
PebBase->NtGlobalFlag = NtGlobalFlag;
|
|
PebBase->CriticalSectionTimeout = MmCriticalSectionTimeout;
|
|
PebBase->HeapSegmentReserve = MmHeapSegmentReserve;
|
|
PebBase->HeapSegmentCommit = MmHeapSegmentCommit;
|
|
PebBase->HeapDeCommitTotalFreeThreshold = MmHeapDeCommitTotalFreeThreshold;
|
|
PebBase->HeapDeCommitFreeBlockThreshold = MmHeapDeCommitFreeBlockThreshold;
|
|
PebBase->NumberOfHeaps = 0;
|
|
PebBase->MaximumNumberOfHeaps = (PAGE_SIZE - sizeof( PEB )) / sizeof( PVOID );
|
|
PebBase->ProcessHeaps = (PVOID *)(PebBase+1);
|
|
|
|
PebBase->OSMajorVersion = NtMajorVersion;
|
|
PebBase->OSMinorVersion = NtMinorVersion;
|
|
PebBase->OSBuildNumber = NtBuildNumber & 0x3FFF;
|
|
PebBase->OSPlatformId = 2; // VER_PLATFORM_WIN32_NT from winbase.h
|
|
|
|
NtHeaders = RtlImageNtHeader( PebBase->ImageBaseAddress );
|
|
if (NtHeaders != NULL) {
|
|
PebBase->ImageSubsystem = NtHeaders->OptionalHeader.Subsystem;
|
|
PebBase->ImageSubsystemMajorVersion = NtHeaders->OptionalHeader.MajorSubsystemVersion;
|
|
PebBase->ImageSubsystemMinorVersion = NtHeaders->OptionalHeader.MinorSubsystemVersion;
|
|
|
|
//
|
|
// See if this image wants GetVersion to lie about who the system is
|
|
// If so, capture the lie into the PEB for the process.
|
|
//
|
|
|
|
if (NtHeaders->OptionalHeader.Win32VersionValue != 0) {
|
|
PebBase->OSMajorVersion = NtHeaders->OptionalHeader.Win32VersionValue & 0xFF;
|
|
PebBase->OSMinorVersion = (NtHeaders->OptionalHeader.Win32VersionValue >> 8) & 0xFF;
|
|
PebBase->OSBuildNumber = (NtHeaders->OptionalHeader.Win32VersionValue >> 16) & 0x3FFF;
|
|
|
|
//
|
|
// Win32 API GetVersion returns the following bogus bit definitions
|
|
// in the high two bits:
|
|
//
|
|
// 00 - Windows NT
|
|
// 01 - reserved
|
|
// 10 - Win32s running on Windows 3.x
|
|
// 11 - Windows 95
|
|
//
|
|
//
|
|
// Win32 API GetVersionEx returns a dwPlatformId with the following values
|
|
// defined in winbase.h
|
|
//
|
|
// 00 - VER_PLATFORM_WIN32s
|
|
// 01 - VER_PLATFORM_WIN32_WINDOWS
|
|
// 10 - VER_PLATFORM_WIN32_NT
|
|
// 11 - reserved
|
|
//
|
|
//
|
|
// So convert the former from the Win32VersionValue field into the OSPlatformId
|
|
// field. This is done by XORing with 0x2. The translation is symetric so there
|
|
// is the same code to do the reverse in windows\base\client\module.c (GetVersion)
|
|
//
|
|
|
|
PebBase->OSPlatformId = (NtHeaders->OptionalHeader.Win32VersionValue >> 30) ^ 0x2;
|
|
}
|
|
|
|
if ( MmProductType == 0 ) {
|
|
if (NtHeaders->FileHeader.Characteristics & IMAGE_FILE_AGGRESIVE_WS_TRIM ) {
|
|
TargetProcess->MmAgressiveWsTrimMask = PS_WS_TRIM_FROM_EXE_HEADER;
|
|
}
|
|
#if defined(_X86_)
|
|
if ( MmSystemSize == MmSmallSystem ) {
|
|
TargetProcess->MmAgressiveWsTrimMask |= PS_WS_TRIM_BACKGROUND_ONLY_APP;
|
|
}
|
|
#endif // _X86_
|
|
}
|
|
|
|
//
|
|
// See if image wants to override the default processor affinity mask
|
|
//
|
|
if (NtHeaders->FileHeader.Characteristics & IMAGE_FILE_UP_SYSTEM_ONLY) {
|
|
//
|
|
// Image is NOT MP safe. So assign it a processor on a rotating
|
|
// basis to spread these processes around on MP systems
|
|
//
|
|
do {
|
|
PebBase->ImageProcessAffinityMask = (KAFFINITY)(0x1 << MmRotatingUniprocessorNumber);
|
|
if (++MmRotatingUniprocessorNumber >= KeNumberProcessors) {
|
|
MmRotatingUniprocessorNumber = 0;
|
|
}
|
|
} while ((PebBase->ImageProcessAffinityMask & KeActiveProcessors) == 0);
|
|
} else {
|
|
ImageConfigData = RtlImageDirectoryEntryToData( PebBase->ImageBaseAddress,
|
|
TRUE,
|
|
IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG,
|
|
&ReturnedSize
|
|
);
|
|
if (ImageConfigData != NULL && ImageConfigData->ProcessAffinityMask != 0) {
|
|
//
|
|
// Yes, get it and pass it up to LdrpInitializeProcess via the PEB
|
|
//
|
|
PebBase->ImageProcessAffinityMask = ImageConfigData->ProcessAffinityMask;
|
|
}
|
|
}
|
|
}
|
|
|
|
KeDetachProcess();
|
|
return PebBase;
|
|
}
|
|
|
|
VOID
|
|
MmDeleteTeb (
|
|
IN PEPROCESS TargetProcess,
|
|
IN PVOID TebBase
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine deletes a TEB page wihtin the target process.
|
|
|
|
Arguments:
|
|
|
|
TargetProcess - Supplies a pointer to the process in which to delete
|
|
the TEB.
|
|
|
|
TebBase - Supplies the base address of the TEB to delete.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Kernel mode.
|
|
|
|
--*/
|
|
|
|
{
|
|
PVOID EndingAddress;
|
|
PMMVAD Vad;
|
|
|
|
EndingAddress = (PVOID)((ULONG)TebBase +
|
|
ROUND_TO_PAGES (sizeof(TEB)) - 1);
|
|
|
|
//
|
|
// Attach to the specified process.
|
|
//
|
|
|
|
KeAttachProcess (&TargetProcess->Pcb);
|
|
|
|
//
|
|
// Get the address creation mutex to block multiple threads from
|
|
// creating or deleting address space at the same time and
|
|
// get the working set mutex so virtual address descriptors can
|
|
// be inserted and walked.
|
|
//
|
|
|
|
LOCK_WS_AND_ADDRESS_SPACE (TargetProcess);
|
|
|
|
Vad = MiLocateAddress (TebBase);
|
|
|
|
ASSERT (Vad != (PMMVAD)NULL);
|
|
|
|
ASSERT ((Vad->StartingVa == TebBase) && (Vad->EndingVa == EndingAddress));
|
|
|
|
MiRemoveVad (Vad);
|
|
ExFreePool (Vad);
|
|
|
|
MiDeleteFreeVm (TebBase, EndingAddress);
|
|
|
|
UNLOCK_WS (TargetProcess);
|
|
UNLOCK_ADDRESS_SPACE (TargetProcess);
|
|
KeDetachProcess();
|
|
return;
|
|
|
|
}
|
|
|
|
VOID
|
|
MmAllowWorkingSetExpansion (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine updates the working set list head FLINK field to
|
|
indicate that working set adjustment is allowed.
|
|
|
|
NOTE: This routine may be called more than once per process.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Kernel mode.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
PEPROCESS CurrentProcess;
|
|
KIRQL OldIrql;
|
|
|
|
//
|
|
// Check the current state of the working set adjustment flag
|
|
// in the process header.
|
|
//
|
|
|
|
CurrentProcess = PsGetCurrentProcess();
|
|
|
|
LOCK_EXPANSION (OldIrql);
|
|
|
|
if (!CurrentProcess->Vm.AllowWorkingSetAdjustment) {
|
|
CurrentProcess->Vm.AllowWorkingSetAdjustment = TRUE;
|
|
|
|
InsertTailList (&MmWorkingSetExpansionHead.ListHead,
|
|
&CurrentProcess->Vm.WorkingSetExpansionLinks);
|
|
}
|
|
|
|
UNLOCK_EXPANSION (OldIrql);
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
MiDeleteAddressesInWorkingSet (
|
|
IN PEPROCESS Process
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine deletes all user mode addresses from the working set
|
|
list.
|
|
|
|
Arguments:
|
|
|
|
Process = Pointer to the current process.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, Working Set Lock held.
|
|
|
|
--*/
|
|
|
|
{
|
|
PMMWSLE Wsle;
|
|
ULONG index;
|
|
PVOID Va;
|
|
KIRQL OldIrql;
|
|
#if DBG
|
|
ULONG LastEntry;
|
|
PMMWSLE LastWsle;
|
|
#endif
|
|
|
|
//
|
|
// Go through the working set and for any page which is in the
|
|
// working set tree, rip it out of the tree by zeroing it's
|
|
// link pointers and set the WasInTree bit to indicate that
|
|
// this has been done.
|
|
//
|
|
|
|
Wsle = &MmWsle[2];
|
|
index = 2;
|
|
#if DBG
|
|
LastEntry = MmWorkingSetList->LastEntry;
|
|
#endif
|
|
while (index <= MmWorkingSetList->LastEntry) {
|
|
if ((Wsle->u1.e1.Valid == 1) &&
|
|
(Wsle->u1.e1.Direct == 0)) {
|
|
|
|
if (Wsle->u1.VirtualAddress > (PVOID)MM_HIGHEST_USER_ADDRESS) {
|
|
|
|
//
|
|
// System space address, set the WasInTree bit.
|
|
//
|
|
|
|
ASSERT (Wsle->u1.VirtualAddress > (PVOID)PDE_TOP);
|
|
Wsle->u1.e1.WasInTree = 1;
|
|
}
|
|
}
|
|
index += 1;
|
|
Wsle += 1;
|
|
}
|
|
|
|
MmWorkingSetList->HashTable = NULL;
|
|
|
|
//
|
|
// Go through the working set list and remove all pages for user
|
|
// space addresses.
|
|
//
|
|
|
|
Wsle = &MmWsle[2];
|
|
index = 2;
|
|
|
|
ASSERT (LastEntry >= MmWorkingSetList->LastEntry);
|
|
|
|
while (index <= MmWorkingSetList->LastEntry) {
|
|
if (Wsle->u1.e1.Valid == 1) {
|
|
|
|
Va = Wsle->u1.VirtualAddress;
|
|
if (Wsle->u1.VirtualAddress < (PVOID)MM_HIGHEST_USER_ADDRESS) {
|
|
|
|
//
|
|
// This is a user mode address.
|
|
//
|
|
|
|
//
|
|
// This entry is in the working set list tree.
|
|
//
|
|
|
|
MiReleaseWsle (index, &Process->Vm);
|
|
LOCK_PFN (OldIrql);
|
|
MiDeleteValidAddress (Va, Process);
|
|
UNLOCK_PFN (OldIrql);
|
|
} else {
|
|
|
|
//
|
|
// If this entry was ripped out of the working set
|
|
// tree, put it back in.
|
|
//
|
|
|
|
if (Wsle->u1.e1.WasInTree == 1) {
|
|
Wsle->u1.e1.WasInTree = 0;
|
|
MiInsertWsle (index, MmWorkingSetList);
|
|
}
|
|
ASSERT (MiGetPteAddress(Wsle->u1.VirtualAddress)->u.Hard.Valid == 1);
|
|
}
|
|
}
|
|
index += 1;
|
|
Wsle += 1;
|
|
}
|
|
#if DBG
|
|
Wsle = &MmWsle[2];
|
|
LastWsle = &MmWsle[MmWorkingSetList->LastInitializedWsle];
|
|
while (Wsle <= LastWsle) {
|
|
if (Wsle->u1.e1.Valid == 1) {
|
|
ASSERT (MiGetPteAddress(Wsle->u1.VirtualAddress)->u.Hard.Valid == 1);
|
|
}
|
|
Wsle += 1;
|
|
}
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
MiDeleteValidAddress (
|
|
IN PVOID Va,
|
|
IN PEPROCESS CurrentProcess
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine deletes the specified virtual address.
|
|
|
|
Arguments:
|
|
|
|
Va - Supplies the virtual address to delete.
|
|
|
|
CurrentProcess - Supplies the current process.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Kernel mode. PFN LOCK HELD.
|
|
|
|
--*/
|
|
|
|
{
|
|
PMMPTE PointerPde;
|
|
PMMPTE PointerPte;
|
|
PMMPFN Pfn1;
|
|
PMMCLONE_BLOCK CloneBlock;
|
|
PMMCLONE_DESCRIPTOR CloneDescriptor;
|
|
ULONG PageFrameIndex;
|
|
|
|
PointerPte = MiGetPteAddress (Va);
|
|
ASSERT (PointerPte->u.Hard.Valid == 1);
|
|
PageFrameIndex = PointerPte->u.Hard.PageFrameNumber;
|
|
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
|
|
CloneDescriptor = NULL;
|
|
|
|
if (Pfn1->u3.e1.PrototypePte == 1) {
|
|
|
|
CloneBlock = (PMMCLONE_BLOCK)Pfn1->PteAddress;
|
|
|
|
//
|
|
// Capture the state of the modified bit for this
|
|
// pte.
|
|
//
|
|
|
|
MI_CAPTURE_DIRTY_BIT_TO_PFN (PointerPte, Pfn1);
|
|
|
|
//
|
|
// Decrement the share and valid counts of the page table
|
|
// page which maps this PTE.
|
|
//
|
|
|
|
PointerPde = MiGetPteAddress (PointerPte);
|
|
MiDecrementShareAndValidCount (PointerPde->u.Hard.PageFrameNumber);
|
|
|
|
//
|
|
// Decrement the share count for the physical page.
|
|
//
|
|
|
|
MiDecrementShareCount (PageFrameIndex);
|
|
|
|
//
|
|
// Check to see if this is a fork prototype PTE and if so
|
|
// update the clone descriptor address.
|
|
//
|
|
|
|
if (Va <= MM_HIGHEST_USER_ADDRESS) {
|
|
|
|
//
|
|
// Locate the clone descriptor within the clone tree.
|
|
//
|
|
|
|
CloneDescriptor = MiLocateCloneAddress ((PVOID)CloneBlock);
|
|
}
|
|
} else {
|
|
|
|
//
|
|
// This pte is a NOT a prototype PTE, delete the physical page.
|
|
//
|
|
|
|
//
|
|
// Decrement the share and valid counts of the page table
|
|
// page which maps this PTE.
|
|
//
|
|
|
|
MiDecrementShareAndValidCount (Pfn1->PteFrame);
|
|
|
|
MI_SET_PFN_DELETED (Pfn1);
|
|
|
|
//
|
|
// Decrement the share count for the physical page. As the page
|
|
// is private it will be put on the free list.
|
|
//
|
|
|
|
MiDecrementShareCountOnly (PageFrameIndex);
|
|
|
|
//
|
|
// Decrement the count for the number of private pages.
|
|
//
|
|
|
|
CurrentProcess->NumberOfPrivatePages -= 1;
|
|
}
|
|
|
|
//
|
|
// Set the pointer to PTE to be a demand zero PTE. This allows
|
|
// the page usage count to be kept properly and handles the case
|
|
// when a page table page has only valid ptes and needs to be
|
|
// deleted later when the VADs are removed.
|
|
//
|
|
|
|
PointerPte->u.Long = MM_DEMAND_ZERO_WRITE_PTE;
|
|
|
|
if (CloneDescriptor != NULL) {
|
|
|
|
//
|
|
// Decrement the reference count for the clone block,
|
|
// note that this could release and reacquire
|
|
// the mutexes hence cannot be done until after the
|
|
// working set index has been removed.
|
|
//
|
|
|
|
if (MiDecrementCloneBlockReference ( CloneDescriptor,
|
|
CloneBlock,
|
|
CurrentProcess )) {
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
ULONG
|
|
MiMakeOutswappedPageResident (
|
|
IN PMMPTE ActualPteAddress,
|
|
IN OUT PMMPTE PointerTempPte,
|
|
IN ULONG Global,
|
|
IN ULONG ContainingPage,
|
|
OUT PULONG ActiveTransition
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine makes the specified PTE valid.
|
|
|
|
Arguments:
|
|
|
|
ActualPteAddress - Supplies the actual address that the PTE will
|
|
reside at. This is used for page coloring.
|
|
|
|
PointerTempPte - Supplies the PTE to operate on, returns a valid
|
|
PTE.
|
|
|
|
Global - Supplies 1 if the resulting PTE is global.
|
|
|
|
ContainingPage - Supplies the phyical page number of the page which
|
|
contains the resulting PTE. If this value is 0, no
|
|
operations on the containing page are performed.
|
|
|
|
ActiveTransition - Returns 1 if the in page operation was for a
|
|
transition page in the ActiveAndValid state.
|
|
|
|
Return Value:
|
|
|
|
Returns the physical page number that was allocated for the PTE.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, PFN LOCK HELD!
|
|
|
|
--*/
|
|
|
|
{
|
|
MMPTE TempPte;
|
|
KIRQL OldIrql = APC_LEVEL;
|
|
ULONG PageFrameIndex;
|
|
PMMPFN Pfn1;
|
|
ULONG MdlHack[(sizeof(MDL)/4) + 2];
|
|
PMDL Mdl;
|
|
LARGE_INTEGER StartingOffset;
|
|
KEVENT Event;
|
|
IO_STATUS_BLOCK IoStatus;
|
|
ULONG PageFileNumber;
|
|
NTSTATUS Status;
|
|
PULONG Page;
|
|
ULONG RefaultCount;
|
|
|
|
MM_PFN_LOCK_ASSERT();
|
|
|
|
ASSERT (PointerTempPte->u.Hard.Valid == 0);
|
|
|
|
*ActiveTransition = 0;
|
|
|
|
if (PointerTempPte->u.Long == MM_KERNEL_DEMAND_ZERO_PTE) {
|
|
|
|
//
|
|
// Any page will do.
|
|
//
|
|
|
|
MiEnsureAvailablePageOrWait (NULL, NULL);
|
|
PageFrameIndex = MiRemoveAnyPage (
|
|
MI_GET_PAGE_COLOR_FROM_PTE (ActualPteAddress));
|
|
|
|
MI_MAKE_VALID_PTE (TempPte,
|
|
PageFrameIndex,
|
|
MM_READWRITE,
|
|
ActualPteAddress );
|
|
MI_SET_PTE_DIRTY (TempPte);
|
|
MI_SET_GLOBAL_STATE (TempPte, Global);
|
|
|
|
*PointerTempPte = TempPte;
|
|
MiInitializePfnForOtherProcess (PageFrameIndex,
|
|
ActualPteAddress,
|
|
ContainingPage);
|
|
|
|
} else if (PointerTempPte->u.Soft.Transition == 1) {
|
|
|
|
PageFrameIndex = PointerTempPte->u.Trans.PageFrameNumber;
|
|
PointerTempPte->u.Trans.Protection = MM_READWRITE;
|
|
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
|
|
|
|
//
|
|
// PTE refers to a transition PTE.
|
|
//
|
|
|
|
if (Pfn1->u3.e1.PageLocation != ActiveAndValid) {
|
|
MiUnlinkPageFromList (Pfn1);
|
|
Pfn1->u3.e2.ReferenceCount += 1;
|
|
Pfn1->u3.e1.PageLocation = ActiveAndValid;
|
|
} else {
|
|
*ActiveTransition = 1;
|
|
}
|
|
|
|
//
|
|
// Update the PFN database, the share count is now 1 and
|
|
// the reference count is incremented as the share count
|
|
// just went from zero to 1.
|
|
//
|
|
|
|
Pfn1->u2.ShareCount += 1;
|
|
Pfn1->u3.e1.Modified = 1;
|
|
if (Pfn1->u3.e1.WriteInProgress == 0) {
|
|
|
|
//
|
|
// Release the page file space for this page.
|
|
//
|
|
|
|
MiReleasePageFileSpace (Pfn1->OriginalPte);
|
|
Pfn1->OriginalPte.u.Long = MM_KERNEL_DEMAND_ZERO_PTE;
|
|
}
|
|
|
|
MI_MAKE_TRANSITION_PTE_VALID (TempPte, PointerTempPte);
|
|
|
|
MI_SET_PTE_DIRTY (TempPte);
|
|
MI_SET_GLOBAL_STATE (TempPte, Global);
|
|
*PointerTempPte = TempPte;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Page resides in a paging file.
|
|
// Any page will do.
|
|
//
|
|
|
|
PointerTempPte->u.Soft.Protection = MM_READWRITE;
|
|
MiEnsureAvailablePageOrWait (NULL, NULL);
|
|
PageFrameIndex = MiRemoveAnyPage (
|
|
MI_GET_PAGE_COLOR_FROM_PTE (ActualPteAddress));
|
|
|
|
//
|
|
// Initialize the PFN database element, but don't
|
|
// set read in progress as collided page faults cannot
|
|
// occur here.
|
|
//
|
|
|
|
MiInitializePfnForOtherProcess (PageFrameIndex,
|
|
ActualPteAddress,
|
|
ContainingPage);
|
|
|
|
KeInitializeEvent (&Event, NotificationEvent, FALSE);
|
|
|
|
//
|
|
// Calculate the VBN for the in-page operation.
|
|
//
|
|
|
|
TempPte = *PointerTempPte;
|
|
PageFileNumber = GET_PAGING_FILE_NUMBER (TempPte);
|
|
|
|
StartingOffset.QuadPart = (LONGLONG)(GET_PAGING_FILE_OFFSET (TempPte)) <<
|
|
PAGE_SHIFT;
|
|
|
|
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
|
|
|
|
//
|
|
// Build MDL for request.
|
|
//
|
|
|
|
Mdl = (PMDL)&MdlHack[0];
|
|
MmInitializeMdl(Mdl,
|
|
MiGetVirtualAddressMappedByPte (ActualPteAddress),
|
|
PAGE_SIZE);
|
|
Mdl->MdlFlags |= MDL_PAGES_LOCKED;
|
|
|
|
Page = (PULONG)(Mdl + 1);
|
|
*Page = PageFrameIndex;
|
|
|
|
UNLOCK_PFN (OldIrql);
|
|
|
|
//
|
|
// Issue the read request.
|
|
//
|
|
|
|
RefaultCount = 0;
|
|
|
|
Refault:
|
|
Status = IoPageRead ( MmPagingFile[PageFileNumber]->File,
|
|
Mdl,
|
|
&StartingOffset,
|
|
&Event,
|
|
&IoStatus
|
|
);
|
|
|
|
if (Status == STATUS_PENDING) {
|
|
KeWaitForSingleObject( &Event,
|
|
WrPageIn,
|
|
KernelMode,
|
|
FALSE,
|
|
(PLARGE_INTEGER)NULL);
|
|
}
|
|
|
|
if (Mdl->MdlFlags & MDL_MAPPED_TO_SYSTEM_VA) {
|
|
MmUnmapLockedPages (Mdl->MappedSystemVa, Mdl);
|
|
}
|
|
|
|
if ((!NT_SUCCESS(Status)) || (!NT_SUCCESS(IoStatus.Status))) {
|
|
if ((IoStatus.Status == STATUS_INSUFFICIENT_RESOURCES) &&
|
|
(RefaultCount < 20)) {
|
|
|
|
//
|
|
// Insuffiencient resources, delay and reissue
|
|
// the in page operation.
|
|
//
|
|
|
|
KeDelayExecutionThread (KernelMode,
|
|
FALSE,
|
|
&MmHalfSecond);
|
|
KeClearEvent (&Event);
|
|
RefaultCount += 1;
|
|
goto Refault;
|
|
}
|
|
KdPrint(("MMINPAGE: status %lx io-status %lx\n",
|
|
Status, IoStatus.Status));
|
|
KeBugCheckEx (KERNEL_STACK_INPAGE_ERROR,
|
|
Status,
|
|
IoStatus.Status,
|
|
PageFileNumber,
|
|
StartingOffset.LowPart);
|
|
}
|
|
|
|
LOCK_PFN (OldIrql);
|
|
|
|
//
|
|
// Release the page file space.
|
|
//
|
|
|
|
MiReleasePageFileSpace (TempPte);
|
|
Pfn1->OriginalPte.u.Long = MM_KERNEL_DEMAND_ZERO_PTE;
|
|
|
|
MI_MAKE_VALID_PTE (TempPte,
|
|
PageFrameIndex,
|
|
MM_READWRITE,
|
|
ActualPteAddress );
|
|
MI_SET_PTE_DIRTY (TempPte);
|
|
Pfn1->u3.e1.Modified = 1;
|
|
MI_SET_GLOBAL_STATE (TempPte, Global);
|
|
|
|
*PointerTempPte = TempPte;
|
|
}
|
|
return PageFrameIndex;
|
|
}
|
|
|
|
|
|
VOID
|
|
MmSetMemoryPriorityProcess(
|
|
IN PEPROCESS Process,
|
|
IN UCHAR MemoryPriority
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sets the memory priority of a process.
|
|
|
|
Arguments:
|
|
|
|
Process - Supplies the process to update
|
|
|
|
MemoryPriority - Supplies the new memory priority of the process
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
KIRQL OldIrql;
|
|
PMMSUPPORT VmSupport;
|
|
ULONG i;
|
|
ULONG Trim;
|
|
UCHAR OldPriority;
|
|
|
|
if (MmSystemSize == MmSmallSystem && MmNumberOfPhysicalPages < ((15*1024*1024)/PAGE_SIZE)) {
|
|
|
|
//
|
|
// If this is a small system, make every process BACKGROUND.
|
|
//
|
|
|
|
MemoryPriority = MEMORY_PRIORITY_BACKGROUND;
|
|
}
|
|
|
|
LOCK_EXPANSION (OldIrql);
|
|
|
|
OldPriority = Process->Vm.MemoryPriority;
|
|
Process->Vm.MemoryPriority = MemoryPriority;
|
|
|
|
UNLOCK_EXPANSION (OldIrql);
|
|
|
|
if ((OldPriority > MemoryPriority) &&
|
|
(MmAvailablePages < MmMoreThanEnoughFreePages)) {
|
|
|
|
//
|
|
// The priority is being lowered, see if the working set
|
|
// should be trimmed.
|
|
//
|
|
|
|
VmSupport = &Process->Vm;
|
|
i = VmSupport->WorkingSetSize - VmSupport->MaximumWorkingSetSize;
|
|
if ((LONG)i > 0) {
|
|
Trim = i;
|
|
if (Trim > MmWorkingSetReductionMax) {
|
|
Trim = MmWorkingSetReductionMax;
|
|
}
|
|
if (Process != PsGetCurrentProcess()) {
|
|
KeAttachProcess (&Process->Pcb);
|
|
}
|
|
LOCK_WS (Process);
|
|
|
|
Trim = MiTrimWorkingSet (Trim,
|
|
VmSupport,
|
|
FALSE);
|
|
|
|
MmWorkingSetList->Quota = VmSupport->WorkingSetSize;
|
|
if (MmWorkingSetList->Quota < VmSupport->MinimumWorkingSetSize) {
|
|
MmWorkingSetList->Quota = VmSupport->MinimumWorkingSetSize;
|
|
}
|
|
|
|
UNLOCK_WS (Process);
|
|
KeDetachProcess ();
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
#if 0
|
|
VOID
|
|
MiVerifyReferenceCounts (
|
|
IN ULONG PdePage
|
|
)
|
|
|
|
//
|
|
// Verify the share and valid PTE counts for page directory page.
|
|
//
|
|
|
|
{
|
|
PMMPFN Pfn1;
|
|
PMMPFN Pfn3;
|
|
PMMPTE Pte1;
|
|
ULONG Share = 0;
|
|
ULONG Valid = 0;
|
|
ULONG i, ix, iy;
|
|
PMMPTE PageDirectoryMap;
|
|
KIRQL OldIrql;
|
|
|
|
PageDirectoryMap = (PMMPTE)MiMapPageInHyperSpace (PdePage, &OldIrql);
|
|
Pfn1 = MI_PFN_ELEMENT (PdePage);
|
|
Pte1 = (PMMPTE)PageDirectoryMap;
|
|
|
|
//
|
|
// Map in the non paged portion of the system.
|
|
//
|
|
|
|
ix = MiGetPdeOffset(CODE_START);
|
|
|
|
for (i=0;i < ix; i++ ) {
|
|
if (Pte1->u.Hard.Valid == 1) {
|
|
Valid += 1;
|
|
} else if ((Pte1->u.Soft.Prototype == 0) &&
|
|
(Pte1->u.Soft.Transition == 1)) {
|
|
Pfn3 = MI_PFN_ELEMENT (Pte1->u.Trans.PageFrameNumber);
|
|
if (Pfn3->u3.e1.PageLocation == ActiveAndValid) {
|
|
ASSERT (Pfn1->u2.ShareCount > 1);
|
|
Valid += 1;
|
|
} else {
|
|
Share += 1;
|
|
}
|
|
}
|
|
Pte1 += 1;
|
|
}
|
|
|
|
iy = MiGetPdeOffset(PTE_BASE);
|
|
Pte1 = &PageDirectoryMap[iy];
|
|
ix = MiGetPdeOffset(HYPER_SPACE_END) + 1;
|
|
|
|
for (i = iy; i < ix; i++) {
|
|
if (Pte1->u.Hard.Valid == 1) {
|
|
Valid += 1;
|
|
} else if ((Pte1->u.Soft.Prototype == 0) &&
|
|
(Pte1->u.Soft.Transition == 1)) {
|
|
Pfn3 = MI_PFN_ELEMENT (Pte1->u.Trans.PageFrameNumber);
|
|
if (Pfn3->u3.e1.PageLocation == ActiveAndValid) {
|
|
ASSERT (Pfn1->u2.ShareCount > 1);
|
|
Valid += 1;
|
|
} else {
|
|
Share += 1;
|
|
}
|
|
}
|
|
Pte1 += 1;
|
|
}
|
|
|
|
if (Pfn1->u2.ShareCount != (Share+Valid+1)) {
|
|
DbgPrint ("MMPROCSUP - PDE page %lx ShareCount %lx found %lx\n",
|
|
PdePage, Pfn1->u2.ShareCount, Valid+Share+1);
|
|
}
|
|
|
|
MiUnmapPageInHyperSpace (OldIrql);
|
|
ASSERT (Pfn1->u2.ShareCount == (Share+Valid+1));
|
|
return;
|
|
}
|
|
#endif //0
|