Virtual memory support
[reactos.git] / reactos / ntoskrnl / cc / view.c
1 /*
2 * ReactOS kernel
3 * Copyright (C) 1998, 1999, 2000, 2001 ReactOS Team
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19 /* $Id: view.c,v 1.32 2001/12/31 01:53:44 dwelch Exp $
20 *
21 * COPYRIGHT: See COPYING in the top level directory
22 * PROJECT: ReactOS kernel
23 * FILE: ntoskrnl/cc/view.c
24 * PURPOSE: Cache manager
25 * PROGRAMMER: David Welch (welch@mcmail.com)
26 * PORTABILITY: Checked
27 * UPDATE HISTORY:
28 * Created 22/05/98
29 */
30
31 /* NOTES **********************************************************************
32 *
33 * This is not the NT implementation of a file cache nor anything much like
34 * it.
35 *
36 * The general procedure for a filesystem to implement a read or write
37 * dispatch routine is as follows
38 *
39 * (1) If caching for the FCB hasn't been initiated then so do by calling
40 * CcInitializeFileCache.
41 *
42 * (2) For each 4k region which is being read or written obtain a cache page
43 * by calling CcRequestCachePage.
44 *
45 * (3) If either the page is being read or not completely written, and it is
46 * not up to date then read its data from the underlying medium. If the read
47 * fails then call CcReleaseCachePage with VALID as FALSE and return a error.
48 *
49 * (4) Copy the data into or out of the page as necessary.
50 *
51 * (5) Release the cache page
52 */
53 /* INCLUDES ******************************************************************/
54
55 #include <ddk/ntddk.h>
56 #include <ddk/ntifs.h>
57 #include <internal/mm.h>
58 #include <internal/cc.h>
59 #include <internal/pool.h>
60 #include <ntos/minmax.h>
61
62 #define NDEBUG
63 #include <internal/debug.h>
64
65 /* GLOBALS *******************************************************************/
66
67 #define ROUND_UP(N, S) ((((N) + (S) - 1) / (S)) * (S))
68 #define ROUND_DOWN(N, S) (((N) % (S)) ? ROUND_UP(N, S) - S : N)
69
70 #define TAG_CSEG TAG('C', 'S', 'E', 'G')
71 #define TAG_BCB TAG('B', 'C', 'B', ' ')
72
73 static LIST_ENTRY DirtySegmentListHead;
74 static LIST_ENTRY CacheSegmentListHead;
75 static LIST_ENTRY CacheSegmentLRUListHead;
76
77 static FAST_MUTEX ViewLock;
78
79 NTSTATUS STDCALL
80 CcRosInternalFreeCacheSegment(PBCB Bcb, PCACHE_SEGMENT CacheSeg);
81
82 /* FUNCTIONS *****************************************************************/
83
84 NTSTATUS
85 CcRosTrimCache(ULONG Target, ULONG Priority, PULONG NrFreed)
86 {
87 PLIST_ENTRY current_entry;
88 PCACHE_SEGMENT current;
89 ULONG PagesPerSegment;
90 ULONG PagesFreed;
91
92 DPRINT("CcRosTrimCache(Target %d)\n", Target);
93
94 *NrFreed = 0;
95
96 ExAcquireFastMutex(&ViewLock);
97 current_entry = CacheSegmentLRUListHead.Flink;
98 while (current_entry != &CacheSegmentLRUListHead && Target > 0)
99 {
100 current = CONTAINING_RECORD(current_entry, CACHE_SEGMENT, CacheSegmentLRUListEntry);
101 current_entry = current_entry->Flink;
102 ExAcquireFastMutex(&current->Lock);
103 if (current->MappedCount > 0 || current->Dirty || current->ReferenceCount > 0)
104 {
105 ExReleaseFastMutex(&current->Lock);
106 continue;
107 }
108 ExReleaseFastMutex(&current->Lock);
109 DPRINT("current->Bcb->CacheSegmentSize %d\n", current->Bcb->CacheSegmentSize);
110 PagesPerSegment = current->Bcb->CacheSegmentSize / PAGESIZE;
111 CcRosInternalFreeCacheSegment(current->Bcb, current);
112 DPRINT("CcRosTrimCache(): Freed %d\n", PagesPerSegment);
113 PagesFreed = min(PagesPerSegment, Target);
114 Target = Target - PagesFreed;
115 (*NrFreed) = (*NrFreed) + PagesFreed;
116 }
117 ExReleaseFastMutex(&ViewLock);
118 DPRINT("CcRosTrimCache() finished\n");
119 return(STATUS_SUCCESS);
120 }
121
122 NTSTATUS STDCALL
123 CcRosReleaseCacheSegment(PBCB Bcb,
124 PCACHE_SEGMENT CacheSeg,
125 BOOLEAN Valid,
126 BOOLEAN Dirty,
127 BOOLEAN Mapped)
128 {
129 DPRINT("CcReleaseCachePage(Bcb %x, CacheSeg %x, Valid %d)\n",
130 Bcb, CacheSeg, Valid);
131
132 CacheSeg->Valid = Valid;
133 CacheSeg->Dirty = CacheSeg->Dirty || Dirty;
134 if (Mapped)
135 {
136 CacheSeg->MappedCount++;
137 }
138 ExReleaseFastMutex(&CacheSeg->Lock);
139 ExAcquireFastMutex(&ViewLock);
140 RemoveEntryList(&CacheSeg->CacheSegmentLRUListEntry);
141 InsertTailList(&CacheSegmentLRUListHead, &CacheSeg->CacheSegmentLRUListEntry);
142 ExReleaseFastMutex(&ViewLock);
143 InterlockedDecrement(&CacheSeg->ReferenceCount);
144
145 DPRINT("CcReleaseCachePage() finished\n");
146
147 return(STATUS_SUCCESS);
148 }
149
150 PCACHE_SEGMENT CcRosLookupCacheSegment(PBCB Bcb, ULONG FileOffset)
151 {
152 PLIST_ENTRY current_entry;
153 PCACHE_SEGMENT current;
154 KIRQL oldIrql;
155
156 KeAcquireSpinLock(&Bcb->BcbLock, &oldIrql);
157 current_entry = Bcb->BcbSegmentListHead.Flink;
158 while (current_entry != &Bcb->BcbSegmentListHead)
159 {
160 current = CONTAINING_RECORD(current_entry, CACHE_SEGMENT,
161 BcbSegmentListEntry);
162 if (current->FileOffset <= FileOffset &&
163 (current->FileOffset + Bcb->CacheSegmentSize) > FileOffset)
164 {
165 KeReleaseSpinLock(&Bcb->BcbLock, oldIrql);
166 return(current);
167 }
168 current_entry = current_entry->Flink;
169 }
170 KeReleaseSpinLock(&Bcb->BcbLock, oldIrql);
171 return(NULL);
172 }
173
174 NTSTATUS
175 CcRosSuggestFreeCacheSegment(PBCB Bcb, ULONG FileOffset, BOOLEAN NowDirty)
176 {
177 PCACHE_SEGMENT CacheSeg;
178
179 ExAcquireFastMutex(&ViewLock);
180 CacheSeg = CcRosLookupCacheSegment(Bcb, FileOffset);
181 if (CacheSeg == NULL)
182 {
183 KeBugCheck(0);
184 }
185 ExAcquireFastMutex(&CacheSeg->Lock);
186 if (CacheSeg->MappedCount > 0)
187 {
188 KeBugCheck(0);
189 }
190 CacheSeg->Dirty = CacheSeg->Dirty || NowDirty;
191 if (CacheSeg->Dirty || CacheSeg->ReferenceCount > 0)
192 {
193 ExReleaseFastMutex(&CacheSeg->Lock);
194 ExReleaseFastMutex(&ViewLock);
195 return(STATUS_UNSUCCESSFUL);
196 }
197 ExReleaseFastMutex(&CacheSeg->Lock);
198 CcRosInternalFreeCacheSegment(CacheSeg->Bcb, CacheSeg);
199 ExReleaseFastMutex(&ViewLock);
200 return(STATUS_SUCCESS);
201 }
202
203 NTSTATUS
204 CcRosUnmapCacheSegment(PBCB Bcb, ULONG FileOffset, BOOLEAN NowDirty)
205 {
206 PCACHE_SEGMENT CacheSeg;
207
208 ExAcquireFastMutex(&ViewLock);
209 CacheSeg = CcRosLookupCacheSegment(Bcb, FileOffset);
210 if (CacheSeg == NULL)
211 {
212 ExReleaseFastMutex(&ViewLock);
213 return(STATUS_UNSUCCESSFUL);
214 }
215 CacheSeg->ReferenceCount++;
216 ExReleaseFastMutex(&ViewLock);
217 ExAcquireFastMutex(&CacheSeg->Lock);
218 CacheSeg->MappedCount--;
219 CacheSeg->Dirty = CacheSeg->Dirty || NowDirty;
220 ExReleaseFastMutex(&CacheSeg->Lock);
221 return(STATUS_SUCCESS);
222 }
223
224 NTSTATUS
225 CcRosGetCacheSegment(PBCB Bcb,
226 ULONG FileOffset,
227 PULONG BaseOffset,
228 PVOID* BaseAddress,
229 PBOOLEAN UptoDate,
230 PCACHE_SEGMENT* CacheSeg)
231 {
232 PCACHE_SEGMENT current;
233 ULONG i;
234 NTSTATUS Status;
235 KIRQL oldIrql;
236
237 /*
238 * Acquire the global lock.
239 */
240 ExAcquireFastMutex(&ViewLock);
241
242 /*
243 * Look for a cache segment already mapping the same data.
244 */
245 current = CcRosLookupCacheSegment(Bcb, FileOffset);
246 if (current != NULL)
247 {
248 /*
249 * Make sure the cache segment can't go away outside of our control.
250 */
251 current->ReferenceCount++;
252 /*
253 * Release the global lock and lock the cache segment.
254 */
255 ExReleaseFastMutex(&ViewLock);
256 ExAcquireFastMutex(&current->Lock);
257 /*
258 * Return information about the segment to the caller.
259 */
260 *UptoDate = current->Valid;
261 *BaseAddress = current->BaseAddress;
262 *CacheSeg = current;
263 *BaseOffset = current->FileOffset;
264 return(STATUS_SUCCESS);
265 }
266
267 /*
268 * Otherwise create a new segment.
269 */
270 current = ExAllocatePoolWithTag(NonPagedPool, sizeof(CACHE_SEGMENT),
271 TAG_CSEG);
272
273 MmLockAddressSpace(MmGetKernelAddressSpace());
274 current->BaseAddress = NULL;
275 Status = MmCreateMemoryArea(KernelMode,
276 MmGetKernelAddressSpace(),
277 MEMORY_AREA_CACHE_SEGMENT,
278 &current->BaseAddress,
279 Bcb->CacheSegmentSize,
280 PAGE_READWRITE,
281 (PMEMORY_AREA*)&current->MemoryArea,
282 FALSE);
283 if (!NT_SUCCESS(Status))
284 {
285 MmUnlockAddressSpace(MmGetKernelAddressSpace());
286 KeBugCheck(0);
287 }
288 MmUnlockAddressSpace(MmGetKernelAddressSpace());
289 current->Valid = FALSE;
290 current->Dirty = FALSE;
291 current->FileOffset = ROUND_DOWN(FileOffset, Bcb->CacheSegmentSize);
292 current->Bcb = Bcb;
293 current->MappedCount = 0;
294 ExInitializeFastMutex(&current->Lock);
295 current->ReferenceCount = 1;
296 KeAcquireSpinLock(&Bcb->BcbLock, &oldIrql);
297 InsertTailList(&Bcb->BcbSegmentListHead, &current->BcbSegmentListEntry);
298 KeReleaseSpinLock(&Bcb->BcbLock, oldIrql);
299 InsertTailList(&CacheSegmentListHead, &current->CacheSegmentListEntry);
300 InsertTailList(&CacheSegmentLRUListHead, &current->CacheSegmentLRUListEntry);
301 current->DirtySegmentListEntry.Flink = current->DirtySegmentListEntry.Blink = NULL;
302 ExAcquireFastMutex(&current->Lock);
303 ExReleaseFastMutex(&ViewLock);
304 *UptoDate = current->Valid;
305 *BaseAddress = current->BaseAddress;
306 *CacheSeg = current;
307 *BaseOffset = current->FileOffset;
308 for (i = 0; i < (Bcb->CacheSegmentSize / PAGESIZE); i++)
309 {
310 PVOID Page;
311
312 Status = MmRequestPageMemoryConsumer(MC_CACHE, TRUE, &Page);
313 if (!NT_SUCCESS(Status))
314 {
315 KeBugCheck(0);
316 }
317
318 Status = MmCreateVirtualMapping(NULL,
319 current->BaseAddress + (i * PAGESIZE),
320 PAGE_READWRITE,
321 (ULONG)Page);
322
323 if (!NT_SUCCESS(Status))
324 {
325 KeBugCheck(0);
326 }
327 }
328 return(STATUS_SUCCESS);
329 }
330
331 NTSTATUS STDCALL
332 CcRosRequestCacheSegment(PBCB Bcb,
333 ULONG FileOffset,
334 PVOID* BaseAddress,
335 PBOOLEAN UptoDate,
336 PCACHE_SEGMENT* CacheSeg)
337 /*
338 * FUNCTION: Request a page mapping for a BCB
339 */
340 {
341 ULONG BaseOffset;
342
343 if ((FileOffset % Bcb->CacheSegmentSize) != 0)
344 {
345 CPRINT("Bad fileoffset %x should be multiple of %x",
346 FileOffset, Bcb->CacheSegmentSize);
347 KeBugCheck(0);
348 }
349
350 return(CcRosGetCacheSegment(Bcb,
351 FileOffset,
352 &BaseOffset,
353 BaseAddress,
354 UptoDate,
355 CacheSeg));
356 }
357
358 STATIC VOID
359 CcFreeCachePage(PVOID Context, MEMORY_AREA* MemoryArea, PVOID Address, ULONG PhysAddr,
360 BOOLEAN Dirty)
361 {
362 if (PhysAddr != 0)
363 {
364 MmReleasePageMemoryConsumer(MC_CACHE, (PVOID)PhysAddr);
365 }
366 }
367
368 NTSTATUS STDCALL
369 CcRosInternalFreeCacheSegment(PBCB Bcb, PCACHE_SEGMENT CacheSeg)
370 /*
371 * FUNCTION: Releases a cache segment associated with a BCB
372 */
373 {
374 DPRINT("Freeing cache segment %x\n", CacheSeg);
375 RemoveEntryList(&CacheSeg->CacheSegmentListEntry);
376 RemoveEntryList(&CacheSeg->CacheSegmentLRUListEntry);
377 RemoveEntryList(&CacheSeg->BcbSegmentListEntry);
378 MmLockAddressSpace(MmGetKernelAddressSpace());
379 MmFreeMemoryArea(MmGetKernelAddressSpace(),
380 CacheSeg->BaseAddress,
381 Bcb->CacheSegmentSize,
382 CcFreeCachePage,
383 NULL);
384 MmUnlockAddressSpace(MmGetKernelAddressSpace());
385 ExFreePool(CacheSeg);
386 return(STATUS_SUCCESS);
387 }
388
389 NTSTATUS STDCALL
390 CcRosFreeCacheSegment(PBCB Bcb, PCACHE_SEGMENT CacheSeg)
391 {
392 NTSTATUS Status;
393 ExAcquireFastMutex(&ViewLock);
394 Status = CcRosInternalFreeCacheSegment(Bcb, CacheSeg);
395 ExReleaseFastMutex(&ViewLock);
396 return(Status);
397 }
398
399 NTSTATUS STDCALL
400 CcRosReleaseFileCache(PFILE_OBJECT FileObject, PBCB Bcb)
401 /*
402 * FUNCTION: Releases the BCB associated with a file object
403 */
404 {
405 PLIST_ENTRY current_entry;
406 PCACHE_SEGMENT current;
407
408 DPRINT("CcRosReleaseFileCache(FileObject %x, Bcb %x)\n", Bcb->FileObject, Bcb);
409
410 MmFreeSectionSegments(Bcb->FileObject);
411
412 /*
413 * Release all cache segments.
414 */
415 current_entry = Bcb->BcbSegmentListHead.Flink;
416 while (current_entry != &Bcb->BcbSegmentListHead)
417 {
418 current =
419 CONTAINING_RECORD(current_entry, CACHE_SEGMENT, BcbSegmentListEntry);
420 current_entry = current_entry->Flink;
421 CcRosFreeCacheSegment(Bcb, current);
422 }
423
424 ObDereferenceObject (Bcb->FileObject);
425 ExFreePool(Bcb);
426
427 return(STATUS_SUCCESS);
428 }
429
430 NTSTATUS STDCALL
431 CcRosInitializeFileCache(PFILE_OBJECT FileObject,
432 PBCB* Bcb,
433 ULONG CacheSegmentSize)
434 /*
435 * FUNCTION: Initializes a BCB for a file object
436 */
437 {
438 (*Bcb) = ExAllocatePoolWithTag(NonPagedPool, sizeof(BCB), TAG_BCB);
439 if ((*Bcb) == NULL)
440 {
441 return(STATUS_UNSUCCESSFUL);
442 }
443
444 ObReferenceObjectByPointer(FileObject,
445 FILE_ALL_ACCESS,
446 NULL,
447 KernelMode);
448 (*Bcb)->FileObject = FileObject;
449 (*Bcb)->CacheSegmentSize = CacheSegmentSize;
450 if (FileObject->FsContext)
451 {
452 (*Bcb)->AllocationSize =
453 ((REACTOS_COMMON_FCB_HEADER*)FileObject->FsContext)->AllocationSize;
454 (*Bcb)->FileSize = ((REACTOS_COMMON_FCB_HEADER*)FileObject->FsContext)->FileSize;
455 }
456 KeInitializeSpinLock(&(*Bcb)->BcbLock);
457 InitializeListHead(&(*Bcb)->BcbSegmentListHead);
458
459 return(STATUS_SUCCESS);
460 }
461
462 VOID
463 CcInitView(VOID)
464 {
465 DPRINT1("CcInitView()\n");
466 InitializeListHead(&CacheSegmentListHead);
467 InitializeListHead(&DirtySegmentListHead);
468 InitializeListHead(&CacheSegmentLRUListHead);
469 ExInitializeFastMutex(&ViewLock);
470 MmInitializeMemoryConsumer(MC_CACHE, CcRosTrimCache);
471 }
472
473 /* EOF */
474
475
476
477
478
479
480