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 } 364