diff options
author | Adam <you@example.com> | 2020-05-17 05:51:50 +0200 |
---|---|---|
committer | Adam <you@example.com> | 2020-05-17 05:51:50 +0200 |
commit | e611b132f9b8abe35b362e5870b74bce94a1e58e (patch) | |
tree | a5781d2ec0e085eeca33cf350cf878f2efea6fe5 /private/ntos/cache/lazyrite.c | |
download | NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.gz NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.bz2 NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.lz NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.xz NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.zst NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.zip |
Diffstat (limited to '')
-rw-r--r-- | private/ntos/cache/lazyrite.c | 732 |
1 files changed, 732 insertions, 0 deletions
diff --git a/private/ntos/cache/lazyrite.c b/private/ntos/cache/lazyrite.c new file mode 100644 index 000000000..d61b0864d --- /dev/null +++ b/private/ntos/cache/lazyrite.c @@ -0,0 +1,732 @@ +/*++ + +Copyright (c) 1990 Microsoft Corporation + +Module Name: + + lazyrite.c + +Abstract: + + This module implements the lazy writer for the Cache subsystem. + +Author: + + Tom Miller [TomM] 22-July-1990 + +Revision History: + +--*/ + +#include "cc.h" + +// +// The Bug check file id for this module +// + +#define BugCheckFileId (CACHE_BUG_CHECK_LAZYRITE) + +// +// Define our debug constant +// + +#define me 0x00000020 + +// +// Local support routines +// + +PWORK_QUEUE_ENTRY +CcReadWorkQueue ( + ); + +VOID +CcLazyWriteScan ( + ); + + +VOID +CcScheduleLazyWriteScan ( + ) + +/*++ + +Routine Description: + + This routine may be called to schedule the next lazy writer scan, + during which lazy write and lazy close activity is posted to other + worker threads. Callers should acquire the lazy writer spin lock + to see if the scan is currently active, and then call this routine + still holding the spin lock if not. One special call is used at + the end of the lazy write scan to propagate lazy write active once + we go active. This call is "the" scan thread, and it can therefore + safely schedule the next scan without taking out the spin lock. + +Arguments: + + None + +Return Value: + + None. + +--*/ + +{ + // + // It is important to set the active flag TRUE first for the propagate + // case, because it is conceivable that once the timer is set, another + // thread could actually run and make the scan go idle before we then + // jam the flag TRUE. + // + // When going from idle to active, we delay a little longer to let the + // app finish saving its file. + // + + if (LazyWriter.ScanActive) { + + KeSetTimer( &LazyWriter.ScanTimer, CcIdleDelay, &LazyWriter.ScanDpc ); + + } else { + + LazyWriter.ScanActive = TRUE; + KeSetTimer( &LazyWriter.ScanTimer, CcFirstDelay, &LazyWriter.ScanDpc ); + } +} + + +VOID +CcScanDpc ( + IN PKDPC Dpc, + IN PVOID DeferredContext, + IN PVOID SystemArgument1, + IN PVOID SystemArgument2 + ) + +/*++ + +Routine Description: + + This is the Dpc routine which runs when the scan timer goes off. It + simply posts an element for an Ex Worker thread to do the scan. + +Arguments: + + (All are ignored) + +Return Value: + + None. + +--*/ + +{ + PWORK_QUEUE_ENTRY WorkQueueEntry; + + UNREFERENCED_PARAMETER(Dpc); + UNREFERENCED_PARAMETER(DeferredContext); + UNREFERENCED_PARAMETER(SystemArgument1); + UNREFERENCED_PARAMETER(SystemArgument2); + + WorkQueueEntry = CcAllocateWorkQueueEntry(); + + // + // If we failed to allocate a WorkQueueEntry, things must + // be in pretty bad shape. However, all we have to do is + // say we are not active, and wait for another event to + // wake things up again. + // + + if (WorkQueueEntry == NULL) { + + LazyWriter.ScanActive = FALSE; + + } else { + + // + // Otherwise post a work queue entry to do the scan. + // + + WorkQueueEntry->Function = (UCHAR)LazyWriteScan; + + CcPostWorkQueue( WorkQueueEntry, &CcRegularWorkQueue ); + } +} + + +VOID +CcLazyWriteScan ( + ) + +/*++ + +Routine Description: + + This routine implements the Lazy Writer scan for dirty data to flush + or any other work to do (lazy close). This routine is scheduled by + calling CcScheduleLazyWriteScan. + +Arguments: + + None. + +Return Value: + + None. + +--*/ + +{ + ULONG PagesToWrite, ForegroundRate, EstimatedDirtyNextInterval; + PSHARED_CACHE_MAP SharedCacheMap, FirstVisited; + KIRQL OldIrql; + ULONG LoopsWithLockHeld = 0; + BOOLEAN AlreadyMoved = FALSE; + + // + // Top of Lazy Writer scan. + // + + try { + + // + // If there is no work to do, then we will go inactive, and return. + // + + ExAcquireSpinLock( &CcMasterSpinLock, &OldIrql ); + + if ((CcTotalDirtyPages == 0) && !LazyWriter.OtherWork) { + + LazyWriter.ScanActive = FALSE; + ExReleaseSpinLock( &CcMasterSpinLock, OldIrql ); + return; + } + + // + // Acquire the Lazy Writer spinlock, calculate the next sweep time + // stamp, then update all relevant fields for the next time around. + // Also we can clear the OtherWork flag. + // + + LazyWriter.OtherWork = FALSE; + + // + // Assume we will write our usual fraction of dirty pages. Do not do the + // divide if there is not enough dirty pages, or else we will never write + // the last few pages. + // + + PagesToWrite = CcTotalDirtyPages; + if (PagesToWrite > LAZY_WRITER_MAX_AGE_TARGET) { + PagesToWrite /= LAZY_WRITER_MAX_AGE_TARGET; + } + + // + // Estimate the rate of dirty pages being produced in the foreground. + // This is the total number of dirty pages now plus the number of dirty + // pages we scheduled to write last time, minus the number of dirty + // pages we have now. Throw out any cases which would not produce a + // positive rate. + // + + ForegroundRate = 0; + + if ((CcTotalDirtyPages + CcPagesWrittenLastTime) > CcDirtyPagesLastScan) { + ForegroundRate = (CcTotalDirtyPages + CcPagesWrittenLastTime) - + CcDirtyPagesLastScan; + } + + // + // If we estimate that we will exceed our dirty page target by the end + // of this interval, then we must write more. Try to arrive on target. + // + + EstimatedDirtyNextInterval = CcTotalDirtyPages - PagesToWrite + ForegroundRate; + + if (EstimatedDirtyNextInterval > CcDirtyPageTarget) { + PagesToWrite += EstimatedDirtyNextInterval - CcDirtyPageTarget; + } + + // + // Now save away the number of dirty pages and the number of pages we + // just calculated to write. + // + + CcDirtyPagesLastScan = CcTotalDirtyPages; + CcPagesYetToWrite = CcPagesWrittenLastTime = PagesToWrite; + + // + // Loop to flush enough Shared Cache Maps to write the number of pages + // we just calculated. + // + + SharedCacheMap = CONTAINING_RECORD( CcLazyWriterCursor.SharedCacheMapLinks.Flink, + SHARED_CACHE_MAP, + SharedCacheMapLinks ); + + DebugTrace( 0, me, "Start of Lazy Writer Scan\n", 0 ); + + // + // Normally we would just like to visit every Cache Map once on each scan, + // so the scan will terminate normally when we return to FirstVisited. But + // in the off chance that FirstVisited gets deleted, we are guaranteed to stop + // when we get back to our own listhead. + // + + FirstVisited = NULL; + while ((SharedCacheMap != FirstVisited) && + (&SharedCacheMap->SharedCacheMapLinks != &CcLazyWriterCursor.SharedCacheMapLinks)) { + + if (FirstVisited == NULL) { + FirstVisited = SharedCacheMap; + } + + // + // Skip the SharedCacheMap if a write behind request is + // already queued, write behind has been disabled, or + // if there is no work to do (either dirty data to be written + // or a delete is required). + // + // Note that for streams where modified writing is disabled, we + // need to take out Bcbs exclusive, which serializes with foreground + // activity. Therefore we use a special counter in the SharedCacheMap + // to only service these once every n intervals. + // + // Skip temporary files unless we currently could not write 196KB + // + + if (!FlagOn(SharedCacheMap->Flags, WRITE_QUEUED | IS_CURSOR) + + && + + (((PagesToWrite != 0) && (SharedCacheMap->DirtyPages != 0) && + (((++SharedCacheMap->LazyWritePassCount & 0xF) == 0) || + !FlagOn(SharedCacheMap->Flags, MODIFIED_WRITE_DISABLED) || + (CcCapturedSystemSize == MmSmallSystem) || + (SharedCacheMap->DirtyPages >= (4 * (MAX_WRITE_BEHIND / PAGE_SIZE)))) && + (!FlagOn(SharedCacheMap->FileObject->Flags, FO_TEMPORARY_FILE) || + !CcCanIWrite(SharedCacheMap->FileObject, 0x30000, FALSE, MAXUCHAR))) + + || + + (SharedCacheMap->OpenCount == 0))) { + + PWORK_QUEUE_ENTRY WorkQueueEntry; + + // + // If this is a metadata stream with at least 4 times + // the maximum write behind I/O size, then let's tell + // this guy to write 1/8 of his dirty data on this pass + // so it doesn't build up. + // + // Else assume we can write everything (PagesToWrite only affects + // metadata streams - otherwise writing is controlled by the Mbcb). + // + + SharedCacheMap->PagesToWrite = SharedCacheMap->DirtyPages; + + if (FlagOn(SharedCacheMap->Flags, MODIFIED_WRITE_DISABLED) && + (SharedCacheMap->PagesToWrite >= (4 * (MAX_WRITE_BEHIND / PAGE_SIZE))) && + (CcCapturedSystemSize != MmSmallSystem)) { + + SharedCacheMap->PagesToWrite /= 8; + } + + // + // See if he exhausts the number of pages to write. (We + // keep going in case there are any closes to do.) + // + + if ((SharedCacheMap->PagesToWrite >= PagesToWrite) && !AlreadyMoved) { + + // + // If we met our write quota on a given SharedCacheMap, then make sure + // we start at him on the next scan, unless it is a metadata stream. + // + + RemoveEntryList( &CcLazyWriterCursor.SharedCacheMapLinks ); + + // + // For Metadata streams, set up to resume on the next stream on the + // next scan. + // + + if (FlagOn(SharedCacheMap->Flags, MODIFIED_WRITE_DISABLED)) { + InsertHeadList( &SharedCacheMap->SharedCacheMapLinks, &CcLazyWriterCursor.SharedCacheMapLinks ); + + // + // For other streams, set up to resume on the same stream on the + // next scan. + // + + } else { + InsertTailList( &SharedCacheMap->SharedCacheMapLinks, &CcLazyWriterCursor.SharedCacheMapLinks ); + } + + PagesToWrite = 0; + AlreadyMoved = TRUE; + + } else { + + PagesToWrite -= SharedCacheMap->PagesToWrite; + } + + // + // Otherwise show we are actively writing, and keep it in the dirty + // list. + // + + SetFlag(SharedCacheMap->Flags, WRITE_QUEUED); + SharedCacheMap->DirtyPages += 1; + + ExReleaseSpinLock( &CcMasterSpinLock, OldIrql ); + + // + // Queue the request to do the work to a worker thread. + // + + WorkQueueEntry = CcAllocateWorkQueueEntry(); + + // + // If we failed to allocate a WorkQueueEntry, things must + // be in pretty bad shape. However, all we have to do is + // break out of our current loop, and try to go back and + // delay a while. Even if the current guy should have gone + // away when we clear WRITE_QUEUED, we will find him again + // in the LW scan. + // + + if (WorkQueueEntry == NULL) { + + ExAcquireSpinLock( &CcMasterSpinLock, &OldIrql ); + ClearFlag(SharedCacheMap->Flags, WRITE_QUEUED); + SharedCacheMap->DirtyPages -= 1; + break; + } + + WorkQueueEntry->Function = (UCHAR)WriteBehind; + WorkQueueEntry->Parameters.Write.SharedCacheMap = SharedCacheMap; + + // + // Post it to the regular work queue. + // + + ExAcquireSpinLock( &CcMasterSpinLock, &OldIrql ); + SharedCacheMap->DirtyPages -= 1; + CcPostWorkQueue( WorkQueueEntry, &CcRegularWorkQueue ); + + LoopsWithLockHeld = 0; + + // + // Make sure we occassionally drop the lock. Set WRITE_QUEUED + // to keep the guy from going away. + // + + } else if ((++LoopsWithLockHeld >= 20) && + !FlagOn(SharedCacheMap->Flags, WRITE_QUEUED | IS_CURSOR)) { + + SetFlag(SharedCacheMap->Flags, WRITE_QUEUED); + SharedCacheMap->DirtyPages += 1; + ExReleaseSpinLock( &CcMasterSpinLock, OldIrql ); + LoopsWithLockHeld = 0; + ExAcquireSpinLock( &CcMasterSpinLock, &OldIrql ); + ClearFlag(SharedCacheMap->Flags, WRITE_QUEUED); + SharedCacheMap->DirtyPages -= 1; + } + + // + // Now loop back. + // + + SharedCacheMap = + CONTAINING_RECORD( SharedCacheMap->SharedCacheMapLinks.Flink, + SHARED_CACHE_MAP, + SharedCacheMapLinks ); + } + + DebugTrace( 0, me, "End of Lazy Writer Scan\n", 0 ); + + // + // Now we can release the global list and loop back, per chance to sleep. + // + + ExReleaseSpinLock( &CcMasterSpinLock, OldIrql ); + + // + // Now go ahead and schedule the next scan. + // + + CcScheduleLazyWriteScan(); + + // + // Basically, the Lazy Writer thread should never get an exception, + // so we put a try-except around it that bug checks one way or the other. + // Better we bug check here than worry about what happens if we let one + // get by. + // + + } except( CcExceptionFilter( GetExceptionCode() )) { + + CcBugCheck( GetExceptionCode(), 0, 0 ); + } +} + + +// +// Internal support routine +// + +LONG +CcExceptionFilter ( + IN NTSTATUS ExceptionCode + ) + +/*++ + +Routine Description: + + This is the standard exception filter for worker threads which simply + calls an FsRtl routine to see if an expected status is being raised. + If so, the exception is handled, else we bug check. + +Arguments: + + ExceptionCode - the exception code which was raised. + +Return Value: + + EXCEPTION_EXECUTE_HANDLER if expected, else a Bug Check occurs. + +--*/ + +{ + DebugTrace(0, 0, "CcExceptionFilter %08lx\n", ExceptionCode); +// DbgBreakPoint(); + + if (FsRtlIsNtstatusExpected( ExceptionCode )) { + + return EXCEPTION_EXECUTE_HANDLER; + + } else { + + return EXCEPTION_CONTINUE_SEARCH; + } +} + + + +// +// Internal support routine +// + +VOID +FASTCALL +CcPostWorkQueue ( + IN PWORK_QUEUE_ENTRY WorkQueueEntry, + IN PLIST_ENTRY WorkQueue + ) + +/*++ + +Routine Description: + + This routine queues a WorkQueueEntry, which has been allocated and + initialized by the caller, to the WorkQueue for FIFO processing by + the work threads. + +Arguments: + + WorkQueueEntry - supplies a pointer to the entry to queue + +Return Value: + + None + +--*/ + +{ + KIRQL OldIrql; + PLIST_ENTRY WorkerThreadEntry = NULL; + + ASSERT(FIELD_OFFSET(WORK_QUEUE_ITEM, List) == 0); + + DebugTrace(+1, me, "CcPostWorkQueue:\n", 0 ); + DebugTrace( 0, me, " WorkQueueEntry = %08lx\n", WorkQueueEntry ); + + // + // Queue the entry to the respective work queue. + // + + ExAcquireFastLock( &CcWorkQueueSpinlock, &OldIrql ); + InsertTailList( WorkQueue, &WorkQueueEntry->WorkQueueLinks ); + + // + // Now, if we have any more idle threads we can use, then activate + // one. + // + + if (!IsListEmpty(&CcIdleWorkerThreadList)) { + WorkerThreadEntry = RemoveHeadList( &CcIdleWorkerThreadList ); + } + ExReleaseFastLock( &CcWorkQueueSpinlock, OldIrql ); + + if (WorkerThreadEntry != NULL) { + + // + // I had to peak in the sources to verify that this routine + // is a noop if the Flink is not NULL. Sheeeeit! + // + + ((PWORK_QUEUE_ITEM)WorkerThreadEntry)->List.Flink = NULL; + ExQueueWorkItem( (PWORK_QUEUE_ITEM)WorkerThreadEntry, CriticalWorkQueue ); + } + + // + // And return to our caller + // + + DebugTrace(-1, me, "CcPostWorkQueue -> VOID\n", 0 ); + + return; +} + + +// +// Internal support routine +// + +VOID +CcWorkerThread ( + PVOID ExWorkQueueItem + ) + +/*++ + +Routine Description: + + This is worker thread routine for processing cache manager work queue + entries. + +Arguments: + + ExWorkQueueItem - The work item used for this thread + +Return Value: + + None + +--*/ + +{ + KIRQL OldIrql; + PWORK_QUEUE_ENTRY WorkQueueEntry; + BOOLEAN RescanOk = FALSE; + + ASSERT(FIELD_OFFSET(WORK_QUEUE_ENTRY, WorkQueueLinks) == 0); + + while (TRUE) { + + ExAcquireFastLock( &CcWorkQueueSpinlock, &OldIrql ); + + // + // First see if there is something in the express queue. + // + + if (!IsListEmpty(&CcExpressWorkQueue)) { + WorkQueueEntry = (PWORK_QUEUE_ENTRY)RemoveHeadList( &CcExpressWorkQueue ); + + // + // If there was nothing there, then try the regular queue. + // + + } else if (!IsListEmpty(&CcRegularWorkQueue)) { + WorkQueueEntry = (PWORK_QUEUE_ENTRY)RemoveHeadList( &CcRegularWorkQueue ); + + // + // Else we can break and go idle. + // + + } else { + break; + } + + ExReleaseFastLock( &CcWorkQueueSpinlock, OldIrql ); + + // + // Process the entry within a try-except clause, so that any errors + // will cause us to continue after the called routine has unwound. + // + + try { + + switch (WorkQueueEntry->Function) { + + // + // A read ahead or write behind request has been nooped (but + // left in the queue to keep the semaphore count right). + // + + case Noop: + break; + + // + // Perform read ahead + // + + case ReadAhead: + + DebugTrace( 0, me, "CcWorkerThread Read Ahead FileObject = %08lx\n", + WorkQueueEntry->Parameters.Read.FileObject ); + + CcPerformReadAhead( WorkQueueEntry->Parameters.Read.FileObject ); + + break; + + // + // Perform write behind + // + + case WriteBehind: + + DebugTrace( 0, me, "CcWorkerThread WriteBehind SharedCacheMap = %08lx\n", + WorkQueueEntry->Parameters.Write.SharedCacheMap ); + + RescanOk = (BOOLEAN)NT_SUCCESS(CcWriteBehind( WorkQueueEntry->Parameters.Write.SharedCacheMap )); + break; + + // + // Perform Lazy Write Scan + // + + case LazyWriteScan: + + DebugTrace( 0, me, "CcWorkerThread Lazy Write Scan\n", 0 ); + + CcLazyWriteScan(); + break; + } + + } + except( CcExceptionFilter( GetExceptionCode() )) { + + NOTHING; + } + + CcFreeWorkQueueEntry( WorkQueueEntry ); + } + + // + // No more work. Requeue our worker thread entry and get out. + // + + InsertTailList( &CcIdleWorkerThreadList, + &((PWORK_QUEUE_ITEM)ExWorkQueueItem)->List ); + + ExReleaseFastLock( &CcWorkQueueSpinlock, OldIrql ); + + if (!IsListEmpty(&CcDeferredWrites) && (CcTotalDirtyPages >= 20) && RescanOk) { + CcLazyWriteScan(); + } + + return; +} |