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 *****************************************************************/
16 typedef enum _WORK_QUEUE_FUNCTIONS
22 } WORK_QUEUE_FUNCTIONS
, *PWORK_QUEUE_FUNCTIONS
;
25 * - Amount of pages flushed by lazy writer
26 * - Number of times lazy writer ran
28 ULONG CcLazyWritePages
= 0;
29 ULONG CcLazyWriteIos
= 0;
31 /* Internal vars (MS):
32 * - Lazy writer status structure
33 * - Lookaside list where to allocate work items
34 * - Queue for regular work items
35 * - Available worker threads
36 * - Queue for stuff to be queued after lazy writer is done
37 * - Marker for throttling queues
38 * - Number of ongoing workers
39 * - Three seconds delay for lazy writer
40 * - One second delay for lazy writer
41 * - Zero delay for lazy writer
42 * - Number of worker threads
44 LAZY_WRITER LazyWriter
;
45 NPAGED_LOOKASIDE_LIST CcTwilightLookasideList
;
46 LIST_ENTRY CcRegularWorkQueue
;
47 LIST_ENTRY CcIdleWorkerThreadList
;
48 LIST_ENTRY CcPostTickWorkQueue
;
49 BOOLEAN CcQueueThrottle
= FALSE
;
50 ULONG CcNumberActiveWorkerThreads
= 0;
51 LARGE_INTEGER CcFirstDelay
= RTL_CONSTANT_LARGE_INTEGER((LONGLONG
)-1*3000*1000*10);
52 LARGE_INTEGER CcIdleDelay
= RTL_CONSTANT_LARGE_INTEGER((LONGLONG
)-1*1000*1000*10);
53 LARGE_INTEGER CcNoDelay
= RTL_CONSTANT_LARGE_INTEGER((LONGLONG
)0);
54 ULONG CcNumberWorkerThreads
;
56 /* FUNCTIONS *****************************************************************/
60 IN PWORK_QUEUE_ENTRY WorkItem
,
61 IN PLIST_ENTRY WorkQueue
)
64 PWORK_QUEUE_ITEM ThreadToSpawn
;
66 /* First of all, insert the item in the queue */
67 OldIrql
= KeAcquireQueuedSpinLock(LockQueueWorkQueueLock
);
68 InsertTailList(WorkQueue
, &WorkItem
->WorkQueueLinks
);
70 /* Now, define whether we have to spawn a new work thread
71 * We will spawn a new one if:
72 * - There's no throttle in action
73 * - There's still at least one idle thread
76 if (!CcQueueThrottle
&& !IsListEmpty(&CcIdleWorkerThreadList
))
78 PLIST_ENTRY ListEntry
;
80 /* Get the idle thread */
81 ListEntry
= RemoveHeadList(&CcIdleWorkerThreadList
);
82 ThreadToSpawn
= CONTAINING_RECORD(ListEntry
, WORK_QUEUE_ITEM
, List
);
84 /* We're going to have one more! */
85 CcNumberActiveWorkerThreads
+= 1;
88 KeReleaseQueuedSpinLock(LockQueueWorkQueueLock
, OldIrql
);
90 /* If we have a thread to spawn, do it! */
91 if (ThreadToSpawn
!= NULL
)
93 /* We NULLify it to be consistent with initialization */
94 ThreadToSpawn
->List
.Flink
= NULL
;
95 ExQueueWorkItem(ThreadToSpawn
, CriticalWorkQueue
);
103 IN PVOID DeferredContext
,
104 IN PVOID SystemArgument1
,
105 IN PVOID SystemArgument2
)
107 PWORK_QUEUE_ENTRY WorkItem
;
109 /* Allocate a work item */
110 WorkItem
= ExAllocateFromNPagedLookasideList(&CcTwilightLookasideList
);
111 if (WorkItem
== NULL
)
113 LazyWriter
.ScanActive
= FALSE
;
117 /* And post it, it will be for lazy write */
118 WorkItem
->Function
= LazyWrite
;
119 CcPostWorkQueue(WorkItem
, &CcRegularWorkQueue
);
122 /* FIXME: handle master lock */
124 CcLazyWriteScan(VOID
)
129 PLIST_ENTRY ListEntry
;
131 PWORK_QUEUE_ENTRY WorkItem
;
133 /* Do we have entries to queue after we're done? */
134 InitializeListHead(&ToPost
);
135 OldIrql
= KeAcquireQueuedSpinLock(LockQueueMasterLock
);
136 if (LazyWriter
.OtherWork
)
138 while (!IsListEmpty(&CcPostTickWorkQueue
))
140 ListEntry
= RemoveHeadList(&CcPostTickWorkQueue
);
141 WorkItem
= CONTAINING_RECORD(ListEntry
, WORK_QUEUE_ENTRY
, WorkQueueLinks
);
142 InsertTailList(&ToPost
, &WorkItem
->WorkQueueLinks
);
144 LazyWriter
.OtherWork
= FALSE
;
146 KeReleaseQueuedSpinLock(LockQueueMasterLock
, OldIrql
);
148 /* Our target is one-eighth of the dirty pages */
149 Target
= CcTotalDirtyPages
/ 8;
153 DPRINT1("Lazy writer starting (%d)\n", Target
);
154 CcRosFlushDirtyPages(Target
, &Count
, FALSE
, TRUE
);
156 /* And update stats */
157 CcLazyWritePages
+= Count
;
159 DPRINT1("Lazy writer done (%d)\n", Count
);
162 /* If we have deferred writes, try them now! */
163 if (!IsListEmpty(&CcDeferredWrites
))
165 CcPostDeferredWrites();
168 while (!IsListEmpty(&ToPost
))
170 ListEntry
= RemoveHeadList(&ToPost
);
171 WorkItem
= CONTAINING_RECORD(ListEntry
, WORK_QUEUE_ENTRY
, WorkQueueLinks
);
172 CcPostWorkQueue(WorkItem
, &CcRegularWorkQueue
);
175 /* We're no longer active */
176 LazyWriter
.ScanActive
= FALSE
;
179 VOID
CcScheduleLazyWriteScan(
182 /* If no delay, immediately start lazy writer,
183 * no matter it was already started
187 LazyWriter
.ScanActive
= TRUE
;
188 KeSetTimer(&LazyWriter
.ScanTimer
, CcNoDelay
, &LazyWriter
.ScanDpc
);
190 /* Otherwise, if it's not running, just wait three seconds to start it */
191 else if (!LazyWriter
.ScanActive
)
193 LazyWriter
.ScanActive
= TRUE
;
194 KeSetTimer(&LazyWriter
.ScanTimer
, CcFirstDelay
, &LazyWriter
.ScanDpc
);
196 /* Finally, already running, so queue for the next second */
199 KeSetTimer(&LazyWriter
.ScanTimer
, CcIdleDelay
, &LazyWriter
.ScanDpc
);
209 BOOLEAN DropThrottle
;
210 PWORK_QUEUE_ITEM Item
;
212 /* Get back our thread item */
214 /* And by default, don't touch throttle */
215 DropThrottle
= FALSE
;
217 /* Loop till we have jobs */
220 PWORK_QUEUE_ENTRY WorkItem
;
223 OldIrql
= KeAcquireQueuedSpinLock(LockQueueWorkQueueLock
);
225 /* If we have to touch throttle, reset it now! */
228 CcQueueThrottle
= FALSE
;
229 DropThrottle
= FALSE
;
232 /* If no work to do, we're done */
233 if (IsListEmpty(&CcRegularWorkQueue
))
238 /* Get our work item, if someone is waiting for us to finish
239 * and we're not the only thread in queue
240 * then, quit running to let the others do
241 * and throttle so that noone starts till current activity is over
243 WorkItem
= CONTAINING_RECORD(CcRegularWorkQueue
.Flink
, WORK_QUEUE_ENTRY
, WorkQueueLinks
);
244 if (WorkItem
->Function
== SetDone
&& CcNumberActiveWorkerThreads
> 1)
246 CcQueueThrottle
= TRUE
;
250 /* Otherwise, remove current entry */
251 RemoveEntryList(&WorkItem
->WorkQueueLinks
);
252 KeReleaseQueuedSpinLock(LockQueueWorkQueueLock
, OldIrql
);
255 switch (WorkItem
->Function
)
257 /* We only support lazy write now */
263 KeSetEvent(WorkItem
->Parameters
.Event
.Event
, IO_NO_INCREMENT
, FALSE
);
268 /* And release the item */
269 ExFreeToNPagedLookasideList(&CcTwilightLookasideList
, WorkItem
);
272 /* Our thread is available again */
273 InsertTailList(&CcIdleWorkerThreadList
, &Item
->List
);
274 /* One less worker */
275 --CcNumberActiveWorkerThreads
;
276 KeReleaseQueuedSpinLock(LockQueueWorkQueueLock
, OldIrql
);
284 CcWaitForCurrentLazyWriterActivity (
289 PWORK_QUEUE_ENTRY WorkItem
;
291 /* Allocate a work item */
292 WorkItem
= ExAllocateFromNPagedLookasideList(&CcTwilightLookasideList
);
293 if (WorkItem
== NULL
)
295 return STATUS_INSUFFICIENT_RESOURCES
;
298 /* We want lazy writer to set our event */
299 WorkItem
->Function
= SetDone
;
300 KeInitializeEvent(&WaitEvent
, NotificationEvent
, FALSE
);
301 WorkItem
->Parameters
.Event
.Event
= &WaitEvent
;
303 /* Use the post tick queue */
304 OldIrql
= KeAcquireQueuedSpinLock(LockQueueMasterLock
);
305 InsertTailList(&CcPostTickWorkQueue
, &WorkItem
->WorkQueueLinks
);
307 /* Inform the lazy writer it will have to handle the post tick queue */
308 LazyWriter
.OtherWork
= TRUE
;
309 /* And if it's not running, queue a lazy writer run
310 * And start it NOW, we want the response now
312 if (!LazyWriter
.ScanActive
)
314 CcScheduleLazyWriteScan(TRUE
);
317 KeReleaseQueuedSpinLock(LockQueueMasterLock
, OldIrql
);
319 /* And now, wait until lazy writer replies */
320 return KeWaitForSingleObject(&WaitEvent
, Executive
, KernelMode
, FALSE
, NULL
);