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 if (MmGetPfnForProcess(Process, Address) != Page) 106 { 107 /* This changed in the short window where we didn't have any locks */ 108 MmUnlockAddressSpace(AddressSpace); 109 ExReleaseRundownProtection(&Process->RundownProtect); 110 ObDereferenceObject(Process); 111 goto GetEntry; 112 } 113 114 MemoryArea = MmLocateMemoryAreaByAddress(AddressSpace, Address); 115 if (MemoryArea == NULL || MemoryArea->DeleteInProgress) 116 { 117 MmUnlockAddressSpace(AddressSpace); 118 ExReleaseRundownProtection(&Process->RundownProtect); 119 ObDereferenceObject(Process); 120 goto GetEntry; 121 } 122 123 if (MemoryArea->Type == MEMORY_AREA_SECTION_VIEW) 124 { 125 ULONG_PTR Entry; 126 BOOLEAN Dirty; 127 PFN_NUMBER MapPage; 128 LARGE_INTEGER Offset; 129 BOOLEAN Released; 130 131 Offset.QuadPart = MemoryArea->SectionData.ViewOffset + 132 ((ULONG_PTR)Address - MA_GetStartingAddress(MemoryArea)); 133 134 Segment = MemoryArea->SectionData.Segment; 135 136 MmLockSectionSegment(Segment); 137 138 Entry = MmGetPageEntrySectionSegment(Segment, &Offset); 139 if (Entry && MM_IS_WAIT_PTE(Entry)) 140 { 141 /* The segment is being read or something. Give up */ 142 MmUnlockSectionSegment(Segment); 143 MmUnlockAddressSpace(AddressSpace); 144 if (Address < MmSystemRangeStart) 145 { 146 ExReleaseRundownProtection(&Process->RundownProtect); 147 ObDereferenceObject(Process); 148 } 149 return(STATUS_UNSUCCESSFUL); 150 } 151 152 /* Delete this virtual mapping in the process */ 153 MmDeleteVirtualMapping(Process, Address, &Dirty, &MapPage); 154 155 /* We checked this earlier */ 156 ASSERT(MapPage == Page); 157 158 if (Page != PFN_FROM_SSE(Entry)) 159 { 160 SWAPENTRY SwapEntry; 161 162 /* This page is private to the process */ 163 MmUnlockSectionSegment(Segment); 164 165 /* Check if we should write it back to the page file */ 166 SwapEntry = MmGetSavedSwapEntryPage(Page); 167 168 if ((SwapEntry == 0) && Dirty) 169 { 170 /* We don't have a Swap entry, yet the page is dirty. Get one */ 171 SwapEntry = MmAllocSwapPage(); 172 if (!SwapEntry) 173 { 174 PMM_REGION Region = MmFindRegion((PVOID)MA_GetStartingAddress(MemoryArea), 175 &MemoryArea->SectionData.RegionListHead, 176 Address, NULL); 177 178 /* We can't, so let this page in the Process VM */ 179 MmCreateVirtualMapping(Process, Address, Region->Protect, &Page, 1); 180 MmSetDirtyPage(Process, Address); 181 182 MmUnlockAddressSpace(AddressSpace); 183 if (Address < MmSystemRangeStart) 184 { 185 ExReleaseRundownProtection(&Process->RundownProtect); 186 ObDereferenceObject(Process); 187 } 188 189 return STATUS_UNSUCCESSFUL; 190 } 191 } 192 193 if (Dirty) 194 { 195 Status = MmWriteToSwapPage(SwapEntry, Page); 196 if (!NT_SUCCESS(Status)) 197 { 198 /* We failed at saving the content of this page. Keep it in */ 199 PMM_REGION Region = MmFindRegion((PVOID)MA_GetStartingAddress(MemoryArea), 200 &MemoryArea->SectionData.RegionListHead, 201 Address, NULL); 202 203 /* This Swap Entry is useless to us */ 204 MmSetSavedSwapEntryPage(Page, 0); 205 MmFreeSwapPage(SwapEntry); 206 207 /* We can't, so let this page in the Process VM */ 208 MmCreateVirtualMapping(Process, Address, Region->Protect, &Page, 1); 209 MmSetDirtyPage(Process, Address); 210 211 MmUnlockAddressSpace(AddressSpace); 212 ExReleaseRundownProtection(&Process->RundownProtect); 213 ObDereferenceObject(Process); 214 215 return STATUS_UNSUCCESSFUL; 216 } 217 } 218 219 if (SwapEntry) 220 { 221 /* Keep this in the process VM */ 222 MmCreatePageFileMapping(Process, Address, SwapEntry); 223 MmSetSavedSwapEntryPage(Page, 0); 224 } 225 226 MmUnlockAddressSpace(AddressSpace); 227 228 /* We can finally let this page go */ 229 MmDeleteRmap(Page, Process, Address); 230 #if DBG 231 OldIrql = MiAcquirePfnLock(); 232 ASSERT(MmGetRmapListHeadPage(Page) == NULL); 233 MiReleasePfnLock(OldIrql); 234 #endif 235 MmReleasePageMemoryConsumer(MC_USER, Page); 236 237 ExReleaseRundownProtection(&Process->RundownProtect); 238 ObDereferenceObject(Process); 239 240 return STATUS_SUCCESS; 241 } 242 243 /* Delete this RMAP */ 244 MmDeleteRmap(Page, Process, Address); 245 246 /* One less mapping referencing this segment */ 247 Released = MmUnsharePageEntrySectionSegment(MemoryArea, Segment, &Offset, Dirty, TRUE, NULL); 248 249 MmUnlockSectionSegment(Segment); 250 MmUnlockAddressSpace(AddressSpace); 251 252 ExReleaseRundownProtection(&Process->RundownProtect); 253 ObDereferenceObject(Process); 254 255 if (Released) return STATUS_SUCCESS; 256 } 257 #ifdef NEWCC 258 else if (Type == MEMORY_AREA_CACHE) 259 { 260 /* NEWCC does locking itself */ 261 MmUnlockAddressSpace(AddressSpace); 262 Status = MmpPageOutPhysicalAddress(Page); 263 } 264 #endif 265 else 266 { 267 KeBugCheck(MEMORY_MANAGEMENT); 268 } 269 270 WriteSegment: 271 /* Now write this page to file, if needed */ 272 Segment = MmGetSectionAssociation(Page, &SegmentOffset); 273 if (Segment) 274 { 275 BOOLEAN Released; 276 277 MmLockSectionSegment(Segment); 278 279 Released = MmCheckDirtySegment(Segment, &SegmentOffset, FALSE, TRUE); 280 281 MmUnlockSectionSegment(Segment); 282 283 MmDereferenceSegment(Segment); 284 285 if (Released) 286 { 287 return STATUS_SUCCESS; 288 } 289 } 290 291 /* If we are here, then we didn't release the page */ 292 return STATUS_UNSUCCESSFUL; 293 } 294 295 VOID 296 NTAPI 297 MmSetCleanAllRmaps(PFN_NUMBER Page) 298 { 299 PMM_RMAP_ENTRY current_entry; 300 KIRQL OldIrql; 301 302 OldIrql = MiAcquirePfnLock(); 303 current_entry = MmGetRmapListHeadPage(Page); 304 if (current_entry == NULL) 305 { 306 DPRINT1("MmSetCleanAllRmaps: No rmaps.\n"); 307 KeBugCheck(MEMORY_MANAGEMENT); 308 } 309 while (current_entry != NULL) 310 { 311 if (!RMAP_IS_SEGMENT(current_entry->Address)) 312 MmSetCleanPage(current_entry->Process, current_entry->Address); 313 current_entry = current_entry->Next; 314 } 315 MiReleasePfnLock(OldIrql); 316 } 317 318 BOOLEAN 319 NTAPI 320 MmIsDirtyPageRmap(PFN_NUMBER Page) 321 { 322 PMM_RMAP_ENTRY current_entry; 323 KIRQL OldIrql; 324 BOOLEAN Dirty = FALSE; 325 326 OldIrql = MiAcquirePfnLock(); 327 current_entry = MmGetRmapListHeadPage(Page); 328 if (current_entry == NULL) 329 { 330 DPRINT1("MmIsDirtyPageRmap: No rmaps.\n"); 331 KeBugCheck(MEMORY_MANAGEMENT); 332 } 333 while (current_entry != NULL) 334 { 335 if (!RMAP_IS_SEGMENT(current_entry->Address)) 336 { 337 if (MmIsDirtyPage(current_entry->Process, current_entry->Address)) 338 { 339 Dirty = TRUE; 340 break; 341 } 342 } 343 current_entry = current_entry->Next; 344 } 345 MiReleasePfnLock(OldIrql); 346 347 return Dirty; 348 } 349 350 VOID 351 NTAPI 352 MmInsertRmap(PFN_NUMBER Page, PEPROCESS Process, 353 PVOID Address) 354 { 355 PMM_RMAP_ENTRY current_entry; 356 PMM_RMAP_ENTRY new_entry; 357 ULONG PrevSize; 358 KIRQL OldIrql; 359 360 if (!RMAP_IS_SEGMENT(Address)) 361 Address = (PVOID)PAGE_ROUND_DOWN(Address); 362 363 new_entry = ExAllocateFromNPagedLookasideList(&RmapLookasideList); 364 if (new_entry == NULL) 365 { 366 KeBugCheck(MEMORY_MANAGEMENT); 367 } 368 new_entry->Address = Address; 369 new_entry->Process = (PEPROCESS)Process; 370 #if DBG 371 new_entry->Caller = _ReturnAddress(); 372 #endif 373 374 if ( 375 !RMAP_IS_SEGMENT(Address) && 376 MmGetPfnForProcess(Process, Address) != Page) 377 { 378 DPRINT1("Insert rmap (%d, 0x%.8X) 0x%.8X which doesn't match physical " 379 "address 0x%.8X\n", Process ? Process->UniqueProcessId : 0, 380 Address, 381 MmGetPfnForProcess(Process, Address) << PAGE_SHIFT, 382 Page << PAGE_SHIFT); 383 KeBugCheck(MEMORY_MANAGEMENT); 384 } 385 386 OldIrql = MiAcquirePfnLock(); 387 current_entry = MmGetRmapListHeadPage(Page); 388 389 PMM_RMAP_ENTRY previous_entry = NULL; 390 /* Keep the list sorted */ 391 while (current_entry && (current_entry->Address < Address)) 392 { 393 previous_entry = current_entry; 394 current_entry = current_entry->Next; 395 } 396 397 /* In case of clash in the address, sort by process */ 398 if (current_entry && (current_entry->Address == Address)) 399 { 400 while (current_entry && (current_entry->Process < Process)) 401 { 402 previous_entry = current_entry; 403 current_entry = current_entry->Next; 404 } 405 } 406 407 if (current_entry && (current_entry->Address == Address) && (current_entry->Process == Process)) 408 { 409 #if DBG 410 DbgPrint("MmInsertRmap tries to add a second rmap entry for address %p\n", current_entry->Address); 411 DbgPrint(" current caller %p\n", new_entry->Caller); 412 DbgPrint(" previous caller %p\n", current_entry->Caller); 413 #endif 414 KeBugCheck(MEMORY_MANAGEMENT); 415 } 416 417 new_entry->Next = current_entry; 418 if (previous_entry) 419 previous_entry->Next = new_entry; 420 else 421 MmSetRmapListHeadPage(Page, new_entry); 422 423 MiReleasePfnLock(OldIrql); 424 425 if (!RMAP_IS_SEGMENT(Address)) 426 { 427 ASSERT(Process != NULL); 428 PrevSize = InterlockedExchangeAddUL(&Process->Vm.WorkingSetSize, PAGE_SIZE); 429 if (PrevSize >= Process->Vm.PeakWorkingSetSize) 430 { 431 Process->Vm.PeakWorkingSetSize = PrevSize + PAGE_SIZE; 432 } 433 } 434 } 435 436 VOID 437 NTAPI 438 MmDeleteRmap(PFN_NUMBER Page, PEPROCESS Process, 439 PVOID Address) 440 { 441 PMM_RMAP_ENTRY current_entry, previous_entry; 442 KIRQL OldIrql; 443 444 OldIrql = MiAcquirePfnLock(); 445 previous_entry = NULL; 446 current_entry = MmGetRmapListHeadPage(Page); 447 448 while (current_entry != NULL) 449 { 450 if (current_entry->Process == (PEPROCESS)Process && 451 current_entry->Address == Address) 452 { 453 if (previous_entry == NULL) 454 { 455 MmSetRmapListHeadPage(Page, current_entry->Next); 456 } 457 else 458 { 459 previous_entry->Next = current_entry->Next; 460 } 461 MiReleasePfnLock(OldIrql); 462 463 ExFreeToNPagedLookasideList(&RmapLookasideList, current_entry); 464 if (!RMAP_IS_SEGMENT(Address)) 465 { 466 ASSERT(Process != NULL); 467 (void)InterlockedExchangeAddUL(&Process->Vm.WorkingSetSize, -PAGE_SIZE); 468 } 469 return; 470 } 471 previous_entry = current_entry; 472 current_entry = current_entry->Next; 473 } 474 KeBugCheck(MEMORY_MANAGEMENT); 475 } 476 477 /* 478 479 Return the process pointer given when a previous call to MmInsertRmap was 480 called with a process and address pointer that conform to the segment rmap 481 schema. In short, this requires the address part to be 0xffffff00 + n 482 where n is between 0 and 255. When such an rmap exists, it specifies a 483 segment rmap in which the process part is a pointer to a slice of a section 484 page table, and the low 8 bits of the address represent a page index in the 485 page table slice. Together, this information is used by 486 MmGetSectionAssociation to determine which page entry points to this page in 487 the segment page table. 488 489 */ 490 491 PVOID 492 NTAPI 493 MmGetSegmentRmap(PFN_NUMBER Page, PULONG RawOffset) 494 { 495 PCACHE_SECTION_PAGE_TABLE Result = NULL; 496 PMM_RMAP_ENTRY current_entry;//, previous_entry; 497 KIRQL OldIrql = MiAcquirePfnLock(); 498 499 //previous_entry = NULL; 500 current_entry = MmGetRmapListHeadPage(Page); 501 while (current_entry != NULL) 502 { 503 if (RMAP_IS_SEGMENT(current_entry->Address)) 504 { 505 Result = (PCACHE_SECTION_PAGE_TABLE)current_entry->Process; 506 *RawOffset = (ULONG_PTR)current_entry->Address & ~RMAP_SEGMENT_MASK; 507 if (*Result->Segment->Flags & MM_SEGMENT_INDELETE) 508 { 509 MiReleasePfnLock(OldIrql); 510 return NULL; 511 } 512 513 InterlockedIncrement64(Result->Segment->ReferenceCount); 514 MiReleasePfnLock(OldIrql); 515 return Result; 516 } 517 //previous_entry = current_entry; 518 current_entry = current_entry->Next; 519 } 520 MiReleasePfnLock(OldIrql); 521 return NULL; 522 } 523 524 /* 525 526 Remove the section rmap associated with the indicated page, if it exists. 527 528 */ 529 530 VOID 531 NTAPI 532 MmDeleteSectionAssociation(PFN_NUMBER Page) 533 { 534 PMM_RMAP_ENTRY current_entry, previous_entry; 535 KIRQL OldIrql = MiAcquirePfnLock(); 536 537 previous_entry = NULL; 538 current_entry = MmGetRmapListHeadPage(Page); 539 while (current_entry != NULL) 540 { 541 if (RMAP_IS_SEGMENT(current_entry->Address)) 542 { 543 if (previous_entry == NULL) 544 { 545 MmSetRmapListHeadPage(Page, current_entry->Next); 546 } 547 else 548 { 549 previous_entry->Next = current_entry->Next; 550 } 551 MiReleasePfnLock(OldIrql); 552 ExFreeToNPagedLookasideList(&RmapLookasideList, current_entry); 553 return; 554 } 555 previous_entry = current_entry; 556 current_entry = current_entry->Next; 557 } 558 MiReleasePfnLock(OldIrql); 559 } 560