/* * COPYRIGHT: See COPYING in the top level directory * PROJECT: ReactOS kernel * FILE: ntoskrnl/cc/view.c * PURPOSE: Cache manager * * PROGRAMMERS: David Welch (welch@mcmail.com) * Pierre Schweitzer (pierre@reactos.org) */ /* NOTES ********************************************************************** * * This is not the NT implementation of a file cache nor anything much like * it. * * The general procedure for a filesystem to implement a read or write * dispatch routine is as follows * * (1) If caching for the FCB hasn't been initiated then so do by calling * CcInitializeFileCache. * * (2) For each 4k region which is being read or written obtain a cache page * by calling CcRequestCachePage. * * (3) If either the page is being read or not completely written, and it is * not up to date then read its data from the underlying medium. If the read * fails then call CcReleaseCachePage with VALID as FALSE and return a error. * * (4) Copy the data into or out of the page as necessary. * * (5) Release the cache page */ /* INCLUDES ******************************************************************/ #include #define NDEBUG #include /* GLOBALS *******************************************************************/ LIST_ENTRY DirtyVacbListHead; static LIST_ENTRY VacbLruListHead; NPAGED_LOOKASIDE_LIST iBcbLookasideList; static NPAGED_LOOKASIDE_LIST SharedCacheMapLookasideList; static NPAGED_LOOKASIDE_LIST VacbLookasideList; /* Internal vars (MS): * - Threshold above which lazy writer will start action * - Amount of dirty pages * - List for deferred writes * - Spinlock when dealing with the deferred list * - List for "clean" shared cache maps */ ULONG CcDirtyPageThreshold = 0; ULONG CcTotalDirtyPages = 0; LIST_ENTRY CcDeferredWrites; KSPIN_LOCK CcDeferredWriteSpinLock; LIST_ENTRY CcCleanSharedCacheMapList; #if DBG ULONG CcRosVacbIncRefCount_(PROS_VACB vacb, PCSTR file, INT line) { ULONG Refs; Refs = InterlockedIncrement((PLONG)&vacb->ReferenceCount); if (vacb->SharedCacheMap->Trace) { DbgPrint("(%s:%i) VACB %p ++RefCount=%lu, Dirty %u, PageOut %lu\n", file, line, vacb, Refs, vacb->Dirty, vacb->PageOut); } return Refs; } ULONG CcRosVacbDecRefCount_(PROS_VACB vacb, PCSTR file, INT line) { ULONG Refs; BOOLEAN VacbDirty = vacb->Dirty; BOOLEAN VacbTrace = vacb->SharedCacheMap->Trace; BOOLEAN VacbPageOut = vacb->PageOut; Refs = InterlockedDecrement((PLONG)&vacb->ReferenceCount); ASSERT(!(Refs == 0 && VacbDirty)); if (VacbTrace) { DbgPrint("(%s:%i) VACB %p --RefCount=%lu, Dirty %u, PageOut %lu\n", file, line, vacb, Refs, VacbDirty, VacbPageOut); } if (Refs == 0) { CcRosInternalFreeVacb(vacb); } return Refs; } ULONG CcRosVacbGetRefCount_(PROS_VACB vacb, PCSTR file, INT line) { ULONG Refs; Refs = InterlockedCompareExchange((PLONG)&vacb->ReferenceCount, 0, 0); if (vacb->SharedCacheMap->Trace) { DbgPrint("(%s:%i) VACB %p ==RefCount=%lu, Dirty %u, PageOut %lu\n", file, line, vacb, Refs, vacb->Dirty, vacb->PageOut); } return Refs; } #endif /* FUNCTIONS *****************************************************************/ VOID CcRosTraceCacheMap ( PROS_SHARED_CACHE_MAP SharedCacheMap, BOOLEAN Trace ) { #if DBG KIRQL oldirql; PLIST_ENTRY current_entry; PROS_VACB current; if (!SharedCacheMap) return; SharedCacheMap->Trace = Trace; if (Trace) { DPRINT1("Enabling Tracing for CacheMap 0x%p:\n", SharedCacheMap); oldirql = KeAcquireQueuedSpinLock(LockQueueMasterLock); KeAcquireSpinLockAtDpcLevel(&SharedCacheMap->CacheMapLock); current_entry = SharedCacheMap->CacheMapVacbListHead.Flink; while (current_entry != &SharedCacheMap->CacheMapVacbListHead) { current = CONTAINING_RECORD(current_entry, ROS_VACB, CacheMapVacbListEntry); current_entry = current_entry->Flink; DPRINT1(" VACB 0x%p enabled, RefCount %lu, Dirty %u, PageOut %lu, BaseAddress %p, FileOffset %I64d\n", current, current->ReferenceCount, current->Dirty, current->PageOut, current->BaseAddress, current->FileOffset.QuadPart); } KeReleaseSpinLockFromDpcLevel(&SharedCacheMap->CacheMapLock); KeReleaseQueuedSpinLock(LockQueueMasterLock, oldirql); } else { DPRINT1("Disabling Tracing for CacheMap 0x%p:\n", SharedCacheMap); } #else UNREFERENCED_PARAMETER(SharedCacheMap); UNREFERENCED_PARAMETER(Trace); #endif } NTSTATUS CcRosFlushVacb ( _In_ PROS_VACB Vacb, _Out_opt_ PIO_STATUS_BLOCK Iosb) { NTSTATUS Status; BOOLEAN HaveLock = FALSE; PROS_SHARED_CACHE_MAP SharedCacheMap = Vacb->SharedCacheMap; CcRosUnmarkDirtyVacb(Vacb, TRUE); /* Lock for flush, if we are not already the top-level */ if (IoGetTopLevelIrp() != (PIRP)FSRTL_CACHE_TOP_LEVEL_IRP) { Status = FsRtlAcquireFileForCcFlushEx(Vacb->SharedCacheMap->FileObject); if (!NT_SUCCESS(Status)) goto quit; HaveLock = TRUE; } Status = MmFlushSegment(SharedCacheMap->FileObject->SectionObjectPointer, &Vacb->FileOffset, VACB_MAPPING_GRANULARITY, Iosb); if (HaveLock) { FsRtlReleaseFileForCcFlush(Vacb->SharedCacheMap->FileObject); } quit: if (!NT_SUCCESS(Status)) CcRosMarkDirtyVacb(Vacb); else { /* Update VDL */ if (SharedCacheMap->ValidDataLength.QuadPart < (Vacb->FileOffset.QuadPart + VACB_MAPPING_GRANULARITY)) { SharedCacheMap->ValidDataLength.QuadPart = Vacb->FileOffset.QuadPart + VACB_MAPPING_GRANULARITY; } } return Status; } static NTSTATUS CcRosDeleteFileCache ( PFILE_OBJECT FileObject, PROS_SHARED_CACHE_MAP SharedCacheMap, PKIRQL OldIrql) /* * FUNCTION: Releases the shared cache map associated with a file object */ { PLIST_ENTRY current_entry; ASSERT(SharedCacheMap); ASSERT(SharedCacheMap == FileObject->SectionObjectPointer->SharedCacheMap); ASSERT(SharedCacheMap->OpenCount == 0); /* Remove all VACBs from the global lists */ KeAcquireSpinLockAtDpcLevel(&SharedCacheMap->CacheMapLock); current_entry = SharedCacheMap->CacheMapVacbListHead.Flink; while (current_entry != &SharedCacheMap->CacheMapVacbListHead) { PROS_VACB Vacb = CONTAINING_RECORD(current_entry, ROS_VACB, CacheMapVacbListEntry); RemoveEntryList(&Vacb->VacbLruListEntry); InitializeListHead(&Vacb->VacbLruListEntry); if (Vacb->Dirty) { CcRosUnmarkDirtyVacb(Vacb, FALSE); /* Mark it as dirty again so we know that we have to flush before freeing it */ Vacb->Dirty = TRUE; } current_entry = current_entry->Flink; } /* Make sure there is no trace anymore of this map */ FileObject->SectionObjectPointer->SharedCacheMap = NULL; RemoveEntryList(&SharedCacheMap->SharedCacheMapLinks); KeReleaseSpinLockFromDpcLevel(&SharedCacheMap->CacheMapLock); KeReleaseQueuedSpinLock(LockQueueMasterLock, *OldIrql); /* Now that we're out of the locks, free everything for real */ while (!IsListEmpty(&SharedCacheMap->CacheMapVacbListHead)) { PROS_VACB Vacb = CONTAINING_RECORD(RemoveHeadList(&SharedCacheMap->CacheMapVacbListHead), ROS_VACB, CacheMapVacbListEntry); ULONG RefCount; InitializeListHead(&Vacb->CacheMapVacbListEntry); /* Flush to disk, if needed */ if (Vacb->Dirty) { IO_STATUS_BLOCK Iosb; NTSTATUS Status; Status = MmFlushSegment(FileObject->SectionObjectPointer, &Vacb->FileOffset, VACB_MAPPING_GRANULARITY, &Iosb); if (!NT_SUCCESS(Status)) { /* Complain. There's not much we can do */ DPRINT1("Failed to flush VACB to disk while deleting the cache entry. Status: 0x%08x\n", Status); } Vacb->Dirty = FALSE; } RefCount = CcRosVacbDecRefCount(Vacb); #if DBG // CORE-14578 if (RefCount != 0) { DPRINT1("Leaking VACB %p attached to %p (%I64d)\n", Vacb, FileObject, Vacb->FileOffset.QuadPart); DPRINT1("There are: %d references left\n", RefCount); DPRINT1("Map: %d\n", Vacb->MappedCount); DPRINT1("Dirty: %d\n", Vacb->Dirty); if (FileObject->FileName.Length != 0) { DPRINT1("File was: %wZ\n", &FileObject->FileName); } else { DPRINT1("No name for the file\n"); } } #else (void)RefCount; #endif } /* Release the references we own */ if(SharedCacheMap->Section) ObDereferenceObject(SharedCacheMap->Section); ObDereferenceObject(SharedCacheMap->FileObject); ExFreeToNPagedLookasideList(&SharedCacheMapLookasideList, SharedCacheMap); /* Acquire the lock again for our caller */ *OldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock); return STATUS_SUCCESS; } NTSTATUS CcRosFlushDirtyPages ( ULONG Target, PULONG Count, BOOLEAN Wait, BOOLEAN CalledFromLazy) { PLIST_ENTRY current_entry; NTSTATUS Status; KIRQL OldIrql; BOOLEAN FlushAll = (Target == MAXULONG); DPRINT("CcRosFlushDirtyPages(Target %lu)\n", Target); (*Count) = 0; KeEnterCriticalRegion(); OldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock); current_entry = DirtyVacbListHead.Flink; if (current_entry == &DirtyVacbListHead) { DPRINT("No Dirty pages\n"); } while (((current_entry != &DirtyVacbListHead) && (Target > 0)) || FlushAll) { PROS_SHARED_CACHE_MAP SharedCacheMap; PROS_VACB current; BOOLEAN Locked; if (current_entry == &DirtyVacbListHead) { ASSERT(FlushAll); if (IsListEmpty(&DirtyVacbListHead)) break; current_entry = DirtyVacbListHead.Flink; } current = CONTAINING_RECORD(current_entry, ROS_VACB, DirtyVacbListEntry); current_entry = current_entry->Flink; CcRosVacbIncRefCount(current); SharedCacheMap = current->SharedCacheMap; /* When performing lazy write, don't handle temporary files */ if (CalledFromLazy && BooleanFlagOn(SharedCacheMap->FileObject->Flags, FO_TEMPORARY_FILE)) { CcRosVacbDecRefCount(current); continue; } /* Don't attempt to lazy write the files that asked not to */ if (CalledFromLazy && BooleanFlagOn(SharedCacheMap->Flags, WRITEBEHIND_DISABLED)) { CcRosVacbDecRefCount(current); continue; } ASSERT(current->Dirty); /* Do not lazy-write the same file concurrently. Fastfat ASSERTS on that */ if (SharedCacheMap->Flags & SHARED_CACHE_MAP_IN_LAZYWRITE) { CcRosVacbDecRefCount(current); continue; } SharedCacheMap->Flags |= SHARED_CACHE_MAP_IN_LAZYWRITE; /* Keep a ref on the shared cache map */ SharedCacheMap->OpenCount++; KeReleaseQueuedSpinLock(LockQueueMasterLock, OldIrql); Locked = SharedCacheMap->Callbacks->AcquireForLazyWrite(SharedCacheMap->LazyWriteContext, Wait); if (!Locked) { DPRINT("Not locked!"); ASSERT(!Wait); CcRosVacbDecRefCount(current); OldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock); SharedCacheMap->Flags &= ~SHARED_CACHE_MAP_IN_LAZYWRITE; if (--SharedCacheMap->OpenCount == 0) CcRosDeleteFileCache(SharedCacheMap->FileObject, SharedCacheMap, &OldIrql); continue; } IO_STATUS_BLOCK Iosb; Status = CcRosFlushVacb(current, &Iosb); SharedCacheMap->Callbacks->ReleaseFromLazyWrite(SharedCacheMap->LazyWriteContext); /* We release the VACB before acquiring the lock again, because * CcRosVacbDecRefCount might free the VACB, as CcRosFlushVacb dropped a * Refcount. Freeing must be done outside of the lock. * The refcount is decremented atomically. So this is OK. */ CcRosVacbDecRefCount(current); OldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock); SharedCacheMap->Flags &= ~SHARED_CACHE_MAP_IN_LAZYWRITE; if (--SharedCacheMap->OpenCount == 0) CcRosDeleteFileCache(SharedCacheMap->FileObject, SharedCacheMap, &OldIrql); if (!NT_SUCCESS(Status) && (Status != STATUS_END_OF_FILE) && (Status != STATUS_MEDIA_WRITE_PROTECTED)) { DPRINT1("CC: Failed to flush VACB.\n"); } else { ULONG PagesFreed; /* How many pages did we free? */ PagesFreed = Iosb.Information / PAGE_SIZE; (*Count) += PagesFreed; if (!Wait) { /* Make sure we don't overflow target! */ if (Target < PagesFreed) { /* If we would have, jump to zero directly */ Target = 0; } else { Target -= PagesFreed; } } } current_entry = DirtyVacbListHead.Flink; } KeReleaseQueuedSpinLock(LockQueueMasterLock, OldIrql); KeLeaveCriticalRegion(); DPRINT("CcRosFlushDirtyPages() finished\n"); return STATUS_SUCCESS; } VOID CcRosTrimCache( _In_ ULONG Target, _Out_ PULONG NrFreed) /* * FUNCTION: Try to free some memory from the file cache. * ARGUMENTS: * Target - The number of pages to be freed. * NrFreed - Points to a variable where the number of pages * actually freed is returned. */ { PLIST_ENTRY current_entry; PROS_VACB current; ULONG PagesFreed; KIRQL oldIrql; LIST_ENTRY FreeList; PFN_NUMBER Page; ULONG i; BOOLEAN FlushedPages = FALSE; DPRINT("CcRosTrimCache(Target %lu)\n", Target); InitializeListHead(&FreeList); *NrFreed = 0; retry: oldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock); current_entry = VacbLruListHead.Flink; while (current_entry != &VacbLruListHead) { ULONG Refs; current = CONTAINING_RECORD(current_entry, ROS_VACB, VacbLruListEntry); current_entry = current_entry->Flink; KeAcquireSpinLockAtDpcLevel(¤t->SharedCacheMap->CacheMapLock); /* Reference the VACB */ CcRosVacbIncRefCount(current); /* Check if it's mapped and not dirty */ if (InterlockedCompareExchange((PLONG)¤t->MappedCount, 0, 0) > 0 && !current->Dirty) { /* Page out the VACB */ for (i = 0; i < VACB_MAPPING_GRANULARITY / PAGE_SIZE; i++) { Page = (PFN_NUMBER)(MmGetPhysicalAddress((PUCHAR)current->BaseAddress + (i * PAGE_SIZE)).QuadPart >> PAGE_SHIFT); MmPageOutPhysicalAddress(Page); } } /* Dereference the VACB */ Refs = CcRosVacbDecRefCount(current); /* Check if we can free this entry now */ if (Refs < 2) { ASSERT(!current->Dirty); ASSERT(!current->MappedCount); ASSERT(Refs == 1); RemoveEntryList(¤t->CacheMapVacbListEntry); RemoveEntryList(¤t->VacbLruListEntry); InitializeListHead(¤t->VacbLruListEntry); InsertHeadList(&FreeList, ¤t->CacheMapVacbListEntry); /* Calculate how many pages we freed for Mm */ PagesFreed = min(VACB_MAPPING_GRANULARITY / PAGE_SIZE, Target); Target -= PagesFreed; (*NrFreed) += PagesFreed; } KeReleaseSpinLockFromDpcLevel(¤t->SharedCacheMap->CacheMapLock); } KeReleaseQueuedSpinLock(LockQueueMasterLock, oldIrql); /* Try flushing pages if we haven't met our target */ if ((Target > 0) && !FlushedPages) { /* Flush dirty pages to disk */ CcRosFlushDirtyPages(Target, &PagesFreed, FALSE, FALSE); FlushedPages = TRUE; /* We can only swap as many pages as we flushed */ if (PagesFreed < Target) Target = PagesFreed; /* Check if we flushed anything */ if (PagesFreed != 0) { /* Try again after flushing dirty pages */ DPRINT("Flushed %lu dirty cache pages to disk\n", PagesFreed); goto retry; } } while (!IsListEmpty(&FreeList)) { ULONG Refs; current_entry = RemoveHeadList(&FreeList); current = CONTAINING_RECORD(current_entry, ROS_VACB, CacheMapVacbListEntry); InitializeListHead(¤t->CacheMapVacbListEntry); Refs = CcRosVacbDecRefCount(current); ASSERT(Refs == 0); } DPRINT("Evicted %lu cache pages\n", (*NrFreed)); } NTSTATUS CcRosReleaseVacb ( PROS_SHARED_CACHE_MAP SharedCacheMap, PROS_VACB Vacb, BOOLEAN Dirty, BOOLEAN Mapped) { ULONG Refs; ASSERT(SharedCacheMap); DPRINT("CcRosReleaseVacb(SharedCacheMap 0x%p, Vacb 0x%p)\n", SharedCacheMap, Vacb); if (Dirty && !Vacb->Dirty) { CcRosMarkDirtyVacb(Vacb); } if (Mapped) { if (InterlockedIncrement((PLONG)&Vacb->MappedCount) == 1) { CcRosVacbIncRefCount(Vacb); } } Refs = CcRosVacbDecRefCount(Vacb); ASSERT(Refs > 0); return STATUS_SUCCESS; } /* Returns with VACB Lock Held! */ PROS_VACB CcRosLookupVacb ( PROS_SHARED_CACHE_MAP SharedCacheMap, LONGLONG FileOffset) { PLIST_ENTRY current_entry; PROS_VACB current; KIRQL oldIrql; ASSERT(SharedCacheMap); DPRINT("CcRosLookupVacb(SharedCacheMap 0x%p, FileOffset %I64u)\n", SharedCacheMap, FileOffset); oldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock); KeAcquireSpinLockAtDpcLevel(&SharedCacheMap->CacheMapLock); current_entry = SharedCacheMap->CacheMapVacbListHead.Flink; while (current_entry != &SharedCacheMap->CacheMapVacbListHead) { current = CONTAINING_RECORD(current_entry, ROS_VACB, CacheMapVacbListEntry); if (IsPointInRange(current->FileOffset.QuadPart, VACB_MAPPING_GRANULARITY, FileOffset)) { CcRosVacbIncRefCount(current); KeReleaseSpinLockFromDpcLevel(&SharedCacheMap->CacheMapLock); KeReleaseQueuedSpinLock(LockQueueMasterLock, oldIrql); return current; } if (current->FileOffset.QuadPart > FileOffset) break; current_entry = current_entry->Flink; } KeReleaseSpinLockFromDpcLevel(&SharedCacheMap->CacheMapLock); KeReleaseQueuedSpinLock(LockQueueMasterLock, oldIrql); return NULL; } VOID CcRosMarkDirtyVacb ( PROS_VACB Vacb) { KIRQL oldIrql; PROS_SHARED_CACHE_MAP SharedCacheMap; SharedCacheMap = Vacb->SharedCacheMap; oldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock); KeAcquireSpinLockAtDpcLevel(&SharedCacheMap->CacheMapLock); ASSERT(!Vacb->Dirty); InsertTailList(&DirtyVacbListHead, &Vacb->DirtyVacbListEntry); /* FIXME: There is no reason to account for the whole VACB. */ CcTotalDirtyPages += VACB_MAPPING_GRANULARITY / PAGE_SIZE; Vacb->SharedCacheMap->DirtyPages += VACB_MAPPING_GRANULARITY / PAGE_SIZE; CcRosVacbIncRefCount(Vacb); /* Move to the tail of the LRU list */ RemoveEntryList(&Vacb->VacbLruListEntry); InsertTailList(&VacbLruListHead, &Vacb->VacbLruListEntry); Vacb->Dirty = TRUE; KeReleaseSpinLockFromDpcLevel(&SharedCacheMap->CacheMapLock); /* Schedule a lazy writer run to now that we have dirty VACB */ if (!LazyWriter.ScanActive) { CcScheduleLazyWriteScan(FALSE); } KeReleaseQueuedSpinLock(LockQueueMasterLock, oldIrql); } VOID CcRosUnmarkDirtyVacb ( PROS_VACB Vacb, BOOLEAN LockViews) { KIRQL oldIrql; PROS_SHARED_CACHE_MAP SharedCacheMap; SharedCacheMap = Vacb->SharedCacheMap; if (LockViews) { oldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock); KeAcquireSpinLockAtDpcLevel(&SharedCacheMap->CacheMapLock); } ASSERT(Vacb->Dirty); Vacb->Dirty = FALSE; RemoveEntryList(&Vacb->DirtyVacbListEntry); InitializeListHead(&Vacb->DirtyVacbListEntry); CcTotalDirtyPages -= VACB_MAPPING_GRANULARITY / PAGE_SIZE; Vacb->SharedCacheMap->DirtyPages -= VACB_MAPPING_GRANULARITY / PAGE_SIZE; CcRosVacbDecRefCount(Vacb); if (LockViews) { KeReleaseSpinLockFromDpcLevel(&SharedCacheMap->CacheMapLock); KeReleaseQueuedSpinLock(LockQueueMasterLock, oldIrql); } } BOOLEAN CcRosFreeOneUnusedVacb( VOID) { KIRQL oldIrql; PLIST_ENTRY current_entry; PROS_VACB to_free = NULL; oldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock); /* Browse all the available VACB */ current_entry = VacbLruListHead.Flink; while ((current_entry != &VacbLruListHead) && (to_free == NULL)) { ULONG Refs; PROS_VACB current; current = CONTAINING_RECORD(current_entry, ROS_VACB, VacbLruListEntry); KeAcquireSpinLockAtDpcLevel(¤t->SharedCacheMap->CacheMapLock); /* Only deal with unused VACB, we will free them */ Refs = CcRosVacbGetRefCount(current); if (Refs < 2) { ASSERT(!current->Dirty); ASSERT(!current->MappedCount); ASSERT(Refs == 1); /* Reset it, this is the one we want to free */ RemoveEntryList(¤t->CacheMapVacbListEntry); InitializeListHead(¤t->CacheMapVacbListEntry); RemoveEntryList(¤t->VacbLruListEntry); InitializeListHead(¤t->VacbLruListEntry); to_free = current; } KeReleaseSpinLockFromDpcLevel(¤t->SharedCacheMap->CacheMapLock); current_entry = current_entry->Flink; } KeReleaseQueuedSpinLock(LockQueueMasterLock, oldIrql); /* And now, free the VACB that we found, if any. */ if (to_free == NULL) { return FALSE; } /* This must be its last ref */ NT_VERIFY(CcRosVacbDecRefCount(to_free) == 0); return TRUE; } static NTSTATUS CcRosCreateVacb ( PROS_SHARED_CACHE_MAP SharedCacheMap, LONGLONG FileOffset, PROS_VACB *Vacb) { PROS_VACB current; PROS_VACB previous; PLIST_ENTRY current_entry; NTSTATUS Status; KIRQL oldIrql; ULONG Refs; SIZE_T ViewSize = VACB_MAPPING_GRANULARITY; ASSERT(SharedCacheMap); DPRINT("CcRosCreateVacb()\n"); current = ExAllocateFromNPagedLookasideList(&VacbLookasideList); current->BaseAddress = NULL; current->Dirty = FALSE; current->PageOut = FALSE; current->FileOffset.QuadPart = ROUND_DOWN(FileOffset, VACB_MAPPING_GRANULARITY); current->SharedCacheMap = SharedCacheMap; current->MappedCount = 0; current->ReferenceCount = 0; InitializeListHead(¤t->CacheMapVacbListEntry); InitializeListHead(¤t->DirtyVacbListEntry); InitializeListHead(¤t->VacbLruListEntry); CcRosVacbIncRefCount(current); while (TRUE) { /* Map VACB in system space */ Status = MmMapViewInSystemSpaceEx(SharedCacheMap->Section, ¤t->BaseAddress, &ViewSize, ¤t->FileOffset, 0); if (NT_SUCCESS(Status)) { break; } /* * If no space left, try to prune one unused VACB to recover space to map our VACB. * If it succeeds, retry to map, otherwise just fail. */ if (!CcRosFreeOneUnusedVacb()) { ExFreeToNPagedLookasideList(&VacbLookasideList, current); return Status; } } #if DBG if (SharedCacheMap->Trace) { DPRINT1("CacheMap 0x%p: new VACB: 0x%p, file offset %I64d, BaseAddress %p\n", SharedCacheMap, current, current->FileOffset.QuadPart, current->BaseAddress); } #endif oldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock); *Vacb = current; /* There is window between the call to CcRosLookupVacb * and CcRosCreateVacb. We must check if a VACB for the * file offset exist. If there is a VACB, we release * our newly created VACB and return the existing one. */ KeAcquireSpinLockAtDpcLevel(&SharedCacheMap->CacheMapLock); current_entry = SharedCacheMap->CacheMapVacbListHead.Flink; previous = NULL; while (current_entry != &SharedCacheMap->CacheMapVacbListHead) { current = CONTAINING_RECORD(current_entry, ROS_VACB, CacheMapVacbListEntry); if (IsPointInRange(current->FileOffset.QuadPart, VACB_MAPPING_GRANULARITY, FileOffset)) { CcRosVacbIncRefCount(current); KeReleaseSpinLockFromDpcLevel(&SharedCacheMap->CacheMapLock); #if DBG if (SharedCacheMap->Trace) { DPRINT1("CacheMap 0x%p: deleting newly created VACB 0x%p ( found existing one 0x%p )\n", SharedCacheMap, (*Vacb), current); } #endif KeReleaseQueuedSpinLock(LockQueueMasterLock, oldIrql); Refs = CcRosVacbDecRefCount(*Vacb); ASSERT(Refs == 0); *Vacb = current; return STATUS_SUCCESS; } if (current->FileOffset.QuadPart < FileOffset) { ASSERT(previous == NULL || previous->FileOffset.QuadPart < current->FileOffset.QuadPart); previous = current; } if (current->FileOffset.QuadPart > FileOffset) break; current_entry = current_entry->Flink; } /* There was no existing VACB. */ current = *Vacb; if (previous) { InsertHeadList(&previous->CacheMapVacbListEntry, ¤t->CacheMapVacbListEntry); } else { InsertHeadList(&SharedCacheMap->CacheMapVacbListHead, ¤t->CacheMapVacbListEntry); } KeReleaseSpinLockFromDpcLevel(&SharedCacheMap->CacheMapLock); InsertTailList(&VacbLruListHead, ¤t->VacbLruListEntry); /* Reference it to allow release */ CcRosVacbIncRefCount(current); KeReleaseQueuedSpinLock(LockQueueMasterLock, oldIrql); return Status; } BOOLEAN CcRosEnsureVacbResident( _In_ PROS_VACB Vacb, _In_ BOOLEAN Wait, _In_ BOOLEAN NoRead, _In_ ULONG Offset, _In_ ULONG Length ) { PVOID BaseAddress; ASSERT((Offset + Length) <= VACB_MAPPING_GRANULARITY); #if 0 if ((Vacb->FileOffset.QuadPart + Offset) > Vacb->SharedCacheMap->SectionSize.QuadPart) { DPRINT1("Vacb read beyond the file size!\n"); return FALSE; } #endif BaseAddress = (PVOID)((ULONG_PTR)Vacb->BaseAddress + Offset); /* Check if the pages are resident */ if (!MmArePagesResident(NULL, BaseAddress, Length)) { if (!Wait) { return FALSE; } if (!NoRead) { PROS_SHARED_CACHE_MAP SharedCacheMap = Vacb->SharedCacheMap; NTSTATUS Status = MmMakeDataSectionResident(SharedCacheMap->FileObject->SectionObjectPointer, Vacb->FileOffset.QuadPart + Offset, Length, &SharedCacheMap->ValidDataLength); if (!NT_SUCCESS(Status)) ExRaiseStatus(Status); } } return TRUE; } NTSTATUS CcRosGetVacb ( PROS_SHARED_CACHE_MAP SharedCacheMap, LONGLONG FileOffset, PROS_VACB *Vacb) { PROS_VACB current; NTSTATUS Status; ULONG Refs; KIRQL OldIrql; ASSERT(SharedCacheMap); DPRINT("CcRosGetVacb()\n"); /* * Look for a VACB already mapping the same data. */ current = CcRosLookupVacb(SharedCacheMap, FileOffset); if (current == NULL) { /* * Otherwise create a new VACB. */ Status = CcRosCreateVacb(SharedCacheMap, FileOffset, ¤t); if (!NT_SUCCESS(Status)) { return Status; } } Refs = CcRosVacbGetRefCount(current); OldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock); /* Move to the tail of the LRU list */ RemoveEntryList(¤t->VacbLruListEntry); InsertTailList(&VacbLruListHead, ¤t->VacbLruListEntry); KeReleaseQueuedSpinLock(LockQueueMasterLock, OldIrql); /* * Return the VACB to the caller. */ *Vacb = current; ASSERT(Refs > 1); return STATUS_SUCCESS; } NTSTATUS CcRosRequestVacb ( PROS_SHARED_CACHE_MAP SharedCacheMap, LONGLONG FileOffset, PROS_VACB *Vacb) /* * FUNCTION: Request a page mapping for a shared cache map */ { ASSERT(SharedCacheMap); if (FileOffset % VACB_MAPPING_GRANULARITY != 0) { DPRINT1("Bad fileoffset %I64x should be multiple of %x", FileOffset, VACB_MAPPING_GRANULARITY); KeBugCheck(CACHE_MANAGER); } return CcRosGetVacb(SharedCacheMap, FileOffset, Vacb); } NTSTATUS CcRosInternalFreeVacb ( PROS_VACB Vacb) /* * FUNCTION: Releases a VACB associated with a shared cache map */ { NTSTATUS Status; DPRINT("Freeing VACB 0x%p\n", Vacb); #if DBG if (Vacb->SharedCacheMap->Trace) { DPRINT1("CacheMap 0x%p: deleting VACB: 0x%p\n", Vacb->SharedCacheMap, Vacb); } #endif if (Vacb->ReferenceCount != 0) { DPRINT1("Invalid free: %ld\n", Vacb->ReferenceCount); if (Vacb->SharedCacheMap->FileObject && Vacb->SharedCacheMap->FileObject->FileName.Length) { DPRINT1("For file: %wZ\n", &Vacb->SharedCacheMap->FileObject->FileName); } } ASSERT(Vacb->ReferenceCount == 0); ASSERT(IsListEmpty(&Vacb->CacheMapVacbListEntry)); ASSERT(IsListEmpty(&Vacb->DirtyVacbListEntry)); ASSERT(IsListEmpty(&Vacb->VacbLruListEntry)); /* Delete the mapping */ Status = MmUnmapViewInSystemSpace(Vacb->BaseAddress); if (!NT_SUCCESS(Status)) { DPRINT1("Failed to unmap VACB from System address space! Status 0x%08X\n", Status); ASSERT(FALSE); /* Proceed with the deĺetion anyway */ } RtlFillMemory(Vacb, sizeof(*Vacb), 0xfd); ExFreeToNPagedLookasideList(&VacbLookasideList, Vacb); return STATUS_SUCCESS; } /* * @implemented */ VOID NTAPI CcFlushCache ( IN PSECTION_OBJECT_POINTERS SectionObjectPointers, IN PLARGE_INTEGER FileOffset OPTIONAL, IN ULONG Length, OUT PIO_STATUS_BLOCK IoStatus) { PROS_SHARED_CACHE_MAP SharedCacheMap; LONGLONG FlushStart, FlushEnd; NTSTATUS Status; CCTRACE(CC_API_DEBUG, "SectionObjectPointers=%p FileOffset=0x%I64X Length=%lu\n", SectionObjectPointers, FileOffset ? FileOffset->QuadPart : 0LL, Length); if (!SectionObjectPointers) { Status = STATUS_INVALID_PARAMETER; goto quit; } if (!SectionObjectPointers->SharedCacheMap) { /* Forward this to Mm */ MmFlushSegment(SectionObjectPointers, FileOffset, Length, IoStatus); return; } SharedCacheMap = SectionObjectPointers->SharedCacheMap; ASSERT(SharedCacheMap); if (FileOffset) { FlushStart = FileOffset->QuadPart; Status = RtlLongLongAdd(FlushStart, Length, &FlushEnd); if (!NT_SUCCESS(Status)) goto quit; } else { FlushStart = 0; FlushEnd = SharedCacheMap->FileSize.QuadPart; } Status = STATUS_SUCCESS; if (IoStatus) { IoStatus->Information = 0; } /* * We flush the VACBs that we find here. * If there is no (dirty) VACB, it doesn't mean that there is no data to flush, so we call Mm to be sure. * This is suboptimal, but this is due to the lack of granularity of how we track dirty cache data */ while (FlushStart < FlushEnd) { BOOLEAN DirtyVacb = FALSE; PROS_VACB vacb = CcRosLookupVacb(SharedCacheMap, FlushStart); if (vacb != NULL) { if (vacb->Dirty) { IO_STATUS_BLOCK VacbIosb = { 0 }; Status = CcRosFlushVacb(vacb, &VacbIosb); if (!NT_SUCCESS(Status)) { goto quit; } DirtyVacb = TRUE; if (IoStatus) IoStatus->Information += VacbIosb.Information; } CcRosReleaseVacb(SharedCacheMap, vacb, FALSE, FALSE); } if (!DirtyVacb) { IO_STATUS_BLOCK MmIosb; LARGE_INTEGER MmOffset; MmOffset.QuadPart = FlushStart; if (FlushEnd - (FlushEnd % VACB_MAPPING_GRANULARITY) <= FlushStart) { /* The whole range fits within a VACB chunk. */ Status = MmFlushSegment(SectionObjectPointers, &MmOffset, FlushEnd - FlushStart, &MmIosb); } else { ULONG MmLength = VACB_MAPPING_GRANULARITY - (FlushStart % VACB_MAPPING_GRANULARITY); Status = MmFlushSegment(SectionObjectPointers, &MmOffset, MmLength, &MmIosb); } if (!NT_SUCCESS(Status)) goto quit; if (IoStatus) IoStatus->Information += MmIosb.Information; /* Update VDL */ if (SharedCacheMap->ValidDataLength.QuadPart < FlushEnd) SharedCacheMap->ValidDataLength.QuadPart = FlushEnd; } if (!NT_SUCCESS(RtlLongLongAdd(FlushStart, VACB_MAPPING_GRANULARITY, &FlushStart))) { /* We're at the end of file ! */ break; } /* Round down to next VACB start now */ FlushStart -= FlushStart % VACB_MAPPING_GRANULARITY; } quit: if (IoStatus) { IoStatus->Status = Status; } } NTSTATUS CcRosReleaseFileCache ( PFILE_OBJECT FileObject) /* * FUNCTION: Called by the file system when a handle to a file object * has been closed. */ { KIRQL OldIrql; PPRIVATE_CACHE_MAP PrivateMap; PROS_SHARED_CACHE_MAP SharedCacheMap; OldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock); if (FileObject->SectionObjectPointer->SharedCacheMap != NULL) { SharedCacheMap = FileObject->SectionObjectPointer->SharedCacheMap; /* Closing the handle, so kill the private cache map * Before you event try to remove it from FO, always * lock the master lock, to be sure not to race * with a potential read ahead ongoing! */ PrivateMap = FileObject->PrivateCacheMap; FileObject->PrivateCacheMap = NULL; if (PrivateMap != NULL) { /* Remove it from the file */ KeAcquireSpinLockAtDpcLevel(&SharedCacheMap->CacheMapLock); RemoveEntryList(&PrivateMap->PrivateLinks); KeReleaseSpinLockFromDpcLevel(&SharedCacheMap->CacheMapLock); /* And free it. */ if (PrivateMap != &SharedCacheMap->PrivateCacheMap) { ExFreePoolWithTag(PrivateMap, TAG_PRIVATE_CACHE_MAP); } else { PrivateMap->NodeTypeCode = 0; } ASSERT(SharedCacheMap->OpenCount > 0); SharedCacheMap->OpenCount--; if (SharedCacheMap->OpenCount == 0) { CcRosDeleteFileCache(FileObject, SharedCacheMap, &OldIrql); } } } KeReleaseQueuedSpinLock(LockQueueMasterLock, OldIrql); return STATUS_SUCCESS; } NTSTATUS CcRosInitializeFileCache ( PFILE_OBJECT FileObject, PCC_FILE_SIZES FileSizes, BOOLEAN PinAccess, PCACHE_MANAGER_CALLBACKS CallBacks, PVOID LazyWriterContext) /* * FUNCTION: Initializes a shared cache map for a file object */ { KIRQL OldIrql; BOOLEAN Allocated; PROS_SHARED_CACHE_MAP SharedCacheMap; DPRINT("CcRosInitializeFileCache(FileObject 0x%p)\n", FileObject); OldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock); Allocated = FALSE; SharedCacheMap = FileObject->SectionObjectPointer->SharedCacheMap; if (SharedCacheMap == NULL) { Allocated = TRUE; SharedCacheMap = ExAllocateFromNPagedLookasideList(&SharedCacheMapLookasideList); if (SharedCacheMap == NULL) { return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory(SharedCacheMap, sizeof(*SharedCacheMap)); SharedCacheMap->NodeTypeCode = NODE_TYPE_SHARED_MAP; SharedCacheMap->NodeByteSize = sizeof(*SharedCacheMap); SharedCacheMap->FileObject = FileObject; SharedCacheMap->Callbacks = CallBacks; SharedCacheMap->LazyWriteContext = LazyWriterContext; SharedCacheMap->SectionSize = FileSizes->AllocationSize; SharedCacheMap->FileSize = FileSizes->FileSize; SharedCacheMap->ValidDataLength = FileSizes->ValidDataLength; SharedCacheMap->PinAccess = PinAccess; SharedCacheMap->DirtyPageThreshold = 0; SharedCacheMap->DirtyPages = 0; InitializeListHead(&SharedCacheMap->PrivateList); KeInitializeSpinLock(&SharedCacheMap->CacheMapLock); InitializeListHead(&SharedCacheMap->CacheMapVacbListHead); InitializeListHead(&SharedCacheMap->BcbList); SharedCacheMap->Flags = SHARED_CACHE_MAP_IN_CREATION; ObReferenceObjectByPointer(FileObject, FILE_ALL_ACCESS, NULL, KernelMode); FileObject->SectionObjectPointer->SharedCacheMap = SharedCacheMap; //CcRosTraceCacheMap(SharedCacheMap, TRUE); } else if (SharedCacheMap->Flags & SHARED_CACHE_MAP_IN_CREATION) { /* The shared cache map is being created somewhere else. Wait for that to happen */ KEVENT Waiter; PKEVENT PreviousWaiter = SharedCacheMap->CreateEvent; KeInitializeEvent(&Waiter, NotificationEvent, FALSE); SharedCacheMap->CreateEvent = &Waiter; KeReleaseQueuedSpinLock(LockQueueMasterLock, OldIrql); KeWaitForSingleObject(&Waiter, Executive, KernelMode, FALSE, NULL); if (PreviousWaiter) KeSetEvent(PreviousWaiter, IO_NO_INCREMENT, FALSE); OldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock); } if (FileObject->PrivateCacheMap == NULL) { PPRIVATE_CACHE_MAP PrivateMap; /* Allocate the private cache map for this handle */ if (SharedCacheMap->PrivateCacheMap.NodeTypeCode != 0) { PrivateMap = ExAllocatePoolWithTag(NonPagedPool, sizeof(PRIVATE_CACHE_MAP), TAG_PRIVATE_CACHE_MAP); } else { PrivateMap = &SharedCacheMap->PrivateCacheMap; } if (PrivateMap == NULL) { /* If we also allocated the shared cache map for this file, kill it */ if (Allocated) { RemoveEntryList(&SharedCacheMap->SharedCacheMapLinks); FileObject->SectionObjectPointer->SharedCacheMap = NULL; ObDereferenceObject(FileObject); ExFreeToNPagedLookasideList(&SharedCacheMapLookasideList, SharedCacheMap); } KeReleaseQueuedSpinLock(LockQueueMasterLock, OldIrql); return STATUS_INSUFFICIENT_RESOURCES; } /* Initialize it */ RtlZeroMemory(PrivateMap, sizeof(PRIVATE_CACHE_MAP)); PrivateMap->NodeTypeCode = NODE_TYPE_PRIVATE_MAP; PrivateMap->ReadAheadMask = PAGE_SIZE - 1; PrivateMap->FileObject = FileObject; KeInitializeSpinLock(&PrivateMap->ReadAheadSpinLock); /* Link it to the file */ KeAcquireSpinLockAtDpcLevel(&SharedCacheMap->CacheMapLock); InsertTailList(&SharedCacheMap->PrivateList, &PrivateMap->PrivateLinks); KeReleaseSpinLockFromDpcLevel(&SharedCacheMap->CacheMapLock); FileObject->PrivateCacheMap = PrivateMap; SharedCacheMap->OpenCount++; } KeReleaseQueuedSpinLock(LockQueueMasterLock, OldIrql); /* Create the section */ if (Allocated) { NTSTATUS Status; ASSERT(SharedCacheMap->Section == NULL); Status = MmCreateSection( &SharedCacheMap->Section, SECTION_ALL_ACCESS, NULL, &SharedCacheMap->SectionSize, PAGE_READWRITE, SEC_RESERVE, NULL, FileObject); ASSERT(NT_SUCCESS(Status)); if (!NT_SUCCESS(Status)) { CcRosReleaseFileCache(FileObject); return Status; } OldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock); InsertTailList(&CcCleanSharedCacheMapList, &SharedCacheMap->SharedCacheMapLinks); SharedCacheMap->Flags &= ~SHARED_CACHE_MAP_IN_CREATION; if (SharedCacheMap->CreateEvent) { KeSetEvent(SharedCacheMap->CreateEvent, IO_NO_INCREMENT, FALSE); SharedCacheMap->CreateEvent = NULL; } KeReleaseQueuedSpinLock(LockQueueMasterLock, OldIrql); } return STATUS_SUCCESS; } /* * @implemented */ PFILE_OBJECT NTAPI CcGetFileObjectFromSectionPtrs ( IN PSECTION_OBJECT_POINTERS SectionObjectPointers) { PROS_SHARED_CACHE_MAP SharedCacheMap; CCTRACE(CC_API_DEBUG, "SectionObjectPointers=%p\n", SectionObjectPointers); if (SectionObjectPointers && SectionObjectPointers->SharedCacheMap) { SharedCacheMap = SectionObjectPointers->SharedCacheMap; ASSERT(SharedCacheMap); return SharedCacheMap->FileObject; } return NULL; } CODE_SEG("INIT") VOID NTAPI CcInitView ( VOID) { DPRINT("CcInitView()\n"); InitializeListHead(&DirtyVacbListHead); InitializeListHead(&VacbLruListHead); InitializeListHead(&CcDeferredWrites); InitializeListHead(&CcCleanSharedCacheMapList); KeInitializeSpinLock(&CcDeferredWriteSpinLock); ExInitializeNPagedLookasideList(&iBcbLookasideList, NULL, NULL, 0, sizeof(INTERNAL_BCB), TAG_BCB, 20); ExInitializeNPagedLookasideList(&SharedCacheMapLookasideList, NULL, NULL, 0, sizeof(ROS_SHARED_CACHE_MAP), TAG_SHARED_CACHE_MAP, 20); ExInitializeNPagedLookasideList(&VacbLookasideList, NULL, NULL, 0, sizeof(ROS_VACB), TAG_VACB, 20); CcInitCacheZeroPage(); } #if DBG && defined(KDBG) #include BOOLEAN ExpKdbgExtFileCache(ULONG Argc, PCHAR Argv[]) { PLIST_ENTRY ListEntry; UNICODE_STRING NoName = RTL_CONSTANT_STRING(L"No name for File"); KdbpPrint(" Usage Summary (in kb)\n"); KdbpPrint("Shared\t\tMapped\tDirty\tName\n"); /* No need to lock the spin lock here, we're in DBG */ for (ListEntry = CcCleanSharedCacheMapList.Flink; ListEntry != &CcCleanSharedCacheMapList; ListEntry = ListEntry->Flink) { PLIST_ENTRY Vacbs; ULONG Mapped = 0, Dirty = 0; PROS_SHARED_CACHE_MAP SharedCacheMap; PUNICODE_STRING FileName; PWSTR Extra = L""; SharedCacheMap = CONTAINING_RECORD(ListEntry, ROS_SHARED_CACHE_MAP, SharedCacheMapLinks); /* Dirty size */ Dirty = (SharedCacheMap->DirtyPages * PAGE_SIZE) / 1024; /* First, count for all the associated VACB */ for (Vacbs = SharedCacheMap->CacheMapVacbListHead.Flink; Vacbs != &SharedCacheMap->CacheMapVacbListHead; Vacbs = Vacbs->Flink) { Mapped += VACB_MAPPING_GRANULARITY / 1024; } /* Setup name */ if (SharedCacheMap->FileObject != NULL && SharedCacheMap->FileObject->FileName.Length != 0) { FileName = &SharedCacheMap->FileObject->FileName; } else if (SharedCacheMap->FileObject != NULL && SharedCacheMap->FileObject->FsContext != NULL && ((PFSRTL_COMMON_FCB_HEADER)(SharedCacheMap->FileObject->FsContext))->NodeTypeCode == 0x0502 && ((PFSRTL_COMMON_FCB_HEADER)(SharedCacheMap->FileObject->FsContext))->NodeByteSize == 0x1F8 && ((PUNICODE_STRING)(((PUCHAR)SharedCacheMap->FileObject->FsContext) + 0x100))->Length != 0) { FileName = (PUNICODE_STRING)(((PUCHAR)SharedCacheMap->FileObject->FsContext) + 0x100); Extra = L" (FastFAT)"; } else { FileName = &NoName; } /* And print */ KdbpPrint("%p\t%d\t%d\t%wZ%S\n", SharedCacheMap, Mapped, Dirty, FileName, Extra); } return TRUE; } BOOLEAN ExpKdbgExtDefWrites(ULONG Argc, PCHAR Argv[]) { KdbpPrint("CcTotalDirtyPages:\t%lu (%lu Kb)\n", CcTotalDirtyPages, (CcTotalDirtyPages * PAGE_SIZE) / 1024); KdbpPrint("CcDirtyPageThreshold:\t%lu (%lu Kb)\n", CcDirtyPageThreshold, (CcDirtyPageThreshold * PAGE_SIZE) / 1024); KdbpPrint("MmAvailablePages:\t%lu (%lu Kb)\n", MmAvailablePages, (MmAvailablePages * PAGE_SIZE) / 1024); KdbpPrint("MmThrottleTop:\t\t%lu (%lu Kb)\n", MmThrottleTop, (MmThrottleTop * PAGE_SIZE) / 1024); KdbpPrint("MmThrottleBottom:\t%lu (%lu Kb)\n", MmThrottleBottom, (MmThrottleBottom * PAGE_SIZE) / 1024); KdbpPrint("MmModifiedPageListHead.Total:\t%lu (%lu Kb)\n", MmModifiedPageListHead.Total, (MmModifiedPageListHead.Total * PAGE_SIZE) / 1024); if (CcTotalDirtyPages >= CcDirtyPageThreshold) { KdbpPrint("CcTotalDirtyPages above the threshold, writes should be throttled\n"); } else if (CcTotalDirtyPages + 64 >= CcDirtyPageThreshold) { KdbpPrint("CcTotalDirtyPages within 64 (max charge) pages of the threshold, writes may be throttled\n"); } else { KdbpPrint("CcTotalDirtyPages below the threshold, writes should not be throttled\n"); } return TRUE; } #endif // DBG && defined(KDBG) /* EOF */