3 * Copyright (C) 1998, 1999, 2000, 2001 ReactOS Team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 * PROJECT: ReactOS kernel
21 * FILE: ntoskrnl/mm/pagefile.c
22 * PURPOSE: Paging file functions
23 * PROGRAMMER: David Welch (welch@mcmail.com)
29 /* INCLUDES *****************************************************************/
35 #if defined (ALLOC_PRAGMA)
36 #pragma alloc_text(INIT, MmInitPagingFile)
39 /* GLOBALS *******************************************************************/
41 #define PAIRS_PER_RUN (1024)
43 /* List of paging files, both used and free */
44 PMMPAGING_FILE MmPagingFile
[MAX_PAGING_FILES
];
46 /* Lock for examining the list of paging files */
47 static KSPIN_LOCK PagingFileListLock
;
49 /* Number of paging files */
50 ULONG MmNumberOfPagingFiles
;
52 /* Number of pages that are available for swapping */
53 PFN_COUNT MiFreeSwapPages
;
55 /* Number of pages that have been allocated for swapping */
56 PFN_COUNT MiUsedSwapPages
;
58 BOOLEAN MmZeroPageFile
;
61 * Number of pages that have been reserved for swapping but not yet allocated
63 static PFN_COUNT MiReservedSwapPages
;
66 * Ratio between reserved and available swap pages, e.g. setting this to five
67 * forces one swap page to be available for every five swap pages that are
68 * reserved. Setting this to zero turns off commit checking altogether.
70 #define MM_PAGEFILE_COMMIT_RATIO (1)
73 * Number of pages that can be used for potentially swapable memory without
74 * pagefile space being reserved. The intention is that this allows smss
75 * to start up and create page files while ordinarily having a commit
78 #define MM_PAGEFILE_COMMIT_GRACE (256)
81 * Translate between a swap entry and a file and offset pair.
83 #define FILE_FROM_ENTRY(i) ((i) & 0x0f)
84 #define OFFSET_FROM_ENTRY(i) ((i) >> 11)
85 #define ENTRY_FROM_FILE_OFFSET(i, j) ((i) | ((j) << 11) | 0x400)
87 /* Make sure there can be only 16 paging files */
88 C_ASSERT(FILE_FROM_ENTRY(0xffffffff) < MAX_PAGING_FILES
);
90 static BOOLEAN MmSwapSpaceMessage
= FALSE
;
92 /* FUNCTIONS *****************************************************************/
96 MmBuildMdlFromPages(PMDL Mdl
, PPFN_NUMBER Pages
)
98 memcpy(Mdl
+ 1, Pages
, sizeof(PFN_NUMBER
) * (PAGE_ROUND_UP(Mdl
->ByteOffset
+Mdl
->ByteCount
)/PAGE_SIZE
));
100 /* FIXME: this flag should be set by the caller perhaps? */
101 Mdl
->MdlFlags
|= MDL_IO_PAGE_READ
;
107 MmIsFileObjectAPagingFile(PFILE_OBJECT FileObject
)
111 /* Loop through all the paging files */
112 for (i
= 0; i
< MmNumberOfPagingFiles
; i
++)
114 /* Check if this is one of them */
115 if (MmPagingFile
[i
]->FileObject
== FileObject
) return TRUE
;
124 MmShowOutOfSpaceMessagePagingFile(VOID
)
126 if (!MmSwapSpaceMessage
)
128 DPRINT1("MM: Out of swap space.\n");
129 MmSwapSpaceMessage
= TRUE
;
135 MmWriteToSwapPage(SWAPENTRY SwapEntry
, PFN_NUMBER Page
)
139 LARGE_INTEGER file_offset
;
140 IO_STATUS_BLOCK Iosb
;
143 UCHAR MdlBase
[sizeof(MDL
) + sizeof(ULONG
)];
144 PMDL Mdl
= (PMDL
)MdlBase
;
146 DPRINT("MmWriteToSwapPage\n");
150 KeBugCheck(MEMORY_MANAGEMENT
);
151 return(STATUS_UNSUCCESSFUL
);
154 i
= FILE_FROM_ENTRY(SwapEntry
);
155 offset
= OFFSET_FROM_ENTRY(SwapEntry
) - 1;
157 if (MmPagingFile
[i
]->FileObject
== NULL
||
158 MmPagingFile
[i
]->FileObject
->DeviceObject
== NULL
)
160 DPRINT1("Bad paging file 0x%.8X\n", SwapEntry
);
161 KeBugCheck(MEMORY_MANAGEMENT
);
164 MmInitializeMdl(Mdl
, NULL
, PAGE_SIZE
);
165 MmBuildMdlFromPages(Mdl
, &Page
);
166 Mdl
->MdlFlags
|= MDL_PAGES_LOCKED
;
168 file_offset
.QuadPart
= offset
* PAGE_SIZE
;
170 KeInitializeEvent(&Event
, NotificationEvent
, FALSE
);
171 Status
= IoSynchronousPageWrite(MmPagingFile
[i
]->FileObject
,
176 if (Status
== STATUS_PENDING
)
178 KeWaitForSingleObject(&Event
, Executive
, KernelMode
, FALSE
, NULL
);
179 Status
= Iosb
.Status
;
182 if (Mdl
->MdlFlags
& MDL_MAPPED_TO_SYSTEM_VA
)
184 MmUnmapLockedPages (Mdl
->MappedSystemVa
, Mdl
);
192 MmReadFromSwapPage(SWAPENTRY SwapEntry
, PFN_NUMBER Page
)
194 return MiReadPageFile(Page
, FILE_FROM_ENTRY(SwapEntry
), OFFSET_FROM_ENTRY(SwapEntry
) - 1);
200 _In_ PFN_NUMBER Page
,
201 _In_ ULONG PageFileIndex
,
202 _In_ ULONG_PTR PageFileOffset
)
204 LARGE_INTEGER file_offset
;
205 IO_STATUS_BLOCK Iosb
;
208 UCHAR MdlBase
[sizeof(MDL
) + sizeof(ULONG
)];
209 PMDL Mdl
= (PMDL
)MdlBase
;
210 PMMPAGING_FILE PagingFile
;
212 DPRINT("MiReadSwapFile\n");
214 if (PageFileOffset
== 0)
216 KeBugCheck(MEMORY_MANAGEMENT
);
217 return(STATUS_UNSUCCESSFUL
);
220 ASSERT(PageFileIndex
< MAX_PAGING_FILES
);
222 PagingFile
= MmPagingFile
[PageFileIndex
];
224 if (PagingFile
->FileObject
== NULL
|| PagingFile
->FileObject
->DeviceObject
== NULL
)
226 DPRINT1("Bad paging file %u\n", PageFileIndex
);
227 KeBugCheck(MEMORY_MANAGEMENT
);
230 MmInitializeMdl(Mdl
, NULL
, PAGE_SIZE
);
231 MmBuildMdlFromPages(Mdl
, &Page
);
232 Mdl
->MdlFlags
|= MDL_PAGES_LOCKED
;
234 file_offset
.QuadPart
= PageFileOffset
* PAGE_SIZE
;
236 KeInitializeEvent(&Event
, NotificationEvent
, FALSE
);
237 Status
= IoPageRead(PagingFile
->FileObject
,
242 if (Status
== STATUS_PENDING
)
244 KeWaitForSingleObject(&Event
, Executive
, KernelMode
, FALSE
, NULL
);
245 Status
= Iosb
.Status
;
247 if (Mdl
->MdlFlags
& MDL_MAPPED_TO_SYSTEM_VA
)
249 MmUnmapLockedPages (Mdl
->MappedSystemVa
, Mdl
);
257 MmInitPagingFile(VOID
)
261 KeInitializeSpinLock(&PagingFileListLock
);
265 MiReservedSwapPages
= 0;
267 for (i
= 0; i
< MAX_PAGING_FILES
; i
++)
269 MmPagingFile
[i
] = NULL
;
271 MmNumberOfPagingFiles
= 0;
275 MiAllocPageFromPagingFile(PMMPAGING_FILE PagingFile
)
280 KeAcquireSpinLock(&PagingFile
->AllocMapLock
, &oldIrql
);
281 off
= RtlFindClearBitsAndSet(PagingFile
->AllocMap
, 1, 0);
282 KeReleaseSpinLock(&PagingFile
->AllocMapLock
, oldIrql
);
289 MmFreeSwapPage(SWAPENTRY Entry
)
294 PMMPAGING_FILE PagingFile
;
296 i
= FILE_FROM_ENTRY(Entry
);
297 off
= OFFSET_FROM_ENTRY(Entry
) - 1;
299 KeAcquireSpinLock(&PagingFileListLock
, &oldIrql
);
301 PagingFile
= MmPagingFile
[i
];
302 if (PagingFile
== NULL
)
304 KeBugCheck(MEMORY_MANAGEMENT
);
306 KeAcquireSpinLockAtDpcLevel(&PagingFile
->AllocMapLock
);
308 RtlClearBit(PagingFile
->AllocMap
, off
>> 5);
310 PagingFile
->FreePages
++;
311 PagingFile
->UsedPages
--;
316 KeReleaseSpinLockFromDpcLevel(&PagingFile
->AllocMapLock
);
317 KeReleaseSpinLock(&PagingFileListLock
, oldIrql
);
322 MmAllocSwapPage(VOID
)
329 KeAcquireSpinLock(&PagingFileListLock
, &oldIrql
);
331 if (MiFreeSwapPages
== 0)
333 KeReleaseSpinLock(&PagingFileListLock
, oldIrql
);
337 for (i
= 0; i
< MAX_PAGING_FILES
; i
++)
339 if (MmPagingFile
[i
] != NULL
&&
340 MmPagingFile
[i
]->FreePages
>= 1)
342 off
= MiAllocPageFromPagingFile(MmPagingFile
[i
]);
343 if (off
== 0xFFFFFFFF)
345 KeBugCheck(MEMORY_MANAGEMENT
);
346 KeReleaseSpinLock(&PagingFileListLock
, oldIrql
);
347 return(STATUS_UNSUCCESSFUL
);
351 KeReleaseSpinLock(&PagingFileListLock
, oldIrql
);
353 entry
= ENTRY_FROM_FILE_OFFSET(i
, off
+ 1);
358 KeReleaseSpinLock(&PagingFileListLock
, oldIrql
);
359 KeBugCheck(MEMORY_MANAGEMENT
);
364 NtCreatePagingFile(IN PUNICODE_STRING FileName
,
365 IN PLARGE_INTEGER InitialSize
,
366 IN PLARGE_INTEGER MaximumSize
,
370 OBJECT_ATTRIBUTES ObjectAttributes
;
372 IO_STATUS_BLOCK IoStatus
;
373 PFILE_OBJECT FileObject
;
374 PMMPAGING_FILE PagingFile
;
378 KPROCESSOR_MODE PreviousMode
;
379 UNICODE_STRING PageFileName
;
380 LARGE_INTEGER SafeInitialSize
, SafeMaximumSize
, AllocationSize
;
381 FILE_FS_DEVICE_INFORMATION FsDeviceInfo
;
382 SECURITY_DESCRIPTOR SecurityDescriptor
;
386 DPRINT("NtCreatePagingFile(FileName %wZ, InitialSize %I64d)\n",
387 FileName
, InitialSize
->QuadPart
);
389 if (MmNumberOfPagingFiles
>= MAX_PAGING_FILES
)
391 return STATUS_TOO_MANY_PAGING_FILES
;
394 PreviousMode
= ExGetPreviousMode();
396 if (PreviousMode
!= KernelMode
)
398 if (SeSinglePrivilegeCheck(SeCreatePagefilePrivilege
, PreviousMode
) != TRUE
)
400 return STATUS_PRIVILEGE_NOT_HELD
;
405 SafeInitialSize
= ProbeForReadLargeInteger(InitialSize
);
406 SafeMaximumSize
= ProbeForReadLargeInteger(MaximumSize
);
408 PageFileName
.Length
= FileName
->Length
;
409 PageFileName
.MaximumLength
= FileName
->MaximumLength
;
410 PageFileName
.Buffer
= FileName
->Buffer
;
412 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
414 /* Return the exception code */
415 _SEH2_YIELD(return _SEH2_GetExceptionCode());
421 SafeInitialSize
= *InitialSize
;
422 SafeMaximumSize
= *MaximumSize
;
424 PageFileName
.Length
= FileName
->Length
;
425 PageFileName
.MaximumLength
= FileName
->MaximumLength
;
426 PageFileName
.Buffer
= FileName
->Buffer
;
429 /* Pagefiles can't be larger than 4GB and ofcourse the minimum should be
430 smaller than the maximum */
431 if (0 != SafeInitialSize
.u
.HighPart
)
433 return STATUS_INVALID_PARAMETER_2
;
435 if (0 != SafeMaximumSize
.u
.HighPart
)
437 return STATUS_INVALID_PARAMETER_3
;
439 if (SafeMaximumSize
.u
.LowPart
< SafeInitialSize
.u
.LowPart
)
441 return STATUS_INVALID_PARAMETER_MIX
;
444 /* Validate name length */
445 if (PageFileName
.Length
> 128 * sizeof(WCHAR
))
447 return STATUS_OBJECT_NAME_INVALID
;
450 /* We won't care about any potential UNICODE_NULL */
451 PageFileName
.MaximumLength
= PageFileName
.Length
;
452 /* Allocate a buffer to keep name copy */
453 Buffer
= ExAllocatePoolWithTag(PagedPool
, PageFileName
.Length
, TAG_MM
);
456 return STATUS_INSUFFICIENT_RESOURCES
;
460 if (PreviousMode
!= KernelMode
)
464 if (PageFileName
.Length
!= 0)
466 ProbeForRead(PageFileName
.Buffer
, PageFileName
.Length
, sizeof(WCHAR
));
469 RtlCopyMemory(Buffer
, PageFileName
.Buffer
, PageFileName
.Length
);
471 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
473 ExFreePoolWithTag(Buffer
, TAG_MM
);
475 /* Return the exception code */
476 _SEH2_YIELD(return _SEH2_GetExceptionCode());
482 RtlCopyMemory(Buffer
, PageFileName
.Buffer
, PageFileName
.Length
);
485 /* Erase caller's buffer with ours */
486 PageFileName
.Buffer
= Buffer
;
488 /* Create the security descriptor for the page file */
489 Status
= RtlCreateSecurityDescriptor(&SecurityDescriptor
, SECURITY_DESCRIPTOR_REVISION
);
490 if (!NT_SUCCESS(Status
))
492 ExFreePoolWithTag(Buffer
, TAG_MM
);
496 /* Create the DACL: we will only allow two SIDs */
497 Count
= sizeof(ACL
) + (sizeof(ACE
) + RtlLengthSid(SeLocalSystemSid
)) +
498 (sizeof(ACE
) + RtlLengthSid(SeAliasAdminsSid
));
499 Dacl
= ExAllocatePoolWithTag(PagedPool
, Count
, 'lcaD');
502 ExFreePoolWithTag(Buffer
, TAG_MM
);
503 return STATUS_INSUFFICIENT_RESOURCES
;
506 /* Initialize the DACL */
507 Status
= RtlCreateAcl(Dacl
, Count
, ACL_REVISION
);
508 if (!NT_SUCCESS(Status
))
510 ExFreePoolWithTag(Dacl
, 'lcaD');
511 ExFreePoolWithTag(Buffer
, TAG_MM
);
515 /* Grant full access to admins */
516 Status
= RtlAddAccessAllowedAce(Dacl
, ACL_REVISION
, FILE_ALL_ACCESS
, SeAliasAdminsSid
);
517 if (!NT_SUCCESS(Status
))
519 ExFreePoolWithTag(Dacl
, 'lcaD');
520 ExFreePoolWithTag(Buffer
, TAG_MM
);
524 /* Grant full access to SYSTEM */
525 Status
= RtlAddAccessAllowedAce(Dacl
, ACL_REVISION
, FILE_ALL_ACCESS
, SeLocalSystemSid
);
526 if (!NT_SUCCESS(Status
))
528 ExFreePoolWithTag(Dacl
, 'lcaD');
529 ExFreePoolWithTag(Buffer
, TAG_MM
);
533 /* Attach the DACL to the security descriptor */
534 Status
= RtlSetDaclSecurityDescriptor(&SecurityDescriptor
, TRUE
, Dacl
, FALSE
);
535 if (!NT_SUCCESS(Status
))
537 ExFreePoolWithTag(Dacl
, 'lcaD');
538 ExFreePoolWithTag(Buffer
, TAG_MM
);
542 InitializeObjectAttributes(&ObjectAttributes
,
546 &SecurityDescriptor
);
548 /* Make sure we can at least store a complete page:
549 * If we have 2048 BytesPerAllocationUnit (FAT16 < 128MB) there is
550 * a problem if the paging file is fragmented. Suppose the first cluster
551 * of the paging file is cluster 3042 but cluster 3043 is NOT part of the
552 * paging file but of another file. We can't write a complete page (4096
553 * bytes) to the physical location of cluster 3042 then. */
554 AllocationSize
.QuadPart
= SafeInitialSize
.QuadPart
+ PAGE_SIZE
;
556 /* First, attempt to replace the page file, if existing */
557 Status
= IoCreateFile(&FileHandle
,
558 SYNCHRONIZE
| WRITE_DAC
| FILE_READ_DATA
| FILE_WRITE_DATA
,
562 FILE_ATTRIBUTE_SYSTEM
| FILE_ATTRIBUTE_HIDDEN
,
565 FILE_DELETE_ON_CLOSE
| FILE_NO_COMPRESSION
| FILE_NO_INTERMEDIATE_BUFFERING
,
570 SL_OPEN_PAGING_FILE
| IO_NO_PARAMETER_CHECKING
);
571 /* If we failed, relax a bit constraints, someone may be already holding the
572 * the file, so share write, don't attempt to replace and don't delete on close
573 * (basically, don't do anything conflicting)
575 if (!NT_SUCCESS(Status
))
577 Status
= IoCreateFile(&FileHandle
,
578 SYNCHRONIZE
| FILE_WRITE_DATA
,
582 FILE_ATTRIBUTE_SYSTEM
| FILE_ATTRIBUTE_HIDDEN
,
583 FILE_SHARE_WRITE
| FILE_SHARE_READ
,
585 FILE_NO_COMPRESSION
| FILE_NO_INTERMEDIATE_BUFFERING
,
590 SL_OPEN_PAGING_FILE
| IO_NO_PARAMETER_CHECKING
);
593 if (!NT_SUCCESS(Status
))
595 DPRINT1("Failed creating page file: %lx\n", Status
);
596 ExFreePoolWithTag(Dacl
, 'lcaD');
597 ExFreePoolWithTag(Buffer
, TAG_MM
);
601 /* Set the security descriptor */
602 if (NT_SUCCESS(IoStatus
.Status
))
604 Status
= ZwSetSecurityObject(FileHandle
, DACL_SECURITY_INFORMATION
, &SecurityDescriptor
);
605 if (!NT_SUCCESS(Status
))
607 ExFreePoolWithTag(Dacl
, 'lcaD');
609 ExFreePoolWithTag(Buffer
, TAG_MM
);
614 /* DACL is no longer needed, free it */
615 ExFreePoolWithTag(Dacl
, 'lcaD');
617 /* Set its end of file to initial size */
618 Status
= ZwSetInformationFile(FileHandle
,
621 sizeof(LARGE_INTEGER
),
622 FileEndOfFileInformation
);
623 if (!NT_SUCCESS(Status
) || !NT_SUCCESS(IoStatus
.Status
))
626 ExFreePoolWithTag(Buffer
, TAG_MM
);
630 Status
= ObReferenceObjectByHandle(FileHandle
,
636 if (!NT_SUCCESS(Status
))
639 ExFreePoolWithTag(Buffer
, TAG_MM
);
643 /* Deny page file creation on a floppy disk */
644 FsDeviceInfo
.Characteristics
= 0;
645 IoQueryVolumeInformation(FileObject
, FileFsDeviceInformation
, sizeof(FsDeviceInfo
), &FsDeviceInfo
, &Count
);
646 if (BooleanFlagOn(FsDeviceInfo
.Characteristics
, FILE_FLOPPY_DISKETTE
))
648 ObDereferenceObject(FileObject
);
650 ExFreePoolWithTag(Buffer
, TAG_MM
);
651 return STATUS_FLOPPY_VOLUME
;
654 PagingFile
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(*PagingFile
), TAG_MM
);
655 if (PagingFile
== NULL
)
657 ObDereferenceObject(FileObject
);
659 ExFreePoolWithTag(Buffer
, TAG_MM
);
660 return STATUS_INSUFFICIENT_RESOURCES
;
663 RtlZeroMemory(PagingFile
, sizeof(*PagingFile
));
665 PagingFile
->FileHandle
= FileHandle
;
666 PagingFile
->FileObject
= FileObject
;
667 PagingFile
->MaximumSize
.QuadPart
= SafeMaximumSize
.QuadPart
;
668 PagingFile
->CurrentSize
.QuadPart
= SafeInitialSize
.QuadPart
;
669 PagingFile
->FreePages
= (ULONG
)(SafeInitialSize
.QuadPart
/ PAGE_SIZE
);
670 PagingFile
->UsedPages
= 0;
671 KeInitializeSpinLock(&PagingFile
->AllocMapLock
);
672 PagingFile
->PageFileName
= PageFileName
;
674 AllocMapSize
= sizeof(RTL_BITMAP
) + (((PagingFile
->FreePages
+ 31) / 32) * sizeof(ULONG
));
675 PagingFile
->AllocMap
= ExAllocatePoolWithTag(NonPagedPool
,
678 if (PagingFile
->AllocMap
== NULL
)
680 ExFreePoolWithTag(PagingFile
, TAG_MM
);
681 ObDereferenceObject(FileObject
);
683 ExFreePoolWithTag(Buffer
, TAG_MM
);
684 return STATUS_INSUFFICIENT_RESOURCES
;
687 RtlInitializeBitMap(PagingFile
->AllocMap
,
688 (PULONG
)(PagingFile
->AllocMap
+ 1),
689 (ULONG
)(PagingFile
->FreePages
));
690 RtlClearAllBits(PagingFile
->AllocMap
);
692 KeAcquireSpinLock(&PagingFileListLock
, &oldIrql
);
693 ASSERT(MmPagingFile
[MmNumberOfPagingFiles
] == NULL
);
694 MmPagingFile
[MmNumberOfPagingFiles
] = PagingFile
;
695 MmNumberOfPagingFiles
++;
696 MiFreeSwapPages
= MiFreeSwapPages
+ PagingFile
->FreePages
;
697 KeReleaseSpinLock(&PagingFileListLock
, oldIrql
);
699 MmSwapSpaceMessage
= FALSE
;
701 return STATUS_SUCCESS
;