275507e9042630b9f6f6def42d5cad12e9f0398a
[reactos.git] / ntoskrnl / cc / lazywrite.c
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 = LazyScan;
113 CcPostWorkQueue(WorkItem, &CcRegularWorkQueue);
114 }
115
116 VOID
117 CcWriteBehind(VOID)
118 {
119 ULONG Target, Count;
120
121 Target = CcTotalDirtyPages / 8;
122 if (Target != 0)
123 {
124 /* Flush! */
125 DPRINT("Lazy writer starting (%d)\n", Target);
126 CcRosFlushDirtyPages(Target, &Count, FALSE, TRUE);
127
128 /* And update stats */
129 CcLazyWritePages += Count;
130 ++CcLazyWriteIos;
131 DPRINT("Lazy writer done (%d)\n", Count);
132 }
133 }
134
135 VOID
136 CcLazyWriteScan(VOID)
137 {
138 ULONG Target;
139 KIRQL OldIrql;
140 PLIST_ENTRY ListEntry;
141 LIST_ENTRY ToPost;
142 PWORK_QUEUE_ENTRY WorkItem;
143
144 /* Do we have entries to queue after we're done? */
145 InitializeListHead(&ToPost);
146 OldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock);
147 if (LazyWriter.OtherWork)
148 {
149 while (!IsListEmpty(&CcPostTickWorkQueue))
150 {
151 ListEntry = RemoveHeadList(&CcPostTickWorkQueue);
152 WorkItem = CONTAINING_RECORD(ListEntry, WORK_QUEUE_ENTRY, WorkQueueLinks);
153 InsertTailList(&ToPost, &WorkItem->WorkQueueLinks);
154 }
155 LazyWriter.OtherWork = FALSE;
156 }
157 KeReleaseQueuedSpinLock(LockQueueMasterLock, OldIrql);
158
159 /* Our target is one-eighth of the dirty pages */
160 Target = CcTotalDirtyPages / 8;
161 if (Target != 0)
162 {
163 /* There is stuff to flush, schedule a write-behind operation */
164
165 /* Allocate a work item */
166 WorkItem = ExAllocateFromNPagedLookasideList(&CcTwilightLookasideList);
167 if (WorkItem != NULL)
168 {
169 WorkItem->Function = WriteBehind;
170 CcPostWorkQueue(WorkItem, &CcRegularWorkQueue);
171 }
172 }
173
174 /* Post items that were due for end of run */
175 while (!IsListEmpty(&ToPost))
176 {
177 ListEntry = RemoveHeadList(&ToPost);
178 WorkItem = CONTAINING_RECORD(ListEntry, WORK_QUEUE_ENTRY, WorkQueueLinks);
179 CcPostWorkQueue(WorkItem, &CcRegularWorkQueue);
180 }
181
182 /* If we have deferred writes, try them now! */
183 if (!IsListEmpty(&CcDeferredWrites))
184 {
185 CcPostDeferredWrites();
186 /* Reschedule immediately a lazy writer run
187 * Keep us active to have short idle delay
188 */
189 CcScheduleLazyWriteScan(FALSE);
190 }
191 else
192 {
193 /* We're no longer active */
194 OldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock);
195 LazyWriter.ScanActive = FALSE;
196 KeReleaseQueuedSpinLock(LockQueueMasterLock, OldIrql);
197 }
198 }
199
200 VOID CcScheduleLazyWriteScan(
201 IN BOOLEAN NoDelay)
202 {
203 /* If no delay, immediately start lazy writer,
204 * no matter it was already started
205 */
206 if (NoDelay)
207 {
208 LazyWriter.ScanActive = TRUE;
209 KeSetTimer(&LazyWriter.ScanTimer, CcNoDelay, &LazyWriter.ScanDpc);
210 }
211 /* Otherwise, if it's not running, just wait three seconds to start it */
212 else if (!LazyWriter.ScanActive)
213 {
214 LazyWriter.ScanActive = TRUE;
215 KeSetTimer(&LazyWriter.ScanTimer, CcFirstDelay, &LazyWriter.ScanDpc);
216 }
217 /* Finally, already running, so queue for the next second */
218 else
219 {
220 KeSetTimer(&LazyWriter.ScanTimer, CcIdleDelay, &LazyWriter.ScanDpc);
221 }
222 }
223
224 VOID
225 NTAPI
226 CcWorkerThread(
227 IN PVOID Parameter)
228 {
229 KIRQL OldIrql;
230 BOOLEAN DropThrottle, WritePerformed;
231 PWORK_QUEUE_ITEM Item;
232 #if DBG
233 PIRP TopLevel;
234 #endif
235
236 /* Get back our thread item */
237 Item = Parameter;
238 /* And by default, don't touch throttle */
239 DropThrottle = FALSE;
240 /* No write performed */
241 WritePerformed = FALSE;
242
243 #if DBG
244 /* Top level IRP should be clean when started
245 * Save it to catch buggy drivers (or bugs!)
246 */
247 TopLevel = IoGetTopLevelIrp();
248 if (TopLevel != NULL)
249 {
250 DPRINT1("(%p) TopLevel IRP for this thread: %p\n", PsGetCurrentThread(), TopLevel);
251 }
252 #endif
253
254 /* Loop till we have jobs */
255 while (TRUE)
256 {
257 PWORK_QUEUE_ENTRY WorkItem;
258
259 /* Lock queues */
260 OldIrql = KeAcquireQueuedSpinLock(LockQueueWorkQueueLock);
261
262 /* If we have to touch throttle, reset it now! */
263 if (DropThrottle)
264 {
265 CcQueueThrottle = FALSE;
266 DropThrottle = FALSE;
267 }
268
269 /* Check first if we have read ahead to do */
270 if (IsListEmpty(&CcExpressWorkQueue))
271 {
272 /* If not, check regular queue */
273 if (IsListEmpty(&CcRegularWorkQueue))
274 {
275 break;
276 }
277 else
278 {
279 WorkItem = CONTAINING_RECORD(CcRegularWorkQueue.Flink, WORK_QUEUE_ENTRY, WorkQueueLinks);
280 }
281 }
282 else
283 {
284 WorkItem = CONTAINING_RECORD(CcExpressWorkQueue.Flink, WORK_QUEUE_ENTRY, WorkQueueLinks);
285 }
286
287 /* Get our work item, if someone is waiting for us to finish
288 * and we're not the only thread in queue
289 * then, quit running to let the others do
290 * and throttle so that noone starts till current activity is over
291 */
292 if (WorkItem->Function == SetDone && CcNumberActiveWorkerThreads > 1)
293 {
294 CcQueueThrottle = TRUE;
295 break;
296 }
297
298 /* Otherwise, remove current entry */
299 RemoveEntryList(&WorkItem->WorkQueueLinks);
300 KeReleaseQueuedSpinLock(LockQueueWorkQueueLock, OldIrql);
301
302 /* And handle it */
303 switch (WorkItem->Function)
304 {
305 case ReadAhead:
306 CcPerformReadAhead(WorkItem->Parameters.Read.FileObject);
307 break;
308
309 case WriteBehind:
310 CcWriteBehind();
311 WritePerformed = TRUE;
312 break;
313
314 case LazyScan:
315 CcLazyWriteScan();
316 break;
317
318 case SetDone:
319 KeSetEvent(WorkItem->Parameters.Event.Event, IO_NO_INCREMENT, FALSE);
320 DropThrottle = TRUE;
321 break;
322
323 default:
324 DPRINT1("Ignored item: %p (%d)\n", WorkItem, WorkItem->Function);
325 break;
326 }
327
328 /* And release the item */
329 ExFreeToNPagedLookasideList(&CcTwilightLookasideList, WorkItem);
330 }
331
332 /* Our thread is available again */
333 InsertTailList(&CcIdleWorkerThreadList, &Item->List);
334 /* One less worker */
335 --CcNumberActiveWorkerThreads;
336 KeReleaseQueuedSpinLock(LockQueueWorkQueueLock, OldIrql);
337
338 /* If there are pending write openations and we have at least 20 dirty pages */
339 if (!IsListEmpty(&CcDeferredWrites) && CcTotalDirtyPages >= 20)
340 {
341 /* And if we performed a write operation previously, then
342 * stress the system a bit and reschedule a scan to find
343 * stuff to write
344 */
345 if (WritePerformed)
346 {
347 CcLazyWriteScan();
348 }
349 }
350
351 #if DBG
352 /* Top level shouldn't have changed */
353 if (TopLevel != IoGetTopLevelIrp())
354 {
355 DPRINT1("(%p) Mismatching TopLevel: %p, %p\n", PsGetCurrentThread(), TopLevel, IoGetTopLevelIrp());
356 }
357 #endif
358 }
359
360 /*
361 * @implemented
362 */
363 NTSTATUS
364 NTAPI
365 CcWaitForCurrentLazyWriterActivity (
366 VOID)
367 {
368 KIRQL OldIrql;
369 KEVENT WaitEvent;
370 PWORK_QUEUE_ENTRY WorkItem;
371
372 /* Allocate a work item */
373 WorkItem = ExAllocateFromNPagedLookasideList(&CcTwilightLookasideList);
374 if (WorkItem == NULL)
375 {
376 return STATUS_INSUFFICIENT_RESOURCES;
377 }
378
379 /* We want lazy writer to set our event */
380 WorkItem->Function = SetDone;
381 KeInitializeEvent(&WaitEvent, NotificationEvent, FALSE);
382 WorkItem->Parameters.Event.Event = &WaitEvent;
383
384 /* Use the post tick queue */
385 OldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock);
386 InsertTailList(&CcPostTickWorkQueue, &WorkItem->WorkQueueLinks);
387
388 /* Inform the lazy writer it will have to handle the post tick queue */
389 LazyWriter.OtherWork = TRUE;
390 /* And if it's not running, queue a lazy writer run
391 * And start it NOW, we want the response now
392 */
393 if (!LazyWriter.ScanActive)
394 {
395 CcScheduleLazyWriteScan(TRUE);
396 }
397
398 KeReleaseQueuedSpinLock(LockQueueMasterLock, OldIrql);
399
400 /* And now, wait until lazy writer replies */
401 return KeWaitForSingleObject(&WaitEvent, Executive, KernelMode, FALSE, NULL);
402 }