xref: /reactos/ntoskrnl/cc/lazywrite.c (revision 595b846d)
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     /* If we have deferred writes, try them now! */
156     if (!IsListEmpty(&CcDeferredWrites))
157     {
158         CcPostDeferredWrites();
159     }
160 
161     /* Post items that were due for end of run */
162     while (!IsListEmpty(&ToPost))
163     {
164         ListEntry = RemoveHeadList(&ToPost);
165         WorkItem = CONTAINING_RECORD(ListEntry, WORK_QUEUE_ENTRY, WorkQueueLinks);
166         CcPostWorkQueue(WorkItem, &CcRegularWorkQueue);
167     }
168 
169     /* We're no longer active */
170     OldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock);
171     LazyWriter.ScanActive = FALSE;
172     KeReleaseQueuedSpinLock(LockQueueMasterLock, OldIrql);
173 }
174 
175 VOID CcScheduleLazyWriteScan(
176     IN BOOLEAN NoDelay)
177 {
178     /* If no delay, immediately start lazy writer,
179      * no matter it was already started
180      */
181     if (NoDelay)
182     {
183         LazyWriter.ScanActive = TRUE;
184         KeSetTimer(&LazyWriter.ScanTimer, CcNoDelay, &LazyWriter.ScanDpc);
185     }
186     /* Otherwise, if it's not running, just wait three seconds to start it */
187     else if (!LazyWriter.ScanActive)
188     {
189         LazyWriter.ScanActive = TRUE;
190         KeSetTimer(&LazyWriter.ScanTimer, CcFirstDelay, &LazyWriter.ScanDpc);
191     }
192     /* Finally, already running, so queue for the next second */
193     else
194     {
195         KeSetTimer(&LazyWriter.ScanTimer, CcIdleDelay, &LazyWriter.ScanDpc);
196     }
197 }
198 
199 VOID
200 NTAPI
201 CcWorkerThread(
202     IN PVOID Parameter)
203 {
204     KIRQL OldIrql;
205     BOOLEAN DropThrottle;
206     PWORK_QUEUE_ITEM Item;
207 #if DBG
208     PIRP TopLevel;
209 #endif
210 
211     /* Get back our thread item */
212     Item = Parameter;
213     /* And by default, don't touch throttle */
214     DropThrottle = FALSE;
215 
216 #if DBG
217     /* Top level IRP should be clean when started
218      * Save it to catch buggy drivers (or bugs!)
219      */
220     TopLevel = IoGetTopLevelIrp();
221     if (TopLevel != NULL)
222     {
223         DPRINT1("(%p) TopLevel IRP for this thread: %p\n", PsGetCurrentThread(), TopLevel);
224     }
225 #endif
226 
227     /* Loop till we have jobs */
228     while (TRUE)
229     {
230         PWORK_QUEUE_ENTRY WorkItem;
231 
232         /* Lock queues */
233         OldIrql = KeAcquireQueuedSpinLock(LockQueueWorkQueueLock);
234 
235         /* If we have to touch throttle, reset it now! */
236         if (DropThrottle)
237         {
238             CcQueueThrottle = FALSE;
239             DropThrottle = FALSE;
240         }
241 
242         /* Check first if we have read ahead to do */
243         if (IsListEmpty(&CcExpressWorkQueue))
244         {
245             /* If not, check regular queue */
246             if (IsListEmpty(&CcRegularWorkQueue))
247             {
248                 break;
249             }
250             else
251             {
252                 WorkItem = CONTAINING_RECORD(CcRegularWorkQueue.Flink, WORK_QUEUE_ENTRY, WorkQueueLinks);
253             }
254         }
255         else
256         {
257             WorkItem = CONTAINING_RECORD(CcExpressWorkQueue.Flink, WORK_QUEUE_ENTRY, WorkQueueLinks);
258         }
259 
260         /* Get our work item, if someone is waiting for us to finish
261          * and we're not the only thread in queue
262          * then, quit running to let the others do
263          * and throttle so that noone starts till current activity is over
264          */
265         if (WorkItem->Function == SetDone && CcNumberActiveWorkerThreads > 1)
266         {
267             CcQueueThrottle = TRUE;
268             break;
269         }
270 
271         /* Otherwise, remove current entry */
272         RemoveEntryList(&WorkItem->WorkQueueLinks);
273         KeReleaseQueuedSpinLock(LockQueueWorkQueueLock, OldIrql);
274 
275         /* And handle it */
276         switch (WorkItem->Function)
277         {
278             case ReadAhead:
279                 CcPerformReadAhead(WorkItem->Parameters.Read.FileObject);
280                 break;
281 
282             case LazyWrite:
283                 CcLazyWriteScan();
284                 break;
285 
286             case SetDone:
287                 KeSetEvent(WorkItem->Parameters.Event.Event, IO_NO_INCREMENT, FALSE);
288                 DropThrottle = TRUE;
289                 break;
290 
291             default:
292                 DPRINT1("Ignored item: %p (%d)\n", WorkItem, WorkItem->Function);
293                 break;
294         }
295 
296         /* And release the item */
297         ExFreeToNPagedLookasideList(&CcTwilightLookasideList, WorkItem);
298     }
299 
300     /* Our thread is available again */
301     InsertTailList(&CcIdleWorkerThreadList, &Item->List);
302     /* One less worker */
303     --CcNumberActiveWorkerThreads;
304     KeReleaseQueuedSpinLock(LockQueueWorkQueueLock, OldIrql);
305 
306 #if DBG
307     /* Top level shouldn't have changed */
308     if (TopLevel != IoGetTopLevelIrp())
309     {
310         DPRINT1("(%p) Mismatching TopLevel: %p, %p\n", PsGetCurrentThread(), TopLevel, IoGetTopLevelIrp());
311     }
312 #endif
313 }
314 
315 /*
316  * @implemented
317  */
318 NTSTATUS
319 NTAPI
320 CcWaitForCurrentLazyWriterActivity (
321     VOID)
322 {
323     KIRQL OldIrql;
324     KEVENT WaitEvent;
325     PWORK_QUEUE_ENTRY WorkItem;
326 
327     /* Allocate a work item */
328     WorkItem = ExAllocateFromNPagedLookasideList(&CcTwilightLookasideList);
329     if (WorkItem == NULL)
330     {
331         return STATUS_INSUFFICIENT_RESOURCES;
332     }
333 
334     /* We want lazy writer to set our event */
335     WorkItem->Function = SetDone;
336     KeInitializeEvent(&WaitEvent, NotificationEvent, FALSE);
337     WorkItem->Parameters.Event.Event = &WaitEvent;
338 
339     /* Use the post tick queue */
340     OldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock);
341     InsertTailList(&CcPostTickWorkQueue, &WorkItem->WorkQueueLinks);
342 
343     /* Inform the lazy writer it will have to handle the post tick queue */
344     LazyWriter.OtherWork = TRUE;
345     /* And if it's not running, queue a lazy writer run
346      * And start it NOW, we want the response now
347      */
348     if (!LazyWriter.ScanActive)
349     {
350         CcScheduleLazyWriteScan(TRUE);
351     }
352 
353     KeReleaseQueuedSpinLock(LockQueueMasterLock, OldIrql);
354 
355     /* And now, wait until lazy writer replies */
356     return KeWaitForSingleObject(&WaitEvent, Executive, KernelMode, FALSE, NULL);
357 }
358