Windows NT 4.0 source code leak
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

732 lines
19 KiB

4 years ago
  1. /*++
  2. Copyright (c) 1990 Microsoft Corporation
  3. Module Name:
  4. lazyrite.c
  5. Abstract:
  6. This module implements the lazy writer for the Cache subsystem.
  7. Author:
  8. Tom Miller [TomM] 22-July-1990
  9. Revision History:
  10. --*/
  11. #include "cc.h"
  12. //
  13. // The Bug check file id for this module
  14. //
  15. #define BugCheckFileId (CACHE_BUG_CHECK_LAZYRITE)
  16. //
  17. // Define our debug constant
  18. //
  19. #define me 0x00000020
  20. //
  21. // Local support routines
  22. //
  23. PWORK_QUEUE_ENTRY
  24. CcReadWorkQueue (
  25. );
  26. VOID
  27. CcLazyWriteScan (
  28. );
  29. VOID
  30. CcScheduleLazyWriteScan (
  31. )
  32. /*++
  33. Routine Description:
  34. This routine may be called to schedule the next lazy writer scan,
  35. during which lazy write and lazy close activity is posted to other
  36. worker threads. Callers should acquire the lazy writer spin lock
  37. to see if the scan is currently active, and then call this routine
  38. still holding the spin lock if not. One special call is used at
  39. the end of the lazy write scan to propagate lazy write active once
  40. we go active. This call is "the" scan thread, and it can therefore
  41. safely schedule the next scan without taking out the spin lock.
  42. Arguments:
  43. None
  44. Return Value:
  45. None.
  46. --*/
  47. {
  48. //
  49. // It is important to set the active flag TRUE first for the propagate
  50. // case, because it is conceivable that once the timer is set, another
  51. // thread could actually run and make the scan go idle before we then
  52. // jam the flag TRUE.
  53. //
  54. // When going from idle to active, we delay a little longer to let the
  55. // app finish saving its file.
  56. //
  57. if (LazyWriter.ScanActive) {
  58. KeSetTimer( &LazyWriter.ScanTimer, CcIdleDelay, &LazyWriter.ScanDpc );
  59. } else {
  60. LazyWriter.ScanActive = TRUE;
  61. KeSetTimer( &LazyWriter.ScanTimer, CcFirstDelay, &LazyWriter.ScanDpc );
  62. }
  63. }
  64. VOID
  65. CcScanDpc (
  66. IN PKDPC Dpc,
  67. IN PVOID DeferredContext,
  68. IN PVOID SystemArgument1,
  69. IN PVOID SystemArgument2
  70. )
  71. /*++
  72. Routine Description:
  73. This is the Dpc routine which runs when the scan timer goes off. It
  74. simply posts an element for an Ex Worker thread to do the scan.
  75. Arguments:
  76. (All are ignored)
  77. Return Value:
  78. None.
  79. --*/
  80. {
  81. PWORK_QUEUE_ENTRY WorkQueueEntry;
  82. UNREFERENCED_PARAMETER(Dpc);
  83. UNREFERENCED_PARAMETER(DeferredContext);
  84. UNREFERENCED_PARAMETER(SystemArgument1);
  85. UNREFERENCED_PARAMETER(SystemArgument2);
  86. WorkQueueEntry = CcAllocateWorkQueueEntry();
  87. //
  88. // If we failed to allocate a WorkQueueEntry, things must
  89. // be in pretty bad shape. However, all we have to do is
  90. // say we are not active, and wait for another event to
  91. // wake things up again.
  92. //
  93. if (WorkQueueEntry == NULL) {
  94. LazyWriter.ScanActive = FALSE;
  95. } else {
  96. //
  97. // Otherwise post a work queue entry to do the scan.
  98. //
  99. WorkQueueEntry->Function = (UCHAR)LazyWriteScan;
  100. CcPostWorkQueue( WorkQueueEntry, &CcRegularWorkQueue );
  101. }
  102. }
  103. VOID
  104. CcLazyWriteScan (
  105. )
  106. /*++
  107. Routine Description:
  108. This routine implements the Lazy Writer scan for dirty data to flush
  109. or any other work to do (lazy close). This routine is scheduled by
  110. calling CcScheduleLazyWriteScan.
  111. Arguments:
  112. None.
  113. Return Value:
  114. None.
  115. --*/
  116. {
  117. ULONG PagesToWrite, ForegroundRate, EstimatedDirtyNextInterval;
  118. PSHARED_CACHE_MAP SharedCacheMap, FirstVisited;
  119. KIRQL OldIrql;
  120. ULONG LoopsWithLockHeld = 0;
  121. BOOLEAN AlreadyMoved = FALSE;
  122. //
  123. // Top of Lazy Writer scan.
  124. //
  125. try {
  126. //
  127. // If there is no work to do, then we will go inactive, and return.
  128. //
  129. ExAcquireSpinLock( &CcMasterSpinLock, &OldIrql );
  130. if ((CcTotalDirtyPages == 0) && !LazyWriter.OtherWork) {
  131. LazyWriter.ScanActive = FALSE;
  132. ExReleaseSpinLock( &CcMasterSpinLock, OldIrql );
  133. return;
  134. }
  135. //
  136. // Acquire the Lazy Writer spinlock, calculate the next sweep time
  137. // stamp, then update all relevant fields for the next time around.
  138. // Also we can clear the OtherWork flag.
  139. //
  140. LazyWriter.OtherWork = FALSE;
  141. //
  142. // Assume we will write our usual fraction of dirty pages. Do not do the
  143. // divide if there is not enough dirty pages, or else we will never write
  144. // the last few pages.
  145. //
  146. PagesToWrite = CcTotalDirtyPages;
  147. if (PagesToWrite > LAZY_WRITER_MAX_AGE_TARGET) {
  148. PagesToWrite /= LAZY_WRITER_MAX_AGE_TARGET;
  149. }
  150. //
  151. // Estimate the rate of dirty pages being produced in the foreground.
  152. // This is the total number of dirty pages now plus the number of dirty
  153. // pages we scheduled to write last time, minus the number of dirty
  154. // pages we have now. Throw out any cases which would not produce a
  155. // positive rate.
  156. //
  157. ForegroundRate = 0;
  158. if ((CcTotalDirtyPages + CcPagesWrittenLastTime) > CcDirtyPagesLastScan) {
  159. ForegroundRate = (CcTotalDirtyPages + CcPagesWrittenLastTime) -
  160. CcDirtyPagesLastScan;
  161. }
  162. //
  163. // If we estimate that we will exceed our dirty page target by the end
  164. // of this interval, then we must write more. Try to arrive on target.
  165. //
  166. EstimatedDirtyNextInterval = CcTotalDirtyPages - PagesToWrite + ForegroundRate;
  167. if (EstimatedDirtyNextInterval > CcDirtyPageTarget) {
  168. PagesToWrite += EstimatedDirtyNextInterval - CcDirtyPageTarget;
  169. }
  170. //
  171. // Now save away the number of dirty pages and the number of pages we
  172. // just calculated to write.
  173. //
  174. CcDirtyPagesLastScan = CcTotalDirtyPages;
  175. CcPagesYetToWrite = CcPagesWrittenLastTime = PagesToWrite;
  176. //
  177. // Loop to flush enough Shared Cache Maps to write the number of pages
  178. // we just calculated.
  179. //
  180. SharedCacheMap = CONTAINING_RECORD( CcLazyWriterCursor.SharedCacheMapLinks.Flink,
  181. SHARED_CACHE_MAP,
  182. SharedCacheMapLinks );
  183. DebugTrace( 0, me, "Start of Lazy Writer Scan\n", 0 );
  184. //
  185. // Normally we would just like to visit every Cache Map once on each scan,
  186. // so the scan will terminate normally when we return to FirstVisited. But
  187. // in the off chance that FirstVisited gets deleted, we are guaranteed to stop
  188. // when we get back to our own listhead.
  189. //
  190. FirstVisited = NULL;
  191. while ((SharedCacheMap != FirstVisited) &&
  192. (&SharedCacheMap->SharedCacheMapLinks != &CcLazyWriterCursor.SharedCacheMapLinks)) {
  193. if (FirstVisited == NULL) {
  194. FirstVisited = SharedCacheMap;
  195. }
  196. //
  197. // Skip the SharedCacheMap if a write behind request is
  198. // already queued, write behind has been disabled, or
  199. // if there is no work to do (either dirty data to be written
  200. // or a delete is required).
  201. //
  202. // Note that for streams where modified writing is disabled, we
  203. // need to take out Bcbs exclusive, which serializes with foreground
  204. // activity. Therefore we use a special counter in the SharedCacheMap
  205. // to only service these once every n intervals.
  206. //
  207. // Skip temporary files unless we currently could not write 196KB
  208. //
  209. if (!FlagOn(SharedCacheMap->Flags, WRITE_QUEUED | IS_CURSOR)
  210. &&
  211. (((PagesToWrite != 0) && (SharedCacheMap->DirtyPages != 0) &&
  212. (((++SharedCacheMap->LazyWritePassCount & 0xF) == 0) ||
  213. !FlagOn(SharedCacheMap->Flags, MODIFIED_WRITE_DISABLED) ||
  214. (CcCapturedSystemSize == MmSmallSystem) ||
  215. (SharedCacheMap->DirtyPages >= (4 * (MAX_WRITE_BEHIND / PAGE_SIZE)))) &&
  216. (!FlagOn(SharedCacheMap->FileObject->Flags, FO_TEMPORARY_FILE) ||
  217. !CcCanIWrite(SharedCacheMap->FileObject, 0x30000, FALSE, MAXUCHAR)))
  218. ||
  219. (SharedCacheMap->OpenCount == 0))) {
  220. PWORK_QUEUE_ENTRY WorkQueueEntry;
  221. //
  222. // If this is a metadata stream with at least 4 times
  223. // the maximum write behind I/O size, then let's tell
  224. // this guy to write 1/8 of his dirty data on this pass
  225. // so it doesn't build up.
  226. //
  227. // Else assume we can write everything (PagesToWrite only affects
  228. // metadata streams - otherwise writing is controlled by the Mbcb).
  229. //
  230. SharedCacheMap->PagesToWrite = SharedCacheMap->DirtyPages;
  231. if (FlagOn(SharedCacheMap->Flags, MODIFIED_WRITE_DISABLED) &&
  232. (SharedCacheMap->PagesToWrite >= (4 * (MAX_WRITE_BEHIND / PAGE_SIZE))) &&
  233. (CcCapturedSystemSize != MmSmallSystem)) {
  234. SharedCacheMap->PagesToWrite /= 8;
  235. }
  236. //
  237. // See if he exhausts the number of pages to write. (We
  238. // keep going in case there are any closes to do.)
  239. //
  240. if ((SharedCacheMap->PagesToWrite >= PagesToWrite) && !AlreadyMoved) {
  241. //
  242. // If we met our write quota on a given SharedCacheMap, then make sure
  243. // we start at him on the next scan, unless it is a metadata stream.
  244. //
  245. RemoveEntryList( &CcLazyWriterCursor.SharedCacheMapLinks );
  246. //
  247. // For Metadata streams, set up to resume on the next stream on the
  248. // next scan.
  249. //
  250. if (FlagOn(SharedCacheMap->Flags, MODIFIED_WRITE_DISABLED)) {
  251. InsertHeadList( &SharedCacheMap->SharedCacheMapLinks, &CcLazyWriterCursor.SharedCacheMapLinks );
  252. //
  253. // For other streams, set up to resume on the same stream on the
  254. // next scan.
  255. //
  256. } else {
  257. InsertTailList( &SharedCacheMap->SharedCacheMapLinks, &CcLazyWriterCursor.SharedCacheMapLinks );
  258. }
  259. PagesToWrite = 0;
  260. AlreadyMoved = TRUE;
  261. } else {
  262. PagesToWrite -= SharedCacheMap->PagesToWrite;
  263. }
  264. //
  265. // Otherwise show we are actively writing, and keep it in the dirty
  266. // list.
  267. //
  268. SetFlag(SharedCacheMap->Flags, WRITE_QUEUED);
  269. SharedCacheMap->DirtyPages += 1;
  270. ExReleaseSpinLock( &CcMasterSpinLock, OldIrql );
  271. //
  272. // Queue the request to do the work to a worker thread.
  273. //
  274. WorkQueueEntry = CcAllocateWorkQueueEntry();
  275. //
  276. // If we failed to allocate a WorkQueueEntry, things must
  277. // be in pretty bad shape. However, all we have to do is
  278. // break out of our current loop, and try to go back and
  279. // delay a while. Even if the current guy should have gone
  280. // away when we clear WRITE_QUEUED, we will find him again
  281. // in the LW scan.
  282. //
  283. if (WorkQueueEntry == NULL) {
  284. ExAcquireSpinLock( &CcMasterSpinLock, &OldIrql );
  285. ClearFlag(SharedCacheMap->Flags, WRITE_QUEUED);
  286. SharedCacheMap->DirtyPages -= 1;
  287. break;
  288. }
  289. WorkQueueEntry->Function = (UCHAR)WriteBehind;
  290. WorkQueueEntry->Parameters.Write.SharedCacheMap = SharedCacheMap;
  291. //
  292. // Post it to the regular work queue.
  293. //
  294. ExAcquireSpinLock( &CcMasterSpinLock, &OldIrql );
  295. SharedCacheMap->DirtyPages -= 1;
  296. CcPostWorkQueue( WorkQueueEntry, &CcRegularWorkQueue );
  297. LoopsWithLockHeld = 0;
  298. //
  299. // Make sure we occassionally drop the lock. Set WRITE_QUEUED
  300. // to keep the guy from going away.
  301. //
  302. } else if ((++LoopsWithLockHeld >= 20) &&
  303. !FlagOn(SharedCacheMap->Flags, WRITE_QUEUED | IS_CURSOR)) {
  304. SetFlag(SharedCacheMap->Flags, WRITE_QUEUED);
  305. SharedCacheMap->DirtyPages += 1;
  306. ExReleaseSpinLock( &CcMasterSpinLock, OldIrql );
  307. LoopsWithLockHeld = 0;
  308. ExAcquireSpinLock( &CcMasterSpinLock, &OldIrql );
  309. ClearFlag(SharedCacheMap->Flags, WRITE_QUEUED);
  310. SharedCacheMap->DirtyPages -= 1;
  311. }
  312. //
  313. // Now loop back.
  314. //
  315. SharedCacheMap =
  316. CONTAINING_RECORD( SharedCacheMap->SharedCacheMapLinks.Flink,
  317. SHARED_CACHE_MAP,
  318. SharedCacheMapLinks );
  319. }
  320. DebugTrace( 0, me, "End of Lazy Writer Scan\n", 0 );
  321. //
  322. // Now we can release the global list and loop back, per chance to sleep.
  323. //
  324. ExReleaseSpinLock( &CcMasterSpinLock, OldIrql );
  325. //
  326. // Now go ahead and schedule the next scan.
  327. //
  328. CcScheduleLazyWriteScan();
  329. //
  330. // Basically, the Lazy Writer thread should never get an exception,
  331. // so we put a try-except around it that bug checks one way or the other.
  332. // Better we bug check here than worry about what happens if we let one
  333. // get by.
  334. //
  335. } except( CcExceptionFilter( GetExceptionCode() )) {
  336. CcBugCheck( GetExceptionCode(), 0, 0 );
  337. }
  338. }
  339. //
  340. // Internal support routine
  341. //
  342. LONG
  343. CcExceptionFilter (
  344. IN NTSTATUS ExceptionCode
  345. )
  346. /*++
  347. Routine Description:
  348. This is the standard exception filter for worker threads which simply
  349. calls an FsRtl routine to see if an expected status is being raised.
  350. If so, the exception is handled, else we bug check.
  351. Arguments:
  352. ExceptionCode - the exception code which was raised.
  353. Return Value:
  354. EXCEPTION_EXECUTE_HANDLER if expected, else a Bug Check occurs.
  355. --*/
  356. {
  357. DebugTrace(0, 0, "CcExceptionFilter %08lx\n", ExceptionCode);
  358. // DbgBreakPoint();
  359. if (FsRtlIsNtstatusExpected( ExceptionCode )) {
  360. return EXCEPTION_EXECUTE_HANDLER;
  361. } else {
  362. return EXCEPTION_CONTINUE_SEARCH;
  363. }
  364. }
  365. //
  366. // Internal support routine
  367. //
  368. VOID
  369. FASTCALL
  370. CcPostWorkQueue (
  371. IN PWORK_QUEUE_ENTRY WorkQueueEntry,
  372. IN PLIST_ENTRY WorkQueue
  373. )
  374. /*++
  375. Routine Description:
  376. This routine queues a WorkQueueEntry, which has been allocated and
  377. initialized by the caller, to the WorkQueue for FIFO processing by
  378. the work threads.
  379. Arguments:
  380. WorkQueueEntry - supplies a pointer to the entry to queue
  381. Return Value:
  382. None
  383. --*/
  384. {
  385. KIRQL OldIrql;
  386. PLIST_ENTRY WorkerThreadEntry = NULL;
  387. ASSERT(FIELD_OFFSET(WORK_QUEUE_ITEM, List) == 0);
  388. DebugTrace(+1, me, "CcPostWorkQueue:\n", 0 );
  389. DebugTrace( 0, me, " WorkQueueEntry = %08lx\n", WorkQueueEntry );
  390. //
  391. // Queue the entry to the respective work queue.
  392. //
  393. ExAcquireFastLock( &CcWorkQueueSpinlock, &OldIrql );
  394. InsertTailList( WorkQueue, &WorkQueueEntry->WorkQueueLinks );
  395. //
  396. // Now, if we have any more idle threads we can use, then activate
  397. // one.
  398. //
  399. if (!IsListEmpty(&CcIdleWorkerThreadList)) {
  400. WorkerThreadEntry = RemoveHeadList( &CcIdleWorkerThreadList );
  401. }
  402. ExReleaseFastLock( &CcWorkQueueSpinlock, OldIrql );
  403. if (WorkerThreadEntry != NULL) {
  404. //
  405. // I had to peak in the sources to verify that this routine
  406. // is a noop if the Flink is not NULL. Sheeeeit!
  407. //
  408. ((PWORK_QUEUE_ITEM)WorkerThreadEntry)->List.Flink = NULL;
  409. ExQueueWorkItem( (PWORK_QUEUE_ITEM)WorkerThreadEntry, CriticalWorkQueue );
  410. }
  411. //
  412. // And return to our caller
  413. //
  414. DebugTrace(-1, me, "CcPostWorkQueue -> VOID\n", 0 );
  415. return;
  416. }
  417. //
  418. // Internal support routine
  419. //
  420. VOID
  421. CcWorkerThread (
  422. PVOID ExWorkQueueItem
  423. )
  424. /*++
  425. Routine Description:
  426. This is worker thread routine for processing cache manager work queue
  427. entries.
  428. Arguments:
  429. ExWorkQueueItem - The work item used for this thread
  430. Return Value:
  431. None
  432. --*/
  433. {
  434. KIRQL OldIrql;
  435. PWORK_QUEUE_ENTRY WorkQueueEntry;
  436. BOOLEAN RescanOk = FALSE;
  437. ASSERT(FIELD_OFFSET(WORK_QUEUE_ENTRY, WorkQueueLinks) == 0);
  438. while (TRUE) {
  439. ExAcquireFastLock( &CcWorkQueueSpinlock, &OldIrql );
  440. //
  441. // First see if there is something in the express queue.
  442. //
  443. if (!IsListEmpty(&CcExpressWorkQueue)) {
  444. WorkQueueEntry = (PWORK_QUEUE_ENTRY)RemoveHeadList( &CcExpressWorkQueue );
  445. //
  446. // If there was nothing there, then try the regular queue.
  447. //
  448. } else if (!IsListEmpty(&CcRegularWorkQueue)) {
  449. WorkQueueEntry = (PWORK_QUEUE_ENTRY)RemoveHeadList( &CcRegularWorkQueue );
  450. //
  451. // Else we can break and go idle.
  452. //
  453. } else {
  454. break;
  455. }
  456. ExReleaseFastLock( &CcWorkQueueSpinlock, OldIrql );
  457. //
  458. // Process the entry within a try-except clause, so that any errors
  459. // will cause us to continue after the called routine has unwound.
  460. //
  461. try {
  462. switch (WorkQueueEntry->Function) {
  463. //
  464. // A read ahead or write behind request has been nooped (but
  465. // left in the queue to keep the semaphore count right).
  466. //
  467. case Noop:
  468. break;
  469. //
  470. // Perform read ahead
  471. //
  472. case ReadAhead:
  473. DebugTrace( 0, me, "CcWorkerThread Read Ahead FileObject = %08lx\n",
  474. WorkQueueEntry->Parameters.Read.FileObject );
  475. CcPerformReadAhead( WorkQueueEntry->Parameters.Read.FileObject );
  476. break;
  477. //
  478. // Perform write behind
  479. //
  480. case WriteBehind:
  481. DebugTrace( 0, me, "CcWorkerThread WriteBehind SharedCacheMap = %08lx\n",
  482. WorkQueueEntry->Parameters.Write.SharedCacheMap );
  483. RescanOk = (BOOLEAN)NT_SUCCESS(CcWriteBehind( WorkQueueEntry->Parameters.Write.SharedCacheMap ));
  484. break;
  485. //
  486. // Perform Lazy Write Scan
  487. //
  488. case LazyWriteScan:
  489. DebugTrace( 0, me, "CcWorkerThread Lazy Write Scan\n", 0 );
  490. CcLazyWriteScan();
  491. break;
  492. }
  493. }
  494. except( CcExceptionFilter( GetExceptionCode() )) {
  495. NOTHING;
  496. }
  497. CcFreeWorkQueueEntry( WorkQueueEntry );
  498. }
  499. //
  500. // No more work. Requeue our worker thread entry and get out.
  501. //
  502. InsertTailList( &CcIdleWorkerThreadList,
  503. &((PWORK_QUEUE_ITEM)ExWorkQueueItem)->List );
  504. ExReleaseFastLock( &CcWorkQueueSpinlock, OldIrql );
  505. if (!IsListEmpty(&CcDeferredWrites) && (CcTotalDirtyPages >= 20) && RescanOk) {
  506. CcLazyWriteScan();
  507. }
  508. return;
  509. }