xref: /reactos/ntoskrnl/cache/section/sptab.c (revision 918e3319)
1 /*
2  * Copyright (C) 1998-2005 ReactOS Team (and the authors from the programmers section)
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17  *
18  *
19  * PROJECT:         ReactOS kernel
20  * FILE:            ntoskrnl/cache/section/sptab.c
21  * PURPOSE:         Section object page tables
22  *
23  * PROGRAMMERS:     arty
24  */
25 
26 /*
27 
28 This file implements the section page table.  It relies on rtl generic table
29 functionality to provide access to 256-page chunks.  Calls to
30 MiSetPageEntrySectionSegment and MiGetPageEntrySectionSegment must be
31 synchronized by holding the segment lock.
32 
33 Each page table entry is a ULONG as in x86.
34 
35 Bit 1 is used as a swap entry indication as in the main page table.
36 Bit 2 is used as a dirty indication.  A dirty page will eventually be written
37 back to the file.
38 Bits 3-11 are used as a map count in the legacy mm code, Note that zero is
39 illegal, as the legacy code does not take advantage of segment rmaps.
40 Therefore, every segment page is mapped in at least one address space, and
41 MmUnsharePageEntry is quite complicated.  In addition, the page may also be
42 owned by the legacy cache manager, giving an implied additional reference.
43 Upper bits are a PFN_NUMBER.
44 
45 These functions, in addition to maintaining the segment page table also
46 automatically maintain the segment rmap by calling MmSetSectionAssociation
47 and MmDeleteSectionAssociation.  Segment rmaps are discussed in rmap.c.  The
48 upshot is that it is impossible to have a page properly registered in a segment
49 page table and not also found in a segment rmap that can be found from the
50 paging machinery.
51 
52 */
53 
54 /* INCLUDES *****************************************************************/
55 
56 #include <ntoskrnl.h>
57 #include "newmm.h"
58 #define NDEBUG
59 #include <debug.h>
60 
61 #define DPRINTC DPRINT
62 
63 /* TYPES *********************************************************************/
64 
65 extern KSPIN_LOCK MiSectionPageTableLock;
66 
_Function_class_(RTL_GENERIC_ALLOCATE_ROUTINE)67 _Function_class_(RTL_GENERIC_ALLOCATE_ROUTINE)
68 static
69 PVOID
70 NTAPI
71 MiSectionPageTableAllocate(PRTL_GENERIC_TABLE Table, CLONG Bytes)
72 {
73     PVOID Result;
74     Result = ExAllocatePoolWithTag(NonPagedPool, Bytes, 'tPmM');
75     //DPRINT("MiSectionPageTableAllocate(%d) => %p\n", Bytes, Result);
76     return Result;
77 }
78 
_Function_class_(RTL_GENERIC_FREE_ROUTINE)79 _Function_class_(RTL_GENERIC_FREE_ROUTINE)
80 static
81 VOID
82 NTAPI
83 MiSectionPageTableFree(PRTL_GENERIC_TABLE Table, PVOID Data)
84 {
85     //DPRINT("MiSectionPageTableFree(%p)\n", Data);
86     ExFreePoolWithTag(Data, 'tPmM');
87 }
88 
_Function_class_(RTL_GENERIC_COMPARE_ROUTINE)89 _Function_class_(RTL_GENERIC_COMPARE_ROUTINE)
90 static
91 RTL_GENERIC_COMPARE_RESULTS
92 NTAPI
93 MiSectionPageTableCompare(PRTL_GENERIC_TABLE Table,
94                           PVOID PtrA,
95                           PVOID PtrB)
96 {
97     PLARGE_INTEGER A = PtrA, B = PtrB;
98     BOOLEAN Result = (A->QuadPart < B->QuadPart) ? GenericLessThan :
99         (A->QuadPart == B->QuadPart) ? GenericEqual : GenericGreaterThan;
100 
101 #if 0
102     DPRINT
103         ("Compare: %08x%08x vs %08x%08x => %s\n",
104          A->u.HighPart, A->u.LowPart,
105          B->u.HighPart, B->u.LowPart,
106          Result == GenericLessThan ? "GenericLessThan" :
107          Result == GenericGreaterThan ? "GenericGreaterThan" :
108             "GenericEqual");
109 #endif
110 
111     return Result;
112 }
113 
114 static
115 PCACHE_SECTION_PAGE_TABLE
116 NTAPI
MiSectionPageTableGet(PRTL_GENERIC_TABLE Table,PLARGE_INTEGER FileOffset)117 MiSectionPageTableGet(PRTL_GENERIC_TABLE Table,
118                       PLARGE_INTEGER FileOffset)
119 {
120     LARGE_INTEGER SearchFileOffset;
121     PCACHE_SECTION_PAGE_TABLE PageTable;
122     SearchFileOffset.QuadPart = ROUND_DOWN(FileOffset->QuadPart,
123                                            ENTRIES_PER_ELEMENT * PAGE_SIZE);
124     PageTable = RtlLookupElementGenericTable(Table, &SearchFileOffset);
125 
126     DPRINT("MiSectionPageTableGet(%p,%I64x)\n",
127            Table,
128            FileOffset->QuadPart);
129 
130     return PageTable;
131 }
132 
133 static
134 PCACHE_SECTION_PAGE_TABLE
135 NTAPI
MiSectionPageTableGetOrAllocate(PRTL_GENERIC_TABLE Table,PLARGE_INTEGER FileOffset)136 MiSectionPageTableGetOrAllocate(PRTL_GENERIC_TABLE Table,
137                                 PLARGE_INTEGER FileOffset)
138 {
139     LARGE_INTEGER SearchFileOffset;
140     CACHE_SECTION_PAGE_TABLE SectionZeroPageTable;
141     PCACHE_SECTION_PAGE_TABLE PageTableSlice = MiSectionPageTableGet(Table,
142                                                                      FileOffset);
143     /* Please zero memory when taking away zero initialization. */
144     RtlZeroMemory(&SectionZeroPageTable, sizeof(CACHE_SECTION_PAGE_TABLE));
145     if (!PageTableSlice)
146     {
147         SearchFileOffset.QuadPart = ROUND_DOWN(FileOffset->QuadPart,
148                                                ENTRIES_PER_ELEMENT * PAGE_SIZE);
149         SectionZeroPageTable.FileOffset = SearchFileOffset;
150         SectionZeroPageTable.Refcount = 1;
151         PageTableSlice = RtlInsertElementGenericTable(Table,
152                                                       &SectionZeroPageTable,
153                                                       sizeof(SectionZeroPageTable),
154                                                       NULL);
155         if (!PageTableSlice) return NULL;
156         DPRINT("Allocate page table %p (%I64x)\n",
157                PageTableSlice,
158                PageTableSlice->FileOffset.QuadPart);
159     }
160     return PageTableSlice;
161 }
162 
163 VOID
164 NTAPI
MiInitializeSectionPageTable(PMM_SECTION_SEGMENT Segment)165 MiInitializeSectionPageTable(PMM_SECTION_SEGMENT Segment)
166 {
167     RtlInitializeGenericTable(&Segment->PageTable,
168                               MiSectionPageTableCompare,
169                               MiSectionPageTableAllocate,
170                               MiSectionPageTableFree,
171                               NULL);
172 
173     DPRINT("MiInitializeSectionPageTable(%p)\n", &Segment->PageTable);
174 }
175 
176 NTSTATUS
177 NTAPI
_MmSetPageEntrySectionSegment(PMM_SECTION_SEGMENT Segment,PLARGE_INTEGER Offset,ULONG_PTR Entry,const char * file,int line)178 _MmSetPageEntrySectionSegment(PMM_SECTION_SEGMENT Segment,
179                               PLARGE_INTEGER Offset,
180                               ULONG_PTR Entry,
181                               const char *file,
182                               int line)
183 {
184     ULONG_PTR PageIndex, OldEntry;
185     PCACHE_SECTION_PAGE_TABLE PageTable;
186 
187     ASSERT(Segment->Locked);
188     ASSERT(!IS_SWAP_FROM_SSE(Entry) || !IS_DIRTY_SSE(Entry));
189 
190     PageTable = MiSectionPageTableGetOrAllocate(&Segment->PageTable, Offset);
191 
192     if (!PageTable) return STATUS_NO_MEMORY;
193 
194     ASSERT(MiSectionPageTableGet(&Segment->PageTable, Offset));
195 
196     PageTable->Segment = Segment;
197     PageIndex = (ULONG_PTR)((Offset->QuadPart - PageTable->FileOffset.QuadPart) / PAGE_SIZE);
198     OldEntry = PageTable->PageEntries[PageIndex];
199 
200     DPRINT("MiSetPageEntrySectionSegment(%p,%08x%08x,%x=>%x)\n",
201             Segment,
202             Offset->u.HighPart,
203             Offset->u.LowPart,
204             OldEntry,
205             Entry);
206 
207     /* Manage ref on segment */
208     if (Entry && !OldEntry)
209     {
210         InterlockedIncrement64(Segment->ReferenceCount);
211     }
212     if (OldEntry && !Entry)
213     {
214         MmDereferenceSegment(Segment);
215     }
216 
217     if (Entry && !IS_SWAP_FROM_SSE(Entry))
218     {
219         /* We have a valid entry. See if we must do something */
220         if (OldEntry && !IS_SWAP_FROM_SSE(OldEntry))
221         {
222             /* The previous entry was valid. Shall we swap the Rmaps ? */
223             if (PFN_FROM_SSE(Entry) != PFN_FROM_SSE(OldEntry))
224             {
225                 MmDeleteSectionAssociation(PFN_FROM_SSE(OldEntry));
226 
227                 /* This has to be done before setting the new section association
228                    to prevent a race condition with the paging out path */
229                 PageTable->PageEntries[PageIndex] = Entry;
230 
231                 MmSetSectionAssociation(PFN_FROM_SSE(Entry), Segment, Offset);
232             }
233             else
234             {
235                 PageTable->PageEntries[PageIndex] = Entry;
236             }
237         }
238         else
239         {
240             /*
241              * We're switching to a valid entry from an invalid one.
242              * Add the Rmap and take a ref on the segment.
243              */
244             PageTable->PageEntries[PageIndex] = Entry;
245             MmSetSectionAssociation(PFN_FROM_SSE(Entry), Segment, Offset);
246 
247             if (Offset->QuadPart >= (Segment->LastPage << PAGE_SHIFT))
248                 Segment->LastPage = (Offset->QuadPart >> PAGE_SHIFT) + 1;
249         }
250     }
251     else if (OldEntry && !IS_SWAP_FROM_SSE(OldEntry))
252     {
253         /* We're switching to an invalid entry from a valid one */
254         MmDeleteSectionAssociation(PFN_FROM_SSE(OldEntry));
255         PageTable->PageEntries[PageIndex] = Entry;
256 
257         if (Offset->QuadPart == ((Segment->LastPage - 1ULL) << PAGE_SHIFT))
258         {
259             /* We are unsetting the last page */
260             while (--Segment->LastPage)
261             {
262                 LARGE_INTEGER CheckOffset;
263                 CheckOffset.QuadPart = (Segment->LastPage - 1) << PAGE_SHIFT;
264                 ULONG_PTR Entry = MmGetPageEntrySectionSegment(Segment, &CheckOffset);
265                 if ((Entry != 0) && !IS_SWAP_FROM_SSE(Entry))
266                     break;
267             }
268         }
269     }
270     else
271     {
272         PageTable->PageEntries[PageIndex] = Entry;
273     }
274 
275     return STATUS_SUCCESS;
276 }
277 
278 ULONG_PTR
279 NTAPI
_MmGetPageEntrySectionSegment(PMM_SECTION_SEGMENT Segment,PLARGE_INTEGER Offset,const char * file,int line)280 _MmGetPageEntrySectionSegment(PMM_SECTION_SEGMENT Segment,
281                               PLARGE_INTEGER Offset,
282                               const char *file,
283                               int line)
284 {
285     LARGE_INTEGER FileOffset;
286     ULONG_PTR PageIndex, Result;
287     PCACHE_SECTION_PAGE_TABLE PageTable;
288 
289     ASSERT(Segment->Locked);
290     FileOffset.QuadPart = ROUND_DOWN(Offset->QuadPart,
291                                      ENTRIES_PER_ELEMENT * PAGE_SIZE);
292     PageTable = MiSectionPageTableGet(&Segment->PageTable, &FileOffset);
293     if (!PageTable) return 0;
294     PageIndex = (ULONG_PTR)((Offset->QuadPart - PageTable->FileOffset.QuadPart) / PAGE_SIZE);
295     Result = PageTable->PageEntries[PageIndex];
296 #if 0
297     DPRINTC
298         ("MiGetPageEntrySectionSegment(%p,%08x%08x) => %x %s:%d\n",
299          Segment,
300          FileOffset.u.HighPart,
301          FileOffset.u.LowPart + PageIndex * PAGE_SIZE,
302          Result,
303          file, line);
304 #endif
305     return Result;
306 }
307 
308 /*
309 
310 Destroy the rtl generic table that serves as the section's page table.  Call
311 the FreePage function for each non-zero entry in the section page table as
312 we go.  Note that the page table is still techinally valid until after all
313 pages are destroyed, as we don't finally destroy the table until we've free
314 each slice.  There is no order guarantee for deletion of individual elements
315 although it's in-order as written now.
316 
317 */
318 
319 VOID
320 NTAPI
MmFreePageTablesSectionSegment(PMM_SECTION_SEGMENT Segment,FREE_SECTION_PAGE_FUN FreePage)321 MmFreePageTablesSectionSegment(PMM_SECTION_SEGMENT Segment,
322                                FREE_SECTION_PAGE_FUN FreePage)
323 {
324     PCACHE_SECTION_PAGE_TABLE Element;
325     DPRINT("MiFreePageTablesSectionSegment(%p)\n", &Segment->PageTable);
326     while ((Element = RtlGetElementGenericTable(&Segment->PageTable, 0))) {
327         DPRINT("Delete table for <%wZ> %p -> %I64x\n",
328                Segment->FileObject ? &Segment->FileObject->FileName : NULL,
329                Segment,
330                Element->FileOffset.QuadPart);
331         if (FreePage)
332         {
333             ULONG i;
334             for (i = 0; i < ENTRIES_PER_ELEMENT; i++)
335             {
336                 ULONG_PTR Entry;
337                 LARGE_INTEGER Offset;
338                 Offset.QuadPart = Element->FileOffset.QuadPart + i * PAGE_SIZE;
339                 Entry = Element->PageEntries[i];
340                 if (Entry && !IS_SWAP_FROM_SSE(Entry))
341                 {
342                     DPRINT("Freeing page %p:%Ix @ %I64x\n",
343                            Segment,
344                            Entry,
345                            Offset.QuadPart);
346 
347                     FreePage(Segment, &Offset);
348                 }
349             }
350         }
351         DPRINT("Remove memory\n");
352         RtlDeleteElementGenericTable(&Segment->PageTable, Element);
353     }
354     DPRINT("Done\n");
355 }
356 
357 /*
358 
359 Retrieves the MM_SECTION_SEGMENT and fills in the LARGE_INTEGER Offset given
360 by the caller that corresponds to the page specified.  This uses
361 MmGetSegmentRmap to find the rmap belonging to the segment itself, and uses
362 the result as a pointer to a 256-entry page table structure.  The rmap also
363 includes 8 bits of offset information indication one of 256 page entries that
364 the rmap corresponds to.  This information together gives us an exact offset
365 into the file, as well as the MM_SECTION_SEGMENT pointer stored in the page
366 table slice.
367 
368 NULL is returned is there is no segment rmap for the page.
369 
370 */
371 
372 PMM_SECTION_SEGMENT
373 NTAPI
MmGetSectionAssociation(PFN_NUMBER Page,PLARGE_INTEGER Offset)374 MmGetSectionAssociation(PFN_NUMBER Page,
375                         PLARGE_INTEGER Offset)
376 {
377     ULONG RawOffset;
378     PMM_SECTION_SEGMENT Segment = NULL;
379     PCACHE_SECTION_PAGE_TABLE PageTable;
380 
381     KIRQL OldIrql = MiAcquirePfnLock();
382 
383     PageTable = MmGetSegmentRmap(Page, &RawOffset);
384     if (PageTable)
385     {
386         Segment = PageTable->Segment;
387         Offset->QuadPart = PageTable->FileOffset.QuadPart +
388                            ((ULONG64)RawOffset << PAGE_SHIFT);
389         ASSERT(PFN_FROM_SSE(PageTable->PageEntries[RawOffset]) == Page);
390         InterlockedIncrement64(Segment->ReferenceCount);
391     }
392 
393     MiReleasePfnLock(OldIrql);
394 
395     return Segment;
396 }
397 
398 NTSTATUS
399 NTAPI
MmSetSectionAssociation(PFN_NUMBER Page,PMM_SECTION_SEGMENT Segment,PLARGE_INTEGER Offset)400 MmSetSectionAssociation(PFN_NUMBER Page,
401                         PMM_SECTION_SEGMENT Segment,
402                         PLARGE_INTEGER Offset)
403 {
404     PCACHE_SECTION_PAGE_TABLE PageTable;
405     ULONG ActualOffset;
406 
407     PageTable = MiSectionPageTableGet(&Segment->PageTable, Offset);
408     ASSERT(PageTable);
409 
410     ActualOffset = (ULONG)(Offset->QuadPart - PageTable->FileOffset.QuadPart);
411     MmInsertRmap(Page,
412                  (PEPROCESS)PageTable,
413                  (PVOID)(RMAP_SEGMENT_MASK | (ActualOffset >> PAGE_SHIFT)));
414 
415     return STATUS_SUCCESS;
416 }
417