mirror of
https://github.com/reactos/reactos.git
synced 2024-12-29 10:35:28 +00:00
414 lines
12 KiB
C
414 lines
12 KiB
C
/*
|
|
* 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 *****************************************************************/
|
|
|
|
VOID
|
|
CcPostWorkQueue(
|
|
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);
|
|
}
|
|
}
|
|
|
|
VOID
|
|
NTAPI
|
|
CcScanDpc(
|
|
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);
|
|
}
|
|
|
|
VOID
|
|
CcWriteBehind(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;
|
|
}
|
|
}
|
|
}
|
|
|
|
VOID
|
|
CcLazyWriteScan(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);
|
|
}
|
|
}
|
|
|
|
VOID
|
|
NTAPI
|
|
CcWorkerThread(
|
|
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
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
CcWaitForCurrentLazyWriterActivity (
|
|
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);
|
|
}
|