1 /* 2 * COPYRIGHT: See COPYING in the top level directory 3 * PROJECT: ReactOS Kernel 4 * FILE: ntoskrnl/ex/work.c 5 * PURPOSE: Manage system work queues and worker threads 6 * PROGRAMMER: Alex Ionescu (alex@relsoft.net) 7 */ 8 9 /* INCLUDES ******************************************************************/ 10 11 #include <ntoskrnl.h> 12 #define NDEBUG 13 #include <debug.h> 14 15 /* DATA **********************************************************************/ 16 17 /* Number of worker threads for each Queue */ 18 #define EX_HYPERCRITICAL_WORK_THREADS 1 19 #define EX_DELAYED_WORK_THREADS 3 20 #define EX_CRITICAL_WORK_THREADS 5 21 22 /* Magic flag for dynamic worker threads */ 23 #define EX_DYNAMIC_WORK_THREAD 0x80000000 24 25 /* Worker thread priority increments (added to base priority) */ 26 #define EX_HYPERCRITICAL_QUEUE_PRIORITY_INCREMENT 7 27 #define EX_CRITICAL_QUEUE_PRIORITY_INCREMENT 5 28 #define EX_DELAYED_QUEUE_PRIORITY_INCREMENT 4 29 30 /* The actual worker queue array */ 31 EX_WORK_QUEUE ExWorkerQueue[MaximumWorkQueue]; 32 33 /* Accounting of the total threads and registry hacked threads */ 34 ULONG ExCriticalWorkerThreads; 35 ULONG ExDelayedWorkerThreads; 36 ULONG ExpAdditionalCriticalWorkerThreads; 37 ULONG ExpAdditionalDelayedWorkerThreads; 38 39 /* Future support for stack swapping worker threads */ 40 BOOLEAN ExpWorkersCanSwap; 41 LIST_ENTRY ExpWorkerListHead; 42 FAST_MUTEX ExpWorkerSwapinMutex; 43 44 /* The worker balance set manager events */ 45 KEVENT ExpThreadSetManagerEvent; 46 KEVENT ExpThreadSetManagerShutdownEvent; 47 48 /* Thread pointers for future worker thread shutdown support */ 49 PETHREAD ExpWorkerThreadBalanceManagerPtr; 50 PETHREAD ExpLastWorkerThread; 51 52 /* PRIVATE FUNCTIONS *********************************************************/ 53 54 /*++ 55 * @name ExpWorkerThreadEntryPoint 56 * 57 * The ExpWorkerThreadEntryPoint routine is the entrypoint for any new 58 * worker thread created by teh system. 59 * 60 * @param Context 61 * Contains the work queue type masked with a flag specifing whether the 62 * thread is dynamic or not. 63 * 64 * @return None. 65 * 66 * @remarks A dynamic thread can timeout after 10 minutes of waiting on a queue 67 * while a static thread will never timeout. 68 * 69 * Worker threads must return at IRQL == PASSIVE_LEVEL, must not have 70 * active impersonation info, and must not have disabled APCs. 71 * 72 * NB: We will re-enable APCs for broken threads but all other cases 73 * will generate a bugcheck. 74 * 75 *--*/ 76 VOID 77 NTAPI 78 ExpWorkerThreadEntryPoint(IN PVOID Context) 79 { 80 PWORK_QUEUE_ITEM WorkItem; 81 PLIST_ENTRY QueueEntry; 82 WORK_QUEUE_TYPE WorkQueueType; 83 PEX_WORK_QUEUE WorkQueue; 84 LARGE_INTEGER Timeout; 85 PLARGE_INTEGER TimeoutPointer = NULL; 86 PETHREAD Thread = PsGetCurrentThread(); 87 KPROCESSOR_MODE WaitMode; 88 EX_QUEUE_WORKER_INFO OldValue, NewValue; 89 90 /* Check if this is a dyamic thread */ 91 if ((ULONG_PTR)Context & EX_DYNAMIC_WORK_THREAD) 92 { 93 /* It is, which means we will eventually time out after 10 minutes */ 94 Timeout.QuadPart = Int32x32To64(10, -10000000 * 60); 95 TimeoutPointer = &Timeout; 96 } 97 98 /* Get Queue Type and Worker Queue */ 99 WorkQueueType = (WORK_QUEUE_TYPE)((ULONG_PTR)Context & 100 ~EX_DYNAMIC_WORK_THREAD); 101 WorkQueue = &ExWorkerQueue[WorkQueueType]; 102 103 /* Select the wait mode */ 104 WaitMode = (UCHAR)WorkQueue->Info.WaitMode; 105 106 /* Nobody should have initialized this yet, do it now */ 107 ASSERT(Thread->ExWorkerCanWaitUser == 0); 108 if (WaitMode == UserMode) Thread->ExWorkerCanWaitUser = TRUE; 109 110 /* If we shouldn't swap, disable that feature */ 111 if (!ExpWorkersCanSwap) KeSetKernelStackSwapEnable(FALSE); 112 113 /* Set the worker flags */ 114 do 115 { 116 /* Check if the queue is being disabled */ 117 if (WorkQueue->Info.QueueDisabled) 118 { 119 /* Re-enable stack swapping and kill us */ 120 KeSetKernelStackSwapEnable(TRUE); 121 PsTerminateSystemThread(STATUS_SYSTEM_SHUTDOWN); 122 } 123 124 /* Increase the worker count */ 125 OldValue = WorkQueue->Info; 126 NewValue = OldValue; 127 NewValue.WorkerCount++; 128 } 129 while (InterlockedCompareExchange((PLONG)&WorkQueue->Info, 130 *(PLONG)&NewValue, 131 *(PLONG)&OldValue) != *(PLONG)&OldValue); 132 133 /* Success, you are now officially a worker thread! */ 134 Thread->ActiveExWorker = TRUE; 135 136 /* Loop forever */ 137 ProcessLoop: 138 for (;;) 139 { 140 /* Wait for something to happen on the queue */ 141 QueueEntry = KeRemoveQueue(&WorkQueue->WorkerQueue, 142 WaitMode, 143 TimeoutPointer); 144 145 /* Check if we timed out and quit this loop in that case */ 146 if ((NTSTATUS)(ULONG_PTR)QueueEntry == STATUS_TIMEOUT) break; 147 148 /* Increment Processed Work Items */ 149 InterlockedIncrement((PLONG)&WorkQueue->WorkItemsProcessed); 150 151 /* Get the Work Item */ 152 WorkItem = CONTAINING_RECORD(QueueEntry, WORK_QUEUE_ITEM, List); 153 154 /* Make sure nobody is trying to play smart with us */ 155 ASSERT((ULONG_PTR)WorkItem->WorkerRoutine > MmUserProbeAddress); 156 157 /* Call the Worker Routine */ 158 WorkItem->WorkerRoutine(WorkItem->Parameter); 159 160 /* Make sure APCs are not disabled */ 161 if (Thread->Tcb.CombinedApcDisable != 0) 162 { 163 /* We're nice and do it behind your back */ 164 DPRINT1("Warning: Broken Worker Thread: %p %p %p came back " 165 "with APCs disabled!\n", 166 WorkItem->WorkerRoutine, 167 WorkItem->Parameter, 168 WorkItem); 169 ASSERT(Thread->Tcb.CombinedApcDisable == 0); 170 Thread->Tcb.CombinedApcDisable = 0; 171 } 172 173 /* Make sure it returned at right IRQL */ 174 if (KeGetCurrentIrql() != PASSIVE_LEVEL) 175 { 176 /* It didn't, bugcheck! */ 177 KeBugCheckEx(WORKER_THREAD_RETURNED_AT_BAD_IRQL, 178 (ULONG_PTR)WorkItem->WorkerRoutine, 179 KeGetCurrentIrql(), 180 (ULONG_PTR)WorkItem->Parameter, 181 (ULONG_PTR)WorkItem); 182 } 183 184 /* Make sure it returned with Impersionation Disabled */ 185 if (Thread->ActiveImpersonationInfo) 186 { 187 /* It didn't, bugcheck! */ 188 KeBugCheckEx(IMPERSONATING_WORKER_THREAD, 189 (ULONG_PTR)WorkItem->WorkerRoutine, 190 (ULONG_PTR)WorkItem->Parameter, 191 (ULONG_PTR)WorkItem, 192 0); 193 } 194 } 195 196 /* This is a dynamic thread. Terminate it unless IRPs are pending */ 197 if (!IsListEmpty(&Thread->IrpList)) goto ProcessLoop; 198 199 /* Don't terminate it if the queue is disabled either */ 200 if (WorkQueue->Info.QueueDisabled) goto ProcessLoop; 201 202 /* Set the worker flags */ 203 do 204 { 205 /* Decrease the worker count */ 206 OldValue = WorkQueue->Info; 207 NewValue = OldValue; 208 NewValue.WorkerCount--; 209 } 210 while (InterlockedCompareExchange((PLONG)&WorkQueue->Info, 211 *(PLONG)&NewValue, 212 *(PLONG)&OldValue) != *(PLONG)&OldValue); 213 214 /* Decrement dynamic thread count */ 215 InterlockedDecrement(&WorkQueue->DynamicThreadCount); 216 217 /* We're not a worker thread anymore */ 218 Thread->ActiveExWorker = FALSE; 219 220 /* Re-enable the stack swap */ 221 KeSetKernelStackSwapEnable(TRUE); 222 return; 223 } 224 225 /*++ 226 * @name ExpCreateWorkerThread 227 * 228 * The ExpCreateWorkerThread routine creates a new worker thread for the 229 * specified queue. 230 * 231 * @param QueueType 232 * Type of the queue to use for this thread. Valid values are: 233 * - DelayedWorkQueue 234 * - CriticalWorkQueue 235 * - HyperCriticalWorkQueue 236 * 237 * @param Dynamic 238 * Specifies whether or not this thread is a dynamic thread. 239 * 240 * @return None. 241 * 242 * @remarks HyperCritical work threads run at priority 7; Critical work threads 243 * run at priority 5, and delayed work threads run at priority 4. 244 * 245 * This, worker threads cannot pre-empty a normal user-mode thread. 246 * 247 *--*/ 248 VOID 249 NTAPI 250 ExpCreateWorkerThread(WORK_QUEUE_TYPE WorkQueueType, 251 IN BOOLEAN Dynamic) 252 { 253 PETHREAD Thread; 254 HANDLE hThread; 255 ULONG Context; 256 KPRIORITY Priority; 257 NTSTATUS Status; 258 259 /* Check if this is going to be a dynamic thread */ 260 Context = WorkQueueType; 261 262 /* Add the dynamic mask */ 263 if (Dynamic) Context |= EX_DYNAMIC_WORK_THREAD; 264 265 /* Create the System Thread */ 266 Status = PsCreateSystemThread(&hThread, 267 THREAD_ALL_ACCESS, 268 NULL, 269 NULL, 270 NULL, 271 ExpWorkerThreadEntryPoint, 272 UlongToPtr(Context)); 273 if (!NT_SUCCESS(Status)) 274 { 275 /* Well... */ 276 DPRINT1("Failed to create worker thread: 0x%08x\n", Status); 277 return; 278 } 279 280 /* If the thread is dynamic */ 281 if (Dynamic) 282 { 283 /* Increase the count */ 284 InterlockedIncrement(&ExWorkerQueue[WorkQueueType].DynamicThreadCount); 285 } 286 287 /* Set the priority */ 288 if (WorkQueueType == DelayedWorkQueue) 289 { 290 /* Priority == 4 */ 291 Priority = EX_DELAYED_QUEUE_PRIORITY_INCREMENT; 292 } 293 else if (WorkQueueType == CriticalWorkQueue) 294 { 295 /* Priority == 5 */ 296 Priority = EX_CRITICAL_QUEUE_PRIORITY_INCREMENT; 297 } 298 else 299 { 300 /* Priority == 7 */ 301 Priority = EX_HYPERCRITICAL_QUEUE_PRIORITY_INCREMENT; 302 } 303 304 /* Get the Thread */ 305 ObReferenceObjectByHandle(hThread, 306 THREAD_SET_INFORMATION, 307 PsThreadType, 308 KernelMode, 309 (PVOID*)&Thread, 310 NULL); 311 312 /* Set the Priority */ 313 KeSetBasePriorityThread(&Thread->Tcb, Priority); 314 315 /* Dereference and close handle */ 316 ObDereferenceObject(Thread); 317 ObCloseHandle(hThread, KernelMode); 318 } 319 320 /*++ 321 * @name ExpDetectWorkerThreadDeadlock 322 * 323 * The ExpDetectWorkerThreadDeadlock routine checks every queue and creates 324 * a dynamic thread if the queue seems to be deadlocked. 325 * 326 * @param None 327 * 328 * @return None. 329 * 330 * @remarks The algorithm for deciding if a new thread must be created is based 331 * on whether the queue has processed no new items in the last second, 332 * and new items are still enqueued. 333 * 334 *--*/ 335 VOID 336 NTAPI 337 ExpDetectWorkerThreadDeadlock(VOID) 338 { 339 ULONG i; 340 PEX_WORK_QUEUE Queue; 341 342 /* Loop the 3 queues */ 343 for (i = 0; i < MaximumWorkQueue; i++) 344 { 345 /* Get the queue */ 346 Queue = &ExWorkerQueue[i]; 347 ASSERT(Queue->DynamicThreadCount <= 16); 348 349 /* Check if stuff is on the queue that still is unprocessed */ 350 if ((Queue->QueueDepthLastPass) && 351 (Queue->WorkItemsProcessed == Queue->WorkItemsProcessedLastPass) && 352 (Queue->DynamicThreadCount < 16)) 353 { 354 /* Stuff is still on the queue and nobody did anything about it */ 355 DPRINT1("EX: Work Queue Deadlock detected: %lu\n", i); 356 ExpCreateWorkerThread(i, TRUE); 357 DPRINT1("Dynamic threads queued %d\n", Queue->DynamicThreadCount); 358 } 359 360 /* Update our data */ 361 Queue->WorkItemsProcessedLastPass = Queue->WorkItemsProcessed; 362 Queue->QueueDepthLastPass = KeReadStateQueue(&Queue->WorkerQueue); 363 } 364 } 365 366 /*++ 367 * @name ExpCheckDynamicThreadCount 368 * 369 * The ExpCheckDynamicThreadCount routine checks every queue and creates 370 * a dynamic thread if the queue requires one. 371 * 372 * @param None 373 * 374 * @return None. 375 * 376 * @remarks The algorithm for deciding if a new thread must be created is 377 * documented in the ExQueueWorkItem routine. 378 * 379 *--*/ 380 VOID 381 NTAPI 382 ExpCheckDynamicThreadCount(VOID) 383 { 384 ULONG i; 385 PEX_WORK_QUEUE Queue; 386 387 /* Loop the 3 queues */ 388 for (i = 0; i < MaximumWorkQueue; i++) 389 { 390 /* Get the queue */ 391 Queue = &ExWorkerQueue[i]; 392 393 /* Check if still need a new thread. See ExQueueWorkItem */ 394 if ((Queue->Info.MakeThreadsAsNecessary) && 395 (!IsListEmpty(&Queue->WorkerQueue.EntryListHead)) && 396 (Queue->WorkerQueue.CurrentCount < 397 Queue->WorkerQueue.MaximumCount) && 398 (Queue->DynamicThreadCount < 16)) 399 { 400 /* Create a new thread */ 401 DPRINT1("EX: Creating new dynamic thread as requested\n"); 402 ExpCreateWorkerThread(i, TRUE); 403 } 404 } 405 } 406 407 /*++ 408 * @name ExpWorkerThreadBalanceManager 409 * 410 * The ExpWorkerThreadBalanceManager routine is the entrypoint for the 411 * worker thread balance set manager. 412 * 413 * @param Context 414 * Unused. 415 * 416 * @return None. 417 * 418 * @remarks The worker thread balance set manager listens every second, but can 419 * also be woken up by an event when a new thread is needed, or by the 420 * special shutdown event. This thread runs at priority 7. 421 * 422 * This routine must run at IRQL == PASSIVE_LEVEL. 423 * 424 *--*/ 425 VOID 426 NTAPI 427 ExpWorkerThreadBalanceManager(IN PVOID Context) 428 { 429 KTIMER Timer; 430 LARGE_INTEGER Timeout; 431 NTSTATUS Status; 432 PVOID WaitEvents[3]; 433 PAGED_CODE(); 434 UNREFERENCED_PARAMETER(Context); 435 436 /* Raise our priority above all other worker threads */ 437 KeSetBasePriorityThread(KeGetCurrentThread(), 438 EX_CRITICAL_QUEUE_PRIORITY_INCREMENT + 1); 439 440 /* Setup the timer */ 441 KeInitializeTimer(&Timer); 442 Timeout.QuadPart = Int32x32To64(-1, 10000000); 443 444 /* We'll wait on the periodic timer and also the emergency event */ 445 WaitEvents[0] = &Timer; 446 WaitEvents[1] = &ExpThreadSetManagerEvent; 447 WaitEvents[2] = &ExpThreadSetManagerShutdownEvent; 448 449 /* Start wait loop */ 450 for (;;) 451 { 452 /* Wait for the timer */ 453 KeSetTimer(&Timer, Timeout, NULL); 454 Status = KeWaitForMultipleObjects(3, 455 WaitEvents, 456 WaitAny, 457 Executive, 458 KernelMode, 459 FALSE, 460 NULL, 461 NULL); 462 if (Status == 0) 463 { 464 /* Our timer expired. Check for deadlocks */ 465 ExpDetectWorkerThreadDeadlock(); 466 } 467 else if (Status == 1) 468 { 469 /* Someone notified us, verify if we should create a new thread */ 470 ExpCheckDynamicThreadCount(); 471 } 472 else if (Status == 2) 473 { 474 /* We are shutting down. Cancel the timer */ 475 DPRINT1("System shutdown\n"); 476 KeCancelTimer(&Timer); 477 478 /* Make sure we have a final thread */ 479 ASSERT(ExpLastWorkerThread); 480 481 /* Wait for it */ 482 KeWaitForSingleObject(ExpLastWorkerThread, 483 Executive, 484 KernelMode, 485 FALSE, 486 NULL); 487 488 /* Dereference it and kill us */ 489 ObDereferenceObject(ExpLastWorkerThread); 490 PsTerminateSystemThread(STATUS_SYSTEM_SHUTDOWN); 491 } 492 493 /* 494 * If WinDBG wants to attach or kill a user-mode process, and/or 495 * page-in an address region, queue a debugger worker thread. 496 */ 497 if (ExpDebuggerWork == WinKdWorkerStart) 498 { 499 ExInitializeWorkItem(&ExpDebuggerWorkItem, ExpDebuggerWorker, NULL); 500 ExpDebuggerWork = WinKdWorkerInitialized; 501 ExQueueWorkItem(&ExpDebuggerWorkItem, DelayedWorkQueue); 502 } 503 } 504 } 505 506 /*++ 507 * @name ExpInitializeWorkerThreads 508 * 509 * The ExpInitializeWorkerThreads routine initializes worker thread and 510 * work queue support. 511 * 512 * @param None. 513 * 514 * @return None. 515 * 516 * @remarks This routine is only called once during system initialization. 517 * 518 *--*/ 519 CODE_SEG("INIT") 520 VOID 521 NTAPI 522 ExpInitializeWorkerThreads(VOID) 523 { 524 ULONG WorkQueueType; 525 ULONG CriticalThreads, DelayedThreads; 526 HANDLE ThreadHandle; 527 PETHREAD Thread; 528 ULONG i; 529 NTSTATUS Status; 530 531 /* Setup the stack swap support */ 532 ExInitializeFastMutex(&ExpWorkerSwapinMutex); 533 InitializeListHead(&ExpWorkerListHead); 534 ExpWorkersCanSwap = TRUE; 535 536 /* Set the number of critical and delayed threads. We shouldn't hardcode */ 537 DelayedThreads = EX_DELAYED_WORK_THREADS; 538 CriticalThreads = EX_CRITICAL_WORK_THREADS; 539 540 /* Protect against greedy registry modifications */ 541 ExpAdditionalDelayedWorkerThreads = 542 min(ExpAdditionalDelayedWorkerThreads, 16); 543 ExpAdditionalCriticalWorkerThreads = 544 min(ExpAdditionalCriticalWorkerThreads, 16); 545 546 /* Calculate final count */ 547 DelayedThreads += ExpAdditionalDelayedWorkerThreads; 548 CriticalThreads += ExpAdditionalCriticalWorkerThreads; 549 550 /* Initialize the Array */ 551 for (WorkQueueType = 0; WorkQueueType < MaximumWorkQueue; WorkQueueType++) 552 { 553 /* Clear the structure and initialize the queue */ 554 RtlZeroMemory(&ExWorkerQueue[WorkQueueType], sizeof(EX_WORK_QUEUE)); 555 KeInitializeQueue(&ExWorkerQueue[WorkQueueType].WorkerQueue, 0); 556 } 557 558 /* Dynamic threads are only used for the critical queue */ 559 ExWorkerQueue[CriticalWorkQueue].Info.MakeThreadsAsNecessary = TRUE; 560 561 /* Initialize the balance set manager events */ 562 KeInitializeEvent(&ExpThreadSetManagerEvent, SynchronizationEvent, FALSE); 563 KeInitializeEvent(&ExpThreadSetManagerShutdownEvent, 564 NotificationEvent, 565 FALSE); 566 567 /* Create the built-in worker threads for the critical queue */ 568 for (i = 0; i < CriticalThreads; i++) 569 { 570 /* Create the thread */ 571 ExpCreateWorkerThread(CriticalWorkQueue, FALSE); 572 ExCriticalWorkerThreads++; 573 } 574 575 /* Create the built-in worker threads for the delayed queue */ 576 for (i = 0; i < DelayedThreads; i++) 577 { 578 /* Create the thread */ 579 ExpCreateWorkerThread(DelayedWorkQueue, FALSE); 580 ExDelayedWorkerThreads++; 581 } 582 583 /* Create the built-in worker thread for the hypercritical queue */ 584 ExpCreateWorkerThread(HyperCriticalWorkQueue, FALSE); 585 586 /* Create the balance set manager thread */ 587 Status = PsCreateSystemThread(&ThreadHandle, 588 THREAD_ALL_ACCESS, 589 NULL, 590 0, 591 NULL, 592 ExpWorkerThreadBalanceManager, 593 NULL); 594 if (!NT_SUCCESS(Status)) 595 { 596 KeBugCheckEx(PHASE1_INITIALIZATION_FAILED, Status, 0, 0, 0); 597 } 598 599 /* Get a pointer to it for the shutdown process */ 600 ObReferenceObjectByHandle(ThreadHandle, 601 THREAD_ALL_ACCESS, 602 NULL, 603 KernelMode, 604 (PVOID*)&Thread, 605 NULL); 606 ExpWorkerThreadBalanceManagerPtr = Thread; 607 608 /* Close the handle and return */ 609 ObCloseHandle(ThreadHandle, KernelMode); 610 } 611 612 VOID 613 NTAPI 614 ExpSetSwappingKernelApc(IN PKAPC Apc, 615 OUT PKNORMAL_ROUTINE *NormalRoutine, 616 IN OUT PVOID *NormalContext, 617 IN OUT PVOID *SystemArgument1, 618 IN OUT PVOID *SystemArgument2) 619 { 620 PBOOLEAN AllowSwap; 621 PKEVENT Event = (PKEVENT)*SystemArgument1; 622 623 /* Make sure it's an active worker */ 624 if (PsGetCurrentThread()->ActiveExWorker) 625 { 626 /* Read the setting from the context flag */ 627 AllowSwap = (PBOOLEAN)NormalContext; 628 KeSetKernelStackSwapEnable(*AllowSwap); 629 } 630 631 /* Let caller know that we're done */ 632 KeSetEvent(Event, 0, FALSE); 633 } 634 635 VOID 636 NTAPI 637 ExSwapinWorkerThreads(IN BOOLEAN AllowSwap) 638 { 639 KEVENT Event; 640 PETHREAD CurrentThread = PsGetCurrentThread(), Thread; 641 PEPROCESS Process = PsInitialSystemProcess; 642 KAPC Apc; 643 PAGED_CODE(); 644 645 /* Initialize an event so we know when we're done */ 646 KeInitializeEvent(&Event, NotificationEvent, FALSE); 647 648 /* Lock this routine */ 649 ExAcquireFastMutex(&ExpWorkerSwapinMutex); 650 651 /* New threads cannot swap anymore */ 652 ExpWorkersCanSwap = AllowSwap; 653 654 /* Loop all threads in the system process */ 655 Thread = PsGetNextProcessThread(Process, NULL); 656 while (Thread) 657 { 658 /* Skip threads with explicit permission to do this */ 659 if (Thread->ExWorkerCanWaitUser) goto Next; 660 661 /* Check if we reached ourselves */ 662 if (Thread == CurrentThread) 663 { 664 /* Do it inline */ 665 KeSetKernelStackSwapEnable(AllowSwap); 666 } 667 else 668 { 669 /* Queue an APC */ 670 KeInitializeApc(&Apc, 671 &Thread->Tcb, 672 InsertApcEnvironment, 673 ExpSetSwappingKernelApc, 674 NULL, 675 NULL, 676 KernelMode, 677 &AllowSwap); 678 if (KeInsertQueueApc(&Apc, &Event, NULL, 3)) 679 { 680 /* Wait for the APC to run */ 681 KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL); 682 KeClearEvent(&Event); 683 } 684 } 685 686 /* Next thread */ 687 Next: 688 Thread = PsGetNextProcessThread(Process, Thread); 689 } 690 691 /* Release the lock */ 692 ExReleaseFastMutex(&ExpWorkerSwapinMutex); 693 } 694 695 /* PUBLIC FUNCTIONS **********************************************************/ 696 697 /*++ 698 * @name ExQueueWorkItem 699 * @implemented NT4 700 * 701 * The ExQueueWorkItem routine acquires rundown protection for 702 * the specified descriptor. 703 * 704 * @param WorkItem 705 * Pointer to an initialized Work Queue Item structure. This structure 706 * must be located in nonpaged pool memory. 707 * 708 * @param QueueType 709 * Type of the queue to use for this item. Can be one of the following: 710 * - DelayedWorkQueue 711 * - CriticalWorkQueue 712 * - HyperCriticalWorkQueue 713 * 714 * @return None. 715 * 716 * @remarks This routine is obsolete. Use IoQueueWorkItem instead. 717 * 718 * Callers of this routine must be running at IRQL <= DISPATCH_LEVEL. 719 * 720 *--*/ 721 VOID 722 NTAPI 723 ExQueueWorkItem(IN PWORK_QUEUE_ITEM WorkItem, 724 IN WORK_QUEUE_TYPE QueueType) 725 { 726 PEX_WORK_QUEUE WorkQueue = &ExWorkerQueue[QueueType]; 727 ASSERT(QueueType < MaximumWorkQueue); 728 ASSERT(WorkItem->List.Flink == NULL); 729 730 /* Don't try to trick us */ 731 if ((ULONG_PTR)WorkItem->WorkerRoutine < MmUserProbeAddress) 732 { 733 /* Bugcheck the system */ 734 KeBugCheckEx(WORKER_INVALID, 735 1, 736 (ULONG_PTR)WorkItem, 737 (ULONG_PTR)WorkItem->WorkerRoutine, 738 0); 739 } 740 741 /* Insert the Queue */ 742 KeInsertQueue(&WorkQueue->WorkerQueue, &WorkItem->List); 743 ASSERT(!WorkQueue->Info.QueueDisabled); 744 745 /* 746 * Check if we need a new thread. Our decision is as follows: 747 * - This queue type must support Dynamic Threads (duh!) 748 * - It actually has to have unprocessed items 749 * - We have CPUs which could be handling another thread 750 * - We haven't abused our usage of dynamic threads. 751 */ 752 if ((WorkQueue->Info.MakeThreadsAsNecessary) && 753 (!IsListEmpty(&WorkQueue->WorkerQueue.EntryListHead)) && 754 (WorkQueue->WorkerQueue.CurrentCount < 755 WorkQueue->WorkerQueue.MaximumCount) && 756 (WorkQueue->DynamicThreadCount < 16)) 757 { 758 /* Let the balance manager know about it */ 759 DPRINT1("Requesting a new thread. CurrentCount: %lu. MaxCount: %lu\n", 760 WorkQueue->WorkerQueue.CurrentCount, 761 WorkQueue->WorkerQueue.MaximumCount); 762 KeSetEvent(&ExpThreadSetManagerEvent, 0, FALSE); 763 } 764 } 765 766 /* EOF */ 767