Windows2000/private/ntos/mm/modwrite.c
2020-09-30 17:12:32 +02:00

3390 lines
123 KiB
C

/*++
Copyright (c) 1989 Microsoft Corporation
Module Name:
modwrite.c
Abstract:
This module contains the modified page writer for memory management.
Author:
Lou Perazzoli (loup) 10-Jun-1989
Landy Wang (landyw) 02-Jun-1997
--*/
#include "mi.h"
typedef enum _MODIFIED_WRITER_OBJECT{
NormalCase,
MappedPagesNeedWriting,
ModifiedWriterMaximumObject
} MODIFIED_WRITER_OBJECT;
typedef struct _MMWORK_CONTEXT{
LARGE_INTEGER Size;
NTSTATUS Status;
KEVENT Event;
} MMWORK_CONTEXT, *PMMWORK_CONTEXT;
typedef struct _MM_WRITE_CLUSTER{
ULONG Count;
ULONG StartIndex;
ULONG Cluster[2 * (MM_MAXIMUM_DISK_IO_SIZE / PAGE_SIZE) + 1];
} MM_WRITE_CLUSTER, *PMM_WRITE_CLUSTER;
ULONG MmWriteAllModifiedPages;
LOGICAL MiFirstPageFileCreatedAndReady = FALSE;
#define ONEMB_IN_PAGES ((1024 * 1024) / PAGE_SIZE)
NTSTATUS MiCheckForCrashDump(PFILE_OBJECT File, IN ULONG FileNumber);
VOID MiCrashDumpWorker(IN PVOID Context);
VOID MiClusterWritePages(IN PMMPFN Pfn1, IN PFN_NUMBER PageFrameIndex, IN PMM_WRITE_CLUSTER WriteCluster, IN ULONG Size);
VOID MiExtendPagingFileMaximum(IN ULONG PageFileNumber, IN PRTL_BITMAP NewBitmap);
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE,NtCreatePagingFile)
#pragma alloc_text(PAGE,MmGetPageFileInformation)
#pragma alloc_text(PAGE,MiModifiedPageWriter)
#pragma alloc_text(PAGE,MiCheckForCrashDump)
#pragma alloc_text(PAGE,MmGetCrashDumpInformation)
#pragma alloc_text(PAGE,MiCrashDumpWorker)
#pragma alloc_text(PAGELK,MiFlushAllPages)
#endif
PSECTION MmCrashDumpSection;
extern POBJECT_TYPE IoFileObjectType;
extern HANDLE PspInitialSystemProcessHandle;
LIST_ENTRY MmMappedPageWriterList;
KEVENT MmMappedPageWriterEvent;
KEVENT MmMappedFileIoComplete;
ULONG MmSystemShutdown;
SIZE_T MmOverCommit2;
SIZE_T MmPageFileFullExtendPages;
ULONG MmPageFileFullExtendCount;
#define MI_PAGEFILE_FULL_CHARGE 100
SIZE_T MiPageFileFullCharge;
LOGICAL MmPageFileFullPopupShown = FALSE;
ULONG MmModNoWriteInsert;
BOOLEAN MmSystemPageFileLocated;
NTSTATUS MiCheckPageFileMapping(IN PFILE_OBJECT File);
VOID MiInsertPageFileInList(VOID);
VOID MiGatherMappedPages(IN PMMPFN Pfn1, IN PFN_NUMBER PageFrameIndex);
VOID MiGatherPagefilePages(IN PMMPFN Pfn1, IN PFN_NUMBER PageFrameIndex);
VOID MiPageFileFull(VOID);
LOGICAL MiCauseOverCommitPopup(IN SIZE_T NumberOfPages, IN ULONG Extension);
#if DBG
ULONG_PTR MmPagingFileDebug[8192];
#endif
extern PFN_NUMBER MmMoreThanEnoughFreePages;
#define MINIMUM_PAGE_FILE_SIZE ((ULONG)(256*PAGE_SIZE))
VOID MiModifiedPageWriterWorker(VOID);
SIZE_T MiAttemptPageFileExtension(IN ULONG PageFileNumber, IN SIZE_T ExtendSize, IN SIZE_T Maximum);
NTSTATUS MiCheckPageFilePath(PFILE_OBJECT FileObject)
{
PIRP irp;
NTSTATUS status;
PDEVICE_OBJECT deviceObject;
KEVENT event;
PIO_STACK_LOCATION irpSp;
IO_STATUS_BLOCK localIoStatus;
PAGED_CODE();
// Reference the file object here so that no special checks need be made
// in I/O completion to determine whether or not to dereference the file object.
ObReferenceObject(FileObject);
// Initialize the local event.
KeInitializeEvent(&event, SynchronizationEvent, FALSE);
// Get the address of the target device object.
deviceObject = IoGetRelatedDeviceObject(FileObject);
// Allocate and initialize the I/O Request Packet (IRP) for this operation.
irp = IoAllocateIrp(deviceObject->StackSize, FALSE);
ASSERT(irp != NULL);
if (irp == NULL) {
// Don't dereference the file object, our caller will take care of that.
return STATUS_NO_MEMORY;
}
irp->Tail.Overlay.OriginalFileObject = FileObject;
irp->Tail.Overlay.Thread = PsGetCurrentThread();
irp->RequestorMode = KernelMode;
// Fill in the service independent parameters in the IRP.
irp->UserEvent = &event;
irp->Flags = IRP_SYNCHRONOUS_API;
irp->UserIosb = &localIoStatus;
irp->Overlay.AsynchronousParameters.UserApcRoutine = (PIO_APC_ROUTINE)NULL;
// Get a pointer to the stack location for the first driver. This will be
// used to pass the original function codes and parameters.
irpSp = IoGetNextIrpStackLocation(irp);
irpSp->MajorFunction = IRP_MJ_PNP;
irpSp->MinorFunction = IRP_MN_DEVICE_USAGE_NOTIFICATION;
irpSp->FileObject = FileObject;
irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
irp->AssociatedIrp.SystemBuffer = NULL;
// irp->Flags = 0;
irpSp->Parameters.UsageNotification.InPath = TRUE;
irpSp->Parameters.UsageNotification.Type = DeviceUsageTypePaging;
// Insert the packet at the head of the IRP list for the thread.
IoQueueThreadIrp(irp);
// Now simply invoke the driver at its dispatch entry with the IRP.
status = IoCallDriver(deviceObject, irp);
// Wait for the local event and copy the final status information
// back to the caller.
if (status == STATUS_PENDING) {
(VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, (PLARGE_INTEGER)NULL);
status = localIoStatus.Status;
}
return status;
}
VOID MiReleaseModifiedWriter(VOID)
/*++
Routine Description:
Nonpagable wrapper to signal the modified writer when the first pagefile
creation has completely finished.
--*/
{
KIRQL OldIrql;
LOCK_PFN(OldIrql);
MiFirstPageFileCreatedAndReady = TRUE;
UNLOCK_PFN(OldIrql);
}
NTSTATUS NtCreatePagingFile(IN PUNICODE_STRING PageFileName,
IN PLARGE_INTEGER MinimumSize,
IN PLARGE_INTEGER MaximumSize,
IN ULONG Priority OPTIONAL)
/*++
Routine Description:
This routine opens the specified file, attempts to write a page
to the specified file, and creates the necessary structures to use the file as a paging file.
If this file is the first paging file, the modified page writer is started.
This system service requires the caller to have SeCreatePagefilePrivilege.
Arguments:
PageFileName - Supplies the fully qualified file name.
MinimumSize - Supplies the starting size of the paging file.
This value is rounded up to the host page size.
MaximumSize - Supplies the maximum number of bytes to write to the file.
This value is rounded up to the host page size.
Priority - Supplies the relative priority of this paging file.
Return Value:
tbs
--*/
{
PFILE_OBJECT File;
NTSTATUS Status;
OBJECT_ATTRIBUTES PagingFileAttributes;
HANDLE FileHandle;
IO_STATUS_BLOCK IoStatus;
UNICODE_STRING CapturedName;
PWSTR CapturedBuffer;
LARGE_INTEGER CapturedMaximumSize;
LARGE_INTEGER CapturedMinimumSize;
LARGE_INTEGER SpecifiedSize;
FILE_END_OF_FILE_INFORMATION EndOfFileInformation;
KPROCESSOR_MODE PreviousMode;
BOOLEAN Attached = FALSE;
BOOLEAN HasPrivilege;
HANDLE SystemProcess;
FILE_FS_DEVICE_INFORMATION FileDeviceInfo;
ULONG ReturnedLength;
ULONG FinalStatus;
ULONG PageFileNumber;
ULONG NewMaxSizeInPages;
ULONG NewMinSizeInPages;
PMMPAGING_FILE FoundExisting;
PRTL_BITMAP NewBitmap;
PRTL_BITMAP OldBitmap;
PDEVICE_OBJECT deviceObject;
MMPAGE_FILE_EXPANSION PageExtend;
DBG_UNREFERENCED_PARAMETER(Priority);
PAGED_CODE();
CapturedBuffer = NULL;
if (MmNumberOfPagingFiles == MAX_PAGE_FILES) {
// The maximum number of paging files is already in use.
Status = STATUS_TOO_MANY_PAGING_FILES;
goto ErrorReturn0;
}
PreviousMode = KeGetPreviousMode();
try {
if (PreviousMode != KernelMode) {
// Make sure the caller has the proper privilege to make this call.
HasPrivilege = SeSinglePrivilegeCheck(SeCreatePagefilePrivilege, PreviousMode);
if (!HasPrivilege) {
Status = STATUS_PRIVILEGE_NOT_HELD;
goto ErrorReturn0;
}
// Probe arguments.
ProbeForRead(PageFileName, sizeof(*PageFileName), sizeof(UCHAR));
ProbeForRead(MaximumSize, sizeof(LARGE_INTEGER), 4);
ProbeForRead(MinimumSize, sizeof(LARGE_INTEGER), 4);
}
// Capture arguments.
CapturedMinimumSize = *MinimumSize;
#if defined (_WIN64) || defined (_X86PAE_)
if (CapturedMinimumSize.QuadPart < MINIMUM_PAGE_FILE_SIZE) {
Status = STATUS_INVALID_PARAMETER_2;
goto ErrorReturn0;
}
SpecifiedSize.QuadPart = (ROUND_TO_PAGES(CapturedMinimumSize.QuadPart)) >> PAGE_SHIFT;
if (SpecifiedSize.HighPart != 0) {
Status = STATUS_INVALID_PARAMETER_2;
goto ErrorReturn0;
}
#else
if ((CapturedMinimumSize.HighPart != 0) || (CapturedMinimumSize.LowPart < MINIMUM_PAGE_FILE_SIZE)) {
Status = STATUS_INVALID_PARAMETER_2;
goto ErrorReturn0;
}
#endif
CapturedMaximumSize = *MaximumSize;
#if defined (_WIN64) || defined (_X86PAE_)
SpecifiedSize.QuadPart = (ROUND_TO_PAGES(CapturedMaximumSize.QuadPart)) >> PAGE_SHIFT;
if (SpecifiedSize.HighPart != 0) {
Status = STATUS_INVALID_PARAMETER_3;
goto ErrorReturn0;
}
#else
if (CapturedMaximumSize.HighPart != 0) {
Status = STATUS_INVALID_PARAMETER_3;
goto ErrorReturn0;
}
#endif
if (CapturedMinimumSize.QuadPart > CapturedMaximumSize.QuadPart) {
Status = STATUS_INVALID_PARAMETER_3;
goto ErrorReturn0;
}
CapturedName = *PageFileName;
CapturedName.MaximumLength = CapturedName.Length;
if ((CapturedName.Length == 0) || (CapturedName.Length > MAXIMUM_FILENAME_LENGTH)) {
Status = STATUS_OBJECT_NAME_INVALID;
goto ErrorReturn0;
}
if (PreviousMode != KernelMode) {
ProbeForRead(CapturedName.Buffer, CapturedName.Length, sizeof(UCHAR));
}
CapturedBuffer = ExAllocatePoolWithTag(PagedPool, (ULONG)CapturedName.Length, ' mM');
if (CapturedBuffer == NULL) {
Status = STATUS_INSUFFICIENT_RESOURCES;
goto ErrorReturn0;
}
// Copy the string to the allocated buffer.
RtlMoveMemory(CapturedBuffer, CapturedName.Buffer, CapturedName.Length);
// Point the buffer to the string that was just copied.
CapturedName.Buffer = CapturedBuffer;
} 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.
if (CapturedBuffer != NULL) {
ExFreePool(CapturedBuffer);
}
Status = GetExceptionCode();
goto ErrorReturn0;
}
// Open a paging file and get the size.
InitializeObjectAttributes(&PagingFileAttributes, &CapturedName, OBJ_CASE_INSENSITIVE, NULL, NULL);
#if defined (_WIN64) || defined (_X86PAE_)
EndOfFileInformation.EndOfFile.QuadPart = ROUND_TO_PAGES(CapturedMinimumSize.QuadPart);
#else
EndOfFileInformation.EndOfFile.HighPart = 0;
#endif
EndOfFileInformation.EndOfFile.LowPart = (ULONG)ROUND_TO_PAGES(CapturedMinimumSize.LowPart);
Status = IoCreateFile(&FileHandle,
FILE_READ_DATA | FILE_WRITE_DATA | SYNCHRONIZE,
&PagingFileAttributes,
&IoStatus,
&CapturedMinimumSize,
FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM,
FILE_SHARE_WRITE,
FILE_SUPERSEDE,
FILE_NO_INTERMEDIATE_BUFFERING | FILE_NO_COMPRESSION,
(PVOID)NULL,
0L,
CreateFileTypeNone,
(PVOID)NULL,
IO_OPEN_PAGING_FILE | IO_NO_PARAMETER_CHECKING);
if (!NT_SUCCESS(Status)) {
// Treat this as an extension of an existing pagefile maximum -
// and try to open rather than create the paging file specified.
Status = IoCreateFile(&FileHandle,
FILE_WRITE_DATA | SYNCHRONIZE,
&PagingFileAttributes,
&IoStatus,
&CapturedMinimumSize,
FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM,
FILE_SHARE_READ | FILE_SHARE_WRITE,
FILE_OPEN,
FILE_NO_INTERMEDIATE_BUFFERING | FILE_NO_COMPRESSION,
(PVOID)NULL,
0L,
CreateFileTypeNone,
(PVOID)NULL,
IO_OPEN_PAGING_FILE | IO_NO_PARAMETER_CHECKING);
if (!NT_SUCCESS(Status)) {
#if DBG
if (Status != STATUS_DISK_FULL) {
DbgPrint("MM MODWRITE: unable to open paging file %wZ - status = %X \n", &CapturedName, Status);
}
#endif
goto ErrorReturn1;
}
Status = ObReferenceObjectByHandle(FileHandle,
FILE_READ_DATA | FILE_WRITE_DATA,
IoFileObjectType,
KernelMode,
(PVOID *)&File,
NULL);
if (!NT_SUCCESS(Status)) {
goto ErrorReturn2;
}
FoundExisting = NULL;
ExAcquireFastMutex(&MmPageFileCreationLock);
for (PageFileNumber = 0; PageFileNumber < MmNumberOfPagingFiles; PageFileNumber += 1) {
if (MmPagingFile[PageFileNumber]->File->SectionObjectPointer == File->SectionObjectPointer) {
FoundExisting = MmPagingFile[PageFileNumber];
break;
}
}
if (FoundExisting == NULL) {
Status = STATUS_NOT_FOUND;
goto ErrorReturn4;
}
// Check for increases in the minimum or the maximum paging file sizes.
// Decreasing either paging file size on the fly is not allowed.
NewMaxSizeInPages = (ULONG)(CapturedMaximumSize.QuadPart >> PAGE_SHIFT);
NewMinSizeInPages = (ULONG)(CapturedMinimumSize.QuadPart >> PAGE_SHIFT);
if (FoundExisting->MinimumSize > NewMinSizeInPages) {
Status = STATUS_INVALID_PARAMETER_2;
goto ErrorReturn4;
}
if (FoundExisting->MaximumSize > NewMaxSizeInPages) {
Status = STATUS_INVALID_PARAMETER_3;
goto ErrorReturn4;
}
if (NewMaxSizeInPages > FoundExisting->MaximumSize) {
// Make sure that the pagefile increase doesn't cause the commit
// limit (in pages) to wrap. Currently this can only happen on
// PAE systems where 16 pagefiles of 16TB (==256TB) is greater
// than the 32-bit commit variable (max is 16TB).
if (MmTotalCommitLimitMaximum + (NewMaxSizeInPages - FoundExisting->MaximumSize) <= MmTotalCommitLimitMaximum) {
Status = STATUS_INVALID_PARAMETER_3;
goto ErrorReturn4;
}
// Handle the increase to the maximum paging file size.
MiCreateBitMap(&NewBitmap, NewMaxSizeInPages, NonPagedPool);
if (NewBitmap == NULL) {
Status = STATUS_INSUFFICIENT_RESOURCES;
goto ErrorReturn4;
}
OldBitmap = FoundExisting->Bitmap;
MiExtendPagingFileMaximum(PageFileNumber, NewBitmap);
MiRemoveBitMap(&OldBitmap);
// We may be low on commitment and/or may have put a temporary
// stopgate on things. Clear up the logjam now by forcing an
// extension and immediately returning it.
if (MmTotalCommittedPages + 100 > MmTotalCommitLimit) {
if (MiChargeCommitment(200, NULL) == TRUE) {
MiReturnCommitment(200);
}
}
}
if (NewMinSizeInPages > FoundExisting->MinimumSize) {
// Handle the increase to the minimum paging file size.
if (NewMinSizeInPages > FoundExisting->Size) {
// Queue a message to the segment dereferencing / pagefile
// extending thread to see if the page file can be extended.
PageExtend.RequestedExpansionSize = NewMinSizeInPages - FoundExisting->Size;
PageExtend.Segment = NULL;
PageExtend.PageFileNumber = PageFileNumber;
KeInitializeEvent(&PageExtend.Event, NotificationEvent, FALSE);
MiIssuePageExtendRequest(&PageExtend);
}
// The current size is now greater than the new desired minimum.
// Ensure subsequent contractions obey this new minimum.
if (FoundExisting->Size >= NewMinSizeInPages) {
ASSERT(FoundExisting->Size >= FoundExisting->MinimumSize);
ASSERT(NewMinSizeInPages >= FoundExisting->MinimumSize);
FoundExisting->MinimumSize = NewMinSizeInPages;
} else {
// The pagefile could not be expanded to handle the new minimum.
// No easy way to undo any maximum raising that may have been
// done as the space may have already been used, so just set
// Status so our caller knows it didn't all go perfectly.
Status = STATUS_INSUFFICIENT_RESOURCES;
}
}
goto ErrorReturn4;
}
if (!NT_SUCCESS(IoStatus.Status)) {
KdPrint(("MM MODWRITE: unable to open paging file %wZ - iosb %lx\n", &CapturedName, IoStatus.Status));
Status = IoStatus.Status;
goto ErrorReturn1;
}
// Make sure that the pagefile increase doesn't cause the commit
// limit (in pages) to wrap. Currently this can only happen on
// PAE systems where 16 pagefiles of 16TB (==256TB) is greater
// than the 32-bit commit variable (max is 16TB).
if (MmTotalCommitLimitMaximum + (CapturedMaximumSize.QuadPart >> PAGE_SHIFT)
<= MmTotalCommitLimitMaximum) {
Status = STATUS_INVALID_PARAMETER_3;
goto ErrorReturn2;
}
Status = ZwSetInformationFile(FileHandle,
&IoStatus,
&EndOfFileInformation,
sizeof(EndOfFileInformation),
FileEndOfFileInformation);
if (!NT_SUCCESS(Status)) {
KdPrint(("MM MODWRITE: unable to set length of paging file %wZ status = %X \n", &CapturedName, Status));
goto ErrorReturn2;
}
if (!NT_SUCCESS(IoStatus.Status)) {
KdPrint(("MM MODWRITE: unable to set length of paging file %wZ - iosb %lx\n", &CapturedName, IoStatus.Status));
Status = IoStatus.Status;
goto ErrorReturn2;
}
Status = ObReferenceObjectByHandle(FileHandle,
FILE_READ_DATA | FILE_WRITE_DATA,
IoFileObjectType,
KernelMode,
(PVOID *)&File,
NULL);
if (!NT_SUCCESS(Status)) {
KdPrint(("MM MODWRITE: Unable to reference paging file - %wZ\n", &CapturedName));
goto ErrorReturn2;
}
// Get the address of the target device object and ensure
// the specified file is of a suitable type.
deviceObject = IoGetRelatedDeviceObject(File);
if ((deviceObject->DeviceType != FILE_DEVICE_DISK_FILE_SYSTEM) &&
(deviceObject->DeviceType != FILE_DEVICE_NETWORK_FILE_SYSTEM) &&
(deviceObject->DeviceType != FILE_DEVICE_DFS_VOLUME) &&
(deviceObject->DeviceType != FILE_DEVICE_DFS_FILE_SYSTEM)) {
KdPrint(("MM MODWRITE: Invalid paging file type - %x\n", deviceObject->DeviceType));
Status = STATUS_UNRECOGNIZED_VOLUME;
goto ErrorReturn3;
}
// Make sure the specified file is not currently being used as a mapped data file.
Status = MiCheckPageFileMapping(File);
if (!NT_SUCCESS(Status)) {
goto ErrorReturn3;
}
// Make sure the volume is not a floppy disk.
Status = IoQueryVolumeInformation(File,
FileFsDeviceInformation,
sizeof(FILE_FS_DEVICE_INFORMATION),
&FileDeviceInfo,
&ReturnedLength);
if (FILE_FLOPPY_DISKETTE & FileDeviceInfo.Characteristics) {
Status = STATUS_FLOPPY_VOLUME;
goto ErrorReturn3;
}
// Check with all of the drivers along the path to the file to ensure
// that they are willing to follow the rules required of them and to
// give them a chance to lock down code and data that needs to be locked.
// If any of the drivers along the path refuses to participate, fail the
// pagefile creation.
// BUGBUG: Failing the pagefile creation is commented out until the
// storage drivers have been modified to correctly handle this request.
Status = MiCheckPageFilePath(File);
if (!NT_SUCCESS(Status)) {
KdPrint(("MiCheckPageFilePath(%wZ) FAILED: %x\n", &CapturedName, Status));
//goto ErrorReturn3;
}
// Acquire the global page file creation mutex.
ExAcquireFastMutex(&MmPageFileCreationLock);
MmPagingFile[MmNumberOfPagingFiles] = ExAllocatePoolWithTag(NonPagedPool, sizeof(MMPAGING_FILE), ' mM');
if (MmPagingFile[MmNumberOfPagingFiles] == NULL) {
// Allocate pool failed.
Status = STATUS_INSUFFICIENT_RESOURCES;
goto ErrorReturn4;
}
RtlZeroMemory(MmPagingFile[MmNumberOfPagingFiles], sizeof(MMPAGING_FILE));
MmPagingFile[MmNumberOfPagingFiles]->File = File;
MmPagingFile[MmNumberOfPagingFiles]->Size = (ULONG)(CapturedMinimumSize.QuadPart >> PAGE_SHIFT);
MmPagingFile[MmNumberOfPagingFiles]->MinimumSize = MmPagingFile[MmNumberOfPagingFiles]->Size;
MmPagingFile[MmNumberOfPagingFiles]->FreeSpace = MmPagingFile[MmNumberOfPagingFiles]->Size - 1;
MmPagingFile[MmNumberOfPagingFiles]->MaximumSize = (PFN_NUMBER)(CapturedMaximumSize.QuadPart >> PAGE_SHIFT);
MmPagingFile[MmNumberOfPagingFiles]->PageFileNumber = MmNumberOfPagingFiles;
// Adjust the commit page limit to reflect the new page file space.
MmPagingFile[MmNumberOfPagingFiles]->Entry[0] = ExAllocatePoolWithTag(NonPagedPool,
sizeof(MMMOD_WRITER_MDL_ENTRY) + MmModifiedWriteClusterSize * sizeof(PFN_NUMBER),
' mM');
if (MmPagingFile[MmNumberOfPagingFiles]->Entry[0] == NULL) {
// Allocate pool failed.
Status = STATUS_INSUFFICIENT_RESOURCES;
goto ErrorReturn5;
}
RtlZeroMemory(MmPagingFile[MmNumberOfPagingFiles]->Entry[0], sizeof(MMMOD_WRITER_MDL_ENTRY));
MmPagingFile[MmNumberOfPagingFiles]->Entry[0]->PagingListHead = &MmPagingFileHeader;
MmPagingFile[MmNumberOfPagingFiles]->Entry[0]->PagingFile = MmPagingFile[MmNumberOfPagingFiles];
MmPagingFile[MmNumberOfPagingFiles]->Entry[1] = ExAllocatePoolWithTag(NonPagedPool,
sizeof(MMMOD_WRITER_MDL_ENTRY) + MmModifiedWriteClusterSize * sizeof(PFN_NUMBER),
' mM');
if (MmPagingFile[MmNumberOfPagingFiles]->Entry[1] == NULL) {
// Allocate pool failed.
Status = STATUS_INSUFFICIENT_RESOURCES;
goto ErrorReturn6;
}
RtlZeroMemory(MmPagingFile[MmNumberOfPagingFiles]->Entry[1], sizeof(MMMOD_WRITER_MDL_ENTRY));
MmPagingFile[MmNumberOfPagingFiles]->Entry[1]->PagingListHead = &MmPagingFileHeader;
MmPagingFile[MmNumberOfPagingFiles]->Entry[1]->PagingFile = MmPagingFile[MmNumberOfPagingFiles];
MmPagingFile[MmNumberOfPagingFiles]->PageFileName = CapturedName;
MiCreateBitMap(&MmPagingFile[MmNumberOfPagingFiles]->Bitmap,
MmPagingFile[MmNumberOfPagingFiles]->MaximumSize,
NonPagedPool);
if (MmPagingFile[MmNumberOfPagingFiles]->Bitmap == NULL) {
// Allocate pool failed.
Status = STATUS_INSUFFICIENT_RESOURCES;
goto ErrorReturn7;
}
RtlSetAllBits(MmPagingFile[MmNumberOfPagingFiles]->Bitmap);
// Set the first bit as 0 is an invalid page location, clear the following bits.
RtlClearBits(MmPagingFile[MmNumberOfPagingFiles]->Bitmap,
1,
(ULONG)(MmPagingFile[MmNumberOfPagingFiles]->Size - 1));
PageFileNumber = MmNumberOfPagingFiles;
MiInsertPageFileInList();
FinalStatus = MiCheckForCrashDump(File, PageFileNumber);
if (PageFileNumber == 0) {
// The first paging file has been created and reservation of any
// crashdump pages has completed, signal the modified page writer.
MiReleaseModifiedWriter();
}
ExReleaseFastMutex(&MmPageFileCreationLock);
// Note that the file handle is not closed to prevent the
// paging file from being deleted or opened again. (Actually,
// the file handle is duped to the system process so process
// termination will not close the handle).
Status = ObOpenObjectByPointer(PsInitialSystemProcess, 0, NULL, 0, PsProcessType, KernelMode, &SystemProcess);
if (!NT_SUCCESS(Status)) {
ZwClose(FileHandle);
return FinalStatus;
}
Status = ZwDuplicateObject(NtCurrentProcess(), FileHandle, SystemProcess, NULL, 0, 0, DUPLICATE_SAME_ATTRIBUTES | DUPLICATE_SAME_ACCESS);
ASSERT(NT_SUCCESS(Status));
if (!MmSystemPageFileLocated) {
MmSystemPageFileLocated = IoPageFileCreated(FileHandle);
}
ZwClose(SystemProcess);
ZwClose(FileHandle);
return FinalStatus;
// Error returns:
ErrorReturn7:
ExFreePool(MmPagingFile[MmNumberOfPagingFiles]->Entry[0]);
ErrorReturn6:
ExFreePool(MmPagingFile[MmNumberOfPagingFiles]->Entry[1]);
ErrorReturn5:
ExFreePool(MmPagingFile[MmNumberOfPagingFiles]);
ErrorReturn4:
ExReleaseFastMutex(&MmPageFileCreationLock);
ErrorReturn3:
ObDereferenceObject(File);
ErrorReturn2:
ZwClose(FileHandle);
ErrorReturn1:
ExFreePool(CapturedBuffer);
ErrorReturn0:
return Status;
}
VOID MiExtendPagingFileMaximum(IN ULONG PageFileNumber, IN PRTL_BITMAP NewBitmap)
/*++
Routine Description:
This routine switches from the old bitmap to the new (larger) bitmap.
Arguments:
PageFileNumber - Supplies the paging file number to be extended.
NewBitmap - Supplies the new bitmap to use.
Return Value:
None.
Environment:
Kernel mode, APC_LEVEL, MmPageFileCreationLock held.
--*/
{
KIRQL OldIrql;
PRTL_BITMAP OldBitmap;
OldBitmap = MmPagingFile[PageFileNumber]->Bitmap;
RtlSetAllBits(NewBitmap);
LOCK_PFN(OldIrql);
// Copy the bits from the existing map.
RtlCopyMemory(NewBitmap->Buffer,
OldBitmap->Buffer,
((OldBitmap->SizeOfBitMap + 31) / 32) * sizeof(ULONG));
MmTotalCommitLimitMaximum += (NewBitmap->SizeOfBitMap - OldBitmap->SizeOfBitMap);
MmPagingFile[PageFileNumber]->MaximumSize = NewBitmap->SizeOfBitMap;
MmPagingFile[PageFileNumber]->Bitmap = NewBitmap;
// If any MDLs are waiting for space, get them up now.
if (!IsListEmpty(&MmFreePagingSpaceLow)) {
MiUpdateModifiedWriterMdls(PageFileNumber);
}
UNLOCK_PFN(OldIrql);
}
NTSTATUS MiCheckForCrashDump(PFILE_OBJECT File, IN ULONG FileNumber)
/*++
Routine Description:
This routine checks the first page of the paging file to
determine if a crash dump exists. If a crash dump is found
a section is created which maps the crash dump. A handle
to the section is created via NtQuerySystemInformation specifying SystemCrashDumpInformation.
Arguments:
File - Supplies a pointer to the file object for the paging file.
FileNumber - Supplies the index into the paging file array.
Return Value:
Returns STATUS_CRASH_DUMP if a crash dump exists, success otherwise.
--*/
{
PMDL Mdl;
LARGE_INTEGER Offset = {0,0};
LARGE_INTEGER DumpSpaceUsed;
PFN_NUMBER DumpSpaceUsedInPages;
PULONG Block;
IO_STATUS_BLOCK IoStatus;
NTSTATUS Status;
PFN_NUMBER j;
PPFN_NUMBER Page;
NTSTATUS FinalStatus;
PMMPTE PointerPte;
PMMPFN Pfn;
PFN_NUMBER MdlHack[(sizeof(MDL) / sizeof(PFN_NUMBER)) + 1];
WORK_QUEUE_ITEM WorkItem;
MMWORK_CONTEXT Context;
FinalStatus = STATUS_SUCCESS;
Mdl = (PMDL)&MdlHack[0];
MmCreateMdl(Mdl, NULL, PAGE_SIZE);
Mdl->MdlFlags |= MDL_PAGES_LOCKED;
Page = (PPFN_NUMBER)(Mdl + 1);
*Page = MiGetPageForHeader();
Block = MmGetSystemAddressForMdlSafe(Mdl, HighPagePriority);
if (Block == NULL) {
MiRemoveImageHeaderPage(*Page);
return STATUS_INSUFFICIENT_RESOURCES;
}
KeInitializeEvent(&Context.Event, NotificationEvent, FALSE);
Status = IoPageRead(File, Mdl, &Offset, &Context.Event, &IoStatus);
if (Status == STATUS_PENDING) {
KeWaitForSingleObject(&Context.Event, WrPageIn, KernelMode, FALSE, (PLARGE_INTEGER)NULL);
}
KeClearEvent(&Context.Event);
DumpSpaceUsed.LowPart = Block[DH_REQUIRED_DUMP_SPACE];
DumpSpaceUsed.HighPart = Block[DH_REQUIRED_DUMP_SPACE + 1];
DumpSpaceUsedInPages = (PFN_NUMBER)(DumpSpaceUsed.QuadPart >> PAGE_SHIFT);
if ((Block[0] == 'EGAP') && (Block[1] == 'PMUD') && (DumpSpaceUsedInPages <= MmPagingFile[FileNumber]->Size)) {
// A crash dump already exists, don't let pager use
// it and build named section for it.
Context.Size.QuadPart = DumpSpaceUsed.QuadPart;
ExInitializeWorkItem(&WorkItem, MiCrashDumpWorker, (PVOID)&Context);
ExQueueWorkItem(&WorkItem, DelayedWorkQueue);
KeWaitForSingleObject(&Context.Event, WrPageIn, KernelMode, FALSE, (PLARGE_INTEGER)NULL);
KeClearEvent(&Context.Event);
if (!NT_SUCCESS(Context.Status)) {
goto Failed;
}
// Make the section point to the paging file.
PointerPte = MmCrashDumpSection->Segment->PrototypePte;
MI_WRITE_INVALID_PTE(PointerPte, MmCrashDumpSection->Segment->SegmentPteTemplate);
Pfn = MI_PFN_ELEMENT(*Page);
#if PFN_CONSISTENCY
MiSetModified(Pfn, 1);
#else
Pfn->u3.e1.Modified = 1;
#endif
PointerPte += 1;
for (j = 1; j < DumpSpaceUsedInPages; j += 1) {
MI_SET_PAGING_FILE_INFO(*PointerPte, MmCrashDumpSection->Segment->SegmentPteTemplate, FileNumber, j);
#if DBG
if ((j < 8192) && (FileNumber == 0)) {
ASSERT((MmPagingFileDebug[j] & 1) == 0);
MmPagingFileDebug[j] = (((ULONG_PTR)PointerPte << 3) | 1);
}
#endif //DBG
PointerPte += 1;
}
// Change the original PTE contents to refer to
// the paging file offset where this was written.
RtlSetBits(MmPagingFile[FileNumber]->Bitmap, 1, (ULONG)DumpSpaceUsedInPages - 1);
MmPagingFile[FileNumber]->FreeSpace -= DumpSpaceUsedInPages;
MmPagingFile[FileNumber]->CurrentUsage += DumpSpaceUsedInPages;
FinalStatus = STATUS_CRASH_DUMP;
Failed:
// Indicate that no crash dump is in file so if system is
// rebooted the page file is available.
Block[1] = 'EGAP';
} else {
// Set new pattern into file.
RtlFillMemoryUlong(Block, PAGE_SIZE, 'EGAP');
#if !defined(_WIN64)
// This bit of code does not work on Win64. Worse, it generates
// alignment faults. So, until it (and the other components that
// deal with crashdump blocks) is fixed, it is disabled for Win64.
*(PULONG_PTR)(&Block[4]) = PsInitialSystemProcess->Pcb.DirectoryTableBase[0];
*(PULONG *)(&Block[5]) = (PULONG)MmPfnDatabase;
*(PLIST_ENTRY *)(&Block[6]) = (PLIST_ENTRY)&PsLoadedModuleList;
*(PLIST_ENTRY *)(&Block[7]) = (PLIST_ENTRY)&PsActiveProcessHead;
#endif
Block[8] =
#ifdef _X86_
IMAGE_FILE_MACHINE_I386;
#endif //_X86_
#ifdef _ALPHA_
IMAGE_FILE_MACHINE_ALPHA;
#endif //_ALPHA_
#ifdef _IA64_
IMAGE_FILE_MACHINE_IA64;
#endif //_IA64_
ExAcquireFastMutex(&MmDynamicMemoryMutex);
RtlCopyMemory(&Block[DH_PHYSICAL_MEMORY_BLOCK],
MmPhysicalMemoryBlock,
(sizeof(PHYSICAL_MEMORY_DESCRIPTOR) +
(sizeof(PHYSICAL_MEMORY_RUN) *
(MmPhysicalMemoryBlock->NumberOfRuns - 1))));
ExReleaseFastMutex(&MmDynamicMemoryMutex);
}
Status = IoSynchronousPageWrite(File, Mdl, &Offset, &Context.Event, &IoStatus);
KeWaitForSingleObject(&Context.Event, WrVirtualMemory, KernelMode, FALSE, (PLARGE_INTEGER)NULL);
MmUnmapLockedPages(Mdl->MappedSystemVa, Mdl);
if (FinalStatus == STATUS_CRASH_DUMP) {
// Set the first page to point to the page that was just operated upon.
MiUpdateImageHeaderPage(MmCrashDumpSection->Segment->PrototypePte, *Page, MmCrashDumpSection->Segment->ControlArea);
} else {
MiRemoveImageHeaderPage(*Page);
}
return FinalStatus;
}
VOID MiCrashDumpWorker(IN PVOID Context)
/*++
Routine Description:
This function is called in the context of a delayed worker thread.
Its function is to create a section which will be used to map the crash dump in the paging file.
Arguments:
Context - Supplies the context record which contains the section's
size, an event to set at completion and a status value to be returned.
Return Value:
None.
--*/
{
PMMWORK_CONTEXT Work;
OBJECT_ATTRIBUTES ObjectAttributes;
PAGED_CODE();
Work = (PMMWORK_CONTEXT)Context;
InitializeObjectAttributes(&ObjectAttributes, NULL, 0, NULL, NULL);
Work->Status = MmCreateSection(&MmCrashDumpSection,
SECTION_MAP_READ,
&ObjectAttributes,
&Work->Size,
PAGE_READONLY,
SEC_COMMIT,
NULL,
NULL);
KeSetEvent(&Work->Event, 0, FALSE);
}
NTSTATUS MmGetCrashDumpInformation(IN PSYSTEM_CRASH_DUMP_INFORMATION CrashInfo)
/*++
Routine Description:
This function checks to see if a crash dump section exists and
if so creates a handle to the section and returns that value
in the CrashDumpInformation structure. Once the handle to the
section has been created, no other references can be made
to the crash dump section, and when that handle is closed, the
crash dump section is deleted and the paging file space is
available for reuse.
Arguments:
CrashInfo - Supplies a pointer to the crash dump information structure.
Return Value:
Status of the operation. A handle value of zero indicates no
crash dump was located.
--*/
{
NTSTATUS Status;
HANDLE Handle;
PAGED_CODE();
if (MmCrashDumpSection == NULL) {
Handle = 0;
Status = STATUS_SUCCESS;
} else {
Status = ObInsertObject(MmCrashDumpSection, NULL, SECTION_MAP_READ, 0, (PVOID *)NULL, &Handle);
if (NT_SUCCESS(Status)) {
// One shot operation.
MmCrashDumpSection = NULL;
}
}
CrashInfo->CrashDumpSection = Handle;
return Status;
}
NTSTATUS MmGetCrashDumpStateInformation(IN PSYSTEM_CRASH_STATE_INFORMATION CrashInfo)
/*++
Routine Description:
This function checks to see if a crash dump section exists and
returns a BOOLEAN value in the CrashStateInformation structure based on the outcome.
Arguments:
CrashInfo - Supplies a pointer to the crash dump state information structure.
Return Value:
Status of the operation. A BOOLEAN value of FALSE indicates no
crash dump was located.
--*/
{
PAGED_CODE();
CrashInfo->ValidCrashDump = (MmCrashDumpSection != NULL);
return STATUS_SUCCESS;
}
SIZE_T MiAttemptPageFileExtension(IN ULONG PageFileNumber, IN SIZE_T ExtendSize, IN SIZE_T Maximum)
/*++
Routine Description:
This routine attempts to extend the specified page file by ExtendSize.
Arguments:
PageFileNumber - Supplies the page file number to attempt to extend.
ExtendSize - Supplies the number of pages to extend the file by.
Maximum - Supplies TRUE if the page file should be extended
by the maximum size possible, but not to exceed ExtendSize.
Return Value:
Returns the size of the extension. Zero if the page file cannot be extended.
--*/
{
NTSTATUS status;
FILE_FS_SIZE_INFORMATION FileInfo;
FILE_END_OF_FILE_INFORMATION EndOfFileInformation;
KIRQL OldIrql;
ULONG AllocSize;
PFN_NUMBER AdditionalAllocation;
ULONG ReturnedLength;
PFN_NUMBER PagesAvailable;
SIZE_T SizeToExtend;
LARGE_INTEGER BytesAvailable;
// Check to see if this page file is at the maximum.
if (MmPagingFile[PageFileNumber]->Size == MmPagingFile[PageFileNumber]->MaximumSize) {
return 0;
}
// Find out how much free space is on this volume.
status = IoQueryVolumeInformation(MmPagingFile[PageFileNumber]->File,
FileFsSizeInformation,
sizeof(FileInfo),
&FileInfo,
&ReturnedLength);
if (!NT_SUCCESS(status)) {
// The volume query did not succeed - return 0 indicating
// the paging file was not extended.
return 0;
}
// Always attempt to extend by at least megabyte.
SizeToExtend = ExtendSize;
if (ExtendSize < MmPageFileExtension) {
SizeToExtend = MmPageFileExtension;
}
// Don't go over the maximum size for the paging file.
if ((SizeToExtend + MmPagingFile[PageFileNumber]->Size) > MmPagingFile[PageFileNumber]->MaximumSize) {
SizeToExtend = (MmPagingFile[PageFileNumber]->MaximumSize - MmPagingFile[PageFileNumber]->Size);
}
if ((Maximum == FALSE) && (SizeToExtend < ExtendSize)) {
// Can't meet the requirement.
return 0;
}
// See if there is enough space on the volume for the extension.
AllocSize = FileInfo.SectorsPerAllocationUnit * FileInfo.BytesPerSector;
BytesAvailable = RtlExtendedIntegerMultiply(FileInfo.AvailableAllocationUnits, AllocSize);
if ((UINT64)BytesAvailable.QuadPart > (UINT64)MmMinimumFreeDiskSpace) {
BytesAvailable.QuadPart = BytesAvailable.QuadPart - (LONGLONG)MmMinimumFreeDiskSpace;
if ((UINT64)BytesAvailable.QuadPart > (UINT64)(SizeToExtend << PAGE_SHIFT)) {
BytesAvailable.QuadPart = (LONGLONG)(SizeToExtend << PAGE_SHIFT);
}
PagesAvailable = (PFN_NUMBER)(BytesAvailable.QuadPart >> PAGE_SHIFT);
if ((Maximum == FALSE) && (PagesAvailable < ExtendSize)) {
// Can't satisfy this requirement.
return 0;
}
} else {
// Not enough space is available.
return 0;
}
#if defined (_WIN64) || defined (_X86PAE_)
EndOfFileInformation.EndOfFile.QuadPart = ((ULONG64)MmPagingFile[PageFileNumber]->Size + PagesAvailable) * PAGE_SIZE;
#else
EndOfFileInformation.EndOfFile.LowPart = (MmPagingFile[PageFileNumber]->Size + PagesAvailable) * PAGE_SIZE;
// Set high part to zero as paging files are limited to 4GB.
EndOfFileInformation.EndOfFile.HighPart = 0;
#endif
// Attempt to extend the file by setting the end-of-file position.
ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
status = IoSetInformation(MmPagingFile[PageFileNumber]->File,
FileEndOfFileInformation,
sizeof(FILE_END_OF_FILE_INFORMATION),
&EndOfFileInformation);
if (status != STATUS_SUCCESS) {
KdPrint(("MM MODWRITE: page file extension failed %lx %lx\n", status));
return 0;
}
// Clear bits within the paging file bitmap to allow the extension to take effect.
LOCK_PFN(OldIrql);
ASSERT(RtlCheckBit(MmPagingFile[PageFileNumber]->Bitmap, MmPagingFile[PageFileNumber]->Size) == 1);
AdditionalAllocation = PagesAvailable;
RtlClearBits(MmPagingFile[PageFileNumber]->Bitmap, (ULONG)MmPagingFile[PageFileNumber]->Size, (ULONG)AdditionalAllocation);
MmPagingFile[PageFileNumber]->Size += AdditionalAllocation;
MmPagingFile[PageFileNumber]->FreeSpace += AdditionalAllocation;
MiUpdateModifiedWriterMdls(PageFileNumber);
UNLOCK_PFN(OldIrql);
return AdditionalAllocation;
}
SIZE_T MiExtendPagingFiles(IN PMMPAGE_FILE_EXPANSION PageExpand)
/*++
Routine Description:
This routine attempts to extend the paging files to provide ExtendSize bytes.
Note - Page file expansion and page file reduction are synchronized
because a single thread is responsible for performing the
operation. Hence, while expansion is occurring, a reduction request will be queued to the thread.
Arguments:
DesiredQuota - Supplies the quota in pages desired.
PageFileNumber - Supplies the page file number to extend.
MI_EXTEND_ANY_PAGFILE indicates to extend any page file.
Return Value:
Returns the size of the extension. Zero if the page file(s) cannot be extended.
--*/
{
SIZE_T DesiredQuota;
ULONG PageFileNumber;
SIZE_T ExtendedSize;
SIZE_T ExtendSize;
ULONG i;
KIRQL OldIrql;
LOGICAL LockHeld;
LOGICAL RealExpansion;
RealExpansion = TRUE;
LockHeld = FALSE;
ExtendedSize = 0;
DesiredQuota = PageExpand->RequestedExpansionSize;
PageFileNumber = PageExpand->PageFileNumber;
PageExpand->ActualExpansion = 0;
ASSERT(PageFileNumber < MmNumberOfPagingFiles || PageFileNumber == MI_EXTEND_ANY_PAGEFILE);
if (MmNumberOfPagingFiles == 0) {
goto alldone;
}
if (PageFileNumber < MmNumberOfPagingFiles) {
i = PageFileNumber;
ExtendedSize = MmPagingFile[i]->MaximumSize - MmPagingFile[i]->Size;
if (ExtendedSize < DesiredQuota) {
ExtendedSize = 0;
} else {
ExtendedSize = MiAttemptPageFileExtension(i, DesiredQuota, FALSE);
}
goto alldone;
}
LockHeld = TRUE;
ExAcquireSpinLock(&MmChargeCommitmentLock, &OldIrql);
// Check to see if ample space already exists now that we have the spinlock.
ExtendSize = DesiredQuota + MmTotalCommittedPages;
if (MmTotalCommitLimit >= ExtendSize) {
ExtendedSize = 1;
RealExpansion = FALSE;
goto alldone;
}
// Calculate the additional pages needed.
ExtendSize -= MmTotalCommitLimit;
ExReleaseSpinLock(&MmChargeCommitmentLock, OldIrql);
LockHeld = FALSE;
// Make sure ample space exists within the paging files.
i = 0;
do {
ExtendedSize += MmPagingFile[i]->MaximumSize - MmPagingFile[i]->Size;
i += 1;
} while (i < MmNumberOfPagingFiles);
if (ExtendedSize < ExtendSize) {
ExtendedSize = 0;
goto alldone;
}
// Attempt to extend only one of the paging files.
i = 0;
do {
ExtendedSize = MiAttemptPageFileExtension(i, ExtendSize, FALSE);
if (ExtendedSize != 0) {
goto alldone;
}
i += 1;
} while (i < MmNumberOfPagingFiles);
ASSERT(ExtendedSize == 0);
if (MmNumberOfPagingFiles == 1) {
// If the attempt didn't succeed for one (not enough disk space free) -
// don't try to set it to the maximum size.
goto alldone;
}
// Attempt to extend all paging files.
i = 0;
do {
ASSERT(ExtendSize > ExtendedSize);
ExtendedSize += MiAttemptPageFileExtension(i, ExtendSize - ExtendedSize, TRUE);
if (ExtendedSize >= ExtendSize) {
goto alldone;
}
i += 1;
} while (i < MmNumberOfPagingFiles);
// Not enough space is available.
ExtendedSize = 0;
alldone:
if (LockHeld == FALSE) {
ExAcquireSpinLock(&MmChargeCommitmentLock, &OldIrql);
}
if ((ExtendedSize != 0) && (RealExpansion == TRUE)) {
MmTotalCommitLimit += ExtendedSize;
}
// If commit allotments have been temporarily blocked then unblock now.
if (MmPageFileFullExtendPages) {
ASSERT(MmTotalCommittedPages >= MmPageFileFullExtendPages);
MmTotalCommittedPages -= MmPageFileFullExtendPages;
MmPageFileFullExtendPages = 0;
}
PageExpand->InProgress = FALSE;
PageExpand->ActualExpansion = ExtendedSize;
ExReleaseSpinLock(&MmChargeCommitmentLock, OldIrql);
return ExtendedSize;
}
VOID MiContractPagingFiles(VOID)
/*++
Routine Description:
This routine checks to see if ample space is no longer committed
and if so, does enough free space exist in any paging file. IF
the answer to both these is affirmative, a reduction in the paging file size(s) is attempted.
Arguments:
None.
Return Value:
None.
--*/
{
BOOLEAN Reduce;
PMMPAGE_FILE_EXPANSION PageReduce;
KIRQL OldIrql;
ULONG i;
Reduce = FALSE;
ExAcquireSpinLock(&MmChargeCommitmentLock, &OldIrql);
if ((MmTotalCommitLimit - MmMinimumPageFileReduction) > MmTotalCommittedPages) {
for (i = 0; i < MmNumberOfPagingFiles; i += 1) {
if (MmPagingFile[i]->Size != MmPagingFile[i]->MinimumSize) {
if (MmPagingFile[i]->FreeSpace > MmMinimumPageFileReduction) {
Reduce = TRUE;
break;
}
}
}
ExReleaseSpinLock(&MmChargeCommitmentLock, OldIrql);
if (!Reduce) {
return;
}
PageReduce = ExAllocatePoolWithTag(NonPagedPool, sizeof(MMPAGE_FILE_EXPANSION), ' mM');
if (PageReduce == NULL) {
return;
}
PageReduce->Segment = NULL;
PageReduce->RequestedExpansionSize = 0xFFFFFFFF;
ExAcquireSpinLock(&MmDereferenceSegmentHeader.Lock, &OldIrql);
InsertTailList(&MmDereferenceSegmentHeader.ListHead, &PageReduce->DereferenceList);
ExReleaseSpinLock(&MmDereferenceSegmentHeader.Lock, OldIrql);
KeReleaseSemaphore(&MmDereferenceSegmentHeader.Semaphore, 0L, 1L, FALSE);
return;
}
ExReleaseSpinLock(&MmChargeCommitmentLock, OldIrql);
return;
}
VOID MiAttemptPageFileReduction(VOID)
/*++
Routine Description:
This routine attempts to reduce the size of the paging files to their minimum levels.
Note - Page file expansion and page file reduction are synchronized
because a single thread is responsible for performing the
operation. Hence, while expansion is occurring, a reduction request will be queued to the thread.
Arguments:
None.
Return Value:
None.
--*/
{
BOOLEAN Reduce;
KIRQL OldIrql;
ULONG i;
PFN_NUMBER StartReduction;
PFN_NUMBER ReductionSize;
PFN_NUMBER TryBit;
PFN_NUMBER TryReduction;
SIZE_T MaxReduce;
FILE_ALLOCATION_INFORMATION FileAllocationInfo;
NTSTATUS status;
Reduce = FALSE;
ExAcquireSpinLock(&MmChargeCommitmentLock, &OldIrql);
// Make sure the commit limit is greater than the number of committed
// pages by twice the minimum page file reduction. Keep the
// difference between the two at least minimum page file reduction.
if ((MmTotalCommittedPages + (2 * MmMinimumPageFileReduction)) < MmTotalCommitLimit) {
MaxReduce = MmTotalCommitLimit - (MmMinimumPageFileReduction + MmTotalCommittedPages);
ASSERT((LONG)MaxReduce >= 0);
i = 0;
do {
if (MaxReduce < MmMinimumPageFileReduction) {
// Don't reduce any more paging files.
break;
}
if (MmPagingFile[i]->MinimumSize != MmPagingFile[i]->Size) {
if (MmPagingFile[i]->FreeSpace > MmMinimumPageFileReduction) {
// Attempt to reduce this paging file.
ExReleaseSpinLock(&MmChargeCommitmentLock, OldIrql);
// Lock the PFN database and check to see if ample pages
// are free at the end of the paging file.
TryBit = MmPagingFile[i]->Size - MmMinimumPageFileReduction;
TryReduction = MmMinimumPageFileReduction;
if (TryBit <= MmPagingFile[i]->MinimumSize) {
TryBit = MmPagingFile[i]->MinimumSize;
TryReduction = MmPagingFile[i]->Size - MmPagingFile[i]->MinimumSize;
}
StartReduction = 0;
ReductionSize = 0;
LOCK_PFN(OldIrql);
while (TRUE) {
// Try to reduce.
if ((ReductionSize + TryReduction) > MaxReduce) {
// The reduction attempt would remove more than MaxReduce pages.
break;
}
if (RtlAreBitsClear(MmPagingFile[i]->Bitmap, (ULONG)TryBit, (ULONG)TryReduction)) {
// Can reduce it by TryReduction, see if it can be made smaller.
StartReduction = TryBit;
ReductionSize += TryReduction;
if (StartReduction == MmPagingFile[i]->MinimumSize) {
break;
}
TryBit = StartReduction - MmMinimumPageFileReduction;
if (TryBit <= MmPagingFile[i]->MinimumSize) {
TryReduction -= MmPagingFile[i]->MinimumSize - TryBit;
TryBit = MmPagingFile[i]->MinimumSize;
} else {
TryReduction = MmMinimumPageFileReduction;
}
} else {
// Reduction has failed.
break;
}
} //end while
// Make sure there are no outstanding writes to
// pages within the start reduction range.
if (StartReduction != 0) {
// There is an outstanding write past where the
// new end of the paging file should be. This
// is a very rare condition, so just punt shrinking the file.
if ((MmPagingFile[i]->Entry[0]->LastPageToWrite > StartReduction) ||
(MmPagingFile[i]->Entry[1]->LastPageToWrite > StartReduction)) {
StartReduction = 0;
}
}
// Are there any pages to remove?
if (StartReduction != 0) {
// Reduce the paging file's size and free space.
ASSERT(ReductionSize == (MmPagingFile[i]->Size - StartReduction));
MmPagingFile[i]->Size = StartReduction;
MmPagingFile[i]->FreeSpace -= ReductionSize;
MaxReduce -= ReductionSize;
ASSERT((LONG)MaxReduce >= 0);
RtlSetBits(MmPagingFile[i]->Bitmap, (ULONG)StartReduction, (ULONG)ReductionSize);
// Release the PFN lock now that the size info has been updated.
UNLOCK_PFN(OldIrql);
// Change the commit limit to reflect the returned page file space.
ExAcquireSpinLock(&MmChargeCommitmentLock, &OldIrql);
// Now that the commit lock is again held, recheck
// the commit to ensure it is still safe to contract the paging files.
if ((MmTotalCommittedPages + (2 * MmMinimumPageFileReduction)) >= MmTotalCommitLimit) {
ExReleaseSpinLock(&MmChargeCommitmentLock, OldIrql);
LOCK_PFN(OldIrql);
MmPagingFile[i]->Size = StartReduction + ReductionSize;
MmPagingFile[i]->FreeSpace += ReductionSize;
MaxReduce += ReductionSize;
ASSERT((LONG)MaxReduce >= 0);
RtlClearBits(MmPagingFile[i]->Bitmap, (ULONG)StartReduction, (ULONG)ReductionSize);
UNLOCK_PFN(OldIrql);
ExAcquireSpinLock(&MmChargeCommitmentLock, &OldIrql);
break;
}
MmTotalCommitLimit -= ReductionSize;
ExReleaseSpinLock(&MmChargeCommitmentLock, OldIrql);
#if defined (_WIN64) || defined (_X86PAE_)
FileAllocationInfo.AllocationSize.QuadPart = ((ULONG64)StartReduction << PAGE_SHIFT);
#else
FileAllocationInfo.AllocationSize.LowPart = StartReduction * PAGE_SIZE;
// Set high part to zero, paging files are limited to 4gb.
FileAllocationInfo.AllocationSize.HighPart = 0;
#endif
// Reduce the allocated size of the paging file
// thereby actually freeing the space and setting a new end of file.
ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
status = IoSetInformation(MmPagingFile[i]->File,
FileAllocationInformation,
sizeof(FILE_ALLOCATION_INFORMATION),
&FileAllocationInfo);
#if DBG
// Ignore errors on truncating the paging file
// as we can always have less space in the bitmap than the pagefile holds.
if (status != STATUS_SUCCESS) {
DbgPrint("MM: pagefile truncate status %lx\n", status);
}
#endif
} else {
UNLOCK_PFN(OldIrql);
}
ExAcquireSpinLock(&MmChargeCommitmentLock, &OldIrql);
}
}
i += 1;
} while (i < MmNumberOfPagingFiles);
}
ExReleaseSpinLock(&MmChargeCommitmentLock, OldIrql);
return;
}
VOID MiWriteComplete(IN PVOID Context, IN PIO_STATUS_BLOCK IoStatus, IN ULONG Reserved)
/*++
Routine Description:
This routine is the APC write completion procedure. It is invoked
at APC_LEVEL when a page write operation is completed.
Arguments:
Context - Supplies a pointer to the MOD_WRITER_MDL_ENTRY which was used for this I/O.
IoStatus - Supplies a pointer to the IO_STATUS_BLOCK which was used for this I/O.
Return Value:
None.
Environment:
Kernel mode, APC_LEVEL.
--*/
{
PMMMOD_WRITER_MDL_ENTRY WriterEntry;
PMMMOD_WRITER_MDL_ENTRY NextWriterEntry;
PPFN_NUMBER Page;
PMMPFN Pfn1;
KIRQL OldIrql;
LONG ByteCount;
NTSTATUS status;
PCONTROL_AREA ControlArea;
ULONG FailAllIo;
PFILE_OBJECT FileObject;
PERESOURCE FileResource;
UNREFERENCED_PARAMETER(Reserved);
FailAllIo = FALSE;
#if DBG
if (MmDebug & MM_DBG_MOD_WRITE) {
DbgPrint("MM MODWRITE: modified page write completed\n");
}
#endif
// A page write has completed, at this time the pages are not
// on any lists, write-in-progress is set in the PFN database, and the reference count was incremented.
WriterEntry = (PMMMOD_WRITER_MDL_ENTRY)Context;
ByteCount = (LONG)WriterEntry->Mdl.ByteCount;
Page = &WriterEntry->Page[0];
if (WriterEntry->Mdl.MdlFlags & MDL_MAPPED_TO_SYSTEM_VA) {
MmUnmapLockedPages(WriterEntry->Mdl.MappedSystemVa, &WriterEntry->Mdl);
}
// Get the PFN lock so the PFN database can be manipulated.
status = IoStatus->Status;
ControlArea = WriterEntry->ControlArea;
LOCK_PFN(OldIrql);
// Indicate that the write is complete.
WriterEntry->LastPageToWrite = 0;
while (ByteCount > 0) {
Pfn1 = MI_PFN_ELEMENT(*Page);
ASSERT(Pfn1->u3.e1.WriteInProgress == 1);
#if DBG
if (Pfn1->OriginalPte.u.Soft.Prototype == 0) {
ULONG Offset;
Offset = GET_PAGING_FILE_OFFSET(Pfn1->OriginalPte);
if ((Offset < 8192) && (GET_PAGING_FILE_NUMBER(Pfn1->OriginalPte) == 0)) {
ASSERT((MmPagingFileDebug[Offset] & 1) != 0);
if (!MI_IS_PFN_DELETED(Pfn1)) {
if ((GET_PAGING_FILE_NUMBER(Pfn1->OriginalPte)) == 0) {
if ((MmPagingFileDebug[Offset] & ~0x1f) != ((ULONG_PTR)Pfn1->PteAddress << 3)) {
if (Pfn1->PteAddress != MiGetPteAddress(PDE_BASE)) {
// Make sure this isn't a PTE that was forked during the I/O.
if ((Pfn1->PteAddress < (PMMPTE)PDE_TOP) ||
((Pfn1->OriginalPte.u.Soft.Protection & MM_COPY_ON_WRITE_MASK) ==
MM_PROTECTION_WRITE_MASK)) {
DbgPrint("MMWRITE: Mismatch Pfn1 %p Offset %lx info %p\n",
Pfn1,
Offset,
MmPagingFileDebug[Offset]);
DbgBreakPoint();
} else {
MmPagingFileDebug[Offset] &= 0x1f;
MmPagingFileDebug[Offset] |= ((ULONG_PTR)Pfn1->PteAddress << 3);
}
}
}
}
}
}
}
#endif //DBG
Pfn1->u3.e1.WriteInProgress = 0;
if (NT_ERROR(status)) {
// If the file object is over the network, assume that this
// I/O operation can never complete and mark the pages as
// clean and indicate in the control area all I/O should fail.
// Note that the modified bit in the PFN database is not set.
if (((status != STATUS_FILE_LOCK_CONFLICT) && (ControlArea != NULL) && (ControlArea->u.Flags.Networked == 1)) ||
(status == STATUS_FILE_INVALID)) {
if (ControlArea->u.Flags.FailAllIo == 0) {
ControlArea->u.Flags.FailAllIo = 1;
FailAllIo = TRUE;
KdPrint(("MM MODWRITE: failing all io, controlarea %lx status %lx\n", ControlArea, status));
}
} else {
// The modified write operation failed, SET the modified bit
// for each page which was written and free the page file space.
#if DBG
if ((status != STATUS_FILE_LOCK_CONFLICT) && ((MmDebug & MM_DBG_PRINTS_MODWRITES) == 0)) {
KdPrint(("MM MODWRITE: modified page write iosb failed - status 0x%lx\n", status));
}
#endif
Pfn1->u3.e1.Modified = 1;
}
}
if ((Pfn1->u3.e1.Modified == 1) && (Pfn1->OriginalPte.u.Soft.Prototype == 0)) {
// This page was modified since the write was done, release the page file space.
MiReleasePageFileSpace(Pfn1->OriginalPte);
Pfn1->OriginalPte.u.Soft.PageFileHigh = 0;
}
MI_REMOVE_LOCKED_PAGE_CHARGE(Pfn1, 15);
MiDecrementReferenceCount(*Page);
#if DBG
*Page = 0xF0FFFFFF;
#endif //DBG
Page += 1;
ByteCount -= (LONG)PAGE_SIZE;
}
// Check to which list to insert this entry into depending on
// the amount of free space left in the paging file.
FileObject = WriterEntry->File;
FileResource = WriterEntry->FileResource;
if ((WriterEntry->PagingFile != NULL) && (WriterEntry->PagingFile->FreeSpace < MM_USABLE_PAGES_FREE)) {
if (MmNumberOfActiveMdlEntries == 1) {
// If we put this entry on the list, there will be
// no more paging. Locate all entries which are non zero and pull them from the list.
InsertTailList(&MmFreePagingSpaceLow, &WriterEntry->Links);
WriterEntry->CurrentList = &MmFreePagingSpaceLow;
MmNumberOfActiveMdlEntries -= 1;
// Try to pull entries off the list.
WriterEntry = (PMMMOD_WRITER_MDL_ENTRY)MmFreePagingSpaceLow.Flink;
while ((PLIST_ENTRY)WriterEntry != &MmFreePagingSpaceLow) {
NextWriterEntry = (PMMMOD_WRITER_MDL_ENTRY)WriterEntry->Links.Flink;
if (WriterEntry->PagingFile->FreeSpace != 0) {
RemoveEntryList(&WriterEntry->Links);
// Insert this into the active list.
if (IsListEmpty(&WriterEntry->PagingListHead->ListHead)) {
KeSetEvent(&WriterEntry->PagingListHead->Event, 0, FALSE);
}
InsertTailList(&WriterEntry->PagingListHead->ListHead, &WriterEntry->Links);
WriterEntry->CurrentList = &MmPagingFileHeader.ListHead;
MmNumberOfActiveMdlEntries += 1;
}
WriterEntry = NextWriterEntry;
}
} else {
InsertTailList(&MmFreePagingSpaceLow, &WriterEntry->Links);
WriterEntry->CurrentList = &MmFreePagingSpaceLow;
MmNumberOfActiveMdlEntries -= 1;
}
} else {
// Ample space exists, put this on the active list.
if (IsListEmpty(&WriterEntry->PagingListHead->ListHead)) {
KeSetEvent(&WriterEntry->PagingListHead->Event, 0, FALSE);
}
InsertTailList(&WriterEntry->PagingListHead->ListHead, &WriterEntry->Links);
}
ASSERT(((ULONG_PTR)WriterEntry->Links.Flink & 1) == 0);
UNLOCK_PFN(OldIrql);
if (FileResource != NULL) {
FsRtlReleaseFileForModWrite(FileObject, FileResource);
}
if (FailAllIo) {
if (ControlArea->FilePointer->FileName.Length &&
ControlArea->FilePointer->FileName.MaximumLength &&
ControlArea->FilePointer->FileName.Buffer) {
IoRaiseInformationalHardError(STATUS_LOST_WRITEBEHIND_DATA, &ControlArea->FilePointer->FileName, NULL);
}
}
if (ControlArea != NULL) {
LOCK_PFN(OldIrql);
// A write to a mapped file just completed, check to see if
// there are any waiters on the completion of this i/o.
ControlArea->ModifiedWriteCount -= 1;
ASSERT((SHORT)ControlArea->ModifiedWriteCount >= 0);
if (ControlArea->u.Flags.SetMappedFileIoComplete != 0) {
KePulseEvent(&MmMappedFileIoComplete, 0, FALSE);
}
ControlArea->NumberOfPfnReferences -= 1;
if (ControlArea->NumberOfPfnReferences == 0) {
// This routine return with the PFN lock released!.
MiCheckControlArea(ControlArea, NULL, OldIrql);
} else {
UNLOCK_PFN(OldIrql);
}
}
if (NT_ERROR(status)) {
// Wait for a short time so other processing can continue.
KeDelayExecutionThread(KernelMode, FALSE, &Mm30Milliseconds);
}
return;
}
LOGICAL MiCancelWriteOfMappedPfn(IN PFN_NUMBER PageToStop)
/*++
Routine Description:
This routine attempts to stop a pending mapped page writer write for the
specified PFN. Note that if the write can be stopped, any other pages
that may be clustered with the write are also stopped.
Arguments:
PageToStop - Supplies the frame number that the caller wants to stop.
Return Value:
TRUE if the write was stopped, FALSE if not.
Environment:
Kernel mode, PFN lock held. The PFN lock is released and reacquired if the write was stopped.
N.B. No other locks may be held as IRQL is lowered to APC_LEVEL here.
--*/
{
ULONG i;
ULONG PageCount;
KIRQL OldIrql;
PPFN_NUMBER Page;
PLIST_ENTRY NextEntry;
PMDL MemoryDescriptorList;
PMMMOD_WRITER_MDL_ENTRY ModWriterEntry;
// Walk the MmMappedPageWriterList looking for an MDL which contains
// the argument page. If found, remove it and cancel the write.
NextEntry = MmMappedPageWriterList.Flink;
while (NextEntry != &MmMappedPageWriterList) {
ModWriterEntry = CONTAINING_RECORD(NextEntry, MMMOD_WRITER_MDL_ENTRY, Links);
MemoryDescriptorList = &ModWriterEntry->Mdl;
PageCount = (MemoryDescriptorList->ByteCount >> PAGE_SHIFT);
Page = (PPFN_NUMBER)(MemoryDescriptorList + 1);
for (i = 0; i < PageCount; i += 1) {
if (*Page == PageToStop) {
RemoveEntryList(NextEntry);
goto CancelWrite;
}
Page += 1;
}
NextEntry = NextEntry->Flink;
}
return FALSE;
CancelWrite:
UNLOCK_PFN(APC_LEVEL);
// File lock conflict to indicate an error has occurred,
// but that future I/Os should be allowed. Keep APCs disabled and
// call the write completion routine.
ModWriterEntry->u.IoStatus.Status = STATUS_FILE_LOCK_CONFLICT;
ModWriterEntry->u.IoStatus.Information = 0;
MiWriteComplete((PVOID)ModWriterEntry, &ModWriterEntry->u.IoStatus, 0);
LOCK_PFN(OldIrql);
return TRUE;
}
VOID MiModifiedPageWriter(IN PVOID StartContext)
/*++
Routine Description:
Implements the NT modified page writer thread. When the modified
page threshold is reached, or memory becomes overcommitted the
modified page writer event is set, and this thread becomes active.
Arguments:
StartContext - not used.
Return Value:
None.
Environment:
Kernel mode.
--*/
{
HANDLE ThreadHandle;
OBJECT_ATTRIBUTES ObjectAttributes;
ULONG i;
PAGED_CODE();
StartContext; //avoid compiler warning.
// Initialize listheads as empty.
MmSystemShutdown = 0;
KeInitializeEvent(&MmPagingFileHeader.Event, NotificationEvent, FALSE);
KeInitializeEvent(&MmMappedFileHeader.Event, NotificationEvent, FALSE);
InitializeListHead(&MmPagingFileHeader.ListHead);
InitializeListHead(&MmMappedFileHeader.ListHead);
InitializeListHead(&MmFreePagingSpaceLow);
for (i = 0; i < MM_MAPPED_FILE_MDLS; i += 1) {
MmMappedFileMdl[i] = ExAllocatePoolWithTag(NonPagedPoolMustSucceed,
sizeof(MMMOD_WRITER_MDL_ENTRY) + MmModifiedWriteClusterSize * sizeof(PFN_NUMBER),
' mM');
MmMappedFileMdl[i]->PagingFile = NULL;
MmMappedFileMdl[i]->PagingListHead = &MmMappedFileHeader;
InsertTailList(&MmMappedFileHeader.ListHead, &MmMappedFileMdl[i]->Links);
}
// Make this a real time thread.
(VOID)KeSetPriorityThread(&PsGetCurrentThread()->Tcb, LOW_REALTIME_PRIORITY + 1);
// Start a secondary thread for writing mapped file pages. This
// is required as the writing of mapped file pages could cause
// page faults resulting in requests for free pages. But there
// could be no free pages - hence a dead lock. Rather than deadlock
// the whole system waiting on the modified page writer, creating
// a secondary thread allows that thread to block without affecting on going page file writes.
KeInitializeEvent(&MmMappedPageWriterEvent, NotificationEvent, FALSE);
InitializeListHead(&MmMappedPageWriterList);
InitializeObjectAttributes(&ObjectAttributes, NULL, 0, NULL, NULL);
PsCreateSystemThread(&ThreadHandle,
THREAD_ALL_ACCESS,
&ObjectAttributes,
0L,
NULL,
MiMappedPageWriter,
NULL);
ZwClose(ThreadHandle);
MiModifiedPageWriterWorker();
// Shutdown in progress, wait forever.
{
LARGE_INTEGER Forever;
// System has shutdown, go into LONG wait.
Forever.LowPart = 0;
Forever.HighPart = 0xF000000;
KeDelayExecutionThread(KernelMode, FALSE, &Forever);
}
return;
}
VOID MiModifiedPageWriterTimerDispatch(IN PKDPC Dpc,
IN PVOID DeferredContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2)
/*++
Routine Description:
This routine is executed whenever modified mapped pages are waiting to
be written. Its job is to signal the Modified Page Writer to write these out.
Arguments:
Dpc - Supplies a pointer to a control object of type DPC.
DeferredContext - Optional deferred context; not used.
SystemArgument1 - Optional argument 1; not used.
SystemArgument2 - Optional argument 2; not used.
Return Value:
None.
--*/
{
KIRQL OldIrql;
UNREFERENCED_PARAMETER(Dpc);
UNREFERENCED_PARAMETER(DeferredContext);
UNREFERENCED_PARAMETER(SystemArgument1);
UNREFERENCED_PARAMETER(SystemArgument2);
LOCK_PFN2(OldIrql);
MiTimerPending = TRUE;
KeSetEvent(&MiMappedPagesTooOldEvent, 0, FALSE);
UNLOCK_PFN2(OldIrql);
}
VOID MiModifiedPageWriterWorker(VOID)
/*++
Routine Description:
Implements the NT modified page writer thread. When the modified
page threshold is reached, or memory becomes overcommitted the
modified page writer event is set, and this thread becomes active.
Arguments:
None.
Return Value:
None.
Environment:
Kernel mode.
--*/
{
PMMPFN Pfn1;
PFN_NUMBER PageFrameIndex;
KIRQL OldIrql;
ULONG NextColor;
ULONG i;
static KWAIT_BLOCK WaitBlockArray[ModifiedWriterMaximumObject];
PVOID WaitObjects[ModifiedWriterMaximumObject];
NTSTATUS WakeupStatus;
// Wait for the modified page writer event or the mapped pages event.
WaitObjects[NormalCase] = (PVOID)&MmModifiedPageWriterEvent;
WaitObjects[MappedPagesNeedWriting] = (PVOID)&MiMappedPagesTooOldEvent;
for (;;) {
WakeupStatus = KeWaitForMultipleObjects(ModifiedWriterMaximumObject,
&WaitObjects[0],
WaitAny,
WrFreePage,
KernelMode,
FALSE,
NULL,
&WaitBlockArray[0]);
// Switch on the wait status.
switch (WakeupStatus) {
case NormalCase:
break;
case MappedPagesNeedWriting:
// Our mapped pages DPC went off, only deal with those pages.
// Write all the mapped pages (ONLY), then clear the flag
// and come back to the top.
break;
default:
break;
}
// Indicate that the hint values have not been reset in the paging files.
if (MmNumberOfPagingFiles != 0) {
i = 0;
do {
MmPagingFile[i]->HintSetToZero = FALSE;
i += 1;
} while (i < MmNumberOfPagingFiles);
}
NextColor = 0;
LOCK_PFN(OldIrql);
for (;;) {
// Modified page writer was signalled.
if ((MmAvailablePages < MmFreeGoal) && (MmModNoWriteInsert)) {
// Remove pages from the modified no write list that are waiting for the cache manager to flush them.
i = 0;
while ((MmModifiedNoWritePageListHead.Total != 0) && (i < 32)) {
PSUBSECTION Subsection;
PCONTROL_AREA ControlArea;
PageFrameIndex = MmModifiedNoWritePageListHead.Flink;
Pfn1 = MI_PFN_ELEMENT(PageFrameIndex);
Subsection = MiGetSubsectionAddress(&Pfn1->OriginalPte);
ControlArea = Subsection->ControlArea;
if (ControlArea->u.Flags.NoModifiedWriting) {
MmModNoWriteInsert = FALSE;
break;
}
MiUnlinkPageFromList(Pfn1);
MiInsertPageInList(&MmModifiedPageListHead, PageFrameIndex);
i += 1;
}
}
if (MmModifiedPageListHead.Total == 0) {
// No more pages, clear the event(s) and wait again...
// Note we can clear both events regardless of why we woke up
// since no modified pages of any type exist.
if (MiTimerPending == TRUE) {
MiTimerPending = FALSE;
KeClearEvent(&MiMappedPagesTooOldEvent);
}
UNLOCK_PFN(OldIrql);
KeClearEvent(&MmModifiedPageWriterEvent);
break;
}
// If we didn't wake up explicitly to deal with mapped pages,
// then determine which type of pages are the most popular:
// page file backed pages, or mapped file backed pages.
if (WakeupStatus == MappedPagesNeedWriting) {
PageFrameIndex = MmModifiedPageListHead.Flink;
if (PageFrameIndex == MM_EMPTY_LIST) {
// No more modified mapped pages (there may still be
// modified pagefile-destined pages), so clear only the
// mapped pages event and check for directions at the top again.
MiTimerPending = FALSE;
KeClearEvent(&MiMappedPagesTooOldEvent);
UNLOCK_PFN(OldIrql);
break;
}
} else if (MmTotalPagesForPagingFile >= (MmModifiedPageListHead.Total - MmTotalPagesForPagingFile)) {
// More pages are destined for the paging file.
MI_GET_MODIFIED_PAGE_ANY_COLOR(PageFrameIndex, NextColor);
} else {
// More pages are destined for mapped files.
PageFrameIndex = MmModifiedPageListHead.Flink;
}
// Check to see what type of page (section file backed or page
// file backed) and write out that page and more if possible.
// Check to see if this page is destined for a paging file or a mapped file.
Pfn1 = MI_PFN_ELEMENT(PageFrameIndex);
if (Pfn1->OriginalPte.u.Soft.Prototype == 1) {
if (IsListEmpty(&MmMappedFileHeader.ListHead)) {
// Make sure page is destined for paging file as there are no MDLs for mapped writes free.
if (WakeupStatus != MappedPagesNeedWriting) {
MI_GET_MODIFIED_PAGE_ANY_COLOR(PageFrameIndex, NextColor);
// No pages are destined for the paging file, get the first page destined for a mapped file.
if (PageFrameIndex == MM_EMPTY_LIST) {
// Select the first page from the list anyway.
PageFrameIndex = MmModifiedPageListHead.Flink;
}
Pfn1 = MI_PFN_ELEMENT(PageFrameIndex);
}
}
} else if ((IsListEmpty(&MmPagingFileHeader.ListHead)) || (MiFirstPageFileCreatedAndReady == FALSE)) {
// Try for a dirty section-backed page as no paging file MDLs are available.
if (MmModifiedPageListHead.Flink != MM_EMPTY_LIST) {
ASSERT(MmTotalPagesForPagingFile != MmModifiedPageListHead.Total);
PageFrameIndex = MmModifiedPageListHead.Flink;
Pfn1 = MI_PFN_ELEMENT(PageFrameIndex);
} else {
ASSERT(MmTotalPagesForPagingFile == MmModifiedPageListHead.Total);
if ((MiFirstPageFileCreatedAndReady == FALSE) && (MmNumberOfPagingFiles != 0)) {
// The first paging has been created but the reservation
// checking for crashdumps has not finished yet. Delay
// a bit as this will finish shortly and then restart.
UNLOCK_PFN(OldIrql);
KeDelayExecutionThread(KernelMode, FALSE, &MmShortTime);
LOCK_PFN(OldIrql);
continue;
}
}
}
if (Pfn1->OriginalPte.u.Soft.Prototype == 1) {
if (IsListEmpty(&MmMappedFileHeader.ListHead)) {
if (WakeupStatus == MappedPagesNeedWriting) {
// Since we woke up only to take care of mapped pages,
// don't wait for an MDL below because drivers may take
// an inordinate amount of time processing the
// outstanding ones. We might have to wait too long,
// resulting in the system running out of pages.
if (MiTimerPending == TRUE) {
// This should be normal case - the reason we must
// first check timer pending above is for the rare
// case - when this thread first ran for normal
// modified page processing and took
// care of all the pages including the mapped ones.
// Then this thread woke up again for the mapped reason and here we are.
MiTimerPending = FALSE;
KeClearEvent(&MiMappedPagesTooOldEvent);
}
MiTimerPending = TRUE;
(VOID)KeSetTimerEx(&MiModifiedPageWriterTimer, MiModifiedPageLife, 0, &MiModifiedPageWriterTimerDpc);
UNLOCK_PFN(OldIrql);
break;
}
// Reset the event indicating no mapped files in
// the list, drop the PFN lock and wait for an
// I/O operation to complete with a one second timeout.
KeClearEvent(&MmMappedFileHeader.Event);
UNLOCK_PFN(OldIrql);
KeWaitForSingleObject(&MmMappedFileHeader.Event, WrPageOut, KernelMode, FALSE, &Mm30Milliseconds);
LOCK_PFN(OldIrql);
// Don't go on as the old PageFrameIndex at the
// top of the ModifiedList may have changed states.
continue;
}
MiGatherMappedPages(Pfn1, PageFrameIndex);
} else {
MiGatherPagefilePages(Pfn1, PageFrameIndex);
}
if (MmSystemShutdown) {
// Shutdown has returned. Stop the modified page writer.
UNLOCK_PFN(OldIrql);
return;
}
if (WakeupStatus != MappedPagesNeedWriting && !MmWriteAllModifiedPages) {
if (((MmAvailablePages > MmFreeGoal) && (MmModifiedPageListHead.Total < MmFreeGoal)) ||
(MmAvailablePages > MmMoreThanEnoughFreePages)) {
// There are ample pages, clear the event and wait again...
UNLOCK_PFN(OldIrql);
KeClearEvent(&MmModifiedPageWriterEvent);
break;
}
}
} // end for
} // end for
}
VOID MiGatherMappedPages(IN PMMPFN Pfn1, IN PFN_NUMBER PageFrameIndex)
/*++
Routine Description:
This routine processes the specified modified page by examining
the prototype PTE for that page and the adjacent prototype PTEs
building a cluster of modified pages destined for a mapped file.
Once the cluster is built, it is sent to the mapped writer thread to be processed.
Arguments:
Pfn1 - Supplies a pointer to the PFN element for the corresponding page.
PageFrameIndex - Supplies the physical page frame to write.
Return Value:
None.
Environment:
PFN lock held.
--*/
{
PMMPFN Pfn2;
PMMMOD_WRITER_MDL_ENTRY ModWriterEntry;
PSUBSECTION Subsection;
PCONTROL_AREA ControlArea;
PPFN_NUMBER Page;
PMMPTE LastPte;
PMMPTE BasePte;
PMMPTE NextPte;
PMMPTE PointerPte;
PMMPTE StartingPte;
MMPTE PteContents;
KIRQL OldIrql = 0;
KIRQL OldIrql2;
// This page is destined for a mapped file, check to see if
// there are any physically adjacent pages are also in the
// modified page list and write them out at the same time.
Subsection = MiGetSubsectionAddress(&Pfn1->OriginalPte);
ControlArea = Subsection->ControlArea;
if (ControlArea->u.Flags.NoModifiedWriting) {
// This page should not be written out, add it to the
// tail of the modified NO WRITE list and get the next page.
MiUnlinkPageFromList(Pfn1);
MiInsertPageInList(MmPageLocationList[ModifiedNoWritePageList], PageFrameIndex);
return;
}
if (ControlArea->u.Flags.Image) {
#if 0
// Assert that there are no dangling shared global pages
// for an image section that is not being used.
// This assert can be re-enabled when the segment dereference
// thread list re-insertion is fixed. Note the recovery code is
// fine, so disabling the assert is benign.
ASSERT((ControlArea->NumberOfMappedViews != 0) ||
(ControlArea->NumberOfSectionReferences != 0) ||
(ControlArea->u.Flags.FloppyMedia != 0));
#endif
// This is an image section, writes are not
// allowed to an image section.
// Change page contents to look like it's a demand zero
// page and put it back into the modified list.
// Decrement the count for PfnReferences to the
// segment as paging file pages are not counted as
// "image" references.
ControlArea->NumberOfPfnReferences -= 1;
ASSERT((LONG)ControlArea->NumberOfPfnReferences >= 0);
MiUnlinkPageFromList(Pfn1);
Pfn1->OriginalPte.u.Soft.PageFileHigh = 0;
Pfn1->OriginalPte.u.Soft.Prototype = 0;
Pfn1->OriginalPte.u.Soft.Transition = 0;
// Insert the page at the tail of the list and get
// color update performed.
MiInsertPageInList(MmPageLocationList[ModifiedPageList], PageFrameIndex);
return;
}
if ((ControlArea->u.Flags.HadUserReference == 0) &&
(MmAvailablePages > (MmFreeGoal + 40)) &&
(MmEnoughMemoryForWrite())) {
// This page was modified via the cache manager. Don't
// write it out at this time as there are ample pages.
MiUnlinkPageFromList(Pfn1);
MiInsertFrontModifiedNoWrite(PageFrameIndex);
MmModNoWriteInsert = TRUE;
return;
}
// Look at backwards at previous prototype PTEs to see if
// this can be clustered into a larger write operation.
PointerPte = Pfn1->PteAddress;
NextPte = PointerPte - (MmModifiedWriteClusterSize - 1);
// Make sure NextPte is in the same page.
if (NextPte < (PMMPTE)PAGE_ALIGN(PointerPte)) {
NextPte = (PMMPTE)PAGE_ALIGN(PointerPte);
}
// Make sure NextPte is within the subsection.
if (NextPte < Subsection->SubsectionBase) {
NextPte = Subsection->SubsectionBase;
}
// If the prototype PTEs are not currently mapped,
// map them via hyperspace. BasePte refers to the
// prototype PTEs for nonfaulting references.
OldIrql2 = 99;
if (MmIsAddressValid(PointerPte)) {
BasePte = PointerPte;
} else {
BasePte = MiMapPageInHyperSpace(Pfn1->PteFrame, &OldIrql2);
BasePte = (PMMPTE)((PCHAR)BasePte + BYTE_OFFSET(PointerPte));
}
ASSERT(MI_GET_PAGE_FRAME_FROM_TRANSITION_PTE(BasePte) == PageFrameIndex);
PointerPte -= 1;
BasePte -= 1;
// Don't go before the start of the subsection nor cross a page boundary.
while (PointerPte >= NextPte) {
PteContents = *BasePte;
// If the page is not in transition, exit loop.
if ((PteContents.u.Hard.Valid == 1) ||
(PteContents.u.Soft.Transition == 0) ||
(PteContents.u.Soft.Prototype == 1)) {
break;
}
Pfn2 = MI_PFN_ELEMENT(PteContents.u.Trans.PageFrameNumber);
// Make sure page is modified and on the modified list.
if ((Pfn2->u3.e1.Modified == 0) || (Pfn2->u3.e2.ReferenceCount != 0)) {
break;
}
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_TRANSITION_PTE(&PteContents);
PointerPte -= 1;
BasePte -= 1;
}
StartingPte = PointerPte + 1;
BasePte = BasePte + 1;
Pfn1 = MI_PFN_ELEMENT(PageFrameIndex);
ASSERT(StartingPte == Pfn1->PteAddress);
MiUnlinkPageFromList(Pfn1);
// Get an entry from the list and fill it in.
ModWriterEntry = (PMMMOD_WRITER_MDL_ENTRY)RemoveHeadList(&MmMappedFileHeader.ListHead);
ModWriterEntry->File = ControlArea->FilePointer;
ModWriterEntry->ControlArea = ControlArea;
// Calculate the offset to read into the file.
// offset = base + ((thispte - basepte) << PAGE_SHIFT)
ModWriterEntry->WriteOffset.QuadPart = MiStartingOffset(Subsection, Pfn1->PteAddress);
MmInitializeMdl(&ModWriterEntry->Mdl, (PVOID)ULongToPtr(Pfn1->u3.e1.PageColor << PAGE_SHIFT), PAGE_SIZE);
ModWriterEntry->Mdl.MdlFlags |= MDL_PAGES_LOCKED;
ModWriterEntry->Mdl.Size = (CSHORT)(sizeof(MDL) + (sizeof(PFN_NUMBER) * MmModifiedWriteClusterSize));
Page = &ModWriterEntry->Page[0];
// Up the reference count for the physical page as there is I/O in progress.
MI_ADD_LOCKED_PAGE_CHARGE_FOR_MODIFIED_PAGE(Pfn1, 14);
Pfn1->u3.e2.ReferenceCount += 1;
// Clear the modified bit for the page and set the write in progress bit.
Pfn1->u3.e1.Modified = 0;
Pfn1->u3.e1.WriteInProgress = 1;
// Put this physical page into the MDL.
*Page = PageFrameIndex;
// See if any adjacent pages are also modified and in
// the transition state and if so, write them out at the same time.
// Look at the previous PTE, ensuring a page boundary is not crossed.
LastPte = StartingPte + MmModifiedWriteClusterSize;
// If BasePte is not in the same page as LastPte, set last pte to be the last PTE in this page.
if (StartingPte < (PMMPTE)PAGE_ALIGN(LastPte)) {
LastPte = ((PMMPTE)PAGE_ALIGN(LastPte)) - 1;
}
// Make sure LastPte is within the subsection.
if (LastPte > &Subsection->SubsectionBase[Subsection->PtesInSubsection]) {
LastPte = &Subsection->SubsectionBase[Subsection->PtesInSubsection];
}
// Look forwards.
NextPte = BasePte + 1;
PointerPte = StartingPte + 1;
// Loop until an MDL is filled, the end of a subsection
// is reached, or a page boundary is reached.
// Note, PointerPte points to the PTE. NextPte points
// to where it is mapped in hyperspace (if required).
while (PointerPte < LastPte) {
PteContents = *NextPte;
// If the page is not in transition, exit loop.
if ((PteContents.u.Hard.Valid == 1) ||
(PteContents.u.Soft.Transition == 0) ||
(PteContents.u.Soft.Prototype == 1)) {
break;
}
Pfn2 = MI_PFN_ELEMENT(PteContents.u.Trans.PageFrameNumber);
if ((Pfn2->u3.e1.Modified == 0) || (Pfn2->u3.e2.ReferenceCount != 0)) {
// Page is not dirty or not on the modified list, end clustering operation.
break;
}
Page += 1;
// Add physical page to MDL.
*Page = MI_GET_PAGE_FRAME_FROM_TRANSITION_PTE(&PteContents);
ASSERT(PointerPte == Pfn2->PteAddress);
MiUnlinkPageFromList(Pfn2);
// Up the reference count for the physical page as there
// is I/O in progress.
MI_ADD_LOCKED_PAGE_CHARGE_FOR_MODIFIED_PAGE(Pfn2, 14);
Pfn2->u3.e2.ReferenceCount += 1;
// Clear the modified bit for the page and set the
// write in progress bit.
Pfn2->u3.e1.Modified = 0;
Pfn2->u3.e1.WriteInProgress = 1;
ModWriterEntry->Mdl.ByteCount += PAGE_SIZE;
NextPte += 1;
PointerPte += 1;
} //end while
if (OldIrql2 != 99) {
MiUnmapPageInHyperSpace(OldIrql2);
}
ASSERT(BYTES_TO_PAGES(ModWriterEntry->Mdl.ByteCount) <= MmModifiedWriteClusterSize);
ModWriterEntry->u.LastByte.QuadPart = ModWriterEntry->WriteOffset.QuadPart + ModWriterEntry->Mdl.ByteCount;
ASSERT(Subsection->ControlArea->u.Flags.Image == 0);
#if DBG
if ((ULONG)ModWriterEntry->Mdl.ByteCount >
((1 + MmModifiedWriteClusterSize)*PAGE_SIZE)) {
DbgPrint("Mdl %p, MDL End Offset %lx %lx Subsection %p\n",
ModWriterEntry->Mdl,
ModWriterEntry->u.LastByte.LowPart,
ModWriterEntry->u.LastByte.HighPart,
Subsection);
DbgBreakPoint();
}
#endif //DBG
MmInfoCounters.MappedWriteIoCount += 1;
MmInfoCounters.MappedPagesWriteCount += (ModWriterEntry->Mdl.ByteCount >> PAGE_SHIFT);
// Increment the count of modified page writes outstanding
// in the control area.
ControlArea->ModifiedWriteCount += 1;
// Increment the number of PFN references. This allows the file
// system to purge (i.e. call MmPurgeSection) modified writes.
ControlArea->NumberOfPfnReferences += 1;
ModWriterEntry->FileResource = NULL;
if (ControlArea->u.Flags.BeingPurged == 1) {
UNLOCK_PFN(OldIrql);
ModWriterEntry->u.IoStatus.Status = STATUS_FILE_LOCK_CONFLICT;
ModWriterEntry->u.IoStatus.Information = 0;
KeRaiseIrql(APC_LEVEL, &OldIrql);
MiWriteComplete((PVOID)ModWriterEntry, &ModWriterEntry->u.IoStatus, 0);
KeLowerIrql(OldIrql);
LOCK_PFN(OldIrql);
return;
}
// Send the entry for the MappedPageWriter.
InsertTailList(&MmMappedPageWriterList, &ModWriterEntry->Links);
KeSetEvent(&MmMappedPageWriterEvent, 0, FALSE);
#if 0
UNLOCK_PFN(OldIrql);
ModWriterEntry->FileResource = NULL;
if (ModWriterEntry->ControlArea->u.Flags.FailAllIo == 1) {
Status = STATUS_UNSUCCESSFUL;
} else if (FsRtlAcquireFileForModWrite(ModWriterEntry->File, &ModWriterEntry->u.LastByte, &ModWriterEntry->FileResource)) {
// Issue the write request.
Status = IoAsynchronousPageWrite(
ModWriterEntry->File,
&ModWriterEntry->Mdl,
&ModWriterEntry->WriteOffset,
MiWriteComplete,
(PVOID)ModWriterEntry,
&ModWriterEntry->IoStatus,
&ModWriterEntry->Irp);
} else {
// Unable to get the file system resources, set error status
// to lock conflict (ignored by MiWriteComplete) so the APC routine is explicitly called.
Status = STATUS_FILE_LOCK_CONFLICT;
}
if (NT_ERROR(Status)) {
// An error has occurred, disable APCs and call the write completion routine.
ModWriterEntry->IoStatus.Status = Status;
ModWriterEntry->IoStatus.Information = 0;
KeRaiseIrql(APC_LEVEL, &OldIrql);
MiWriteComplete((PVOID)ModWriterEntry, &ModWriterEntry->IoStatus, 0);
KeLowerIrql(OldIrql);
}
LOCK_PFN(OldIrql);
#endif //0
return;
}
VOID MiGatherPagefilePages(IN PMMPFN Pfn1, IN PFN_NUMBER PageFrameIndex)
/*++
Routine Description:
This routine processes the specified modified page by getting
that page and gather any other pages on the modified list destined
for the paging file in a large write cluster. This cluster is
then written to the paging file.
Arguments:
Pfn1 - Supplies a pointer to the PFN element for the corresponding page.
PageFrameIndex - Supplies the physical page frame to write.
Environment:
PFN lock held.
--*/
{
PFILE_OBJECT File;
PMMMOD_WRITER_MDL_ENTRY ModWriterEntry;
PMMPAGING_FILE CurrentPagingFile;
NTSTATUS Status;
PPFN_NUMBER Page;
ULONG StartBit;
LARGE_INTEGER StartingOffset;
PFN_NUMBER ClusterSize;
PFN_NUMBER ThisCluster;
MMPTE LongPte;
KIRQL OldIrql;
ULONG NextColor;
LOGICAL PageFileFull;
//MM_WRITE_CLUSTER WriteCluster;
OldIrql = 0;
if (IsListEmpty(&MmPagingFileHeader.ListHead)) {
// Reset the event indicating no paging files MDLs in
// the list, drop the PFN lock and wait for an
// I/O operation to complete.
KeClearEvent(&MmPagingFileHeader.Event);
UNLOCK_PFN(OldIrql);
KeWaitForSingleObject(&MmPagingFileHeader.Event, WrPageOut, KernelMode, FALSE, &Mm30Milliseconds);
LOCK_PFN(OldIrql);
// Don't go on as the old PageFrameIndex at the
// top of the ModifiedList may have changed states.
return;
}
// Page is destined for the paging file.
// Find the paging file with the most free space and get a cluster.
NextColor = Pfn1->u3.e1.PageColor;
ModWriterEntry = (PMMMOD_WRITER_MDL_ENTRY)RemoveHeadList(&MmPagingFileHeader.ListHead);
#if DBG
ModWriterEntry->Links.Flink = MM_IO_IN_PROGRESS;
#endif
CurrentPagingFile = ModWriterEntry->PagingFile;
File = ModWriterEntry->PagingFile->File;
ThisCluster = MmModifiedWriteClusterSize;
PageFileFull = FALSE;
do {
// Attempt to cluster MmModifiedWriteClusterSize pages
// together. Reduce by one half until we succeed or can't find a single page free in the paging file.
if (((CurrentPagingFile->Hint + MmModifiedWriteClusterSize) > CurrentPagingFile->MinimumSize) &&
(CurrentPagingFile->HintSetToZero == FALSE)) {
CurrentPagingFile->HintSetToZero = TRUE;
CurrentPagingFile->Hint = 0;
}
StartBit = RtlFindClearBitsAndSet(CurrentPagingFile->Bitmap, (ULONG)ThisCluster, (ULONG)CurrentPagingFile->Hint);
if (StartBit != 0xFFFFFFFF) {
break;
}
if (CurrentPagingFile->Hint != 0) {
// Start looking from front of the file.
CurrentPagingFile->Hint = 0;
} else {
ThisCluster = ThisCluster >> 1;
PageFileFull = TRUE;
}
} while (ThisCluster != 0);
if (StartBit == 0xFFFFFFFF) {
// Paging file must be full.
KdPrint(("MM MODWRITE: page file full\n"));
ASSERT(CurrentPagingFile->FreeSpace == 0);
// Move this entry to the not enough space list, and try again.
InsertTailList(&MmFreePagingSpaceLow, &ModWriterEntry->Links);
ModWriterEntry->CurrentList = &MmFreePagingSpaceLow;
MmNumberOfActiveMdlEntries -= 1;
MiPageFileFull();
return;
}
CurrentPagingFile->FreeSpace -= ThisCluster;
CurrentPagingFile->CurrentUsage += ThisCluster;
if (CurrentPagingFile->FreeSpace < 32) {
PageFileFull = TRUE;
}
StartingOffset.QuadPart = (UINT64)StartBit << PAGE_SHIFT;
MmInitializeMdl(&ModWriterEntry->Mdl, (PVOID)ULongToPtr(Pfn1->u3.e1.PageColor << PAGE_SHIFT), PAGE_SIZE);
ModWriterEntry->Mdl.MdlFlags |= MDL_PAGES_LOCKED;
ModWriterEntry->Mdl.Size = (CSHORT)(sizeof(MDL) + sizeof(PFN_NUMBER) * MmModifiedWriteClusterSize);
Page = &ModWriterEntry->Page[0];
ClusterSize = 0;
// Search through the modified page list looking for other
// pages destined for the paging file and build a cluster.
while (ClusterSize != ThisCluster) {
// Is this page destined for a paging file?
if (Pfn1->OriginalPte.u.Soft.Prototype == 0) {
#if 0 //********* commented out
MiClusterWritePages(Pfn1, PageFrameIndex, &WriteCluster, ThisCluster - ClusterSize);
do {
PageFrameIndex = WriteCluster.Cluster[WriteCluster.StartIndex];
Pfn1 = MI_PFN_ELEMENT(PageFrameIndex);
#endif //0
*Page = PageFrameIndex;
// Remove the page from the modified list. Note that
// write-in-progress marks the state.
// Unlink the page so the same page won't be found on the modified page list by color.
MiUnlinkPageFromList(Pfn1);
NextColor = MI_GET_NEXT_COLOR(NextColor);
MI_GET_MODIFIED_PAGE_BY_COLOR(PageFrameIndex, NextColor);
// Up the reference count for the physical page as there is I/O in progress.
MI_ADD_LOCKED_PAGE_CHARGE_FOR_MODIFIED_PAGE(Pfn1, 16);
Pfn1->u3.e2.ReferenceCount += 1;
// Clear the modified bit for the page and set the
// write in progress bit.
Pfn1->u3.e1.Modified = 0;
Pfn1->u3.e1.WriteInProgress = 1;
ASSERT(Pfn1->OriginalPte.u.Soft.PageFileHigh == 0);
MI_SET_PAGING_FILE_INFO(LongPte, Pfn1->OriginalPte, CurrentPagingFile->PageFileNumber, StartBit);
#if DBG
if ((StartBit < 8192) && (CurrentPagingFile->PageFileNumber == 0)) {
ASSERT((MmPagingFileDebug[StartBit] & 1) == 0);
MmPagingFileDebug[StartBit] = (((ULONG_PTR)Pfn1->PteAddress << 3) | ((ClusterSize & 0xf) << 1) | 1);
}
#endif //DBG
// Change the original PTE contents to refer to
// the paging file offset where this was written.
Pfn1->OriginalPte = LongPte;
ClusterSize += 1;
Page += 1;
StartBit += 1;
#if 0 // COMMENTED OUT
WriteCluster.Count -= 1;
WriteCluster.StartIndex += 1;
} while (WriteCluster.Count != 0);
#endif //0
} else {
// This page was not destined for a paging file, get another page.
// Get a page of the same color as the one which was not usable.
MI_GET_MODIFIED_PAGE_BY_COLOR(PageFrameIndex, NextColor);
}
if (PageFrameIndex == MM_EMPTY_LIST) {
break;
}
Pfn1 = MI_PFN_ELEMENT(PageFrameIndex);
} //end while
if (ClusterSize != ThisCluster) {
// A complete cluster could not be located, free the
// excess page file space that was reserved and adjust the size of the packet.
RtlClearBits(CurrentPagingFile->Bitmap, StartBit, (ULONG)(ThisCluster - ClusterSize));
CurrentPagingFile->FreeSpace += ThisCluster - ClusterSize;
CurrentPagingFile->CurrentUsage -= ThisCluster - ClusterSize;
// If their are no pages to write, don't issue a write
// request and restart the scan loop.
if (ClusterSize == 0) {
// No pages to write. Inset the entry back in the list.
if (IsListEmpty(&ModWriterEntry->PagingListHead->ListHead)) {
KeSetEvent(&ModWriterEntry->PagingListHead->Event, 0, FALSE);
}
InsertTailList(&ModWriterEntry->PagingListHead->ListHead, &ModWriterEntry->Links);
return;
}
}
if (CurrentPagingFile->PeakUsage < CurrentPagingFile->CurrentUsage) {
CurrentPagingFile->PeakUsage = CurrentPagingFile->CurrentUsage;
}
ModWriterEntry->Mdl.ByteCount = (ULONG)(ClusterSize * PAGE_SIZE);
ModWriterEntry->LastPageToWrite = StartBit - 1;
MmInfoCounters.DirtyWriteIoCount += 1;
MmInfoCounters.DirtyPagesWriteCount += (ULONG)ClusterSize;
// For now release the PFN lock and wait for the write to complete.
UNLOCK_PFN(OldIrql);
#if DBG
if (MmDebug & MM_DBG_MOD_WRITE) {
DbgPrint("MM MODWRITE: modified page write begun @ %08lx by %08lx\n",
StartingOffset.LowPart, ModWriterEntry->Mdl.ByteCount);
}
#endif
// Issue the write request.
Status = IoAsynchronousPageWrite(File,
&ModWriterEntry->Mdl,
&StartingOffset,
MiWriteComplete,
(PVOID)ModWriterEntry,
&ModWriterEntry->u.IoStatus,
&ModWriterEntry->Irp);
if (NT_ERROR(Status)) {
KdPrint(("MM MODWRITE: modified page write failed %lx\n", Status));
// An error has occurred, disable APCs and
// call the write completion routine.
ModWriterEntry->u.IoStatus.Status = Status;
ModWriterEntry->u.IoStatus.Information = 0;
KeRaiseIrql(APC_LEVEL, &OldIrql);
MiWriteComplete((PVOID)ModWriterEntry, &ModWriterEntry->u.IoStatus, 0);
KeLowerIrql(OldIrql);
}
LOCK_PFN(OldIrql);
if (PageFileFull == TRUE) {
MiPageFileFull();
}
return;
}
#if 0 // COMMENTED OUT **********
ULONG ClusterCounts[20];
ULONG ClusterSizes[20];
VOID MiClusterWritePages(IN PMMPFN Pfn1,
IN PFN_NUMBER PageFrameIndex,
IN PMM_WRITE_CLUSTER WriteCluster,
IN ULONG Size)
{
PMMPTE PointerClusterPte;
PMMPTE OriginalPte;
PMMPTE StopPte;
PMMPTE ThisPage;
PMMPTE BasePage;
ULONG Start;
PMMPFN Pfn2;
KIRQL OldIrql = 99;
Start = MM_MAXIMUM_DISK_IO_SIZE / PAGE_SIZE;
WriteCluster->Cluster[Start] = PageFrameIndex;
WriteCluster->Count = 1;
ClusterSizes[Size] += 1;
if (Size == 1) {
WriteCluster->StartIndex = Start;
return;
}
// The page points to a page table page which may not be
// for the current process. Map the page into hyperspace
// reference it through hyperspace.
PointerClusterPte = Pfn1->PteAddress;
BasePage = (PMMPTE)((ULONG_PTR)PointerClusterPte & ~(PAGE_SIZE - 1));
ThisPage = BasePage;
if ((PointerClusterPte < (PMMPTE)PDE_TOP) || (!MmIsAddressValid(PointerClusterPte))) {
// Map page into hyperspace as it is either a page table
// page or nonresident paged pool.
PointerClusterPte = (PMMPTE)((PCHAR)MiMapPageInHyperSpace(Pfn1->PteFrame, &OldIrql)
+ BYTE_OFFSET(PointerClusterPte));
ThisPage = (PMMPTE)((ULONG_PTR)PointerClusterPte & ~(PAGE_SIZE - 1));
}
OriginalPte = PointerClusterPte;
ASSERT(MI_GET_PAGE_FRAME_FROM_TRANSITION_PTE(PointerClusterPte) == PageFrameIndex);
// Check backwards and forwards for other pages from this process
// destined for the paging file.
StopPte = PointerClusterPte - (Size - 1);
if (StopPte < ThisPage) {
StopPte = ThisPage;
}
while (PointerClusterPte > StopPte) {
PointerClusterPte -= 1;
// Look for the pointer at start of segment, quit as this is NOT
// a prototype PTE. Normal PTEs will not match this.
if (BasePage != (PMMPTE)(ULONG_PTR)(PointerClusterPte->u.Long & ~(PAGE_SIZE - 1))) {
if ((PointerClusterPte->u.Hard.Valid == 0) &&
(PointerClusterPte->u.Soft.Prototype == 0) &&
(PointerClusterPte->u.Soft.Transition == 1)) {
// PTE is in transition state, see if it is modified.
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_TRANSITION_PTE(PointerClusterPte);
Pfn2 = MI_PFN_ELEMENT(PageFrameIndex);
ASSERT(Pfn2->OriginalPte.u.Soft.Prototype == 0);
if ((Pfn2->u3.e1.Modified != 0) && (Pfn2->u3.e2.ReferenceCount == 0)) {
Start -= 1;
WriteCluster->Count += 1;
WriteCluster->Cluster[Start] = PageFrameIndex;
}
}
}
break;
}
WriteCluster->StartIndex = Start;
PointerClusterPte = OriginalPte + 1;
Start = MM_MAXIMUM_DISK_IO_SIZE / PAGE_SIZE;
// Remove pages looking forward from PointerClusterPte until
// a cluster is filled or a PTE is not on the modified list.
ThisPage = (PMMPTE)((PCHAR)ThisPage + PAGE_SIZE);
while ((WriteCluster->Count < Size) && (PointerClusterPte < ThisPage)) {
if ((PointerClusterPte->u.Hard.Valid == 0) &&
(PointerClusterPte->u.Soft.Prototype == 0) &&
(PointerClusterPte->u.Soft.Transition == 1)) {
// PTE is in transition state, see if it is modified.
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_TRANSITION_PTE(PointerClusterPte);
Pfn2 = MI_PFN_ELEMENT(PageFrameIndex);
ASSERT(Pfn2->OriginalPte.u.Soft.Prototype == 0);
if ((Pfn2->u3.e1.Modified != 0) && (Pfn2->u3.e2.ReferenceCount == 0)) {
Start += 1;
WriteCluster->Count += 1;
WriteCluster->Cluster[Start] = PageFrameIndex;
PointerClusterPte += 1;
continue;
}
}
break;
}
if (OldIrql != 99) {
MiUnmapPageInHyperSpace(OldIrql);
}
ClusterCounts[WriteCluster->Count] += 1;
return;
}
#endif // COMMENTED OUT **********
VOID MiMappedPageWriter(IN PVOID StartContext)
/*++
Routine Description:
Implements the NT secondary modified page writer thread.
Requests for writes to mapped files are sent to this thread.
This is required as the writing of mapped file pages could cause
page faults resulting in requests for free pages. But there
could be no free pages - hence a dead lock. Rather than deadlock
the whole system waiting on the modified page writer, creating
a secondary thread allows that thread to block without affecting on going page file writes.
Arguments:
StartContext - not used.
Environment:
Kernel mode.
--*/
{
PMMMOD_WRITER_MDL_ENTRY ModWriterEntry;
KIRQL OldIrql = 0;
NTSTATUS Status;
KEVENT TempEvent;
UNREFERENCED_PARAMETER(StartContext);
// Make this a real time thread.
(VOID)KeSetPriorityThread(&PsGetCurrentThread()->Tcb, LOW_REALTIME_PRIORITY + 1);
// Let the file system know that we are getting resources.
FsRtlSetTopLevelIrpForModWriter();
KeInitializeEvent(&TempEvent, NotificationEvent, FALSE);
while (TRUE) {
KeWaitForSingleObject(&MmMappedPageWriterEvent, WrVirtualMemory, KernelMode, FALSE, (PLARGE_INTEGER)NULL);
LOCK_PFN(OldIrql);
if (IsListEmpty(&MmMappedPageWriterList)) {
KeClearEvent(&MmMappedPageWriterEvent);
UNLOCK_PFN(OldIrql);
} else {
ModWriterEntry = (PMMMOD_WRITER_MDL_ENTRY)RemoveHeadList(&MmMappedPageWriterList);
UNLOCK_PFN(OldIrql);
if (ModWriterEntry->ControlArea->u.Flags.FailAllIo == 1) {
Status = STATUS_UNSUCCESSFUL;
} else if (FsRtlAcquireFileForModWrite(ModWriterEntry->File,
&ModWriterEntry->u.LastByte,
&ModWriterEntry->FileResource)) {
// Issue the write request.
Status = IoAsynchronousPageWrite(
ModWriterEntry->File,
&ModWriterEntry->Mdl,
&ModWriterEntry->WriteOffset,
MiWriteComplete,
(PVOID)ModWriterEntry,
&ModWriterEntry->u.IoStatus,
&ModWriterEntry->Irp);
} else {
// Unable to get the file system resources, set error status
// to lock conflict (ignored by MiWriteComplete) so the APC
// routine is explicitly called.
Status = STATUS_FILE_LOCK_CONFLICT;
}
if (NT_ERROR(Status)) {
// An error has occurred, disable APC's and call the write completion routine.
ModWriterEntry->u.IoStatus.Status = Status;
ModWriterEntry->u.IoStatus.Information = 0;
KeRaiseIrql(APC_LEVEL, &OldIrql);
MiWriteComplete((PVOID)ModWriterEntry, &ModWriterEntry->u.IoStatus, 0);
KeLowerIrql(OldIrql);
}
#if 0
//TEMPORARY code to use synchronous I/O here.
// Issue the write request.
Status = IoSynchronousPageWrite(ModWriterEntry->File,
&ModWriterEntry->Mdl,
&ModWriterEntry->WriteOffset,
&TempEvent,
&ModWriterEntry->u.IoStatus);
if (NT_ERROR(Status)) {
ModWriterEntry->u.IoStatus.Status = Status;
ModWriterEntry->u.IoStatus.Information = 0;
}
if (NT_ERROR(ModWriterEntry->u.IoStatus.Status)) {
KdPrint(("MM MODWRITE: modified page write failed %lx\n", Status));
}
// Call the write completion routine.
KeRaiseIrql(APC_LEVEL, &OldIrql);
MiWriteComplete((PVOID)ModWriterEntry, &ModWriterEntry->IoStatus, 0);
KeLowerIrql(OldIrql);
#endif //0
}
}
}
BOOLEAN MmDisableModifiedWriteOfSection(IN PSECTION_OBJECT_POINTERS SectionObjectPointer)
/*++
Routine Description:
This function disables page writing by the modified page writer for
the section which is mapped by the specified file object pointer.
This should only be used for files which CANNOT be mapped by user
programs, e.g., volume files, directory files, etc.
Arguments:
SectionObjectPointer - Supplies a pointer to the section objects
Return Value:
Returns TRUE if the operation was a success, FALSE if either
the there is no section or the section already has a view.
--*/
{
PCONTROL_AREA ControlArea;
KIRQL OldIrql;
BOOLEAN state = 1;
LOCK_PFN(OldIrql);
ControlArea = ((PCONTROL_AREA)(SectionObjectPointer->DataSectionObject));
if (ControlArea != NULL) {
if (ControlArea->NumberOfMappedViews == 0) {
// There are no views to this section, indicate no modified
// page writing is allowed.
ControlArea->u.Flags.NoModifiedWriting = 1;
} else {
// Return the current modified page writing state.
state = (BOOLEAN)ControlArea->u.Flags.NoModifiedWriting;
}
} else {
// This file no longer has an associated segment.
state = 0;
}
UNLOCK_PFN(OldIrql);
return state;
}
#define ROUND_UP(VALUE,ROUND) ((ULONG)(((ULONG)VALUE + \
((ULONG)ROUND - 1L)) & (~((ULONG)ROUND - 1L))))
NTSTATUS MmGetPageFileInformation(OUT PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG Length)
/*++
Routine Description:
This routine returns information about the currently active paging files.
Arguments:
SystemInformation - Returns the paging file information.
SystemInformationLength - Supplies the length of the SystemInformation buffer.
Length - Returns the length of the paging file information placed in the buffer.
Return Value:
Returns the status of the operation.
--*/
{
PSYSTEM_PAGEFILE_INFORMATION PageFileInfo;
ULONG NextEntryOffset = 0;
ULONG TotalSize = 0;
ULONG i;
UNICODE_STRING UserBufferPageFileName;
PAGED_CODE();
*Length = 0;
PageFileInfo = (PSYSTEM_PAGEFILE_INFORMATION)SystemInformation;
PageFileInfo->TotalSize = 0;
for (i = 0; i < MmNumberOfPagingFiles; i += 1) {
PageFileInfo = (PSYSTEM_PAGEFILE_INFORMATION)((PUCHAR)PageFileInfo + NextEntryOffset);
NextEntryOffset = sizeof(SYSTEM_PAGEFILE_INFORMATION);
TotalSize += sizeof(SYSTEM_PAGEFILE_INFORMATION);
if (TotalSize > SystemInformationLength) {
return STATUS_INFO_LENGTH_MISMATCH;
}
PageFileInfo->TotalSize = (ULONG)MmPagingFile[i]->Size;
PageFileInfo->TotalInUse = (ULONG)MmPagingFile[i]->CurrentUsage;
PageFileInfo->PeakUsage = (ULONG)MmPagingFile[i]->PeakUsage;
// The PageFileName portion of the UserBuffer must be saved locally
// to protect against a malicious thread changing the contents. This
// is because we will reference the contents ourselves when the actual
// string is copied out carefully below.
UserBufferPageFileName.Length = MmPagingFile[i]->PageFileName.Length;
UserBufferPageFileName.MaximumLength = MmPagingFile[i]->PageFileName.Length + sizeof(WCHAR);
UserBufferPageFileName.Buffer = (PWCHAR)(PageFileInfo + 1);
PageFileInfo->PageFileName = UserBufferPageFileName;
TotalSize += ROUND_UP(UserBufferPageFileName.MaximumLength, sizeof(ULONG));
NextEntryOffset += ROUND_UP(UserBufferPageFileName.MaximumLength, sizeof(ULONG));
if (TotalSize > SystemInformationLength) {
return STATUS_INFO_LENGTH_MISMATCH;
}
// Carefully reference the user buffer here.
RtlMoveMemory(UserBufferPageFileName.Buffer,
MmPagingFile[i]->PageFileName.Buffer,
MmPagingFile[i]->PageFileName.Length);
UserBufferPageFileName.Buffer[MmPagingFile[i]->PageFileName.Length / sizeof(WCHAR)] = UNICODE_NULL;
PageFileInfo->NextEntryOffset = NextEntryOffset;
}
PageFileInfo->NextEntryOffset = 0;
*Length = TotalSize;
return STATUS_SUCCESS;
}
NTSTATUS MiCheckPageFileMapping(IN PFILE_OBJECT File)
/*++
Routine Description:
Non-pagable routine to check to see if a given file has
no sections and therefore is eligible to become a paging file.
Arguments:
File - Supplies a pointer to the file object.
Return Value:
Returns STATUS_SUCCESS if the file can be used as a paging file.
--*/
{
KIRQL OldIrql;
LOCK_PFN(OldIrql);
if (File->SectionObjectPointer == NULL) {
UNLOCK_PFN(OldIrql);
return STATUS_SUCCESS;
}
if ((File->SectionObjectPointer->DataSectionObject != NULL) ||
(File->SectionObjectPointer->ImageSectionObject != NULL)) {
UNLOCK_PFN(OldIrql);
return STATUS_INCOMPATIBLE_FILE_MAP;
}
UNLOCK_PFN(OldIrql);
return STATUS_SUCCESS;
}
VOID MiInsertPageFileInList(VOID)
/*++
Routine Description:
Non-pagable routine to add a page file into the list of system wide page files.
Arguments:
None, implicitly found through page file structures.
Return Value:
None. Operation cannot fail.
--*/
{
KIRQL OldIrql;
ULONG Count;
LOCK_PFN(OldIrql);
MmNumberOfPagingFiles += 1;
Count = MmNumberOfPagingFiles;
if (IsListEmpty(&MmPagingFileHeader.ListHead)) {
KeSetEvent(&MmPagingFileHeader.Event, 0, FALSE);
}
InsertTailList(&MmPagingFileHeader.ListHead, &MmPagingFile[MmNumberOfPagingFiles - 1]->Entry[0]->Links);
MmPagingFile[MmNumberOfPagingFiles - 1]->Entry[0]->CurrentList = &MmPagingFileHeader.ListHead;
InsertTailList(&MmPagingFileHeader.ListHead, &MmPagingFile[MmNumberOfPagingFiles - 1]->Entry[1]->Links);
MmPagingFile[MmNumberOfPagingFiles - 1]->Entry[1]->CurrentList = &MmPagingFileHeader.ListHead;
MmNumberOfActiveMdlEntries += 2;
UNLOCK_PFN(OldIrql);
ExAcquireSpinLock(&MmChargeCommitmentLock, &OldIrql);
if (Count == 1) {
// We have just created the first paging file. Start the modified page writer.
MmTotalCommitLimit = MmPagingFile[MmNumberOfPagingFiles - 1]->FreeSpace + MmOverCommit;
MmTotalCommitLimitMaximum = MmPagingFile[MmNumberOfPagingFiles - 1]->MaximumSize + MmOverCommit;
// Keep commit limit above 20mb so we can boot with a small paging file and clean things up.
if (MmTotalCommitLimit < 5500) {
MmOverCommit2 = 5500 - MmTotalCommitLimit;
MmTotalCommitLimit = 5500;
}
if (MmTotalCommitLimitMaximum < 5500) {
MmTotalCommitLimitMaximum = MmTotalCommitLimit;
}
} else {
// Balance overcommitment in the case an extension was granted.
if (MmOverCommit2 > MmPagingFile[MmNumberOfPagingFiles - 1]->FreeSpace) {
MmOverCommit2 -= MmPagingFile[MmNumberOfPagingFiles - 1]->FreeSpace;
} else {
MmTotalCommitLimit += MmPagingFile[MmNumberOfPagingFiles - 1]->FreeSpace - MmOverCommit2;
MmTotalCommitLimitMaximum += MmPagingFile[MmNumberOfPagingFiles - 1]->MaximumSize - MmOverCommit2;
MmOverCommit2 = 0;
}
}
ExReleaseSpinLock(&MmChargeCommitmentLock, OldIrql);
return;
}
VOID MiPageFileFull()
/*++
Routine Description:
This routine is called when no space can be found in a paging file.
It looks through all the paging files to see if ample space is
available and if not, tries to expand the paging files.
If more than 90% of all paging files is used, the commitment limit
is set to the total and then 100 pages are added.
--*/
{
ULONG i;
PFN_NUMBER Total;
PFN_NUMBER Free;
KIRQL OldIrql;
SIZE_T SizeToExpand;
MM_PFN_LOCK_ASSERT();
Total = 0;
Free = 0;
OldIrql = 0;
i = 0;
do {
Total += MmPagingFile[i]->Size;
Free += MmPagingFile[i]->FreeSpace;
i += 1;
} while (i < MmNumberOfPagingFiles);
// Check to see if more than 90% of the total space has been used.
if (((Total >> 5) + (Total >> 4)) >= Free) {
// Try to expand the paging files.
UNLOCK_PFN(OldIrql);
// Check commit limits and set the limit to what is now used.
// If all the pagefiles are already at their maximums, then don't
// make things worse by setting commit to the maximum - this gives
// systems with lots of memory a longer lease on life when they have small pagefiles.
SizeToExpand = 0;
i = 0;
ExAcquireSpinLock(&MmChargeCommitmentLock, &OldIrql);
do {
SizeToExpand += MmPagingFile[i]->MaximumSize - MmPagingFile[i]->Size;
i += 1;
} while (i < MmNumberOfPagingFiles);
if (SizeToExpand == 0) {
ExReleaseSpinLock(&MmChargeCommitmentLock, OldIrql);
// Display a popup once.
if (MmPageFileFullPopupShown == FALSE) {
MmPageFileFullPopupShown = TRUE;
MiCauseOverCommitPopup(1, 0);
}
LOCK_PFN(OldIrql);
return;
}
if (MmTotalCommittedPages <= MmTotalCommitLimit + 50) {
// The total commit limit is less than the number of committed
// pages + 50. Reset commit limit.
if (MmTotalCommittedPages < MmTotalCommitLimit) {
if (MmPageFileFullExtendPages) {
ASSERT(MmTotalCommittedPages >= MmPageFileFullExtendPages);
MmTotalCommittedPages -= MmPageFileFullExtendPages;
MmPageFileFullExtendPages = 0;
}
MmPageFileFullExtendPages = MmTotalCommitLimit - MmTotalCommittedPages;
MmPageFileFullExtendCount += 1;
MmTotalCommittedPages = MmTotalCommitLimit;
}
// Charge 100 pages against the commitment.
MiPageFileFullCharge += MI_PAGEFILE_FULL_CHARGE;
ExReleaseSpinLock(&MmChargeCommitmentLock, OldIrql);
MiChargeCommitmentCantExpand(MI_PAGEFILE_FULL_CHARGE, TRUE);
MM_TRACK_COMMIT(MM_DBG_COMMIT_PAGEFILE_FULL, 100);
// Display a popup once.
if (MmPageFileFullPopupShown == FALSE) {
MmPageFileFullPopupShown = TRUE;
MiCauseOverCommitPopup(1, 0);
}
// Delay a bit before returning the commitment so the segment
// dereference thread gets a chance to actually grow the pagefile.
if (MiPageFileFullCharge >= 5 * MI_PAGEFILE_FULL_CHARGE) {
ExAcquireSpinLock(&MmChargeCommitmentLock, &OldIrql);
SizeToExpand = MiPageFileFullCharge;
MiPageFileFullCharge = 0;
ExReleaseSpinLock(&MmChargeCommitmentLock, OldIrql);
MiReturnCommitment(SizeToExpand);
}
} else {
// Commit limit is lower than the number of committed pages.
ExReleaseSpinLock(&MmChargeCommitmentLock, OldIrql);
}
LOCK_PFN(OldIrql);
}
}
VOID MiFlushAllPages(VOID)
/*++
Routine Description:
Forces a write of all modified pages.
Environment:
Kernel mode. No locks held. APC_LEVEL or less.
--*/
{
ULONG j = 0xff;
MmWriteAllModifiedPages = TRUE;
KeSetEvent(&MmModifiedPageWriterEvent, 0, FALSE);
do {
KeDelayExecutionThread(KernelMode, FALSE, &Mm30Milliseconds);
j -= 1;
} while ((MmModifiedPageListHead.Total > 50) && (j > 0));
MmWriteAllModifiedPages = FALSE;
}
LOGICAL MiIssuePageExtendRequest(IN PMMPAGE_FILE_EXPANSION PageExtend)
/*++
Routine Description:
Queue a message to the segment dereferencing / pagefile extending
thread to see if the page file can be extended. Extension is done
in the context of a system thread due to mutexes which the current thread may be holding.
Arguments:
PageExtend - Supplies a pointer to the page file extension request.
Return Value:
TRUE indicates the request completed.
FALSE indicates the request timed out and was removed.
Environment:
Kernel mode. No locks held. APC level or less.
--*/
{
KIRQL OldIrql;
NTSTATUS status;
PLIST_ENTRY NextEntry;
ExAcquireFastLock(&MmDereferenceSegmentHeader.Lock, &OldIrql);
InsertTailList(&MmDereferenceSegmentHeader.ListHead, &PageExtend->DereferenceList);
ExReleaseFastLock(&MmDereferenceSegmentHeader.Lock, OldIrql);
KeReleaseSemaphore(&MmDereferenceSegmentHeader.Semaphore, 0L, 1L, TRUE);
// Wait for the thread to extend the paging file.
status = KeWaitForSingleObject(&PageExtend->Event, Executive, KernelMode, FALSE, (PageExtend->RequestedExpansionSize < 10) ? &MmOneSecond : &MmTwentySeconds);
if (status == STATUS_TIMEOUT) {
// The wait has timed out, if this request has not been processed, remove it from the list and check
// to see if we should allow this request to succeed.
// This prevents a deadlock between the file system trying to allocate memory in the FSP and the
// segment dereferencing thread trying to close a file object, and waiting in the file system.
KdPrint(("MiIssuePageExtendRequest: wait timed out, page-extend= %lx, quota = %lx\n",
PageExtend, PageExtend->RequestedExpansionSize));
ExAcquireFastLock(&MmDereferenceSegmentHeader.Lock, &OldIrql);
NextEntry = MmDereferenceSegmentHeader.ListHead.Flink;
while (NextEntry != &MmDereferenceSegmentHeader.ListHead) {
// Check to see if this is the entry we are waiting for.
if (NextEntry == &PageExtend->DereferenceList) {
RemoveEntryList(&PageExtend->DereferenceList);// Remove this entry.
ExReleaseFastLock(&MmDereferenceSegmentHeader.Lock, OldIrql);
return FALSE;
}
NextEntry = NextEntry->Flink;
}
ExReleaseFastLock(&MmDereferenceSegmentHeader.Lock, OldIrql);
// Entry is being processed, wait for completion.
KdPrint(("MiIssuePageExtendRequest: rewaiting...\n"));
KeWaitForSingleObject(&PageExtend->Event, Executive, KernelMode, FALSE, NULL);
}
return TRUE;
}
VOID MiIssuePageExtendRequestNoWait(IN PFN_NUMBER SizeInPages)
/*++
Routine Description:
Queue a message to the segment dereferencing / pagefile extending
thread to see if the page file can be extended. Extension is done
in the context of a system thread due to mutexes which the current
thread may be holding.
Arguments:
SizeInPages - Supplies the size in pages to increase the page file(s) by.
This is rounded up to a 1MB multiple by this routine.
Return Value:
TRUE indicates the request completed. FALSE indicates the request timed out and was removed.
Environment:
Kernel mode. No locks held. APC level or less.
Note this routine must be very careful to not use any paged
pool as the only reason it is being called is because pool is depleted.
--*/
{
KIRQL OldIrql;
NTSTATUS status;
PLIST_ENTRY NextEntry;
ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
ExAcquireFastLock(&MmChargeCommitmentLock, &OldIrql);
if (MmAttemptForCantExtend.InProgress != FALSE) {
// An expansion request is already in progress, assume
// it will help enough (another can always be issued later) and that it will succeed.
ExReleaseFastLock(&MmChargeCommitmentLock, OldIrql);
return;
}
MmAttemptForCantExtend.InProgress = TRUE;
ExReleaseFastLock(&MmChargeCommitmentLock, OldIrql);
SizeInPages = (SizeInPages + ONEMB_IN_PAGES - 1) & ~(ONEMB_IN_PAGES - 1);
MmAttemptForCantExtend.RequestedExpansionSize = SizeInPages;
ExAcquireFastLock(&MmDereferenceSegmentHeader.Lock, &OldIrql);
InsertTailList(&MmDereferenceSegmentHeader.ListHead, &MmAttemptForCantExtend.DereferenceList);
ExReleaseFastLock(&MmDereferenceSegmentHeader.Lock, OldIrql);
KeReleaseSemaphore(&MmDereferenceSegmentHeader.Semaphore, 0L, 1L, FALSE);
}