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 PsGetCurrentThread()->MemoryMaker = 1; 311 CcWriteBehind(); 312 PsGetCurrentThread()->MemoryMaker = 0; 313 WritePerformed = TRUE; 314 break; 315 316 case LazyScan: 317 CcLazyWriteScan(); 318 break; 319 320 case SetDone: 321 KeSetEvent(WorkItem->Parameters.Event.Event, IO_NO_INCREMENT, FALSE); 322 DropThrottle = TRUE; 323 break; 324 325 default: 326 DPRINT1("Ignored item: %p (%d)\n", WorkItem, WorkItem->Function); 327 break; 328 } 329 330 /* And release the item */ 331 ExFreeToNPagedLookasideList(&CcTwilightLookasideList, WorkItem); 332 } 333 334 /* Our thread is available again */ 335 InsertTailList(&CcIdleWorkerThreadList, &Item->List); 336 /* One less worker */ 337 --CcNumberActiveWorkerThreads; 338 KeReleaseQueuedSpinLock(LockQueueWorkQueueLock, OldIrql); 339 340 /* If there are pending write openations and we have at least 20 dirty pages */ 341 if (!IsListEmpty(&CcDeferredWrites) && CcTotalDirtyPages >= 20) 342 { 343 /* And if we performed a write operation previously, then 344 * stress the system a bit and reschedule a scan to find 345 * stuff to write 346 */ 347 if (WritePerformed) 348 { 349 CcLazyWriteScan(); 350 } 351 } 352 353 #if DBG 354 /* Top level shouldn't have changed */ 355 if (TopLevel != IoGetTopLevelIrp()) 356 { 357 DPRINT1("(%p) Mismatching TopLevel: %p, %p\n", PsGetCurrentThread(), TopLevel, IoGetTopLevelIrp()); 358 } 359 #endif 360 } 361 362 /* 363 * @implemented 364 */ 365 NTSTATUS 366 NTAPI 367 CcWaitForCurrentLazyWriterActivity ( 368 VOID) 369 { 370 KIRQL OldIrql; 371 KEVENT WaitEvent; 372 PWORK_QUEUE_ENTRY WorkItem; 373 374 /* Allocate a work item */ 375 WorkItem = ExAllocateFromNPagedLookasideList(&CcTwilightLookasideList); 376 if (WorkItem == NULL) 377 { 378 return STATUS_INSUFFICIENT_RESOURCES; 379 } 380 381 /* We want lazy writer to set our event */ 382 WorkItem->Function = SetDone; 383 KeInitializeEvent(&WaitEvent, NotificationEvent, FALSE); 384 WorkItem->Parameters.Event.Event = &WaitEvent; 385 386 /* Use the post tick queue */ 387 OldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock); 388 InsertTailList(&CcPostTickWorkQueue, &WorkItem->WorkQueueLinks); 389 390 /* Inform the lazy writer it will have to handle the post tick queue */ 391 LazyWriter.OtherWork = TRUE; 392 /* And if it's not running, queue a lazy writer run 393 * And start it NOW, we want the response now 394 */ 395 if (!LazyWriter.ScanActive) 396 { 397 CcScheduleLazyWriteScan(TRUE); 398 } 399 400 KeReleaseQueuedSpinLock(LockQueueMasterLock, OldIrql); 401 402 /* And now, wait until lazy writer replies */ 403 return KeWaitForSingleObject(&WaitEvent, Executive, KernelMode, FALSE, NULL); 404 } 405