1 /* 2 * COPYRIGHT: See COPYING in the top directory 3 * PROJECT: ReactOS kernel 4 * FILE: ntoskrnl/mm/rmap.c 5 * PURPOSE: Kernel memory managment functions 6 * 7 * PROGRAMMERS: David Welch (welch@cwcom.net) 8 */ 9 10 /* INCLUDES *****************************************************************/ 11 12 #include <ntoskrnl.h> 13 #include <cache/section/newmm.h> 14 #define NDEBUG 15 #include <debug.h> 16 17 /* TYPES ********************************************************************/ 18 19 /* GLOBALS ******************************************************************/ 20 21 static NPAGED_LOOKASIDE_LIST RmapLookasideList; 22 23 /* FUNCTIONS ****************************************************************/ 24 25 _IRQL_requires_max_(DISPATCH_LEVEL) 26 static 27 VOID 28 NTAPI 29 RmapListFree( 30 _In_ __drv_freesMem(Mem) PVOID P) 31 { 32 ExFreePoolWithTag(P, TAG_RMAP); 33 } 34 35 CODE_SEG("INIT") 36 VOID 37 NTAPI 38 MmInitializeRmapList(VOID) 39 { 40 ExInitializeNPagedLookasideList (&RmapLookasideList, 41 NULL, 42 RmapListFree, 43 0, 44 sizeof(MM_RMAP_ENTRY), 45 TAG_RMAP, 46 50); 47 } 48 49 NTSTATUS 50 NTAPI 51 MmPageOutPhysicalAddress(PFN_NUMBER Page) 52 { 53 PMM_RMAP_ENTRY entry; 54 PMEMORY_AREA MemoryArea; 55 PMMSUPPORT AddressSpace; 56 PVOID Address = NULL; 57 PEPROCESS Process = NULL; 58 NTSTATUS Status = STATUS_SUCCESS; 59 PMM_SECTION_SEGMENT Segment; 60 LARGE_INTEGER SegmentOffset; 61 KIRQL OldIrql; 62 63 GetEntry: 64 OldIrql = MiAcquirePfnLock(); 65 66 entry = MmGetRmapListHeadPage(Page); 67 68 while (entry && RMAP_IS_SEGMENT(entry->Address)) 69 entry = entry->Next; 70 71 if (entry == NULL) 72 { 73 MiReleasePfnLock(OldIrql); 74 goto WriteSegment; 75 } 76 77 Process = entry->Process; 78 Address = entry->Address; 79 80 if ((((ULONG_PTR)Address) & 0xFFF) != 0) 81 { 82 KeBugCheck(MEMORY_MANAGEMENT); 83 } 84 85 /* This is for user-mode address only */ 86 ASSERT(Address < MmSystemRangeStart); 87 88 if (!ExAcquireRundownProtection(&Process->RundownProtect)) 89 { 90 MiReleasePfnLock(OldIrql); 91 return STATUS_PROCESS_IS_TERMINATING; 92 } 93 94 Status = ObReferenceObjectByPointer(Process, PROCESS_ALL_ACCESS, NULL, KernelMode); 95 MiReleasePfnLock(OldIrql); 96 if (!NT_SUCCESS(Status)) 97 { 98 ExReleaseRundownProtection(&Process->RundownProtect); 99 return Status; 100 } 101 AddressSpace = &Process->Vm; 102 103 MmLockAddressSpace(AddressSpace); 104 105 MemoryArea = MmLocateMemoryAreaByAddress(AddressSpace, Address); 106 if (MemoryArea == NULL || MemoryArea->DeleteInProgress) 107 { 108 MmUnlockAddressSpace(AddressSpace); 109 ExReleaseRundownProtection(&Process->RundownProtect); 110 ObDereferenceObject(Process); 111 goto GetEntry; 112 } 113 114 115 /* Attach to it, if needed */ 116 ASSERT(PsGetCurrentProcess() == PsInitialSystemProcess); 117 if (Process != PsInitialSystemProcess) 118 KeAttachProcess(&Process->Pcb); 119 120 if (MmGetPfnForProcess(Process, Address) != Page) 121 { 122 /* This changed in the short window where we didn't have any locks */ 123 if (Process != PsInitialSystemProcess) 124 KeDetachProcess(); 125 MmUnlockAddressSpace(AddressSpace); 126 ExReleaseRundownProtection(&Process->RundownProtect); 127 ObDereferenceObject(Process); 128 goto GetEntry; 129 } 130 131 if (MemoryArea->Type == MEMORY_AREA_SECTION_VIEW) 132 { 133 ULONG_PTR Entry; 134 BOOLEAN Dirty; 135 PFN_NUMBER MapPage; 136 LARGE_INTEGER Offset; 137 BOOLEAN Released; 138 139 Offset.QuadPart = MemoryArea->SectionData.ViewOffset + 140 ((ULONG_PTR)Address - MA_GetStartingAddress(MemoryArea)); 141 142 Segment = MemoryArea->SectionData.Segment; 143 144 MmLockSectionSegment(Segment); 145 146 Entry = MmGetPageEntrySectionSegment(Segment, &Offset); 147 if (Entry && MM_IS_WAIT_PTE(Entry)) 148 { 149 /* The segment is being read or something. Give up */ 150 MmUnlockSectionSegment(Segment); 151 if (Process != PsInitialSystemProcess) 152 KeDetachProcess(); 153 MmUnlockAddressSpace(AddressSpace); 154 ExReleaseRundownProtection(&Process->RundownProtect); 155 ObDereferenceObject(Process); 156 return(STATUS_UNSUCCESSFUL); 157 } 158 159 /* Delete this virtual mapping in the process */ 160 MmDeleteRmap(Page, Process, Address); 161 MmDeleteVirtualMapping(Process, Address, &Dirty, &MapPage); 162 163 /* We checked this earlier */ 164 ASSERT(MapPage == Page); 165 166 if (Page != PFN_FROM_SSE(Entry)) 167 { 168 SWAPENTRY SwapEntry; 169 170 /* This page is private to the process */ 171 MmUnlockSectionSegment(Segment); 172 173 /* Check if we should write it back to the page file */ 174 SwapEntry = MmGetSavedSwapEntryPage(Page); 175 176 if ((SwapEntry == 0) && Dirty) 177 { 178 /* We don't have a Swap entry, yet the page is dirty. Get one */ 179 SwapEntry = MmAllocSwapPage(); 180 if (!SwapEntry) 181 { 182 PMM_REGION Region = MmFindRegion((PVOID)MA_GetStartingAddress(MemoryArea), 183 &MemoryArea->SectionData.RegionListHead, 184 Address, NULL); 185 186 /* We can't, so let this page in the Process VM */ 187 MmCreateVirtualMapping(Process, Address, Region->Protect, Page); 188 MmInsertRmap(Page, Process, Address); 189 MmSetDirtyPage(Process, Address); 190 191 MmUnlockAddressSpace(AddressSpace); 192 if (Process != PsInitialSystemProcess) 193 KeDetachProcess(); 194 ExReleaseRundownProtection(&Process->RundownProtect); 195 ObDereferenceObject(Process); 196 197 return STATUS_UNSUCCESSFUL; 198 } 199 } 200 201 if (Dirty) 202 { 203 SWAPENTRY Dummy; 204 205 /* Put a wait entry into the process and unlock */ 206 MmCreatePageFileMapping(Process, Address, MM_WAIT_ENTRY); 207 MmUnlockAddressSpace(AddressSpace); 208 209 Status = MmWriteToSwapPage(SwapEntry, Page); 210 211 MmLockAddressSpace(AddressSpace); 212 MmDeletePageFileMapping(Process, Address, &Dummy); 213 ASSERT(Dummy == MM_WAIT_ENTRY); 214 215 if (!NT_SUCCESS(Status)) 216 { 217 /* We failed at saving the content of this page. Keep it in */ 218 PMM_REGION Region = MmFindRegion((PVOID)MA_GetStartingAddress(MemoryArea), 219 &MemoryArea->SectionData.RegionListHead, 220 Address, NULL); 221 222 /* This Swap Entry is useless to us */ 223 MmSetSavedSwapEntryPage(Page, 0); 224 MmFreeSwapPage(SwapEntry); 225 226 /* We can't, so let this page in the Process VM */ 227 MmCreateVirtualMapping(Process, Address, Region->Protect, Page); 228 MmInsertRmap(Page, Process, Address); 229 MmSetDirtyPage(Process, Address); 230 231 MmUnlockAddressSpace(AddressSpace); 232 if (Process != PsInitialSystemProcess) 233 KeDetachProcess(); 234 ExReleaseRundownProtection(&Process->RundownProtect); 235 ObDereferenceObject(Process); 236 237 return STATUS_UNSUCCESSFUL; 238 } 239 } 240 241 if (SwapEntry) 242 { 243 /* Keep this in the process VM */ 244 MmCreatePageFileMapping(Process, Address, SwapEntry); 245 MmSetSavedSwapEntryPage(Page, 0); 246 } 247 248 /* We can finally let this page go */ 249 MmUnlockAddressSpace(AddressSpace); 250 if (Process != PsInitialSystemProcess) 251 KeDetachProcess(); 252 #if DBG 253 OldIrql = MiAcquirePfnLock(); 254 ASSERT(MmGetRmapListHeadPage(Page) == NULL); 255 MiReleasePfnLock(OldIrql); 256 #endif 257 MmReleasePageMemoryConsumer(MC_USER, Page); 258 259 ExReleaseRundownProtection(&Process->RundownProtect); 260 ObDereferenceObject(Process); 261 262 return STATUS_SUCCESS; 263 } 264 265 /* One less mapping referencing this segment */ 266 Released = MmUnsharePageEntrySectionSegment(MemoryArea, Segment, &Offset, Dirty, TRUE, NULL); 267 268 MmUnlockSectionSegment(Segment); 269 if (Process != PsInitialSystemProcess) 270 KeDetachProcess(); 271 MmUnlockAddressSpace(AddressSpace); 272 273 ExReleaseRundownProtection(&Process->RundownProtect); 274 ObDereferenceObject(Process); 275 276 if (Released) return STATUS_SUCCESS; 277 } 278 #ifdef NEWCC 279 else if (Type == MEMORY_AREA_CACHE) 280 { 281 /* NEWCC does locking itself */ 282 MmUnlockAddressSpace(AddressSpace); 283 Status = MmpPageOutPhysicalAddress(Page); 284 } 285 #endif 286 else 287 { 288 KeBugCheck(MEMORY_MANAGEMENT); 289 } 290 291 WriteSegment: 292 /* Now write this page to file, if needed */ 293 Segment = MmGetSectionAssociation(Page, &SegmentOffset); 294 if (Segment) 295 { 296 BOOLEAN Released; 297 298 MmLockSectionSegment(Segment); 299 300 Released = MmCheckDirtySegment(Segment, &SegmentOffset, FALSE, TRUE); 301 302 MmUnlockSectionSegment(Segment); 303 MmDereferenceSegment(Segment); 304 305 if (Released) 306 { 307 return STATUS_SUCCESS; 308 } 309 } 310 311 /* If we are here, then we didn't release the page */ 312 return STATUS_UNSUCCESSFUL; 313 } 314 315 VOID 316 NTAPI 317 MmInsertRmap(PFN_NUMBER Page, PEPROCESS Process, 318 PVOID Address) 319 { 320 PMM_RMAP_ENTRY current_entry; 321 PMM_RMAP_ENTRY new_entry; 322 ULONG PrevSize; 323 KIRQL OldIrql; 324 325 if (!RMAP_IS_SEGMENT(Address)) 326 Address = (PVOID)PAGE_ROUND_DOWN(Address); 327 328 new_entry = ExAllocateFromNPagedLookasideList(&RmapLookasideList); 329 if (new_entry == NULL) 330 { 331 KeBugCheck(MEMORY_MANAGEMENT); 332 } 333 new_entry->Address = Address; 334 new_entry->Process = (PEPROCESS)Process; 335 #if DBG 336 new_entry->Caller = _ReturnAddress(); 337 #endif 338 339 if ( 340 !RMAP_IS_SEGMENT(Address) && 341 MmGetPfnForProcess(Process, Address) != Page) 342 { 343 DPRINT1("Insert rmap (%d, 0x%.8X) 0x%.8X which doesn't match physical " 344 "address 0x%.8X\n", Process ? Process->UniqueProcessId : 0, 345 Address, 346 MmGetPfnForProcess(Process, Address) << PAGE_SHIFT, 347 Page << PAGE_SHIFT); 348 KeBugCheck(MEMORY_MANAGEMENT); 349 } 350 351 OldIrql = MiAcquirePfnLock(); 352 current_entry = MmGetRmapListHeadPage(Page); 353 354 PMM_RMAP_ENTRY previous_entry = NULL; 355 /* Keep the list sorted */ 356 while (current_entry && (current_entry->Address < Address)) 357 { 358 previous_entry = current_entry; 359 current_entry = current_entry->Next; 360 } 361 362 /* In case of clash in the address, sort by process */ 363 if (current_entry && (current_entry->Address == Address)) 364 { 365 while (current_entry && (current_entry->Process < Process)) 366 { 367 previous_entry = current_entry; 368 current_entry = current_entry->Next; 369 } 370 } 371 372 if (current_entry && (current_entry->Address == Address) && (current_entry->Process == Process)) 373 { 374 #if DBG 375 DbgPrint("MmInsertRmap tries to add a second rmap entry for address %p\n", current_entry->Address); 376 DbgPrint(" current caller %p\n", new_entry->Caller); 377 DbgPrint(" previous caller %p\n", current_entry->Caller); 378 #endif 379 KeBugCheck(MEMORY_MANAGEMENT); 380 } 381 382 new_entry->Next = current_entry; 383 if (previous_entry) 384 previous_entry->Next = new_entry; 385 else 386 MmSetRmapListHeadPage(Page, new_entry); 387 388 MiReleasePfnLock(OldIrql); 389 390 if (!RMAP_IS_SEGMENT(Address)) 391 { 392 ASSERT(Process != NULL); 393 PrevSize = InterlockedExchangeAddUL(&Process->Vm.WorkingSetSize, PAGE_SIZE); 394 if (PrevSize >= Process->Vm.PeakWorkingSetSize) 395 { 396 Process->Vm.PeakWorkingSetSize = PrevSize + PAGE_SIZE; 397 } 398 } 399 } 400 401 VOID 402 NTAPI 403 MmDeleteRmap(PFN_NUMBER Page, PEPROCESS Process, 404 PVOID Address) 405 { 406 PMM_RMAP_ENTRY current_entry, previous_entry; 407 KIRQL OldIrql; 408 409 OldIrql = MiAcquirePfnLock(); 410 previous_entry = NULL; 411 current_entry = MmGetRmapListHeadPage(Page); 412 413 while (current_entry != NULL) 414 { 415 if (current_entry->Process == (PEPROCESS)Process && 416 current_entry->Address == Address) 417 { 418 if (previous_entry == NULL) 419 { 420 MmSetRmapListHeadPage(Page, current_entry->Next); 421 } 422 else 423 { 424 previous_entry->Next = current_entry->Next; 425 } 426 MiReleasePfnLock(OldIrql); 427 428 ExFreeToNPagedLookasideList(&RmapLookasideList, current_entry); 429 if (!RMAP_IS_SEGMENT(Address)) 430 { 431 ASSERT(Process != NULL); 432 (void)InterlockedExchangeAddUL(&Process->Vm.WorkingSetSize, -PAGE_SIZE); 433 } 434 return; 435 } 436 previous_entry = current_entry; 437 current_entry = current_entry->Next; 438 } 439 KeBugCheck(MEMORY_MANAGEMENT); 440 } 441 442 /* 443 444 Return the process pointer given when a previous call to MmInsertRmap was 445 called with a process and address pointer that conform to the segment rmap 446 schema. In short, this requires the address part to be 0xffffff00 + n 447 where n is between 0 and 255. When such an rmap exists, it specifies a 448 segment rmap in which the process part is a pointer to a slice of a section 449 page table, and the low 8 bits of the address represent a page index in the 450 page table slice. Together, this information is used by 451 MmGetSectionAssociation to determine which page entry points to this page in 452 the segment page table. 453 454 */ 455 456 PVOID 457 NTAPI 458 MmGetSegmentRmap(PFN_NUMBER Page, PULONG RawOffset) 459 { 460 PCACHE_SECTION_PAGE_TABLE Result = NULL; 461 PMM_RMAP_ENTRY current_entry;//, previous_entry; 462 KIRQL OldIrql = MiAcquirePfnLock(); 463 464 //previous_entry = NULL; 465 current_entry = MmGetRmapListHeadPage(Page); 466 while (current_entry != NULL) 467 { 468 if (RMAP_IS_SEGMENT(current_entry->Address)) 469 { 470 Result = (PCACHE_SECTION_PAGE_TABLE)current_entry->Process; 471 *RawOffset = (ULONG_PTR)current_entry->Address & ~RMAP_SEGMENT_MASK; 472 if (*Result->Segment->Flags & MM_SEGMENT_INDELETE) 473 { 474 MiReleasePfnLock(OldIrql); 475 return NULL; 476 } 477 MiReleasePfnLock(OldIrql); 478 return Result; 479 } 480 //previous_entry = current_entry; 481 current_entry = current_entry->Next; 482 } 483 MiReleasePfnLock(OldIrql); 484 return NULL; 485 } 486 487 /* 488 489 Remove the section rmap associated with the indicated page, if it exists. 490 491 */ 492 493 VOID 494 NTAPI 495 MmDeleteSectionAssociation(PFN_NUMBER Page) 496 { 497 PMM_RMAP_ENTRY current_entry, previous_entry; 498 KIRQL OldIrql = MiAcquirePfnLock(); 499 500 previous_entry = NULL; 501 current_entry = MmGetRmapListHeadPage(Page); 502 while (current_entry != NULL) 503 { 504 if (RMAP_IS_SEGMENT(current_entry->Address)) 505 { 506 if (previous_entry == NULL) 507 { 508 MmSetRmapListHeadPage(Page, current_entry->Next); 509 } 510 else 511 { 512 previous_entry->Next = current_entry->Next; 513 } 514 MiReleasePfnLock(OldIrql); 515 ExFreeToNPagedLookasideList(&RmapLookasideList, current_entry); 516 return; 517 } 518 previous_entry = current_entry; 519 current_entry = current_entry->Next; 520 } 521 MiReleasePfnLock(OldIrql); 522 } 523