xref: /reactos/ntoskrnl/mm/rmap.c (revision 171a9206)
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