Something went wrong. Try again.
Reactos
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415/* * COPYRIGHT: See COPYING in the top level directory * PROJECT: ReactOS kernel * FILE: ntoskrnl/cc/lazywrite.c * PURPOSE: Cache manager * * PROGRAMMERS: Pierre Schweitzer (pierre@reactos.org) */
/* INCLUDES *****************************************************************/
#include <ntoskrnl.h>#define NDEBUG#include <debug.h>
/* Counters: * - Amount of pages flushed by lazy writer * - Number of times lazy writer ran */ULONG CcLazyWritePages = 0;ULONG CcLazyWriteIos = 0;
/* Internal vars (MS): * - Lazy writer status structure * - Lookaside list where to allocate work items * - Queue for high priority work items (read ahead) * - Queue for regular work items * - Available worker threads * - Queue for stuff to be queued after lazy writer is done * - Marker for throttling queues * - Number of ongoing workers * - Three seconds delay for lazy writer * - One second delay for lazy writer * - Zero delay for lazy writer * - Number of worker threads */LAZY_WRITER LazyWriter;NPAGED_LOOKASIDE_LIST CcTwilightLookasideList;LIST_ENTRY CcExpressWorkQueue;LIST_ENTRY CcRegularWorkQueue;LIST_ENTRY CcIdleWorkerThreadList;LIST_ENTRY CcPostTickWorkQueue;BOOLEAN CcQueueThrottle = FALSE;ULONG CcNumberActiveWorkerThreads = 0;LARGE_INTEGER CcFirstDelay = RTL_CONSTANT_LARGE_INTEGER((LONGLONG)-1*3000*1000*10);LARGE_INTEGER CcIdleDelay = RTL_CONSTANT_LARGE_INTEGER((LONGLONG)-1*1000*1000*10);LARGE_INTEGER CcNoDelay = RTL_CONSTANT_LARGE_INTEGER((LONGLONG)0);ULONG CcNumberWorkerThreads;
/* FUNCTIONS *****************************************************************/
VOIDCcPostWorkQueue( IN PWORK_QUEUE_ENTRY WorkItem, IN PLIST_ENTRY WorkQueue){ KIRQL OldIrql; PWORK_QUEUE_ITEM ThreadToSpawn;
/* First of all, insert the item in the queue */ OldIrql = KeAcquireQueuedSpinLock(LockQueueWorkQueueLock); InsertTailList(WorkQueue, &WorkItem->WorkQueueLinks);
/* Now, define whether we have to spawn a new work thread * We will spawn a new one if: * - There's no throttle in action * - There's still at least one idle thread */ ThreadToSpawn = NULL; if (!CcQueueThrottle && !IsListEmpty(&CcIdleWorkerThreadList)) { PLIST_ENTRY ListEntry;
/* Get the idle thread */ ListEntry = RemoveHeadList(&CcIdleWorkerThreadList); ThreadToSpawn = CONTAINING_RECORD(ListEntry, WORK_QUEUE_ITEM, List);
/* We're going to have one more! */ CcNumberActiveWorkerThreads += 1; }
KeReleaseQueuedSpinLock(LockQueueWorkQueueLock, OldIrql);
/* If we have a thread to spawn, do it! */ if (ThreadToSpawn != NULL) { /* We NULLify it to be consistent with initialization */ ThreadToSpawn->List.Flink = NULL; ExQueueWorkItem(ThreadToSpawn, CriticalWorkQueue); }}
VOIDNTAPICcScanDpc( IN PKDPC Dpc, IN PVOID DeferredContext, IN PVOID SystemArgument1, IN PVOID SystemArgument2){ PWORK_QUEUE_ENTRY WorkItem;
/* Allocate a work item */ WorkItem = ExAllocateFromNPagedLookasideList(&CcTwilightLookasideList); if (WorkItem == NULL) { LazyWriter.ScanActive = FALSE; return; }
/* And post it, it will be for lazy write */ WorkItem->Function = LazyScan; CcPostWorkQueue(WorkItem, &CcRegularWorkQueue);}
VOIDCcWriteBehind(VOID){ ULONG Target, Count;
Target = CcTotalDirtyPages / 8; if (Target != 0) { /* Flush! */ DPRINT("Lazy writer starting (%d)\n", Target); CcRosFlushDirtyPages(Target, &Count, FALSE, TRUE);
/* And update stats */ CcLazyWritePages += Count; ++CcLazyWriteIos; DPRINT("Lazy writer done (%d)\n", Count); }
/* Make sure we're not throttling writes after this */ while (MmAvailablePages < MmThrottleTop) { /* Break if we can't even find one to free */ if (!CcRosFreeOneUnusedVacb()) { break; } }}
VOIDCcLazyWriteScan(VOID){ ULONG Target; KIRQL OldIrql; PLIST_ENTRY ListEntry; LIST_ENTRY ToPost; PWORK_QUEUE_ENTRY WorkItem;
/* Do we have entries to queue after we're done? */ InitializeListHead(&ToPost); OldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock); if (LazyWriter.OtherWork) { while (!IsListEmpty(&CcPostTickWorkQueue)) { ListEntry = RemoveHeadList(&CcPostTickWorkQueue); WorkItem = CONTAINING_RECORD(ListEntry, WORK_QUEUE_ENTRY, WorkQueueLinks); InsertTailList(&ToPost, &WorkItem->WorkQueueLinks); } LazyWriter.OtherWork = FALSE; } KeReleaseQueuedSpinLock(LockQueueMasterLock, OldIrql);
/* Our target is one-eighth of the dirty pages */ Target = CcTotalDirtyPages / 8; if (Target != 0) { /* There is stuff to flush, schedule a write-behind operation */
/* Allocate a work item */ WorkItem = ExAllocateFromNPagedLookasideList(&CcTwilightLookasideList); if (WorkItem != NULL) { WorkItem->Function = WriteBehind; CcPostWorkQueue(WorkItem, &CcRegularWorkQueue); } }
/* Post items that were due for end of run */ while (!IsListEmpty(&ToPost)) { ListEntry = RemoveHeadList(&ToPost); WorkItem = CONTAINING_RECORD(ListEntry, WORK_QUEUE_ENTRY, WorkQueueLinks); CcPostWorkQueue(WorkItem, &CcRegularWorkQueue); }
/* If we have deferred writes, try them now! */ if (!IsListEmpty(&CcDeferredWrites)) { CcPostDeferredWrites(); /* Reschedule immediately a lazy writer run * Keep us active to have short idle delay */ CcScheduleLazyWriteScan(FALSE); } else { /* We're no longer active */ OldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock); LazyWriter.ScanActive = FALSE; KeReleaseQueuedSpinLock(LockQueueMasterLock, OldIrql); }}
VOID CcScheduleLazyWriteScan( IN BOOLEAN NoDelay){ /* If no delay, immediately start lazy writer, * no matter it was already started */ if (NoDelay) { LazyWriter.ScanActive = TRUE; KeSetTimer(&LazyWriter.ScanTimer, CcNoDelay, &LazyWriter.ScanDpc); } /* Otherwise, if it's not running, just wait three seconds to start it */ else if (!LazyWriter.ScanActive) { LazyWriter.ScanActive = TRUE; KeSetTimer(&LazyWriter.ScanTimer, CcFirstDelay, &LazyWriter.ScanDpc); } /* Finally, already running, so queue for the next second */ else { KeSetTimer(&LazyWriter.ScanTimer, CcIdleDelay, &LazyWriter.ScanDpc); }}
VOIDNTAPICcWorkerThread( IN PVOID Parameter){ KIRQL OldIrql; BOOLEAN DropThrottle, WritePerformed; PWORK_QUEUE_ITEM Item;#if DBG PIRP TopLevel;#endif
/* Get back our thread item */ Item = Parameter; /* And by default, don't touch throttle */ DropThrottle = FALSE; /* No write performed */ WritePerformed = FALSE;
#if DBG /* Top level IRP should be clean when started * Save it to catch buggy drivers (or bugs!) */ TopLevel = IoGetTopLevelIrp(); if (TopLevel != NULL) { DPRINT1("(%p) TopLevel IRP for this thread: %p\n", PsGetCurrentThread(), TopLevel); }#endif
/* Loop till we have jobs */ while (TRUE) { PWORK_QUEUE_ENTRY WorkItem;
/* Lock queues */ OldIrql = KeAcquireQueuedSpinLock(LockQueueWorkQueueLock);
/* If we have to touch throttle, reset it now! */ if (DropThrottle) { CcQueueThrottle = FALSE; DropThrottle = FALSE; }
/* Check first if we have read ahead to do */ if (IsListEmpty(&CcExpressWorkQueue)) { /* If not, check regular queue */ if (IsListEmpty(&CcRegularWorkQueue)) { break; } else { WorkItem = CONTAINING_RECORD(CcRegularWorkQueue.Flink, WORK_QUEUE_ENTRY, WorkQueueLinks); } } else { WorkItem = CONTAINING_RECORD(CcExpressWorkQueue.Flink, WORK_QUEUE_ENTRY, WorkQueueLinks); }
/* Get our work item, if someone is waiting for us to finish * and we're not the only thread in queue * then, quit running to let the others do * and throttle so that noone starts till current activity is over */ if (WorkItem->Function == SetDone && CcNumberActiveWorkerThreads > 1) { CcQueueThrottle = TRUE; break; }
/* Otherwise, remove current entry */ RemoveEntryList(&WorkItem->WorkQueueLinks); KeReleaseQueuedSpinLock(LockQueueWorkQueueLock, OldIrql);
/* And handle it */ switch (WorkItem->Function) { case ReadAhead: CcPerformReadAhead(WorkItem->Parameters.Read.FileObject); break;
case WriteBehind: PsGetCurrentThread()->MemoryMaker = 1; CcWriteBehind(); PsGetCurrentThread()->MemoryMaker = 0; WritePerformed = TRUE; break;
case LazyScan: CcLazyWriteScan(); break;
case SetDone: KeSetEvent(WorkItem->Parameters.Event.Event, IO_NO_INCREMENT, FALSE); DropThrottle = TRUE; break;
default: DPRINT1("Ignored item: %p (%d)\n", WorkItem, WorkItem->Function); break; }
/* And release the item */ ExFreeToNPagedLookasideList(&CcTwilightLookasideList, WorkItem); }
/* Our thread is available again */ InsertTailList(&CcIdleWorkerThreadList, &Item->List); /* One less worker */ --CcNumberActiveWorkerThreads; KeReleaseQueuedSpinLock(LockQueueWorkQueueLock, OldIrql);
/* If there are pending write openations and we have at least 20 dirty pages */ if (!IsListEmpty(&CcDeferredWrites) && CcTotalDirtyPages >= 20) { /* And if we performed a write operation previously, then * stress the system a bit and reschedule a scan to find * stuff to write */ if (WritePerformed) { CcLazyWriteScan(); } }
#if DBG /* Top level shouldn't have changed */ if (TopLevel != IoGetTopLevelIrp()) { DPRINT1("(%p) Mismatching TopLevel: %p, %p\n", PsGetCurrentThread(), TopLevel, IoGetTopLevelIrp()); }#endif}
/* * @implemented */NTSTATUSNTAPICcWaitForCurrentLazyWriterActivity ( VOID){ KIRQL OldIrql; KEVENT WaitEvent; PWORK_QUEUE_ENTRY WorkItem;
/* Allocate a work item */ WorkItem = ExAllocateFromNPagedLookasideList(&CcTwilightLookasideList); if (WorkItem == NULL) { return STATUS_INSUFFICIENT_RESOURCES; }
/* We want lazy writer to set our event */ WorkItem->Function = SetDone; KeInitializeEvent(&WaitEvent, NotificationEvent, FALSE); WorkItem->Parameters.Event.Event = &WaitEvent;
/* Use the post tick queue */ OldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock); InsertTailList(&CcPostTickWorkQueue, &WorkItem->WorkQueueLinks);
/* Inform the lazy writer it will have to handle the post tick queue */ LazyWriter.OtherWork = TRUE; /* And if it's not running, queue a lazy writer run * And start it NOW, we want the response now */ if (!LazyWriter.ScanActive) { CcScheduleLazyWriteScan(TRUE); }
KeReleaseQueuedSpinLock(LockQueueMasterLock, OldIrql);
/* And now, wait until lazy writer replies */ return KeWaitForSingleObject(&WaitEvent, Executive, KernelMode, FALSE, NULL);}