2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS kernel
4 * FILE: ntoskrnl/cc/lazywrite.c
5 * PURPOSE: Cache manager
7 * PROGRAMMERS: Pierre Schweitzer (pierre@reactos.org)
10 /* INCLUDES *****************************************************************/
17 * - Amount of pages flushed by lazy writer
18 * - Number of times lazy writer ran
20 ULONG CcLazyWritePages
= 0;
21 ULONG CcLazyWriteIos
= 0;
23 /* Internal vars (MS):
24 * - Lazy writer status structure
25 * - Lookaside list where to allocate work items
26 * - Queue for high priority work items (read ahead)
27 * - Queue for regular work items
28 * - Available worker threads
29 * - Queue for stuff to be queued after lazy writer is done
30 * - Marker for throttling queues
31 * - Number of ongoing workers
32 * - Three seconds delay for lazy writer
33 * - One second delay for lazy writer
34 * - Zero delay for lazy writer
35 * - Number of worker threads
37 LAZY_WRITER LazyWriter
;
38 NPAGED_LOOKASIDE_LIST CcTwilightLookasideList
;
39 LIST_ENTRY CcExpressWorkQueue
;
40 LIST_ENTRY CcRegularWorkQueue
;
41 LIST_ENTRY CcIdleWorkerThreadList
;
42 LIST_ENTRY CcPostTickWorkQueue
;
43 BOOLEAN CcQueueThrottle
= FALSE
;
44 ULONG CcNumberActiveWorkerThreads
= 0;
45 LARGE_INTEGER CcFirstDelay
= RTL_CONSTANT_LARGE_INTEGER((LONGLONG
)-1*3000*1000*10);
46 LARGE_INTEGER CcIdleDelay
= RTL_CONSTANT_LARGE_INTEGER((LONGLONG
)-1*1000*1000*10);
47 LARGE_INTEGER CcNoDelay
= RTL_CONSTANT_LARGE_INTEGER((LONGLONG
)0);
48 ULONG CcNumberWorkerThreads
;
50 /* FUNCTIONS *****************************************************************/
54 IN PWORK_QUEUE_ENTRY WorkItem
,
55 IN PLIST_ENTRY WorkQueue
)
58 PWORK_QUEUE_ITEM ThreadToSpawn
;
60 /* First of all, insert the item in the queue */
61 OldIrql
= KeAcquireQueuedSpinLock(LockQueueWorkQueueLock
);
62 InsertTailList(WorkQueue
, &WorkItem
->WorkQueueLinks
);
64 /* Now, define whether we have to spawn a new work thread
65 * We will spawn a new one if:
66 * - There's no throttle in action
67 * - There's still at least one idle thread
70 if (!CcQueueThrottle
&& !IsListEmpty(&CcIdleWorkerThreadList
))
72 PLIST_ENTRY ListEntry
;
74 /* Get the idle thread */
75 ListEntry
= RemoveHeadList(&CcIdleWorkerThreadList
);
76 ThreadToSpawn
= CONTAINING_RECORD(ListEntry
, WORK_QUEUE_ITEM
, List
);
78 /* We're going to have one more! */
79 CcNumberActiveWorkerThreads
+= 1;
82 KeReleaseQueuedSpinLock(LockQueueWorkQueueLock
, OldIrql
);
84 /* If we have a thread to spawn, do it! */
85 if (ThreadToSpawn
!= NULL
)
87 /* We NULLify it to be consistent with initialization */
88 ThreadToSpawn
->List
.Flink
= NULL
;
89 ExQueueWorkItem(ThreadToSpawn
, CriticalWorkQueue
);
97 IN PVOID DeferredContext
,
98 IN PVOID SystemArgument1
,
99 IN PVOID SystemArgument2
)
101 PWORK_QUEUE_ENTRY WorkItem
;
103 /* Allocate a work item */
104 WorkItem
= ExAllocateFromNPagedLookasideList(&CcTwilightLookasideList
);
105 if (WorkItem
== NULL
)
107 LazyWriter
.ScanActive
= FALSE
;
111 /* And post it, it will be for lazy write */
112 WorkItem
->Function
= LazyScan
;
113 CcPostWorkQueue(WorkItem
, &CcRegularWorkQueue
);
121 Target
= CcTotalDirtyPages
/ 8;
125 DPRINT("Lazy writer starting (%d)\n", Target
);
126 CcRosFlushDirtyPages(Target
, &Count
, FALSE
, TRUE
);
128 /* And update stats */
129 CcLazyWritePages
+= Count
;
131 DPRINT("Lazy writer done (%d)\n", Count
);
134 /* Make sure we're not throttling writes after this */
135 while (MmAvailablePages
< MmThrottleTop
)
137 /* Break if we can't even find one to free */
138 if (!CcRosFreeOneUnusedVacb())
146 CcLazyWriteScan(VOID
)
150 PLIST_ENTRY ListEntry
;
152 PWORK_QUEUE_ENTRY WorkItem
;
154 /* Do we have entries to queue after we're done? */
155 InitializeListHead(&ToPost
);
156 OldIrql
= KeAcquireQueuedSpinLock(LockQueueMasterLock
);
157 if (LazyWriter
.OtherWork
)
159 while (!IsListEmpty(&CcPostTickWorkQueue
))
161 ListEntry
= RemoveHeadList(&CcPostTickWorkQueue
);
162 WorkItem
= CONTAINING_RECORD(ListEntry
, WORK_QUEUE_ENTRY
, WorkQueueLinks
);
163 InsertTailList(&ToPost
, &WorkItem
->WorkQueueLinks
);
165 LazyWriter
.OtherWork
= FALSE
;
167 KeReleaseQueuedSpinLock(LockQueueMasterLock
, OldIrql
);
169 /* Our target is one-eighth of the dirty pages */
170 Target
= CcTotalDirtyPages
/ 8;
173 /* There is stuff to flush, schedule a write-behind operation */
175 /* Allocate a work item */
176 WorkItem
= ExAllocateFromNPagedLookasideList(&CcTwilightLookasideList
);
177 if (WorkItem
!= NULL
)
179 WorkItem
->Function
= WriteBehind
;
180 CcPostWorkQueue(WorkItem
, &CcRegularWorkQueue
);
184 /* Post items that were due for end of run */
185 while (!IsListEmpty(&ToPost
))
187 ListEntry
= RemoveHeadList(&ToPost
);
188 WorkItem
= CONTAINING_RECORD(ListEntry
, WORK_QUEUE_ENTRY
, WorkQueueLinks
);
189 CcPostWorkQueue(WorkItem
, &CcRegularWorkQueue
);
192 /* If we have deferred writes, try them now! */
193 if (!IsListEmpty(&CcDeferredWrites
))
195 CcPostDeferredWrites();
196 /* Reschedule immediately a lazy writer run
197 * Keep us active to have short idle delay
199 CcScheduleLazyWriteScan(FALSE
);
203 /* We're no longer active */
204 OldIrql
= KeAcquireQueuedSpinLock(LockQueueMasterLock
);
205 LazyWriter
.ScanActive
= FALSE
;
206 KeReleaseQueuedSpinLock(LockQueueMasterLock
, OldIrql
);
210 VOID
CcScheduleLazyWriteScan(
213 /* If no delay, immediately start lazy writer,
214 * no matter it was already started
218 LazyWriter
.ScanActive
= TRUE
;
219 KeSetTimer(&LazyWriter
.ScanTimer
, CcNoDelay
, &LazyWriter
.ScanDpc
);
221 /* Otherwise, if it's not running, just wait three seconds to start it */
222 else if (!LazyWriter
.ScanActive
)
224 LazyWriter
.ScanActive
= TRUE
;
225 KeSetTimer(&LazyWriter
.ScanTimer
, CcFirstDelay
, &LazyWriter
.ScanDpc
);
227 /* Finally, already running, so queue for the next second */
230 KeSetTimer(&LazyWriter
.ScanTimer
, CcIdleDelay
, &LazyWriter
.ScanDpc
);
240 BOOLEAN DropThrottle
, WritePerformed
;
241 PWORK_QUEUE_ITEM Item
;
246 /* Get back our thread item */
248 /* And by default, don't touch throttle */
249 DropThrottle
= FALSE
;
250 /* No write performed */
251 WritePerformed
= FALSE
;
254 /* Top level IRP should be clean when started
255 * Save it to catch buggy drivers (or bugs!)
257 TopLevel
= IoGetTopLevelIrp();
258 if (TopLevel
!= NULL
)
260 DPRINT1("(%p) TopLevel IRP for this thread: %p\n", PsGetCurrentThread(), TopLevel
);
264 /* Loop till we have jobs */
267 PWORK_QUEUE_ENTRY WorkItem
;
270 OldIrql
= KeAcquireQueuedSpinLock(LockQueueWorkQueueLock
);
272 /* If we have to touch throttle, reset it now! */
275 CcQueueThrottle
= FALSE
;
276 DropThrottle
= FALSE
;
279 /* Check first if we have read ahead to do */
280 if (IsListEmpty(&CcExpressWorkQueue
))
282 /* If not, check regular queue */
283 if (IsListEmpty(&CcRegularWorkQueue
))
289 WorkItem
= CONTAINING_RECORD(CcRegularWorkQueue
.Flink
, WORK_QUEUE_ENTRY
, WorkQueueLinks
);
294 WorkItem
= CONTAINING_RECORD(CcExpressWorkQueue
.Flink
, WORK_QUEUE_ENTRY
, WorkQueueLinks
);
297 /* Get our work item, if someone is waiting for us to finish
298 * and we're not the only thread in queue
299 * then, quit running to let the others do
300 * and throttle so that noone starts till current activity is over
302 if (WorkItem
->Function
== SetDone
&& CcNumberActiveWorkerThreads
> 1)
304 CcQueueThrottle
= TRUE
;
308 /* Otherwise, remove current entry */
309 RemoveEntryList(&WorkItem
->WorkQueueLinks
);
310 KeReleaseQueuedSpinLock(LockQueueWorkQueueLock
, OldIrql
);
313 switch (WorkItem
->Function
)
316 CcPerformReadAhead(WorkItem
->Parameters
.Read
.FileObject
);
320 PsGetCurrentThread()->MemoryMaker
= 1;
322 PsGetCurrentThread()->MemoryMaker
= 0;
323 WritePerformed
= TRUE
;
331 KeSetEvent(WorkItem
->Parameters
.Event
.Event
, IO_NO_INCREMENT
, FALSE
);
336 DPRINT1("Ignored item: %p (%d)\n", WorkItem
, WorkItem
->Function
);
340 /* And release the item */
341 ExFreeToNPagedLookasideList(&CcTwilightLookasideList
, WorkItem
);
344 /* Our thread is available again */
345 InsertTailList(&CcIdleWorkerThreadList
, &Item
->List
);
346 /* One less worker */
347 --CcNumberActiveWorkerThreads
;
348 KeReleaseQueuedSpinLock(LockQueueWorkQueueLock
, OldIrql
);
350 /* If there are pending write openations and we have at least 20 dirty pages */
351 if (!IsListEmpty(&CcDeferredWrites
) && CcTotalDirtyPages
>= 20)
353 /* And if we performed a write operation previously, then
354 * stress the system a bit and reschedule a scan to find
364 /* Top level shouldn't have changed */
365 if (TopLevel
!= IoGetTopLevelIrp())
367 DPRINT1("(%p) Mismatching TopLevel: %p, %p\n", PsGetCurrentThread(), TopLevel
, IoGetTopLevelIrp());
377 CcWaitForCurrentLazyWriterActivity (
382 PWORK_QUEUE_ENTRY WorkItem
;
384 /* Allocate a work item */
385 WorkItem
= ExAllocateFromNPagedLookasideList(&CcTwilightLookasideList
);
386 if (WorkItem
== NULL
)
388 return STATUS_INSUFFICIENT_RESOURCES
;
391 /* We want lazy writer to set our event */
392 WorkItem
->Function
= SetDone
;
393 KeInitializeEvent(&WaitEvent
, NotificationEvent
, FALSE
);
394 WorkItem
->Parameters
.Event
.Event
= &WaitEvent
;
396 /* Use the post tick queue */
397 OldIrql
= KeAcquireQueuedSpinLock(LockQueueMasterLock
);
398 InsertTailList(&CcPostTickWorkQueue
, &WorkItem
->WorkQueueLinks
);
400 /* Inform the lazy writer it will have to handle the post tick queue */
401 LazyWriter
.OtherWork
= TRUE
;
402 /* And if it's not running, queue a lazy writer run
403 * And start it NOW, we want the response now
405 if (!LazyWriter
.ScanActive
)
407 CcScheduleLazyWriteScan(TRUE
);
410 KeReleaseQueuedSpinLock(LockQueueMasterLock
, OldIrql
);
412 /* And now, wait until lazy writer replies */
413 return KeWaitForSingleObject(&WaitEvent
, Executive
, KernelMode
, FALSE
, NULL
);