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.

1654 lines
41 KiB

  1. /*++
  2. Copyright (c) 1989 Microsoft Corporation
  3. Module Name:
  4. worker.c
  5. Abstract:
  6. This module implements a worker thread and a set of functions for
  7. passing work to it.
  8. Author:
  9. Steve Wood (stevewo) 25-Jul-1991
  10. Revision History:
  11. --*/
  12. #include "exp.h"
  13. //
  14. // Define balance set wait object types.
  15. //
  16. typedef enum _BALANCE_OBJECT {
  17. TimerExpiration,
  18. ThreadSetManagerEvent,
  19. ShutdownEvent,
  20. MaximumBalanceObject
  21. } BALANCE_OBJECT;
  22. //
  23. // If this assertion fails then we must supply our own array of wait blocks.
  24. //
  25. C_ASSERT(MaximumBalanceObject <= THREAD_WAIT_OBJECTS);
  26. //
  27. // This is the structure passed around during shutdown
  28. //
  29. typedef struct {
  30. WORK_QUEUE_ITEM WorkItem;
  31. WORK_QUEUE_TYPE QueueType;
  32. PETHREAD PrevThread;
  33. } SHUTDOWN_WORK_ITEM, *PSHUTDOWN_WORK_ITEM;
  34. //
  35. // Used for disabling stack swapping
  36. //
  37. typedef struct _EXP_WORKER_LINK {
  38. LIST_ENTRY List;
  39. PETHREAD Thread;
  40. struct _EXP_WORKER_LINK **StackRef;
  41. } EXP_WORKER_LINK, *PEXP_WORKER_LINK;
  42. //
  43. // Define priorities for delayed and critical worker threads.
  44. // Note that these do not run at realtime.
  45. //
  46. // They run at csrss and below csrss to avoid pre-empting the
  47. // user interface under heavy load.
  48. //
  49. #define DELAYED_WORK_QUEUE_PRIORITY (12 - NORMAL_BASE_PRIORITY)
  50. #define CRITICAL_WORK_QUEUE_PRIORITY (13 - NORMAL_BASE_PRIORITY)
  51. #define HYPER_CRITICAL_WORK_QUEUE_PRIORITY (15 - NORMAL_BASE_PRIORITY)
  52. //
  53. // Number of worker threads to create for each type of system.
  54. //
  55. #define MAX_ADDITIONAL_THREADS 16
  56. #define MAX_ADDITIONAL_DYNAMIC_THREADS 16
  57. #define SMALL_NUMBER_OF_THREADS 2
  58. #define MEDIUM_NUMBER_OF_THREADS 3
  59. #define LARGE_NUMBER_OF_THREADS 5
  60. //
  61. // 10-minute timeout used for terminating dynamic work item worker threads.
  62. //
  63. #define DYNAMIC_THREAD_TIMEOUT ((LONGLONG)10 * 60 * 1000 * 1000 * 10)
  64. //
  65. // 1-second timeout used for waking up the worker thread set manager.
  66. //
  67. #define THREAD_SET_INTERVAL (1 * 1000 * 1000 * 10)
  68. //
  69. // Flag to pass in to the worker thread, indicating whether it is dynamic
  70. // or not.
  71. //
  72. #define DYNAMIC_WORKER_THREAD 0x80000000
  73. //
  74. // Per-queue dynamic thread state.
  75. //
  76. EX_WORK_QUEUE ExWorkerQueue[MaximumWorkQueue];
  77. //
  78. // Additional worker threads... Controlled using registry settings
  79. //
  80. ULONG ExpAdditionalCriticalWorkerThreads;
  81. ULONG ExpAdditionalDelayedWorkerThreads;
  82. ULONG ExCriticalWorkerThreads;
  83. ULONG ExDelayedWorkerThreads;
  84. //
  85. // Global events to wake up the thread set manager.
  86. //
  87. KEVENT ExpThreadSetManagerEvent;
  88. KEVENT ExpThreadSetManagerShutdownEvent;
  89. //
  90. // A reference to the balance manager thread, so that shutdown can
  91. // wait for it to terminate.
  92. //
  93. PETHREAD ExpWorkerThreadBalanceManagerPtr;
  94. //
  95. // A pointer to the last worker thread to exit (so the balance manager
  96. // can wait for it before exiting).
  97. //
  98. PETHREAD ExpLastWorkerThread;
  99. //
  100. // These are used to keep track of the set of workers, and whether or
  101. // not we're allowing them to be paged. Note that we can't use this
  102. // list for shutdown (sadly), as we can't just terminate the threads,
  103. // we need to flush their queues.
  104. //
  105. FAST_MUTEX ExpWorkerSwapinMutex;
  106. LIST_ENTRY ExpWorkerListHead;
  107. BOOLEAN ExpWorkersCanSwap;
  108. //
  109. // Worker queue item that can be filled in by the kernel debugger
  110. // to get code to run on the system.
  111. //
  112. WORK_QUEUE_ITEM ExpDebuggerWorkItem;
  113. PVOID ExpDebuggerProcessAttach;
  114. PVOID ExpDebuggerPageIn;
  115. ULONG ExpDebuggerWork;
  116. VOID
  117. ExpCheckDynamicThreadCount (
  118. VOID
  119. );
  120. NTSTATUS
  121. ExpCreateWorkerThread (
  122. WORK_QUEUE_TYPE QueueType,
  123. BOOLEAN Dynamic
  124. );
  125. VOID
  126. ExpDetectWorkerThreadDeadlock (
  127. VOID
  128. );
  129. VOID
  130. ExpWorkerThreadBalanceManager (
  131. IN PVOID StartContext
  132. );
  133. VOID
  134. ExpSetSwappingKernelApc (
  135. IN PKAPC Apc,
  136. OUT PKNORMAL_ROUTINE *NormalRoutine,
  137. IN OUT PVOID NormalContext,
  138. IN OUT PVOID *SystemArgument1,
  139. IN OUT PVOID *SystemArgument2
  140. );
  141. //
  142. // Procedure prototypes for the worker threads.
  143. //
  144. VOID
  145. ExpWorkerThread (
  146. IN PVOID StartContext
  147. );
  148. LOGICAL
  149. ExpCheckQueueShutdown (
  150. IN WORK_QUEUE_TYPE QueueType,
  151. IN PSHUTDOWN_WORK_ITEM ShutdownItem
  152. );
  153. VOID
  154. ExpShutdownWorker (
  155. IN PVOID Parameter
  156. );
  157. VOID
  158. ExpDebuggerWorker(
  159. IN PVOID Context
  160. );
  161. #ifdef ALLOC_PRAGMA
  162. #pragma alloc_text(INIT, ExpWorkerInitialization)
  163. #pragma alloc_text(PAGE, ExpCheckDynamicThreadCount)
  164. #pragma alloc_text(PAGE, ExpCreateWorkerThread)
  165. #pragma alloc_text(PAGE, ExpDetectWorkerThreadDeadlock)
  166. #pragma alloc_text(PAGE, ExpWorkerThreadBalanceManager)
  167. #pragma alloc_text(PAGE, ExSwapinWorkerThreads)
  168. #pragma alloc_text(PAGEKD, ExpDebuggerWorker)
  169. #pragma alloc_text(PAGELK, ExpSetSwappingKernelApc)
  170. #pragma alloc_text(PAGELK, ExpCheckQueueShutdown)
  171. #pragma alloc_text(PAGELK, ExpShutdownWorker)
  172. #pragma alloc_text(PAGELK, ExpShutdownWorkerThreads)
  173. #endif
  174. LOGICAL
  175. __forceinline
  176. ExpNewThreadNecessary (
  177. IN PEX_WORK_QUEUE Queue
  178. )
  179. /*++
  180. Routine Description:
  181. This function checks the supplied worker queue and determines whether
  182. it is appropriate to spin up a dynamic worker thread for that queue.
  183. Arguments:
  184. Queue - Supplies the queue that should be examined.
  185. Return Value:
  186. TRUE if the given work queue would benefit from the creation of an
  187. additional thread, FALSE if not.
  188. --*/
  189. {
  190. if ((Queue->Info.MakeThreadsAsNecessary == 1) &&
  191. (IsListEmpty (&Queue->WorkerQueue.EntryListHead) == FALSE) &&
  192. (Queue->WorkerQueue.CurrentCount < Queue->WorkerQueue.MaximumCount) &&
  193. (Queue->DynamicThreadCount < MAX_ADDITIONAL_DYNAMIC_THREADS)) {
  194. //
  195. // We know these things:
  196. //
  197. // - This queue is eligible for dynamic creation of threads to try
  198. // to keep the CPUs busy,
  199. //
  200. // - There are work items waiting in the queue,
  201. //
  202. // - The number of runable worker threads for this queue is less than
  203. // the number of processors on this system, and
  204. //
  205. // - We haven't reached the maximum dynamic thread count.
  206. //
  207. // An additional worker thread at this point will help clear the
  208. // backlog.
  209. //
  210. return TRUE;
  211. }
  212. //
  213. // One of the above conditions is false.
  214. //
  215. return FALSE;
  216. }
  217. NTSTATUS
  218. ExpWorkerInitialization (
  219. VOID
  220. )
  221. {
  222. ULONG Index;
  223. OBJECT_ATTRIBUTES ObjectAttributes;
  224. ULONG NumberOfDelayedThreads;
  225. ULONG NumberOfCriticalThreads;
  226. ULONG NumberOfThreads;
  227. NTSTATUS Status;
  228. HANDLE Thread;
  229. BOOLEAN NtAs;
  230. WORK_QUEUE_TYPE WorkQueueType;
  231. ExInitializeFastMutex (&ExpWorkerSwapinMutex);
  232. InitializeListHead (&ExpWorkerListHead);
  233. ExpWorkersCanSwap = TRUE;
  234. //
  235. // Set the number of worker threads based on the system size.
  236. //
  237. NtAs = MmIsThisAnNtAsSystem();
  238. NumberOfCriticalThreads = MEDIUM_NUMBER_OF_THREADS;
  239. //
  240. // 2001-07-13 CenkE Incremented boot time number of delayed threads.
  241. // We did this in Windows XP, because 3COM NICs would take a long
  242. // time with the network stack tying up the delayed worker threads.
  243. // When Mm would need a worker thread to load a driver on the critical
  244. // path of boot, it would also get stuck for a few seconds and hurt
  245. // boot times. Ideally we'd spawn new delayed threads as necessary as
  246. // well to prevent such contention from hurting boot and resume.
  247. //
  248. NumberOfDelayedThreads = MEDIUM_NUMBER_OF_THREADS + 4;
  249. switch (MmQuerySystemSize()) {
  250. case MmSmallSystem:
  251. break;
  252. case MmMediumSystem:
  253. if (NtAs) {
  254. NumberOfCriticalThreads += MEDIUM_NUMBER_OF_THREADS;
  255. }
  256. break;
  257. case MmLargeSystem:
  258. NumberOfCriticalThreads = LARGE_NUMBER_OF_THREADS;
  259. if (NtAs) {
  260. NumberOfCriticalThreads += LARGE_NUMBER_OF_THREADS;
  261. }
  262. break;
  263. default:
  264. break;
  265. }
  266. //
  267. // Initialize the work Queue objects.
  268. //
  269. if (ExpAdditionalCriticalWorkerThreads > MAX_ADDITIONAL_THREADS) {
  270. ExpAdditionalCriticalWorkerThreads = MAX_ADDITIONAL_THREADS;
  271. }
  272. if (ExpAdditionalDelayedWorkerThreads > MAX_ADDITIONAL_THREADS) {
  273. ExpAdditionalDelayedWorkerThreads = MAX_ADDITIONAL_THREADS;
  274. }
  275. //
  276. // Initialize the ExWorkerQueue[] array.
  277. //
  278. RtlZeroMemory (&ExWorkerQueue[0], MaximumWorkQueue * sizeof(EX_WORK_QUEUE));
  279. for (WorkQueueType = 0; WorkQueueType < MaximumWorkQueue; WorkQueueType += 1) {
  280. KeInitializeQueue (&ExWorkerQueue[WorkQueueType].WorkerQueue, 0);
  281. ExWorkerQueue[WorkQueueType].Info.WaitMode = UserMode;
  282. }
  283. //
  284. // Always make stack for this thread resident
  285. // so that worker pool deadlock magic can run
  286. // even when what we are trying to do is inpage
  287. // the hyper critical worker thread's stack.
  288. // Without this fix, we hold the process lock
  289. // but this thread's stack can't come in, and
  290. // the deadlock detection cannot create new threads
  291. // to break the system deadlock.
  292. //
  293. ExWorkerQueue[HyperCriticalWorkQueue].Info.WaitMode = KernelMode;
  294. if (NtAs) {
  295. ExWorkerQueue[CriticalWorkQueue].Info.WaitMode = KernelMode;
  296. }
  297. //
  298. // We only create dynamic threads for the critical work queue (note
  299. // this doesn't apply to dynamic threads created to break deadlocks.)
  300. //
  301. // The rationale is this: folks who use the delayed work queue are
  302. // not time critical, and the hypercritical queue is used rarely
  303. // by folks who are non-blocking.
  304. //
  305. ExWorkerQueue[CriticalWorkQueue].Info.MakeThreadsAsNecessary = 1;
  306. //
  307. // Initialize the global thread set manager events
  308. //
  309. KeInitializeEvent (&ExpThreadSetManagerEvent,
  310. SynchronizationEvent,
  311. FALSE);
  312. KeInitializeEvent (&ExpThreadSetManagerShutdownEvent,
  313. SynchronizationEvent,
  314. FALSE);
  315. //
  316. // Create the desired number of executive worker threads for each
  317. // of the work queues.
  318. //
  319. //
  320. // Create the builtin critical worker threads.
  321. //
  322. NumberOfThreads = NumberOfCriticalThreads + ExpAdditionalCriticalWorkerThreads;
  323. for (Index = 0; Index < NumberOfThreads; Index += 1) {
  324. //
  325. // Create a worker thread to service the critical work queue.
  326. //
  327. Status = ExpCreateWorkerThread (CriticalWorkQueue, FALSE);
  328. if (!NT_SUCCESS(Status)) {
  329. break;
  330. }
  331. }
  332. ExCriticalWorkerThreads += Index;
  333. //
  334. // Create the delayed worker threads.
  335. //
  336. NumberOfThreads = NumberOfDelayedThreads + ExpAdditionalDelayedWorkerThreads;
  337. for (Index = 0; Index < NumberOfThreads; Index += 1) {
  338. //
  339. // Create a worker thread to service the delayed work queue.
  340. //
  341. Status = ExpCreateWorkerThread (DelayedWorkQueue, FALSE);
  342. if (!NT_SUCCESS(Status)) {
  343. break;
  344. }
  345. }
  346. ExDelayedWorkerThreads += Index;
  347. //
  348. // Create the hypercritical worker thread.
  349. //
  350. Status = ExpCreateWorkerThread (HyperCriticalWorkQueue, FALSE);
  351. //
  352. // Create the worker thread set manager thread.
  353. //
  354. InitializeObjectAttributes (&ObjectAttributes, NULL, 0, NULL, NULL);
  355. Status = PsCreateSystemThread (&Thread,
  356. THREAD_ALL_ACCESS,
  357. &ObjectAttributes,
  358. 0,
  359. NULL,
  360. ExpWorkerThreadBalanceManager,
  361. NULL);
  362. if (NT_SUCCESS(Status)) {
  363. Status = ObReferenceObjectByHandle (Thread,
  364. SYNCHRONIZE,
  365. NULL,
  366. KernelMode,
  367. &ExpWorkerThreadBalanceManagerPtr,
  368. NULL);
  369. ZwClose (Thread);
  370. }
  371. return Status;
  372. }
  373. VOID
  374. ExQueueWorkItem (
  375. IN PWORK_QUEUE_ITEM WorkItem,
  376. IN WORK_QUEUE_TYPE QueueType
  377. )
  378. /*++
  379. Routine Description:
  380. This function inserts a work item into a work queue that is processed
  381. by a worker thread of the corresponding type.
  382. Arguments:
  383. WorkItem - Supplies a pointer to the work item to add the the queue.
  384. This structure must be located in NonPagedPool. The work item
  385. structure contains a doubly linked list entry, the address of a
  386. routine to call and a parameter to pass to that routine.
  387. QueueType - Specifies the type of work queue that the work item
  388. should be placed in.
  389. Return Value:
  390. None.
  391. --*/
  392. {
  393. PEX_WORK_QUEUE Queue;
  394. ASSERT (QueueType < MaximumWorkQueue);
  395. ASSERT (WorkItem->List.Flink == NULL);
  396. Queue = &ExWorkerQueue[QueueType];
  397. //
  398. // Insert the work item in the appropriate queue object.
  399. //
  400. KeInsertQueue (&Queue->WorkerQueue, &WorkItem->List);
  401. //
  402. // We check the queue's shutdown state after we insert the work
  403. // item to avoid the race condition when the queue's marked
  404. // between checking the queue and inserting the item. It's
  405. // possible for the queue to be marked for shutdown between the
  406. // insert and this assert (so the insert would've barely sneaked
  407. // in), but it's not worth guarding against this -- barely
  408. // sneaking in is not a good design strategy, and at this point in
  409. // the shutdown sequence, the caller simply should not be trying
  410. // to insert new queue items.
  411. //
  412. ASSERT (!Queue->Info.QueueDisabled);
  413. //
  414. // Determine whether another thread should be created, and signal the
  415. // thread set balance manager if so.
  416. //
  417. if (ExpNewThreadNecessary (Queue) != FALSE) {
  418. KeSetEvent (&ExpThreadSetManagerEvent, 0, FALSE);
  419. }
  420. return;
  421. }
  422. VOID
  423. ExpWorkerThreadBalanceManager (
  424. IN PVOID StartContext
  425. )
  426. /*++
  427. Routine Description:
  428. This function is the startup code for the worker thread manager thread.
  429. The worker thread manager thread is created during system initialization
  430. and begins execution in this function.
  431. This thread is responsible for detecting and breaking circular deadlocks
  432. in the system worker thread queues. It will also create and destroy
  433. additional worker threads as needed based on loading.
  434. Arguments:
  435. Context - Supplies a pointer to an arbitrary data structure (NULL).
  436. Return Value:
  437. None.
  438. --*/
  439. {
  440. KTIMER PeriodTimer;
  441. LARGE_INTEGER DueTime;
  442. PVOID WaitObjects[MaximumBalanceObject];
  443. NTSTATUS Status;
  444. PAGED_CODE();
  445. UNREFERENCED_PARAMETER (StartContext);
  446. //
  447. // Raise the thread priority to just higher than the priority of the
  448. // critical work queue.
  449. //
  450. KeSetBasePriorityThread (KeGetCurrentThread(),
  451. CRITICAL_WORK_QUEUE_PRIORITY + 1);
  452. //
  453. // Initialize the periodic timer and set the manager period.
  454. //
  455. KeInitializeTimer (&PeriodTimer);
  456. DueTime.QuadPart = - THREAD_SET_INTERVAL;
  457. //
  458. // Initialize the wait object array.
  459. //
  460. WaitObjects[TimerExpiration] = (PVOID)&PeriodTimer;
  461. WaitObjects[ThreadSetManagerEvent] = (PVOID)&ExpThreadSetManagerEvent;
  462. WaitObjects[ShutdownEvent] = (PVOID)&ExpThreadSetManagerShutdownEvent;
  463. //
  464. // Loop forever processing events.
  465. //
  466. while (TRUE) {
  467. //
  468. // Set the timer to expire at the next periodic interval.
  469. //
  470. KeSetTimer (&PeriodTimer, DueTime, NULL);
  471. //
  472. // Wake up when the timer expires or the set manager event is
  473. // signalled.
  474. //
  475. Status = KeWaitForMultipleObjects (MaximumBalanceObject,
  476. WaitObjects,
  477. WaitAny,
  478. Executive,
  479. KernelMode,
  480. FALSE,
  481. NULL,
  482. NULL);
  483. switch (Status) {
  484. case TimerExpiration:
  485. //
  486. // Periodic timer expiration - go see if any work queues
  487. // are deadlocked.
  488. //
  489. ExpDetectWorkerThreadDeadlock ();
  490. break;
  491. case ThreadSetManagerEvent:
  492. //
  493. // Someone has asked us to check some metrics to determine
  494. // whether we should create another worker thread.
  495. //
  496. ExpCheckDynamicThreadCount ();
  497. break;
  498. case ShutdownEvent:
  499. //
  500. // Time to exit...
  501. //
  502. KeCancelTimer (&PeriodTimer);
  503. ASSERT (ExpLastWorkerThread);
  504. //
  505. // Wait for the last worker thread to terminate
  506. //
  507. KeWaitForSingleObject (ExpLastWorkerThread,
  508. Executive,
  509. KernelMode,
  510. FALSE,
  511. NULL);
  512. ObDereferenceObject (ExpLastWorkerThread);
  513. PsTerminateSystemThread(STATUS_SYSTEM_SHUTDOWN);
  514. break;
  515. }
  516. //
  517. // Special debugger support.
  518. //
  519. // This checks if special debugging routines need to be run on the
  520. // behalf of the debugger.
  521. //
  522. if (ExpDebuggerWork == 1) {
  523. ExInitializeWorkItem(&ExpDebuggerWorkItem, ExpDebuggerWorker, NULL);
  524. ExpDebuggerWork = 2;
  525. ExQueueWorkItem(&ExpDebuggerWorkItem, DelayedWorkQueue);
  526. }
  527. }
  528. }
  529. VOID
  530. ExpCheckDynamicThreadCount (
  531. VOID
  532. )
  533. /*++
  534. Routine Description:
  535. This routine is called when there is reason to believe that a work queue
  536. might benefit from the creation of an additional worker thread.
  537. This routine checks each queue to determine whether it would benefit from
  538. an additional worker thread (see ExpNewThreadNecessary()), and creates
  539. one if so.
  540. Arguments:
  541. None.
  542. Return Value:
  543. None.
  544. --*/
  545. {
  546. PEX_WORK_QUEUE Queue;
  547. WORK_QUEUE_TYPE QueueType;
  548. PAGED_CODE();
  549. //
  550. // Check each worker queue.
  551. //
  552. Queue = &ExWorkerQueue[0];
  553. for (QueueType = 0; QueueType < MaximumWorkQueue; Queue += 1, QueueType += 1) {
  554. if (ExpNewThreadNecessary (Queue)) {
  555. //
  556. // Create a new thread for this queue. We explicitly ignore
  557. // an error from ExpCreateDynamicThread(): there's nothing
  558. // we can or should do in the event of a failure.
  559. //
  560. ExpCreateWorkerThread (QueueType, TRUE);
  561. }
  562. }
  563. }
  564. VOID
  565. ExpDetectWorkerThreadDeadlock (
  566. VOID
  567. )
  568. /*++
  569. Routine Description:
  570. This function creates new work item threads if a possible deadlock is
  571. detected.
  572. Arguments:
  573. None.
  574. Return Value:
  575. None
  576. --*/
  577. {
  578. ULONG Index;
  579. PEX_WORK_QUEUE Queue;
  580. PAGED_CODE();
  581. //
  582. // Process each queue type.
  583. //
  584. for (Index = 0; Index < MaximumWorkQueue; Index += 1) {
  585. Queue = &ExWorkerQueue[Index];
  586. ASSERT( Queue->DynamicThreadCount <= MAX_ADDITIONAL_DYNAMIC_THREADS );
  587. if ((Queue->QueueDepthLastPass > 0) &&
  588. (Queue->WorkItemsProcessed == Queue->WorkItemsProcessedLastPass) &&
  589. (Queue->DynamicThreadCount < MAX_ADDITIONAL_DYNAMIC_THREADS)) {
  590. //
  591. // These things are known:
  592. //
  593. // - There were work items waiting in the queue at the last pass.
  594. // - No work items have been processed since the last pass.
  595. // - We haven't yet created the maximum number of dynamic threads.
  596. //
  597. // Things look like they're stuck, create a new thread for this
  598. // queue.
  599. //
  600. // We explicitly ignore an error from ExpCreateDynamicThread():
  601. // we'll try again in another detection period if the queue looks
  602. // like it's still stuck.
  603. //
  604. ExpCreateWorkerThread (Index, TRUE);
  605. }
  606. //
  607. // Update some bookkeeping.
  608. //
  609. // Note that WorkItemsProcessed and the queue depth must be recorded
  610. // in that order to avoid getting a false deadlock indication.
  611. //
  612. Queue->WorkItemsProcessedLastPass = Queue->WorkItemsProcessed;
  613. Queue->QueueDepthLastPass = KeReadStateQueue (&Queue->WorkerQueue);
  614. }
  615. }
  616. NTSTATUS
  617. ExpCreateWorkerThread (
  618. IN WORK_QUEUE_TYPE QueueType,
  619. IN BOOLEAN Dynamic
  620. )
  621. /*++
  622. Routine Description:
  623. This function creates a single new static or dynamic worker thread for
  624. the given queue type.
  625. Arguments:
  626. QueueType - Supplies the type of the queue for which the worker thread
  627. should be created.
  628. Dynamic - If TRUE, the worker thread is created as a dynamic thread that
  629. will terminate after a sufficient period of inactivity. If FALSE,
  630. the worker thread will never terminate.
  631. Return Value:
  632. The final status of the operation.
  633. Notes:
  634. This routine is only called from the worker thread set balance thread,
  635. therefore it will not be reentered.
  636. --*/
  637. {
  638. OBJECT_ATTRIBUTES ObjectAttributes;
  639. NTSTATUS Status;
  640. HANDLE ThreadHandle;
  641. ULONG Context;
  642. ULONG BasePriority;
  643. PETHREAD Thread;
  644. InitializeObjectAttributes (&ObjectAttributes, NULL, 0, NULL, NULL);
  645. Context = QueueType;
  646. if (Dynamic != FALSE) {
  647. Context |= DYNAMIC_WORKER_THREAD;
  648. }
  649. Status = PsCreateSystemThread (&ThreadHandle,
  650. THREAD_ALL_ACCESS,
  651. &ObjectAttributes,
  652. 0L,
  653. NULL,
  654. ExpWorkerThread,
  655. (PVOID)(ULONG_PTR)Context);
  656. if (!NT_SUCCESS(Status)) {
  657. return Status;
  658. }
  659. if (Dynamic != FALSE) {
  660. InterlockedIncrement ((PLONG)&ExWorkerQueue[QueueType].DynamicThreadCount);
  661. }
  662. //
  663. // Set the priority according to the type of worker thread.
  664. //
  665. switch (QueueType) {
  666. case HyperCriticalWorkQueue:
  667. BasePriority = HYPER_CRITICAL_WORK_QUEUE_PRIORITY;
  668. break;
  669. case CriticalWorkQueue:
  670. BasePriority = CRITICAL_WORK_QUEUE_PRIORITY;
  671. break;
  672. case DelayedWorkQueue:
  673. default:
  674. BasePriority = DELAYED_WORK_QUEUE_PRIORITY;
  675. break;
  676. }
  677. //
  678. // Set the base priority of the just-created thread.
  679. //
  680. Status = ObReferenceObjectByHandle (ThreadHandle,
  681. THREAD_SET_INFORMATION,
  682. PsThreadType,
  683. KernelMode,
  684. (PVOID *)&Thread,
  685. NULL);
  686. if (NT_SUCCESS(Status)) {
  687. KeSetBasePriorityThread (&Thread->Tcb, BasePriority);
  688. ObDereferenceObject (Thread);
  689. }
  690. ZwClose (ThreadHandle);
  691. return Status;
  692. }
  693. VOID
  694. ExpCheckForWorker (
  695. IN PVOID p,
  696. IN SIZE_T Size
  697. )
  698. {
  699. KIRQL OldIrql;
  700. PLIST_ENTRY Entry;
  701. PCHAR BeginBlock;
  702. PCHAR EndBlock;
  703. WORK_QUEUE_TYPE wqt;
  704. BeginBlock = (PCHAR)p;
  705. EndBlock = (PCHAR)p + Size;
  706. KiLockDispatcherDatabase (&OldIrql);
  707. for (wqt = CriticalWorkQueue; wqt < MaximumWorkQueue; wqt += 1) {
  708. for (Entry = (PLIST_ENTRY) ExWorkerQueue[wqt].WorkerQueue.EntryListHead.Flink;
  709. Entry && (Entry != (PLIST_ENTRY) &ExWorkerQueue[wqt].WorkerQueue.EntryListHead);
  710. Entry = Entry->Flink) {
  711. if (((PCHAR) Entry >= BeginBlock) && ((PCHAR) Entry < EndBlock)) {
  712. KeBugCheckEx(WORKER_INVALID,
  713. 0x0,
  714. (ULONG_PTR)Entry,
  715. (ULONG_PTR)BeginBlock,
  716. (ULONG_PTR)EndBlock);
  717. }
  718. }
  719. }
  720. KiUnlockDispatcherDatabase (OldIrql);
  721. }
  722. #ifdef ALLOC_DATA_PRAGMA
  723. #pragma const_seg("PAGECONST")
  724. #endif
  725. const char ExpWorkerApcDisabledMessage[] =
  726. "EXWORKER: worker exit with APCs disabled, worker routine %x, parameter %x, item %x\n";
  727. #ifdef ALLOC_DATA_PRAGMA
  728. #pragma const_seg()
  729. #endif
  730. VOID
  731. ExpWorkerThread (
  732. IN PVOID StartContext
  733. )
  734. {
  735. PLIST_ENTRY Entry;
  736. WORK_QUEUE_TYPE QueueType;
  737. PWORK_QUEUE_ITEM WorkItem;
  738. KPROCESSOR_MODE WaitMode;
  739. LARGE_INTEGER TimeoutValue;
  740. PLARGE_INTEGER Timeout;
  741. PETHREAD Thread;
  742. PEX_WORK_QUEUE WorkerQueue;
  743. PWORKER_THREAD_ROUTINE WorkerRoutine;
  744. PVOID Parameter;
  745. EX_QUEUE_WORKER_INFO OldWorkerInfo;
  746. EX_QUEUE_WORKER_INFO NewWorkerInfo;
  747. ULONG CountForQueueEmpty;
  748. //
  749. // Set timeout value etc according to whether we are static or dynamic.
  750. //
  751. if (((ULONG_PTR)StartContext & DYNAMIC_WORKER_THREAD) == 0) {
  752. //
  753. // We are being created as a static thread. As such it will not
  754. // terminate, so there is no point in timing out waiting for a work
  755. // item.
  756. //
  757. Timeout = NULL;
  758. }
  759. else {
  760. //
  761. // This is a dynamic worker thread. It has a non-infinite timeout
  762. // so that it can eventually terminate.
  763. //
  764. TimeoutValue.QuadPart = -DYNAMIC_THREAD_TIMEOUT;
  765. Timeout = &TimeoutValue;
  766. }
  767. Thread = PsGetCurrentThread ();
  768. //
  769. // If the thread is a critical worker thread, then set the thread
  770. // priority to the lowest realtime level. Otherwise, set the base
  771. // thread priority to time critical.
  772. //
  773. QueueType = (WORK_QUEUE_TYPE)
  774. ((ULONG_PTR)StartContext & ~DYNAMIC_WORKER_THREAD);
  775. WorkerQueue = &ExWorkerQueue[QueueType];
  776. WaitMode = (KPROCESSOR_MODE) WorkerQueue->Info.WaitMode;
  777. ASSERT (Thread->ExWorkerCanWaitUser == 0);
  778. if (WaitMode == UserMode) {
  779. Thread->ExWorkerCanWaitUser = 1;
  780. }
  781. #if defined(REMOTE_BOOT)
  782. //
  783. // In diskless NT scenarios ensure that the kernel stack of the worker
  784. // threads will not be swapped out.
  785. //
  786. if (IoRemoteBootClient) {
  787. KeSetKernelStackSwapEnable (FALSE);
  788. }
  789. #endif // defined(REMOTE_BOOT)
  790. //
  791. // Register as a worker, exiting if the queue's going down and
  792. // there aren't any workers in the queue to hand us the shutdown
  793. // work item if we enter the queue (we want to be able to enter a
  794. // queue even if the queue's shutting down, in case there's a
  795. // backlog of work items that the balance manager thread's decided
  796. // we should be helping to process).
  797. //
  798. if (PO_SHUTDOWN_QUEUE == QueueType) {
  799. CountForQueueEmpty = 1;
  800. }
  801. else {
  802. CountForQueueEmpty = 0;
  803. }
  804. if (ExpWorkersCanSwap == FALSE) {
  805. KeSetKernelStackSwapEnable (FALSE);
  806. }
  807. do {
  808. OldWorkerInfo.QueueWorkerInfo = WorkerQueue->Info.QueueWorkerInfo;
  809. if (OldWorkerInfo.QueueDisabled &&
  810. OldWorkerInfo.WorkerCount <= CountForQueueEmpty) {
  811. //
  812. // The queue is disabled and empty so just exit.
  813. //
  814. KeSetKernelStackSwapEnable (TRUE);
  815. PsTerminateSystemThread (STATUS_SYSTEM_SHUTDOWN);
  816. }
  817. NewWorkerInfo.QueueWorkerInfo = OldWorkerInfo.QueueWorkerInfo;
  818. NewWorkerInfo.WorkerCount += 1;
  819. } while (OldWorkerInfo.QueueWorkerInfo !=
  820. InterlockedCompareExchange (&WorkerQueue->Info.QueueWorkerInfo,
  821. NewWorkerInfo.QueueWorkerInfo,
  822. OldWorkerInfo.QueueWorkerInfo));
  823. //
  824. // As of this point, we must only exit if we decrement the worker
  825. // count without the queue disabled flag being set. (Unless we
  826. // exit due to the shutdown work item, which also decrements the
  827. // worker count).
  828. //
  829. Thread->ActiveExWorker = 1;
  830. //
  831. // Loop forever waiting for a work queue item, calling the processing
  832. // routine, and then waiting for another work queue item.
  833. //
  834. do {
  835. //
  836. // Wait until something is put in the queue or until we time out.
  837. //
  838. // By specifying a wait mode of UserMode, the thread's kernel
  839. // stack is swappable.
  840. //
  841. Entry = KeRemoveQueue (&WorkerQueue->WorkerQueue,
  842. WaitMode,
  843. Timeout);
  844. if ((ULONG_PTR)Entry != STATUS_TIMEOUT) {
  845. //
  846. // This is a real work item, process it.
  847. //
  848. // Update the total number of work items processed.
  849. //
  850. InterlockedIncrement ((PLONG)&WorkerQueue->WorkItemsProcessed);
  851. WorkItem = CONTAINING_RECORD(Entry, WORK_QUEUE_ITEM, List);
  852. WorkerRoutine = WorkItem->WorkerRoutine;
  853. Parameter = WorkItem->Parameter;
  854. //
  855. // Execute the specified routine.
  856. //
  857. ((PWORKER_THREAD_ROUTINE)WorkerRoutine) (Parameter);
  858. //
  859. // Catch worker routines that forget to do KeLeaveCriticalRegion.
  860. // It has to be zero at this point. In the debug case we enter a
  861. // breakpoint. In the non-debug case just zero the flag so that
  862. // APCs can continue to fire to this thread.
  863. //
  864. if (Thread->Tcb.KernelApcDisable != 0) {
  865. DbgPrint ((char*)ExpWorkerApcDisabledMessage,
  866. WorkerRoutine,
  867. Parameter,
  868. WorkItem);
  869. ASSERT (FALSE);
  870. Thread->Tcb.KernelApcDisable = 0;
  871. }
  872. if (KeGetCurrentIrql () != PASSIVE_LEVEL) {
  873. KeBugCheckEx (WORKER_THREAD_RETURNED_AT_BAD_IRQL,
  874. (ULONG_PTR)WorkerRoutine,
  875. (ULONG_PTR)KeGetCurrentIrql(),
  876. (ULONG_PTR)Parameter,
  877. (ULONG_PTR)WorkItem);
  878. }
  879. if (PS_IS_THREAD_IMPERSONATING (Thread)) {
  880. KeBugCheckEx (IMPERSONATING_WORKER_THREAD,
  881. (ULONG_PTR)WorkerRoutine,
  882. (ULONG_PTR)Parameter,
  883. (ULONG_PTR)WorkItem,
  884. 0);
  885. }
  886. continue;
  887. }
  888. //
  889. // These things are known:
  890. //
  891. // - Static worker threads do not time out, so this is a dynamic
  892. // worker thread.
  893. //
  894. // - This thread has been waiting for a long time with nothing
  895. // to do.
  896. //
  897. if (IsListEmpty (&Thread->IrpList) == FALSE) {
  898. //
  899. // There is still I/O pending, can't terminate yet.
  900. //
  901. continue;
  902. }
  903. //
  904. // Get out of the queue, if we can
  905. //
  906. do {
  907. OldWorkerInfo.QueueWorkerInfo = WorkerQueue->Info.QueueWorkerInfo;
  908. if (OldWorkerInfo.QueueDisabled) {
  909. //
  910. // We're exiting via the queue disable work item;
  911. // there's no point in expiring here.
  912. //
  913. break;
  914. }
  915. NewWorkerInfo.QueueWorkerInfo = OldWorkerInfo.QueueWorkerInfo;
  916. NewWorkerInfo.WorkerCount -= 1;
  917. } while (OldWorkerInfo.QueueWorkerInfo
  918. != InterlockedCompareExchange(&WorkerQueue->Info.QueueWorkerInfo,
  919. NewWorkerInfo.QueueWorkerInfo,
  920. OldWorkerInfo.QueueWorkerInfo));
  921. if (OldWorkerInfo.QueueDisabled) {
  922. //
  923. // We're exiting via the queue disable work item
  924. //
  925. continue;
  926. }
  927. //
  928. // This dynamic thread can be terminated.
  929. //
  930. break;
  931. } while (TRUE);
  932. //
  933. // Terminate this dynamic thread.
  934. //
  935. InterlockedDecrement ((PLONG)&WorkerQueue->DynamicThreadCount);
  936. //
  937. // Carefully clear this before marking the thread stack as swap enabled
  938. // so that an incoming APC won't inadvertently disable the stack swap
  939. // afterwards.
  940. //
  941. Thread->ActiveExWorker = 0;
  942. //
  943. // We will bugcheck if we terminate a thread with stack swapping
  944. // disabled.
  945. //
  946. KeSetKernelStackSwapEnable (TRUE);
  947. return;
  948. }
  949. VOID
  950. ExpSetSwappingKernelApc (
  951. IN PKAPC Apc,
  952. OUT PKNORMAL_ROUTINE *NormalRoutine,
  953. IN OUT PVOID NormalContext,
  954. IN OUT PVOID *SystemArgument1,
  955. IN OUT PVOID *SystemArgument2
  956. )
  957. {
  958. PBOOLEAN AllowSwap;
  959. PKEVENT SwapSetEvent;
  960. UNREFERENCED_PARAMETER (Apc);
  961. UNREFERENCED_PARAMETER (NormalRoutine);
  962. UNREFERENCED_PARAMETER (SystemArgument2);
  963. //
  964. // SystemArgument1 is a pointer to the event to signal once this
  965. // thread has finished servicing the request.
  966. //
  967. SwapSetEvent = (PKEVENT) *SystemArgument1;
  968. //
  969. // Don't disable stack swapping if the thread is exiting because
  970. // it cannot exit this way without bugchecking. Skip it on enables
  971. // too since the thread is bailing anyway.
  972. //
  973. if (PsGetCurrentThread()->ActiveExWorker != 0) {
  974. AllowSwap = NormalContext;
  975. KeSetKernelStackSwapEnable (*AllowSwap);
  976. }
  977. KeSetEvent (SwapSetEvent, 0, FALSE);
  978. }
  979. VOID
  980. ExSwapinWorkerThreads (
  981. IN BOOLEAN AllowSwap
  982. )
  983. /*++
  984. Routine Description:
  985. Sets the kernel stacks of the delayed worker threads to be swappable
  986. or pins them into memory.
  987. Arguments:
  988. AllowSwap - Supplies TRUE if worker kernel stacks should be swappable,
  989. FALSE if not.
  990. Return Value:
  991. None.
  992. --*/
  993. {
  994. PETHREAD Thread;
  995. PETHREAD CurrentThread;
  996. PEPROCESS Process;
  997. KAPC Apc;
  998. KEVENT SwapSetEvent;
  999. PAGED_CODE();
  1000. CurrentThread = PsGetCurrentThread();
  1001. KeInitializeEvent (&SwapSetEvent,
  1002. NotificationEvent,
  1003. FALSE);
  1004. Process = PsInitialSystemProcess;
  1005. //
  1006. // Serialize callers.
  1007. //
  1008. ExAcquireFastMutex (&ExpWorkerSwapinMutex);
  1009. //
  1010. // Stop new threads from swapping.
  1011. //
  1012. ExpWorkersCanSwap = AllowSwap;
  1013. //
  1014. // Stop existing worker threads from swapping.
  1015. //
  1016. for (Thread = PsGetNextProcessThread (Process, NULL);
  1017. Thread != NULL;
  1018. Thread = PsGetNextProcessThread (Process, Thread)) {
  1019. //
  1020. // Skip threads that are not worker threads or worker threads that
  1021. // were permanently marked noswap at creation time.
  1022. //
  1023. if (Thread->ExWorkerCanWaitUser == 0) {
  1024. continue;
  1025. }
  1026. if (Thread == CurrentThread) {
  1027. //
  1028. // No need to use an APC on the current thread.
  1029. //
  1030. KeSetKernelStackSwapEnable (AllowSwap);
  1031. }
  1032. else {
  1033. //
  1034. // Queue an APC to the thread, and wait for it to fire:
  1035. //
  1036. KeInitializeApc (&Apc,
  1037. &Thread->Tcb,
  1038. InsertApcEnvironment,
  1039. ExpSetSwappingKernelApc,
  1040. NULL,
  1041. NULL,
  1042. KernelMode,
  1043. &AllowSwap);
  1044. if (KeInsertQueueApc (&Apc, &SwapSetEvent, NULL, 3)) {
  1045. KeWaitForSingleObject (&SwapSetEvent,
  1046. Executive,
  1047. KernelMode,
  1048. FALSE,
  1049. NULL);
  1050. KeClearEvent(&SwapSetEvent);
  1051. }
  1052. }
  1053. }
  1054. ExReleaseFastMutex (&ExpWorkerSwapinMutex);
  1055. }
  1056. LOGICAL
  1057. ExpCheckQueueShutdown (
  1058. IN WORK_QUEUE_TYPE QueueType,
  1059. IN PSHUTDOWN_WORK_ITEM ShutdownItem
  1060. )
  1061. {
  1062. ULONG CountForQueueEmpty;
  1063. if (PO_SHUTDOWN_QUEUE == QueueType) {
  1064. CountForQueueEmpty = 1;
  1065. }
  1066. else {
  1067. CountForQueueEmpty = 0;
  1068. }
  1069. //
  1070. // Note that using interlocked sequences to increment the worker count
  1071. // and decrement it to CountForQueueEmpty ensures that once it
  1072. // *is* equal to CountForQueueEmpty and the disabled flag is set,
  1073. // we won't be incrementing it any more, so we're safe making this
  1074. // check without locks.
  1075. //
  1076. // See ExpWorkerThread, ExpShutdownWorker, and ExpShutdownWorkerThreads.
  1077. //
  1078. if (ExWorkerQueue[QueueType].Info.WorkerCount > CountForQueueEmpty) {
  1079. //
  1080. // There're still worker threads; send one of them the axe.
  1081. //
  1082. ShutdownItem->QueueType = QueueType;
  1083. ShutdownItem->PrevThread = PsGetCurrentThread();
  1084. ObReferenceObject (ShutdownItem->PrevThread);
  1085. KeInsertQueue (&ExWorkerQueue[QueueType].WorkerQueue,
  1086. &ShutdownItem->WorkItem.List);
  1087. return TRUE;
  1088. }
  1089. return FALSE; // we did not queue a shutdown
  1090. }
  1091. VOID
  1092. ExpShutdownWorker (
  1093. IN PVOID Parameter
  1094. )
  1095. {
  1096. PETHREAD CurrentThread;
  1097. PSHUTDOWN_WORK_ITEM ShutdownItem;
  1098. ShutdownItem = (PSHUTDOWN_WORK_ITEM) Parameter;
  1099. ASSERT (ShutdownItem != NULL);
  1100. if (ShutdownItem->PrevThread != NULL) {
  1101. //
  1102. // Wait for the previous thread to exit -- if it's in the same
  1103. // queue, it probably has already, but we need to make sure
  1104. // (and if it's not, we *definitely* need to make sure).
  1105. //
  1106. KeWaitForSingleObject (ShutdownItem->PrevThread,
  1107. Executive,
  1108. KernelMode,
  1109. FALSE,
  1110. NULL);
  1111. ObDereferenceObject (ShutdownItem->PrevThread);
  1112. ShutdownItem->PrevThread = NULL;
  1113. }
  1114. //
  1115. // Decrement the worker count.
  1116. //
  1117. InterlockedDecrement (&ExWorkerQueue[ShutdownItem->QueueType].Info.QueueWorkerInfo);
  1118. CurrentThread = PsGetCurrentThread();
  1119. if ((!ExpCheckQueueShutdown(DelayedWorkQueue, ShutdownItem)) &&
  1120. (!ExpCheckQueueShutdown(CriticalWorkQueue, ShutdownItem))) {
  1121. //
  1122. // We're the last worker to exit
  1123. //
  1124. ASSERT (!ExpLastWorkerThread);
  1125. ExpLastWorkerThread = CurrentThread;
  1126. ObReferenceObject (ExpLastWorkerThread);
  1127. KeSetEvent (&ExpThreadSetManagerShutdownEvent, 0, FALSE);
  1128. }
  1129. KeSetKernelStackSwapEnable (TRUE);
  1130. CurrentThread->ActiveExWorker = 0;
  1131. PsTerminateSystemThread (STATUS_SYSTEM_SHUTDOWN);
  1132. }
  1133. VOID
  1134. ExpShutdownWorkerThreads (
  1135. VOID
  1136. )
  1137. {
  1138. PULONG QueueEnable;
  1139. SHUTDOWN_WORK_ITEM ShutdownItem;
  1140. if ((PoCleanShutdownEnabled () & PO_CLEAN_SHUTDOWN_WORKERS) == 0) {
  1141. return;
  1142. }
  1143. ASSERT (KeGetCurrentThread()->Queue
  1144. == &ExWorkerQueue[PO_SHUTDOWN_QUEUE].WorkerQueue);
  1145. //
  1146. // Mark the queues as terminating.
  1147. //
  1148. QueueEnable = (PULONG)&ExWorkerQueue[DelayedWorkQueue].Info.QueueWorkerInfo;
  1149. RtlInterlockedSetBitsDiscardReturn (QueueEnable, EX_WORKER_QUEUE_DISABLED);
  1150. QueueEnable = (PULONG)&ExWorkerQueue[CriticalWorkQueue].Info.QueueWorkerInfo;
  1151. RtlInterlockedSetBitsDiscardReturn (QueueEnable, EX_WORKER_QUEUE_DISABLED);
  1152. //
  1153. // Queue the shutdown work item to the delayed work queue. After
  1154. // all currently queued work items are complete, this will fire,
  1155. // repeatedly taking out every worker thread in every queue until
  1156. // they're all done.
  1157. //
  1158. ExInitializeWorkItem (&ShutdownItem.WorkItem,
  1159. &ExpShutdownWorker,
  1160. &ShutdownItem);
  1161. ShutdownItem.QueueType = DelayedWorkQueue;
  1162. ShutdownItem.PrevThread = NULL;
  1163. KeInsertQueue (&ExWorkerQueue[DelayedWorkQueue].WorkerQueue,
  1164. &ShutdownItem.WorkItem.List);
  1165. //
  1166. // Wait for all of the workers and the balancer to exit.
  1167. //
  1168. if (ExpWorkerThreadBalanceManagerPtr != NULL) {
  1169. KeWaitForSingleObject(ExpWorkerThreadBalanceManagerPtr,
  1170. Executive,
  1171. KernelMode,
  1172. FALSE,
  1173. NULL);
  1174. ASSERT(!ShutdownItem.PrevThread);
  1175. ObDereferenceObject(ExpWorkerThreadBalanceManagerPtr);
  1176. }
  1177. }
  1178. VOID
  1179. ExpDebuggerWorker(
  1180. IN PVOID Context
  1181. )
  1182. /*++
  1183. Routine Description:
  1184. This is a worker thread for the kernel debugger that can be used to
  1185. perform certain tasks on the target machine asynchronously.
  1186. This is necessary when the machine needs to run at Dispatch level to
  1187. perform certain operations, such as paging in data.
  1188. Arguments:
  1189. Context - not used as this point.
  1190. Return Value:
  1191. None.
  1192. --*/
  1193. {
  1194. NTSTATUS Status;
  1195. KAPC_STATE ApcState;
  1196. volatile UCHAR Data;
  1197. PRKPROCESS AttachProcess = (PRKPROCESS) ExpDebuggerProcessAttach;
  1198. PUCHAR PageIn = (PUCHAR) ExpDebuggerPageIn;
  1199. PEPROCESS Process;
  1200. ExpDebuggerProcessAttach = 0;
  1201. ExpDebuggerPageIn = 0;
  1202. UNREFERENCED_PARAMETER (Context);
  1203. #if DBG
  1204. if (ExpDebuggerWork != 2)
  1205. {
  1206. DbgPrint("ExpDebuggerWorker being entered with state != 2\n");
  1207. }
  1208. #endif
  1209. ExpDebuggerWork = 0;
  1210. Process = NULL;
  1211. if (AttachProcess) {
  1212. for (Process = PsGetNextProcess (NULL);
  1213. Process != NULL;
  1214. Process = PsGetNextProcess (Process)) {
  1215. if (&Process->Pcb == AttachProcess) {
  1216. KeStackAttachProcess (AttachProcess, &ApcState);
  1217. break;
  1218. }
  1219. }
  1220. }
  1221. if (PageIn) {
  1222. try {
  1223. ProbeForReadSmallStructure (PageIn, sizeof (UCHAR), sizeof (UCHAR));
  1224. Data = *PageIn;
  1225. } except (EXCEPTION_EXECUTE_HANDLER) {
  1226. Status = GetExceptionCode();
  1227. }
  1228. }
  1229. DbgBreakPointWithStatus(DBG_STATUS_WORKER);
  1230. if (Process != NULL) {
  1231. KeUnstackDetachProcess (&ApcState);
  1232. PsQuitNextProcess (Process);
  1233. }
  1234. return;
  1235. }