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