d38c6938b57f1a5c985701015f2e9578e7008c5d
[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 typedef enum _WORK_QUEUE_FUNCTIONS
17 {
18 ReadAhead = 1,
19 WriteBehind = 2,
20 LazyWrite = 3,
21 SetDone = 4,
22 } WORK_QUEUE_FUNCTIONS, *PWORK_QUEUE_FUNCTIONS;
23
24 /* Counters:
25 * - Amount of pages flushed by lazy writer
26 * - Number of times lazy writer ran
27 */
28 ULONG CcLazyWritePages = 0;
29 ULONG CcLazyWriteIos = 0;
30
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
43 */
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;
55
56 /* FUNCTIONS *****************************************************************/
57
58 VOID
59 CcPostWorkQueue(
60 IN PWORK_QUEUE_ENTRY WorkItem,
61 IN PLIST_ENTRY WorkQueue)
62 {
63 KIRQL OldIrql;
64 PWORK_QUEUE_ITEM ThreadToSpawn;
65
66 /* First of all, insert the item in the queue */
67 OldIrql = KeAcquireQueuedSpinLock(LockQueueWorkQueueLock);
68 InsertTailList(WorkQueue, &WorkItem->WorkQueueLinks);
69
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
74 */
75 ThreadToSpawn = NULL;
76 if (!CcQueueThrottle && !IsListEmpty(&CcIdleWorkerThreadList))
77 {
78 PLIST_ENTRY ListEntry;
79
80 /* Get the idle thread */
81 ListEntry = RemoveHeadList(&CcIdleWorkerThreadList);
82 ThreadToSpawn = CONTAINING_RECORD(ListEntry, WORK_QUEUE_ITEM, List);
83
84 /* We're going to have one more! */
85 CcNumberActiveWorkerThreads += 1;
86 }
87
88 KeReleaseQueuedSpinLock(LockQueueWorkQueueLock, OldIrql);
89
90 /* If we have a thread to spawn, do it! */
91 if (ThreadToSpawn != NULL)
92 {
93 /* We NULLify it to be consistent with initialization */
94 ThreadToSpawn->List.Flink = NULL;
95 ExQueueWorkItem(ThreadToSpawn, CriticalWorkQueue);
96 }
97 }
98
99 VOID
100 NTAPI
101 CcScanDpc(
102 IN PKDPC Dpc,
103 IN PVOID DeferredContext,
104 IN PVOID SystemArgument1,
105 IN PVOID SystemArgument2)
106 {
107 PWORK_QUEUE_ENTRY WorkItem;
108
109 /* Allocate a work item */
110 WorkItem = ExAllocateFromNPagedLookasideList(&CcTwilightLookasideList);
111 if (WorkItem == NULL)
112 {
113 LazyWriter.ScanActive = FALSE;
114 return;
115 }
116
117 /* And post it, it will be for lazy write */
118 WorkItem->Function = LazyWrite;
119 CcPostWorkQueue(WorkItem, &CcRegularWorkQueue);
120 }
121
122 /* FIXME: handle master lock */
123 VOID
124 CcLazyWriteScan(VOID)
125 {
126 ULONG Target;
127 ULONG Count;
128 KIRQL OldIrql;
129 PLIST_ENTRY ListEntry;
130 LIST_ENTRY ToPost;
131 PWORK_QUEUE_ENTRY WorkItem;
132
133 /* Do we have entries to queue after we're done? */
134 InitializeListHead(&ToPost);
135 OldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock);
136 if (LazyWriter.OtherWork)
137 {
138 while (!IsListEmpty(&CcPostTickWorkQueue))
139 {
140 ListEntry = RemoveHeadList(&CcPostTickWorkQueue);
141 WorkItem = CONTAINING_RECORD(ListEntry, WORK_QUEUE_ENTRY, WorkQueueLinks);
142 InsertTailList(&ToPost, &WorkItem->WorkQueueLinks);
143 }
144 LazyWriter.OtherWork = FALSE;
145 }
146 KeReleaseQueuedSpinLock(LockQueueMasterLock, OldIrql);
147
148 /* Our target is one-eighth of the dirty pages */
149 Target = CcTotalDirtyPages / 8;
150 if (Target != 0)
151 {
152 /* Flush! */
153 DPRINT1("Lazy writer starting (%d)\n", Target);
154 CcRosFlushDirtyPages(Target, &Count, FALSE, TRUE);
155
156 /* And update stats */
157 CcLazyWritePages += Count;
158 ++CcLazyWriteIos;
159 DPRINT1("Lazy writer done (%d)\n", Count);
160 }
161
162 /* Likely not optimal, but let's handle one deferred write now! */
163 ListEntry = ExInterlockedRemoveHeadList(&CcDeferredWrites, &CcDeferredWriteSpinLock);
164 if (ListEntry != NULL)
165 {
166 PDEFERRED_WRITE Context;
167
168 /* Extract the context */
169 Context = CONTAINING_RECORD(ListEntry, DEFERRED_WRITE, DeferredWriteLinks);
170 ASSERT(Context->NodeTypeCode == NODE_TYPE_DEFERRED_WRITE);
171
172 /* Can we write now? */
173 if (CcCanIWrite(Context->FileObject, Context->BytesToWrite, FALSE, TRUE))
174 {
175 /* Yes! Do it, and destroy the associated context */
176 Context->PostRoutine(Context->Context1, Context->Context2);
177 ExFreePoolWithTag(Context, 'CcDw');
178 }
179 else
180 {
181 /* Otherwise, requeue it, but in tail, so that it doesn't block others
182 * This is clearly to improve, but given the poor algorithm used now
183 * It's better than nothing!
184 */
185 ExInterlockedInsertTailList(&CcDeferredWrites,
186 &Context->DeferredWriteLinks,
187 &CcDeferredWriteSpinLock);
188 }
189 }
190
191 while (!IsListEmpty(&ToPost))
192 {
193 ListEntry = RemoveHeadList(&ToPost);
194 WorkItem = CONTAINING_RECORD(ListEntry, WORK_QUEUE_ENTRY, WorkQueueLinks);
195 CcPostWorkQueue(WorkItem, &CcRegularWorkQueue);
196 }
197
198 /* We're no longer active */
199 LazyWriter.ScanActive = FALSE;
200 }
201
202 VOID CcScheduleLazyWriteScan(
203 IN BOOLEAN NoDelay)
204 {
205 /* If no delay, immediately start lazy writer,
206 * no matter it was already started
207 */
208 if (NoDelay)
209 {
210 LazyWriter.ScanActive = TRUE;
211 KeSetTimer(&LazyWriter.ScanTimer, CcNoDelay, &LazyWriter.ScanDpc);
212 }
213 /* Otherwise, if it's not running, just wait three seconds to start it */
214 else if (!LazyWriter.ScanActive)
215 {
216 LazyWriter.ScanActive = TRUE;
217 KeSetTimer(&LazyWriter.ScanTimer, CcFirstDelay, &LazyWriter.ScanDpc);
218 }
219 /* Finally, already running, so queue for the next second */
220 else
221 {
222 KeSetTimer(&LazyWriter.ScanTimer, CcIdleDelay, &LazyWriter.ScanDpc);
223 }
224 }
225
226 VOID
227 NTAPI
228 CcWorkerThread(
229 IN PVOID Parameter)
230 {
231 KIRQL OldIrql;
232 BOOLEAN DropThrottle;
233 PWORK_QUEUE_ITEM Item;
234
235 /* Get back our thread item */
236 Item = Parameter;
237 /* And by default, don't touch throttle */
238 DropThrottle = FALSE;
239
240 /* Loop till we have jobs */
241 while (TRUE)
242 {
243 PWORK_QUEUE_ENTRY WorkItem;
244
245 /* Lock queues */
246 OldIrql = KeAcquireQueuedSpinLock(LockQueueWorkQueueLock);
247
248 /* If we have to touch throttle, reset it now! */
249 if (DropThrottle)
250 {
251 CcQueueThrottle = FALSE;
252 DropThrottle = FALSE;
253 }
254
255 /* If no work to do, we're done */
256 if (IsListEmpty(&CcRegularWorkQueue))
257 {
258 break;
259 }
260
261 /* Get our work item, if someone is waiting for us to finish
262 * and we're not the only thread in queue
263 * then, quit running to let the others do
264 * and throttle so that noone starts till current activity is over
265 */
266 WorkItem = CONTAINING_RECORD(CcRegularWorkQueue.Flink, WORK_QUEUE_ENTRY, WorkQueueLinks);
267 if (WorkItem->Function == SetDone && CcNumberActiveWorkerThreads > 1)
268 {
269 CcQueueThrottle = TRUE;
270 break;
271 }
272
273 /* Otherwise, remove current entry */
274 RemoveEntryList(&WorkItem->WorkQueueLinks);
275 KeReleaseQueuedSpinLock(LockQueueWorkQueueLock, OldIrql);
276
277 /* And handle it */
278 switch (WorkItem->Function)
279 {
280 /* We only support lazy write now */
281 case LazyWrite:
282 CcLazyWriteScan();
283 break;
284
285 case SetDone:
286 KeSetEvent(WorkItem->Parameters.Event.Event, IO_NO_INCREMENT, FALSE);
287 DropThrottle = TRUE;
288 break;
289 }
290
291 /* And release the item */
292 ExFreeToNPagedLookasideList(&CcTwilightLookasideList, WorkItem);
293 }
294
295 /* Our thread is available again */
296 InsertTailList(&CcIdleWorkerThreadList, &Item->List);
297 /* One less worker */
298 --CcNumberActiveWorkerThreads;
299 KeReleaseQueuedSpinLock(LockQueueWorkQueueLock, OldIrql);
300 }
301
302 /*
303 * @implemented
304 */
305 NTSTATUS
306 NTAPI
307 CcWaitForCurrentLazyWriterActivity (
308 VOID)
309 {
310 KIRQL OldIrql;
311 KEVENT WaitEvent;
312 PWORK_QUEUE_ENTRY WorkItem;
313
314 /* Allocate a work item */
315 WorkItem = ExAllocateFromNPagedLookasideList(&CcTwilightLookasideList);
316 if (WorkItem == NULL)
317 {
318 return STATUS_INSUFFICIENT_RESOURCES;
319 }
320
321 /* We want lazy writer to set our event */
322 WorkItem->Function = SetDone;
323 KeInitializeEvent(&WaitEvent, NotificationEvent, FALSE);
324 WorkItem->Parameters.Event.Event = &WaitEvent;
325
326 /* Use the post tick queue */
327 OldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock);
328 InsertTailList(&CcPostTickWorkQueue, &WorkItem->WorkQueueLinks);
329
330 /* Inform the lazy writer it will have to handle the post tick queue */
331 LazyWriter.OtherWork = TRUE;
332 /* And if it's not running, queue a lazy writer run
333 * And start it NOW, we want the response now
334 */
335 if (!LazyWriter.ScanActive)
336 {
337 CcScheduleLazyWriteScan(TRUE);
338 }
339
340 KeReleaseQueuedSpinLock(LockQueueMasterLock, OldIrql);
341
342 /* And now, wait until lazy writer replies */
343 return KeWaitForSingleObject(&WaitEvent, Executive, KernelMode, FALSE, NULL);
344 }