Source code of Windows XP (NT5)
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.

830 lines
20 KiB

  1. /*++
  2. Copyright (c) 1989 Microsoft Corporation
  3. Module Name:
  4. worker.c
  5. Abstract:
  6. This module implements the LAN Manager server FSP worker thread
  7. function. It also implements routines for managing (i.e., starting
  8. and stopping) worker threads, and balancing load.
  9. Author:
  10. Chuck Lenzmeier (chuckl) 01-Oct-1989
  11. David Treadwell (davidtr)
  12. Environment:
  13. Kernel mode
  14. Revision History:
  15. --*/
  16. #include "precomp.h"
  17. #include "worker.tmh"
  18. #pragma hdrstop
  19. #define BugCheckFileId SRV_FILE_WORKER
  20. //
  21. // Local declarations
  22. //
  23. NTSTATUS
  24. CreateQueueThread (
  25. IN PWORK_QUEUE Queue
  26. );
  27. VOID
  28. InitializeWorkerThread (
  29. IN PWORK_QUEUE WorkQueue,
  30. IN KPRIORITY ThreadPriority
  31. );
  32. VOID
  33. WorkerThread (
  34. IN PWORK_QUEUE WorkQueue
  35. );
  36. #ifdef ALLOC_PRAGMA
  37. #pragma alloc_text( PAGE, SrvCreateWorkerThreads )
  38. #pragma alloc_text( PAGE, CreateQueueThread )
  39. #pragma alloc_text( PAGE, InitializeWorkerThread )
  40. #pragma alloc_text( PAGE, WorkerThread )
  41. #endif
  42. #if 0
  43. NOT PAGEABLE -- SrvQueueWorkToBlockingThread
  44. NOT PAGEABLE -- SrvQueueWorkToFsp
  45. NOT PAGEABLE -- SrvQueueWorkToFspAtSendCompletion
  46. NOT PAGEABLE -- SrvBalanceLoad
  47. #endif
  48. NTSTATUS
  49. SrvCreateWorkerThreads (
  50. VOID
  51. )
  52. /*++
  53. Routine Description:
  54. This function creates the worker threads for the LAN Manager server
  55. FSP.
  56. Arguments:
  57. None.
  58. Return Value:
  59. NTSTATUS - Status of thread creation
  60. --*/
  61. {
  62. NTSTATUS status;
  63. PWORK_QUEUE queue;
  64. PAGED_CODE( );
  65. //
  66. // Create the nonblocking worker threads.
  67. //
  68. for( queue = SrvWorkQueues; queue < eSrvWorkQueues; queue++ ) {
  69. status = CreateQueueThread( queue );
  70. if( !NT_SUCCESS( status ) ) {
  71. return status;
  72. }
  73. }
  74. //
  75. // Create the blocking worker threads
  76. //
  77. return CreateQueueThread( &SrvBlockingWorkQueue );
  78. } // SrvCreateWorkerThreads
  79. NTSTATUS
  80. CreateQueueThread (
  81. IN PWORK_QUEUE Queue
  82. )
  83. /*++
  84. Routine Description:
  85. This function creates a worker thread to service a queue.
  86. NOTE: The scavenger occasionally kills off threads on a queue. If logic
  87. here is modified, you may need to look there too.
  88. Arguments:
  89. Queue - the queue to service
  90. Return Value:
  91. NTSTATUS - Status of thread creation
  92. --*/
  93. {
  94. HANDLE threadHandle;
  95. LARGE_INTEGER interval;
  96. NTSTATUS status;
  97. PAGED_CODE();
  98. //
  99. // Another thread is coming into being. Keep the counts up to date
  100. //
  101. InterlockedIncrement( &Queue->Threads );
  102. InterlockedIncrement( &Queue->AvailableThreads );
  103. status = PsCreateSystemThread(
  104. &threadHandle,
  105. PROCESS_ALL_ACCESS,
  106. NULL,
  107. NtCurrentProcess(),
  108. NULL,
  109. WorkerThread,
  110. Queue
  111. );
  112. if ( !NT_SUCCESS(status) ) {
  113. INTERNAL_ERROR(
  114. ERROR_LEVEL_EXPECTED,
  115. "CreateQueueThread: PsCreateSystemThread for "
  116. "queue %X returned %X",
  117. Queue,
  118. status
  119. );
  120. InterlockedDecrement( &Queue->Threads );
  121. InterlockedDecrement( &Queue->AvailableThreads );
  122. SrvLogServiceFailure( SRV_SVC_PS_CREATE_SYSTEM_THREAD, status );
  123. return status;
  124. }
  125. //
  126. // Close the handle so the thread can die when needed
  127. //
  128. SrvNtClose( threadHandle, FALSE );
  129. //
  130. // If we just created the first queue thread, wait for it
  131. // to store its thread pointer in IrpThread. This pointer is
  132. // stored in all IRPs issued for this queue by the server.
  133. //
  134. while ( Queue->IrpThread == NULL ) {
  135. interval.QuadPart = -1*10*1000*10; // .01 second
  136. KeDelayExecutionThread( KernelMode, FALSE, &interval );
  137. }
  138. return STATUS_SUCCESS;
  139. } // CreateQueueThread
  140. VOID
  141. InitializeWorkerThread (
  142. IN PWORK_QUEUE WorkQueue,
  143. IN KPRIORITY ThreadPriority
  144. )
  145. {
  146. NTSTATUS status;
  147. KPRIORITY basePriority;
  148. PAGED_CODE( );
  149. #if SRVDBG_LOCK
  150. {
  151. //
  152. // Create a special system thread TEB. The size of this TEB is just
  153. // large enough to accommodate the first three user-reserved
  154. // longwords. These three locations are used for lock debugging. If
  155. // the allocation fails, then no lock debugging will be performed
  156. // for this thread.
  157. //
  158. //
  159. PETHREAD Thread = PsGetCurrentThread( );
  160. ULONG TebSize = FIELD_OFFSET( TEB, UserReserved[0] ) + SRV_TEB_USER_SIZE;
  161. Thread->Tcb.Teb = ExAllocatePoolWithTag( NonPagedPool, TebSize, BlockTypeMisc );
  162. if ( Thread->Tcb.Teb != NULL ) {
  163. RtlZeroMemory( Thread->Tcb.Teb, TebSize );
  164. }
  165. }
  166. #endif // SRVDBG_LOCK
  167. //
  168. // Set this thread's priority.
  169. //
  170. basePriority = ThreadPriority;
  171. status = NtSetInformationThread (
  172. NtCurrentThread( ),
  173. ThreadBasePriority,
  174. &basePriority,
  175. sizeof(basePriority)
  176. );
  177. if ( !NT_SUCCESS(status) ) {
  178. INTERNAL_ERROR(
  179. ERROR_LEVEL_UNEXPECTED,
  180. "InitializeWorkerThread: NtSetInformationThread failed: %X\n",
  181. status,
  182. NULL
  183. );
  184. SrvLogServiceFailure( SRV_SVC_NT_SET_INFO_THREAD, status );
  185. }
  186. #if MULTIPROCESSOR
  187. //
  188. // If this is a nonblocking worker thread, set its ideal processor affinity. Setting
  189. // ideal affinity informs ntos that the thread would rather run on its ideal
  190. // processor if reasonable, but if ntos can't schedule it on that processor then it is
  191. // ok to schedule it on a different processor.
  192. //
  193. if( SrvNumberOfProcessors > 1 && WorkQueue >= SrvWorkQueues && WorkQueue < eSrvWorkQueues ) {
  194. KeSetIdealProcessorThread( KeGetCurrentThread(), (CCHAR)(WorkQueue - SrvWorkQueues) );
  195. }
  196. #endif
  197. //
  198. // Disable hard error popups for this thread.
  199. //
  200. IoSetThreadHardErrorMode( FALSE );
  201. return;
  202. } // InitializeWorkerThread
  203. VOID
  204. WorkerThread (
  205. IN PWORK_QUEUE WorkQueue
  206. )
  207. {
  208. PLIST_ENTRY listEntry;
  209. PWORK_CONTEXT workContext;
  210. ULONG timeDifference;
  211. ULONG updateSmbCount = 0;
  212. ULONG updateTime = 0;
  213. ULONG iAmBlockingThread = (WorkQueue == &SrvBlockingWorkQueue);
  214. PLARGE_INTEGER Timeout = NULL;
  215. PAGED_CODE();
  216. //
  217. // If this is the first worker thread, save the thread pointer.
  218. //
  219. if( WorkQueue->IrpThread == NULL ) {
  220. WorkQueue->IrpThread = PsGetCurrentThread( );
  221. }
  222. InitializeWorkerThread( WorkQueue, SrvThreadPriority );
  223. //
  224. // If we are the IrpThread, we don't want to die
  225. //
  226. if( WorkQueue->IrpThread != PsGetCurrentThread( ) ) {
  227. Timeout = &WorkQueue->IdleTimeOut;
  228. }
  229. //
  230. // Loop infinitely dequeueing and processing work items.
  231. //
  232. while ( TRUE ) {
  233. listEntry = KeRemoveQueue(
  234. &WorkQueue->Queue,
  235. WorkQueue->WaitMode,
  236. Timeout
  237. );
  238. if( (ULONG_PTR)listEntry == STATUS_TIMEOUT ) {
  239. //
  240. // We have a non critical thread that hasn't gotten any work for
  241. // awhile. Time to die.
  242. //
  243. InterlockedDecrement( &WorkQueue->AvailableThreads );
  244. InterlockedDecrement( &WorkQueue->Threads );
  245. SrvTerminateWorkerThread( NULL );
  246. }
  247. if( InterlockedDecrement( &WorkQueue->AvailableThreads ) == 0 &&
  248. !SrvFspTransitioning &&
  249. WorkQueue->Threads < WorkQueue->MaxThreads ) {
  250. //
  251. // We are running low on threads for this queue. Spin up
  252. // another one before handling this request
  253. //
  254. CreateQueueThread( WorkQueue );
  255. }
  256. //
  257. // Get the address of the work item.
  258. //
  259. workContext = CONTAINING_RECORD(
  260. listEntry,
  261. WORK_CONTEXT,
  262. ListEntry
  263. );
  264. ASSERT( KeGetCurrentIrql() == 0 );
  265. //
  266. // There is work available. It may be a work contect block or
  267. // an RFCB. (Blocking threads won't get RFCBs.)
  268. //
  269. ASSERT( (GET_BLOCK_TYPE(workContext) == BlockTypeWorkContextInitial) ||
  270. (GET_BLOCK_TYPE(workContext) == BlockTypeWorkContextNormal) ||
  271. (GET_BLOCK_TYPE(workContext) == BlockTypeWorkContextRaw) ||
  272. (GET_BLOCK_TYPE(workContext) == BlockTypeWorkContextSpecial) ||
  273. (GET_BLOCK_TYPE(workContext) == BlockTypeRfcb) );
  274. #if DBG
  275. if ( GET_BLOCK_TYPE( workContext ) == BlockTypeRfcb ) {
  276. ((PRFCB)workContext)->ListEntry.Flink =
  277. ((PRFCB)workContext)->ListEntry.Blink = NULL;
  278. }
  279. #endif
  280. IF_DEBUG(WORKER1) {
  281. KdPrint(( "WorkerThread working on work context %p", workContext ));
  282. }
  283. //
  284. // Make sure we have a resaonable idea of the system time
  285. //
  286. if( ++updateTime == TIME_SMB_INTERVAL ) {
  287. updateTime = 0;
  288. SET_SERVER_TIME( WorkQueue );
  289. }
  290. //
  291. // Update statistics.
  292. //
  293. if ( ++updateSmbCount == STATISTICS_SMB_INTERVAL ) {
  294. updateSmbCount = 0;
  295. GET_SERVER_TIME( WorkQueue, &timeDifference );
  296. timeDifference = timeDifference - workContext->Timestamp;
  297. ++(WorkQueue->stats.WorkItemsQueued.Count);
  298. WorkQueue->stats.WorkItemsQueued.Time.QuadPart += timeDifference;
  299. }
  300. {
  301. //
  302. // Put the workContext out relative to bp so we can find it later if we need
  303. // to debug. The block of memory we're writing to is likely already in cache,
  304. // so this should be relatively cheap.
  305. //
  306. PWORK_CONTEXT volatile savedWorkContext;
  307. savedWorkContext = workContext;
  308. }
  309. //
  310. // Make sure the WorkContext knows if it is on the blocking work queue
  311. //
  312. workContext->UsingBlockingThread = iAmBlockingThread;
  313. //
  314. // Call the restart routine for the work item.
  315. //
  316. IF_SMB_DEBUG( TRACE ) {
  317. KdPrint(( "Blocking %d, Count %d -> %p( %p )\n",
  318. iAmBlockingThread,
  319. workContext->ProcessingCount,
  320. workContext->FspRestartRoutine,
  321. workContext
  322. ));
  323. }
  324. workContext->FspRestartRoutine( workContext );
  325. //
  326. // Make sure we are still at normal level.
  327. //
  328. ASSERT( KeGetCurrentIrql() == 0 );
  329. //
  330. // We're getting ready to be available (i.e. waiting on the queue)
  331. //
  332. InterlockedIncrement( &WorkQueue->AvailableThreads );
  333. }
  334. } // WorkerThread
  335. VOID SRVFASTCALL
  336. SrvQueueWorkToBlockingThread (
  337. IN OUT PWORK_CONTEXT WorkContext
  338. )
  339. /*++
  340. Routine Description:
  341. This routine queues a work item to a blocking thread. These threads
  342. are used to service requests that may block for a long time, so we
  343. don't want to tie up our normal worker threads.
  344. Arguments:
  345. WorkContext - Supplies a pointer to the work context block
  346. representing the work item
  347. Return Value:
  348. None.
  349. --*/
  350. {
  351. //
  352. // Increment the processing count.
  353. //
  354. WorkContext->ProcessingCount++;
  355. //
  356. // Insert the work item at the tail of the blocking work queue.
  357. //
  358. SrvInsertWorkQueueTail(
  359. &SrvBlockingWorkQueue,
  360. (PQUEUEABLE_BLOCK_HEADER)WorkContext
  361. );
  362. return;
  363. } // SrvQueueWorkToBlockingThread
  364. VOID SRVFASTCALL
  365. SrvQueueWorkToFsp (
  366. IN OUT PWORK_CONTEXT WorkContext
  367. )
  368. /*++
  369. Routine Description:
  370. This is the restart routine for work items that are to be queued to
  371. a nonblocking worker thread in the FSP. This function is also
  372. called from elsewhere in the server to transfer work to the FSP.
  373. This function should not be called at dispatch level -- use
  374. SrvQueueWorkToFspAtDpcLevel instead.
  375. Arguments:
  376. WorkContext - Supplies a pointer to the work context block
  377. representing the work item
  378. Return Value:
  379. None.
  380. --*/
  381. {
  382. //
  383. // Increment the processing count.
  384. //
  385. WorkContext->ProcessingCount++;
  386. //
  387. // Insert the work item at the tail of the nonblocking work queue.
  388. //
  389. if( WorkContext->QueueToHead ) {
  390. SrvInsertWorkQueueHead(
  391. WorkContext->CurrentWorkQueue,
  392. (PQUEUEABLE_BLOCK_HEADER)WorkContext
  393. );
  394. } else {
  395. SrvInsertWorkQueueTail(
  396. WorkContext->CurrentWorkQueue,
  397. (PQUEUEABLE_BLOCK_HEADER)WorkContext
  398. );
  399. }
  400. } // SrvQueueWorkToFsp
  401. NTSTATUS
  402. SrvQueueWorkToFspAtSendCompletion (
  403. IN PDEVICE_OBJECT DeviceObject,
  404. IN PIRP Irp,
  405. IN PWORK_CONTEXT WorkContext
  406. )
  407. /*++
  408. Routine Description:
  409. Send completion handler for work items that are to be queued to
  410. a nonblocking worker thread in the FSP. This function is also
  411. called from elsewhere in the server to transfer work to the FSP.
  412. This function should not be called at dispatch level -- use
  413. SrvQueueWorkToFspAtDpcLevel instead.
  414. Arguments:
  415. DeviceObject - Pointer to target device object for the request.
  416. Irp - Pointer to I/O request packet
  417. WorkContext - Caller-specified context parameter associated with IRP.
  418. This is actually a pointer to a Work Context block.
  419. Return Value:
  420. STATUS_MORE_PROCESSING_REQUIRED.
  421. --*/
  422. {
  423. //
  424. // Check the status of the send completion.
  425. //
  426. CHECK_SEND_COMPLETION_STATUS( Irp->IoStatus.Status );
  427. //
  428. // Reset the IRP cancelled bit.
  429. //
  430. Irp->Cancel = FALSE;
  431. //
  432. // Increment the processing count.
  433. //
  434. WorkContext->ProcessingCount++;
  435. //
  436. // Insert the work item on the nonblocking work queue.
  437. //
  438. if( WorkContext->QueueToHead ) {
  439. SrvInsertWorkQueueHead(
  440. WorkContext->CurrentWorkQueue,
  441. (PQUEUEABLE_BLOCK_HEADER)WorkContext
  442. );
  443. } else {
  444. SrvInsertWorkQueueTail(
  445. WorkContext->CurrentWorkQueue,
  446. (PQUEUEABLE_BLOCK_HEADER)WorkContext
  447. );
  448. }
  449. return STATUS_MORE_PROCESSING_REQUIRED;
  450. } // SrvQueueWorkToFspAtSendCompletion
  451. VOID SRVFASTCALL
  452. SrvTerminateWorkerThread (
  453. IN OUT PWORK_CONTEXT WorkItem OPTIONAL
  454. )
  455. /*++
  456. Routine Description:
  457. This routine is called when a thread is being requested to terminate. There
  458. are two cases when this happens. One is at server shutdown -- in this
  459. case we need to keep requeueing the termination request until all the
  460. threads on the queue have terminated. The other time is if a thread has
  461. not received work for some amount of time.
  462. --*/
  463. {
  464. LONG priority = 16;
  465. //
  466. // Raise our priority to ensure that this thread has a chance to get completely
  467. // done before the main thread causes the driver to unload or something
  468. //
  469. NtSetInformationThread (
  470. NtCurrentThread( ),
  471. ThreadBasePriority,
  472. &priority,
  473. sizeof(priority)
  474. );
  475. if( ARGUMENT_PRESENT( WorkItem ) &&
  476. InterlockedDecrement( &WorkItem->CurrentWorkQueue->Threads ) != 0 ) {
  477. //
  478. // We are being asked to terminate all of the worker threads on this queue.
  479. // So, if we're not the last thread, we should requeue the workitem so
  480. // the other threads will terminate
  481. //
  482. //
  483. // There are still other threads servicing this queue, so requeue
  484. // the workitem
  485. //
  486. SrvInsertWorkQueueTail( WorkItem->CurrentWorkQueue,
  487. (PQUEUEABLE_BLOCK_HEADER)WorkItem );
  488. }
  489. PsTerminateSystemThread( STATUS_SUCCESS ); // no return;
  490. }
  491. #if MULTIPROCESSOR
  492. VOID
  493. SrvBalanceLoad(
  494. IN PCONNECTION connection
  495. )
  496. /*++
  497. Routine Description:
  498. Ensure that the processor handling 'connection' is the best one
  499. for the job. This routine is called periodically per connection from
  500. DPC level. It can not be paged.
  501. Arguments:
  502. connection - the connection to inspect
  503. Return Value:
  504. none.
  505. --*/
  506. {
  507. ULONG MyQueueLength, OtherQueueLength;
  508. ULONG i;
  509. PWORK_QUEUE tmpqueue;
  510. PWORK_QUEUE queue = connection->CurrentWorkQueue;
  511. ASSERT( queue >= SrvWorkQueues );
  512. ASSERT( queue < eSrvWorkQueues );
  513. //
  514. // Reset the countdown. After the client performs BalanceCount
  515. // more operations, we'll call this routine again.
  516. //
  517. connection->BalanceCount = SrvBalanceCount;
  518. //
  519. // Figure out the load on the current work queue. The load is
  520. // the sum of the average work queue depth and the current work
  521. // queue depth. This gives us some history mixed in with the
  522. // load *right now*
  523. //
  524. MyQueueLength = queue->AvgQueueDepthSum >> LOG2_QUEUE_SAMPLES;
  525. MyQueueLength += KeReadStateQueue( &queue->Queue );
  526. //
  527. // If we are not on our preferred queue, look to see if we want to
  528. // go back to it. The preferred queue is the queue for the processor
  529. // handling this client's network card DPCs. We prefer to run on that
  530. // processor to avoid sloshing data between CPUs in an MP system.
  531. //
  532. tmpqueue = connection->PreferredWorkQueue;
  533. ASSERT( tmpqueue >= SrvWorkQueues );
  534. ASSERT( tmpqueue < eSrvWorkQueues );
  535. if( tmpqueue != queue ) {
  536. //
  537. // We are not queueing to our preferred queue. See if we
  538. // should go back to our preferred queue
  539. //
  540. ULONG PreferredQueueLength;
  541. PreferredQueueLength = tmpqueue->AvgQueueDepthSum >> LOG2_QUEUE_SAMPLES;
  542. PreferredQueueLength += KeReadStateQueue( &tmpqueue->Queue );
  543. if( PreferredQueueLength <= MyQueueLength + SrvPreferredAffinity ) {
  544. //
  545. // We want to switch back to our preferred processor!
  546. //
  547. IF_DEBUG( REBALANCE ) {
  548. KdPrint(( "%p C%d(%p) > P%p(%d)\n",
  549. connection,
  550. MyQueueLength,
  551. (PVOID)(connection->CurrentWorkQueue - SrvWorkQueues),
  552. (PVOID)(tmpqueue - SrvWorkQueues),
  553. PreferredQueueLength ));
  554. }
  555. InterlockedDecrement( &queue->CurrentClients );
  556. InterlockedExchangePointer( &connection->CurrentWorkQueue, tmpqueue );
  557. InterlockedIncrement( &tmpqueue->CurrentClients );
  558. SrvReBalanced++;
  559. return;
  560. }
  561. }
  562. //
  563. // We didn't hop to the preferred processor, so let's see if
  564. // another processor looks more lightly loaded than we are.
  565. //
  566. //
  567. // SrvNextBalanceProcessor is the next processor we should consider
  568. // moving to. It is a global to ensure everybody doesn't pick the
  569. // the same processor as the next candidate.
  570. //
  571. tmpqueue = &SrvWorkQueues[ SrvNextBalanceProcessor ];
  572. //
  573. // Advance SrvNextBalanceProcessor to the next processor in the system
  574. //
  575. i = SrvNextBalanceProcessor + 1;
  576. if( i >= SrvNumberOfProcessors )
  577. i = 0;
  578. SrvNextBalanceProcessor = i;
  579. //
  580. // Look at the other processors, and pick the next one which is doing
  581. // enough less work than we are to make the jump worthwhile
  582. //
  583. for( i = SrvNumberOfProcessors; i > 1; --i ) {
  584. ASSERT( tmpqueue >= SrvWorkQueues );
  585. ASSERT( tmpqueue < eSrvWorkQueues );
  586. OtherQueueLength = tmpqueue->AvgQueueDepthSum >> LOG2_QUEUE_SAMPLES;
  587. OtherQueueLength += KeReadStateQueue( &tmpqueue->Queue );
  588. if( OtherQueueLength + SrvOtherQueueAffinity < MyQueueLength ) {
  589. //
  590. // This processor looks promising. Switch to it
  591. //
  592. IF_DEBUG( REBALANCE ) {
  593. KdPrint(( "%p %c%p(%d) > %c%p(%d)\n",
  594. connection,
  595. queue == connection->PreferredWorkQueue ? 'P' : 'C',
  596. (PVOID)(queue - SrvWorkQueues),
  597. MyQueueLength,
  598. tmpqueue == connection->PreferredWorkQueue ? 'P' : 'C',
  599. (PVOID)(tmpqueue - SrvWorkQueues),
  600. OtherQueueLength ));
  601. }
  602. InterlockedDecrement( &queue->CurrentClients );
  603. InterlockedExchangePointer( &connection->CurrentWorkQueue, tmpqueue );
  604. InterlockedIncrement( &tmpqueue->CurrentClients );
  605. SrvReBalanced++;
  606. return;
  607. }
  608. if( ++tmpqueue == eSrvWorkQueues )
  609. tmpqueue = SrvWorkQueues;
  610. }
  611. //
  612. // No rebalancing necessary
  613. //
  614. return;
  615. }
  616. #endif