xref: /reactos/ntoskrnl/cc/lazywrite.c (revision 31019e8c)
1 /*
2  * COPYRIGHT:       See COPYING in the top level directory
3  * PROJECT:         ReactOS kernel
4  * FILE:            ntoskrnl/cc/lazywrite.c
5  * PURPOSE:         Cache manager
6  *
7  * PROGRAMMERS:     Pierre Schweitzer (pierre@reactos.org)
8  */
9 
10 /* INCLUDES *****************************************************************/
11 
12 #include <ntoskrnl.h>
13 #define NDEBUG
14 #include <debug.h>
15 
16 /* Counters:
17  * - Amount of pages flushed by lazy writer
18  * - Number of times lazy writer ran
19  */
20 ULONG CcLazyWritePages = 0;
21 ULONG CcLazyWriteIos = 0;
22 
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
36  */
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;
49 
50 /* FUNCTIONS *****************************************************************/
51 
52 VOID
53 CcPostWorkQueue(
54     IN PWORK_QUEUE_ENTRY WorkItem,
55     IN PLIST_ENTRY WorkQueue)
56 {
57     KIRQL OldIrql;
58     PWORK_QUEUE_ITEM ThreadToSpawn;
59 
60     /* First of all, insert the item in the queue */
61     OldIrql = KeAcquireQueuedSpinLock(LockQueueWorkQueueLock);
62     InsertTailList(WorkQueue, &WorkItem->WorkQueueLinks);
63 
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
68      */
69     ThreadToSpawn = NULL;
70     if (!CcQueueThrottle && !IsListEmpty(&CcIdleWorkerThreadList))
71     {
72         PLIST_ENTRY ListEntry;
73 
74         /* Get the idle thread */
75         ListEntry = RemoveHeadList(&CcIdleWorkerThreadList);
76         ThreadToSpawn = CONTAINING_RECORD(ListEntry, WORK_QUEUE_ITEM, List);
77 
78         /* We're going to have one more! */
79         CcNumberActiveWorkerThreads += 1;
80     }
81 
82     KeReleaseQueuedSpinLock(LockQueueWorkQueueLock, OldIrql);
83 
84     /* If we have a thread to spawn, do it! */
85     if (ThreadToSpawn != NULL)
86     {
87         /* We NULLify it to be consistent with initialization */
88         ThreadToSpawn->List.Flink = NULL;
89         ExQueueWorkItem(ThreadToSpawn, CriticalWorkQueue);
90     }
91 }
92 
93 VOID
94 NTAPI
95 CcScanDpc(
96     IN PKDPC Dpc,
97     IN PVOID DeferredContext,
98     IN PVOID SystemArgument1,
99     IN PVOID SystemArgument2)
100 {
101     PWORK_QUEUE_ENTRY WorkItem;
102 
103     /* Allocate a work item */
104     WorkItem = ExAllocateFromNPagedLookasideList(&CcTwilightLookasideList);
105     if (WorkItem == NULL)
106     {
107         LazyWriter.ScanActive = FALSE;
108         return;
109     }
110 
111     /* And post it, it will be for lazy write */
112     WorkItem->Function = LazyWrite;
113     CcPostWorkQueue(WorkItem, &CcRegularWorkQueue);
114 }
115 
116 VOID
117 CcLazyWriteScan(VOID)
118 {
119     ULONG Target;
120     ULONG Count;
121     KIRQL OldIrql;
122     PLIST_ENTRY ListEntry;
123     LIST_ENTRY ToPost;
124     PWORK_QUEUE_ENTRY WorkItem;
125 
126     /* Do we have entries to queue after we're done? */
127     InitializeListHead(&ToPost);
128     OldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock);
129     if (LazyWriter.OtherWork)
130     {
131         while (!IsListEmpty(&CcPostTickWorkQueue))
132         {
133             ListEntry = RemoveHeadList(&CcPostTickWorkQueue);
134             WorkItem = CONTAINING_RECORD(ListEntry, WORK_QUEUE_ENTRY, WorkQueueLinks);
135             InsertTailList(&ToPost, &WorkItem->WorkQueueLinks);
136         }
137         LazyWriter.OtherWork = FALSE;
138     }
139     KeReleaseQueuedSpinLock(LockQueueMasterLock, OldIrql);
140 
141     /* Our target is one-eighth of the dirty pages */
142     Target = CcTotalDirtyPages / 8;
143     if (Target != 0)
144     {
145         /* Flush! */
146         DPRINT("Lazy writer starting (%d)\n", Target);
147         CcRosFlushDirtyPages(Target, &Count, FALSE, TRUE);
148 
149         /* And update stats */
150         CcLazyWritePages += Count;
151         ++CcLazyWriteIos;
152         DPRINT("Lazy writer done (%d)\n", Count);
153     }
154 
155     /* Post items that were due for end of run */
156     while (!IsListEmpty(&ToPost))
157     {
158         ListEntry = RemoveHeadList(&ToPost);
159         WorkItem = CONTAINING_RECORD(ListEntry, WORK_QUEUE_ENTRY, WorkQueueLinks);
160         CcPostWorkQueue(WorkItem, &CcRegularWorkQueue);
161     }
162 
163     /* If we have deferred writes, try them now! */
164     if (!IsListEmpty(&CcDeferredWrites))
165     {
166         CcPostDeferredWrites();
167         /* Reschedule immediately a lazy writer run
168          * Keep us active to have short idle delay
169          */
170         CcScheduleLazyWriteScan(FALSE);
171     }
172     else
173     {
174         /* We're no longer active */
175         OldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock);
176         LazyWriter.ScanActive = FALSE;
177         KeReleaseQueuedSpinLock(LockQueueMasterLock, OldIrql);
178     }
179 }
180 
181 VOID CcScheduleLazyWriteScan(
182     IN BOOLEAN NoDelay)
183 {
184     /* If no delay, immediately start lazy writer,
185      * no matter it was already started
186      */
187     if (NoDelay)
188     {
189         LazyWriter.ScanActive = TRUE;
190         KeSetTimer(&LazyWriter.ScanTimer, CcNoDelay, &LazyWriter.ScanDpc);
191     }
192     /* Otherwise, if it's not running, just wait three seconds to start it */
193     else if (!LazyWriter.ScanActive)
194     {
195         LazyWriter.ScanActive = TRUE;
196         KeSetTimer(&LazyWriter.ScanTimer, CcFirstDelay, &LazyWriter.ScanDpc);
197     }
198     /* Finally, already running, so queue for the next second */
199     else
200     {
201         KeSetTimer(&LazyWriter.ScanTimer, CcIdleDelay, &LazyWriter.ScanDpc);
202     }
203 }
204 
205 VOID
206 NTAPI
207 CcWorkerThread(
208     IN PVOID Parameter)
209 {
210     KIRQL OldIrql;
211     BOOLEAN DropThrottle;
212     PWORK_QUEUE_ITEM Item;
213 #if DBG
214     PIRP TopLevel;
215 #endif
216 
217     /* Get back our thread item */
218     Item = Parameter;
219     /* And by default, don't touch throttle */
220     DropThrottle = FALSE;
221 
222 #if DBG
223     /* Top level IRP should be clean when started
224      * Save it to catch buggy drivers (or bugs!)
225      */
226     TopLevel = IoGetTopLevelIrp();
227     if (TopLevel != NULL)
228     {
229         DPRINT1("(%p) TopLevel IRP for this thread: %p\n", PsGetCurrentThread(), TopLevel);
230     }
231 #endif
232 
233     /* Loop till we have jobs */
234     while (TRUE)
235     {
236         PWORK_QUEUE_ENTRY WorkItem;
237 
238         /* Lock queues */
239         OldIrql = KeAcquireQueuedSpinLock(LockQueueWorkQueueLock);
240 
241         /* If we have to touch throttle, reset it now! */
242         if (DropThrottle)
243         {
244             CcQueueThrottle = FALSE;
245             DropThrottle = FALSE;
246         }
247 
248         /* Check first if we have read ahead to do */
249         if (IsListEmpty(&CcExpressWorkQueue))
250         {
251             /* If not, check regular queue */
252             if (IsListEmpty(&CcRegularWorkQueue))
253             {
254                 break;
255             }
256             else
257             {
258                 WorkItem = CONTAINING_RECORD(CcRegularWorkQueue.Flink, WORK_QUEUE_ENTRY, WorkQueueLinks);
259             }
260         }
261         else
262         {
263             WorkItem = CONTAINING_RECORD(CcExpressWorkQueue.Flink, WORK_QUEUE_ENTRY, WorkQueueLinks);
264         }
265 
266         /* Get our work item, if someone is waiting for us to finish
267          * and we're not the only thread in queue
268          * then, quit running to let the others do
269          * and throttle so that noone starts till current activity is over
270          */
271         if (WorkItem->Function == SetDone && CcNumberActiveWorkerThreads > 1)
272         {
273             CcQueueThrottle = TRUE;
274             break;
275         }
276 
277         /* Otherwise, remove current entry */
278         RemoveEntryList(&WorkItem->WorkQueueLinks);
279         KeReleaseQueuedSpinLock(LockQueueWorkQueueLock, OldIrql);
280 
281         /* And handle it */
282         switch (WorkItem->Function)
283         {
284             case ReadAhead:
285                 CcPerformReadAhead(WorkItem->Parameters.Read.FileObject);
286                 break;
287 
288             case LazyWrite:
289                 CcLazyWriteScan();
290                 break;
291 
292             case SetDone:
293                 KeSetEvent(WorkItem->Parameters.Event.Event, IO_NO_INCREMENT, FALSE);
294                 DropThrottle = TRUE;
295                 break;
296 
297             default:
298                 DPRINT1("Ignored item: %p (%d)\n", WorkItem, WorkItem->Function);
299                 break;
300         }
301 
302         /* And release the item */
303         ExFreeToNPagedLookasideList(&CcTwilightLookasideList, WorkItem);
304     }
305 
306     /* Our thread is available again */
307     InsertTailList(&CcIdleWorkerThreadList, &Item->List);
308     /* One less worker */
309     --CcNumberActiveWorkerThreads;
310     KeReleaseQueuedSpinLock(LockQueueWorkQueueLock, OldIrql);
311 
312 #if DBG
313     /* Top level shouldn't have changed */
314     if (TopLevel != IoGetTopLevelIrp())
315     {
316         DPRINT1("(%p) Mismatching TopLevel: %p, %p\n", PsGetCurrentThread(), TopLevel, IoGetTopLevelIrp());
317     }
318 #endif
319 }
320 
321 /*
322  * @implemented
323  */
324 NTSTATUS
325 NTAPI
326 CcWaitForCurrentLazyWriterActivity (
327     VOID)
328 {
329     KIRQL OldIrql;
330     KEVENT WaitEvent;
331     PWORK_QUEUE_ENTRY WorkItem;
332 
333     /* Allocate a work item */
334     WorkItem = ExAllocateFromNPagedLookasideList(&CcTwilightLookasideList);
335     if (WorkItem == NULL)
336     {
337         return STATUS_INSUFFICIENT_RESOURCES;
338     }
339 
340     /* We want lazy writer to set our event */
341     WorkItem->Function = SetDone;
342     KeInitializeEvent(&WaitEvent, NotificationEvent, FALSE);
343     WorkItem->Parameters.Event.Event = &WaitEvent;
344 
345     /* Use the post tick queue */
346     OldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock);
347     InsertTailList(&CcPostTickWorkQueue, &WorkItem->WorkQueueLinks);
348 
349     /* Inform the lazy writer it will have to handle the post tick queue */
350     LazyWriter.OtherWork = TRUE;
351     /* And if it's not running, queue a lazy writer run
352      * And start it NOW, we want the response now
353      */
354     if (!LazyWriter.ScanActive)
355     {
356         CcScheduleLazyWriteScan(TRUE);
357     }
358 
359     KeReleaseQueuedSpinLock(LockQueueMasterLock, OldIrql);
360 
361     /* And now, wait until lazy writer replies */
362     return KeWaitForSingleObject(&WaitEvent, Executive, KernelMode, FALSE, NULL);
363 }
364