xref: /reactos/ntoskrnl/cache/section/sptab.c (revision c2c66aff)
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 
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 
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 
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
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
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
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
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     if (Entry && !IS_SWAP_FROM_SSE(Entry))
191         MmGetRmapListHeadPage(PFN_FROM_SSE(Entry));
192 
193     PageTable = MiSectionPageTableGetOrAllocate(&Segment->PageTable, Offset);
194 
195     if (!PageTable) return STATUS_NO_MEMORY;
196 
197     ASSERT(MiSectionPageTableGet(&Segment->PageTable, Offset));
198 
199     PageTable->Segment = Segment;
200     PageIndex = (ULONG_PTR)((Offset->QuadPart - PageTable->FileOffset.QuadPart) / PAGE_SIZE);
201     OldEntry = PageTable->PageEntries[PageIndex];
202 
203     DPRINT("MiSetPageEntrySectionSegment(%p,%08x%08x,%x=>%x)\n",
204             Segment,
205             Offset->u.HighPart,
206             Offset->u.LowPart,
207             OldEntry,
208             Entry);
209 
210     if (PFN_FROM_SSE(Entry) == PFN_FROM_SSE(OldEntry)) {
211         /* Nothing */
212     } else if (Entry && !IS_SWAP_FROM_SSE(Entry)) {
213         ASSERT(!OldEntry || IS_SWAP_FROM_SSE(OldEntry));
214         MmSetSectionAssociation(PFN_FROM_SSE(Entry), Segment, Offset);
215     } else if (OldEntry && !IS_SWAP_FROM_SSE(OldEntry)) {
216         ASSERT(!Entry || IS_SWAP_FROM_SSE(Entry));
217         MmDeleteSectionAssociation(PFN_FROM_SSE(OldEntry));
218     } else if (IS_SWAP_FROM_SSE(Entry)) {
219         ASSERT(!IS_SWAP_FROM_SSE(OldEntry) ||
220                SWAPENTRY_FROM_SSE(OldEntry) == MM_WAIT_ENTRY);
221         if (OldEntry && SWAPENTRY_FROM_SSE(OldEntry) != MM_WAIT_ENTRY)
222             MmDeleteSectionAssociation(PFN_FROM_SSE(OldEntry));
223     } else if (IS_SWAP_FROM_SSE(OldEntry)) {
224         ASSERT(!IS_SWAP_FROM_SSE(Entry));
225         if (Entry)
226             MmSetSectionAssociation(PFN_FROM_SSE(OldEntry), Segment, Offset);
227     } else {
228         /* We should not be replacing a page like this */
229         ASSERT(FALSE);
230     }
231     PageTable->PageEntries[PageIndex] = Entry;
232     return STATUS_SUCCESS;
233 }
234 
235 ULONG_PTR
236 NTAPI
237 _MmGetPageEntrySectionSegment(PMM_SECTION_SEGMENT Segment,
238                               PLARGE_INTEGER Offset,
239                               const char *file,
240                               int line)
241 {
242     LARGE_INTEGER FileOffset;
243     ULONG_PTR PageIndex, Result;
244     PCACHE_SECTION_PAGE_TABLE PageTable;
245 
246     ASSERT(Segment->Locked);
247     FileOffset.QuadPart = ROUND_DOWN(Offset->QuadPart,
248                                      ENTRIES_PER_ELEMENT * PAGE_SIZE);
249     PageTable = MiSectionPageTableGet(&Segment->PageTable, &FileOffset);
250     if (!PageTable) return 0;
251     PageIndex = (ULONG_PTR)((Offset->QuadPart - PageTable->FileOffset.QuadPart) / PAGE_SIZE);
252     Result = PageTable->PageEntries[PageIndex];
253 #if 0
254     DPRINTC
255         ("MiGetPageEntrySectionSegment(%p,%08x%08x) => %x %s:%d\n",
256          Segment,
257          FileOffset.u.HighPart,
258          FileOffset.u.LowPart + PageIndex * PAGE_SIZE,
259          Result,
260          file, line);
261 #endif
262     return Result;
263 }
264 
265 /*
266 
267 Destroy the rtl generic table that serves as the section's page table.  Call
268 the FreePage function for each non-zero entry in the section page table as
269 we go.  Note that the page table is still techinally valid until after all
270 pages are destroyed, as we don't finally destroy the table until we've free
271 each slice.  There is no order guarantee for deletion of individual elements
272 although it's in-order as written now.
273 
274 */
275 
276 VOID
277 NTAPI
278 MmFreePageTablesSectionSegment(PMM_SECTION_SEGMENT Segment,
279                                FREE_SECTION_PAGE_FUN FreePage)
280 {
281     PCACHE_SECTION_PAGE_TABLE Element;
282     DPRINT("MiFreePageTablesSectionSegment(%p)\n", &Segment->PageTable);
283     while ((Element = RtlGetElementGenericTable(&Segment->PageTable, 0))) {
284         DPRINT("Delete table for <%wZ> %p -> %I64x\n",
285                Segment->FileObject ? &Segment->FileObject->FileName : NULL,
286                Segment,
287                Element->FileOffset.QuadPart);
288         if (FreePage)
289         {
290             ULONG i;
291             for (i = 0; i < ENTRIES_PER_ELEMENT; i++)
292             {
293                 ULONG_PTR Entry;
294                 LARGE_INTEGER Offset;
295                 Offset.QuadPart = Element->FileOffset.QuadPart + i * PAGE_SIZE;
296                 Entry = Element->PageEntries[i];
297                 if (Entry && !IS_SWAP_FROM_SSE(Entry))
298                 {
299                     DPRINT("Freeing page %p:%Ix @ %I64x\n",
300                            Segment,
301                            Entry,
302                            Offset.QuadPart);
303 
304                     FreePage(Segment, &Offset);
305                 }
306             }
307         }
308         DPRINT("Remove memory\n");
309         RtlDeleteElementGenericTable(&Segment->PageTable, Element);
310     }
311     DPRINT("Done\n");
312 }
313 
314 /*
315 
316 Retrieves the MM_SECTION_SEGMENT and fills in the LARGE_INTEGER Offset given
317 by the caller that corresponds to the page specified.  This uses
318 MmGetSegmentRmap to find the rmap belonging to the segment itself, and uses
319 the result as a pointer to a 256-entry page table structure.  The rmap also
320 includes 8 bits of offset information indication one of 256 page entries that
321 the rmap corresponds to.  This information together gives us an exact offset
322 into the file, as well as the MM_SECTION_SEGMENT pointer stored in the page
323 table slice.
324 
325 NULL is returned is there is no segment rmap for the page.
326 
327 */
328 
329 PMM_SECTION_SEGMENT
330 NTAPI
331 MmGetSectionAssociation(PFN_NUMBER Page,
332                         PLARGE_INTEGER Offset)
333 {
334     ULONG RawOffset;
335     PMM_SECTION_SEGMENT Segment = NULL;
336     PCACHE_SECTION_PAGE_TABLE PageTable;
337 
338     PageTable = (PCACHE_SECTION_PAGE_TABLE)MmGetSegmentRmap(Page,
339                                                             &RawOffset);
340     if (PageTable)
341     {
342         Segment = PageTable->Segment;
343         Offset->QuadPart = PageTable->FileOffset.QuadPart +
344                            ((ULONG64)RawOffset << PAGE_SHIFT);
345     }
346 
347     return Segment;
348 }
349 
350 NTSTATUS
351 NTAPI
352 MmSetSectionAssociation(PFN_NUMBER Page,
353                         PMM_SECTION_SEGMENT Segment,
354                         PLARGE_INTEGER Offset)
355 {
356     PCACHE_SECTION_PAGE_TABLE PageTable;
357     ULONG ActualOffset;
358 
359     PageTable = MiSectionPageTableGet(&Segment->PageTable, Offset);
360     ASSERT(PageTable);
361 
362     ActualOffset = (ULONG)(Offset->QuadPart - PageTable->FileOffset.QuadPart);
363     MmInsertRmap(Page,
364                  (PEPROCESS)PageTable,
365                  (PVOID)(RMAP_SEGMENT_MASK | (ActualOffset >> PAGE_SHIFT)));
366 
367     return STATUS_SUCCESS;
368 }
369