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 /* If we have deferred writes, try them now! */ 156 if (!IsListEmpty(&CcDeferredWrites)) 157 { 158 CcPostDeferredWrites(); 159 } 160 161 /* Post items that were due for end of run */ 162 while (!IsListEmpty(&ToPost)) 163 { 164 ListEntry = RemoveHeadList(&ToPost); 165 WorkItem = CONTAINING_RECORD(ListEntry, WORK_QUEUE_ENTRY, WorkQueueLinks); 166 CcPostWorkQueue(WorkItem, &CcRegularWorkQueue); 167 } 168 169 /* We're no longer active */ 170 OldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock); 171 LazyWriter.ScanActive = FALSE; 172 KeReleaseQueuedSpinLock(LockQueueMasterLock, OldIrql); 173 } 174 175 VOID CcScheduleLazyWriteScan( 176 IN BOOLEAN NoDelay) 177 { 178 /* If no delay, immediately start lazy writer, 179 * no matter it was already started 180 */ 181 if (NoDelay) 182 { 183 LazyWriter.ScanActive = TRUE; 184 KeSetTimer(&LazyWriter.ScanTimer, CcNoDelay, &LazyWriter.ScanDpc); 185 } 186 /* Otherwise, if it's not running, just wait three seconds to start it */ 187 else if (!LazyWriter.ScanActive) 188 { 189 LazyWriter.ScanActive = TRUE; 190 KeSetTimer(&LazyWriter.ScanTimer, CcFirstDelay, &LazyWriter.ScanDpc); 191 } 192 /* Finally, already running, so queue for the next second */ 193 else 194 { 195 KeSetTimer(&LazyWriter.ScanTimer, CcIdleDelay, &LazyWriter.ScanDpc); 196 } 197 } 198 199 VOID 200 NTAPI 201 CcWorkerThread( 202 IN PVOID Parameter) 203 { 204 KIRQL OldIrql; 205 BOOLEAN DropThrottle; 206 PWORK_QUEUE_ITEM Item; 207 #if DBG 208 PIRP TopLevel; 209 #endif 210 211 /* Get back our thread item */ 212 Item = Parameter; 213 /* And by default, don't touch throttle */ 214 DropThrottle = FALSE; 215 216 #if DBG 217 /* Top level IRP should be clean when started 218 * Save it to catch buggy drivers (or bugs!) 219 */ 220 TopLevel = IoGetTopLevelIrp(); 221 if (TopLevel != NULL) 222 { 223 DPRINT1("(%p) TopLevel IRP for this thread: %p\n", PsGetCurrentThread(), TopLevel); 224 } 225 #endif 226 227 /* Loop till we have jobs */ 228 while (TRUE) 229 { 230 PWORK_QUEUE_ENTRY WorkItem; 231 232 /* Lock queues */ 233 OldIrql = KeAcquireQueuedSpinLock(LockQueueWorkQueueLock); 234 235 /* If we have to touch throttle, reset it now! */ 236 if (DropThrottle) 237 { 238 CcQueueThrottle = FALSE; 239 DropThrottle = FALSE; 240 } 241 242 /* Check first if we have read ahead to do */ 243 if (IsListEmpty(&CcExpressWorkQueue)) 244 { 245 /* If not, check regular queue */ 246 if (IsListEmpty(&CcRegularWorkQueue)) 247 { 248 break; 249 } 250 else 251 { 252 WorkItem = CONTAINING_RECORD(CcRegularWorkQueue.Flink, WORK_QUEUE_ENTRY, WorkQueueLinks); 253 } 254 } 255 else 256 { 257 WorkItem = CONTAINING_RECORD(CcExpressWorkQueue.Flink, WORK_QUEUE_ENTRY, WorkQueueLinks); 258 } 259 260 /* Get our work item, if someone is waiting for us to finish 261 * and we're not the only thread in queue 262 * then, quit running to let the others do 263 * and throttle so that noone starts till current activity is over 264 */ 265 if (WorkItem->Function == SetDone && CcNumberActiveWorkerThreads > 1) 266 { 267 CcQueueThrottle = TRUE; 268 break; 269 } 270 271 /* Otherwise, remove current entry */ 272 RemoveEntryList(&WorkItem->WorkQueueLinks); 273 KeReleaseQueuedSpinLock(LockQueueWorkQueueLock, OldIrql); 274 275 /* And handle it */ 276 switch (WorkItem->Function) 277 { 278 case ReadAhead: 279 CcPerformReadAhead(WorkItem->Parameters.Read.FileObject); 280 break; 281 282 case LazyWrite: 283 CcLazyWriteScan(); 284 break; 285 286 case SetDone: 287 KeSetEvent(WorkItem->Parameters.Event.Event, IO_NO_INCREMENT, FALSE); 288 DropThrottle = TRUE; 289 break; 290 291 default: 292 DPRINT1("Ignored item: %p (%d)\n", WorkItem, WorkItem->Function); 293 break; 294 } 295 296 /* And release the item */ 297 ExFreeToNPagedLookasideList(&CcTwilightLookasideList, WorkItem); 298 } 299 300 /* Our thread is available again */ 301 InsertTailList(&CcIdleWorkerThreadList, &Item->List); 302 /* One less worker */ 303 --CcNumberActiveWorkerThreads; 304 KeReleaseQueuedSpinLock(LockQueueWorkQueueLock, OldIrql); 305 306 #if DBG 307 /* Top level shouldn't have changed */ 308 if (TopLevel != IoGetTopLevelIrp()) 309 { 310 DPRINT1("(%p) Mismatching TopLevel: %p, %p\n", PsGetCurrentThread(), TopLevel, IoGetTopLevelIrp()); 311 } 312 #endif 313 } 314 315 /* 316 * @implemented 317 */ 318 NTSTATUS 319 NTAPI 320 CcWaitForCurrentLazyWriterActivity ( 321 VOID) 322 { 323 KIRQL OldIrql; 324 KEVENT WaitEvent; 325 PWORK_QUEUE_ENTRY WorkItem; 326 327 /* Allocate a work item */ 328 WorkItem = ExAllocateFromNPagedLookasideList(&CcTwilightLookasideList); 329 if (WorkItem == NULL) 330 { 331 return STATUS_INSUFFICIENT_RESOURCES; 332 } 333 334 /* We want lazy writer to set our event */ 335 WorkItem->Function = SetDone; 336 KeInitializeEvent(&WaitEvent, NotificationEvent, FALSE); 337 WorkItem->Parameters.Event.Event = &WaitEvent; 338 339 /* Use the post tick queue */ 340 OldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock); 341 InsertTailList(&CcPostTickWorkQueue, &WorkItem->WorkQueueLinks); 342 343 /* Inform the lazy writer it will have to handle the post tick queue */ 344 LazyWriter.OtherWork = TRUE; 345 /* And if it's not running, queue a lazy writer run 346 * And start it NOW, we want the response now 347 */ 348 if (!LazyWriter.ScanActive) 349 { 350 CcScheduleLazyWriteScan(TRUE); 351 } 352 353 KeReleaseQueuedSpinLock(LockQueueMasterLock, OldIrql); 354 355 /* And now, wait until lazy writer replies */ 356 return KeWaitForSingleObject(&WaitEvent, Executive, KernelMode, FALSE, NULL); 357 } 358