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