8cb5adb2e71f14cfbc293370193b1850bbf7491b
[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 = 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 }