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 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 MmSetSectionAssociation(PFN_FROM_SSE(Entry), Segment, Offset); 227 } 228 } 229 else 230 { 231 /* 232 * We're switching to a valid entry from an invalid one. 233 * Add the Rmap and take a ref on the segment. 234 */ 235 MmSetSectionAssociation(PFN_FROM_SSE(Entry), Segment, Offset); 236 237 if (Offset->QuadPart >= (Segment->LastPage << PAGE_SHIFT)) 238 Segment->LastPage = (Offset->QuadPart >> PAGE_SHIFT) + 1; 239 } 240 } 241 else if (OldEntry && !IS_SWAP_FROM_SSE(OldEntry)) 242 { 243 /* We're switching to an invalid entry from a valid one */ 244 MmDeleteSectionAssociation(PFN_FROM_SSE(OldEntry)); 245 246 if (Offset->QuadPart == ((Segment->LastPage - 1ULL) << PAGE_SHIFT)) 247 { 248 /* We are unsetting the last page */ 249 while (--Segment->LastPage) 250 { 251 LARGE_INTEGER CheckOffset; 252 CheckOffset.QuadPart = (Segment->LastPage - 1) << PAGE_SHIFT; 253 ULONG_PTR Entry = MmGetPageEntrySectionSegment(Segment, &CheckOffset); 254 if ((Entry != 0) && !IS_SWAP_FROM_SSE(Entry)) 255 break; 256 } 257 } 258 } 259 260 PageTable->PageEntries[PageIndex] = Entry; 261 return STATUS_SUCCESS; 262 } 263 264 ULONG_PTR 265 NTAPI 266 _MmGetPageEntrySectionSegment(PMM_SECTION_SEGMENT Segment, 267 PLARGE_INTEGER Offset, 268 const char *file, 269 int line) 270 { 271 LARGE_INTEGER FileOffset; 272 ULONG_PTR PageIndex, Result; 273 PCACHE_SECTION_PAGE_TABLE PageTable; 274 275 ASSERT(Segment->Locked); 276 FileOffset.QuadPart = ROUND_DOWN(Offset->QuadPart, 277 ENTRIES_PER_ELEMENT * PAGE_SIZE); 278 PageTable = MiSectionPageTableGet(&Segment->PageTable, &FileOffset); 279 if (!PageTable) return 0; 280 PageIndex = (ULONG_PTR)((Offset->QuadPart - PageTable->FileOffset.QuadPart) / PAGE_SIZE); 281 Result = PageTable->PageEntries[PageIndex]; 282 #if 0 283 DPRINTC 284 ("MiGetPageEntrySectionSegment(%p,%08x%08x) => %x %s:%d\n", 285 Segment, 286 FileOffset.u.HighPart, 287 FileOffset.u.LowPart + PageIndex * PAGE_SIZE, 288 Result, 289 file, line); 290 #endif 291 return Result; 292 } 293 294 /* 295 296 Destroy the rtl generic table that serves as the section's page table. Call 297 the FreePage function for each non-zero entry in the section page table as 298 we go. Note that the page table is still techinally valid until after all 299 pages are destroyed, as we don't finally destroy the table until we've free 300 each slice. There is no order guarantee for deletion of individual elements 301 although it's in-order as written now. 302 303 */ 304 305 VOID 306 NTAPI 307 MmFreePageTablesSectionSegment(PMM_SECTION_SEGMENT Segment, 308 FREE_SECTION_PAGE_FUN FreePage) 309 { 310 PCACHE_SECTION_PAGE_TABLE Element; 311 DPRINT("MiFreePageTablesSectionSegment(%p)\n", &Segment->PageTable); 312 while ((Element = RtlGetElementGenericTable(&Segment->PageTable, 0))) { 313 DPRINT("Delete table for <%wZ> %p -> %I64x\n", 314 Segment->FileObject ? &Segment->FileObject->FileName : NULL, 315 Segment, 316 Element->FileOffset.QuadPart); 317 if (FreePage) 318 { 319 ULONG i; 320 for (i = 0; i < ENTRIES_PER_ELEMENT; i++) 321 { 322 ULONG_PTR Entry; 323 LARGE_INTEGER Offset; 324 Offset.QuadPart = Element->FileOffset.QuadPart + i * PAGE_SIZE; 325 Entry = Element->PageEntries[i]; 326 if (Entry && !IS_SWAP_FROM_SSE(Entry)) 327 { 328 DPRINT("Freeing page %p:%Ix @ %I64x\n", 329 Segment, 330 Entry, 331 Offset.QuadPart); 332 333 FreePage(Segment, &Offset); 334 } 335 } 336 } 337 DPRINT("Remove memory\n"); 338 RtlDeleteElementGenericTable(&Segment->PageTable, Element); 339 } 340 DPRINT("Done\n"); 341 } 342 343 /* 344 345 Retrieves the MM_SECTION_SEGMENT and fills in the LARGE_INTEGER Offset given 346 by the caller that corresponds to the page specified. This uses 347 MmGetSegmentRmap to find the rmap belonging to the segment itself, and uses 348 the result as a pointer to a 256-entry page table structure. The rmap also 349 includes 8 bits of offset information indication one of 256 page entries that 350 the rmap corresponds to. This information together gives us an exact offset 351 into the file, as well as the MM_SECTION_SEGMENT pointer stored in the page 352 table slice. 353 354 NULL is returned is there is no segment rmap for the page. 355 356 */ 357 358 PMM_SECTION_SEGMENT 359 NTAPI 360 MmGetSectionAssociation(PFN_NUMBER Page, 361 PLARGE_INTEGER Offset) 362 { 363 ULONG RawOffset; 364 PMM_SECTION_SEGMENT Segment = NULL; 365 PCACHE_SECTION_PAGE_TABLE PageTable; 366 367 PageTable = MmGetSegmentRmap(Page, &RawOffset); 368 if (PageTable) 369 { 370 Segment = PageTable->Segment; 371 Offset->QuadPart = PageTable->FileOffset.QuadPart + 372 ((ULONG64)RawOffset << PAGE_SHIFT); 373 ASSERT(PFN_FROM_SSE(PageTable->PageEntries[RawOffset]) == Page); 374 InterlockedIncrement64(Segment->ReferenceCount); 375 } 376 377 return Segment; 378 } 379 380 NTSTATUS 381 NTAPI 382 MmSetSectionAssociation(PFN_NUMBER Page, 383 PMM_SECTION_SEGMENT Segment, 384 PLARGE_INTEGER Offset) 385 { 386 PCACHE_SECTION_PAGE_TABLE PageTable; 387 ULONG ActualOffset; 388 389 PageTable = MiSectionPageTableGet(&Segment->PageTable, Offset); 390 ASSERT(PageTable); 391 392 ActualOffset = (ULONG)(Offset->QuadPart - PageTable->FileOffset.QuadPart); 393 MmInsertRmap(Page, 394 (PEPROCESS)PageTable, 395 (PVOID)(RMAP_SEGMENT_MASK | (ActualOffset >> PAGE_SHIFT))); 396 397 return STATUS_SUCCESS; 398 } 399