/*++ Copyright (c) 1989 Microsoft Corporation Module Name: freevm.c Abstract: This module contains the routines which implement the NtFreeVirtualMemory service. Author: Lou Perazzoli (loup) 22-May-1989 Landy Wang (landyw) 02-June-1997 Revision History: --*/ #include "mi.h" #define MEM_CHECK_COMMIT_STATE 0x400000 #define MM_VALID_PTE_SIZE (256) MMPTE MmDecommittedPte = {MM_DECOMMIT << MM_PROTECT_FIELD_SHIFT}; #if DBG extern PEPROCESS MmWatchProcess; VOID MmFooBar(VOID); #endif // DBG // #include "ntos.h" #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE,NtFreeVirtualMemory) #pragma alloc_text(PAGE,MiIsEntireRangeCommitted) #endif VOID MiProcessValidPteList( IN PMMPTE* PteList, IN ULONG Count ); ULONG MiDecommitPages( IN PVOID StartingAddress, IN PMMPTE EndingPte, IN PEPROCESS Process, IN PMMVAD_SHORT Vad ); VOID MiDeleteFreeVm( IN PVOID StartingAddress, IN PVOID EndingAddress ); NTSTATUS NtFreeVirtualMemory( IN HANDLE ProcessHandle, IN OUT PVOID* BaseAddress, IN OUT PSIZE_T RegionSize, IN ULONG FreeType ) /*++ Routine Description: This function deletes a region of pages within the virtual address space of a subject process. Arguments: ProcessHandle - An open handle to a process object. BaseAddress - The base address of the region of pages to be freed. This value is rounded down to the next host page address boundary. RegionSize - A pointer to a variable that will receive the actual size in bytes of the freed region of pages. The initial value of this argument is rounded up to the next host page size boundary. FreeType - A set of flags that describe the type of free that is to be performed for the specified region of pages. FreeType Flags MEM_DECOMMIT - The specified region of pages is to be decommitted. MEM_RELEASE - The specified region of pages is to be released. Return Value: Returns the status --*/ { PMMVAD_SHORT Vad; PMMVAD_SHORT NewVad; PMMVAD PreviousVad; PMMVAD NextVad; PEPROCESS Process; KPROCESSOR_MODE PreviousMode; PVOID StartingAddress; PVOID EndingAddress; NTSTATUS Status; ULONG Attached = FALSE; SIZE_T CapturedRegionSize; PVOID CapturedBase; PMMPTE StartingPte; PMMPTE EndingPte; SIZE_T OldQuota; SIZE_T QuotaCharge; SIZE_T CommitReduction; ULONG_PTR OldEnd; LOGICAL UserPhysicalPages; #if defined(_MIALT4K_) PVOID OriginalStartingAddress; PVOID OriginalEndingAddress; BOOLEAN EmulationFor4kPage = FALSE; #endif PAGED_CODE(); // Check to make sure FreeType is good. if ((FreeType & ~(MEM_DECOMMIT | MEM_RELEASE)) != 0) { return STATUS_INVALID_PARAMETER_4; } // One of MEM_DECOMMIT or MEM_RELEASE must be specified, but not both. if (((FreeType & (MEM_DECOMMIT | MEM_RELEASE)) == 0) || ((FreeType & (MEM_DECOMMIT | MEM_RELEASE)) == (MEM_DECOMMIT | MEM_RELEASE))) { return STATUS_INVALID_PARAMETER_4; } PreviousMode = KeGetPreviousMode(); // Establish an exception handler, probe the specified addresses // for write access and capture the initial values. try { if (PreviousMode != KernelMode) { ProbeForWritePointer(BaseAddress); ProbeForWriteUlong_ptr(RegionSize); } // Capture the base address. CapturedBase = *BaseAddress; // Capture the region size. CapturedRegionSize = *RegionSize; } except(ExSystemExceptionFilter()) { // If an exception occurs during the probe or capture // of the initial values, then handle the exception and // return the exception code as the status value. return GetExceptionCode(); } #if DBG if (MmDebug & MM_DBG_SHOW_NT_CALLS) { if (!MmWatchProcess) { DbgPrint("freevm processhandle %lx base %lx size %lx type %lx\n", ProcessHandle, CapturedBase, CapturedRegionSize, FreeType); } } #endif // Make sure the specified starting and ending addresses are // within the user part of the virtual address space. if (CapturedBase > MM_HIGHEST_USER_ADDRESS) { // Invalid base address. return STATUS_INVALID_PARAMETER_2; } if ((ULONG_PTR)MM_HIGHEST_USER_ADDRESS - (ULONG_PTR)CapturedBase < CapturedRegionSize) { // Invalid region size; return STATUS_INVALID_PARAMETER_3; } EndingAddress = (PVOID)(((LONG_PTR)CapturedBase + CapturedRegionSize - 1) | (PAGE_SIZE - 1)); StartingAddress = (PVOID)PAGE_ALIGN(CapturedBase); if (ProcessHandle == NtCurrentProcess()) { Process = PsGetCurrentProcess(); } else { // Reference the specified process handle for VM_OPERATION access. Status = ObReferenceObjectByHandle(ProcessHandle, PROCESS_VM_OPERATION, PsProcessType, PreviousMode, (PVOID*)&Process, NULL); if (!NT_SUCCESS(Status)) { return Status; } } // If the specified process is not the current process, attach to the specified process. if (PsGetCurrentProcess() != Process) { KeAttachProcess(&Process->Pcb); Attached = TRUE; } // 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. Block APCs to prevent page faults while // we own the working set mutex. LOCK_WS_AND_ADDRESS_SPACE(Process); // Make sure the address space was not deleted. if (Process->AddressSpaceDeleted != 0) { Status = STATUS_PROCESS_IS_TERMINATING; goto ErrorReturn; } #if defined(_MIALT4K_) if (CapturedRegionSize != 0) { OriginalStartingAddress = (PVOID)PAGE_4K_ALIGN(CapturedBase); OriginalEndingAddress = (PVOID)(((LONG_PTR)CapturedBase + CapturedRegionSize - 1) | (PAGE_4K - 1)); if (Process->Wow64Process != NULL) { EmulationFor4kPage = TRUE; // adjust Starting/EndingAddress for the native page size StartingAddress = PAGE_NEXT_ALIGN(OriginalStartingAddress); EndingAddress = (PVOID)((ULONG_PTR)PAGE_ALIGN((ULONG_PTR)OriginalEndingAddress + PAGE_4K) - 1); if (StartingAddress > EndingAddress) { // There is no need to free native pages UNLOCK_WS_UNSAFE(Process); goto perform_free4kpages; } } } #endif Vad = (PMMVAD_SHORT)MiLocateAddress(StartingAddress); if (Vad == NULL) { // No Virtual Address Descriptor located for Base Address. Status = STATUS_MEMORY_NOT_ALLOCATED; goto ErrorReturn; } // Found the associated Virtual Address Descriptor. if (Vad->EndingVpn < MI_VA_TO_VPN(EndingAddress)) { // The entire range to delete is not contained within a single // virtual address descriptor. Return an error. Status = STATUS_UNABLE_TO_FREE_VM; goto ErrorReturn; } // Check to ensure this Vad is deletable. Delete is required // for both decommit and release. if ((Vad->u.VadFlags.PrivateMemory == 0) || (Vad->u.VadFlags.PhysicalMapping == 1)) { Status = STATUS_UNABLE_TO_DELETE_SECTION; goto ErrorReturn; } if (Vad->u.VadFlags.NoChange == 1) { // An attempt is being made to delete a secured VAD, check // to see if this deletion is allowed. if (FreeType & MEM_RELEASE) { // Specify the whole range, this solves the problem with // splitting the VAD and trying to decide where the various secure ranges need to go. Status = MiCheckSecuredVad((PMMVAD)Vad, MI_VPN_TO_VA(Vad->StartingVpn), ((Vad->EndingVpn - Vad->StartingVpn) << PAGE_SHIFT) + (PAGE_SIZE - 1), MM_SECURE_DELETE_CHECK); } else { Status = MiCheckSecuredVad((PMMVAD)Vad, CapturedBase, CapturedRegionSize, MM_SECURE_DELETE_CHECK); } if (!NT_SUCCESS(Status)) { goto ErrorReturn; } } UserPhysicalPages = FALSE; PreviousVad = MiGetPreviousVad(Vad); NextVad = MiGetNextVad(Vad); if (FreeType & MEM_RELEASE) { // ***************************************************************** // MEM_RELEASE was specified. // ***************************************************************** // The descriptor for the address range is deletable. Remove or split the descriptor. // If the region size is zero, remove the whole VAD. if (CapturedRegionSize == 0) { // If the region size is specified as 0, the base address // must be the starting address for the region. if (MI_VA_TO_VPN(CapturedBase) != Vad->StartingVpn) { Status = STATUS_FREE_VM_NOT_AT_BASE; goto ErrorReturn; } // This Virtual Address Descriptor has been deleted. StartingAddress = MI_VPN_TO_VA(Vad->StartingVpn); EndingAddress = MI_VPN_TO_VA_ENDING(Vad->EndingVpn); // Free all the physical pages that this VAD might be mapping. // Since only the AWE lock synchronizes the remap API, carefully // remove this VAD from the list first. if (Vad->u.VadFlags.UserPhysicalPages == 1) { MiPhysicalViewRemover(Process, (PMMVAD)Vad); MiRemoveUserPhysicalPagesVad(Vad); UserPhysicalPages = TRUE; } else if (Vad->u.VadFlags.WriteWatch == 1) { MiPhysicalViewRemover(Process, (PMMVAD)Vad); } MiRemoveVad((PMMVAD)Vad); ExFreePool(Vad); #if defined(_MIALT4K_) OriginalStartingAddress = StartingAddress; OriginalEndingAddress = EndingAddress; if (Process->Wow64Process != NULL) { EmulationFor4kPage = TRUE; } else { EmulationFor4kPage = FALSE; } #endif } else { // Region's size was not specified as zero, delete the // whole VAD or split the VAD. if (MI_VA_TO_VPN(StartingAddress) == Vad->StartingVpn) { if (MI_VA_TO_VPN(EndingAddress) == Vad->EndingVpn) { // This Virtual Address Descriptor has been deleted. // Free all the physical pages that this VAD might be // mapping. Since only the AWE lock synchronizes the // remap API, carefully remove this VAD from the list first. if (Vad->u.VadFlags.UserPhysicalPages == 1) { MiPhysicalViewRemover(Process, (PMMVAD)Vad); MiRemoveUserPhysicalPagesVad(Vad); UserPhysicalPages = TRUE; } else if (Vad->u.VadFlags.WriteWatch == 1) { MiPhysicalViewRemover(Process, (PMMVAD)Vad); } MiRemoveVad((PMMVAD)Vad); ExFreePool(Vad); } else { if ((Vad->u.VadFlags.UserPhysicalPages == 1) || (Vad->u.VadFlags.WriteWatch == 1)) { // Splitting or chopping a physical VAD or a write-watch // VAD is not allowed. Status = STATUS_FREE_VM_NOT_AT_BASE; goto ErrorReturn; } // This Virtual Address Descriptor has a new starting // address. CommitReduction = MiCalculatePageCommitment( StartingAddress, EndingAddress, (PMMVAD)Vad, Process); Vad->StartingVpn = MI_VA_TO_VPN((PCHAR)EndingAddress + 1); Vad->u.VadFlags.CommitCharge -= CommitReduction; ASSERT((SSIZE_T)Vad->u.VadFlags.CommitCharge >= 0); MiReturnPageFileQuota(CommitReduction, Process); MiReturnCommitment(CommitReduction); if (Process->JobStatus & PS_JOB_STATUS_REPORT_COMMIT_CHANGES) { PsChangeJobMemoryUsage(-(SSIZE_T)CommitReduction); } MM_TRACK_COMMIT(MM_DBG_COMMIT_RETURN_NTFREEVM1, CommitReduction); Process->CommitCharge -= CommitReduction; NextVad = (PMMVAD)Vad; } } else { if ((Vad->u.VadFlags.UserPhysicalPages == 1) || (Vad->u.VadFlags.WriteWatch == 1)) { // Splitting or chopping a physical VAD or a write-watch // VAD is not allowed. Status = STATUS_FREE_VM_NOT_AT_BASE; goto ErrorReturn; } // Starting address is greater than start of VAD. if (MI_VA_TO_VPN(EndingAddress) == Vad->EndingVpn) { // Change the ending address of the VAD. CommitReduction = MiCalculatePageCommitment( StartingAddress, EndingAddress, (PMMVAD)Vad, Process); Vad->u.VadFlags.CommitCharge -= CommitReduction; MiReturnPageFileQuota(CommitReduction, Process); MiReturnCommitment(CommitReduction); if (Process->JobStatus & PS_JOB_STATUS_REPORT_COMMIT_CHANGES) { PsChangeJobMemoryUsage(-(SSIZE_T)CommitReduction); } MM_TRACK_COMMIT(MM_DBG_COMMIT_RETURN_NTFREEVM2, CommitReduction); Process->CommitCharge -= CommitReduction; Vad->EndingVpn = MI_VA_TO_VPN((PCHAR)StartingAddress - 1); PreviousVad = (PMMVAD)Vad; } else { // Split this VAD as the address range is within the VAD. // Allocate an new VAD under an exception handler // as there may not be enough quota. NewVad = ExAllocatePoolWithTag(NonPagedPool, sizeof(MMVAD_SHORT), 'SdaV'); if (NewVad == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto ErrorReturn; } CommitReduction = MiCalculatePageCommitment(StartingAddress, EndingAddress, (PMMVAD)Vad, Process); OldQuota = Vad->u.VadFlags.CommitCharge - CommitReduction; OldEnd = Vad->EndingVpn; *NewVad = *Vad; Vad->EndingVpn = MI_VA_TO_VPN((PCHAR)StartingAddress - 1); NewVad->StartingVpn = MI_VA_TO_VPN((PCHAR)EndingAddress + 1); // Set the commit charge to zero so MiInsertVad will // not charge commitment for splitting the VAD. NewVad->u.VadFlags.CommitCharge = 0; try { // Insert the VAD, this could get an exception // on charging quota. MiInsertVad((PMMVAD)NewVad); } except(EXCEPTION_EXECUTE_HANDLER) { // Inserting the Vad failed, reset the original // VAD, free new vad and return an error. Vad->EndingVpn = OldEnd; ExFreePool(NewVad); Status = GetExceptionCode(); goto ErrorReturn; } Vad->u.VadFlags.CommitCharge -= CommitReduction; MiReturnPageFileQuota(CommitReduction, Process); MiReturnCommitment(CommitReduction); if (Process->JobStatus & PS_JOB_STATUS_REPORT_COMMIT_CHANGES) { PsChangeJobMemoryUsage(-(SSIZE_T)CommitReduction); } MM_TRACK_COMMIT(MM_DBG_COMMIT_RETURN_NTFREEVM3, CommitReduction); Process->CommitCharge -= CommitReduction; // As we have split the original VAD into 2 separate VADs // there is know way of knowing what the commit charge // is for each VAD. Calculate the charge and reset // each VAD. Note that we also use the previous value // to make sure the books stay balanced. QuotaCharge = MiCalculatePageCommitment(MI_VPN_TO_VA(Vad->StartingVpn), (PCHAR)StartingAddress - 1, (PMMVAD)Vad, Process); Vad->u.VadFlags.CommitCharge = QuotaCharge; // Give the remaining charge to the new VAD. NewVad->u.VadFlags.CommitCharge = OldQuota - QuotaCharge; PreviousVad = (PMMVAD)Vad; NextVad = (PMMVAD)NewVad; } } } // Return commitment for page table pages if possible. MiReturnPageTablePageCommitment(StartingAddress, EndingAddress, Process, PreviousVad, NextVad); if (UserPhysicalPages == TRUE) { MiDeletePageTablesForPhysicalRange(StartingAddress, EndingAddress); } else { // Get the PFN mutex so the MiDeleteVirtualAddresses can be called. MiDeleteFreeVm(StartingAddress, EndingAddress); } UNLOCK_WS_UNSAFE(Process); CapturedRegionSize = 1 + (PCHAR)EndingAddress - (PCHAR)StartingAddress; // Update the virtual size in the process header. Process->VirtualSize -= CapturedRegionSize; #if defined(_MIALT4K_) if (EmulationFor4kPage == TRUE) { goto perform_free4kpages; } #endif UNLOCK_ADDRESS_SPACE(Process); if (Attached) { KeDetachProcess(); } if (ProcessHandle != NtCurrentProcess()) { ObDereferenceObject(Process); } // Establish an exception handler and write the size and base // address. try { *RegionSize = CapturedRegionSize; *BaseAddress = StartingAddress; } except(EXCEPTION_EXECUTE_HANDLER) { // An exception occurred, don't take any action (just handle // the exception and return success. } #if DBG if (MmDebug & MM_DBG_SHOW_NT_CALLS) { if (MmWatchProcess) { if (MmWatchProcess == PsGetCurrentProcess()) { DbgPrint("\n--- FREE Type 0x%lx Base %lx Size %lx\n", FreeType, StartingAddress, CapturedRegionSize); MmFooBar(); } } } #endif return STATUS_SUCCESS; } if (Vad->u.VadFlags.UserPhysicalPages == 1) { // Pages from a physical VAD must be released via // NtFreeUserPhysicalPages, not this routine. Status = STATUS_MEMORY_NOT_ALLOCATED; goto ErrorReturn; } // ************************************************************** // MEM_DECOMMIT was specified. // ************************************************************** // Check to ensure the complete range of pages is already committed. if (CapturedRegionSize == 0) { if (MI_VA_TO_VPN(CapturedBase) != Vad->StartingVpn) { Status = STATUS_FREE_VM_NOT_AT_BASE; goto ErrorReturn; } EndingAddress = MI_VPN_TO_VA_ENDING(Vad->EndingVpn); } #if defined(_MIALT4K_) OriginalStartingAddress = StartingAddress; OriginalEndingAddress = EndingAddress; if (Process->Wow64Process != NULL) { EmulationFor4kPage = TRUE; } else { EmulationFor4kPage = FALSE; } #endif #if 0 if (FreeType & MEM_CHECK_COMMIT_STATE) { if (!MiIsEntireRangeCommitted(StartingAddress, EndingAddress, Vad, Process)) { // The entire range to be decommitted is not committed, // return an error. Status = STATUS_UNABLE_TO_DECOMMIT_VM; goto ErrorReturn; } } #endif //0 // The address range is entirely committed, decommit it now. // Calculate the initial quotas and commit charges for this VAD. StartingPte = MiGetPteAddress(StartingAddress); EndingPte = MiGetPteAddress(EndingAddress); CommitReduction = 1 + EndingPte - StartingPte; // Check to see if the entire range can be decommitted by // just updating the virtual address descriptor. CommitReduction -= MiDecommitPages(StartingAddress, EndingPte, Process, Vad); // Adjust the quota charges. ASSERT((LONG)CommitReduction >= 0); MiReturnPageFileQuota(CommitReduction, Process); MiReturnCommitment(CommitReduction); MM_TRACK_COMMIT(MM_DBG_COMMIT_RETURN_NTFREEVM4, CommitReduction); Vad->u.VadFlags.CommitCharge -= CommitReduction; if (Process->JobStatus & PS_JOB_STATUS_REPORT_COMMIT_CHANGES) { PsChangeJobMemoryUsage(-(SSIZE_T)CommitReduction); } Process->CommitCharge -= CommitReduction; ASSERT((LONG)Vad->u.VadFlags.CommitCharge >= 0); #if !(defined(_MIALT4K_)) UNLOCK_WS_AND_ADDRESS_SPACE(Process); #else UNLOCK_WS_UNSAFE(Process); perform_free4kpages: if (EmulationFor4kPage == TRUE) { if (FreeType & MEM_RELEASE) { MiReleaseFor4kPage(OriginalStartingAddress, OriginalEndingAddress, Process); } else { MiDecommitFor4kPage(OriginalStartingAddress, OriginalEndingAddress, Process); } StartingAddress = OriginalStartingAddress; EndingAddress = OriginalEndingAddress; } UNLOCK_ADDRESS_SPACE(Process); #endif if (Attached) { KeDetachProcess(); } if (ProcessHandle != NtCurrentProcess()) { ObDereferenceObject(Process); } // Establish an exception handler and write the size and base // address. try { *RegionSize = 1 + (PCHAR)EndingAddress - (PCHAR)StartingAddress; *BaseAddress = StartingAddress; } except(EXCEPTION_EXECUTE_HANDLER) { NOTHING; } return STATUS_SUCCESS; ErrorReturn: UNLOCK_WS_AND_ADDRESS_SPACE(Process); if (Attached) { KeDetachProcess(); } if (ProcessHandle != NtCurrentProcess()) { ObDereferenceObject(Process); } return Status; } ULONG MiIsEntireRangeCommitted( IN PVOID StartingAddress, IN PVOID EndingAddress, IN PMMVAD Vad, IN PEPROCESS Process ) /*++ Routine Description: This routine examines the range of pages from the starting address up to and including the ending address and returns TRUE if every page in the range is committed, FALSE otherwise. Arguments: StartingAddress - Supplies the starting address of the range. EndingAddress - Supplies the ending address of the range. Vad - Supplies the virtual address descriptor which describes the range. Process - Supplies the current process. Return Value: TRUE if the entire range is committed. FALSE if any page within the range is not committed. Environment: Kernel mode, APCs disable, WorkingSetMutex and AddressCreation mutexes held. --*/ { PMMPTE PointerPte; PMMPTE LastPte; PMMPTE PointerPde; PMMPTE PointerPpe; ULONG FirstTime; ULONG Waited; PVOID Va; PAGED_CODE(); FirstTime = TRUE; PointerPde = MiGetPdeAddress(StartingAddress); PointerPte = MiGetPteAddress(StartingAddress); LastPte = MiGetPteAddress(EndingAddress); // Set the Va to the starting address + 8, this solves problems // associated with address 0 (NULL) being used as a valid virtual // address and NULL in the VAD commitment field indicating no pages // are committed. Va = (PVOID)((PCHAR)StartingAddress + 8); while (PointerPte <= LastPte) { if (MiIsPteOnPdeBoundary(PointerPte) || (FirstTime)) { // This may be a PPE/PDE boundary, check to see if both // PPE/PDE pages exist. FirstTime = FALSE; PointerPde = MiGetPteAddress(PointerPte); PointerPpe = MiGetPteAddress(PointerPde); do { while (!MiDoesPpeExistAndMakeValid(PointerPpe, Process, FALSE, &Waited)) { // No PDE exists for the starting address, check the VAD // to see if the pages are committed. PointerPpe += 1; PointerPde = MiGetVirtualAddressMappedByPte(PointerPpe); PointerPte = MiGetVirtualAddressMappedByPte(PointerPde); Va = MiGetVirtualAddressMappedByPte(PointerPte); if (PointerPte > LastPte) { // Make sure the entire range is committed. if (Vad->u.VadFlags.MemCommit == 0) { // The entire range to be decommitted is not committed, // return an error. return FALSE; } else { return TRUE; } } // Make sure the range thus far is committed. if (Vad->u.VadFlags.MemCommit == 0) { // The entire range to be decommitted is not committed, // return an error. return FALSE; } } Waited = 0; while (!MiDoesPdeExistAndMakeValid(PointerPde, Process, FALSE, &Waited)) { // No PDE exists for the starting address, check the VAD // to see if the pages are committed. PointerPde += 1; PointerPpe = MiGetPteAddress(PointerPde); PointerPte = MiGetVirtualAddressMappedByPte(PointerPde); Va = MiGetVirtualAddressMappedByPte(PointerPte); if (PointerPte > LastPte) { // Make sure the entire range is committed. if (Vad->u.VadFlags.MemCommit == 0) { // The entire range to be decommitted is not committed, // return an error. return FALSE; } else { return TRUE; } } // Make sure the range thus far is committed. if (Vad->u.VadFlags.MemCommit == 0) { // The entire range to be decommitted is not committed, // return an error. return FALSE; } #if defined (_WIN64) if (MiIsPteOnPdeBoundary(PointerPde)) { PointerPpe = MiGetPteAddress(PointerPde); Waited = 1; break; } #endif } } while (Waited != 0); } // The page table page exists, check each PTE for commitment. if (PointerPte->u.Long == 0) { // This page has not been committed, check the VAD. if (Vad->u.VadFlags.MemCommit == 0) { // The entire range to be decommitted is not committed, // return an error. return FALSE; } } else { // Has this page been explicitly decommitted? if (MiIsPteDecommittedPage(PointerPte)) { // This page has been explicitly decommitted, return an error. return FALSE; } } PointerPte += 1; Va = (PVOID)((PCHAR)(Va)+PAGE_SIZE); } return TRUE; } ULONG MiDecommitPages( IN PVOID StartingAddress, IN PMMPTE EndingPte, IN PEPROCESS Process, IN PMMVAD_SHORT Vad ) /*++ Routine Description: This routine decommits the specified range of pages. Arguments: StartingAddress - Supplies the starting address of the range. EndingPte - Supplies the ending PTE of the range. Process - Supplies the current process. Vad - Supplies the virtual address descriptor which describes the range. Return Value: Value to reduce commitment by for the VAD. Environment: Kernel mode, APCs disabled, WorkingSetMutex and AddressCreation mutexes held. --*/ { PMMPTE PointerPpe; PMMPTE PointerPde; PMMPTE PointerPte; PVOID Va; ULONG CommitReduction; PMMPTE CommitLimitPte; KIRQL OldIrql; PMMPTE ValidPteList[MM_VALID_PTE_SIZE]; ULONG count; ULONG WorkingSetIndex; PMMPFN Pfn1; PMMPFN Pfn2; PVOID SwapVa; ULONG Entry; MMWSLENTRY Locked; MMPTE PteContents; PVOID UsedPageTableHandle; PVOID UsedPageDirectoryHandle; count = 0; CommitReduction = 0; if (Vad->u.VadFlags.MemCommit) { CommitLimitPte = MiGetPteAddress(MI_VPN_TO_VA(Vad->EndingVpn)); } else { CommitLimitPte = NULL; } // Decommit each page by setting the PTE to be explicitly // decommitted. The PTEs cannot be deleted all at once as // this would set the PTEs to zero which would auto-evaluate // as committed if referenced by another thread when a page // table page is being in-paged. PointerPpe = MiGetPpeAddress(StartingAddress); PointerPde = MiGetPdeAddress(StartingAddress); PointerPte = MiGetPteAddress(StartingAddress); Va = StartingAddress; // Loop through all the PDEs which map this region and ensure that // they exist. If they don't exist create them by touching a // PTE mapped by the PDE. #if defined (_WIN64) MiMakePpeExistAndMakeValid(PointerPpe, Process, FALSE); if (PointerPde->u.Long == 0) { UsedPageDirectoryHandle = MI_GET_USED_PTES_HANDLE(PointerPte); MI_INCREMENT_USED_PTES_BY_HANDLE(UsedPageDirectoryHandle); } #endif MiMakePdeExistAndMakeValid(PointerPde, Process, FALSE); while (PointerPte <= EndingPte) { if (MiIsPteOnPdeBoundary(PointerPte)) { PointerPde = MiGetPdeAddress(Va); PointerPpe = MiGetPpeAddress(Va); if (count != 0) { MiProcessValidPteList(&ValidPteList[0], count); count = 0; } #if defined (_WIN64) MiMakePpeExistAndMakeValid(PointerPpe, Process, FALSE); if (PointerPde->u.Long == 0) { UsedPageDirectoryHandle = MI_GET_USED_PTES_HANDLE(PointerPte); MI_INCREMENT_USED_PTES_BY_HANDLE(UsedPageDirectoryHandle); } #endif MiMakePdeExistAndMakeValid(PointerPde, Process, FALSE); } // The working set lock is held. No PTEs can go from // invalid to valid or valid to invalid. Transition // PTEs can go from transition to pagefile. PteContents = *PointerPte; if (PteContents.u.Long != 0) { if (PointerPte->u.Long == MmDecommittedPte.u.Long) { // This PTE is already decommitted. CommitReduction += 1; } else { Process->NumberOfPrivatePages -= 1; if (PteContents.u.Hard.Valid == 1) { // Make sure this is not a forked PTE. Pfn1 = MI_PFN_ELEMENT(PteContents.u.Hard.PageFrameNumber); if (Pfn1->u3.e1.PrototypePte) { LOCK_PFN(OldIrql); MiDeletePte(PointerPte, Va, FALSE, Process, NULL, NULL); UNLOCK_PFN(OldIrql); Process->NumberOfPrivatePages += 1; MI_WRITE_INVALID_PTE(PointerPte, MmDecommittedPte); } else { // Pte is valid, process later when PFN lock is held. if (count == MM_VALID_PTE_SIZE) { MiProcessValidPteList(&ValidPteList[0], count); count = 0; } ValidPteList[count] = PointerPte; count += 1; // Remove address from working set list. WorkingSetIndex = Pfn1->u1.WsIndex; ASSERT(PAGE_ALIGN(MmWsle[WorkingSetIndex].u1.Long) == Va); // Check to see if this entry is locked in the working set // or locked in memory. Locked = MmWsle[WorkingSetIndex].u1.e1; MiRemoveWsle(WorkingSetIndex, MmWorkingSetList); // Add this entry to the list of free working set entries // and adjust the working set count. MiReleaseWsle(WorkingSetIndex, &Process->Vm); if ((Locked.LockedInWs == 1) || (Locked.LockedInMemory == 1)) { // This entry is locked. MmWorkingSetList->FirstDynamic -= 1; if (WorkingSetIndex != MmWorkingSetList->FirstDynamic) { SwapVa = MmWsle[MmWorkingSetList->FirstDynamic].u1.VirtualAddress; SwapVa = PAGE_ALIGN(SwapVa); Pfn2 = MI_PFN_ELEMENT( MiGetPteAddress(SwapVa)->u.Hard.PageFrameNumber); Entry = MiLocateWsle(SwapVa, MmWorkingSetList, Pfn2->u1.WsIndex); MiSwapWslEntries(Entry, WorkingSetIndex, &Process->Vm); } } MI_SET_PTE_IN_WORKING_SET(PointerPte, 0); } } else if (PteContents.u.Soft.Prototype) { // This is a forked PTE, just delete it. LOCK_PFN(OldIrql); MiDeletePte(PointerPte, Va, FALSE, Process, NULL, NULL); UNLOCK_PFN(OldIrql); Process->NumberOfPrivatePages += 1; MI_WRITE_INVALID_PTE(PointerPte, MmDecommittedPte); } else if (PteContents.u.Soft.Transition == 1) { // Transition PTE, get the PFN database lock // and reprocess this one. LOCK_PFN(OldIrql); PteContents = *PointerPte; if (PteContents.u.Soft.Transition == 1) { // PTE is still in transition, delete it. Pfn1 = MI_PFN_ELEMENT(PteContents.u.Trans.PageFrameNumber); MI_SET_PFN_DELETED(Pfn1); MiDecrementShareCount(Pfn1->PteFrame); // Check the reference count for the page, if the // reference count is zero, move the page to the // free list, if the reference count is not zero, // ignore this page. When the reference count // goes to zero, it will be placed on the free list. if (Pfn1->u3.e2.ReferenceCount == 0) { MiUnlinkPageFromList(Pfn1); MiReleasePageFileSpace(Pfn1->OriginalPte); MiInsertPageInList(MmPageLocationList[FreePageList], MI_GET_PAGE_FRAME_FROM_TRANSITION_PTE(&PteContents)); } } else { // Page MUST be in page file format! ASSERT(PteContents.u.Soft.Valid == 0); ASSERT(PteContents.u.Soft.Prototype == 0); ASSERT(PteContents.u.Soft.PageFileHigh != 0); MiReleasePageFileSpace(PteContents); } MI_WRITE_INVALID_PTE(PointerPte, MmDecommittedPte); UNLOCK_PFN(OldIrql); } else { // Must be demand zero or paging file format. if (PteContents.u.Soft.PageFileHigh != 0) { LOCK_PFN(OldIrql); MiReleasePageFileSpace(PteContents); UNLOCK_PFN(OldIrql); } else { // Don't subtract out the private page count for // a demand zero page. Process->NumberOfPrivatePages += 1; } MI_WRITE_INVALID_PTE(PointerPte, MmDecommittedPte); } } } else { // The PTE is already zero. // Increment the count of non-zero page table entries for this // page table and the number of private pages for the process. UsedPageTableHandle = MI_GET_USED_PTES_HANDLE(Va); MI_INCREMENT_USED_PTES_BY_HANDLE(UsedPageTableHandle); if (PointerPte > CommitLimitPte) { // Pte is not committed. CommitReduction += 1; } MI_WRITE_INVALID_PTE(PointerPte, MmDecommittedPte); } PointerPte += 1; Va = (PVOID)((PCHAR)Va + PAGE_SIZE); } if (count != 0) { MiProcessValidPteList(&ValidPteList[0], count); } return CommitReduction; } VOID MiProcessValidPteList( IN PMMPTE* ValidPteList, IN ULONG Count ) /*++ Routine Description: This routine flushes the specified range of valid PTEs. Arguments: ValidPteList - Supplies a pointer to an array of PTEs to flush. Count - Supplies the count of the number of elements in the array. Return Value: none. Environment: Kernel mode, APCs disabled, WorkingSetMutex and AddressCreation mutexes held. --*/ { ULONG i = 0; MMPTE_FLUSH_LIST PteFlushList; MMPTE PteContents; PMMPFN Pfn1; KIRQL OldIrql; PteFlushList.Count = Count; LOCK_PFN(OldIrql); do { PteContents = *ValidPteList[i]; ASSERT(PteContents.u.Hard.Valid == 1); Pfn1 = MI_PFN_ELEMENT(PteContents.u.Hard.PageFrameNumber); // 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(MI_GET_PAGE_FRAME_FROM_PTE(&PteContents)); if (Count < MM_MAXIMUM_FLUSH_COUNT) { PteFlushList.FlushPte[i] = ValidPteList[i]; PteFlushList.FlushVa[i] = MiGetVirtualAddressMappedByPte(ValidPteList[i]); } *ValidPteList[i] = MmDecommittedPte; i += 1; } while (i != Count); MiFlushPteList(&PteFlushList, FALSE, MmDecommittedPte); UNLOCK_PFN(OldIrql); return; } VOID MiDeleteFreeVm( IN PVOID StartingAddress, IN PVOID EndingAddress ) /*++ Routine Description: Nonpagable routine to call acquire PFN lock and call MiDeleteVirtualAddresses. Arguments: Return Value: none. Environment: Kernel mode, APCs disabled, WorkingSetMutex and AddressCreation mutexes held. --*/ { KIRQL OldIrql; LOCK_PFN(OldIrql); // Delete the address range. MiDeleteVirtualAddresses(StartingAddress, EndingAddress, FALSE, (PMMVAD)NULL); UNLOCK_PFN(OldIrql); }