2 * PROJECT: ReactOS Kernel
3 * LICENSE: GPL - See COPYING in the top level directory
4 * FILE: ntoskrnl/ke/wait.c
5 * PURPOSE: Manages waiting for Dispatcher Objects
6 * PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org)
10 /* INCLUDES ******************************************************************/
16 /* PRIVATE FUNCTIONS *********************************************************/
20 KiWaitTest(IN PVOID ObjectPointer
,
21 IN KPRIORITY Increment
)
23 PLIST_ENTRY WaitEntry
, WaitList
;
24 PKWAIT_BLOCK WaitBlock
;
26 PKMUTANT FirstObject
= ObjectPointer
;
29 /* Loop the Wait Entries */
30 WaitList
= &FirstObject
->Header
.WaitListHead
;
31 WaitEntry
= WaitList
->Flink
;
32 while ((FirstObject
->Header
.SignalState
> 0) && (WaitEntry
!= WaitList
))
34 /* Get the current wait block */
35 WaitBlock
= CONTAINING_RECORD(WaitEntry
, KWAIT_BLOCK
, WaitListEntry
);
36 WaitThread
= WaitBlock
->Thread
;
37 WaitStatus
= STATUS_KERNEL_APC
;
39 /* Check the current Wait Mode */
40 if (WaitBlock
->WaitType
== WaitAny
)
42 /* Easy case, satisfy only this wait */
43 WaitStatus
= (NTSTATUS
)WaitBlock
->WaitKey
;
44 KiSatisfyObjectWait(FirstObject
, WaitThread
);
47 /* Now do the rest of the unwait */
48 KiUnwaitThread(WaitThread
, WaitStatus
, Increment
);
49 WaitEntry
= WaitList
->Flink
;
55 KiUnlinkThread(IN PKTHREAD Thread
,
56 IN NTSTATUS WaitStatus
)
58 PKWAIT_BLOCK WaitBlock
;
61 /* Update wait status */
62 Thread
->WaitStatus
|= WaitStatus
;
64 /* Remove the Wait Blocks from the list */
65 WaitBlock
= Thread
->WaitBlockList
;
69 RemoveEntryList(&WaitBlock
->WaitListEntry
);
71 /* Go to the next one */
72 WaitBlock
= WaitBlock
->NextWaitBlock
;
73 } while (WaitBlock
!= Thread
->WaitBlockList
);
75 /* Remove the thread from the wait list! */
76 if (Thread
->WaitListEntry
.Flink
) RemoveEntryList(&Thread
->WaitListEntry
);
78 /* Check if there's a Thread Timer */
79 Timer
= &Thread
->Timer
;
80 if (Timer
->Header
.Inserted
)
82 /* Remove the timer */
83 Timer
->Header
.Inserted
= FALSE
;
84 RemoveEntryList(&Timer
->TimerListEntry
);
85 //KiRemoveTimer(Timer);
88 /* Increment the Queue's active threads */
89 if (Thread
->Queue
) Thread
->Queue
->CurrentCount
++;
92 /* Must be called with the dispatcher lock held */
95 KiUnwaitThread(IN PKTHREAD Thread
,
96 IN NTSTATUS WaitStatus
,
97 IN KPRIORITY Increment
)
99 /* Unlink the thread */
100 KiUnlinkThread(Thread
, WaitStatus
);
102 /* Tell the scheduler do to the increment when it readies the thread */
103 ASSERT(Increment
>= 0);
104 Thread
->AdjustIncrement
= (SCHAR
)Increment
;
105 Thread
->AdjustReason
= AdjustUnwait
;
107 /* Reschedule the Thread */
108 KiReadyThread(Thread
);
113 KiAcquireFastMutex(IN PFAST_MUTEX FastMutex
)
115 /* Increase contention count */
116 FastMutex
->Contention
++;
118 /* Wait for the event */
119 KeWaitForSingleObject(&FastMutex
->Gate
,
127 // This routine exits the dispatcher after a compatible operation and
128 // swaps the context to the next scheduled thread on the current CPU if
131 // It does NOT attempt to scan for a new thread to schedule.
135 KiExitDispatcher(IN KIRQL OldIrql
)
137 PKPRCB Prcb
= KeGetCurrentPrcb();
138 PKTHREAD Thread
, NextThread
;
141 /* Make sure we're at synchronization level */
142 ASSERT(KeGetCurrentIrql() == SYNCH_LEVEL
);
144 /* Check if we have deferred threads */
145 KiCheckDeferredReadyList(Prcb
);
147 /* Check if we were called at dispatcher level or higher */
148 if (OldIrql
>= DISPATCH_LEVEL
)
150 /* Check if we have a thread to schedule, and that no DPC is active */
151 if ((Prcb
->NextThread
) && !(Prcb
->DpcRoutineActive
))
153 /* Request DPC interrupt */
154 HalRequestSoftwareInterrupt(DISPATCH_LEVEL
);
157 /* Lower IRQL and exit */
161 /* Make sure there's a new thread scheduled */
162 if (!Prcb
->NextThread
) goto Quickie
;
165 KiAcquirePrcbLock(Prcb
);
167 /* Get the next and current threads now */
168 NextThread
= Prcb
->NextThread
;
169 Thread
= Prcb
->CurrentThread
;
171 /* Set current thread's swap busy to true */
172 KiSetThreadSwapBusy(Thread
);
174 /* Switch threads in PRCB */
175 Prcb
->NextThread
= NULL
;
176 Prcb
->CurrentThread
= NextThread
;
178 /* Set thread to running */
179 NextThread
->State
= Running
;
181 /* Queue it on the ready lists */
182 KxQueueReadyThread(Thread
, Prcb
);
185 Thread
->WaitIrql
= OldIrql
;
187 /* Swap threads and check if APCs were pending */
188 PendingApc
= KiSwapContext(Thread
, NextThread
);
191 /* Lower only to APC */
192 KeLowerIrql(APC_LEVEL
);
195 KiDeliverApc(KernelMode
, NULL
, NULL
);
196 ASSERT(OldIrql
== PASSIVE_LEVEL
);
199 /* Lower IRQl back */
201 KeLowerIrql(OldIrql
);
204 /* PUBLIC FUNCTIONS **********************************************************/
211 KeDelayExecutionThread(IN KPROCESSOR_MODE WaitMode
,
212 IN BOOLEAN Alertable
,
213 IN PLARGE_INTEGER Interval OPTIONAL
)
216 PKWAIT_BLOCK TimerBlock
;
217 PKTHREAD Thread
= KeGetCurrentThread();
220 PLARGE_INTEGER OriginalDueTime
;
221 LARGE_INTEGER DueTime
, NewDueTime
, InterruptTime
;
223 /* If this is a user-mode wait of 0 seconds, yield execution */
224 if (!(Interval
->QuadPart
) && (WaitMode
!= KernelMode
))
226 /* Make sure the wait isn't alertable or interrupting an APC */
227 if (!(Alertable
) && !(Thread
->ApcState
.UserApcPending
))
229 /* Yield execution */
234 /* Setup the original time and timer/wait blocks */
235 OriginalDueTime
= Interval
;
236 Timer
= &Thread
->Timer
;
237 TimerBlock
= &Thread
->WaitBlock
[TIMER_WAIT_BLOCK
];
239 /* Check if the lock is already held */
240 if (!Thread
->WaitNext
) goto WaitStart
;
242 /* Otherwise, we already have the lock, so initialize the wait */
243 Thread
->WaitNext
= FALSE
;
246 /* Start wait loop */
249 /* Disable pre-emption */
250 Thread
->Preempted
= FALSE
;
252 /* Check if a kernel APC is pending and we're below APC_LEVEL */
253 if ((Thread
->ApcState
.KernelApcPending
) && !(Thread
->SpecialApcDisable
) &&
254 (Thread
->WaitIrql
< APC_LEVEL
))
256 /* Unlock the dispatcher */
257 KiReleaseDispatcherLock(Thread
->WaitIrql
);
261 /* Check if we have to bail out due to an alerted state */
262 WaitStatus
= KiCheckAlertability(Thread
, Alertable
, WaitMode
);
263 if (WaitStatus
!= STATUS_WAIT_0
) break;
265 /* Check if the timer expired */
266 InterruptTime
.QuadPart
= KeQueryInterruptTime();
267 if ((ULONGLONG
)InterruptTime
.QuadPart
>= Timer
->DueTime
.QuadPart
)
269 /* It did, so we don't need to wait */
273 /* It didn't, so activate it */
274 Timer
->Header
.Inserted
= TRUE
;
276 /* Handle Kernel Queues */
277 if (Thread
->Queue
) KiActivateWaiterQueue(Thread
->Queue
);
279 /* Setup the wait information */
280 Thread
->State
= Waiting
;
282 /* Add the thread to the wait list */
283 KiAddThreadToWaitList(Thread
, Swappable
);
285 /* Insert the timer and swap the thread */
286 ASSERT(Thread
->WaitIrql
<= DISPATCH_LEVEL
);
287 KiSetThreadSwapBusy(Thread
);
288 KiInsertWaitTimer(Timer
);
289 WaitStatus
= KiSwapThread(Thread
, KeGetCurrentPrcb());
291 /* Check if were swapped ok */
292 if (WaitStatus
!= STATUS_KERNEL_APC
)
294 /* This is a good thing */
295 if (WaitStatus
== STATUS_TIMEOUT
) WaitStatus
= STATUS_SUCCESS
;
301 /* Recalculate due times */
302 Interval
= KiRecalculateDueTime(OriginalDueTime
,
308 /* Setup a new wait */
309 Thread
->WaitIrql
= KeRaiseIrqlToSynchLevel();
311 KiAcquireDispatcherLockAtDpcLevel();
315 KiReleaseDispatcherLock(Thread
->WaitIrql
);
319 /* There was nothing to wait for. Did we have a wait interval? */
320 if (!Interval
->QuadPart
)
322 /* Unlock the dispatcher and do a yield */
323 KiReleaseDispatcherLock(Thread
->WaitIrql
);
324 return NtYieldExecution();
327 /* Unlock the dispatcher and adjust the quantum for a no-wait */
328 KiReleaseDispatcherLockFromDpcLevel();
329 KiAdjustQuantumThread(Thread
);
330 return STATUS_SUCCESS
;
338 KeWaitForSingleObject(IN PVOID Object
,
339 IN KWAIT_REASON WaitReason
,
340 IN KPROCESSOR_MODE WaitMode
,
341 IN BOOLEAN Alertable
,
342 IN PLARGE_INTEGER Timeout OPTIONAL
)
344 PKTHREAD Thread
= KeGetCurrentThread();
345 PKMUTANT CurrentObject
= (PKMUTANT
)Object
;
346 PKWAIT_BLOCK WaitBlock
= &Thread
->WaitBlock
[0];
347 PKWAIT_BLOCK TimerBlock
= &Thread
->WaitBlock
[TIMER_WAIT_BLOCK
];
348 PKTIMER Timer
= &Thread
->Timer
;
351 LARGE_INTEGER DueTime
, NewDueTime
, InterruptTime
;
352 PLARGE_INTEGER OriginalDueTime
= Timeout
;
354 /* Check if the lock is already held */
355 if (!Thread
->WaitNext
) goto WaitStart
;
357 /* Otherwise, we already have the lock, so initialize the wait */
358 Thread
->WaitNext
= FALSE
;
359 KxSingleThreadWait();
361 /* Start wait loop */
364 /* Disable pre-emption */
365 Thread
->Preempted
= FALSE
;
367 /* Check if a kernel APC is pending and we're below APC_LEVEL */
368 if ((Thread
->ApcState
.KernelApcPending
) && !(Thread
->SpecialApcDisable
) &&
369 (Thread
->WaitIrql
< APC_LEVEL
))
371 /* Unlock the dispatcher */
372 KiReleaseDispatcherLock(Thread
->WaitIrql
);
377 ASSERT(CurrentObject
->Header
.Type
!= QueueObject
);
379 /* Check if it's a mutant */
380 if (CurrentObject
->Header
.Type
== MutantObject
)
382 /* Check its signal state or if we own it */
383 if ((CurrentObject
->Header
.SignalState
> 0) ||
384 (Thread
== CurrentObject
->OwnerThread
))
386 /* Just unwait this guy and exit */
387 if (CurrentObject
->Header
.SignalState
!= (LONG
)MINLONG
)
389 /* It has a normal signal state. Unwait and return */
390 KiSatisfyMutantWait(CurrentObject
, Thread
);
391 WaitStatus
= Thread
->WaitStatus
;
396 /* Raise an exception */
397 KiReleaseDispatcherLock(Thread
->WaitIrql
);
398 ExRaiseStatus(STATUS_MUTANT_LIMIT_EXCEEDED
);
402 else if (CurrentObject
->Header
.SignalState
> 0)
404 /* Another satisfied object */
405 KiSatisfyNonMutantWait(CurrentObject
);
406 WaitStatus
= STATUS_WAIT_0
;
410 /* Make sure we can satisfy the Alertable request */
411 WaitStatus
= KiCheckAlertability(Thread
, Alertable
, WaitMode
);
412 if (WaitStatus
!= STATUS_WAIT_0
) break;
414 /* Enable the Timeout Timer if there was any specified */
417 /* Check if the timer expired */
418 InterruptTime
.QuadPart
= KeQueryInterruptTime();
419 if ((ULONGLONG
)InterruptTime
.QuadPart
>=
420 Timer
->DueTime
.QuadPart
)
422 /* It did, so we don't need to wait */
423 WaitStatus
= STATUS_TIMEOUT
;
427 /* It didn't, so activate it */
428 Timer
->Header
.Inserted
= TRUE
;
431 /* Link the Object to this Wait Block */
432 InsertTailList(&CurrentObject
->Header
.WaitListHead
,
433 &WaitBlock
->WaitListEntry
);
435 /* Handle Kernel Queues */
436 if (Thread
->Queue
) KiActivateWaiterQueue(Thread
->Queue
);
438 /* Setup the wait information */
439 Thread
->State
= Waiting
;
441 /* Add the thread to the wait list */
442 KiAddThreadToWaitList(Thread
, Swappable
);
444 /* Activate thread swap */
445 ASSERT(Thread
->WaitIrql
<= DISPATCH_LEVEL
);
446 KiSetThreadSwapBusy(Thread
);
448 /* Check if we have a timer */
452 KiInsertWaitTimer(Timer
);
456 /* Otherwise, unlock the dispatcher */
457 KiReleaseDispatcherLockFromDpcLevel();
460 /* Do the actual swap */
461 WaitStatus
= KiSwapThread(Thread
, KeGetCurrentPrcb());
463 /* Check if we were executing an APC */
464 if (WaitStatus
!= STATUS_KERNEL_APC
) return WaitStatus
;
466 /* Check if we had a timeout */
469 /* Recalculate due times */
470 Timeout
= KiRecalculateDueTime(OriginalDueTime
,
476 /* Setup a new wait */
477 Thread
->WaitIrql
= KeRaiseIrqlToSynchLevel();
478 KxSingleThreadWait();
479 KiAcquireDispatcherLockAtDpcLevel();
483 KiReleaseDispatcherLock(Thread
->WaitIrql
);
487 /* Release dispatcher lock but maintain high IRQL */
488 KiReleaseDispatcherLockFromDpcLevel();
490 /* Adjust the Quantum and return the wait status */
491 KiAdjustQuantumThread(Thread
);
500 KeWaitForMultipleObjects(IN ULONG Count
,
502 IN WAIT_TYPE WaitType
,
503 IN KWAIT_REASON WaitReason
,
504 IN KPROCESSOR_MODE WaitMode
,
505 IN BOOLEAN Alertable
,
506 IN PLARGE_INTEGER Timeout OPTIONAL
,
507 OUT PKWAIT_BLOCK WaitBlockArray OPTIONAL
)
509 PKMUTANT CurrentObject
;
510 PKWAIT_BLOCK WaitBlock
;
511 PKTHREAD Thread
= KeGetCurrentThread();
512 PKWAIT_BLOCK TimerBlock
= &Thread
->WaitBlock
[TIMER_WAIT_BLOCK
];
513 PKTIMER Timer
= &Thread
->Timer
;
514 NTSTATUS WaitStatus
= STATUS_SUCCESS
;
516 PLARGE_INTEGER OriginalDueTime
= Timeout
;
517 LARGE_INTEGER DueTime
, NewDueTime
, InterruptTime
;
520 /* Make sure the Wait Count is valid */
523 /* Check in regards to the Thread Object Limit */
524 if (Count
> THREAD_WAIT_OBJECTS
)
527 KEBUGCHECK(MAXIMUM_WAIT_OBJECTS_EXCEEDED
);
530 /* Use the Thread's Wait Block */
531 WaitBlockArray
= &Thread
->WaitBlock
[0];
535 /* Using our own Block Array, so check with the System Object Limit */
536 if (Count
> MAXIMUM_WAIT_OBJECTS
)
539 KEBUGCHECK(MAXIMUM_WAIT_OBJECTS_EXCEEDED
);
546 /* Check if the lock is already held */
547 if (!Thread
->WaitNext
) goto WaitStart
;
549 /* Otherwise, we already have the lock, so initialize the wait */
550 Thread
->WaitNext
= FALSE
;
553 /* Start wait loop */
556 /* Disable pre-emption */
557 Thread
->Preempted
= FALSE
;
559 /* Check if a kernel APC is pending and we're below APC_LEVEL */
560 if ((Thread
->ApcState
.KernelApcPending
) && !(Thread
->SpecialApcDisable
) &&
561 (Thread
->WaitIrql
< APC_LEVEL
))
563 /* Unlock the dispatcher */
564 KiReleaseDispatcherLock(Thread
->WaitIrql
);
568 /* Check what kind of wait this is */
570 if (WaitType
== WaitAny
)
575 /* Get the Current Object */
576 CurrentObject
= (PKMUTANT
)Object
[Index
];
577 ASSERT(CurrentObject
->Header
.Type
!= QueueObject
);
579 /* Check if the Object is a mutant */
580 if (CurrentObject
->Header
.Type
== MutantObject
)
582 /* Check if it's signaled */
583 if ((CurrentObject
->Header
.SignalState
> 0) ||
584 (Thread
== CurrentObject
->OwnerThread
))
586 /* This is a Wait Any, so unwait this and exit */
587 if (CurrentObject
->Header
.SignalState
!=
590 /* Normal signal state, unwait it and return */
591 KiSatisfyMutantWait(CurrentObject
, Thread
);
592 WaitStatus
= Thread
->WaitStatus
| Index
;
597 /* Raise an exception (see wasm.ru) */
598 KiReleaseDispatcherLock(Thread
->WaitIrql
);
599 ExRaiseStatus(STATUS_MUTANT_LIMIT_EXCEEDED
);
603 else if (CurrentObject
->Header
.SignalState
> 0)
605 /* Another signaled object, unwait and return */
606 KiSatisfyNonMutantWait(CurrentObject
);
611 /* Go to the next block */
613 } while (Index
< Count
);
620 /* Get the Current Object */
621 CurrentObject
= (PKMUTANT
)Object
[Index
];
622 ASSERT(CurrentObject
->Header
.Type
!= QueueObject
);
624 /* Check if we're dealing with a mutant again */
625 if (CurrentObject
->Header
.Type
== MutantObject
)
627 /* Check if it has an invalid count */
628 if ((Thread
== CurrentObject
->OwnerThread
) &&
629 (CurrentObject
->Header
.SignalState
== MINLONG
))
631 /* Raise an exception */
632 KiReleaseDispatcherLock(Thread
->WaitIrql
);
633 ExRaiseStatus(STATUS_MUTANT_LIMIT_EXCEEDED
);
635 else if ((CurrentObject
->Header
.SignalState
<= 0) &&
636 (Thread
!= CurrentObject
->OwnerThread
))
638 /* We don't own it, can't satisfy the wait */
642 else if (CurrentObject
->Header
.SignalState
<= 0)
644 /* Not signaled, can't satisfy */
648 /* Go to the next block */
650 } while (Index
< Count
);
652 /* Check if we've went through all the objects */
655 /* Loop wait blocks */
656 WaitBlock
= WaitBlockArray
;
659 /* Get the object and satisfy it */
660 CurrentObject
= (PKMUTANT
)WaitBlock
->Object
;
661 KiSatisfyObjectWait(CurrentObject
, Thread
);
663 /* Go to the next block */
664 WaitBlock
= WaitBlock
->NextWaitBlock
;
665 } while(WaitBlock
!= WaitBlockArray
);
667 /* Set the wait status and get out */
668 WaitStatus
= Thread
->WaitStatus
;
673 /* Make sure we can satisfy the Alertable request */
674 WaitStatus
= KiCheckAlertability(Thread
, Alertable
, WaitMode
);
675 if (WaitStatus
!= STATUS_WAIT_0
) break;
677 /* Enable the Timeout Timer if there was any specified */
680 /* Check if the timer expired */
681 InterruptTime
.QuadPart
= KeQueryInterruptTime();
682 if ((ULONGLONG
)InterruptTime
.QuadPart
>=
683 Timer
->DueTime
.QuadPart
)
685 /* It did, so we don't need to wait */
686 WaitStatus
= STATUS_TIMEOUT
;
690 /* It didn't, so activate it */
691 Timer
->Header
.Inserted
= TRUE
;
693 /* Link the wait blocks */
694 WaitBlock
->NextWaitBlock
= TimerBlock
;
697 /* Insert into Object's Wait List*/
698 WaitBlock
= WaitBlockArray
;
701 /* Get the Current Object */
702 CurrentObject
= WaitBlock
->Object
;
704 /* Link the Object to this Wait Block */
705 InsertTailList(&CurrentObject
->Header
.WaitListHead
,
706 &WaitBlock
->WaitListEntry
);
708 /* Move to the next Wait Block */
709 WaitBlock
= WaitBlock
->NextWaitBlock
;
710 } while (WaitBlock
!= WaitBlockArray
);
712 /* Handle Kernel Queues */
713 if (Thread
->Queue
) KiActivateWaiterQueue(Thread
->Queue
);
715 /* Setup the wait information */
716 Thread
->State
= Waiting
;
718 /* Add the thread to the wait list */
719 KiAddThreadToWaitList(Thread
, Swappable
);
721 /* Activate thread swap */
722 ASSERT(Thread
->WaitIrql
<= DISPATCH_LEVEL
);
723 KiSetThreadSwapBusy(Thread
);
725 /* Check if we have a timer */
729 KiInsertWaitTimer(Timer
);
733 /* Otherwise, unlock the dispatcher */
734 KiReleaseDispatcherLockFromDpcLevel();
737 /* Swap the thread */
738 WaitStatus
= KiSwapThread(Thread
, KeGetCurrentPrcb());
740 /* Check if we were executing an APC */
741 if (WaitStatus
!= STATUS_KERNEL_APC
) return WaitStatus
;
743 /* Check if we had a timeout */
746 /* Recalculate due times */
747 Timeout
= KiRecalculateDueTime(OriginalDueTime
,
754 /* Setup a new wait */
755 Thread
->WaitIrql
= KeRaiseIrqlToSynchLevel();
757 KiAcquireDispatcherLockAtDpcLevel();
761 KiReleaseDispatcherLock(Thread
->WaitIrql
);
765 /* Release dispatcher lock but maintain high IRQL */
766 KiReleaseDispatcherLockFromDpcLevel();
768 /* Adjust the Quantum and return the wait status */
769 KiAdjustQuantumThread(Thread
);
775 NtDelayExecution(IN BOOLEAN Alertable
,
776 IN PLARGE_INTEGER DelayInterval
)
778 KPROCESSOR_MODE PreviousMode
= ExGetPreviousMode();
779 LARGE_INTEGER SafeInterval
;
780 NTSTATUS Status
= STATUS_SUCCESS
;
782 /* Check the previous mode */
783 if(PreviousMode
!= KernelMode
)
785 /* Enter SEH for probing */
788 /* Probe and capture the time out */
789 SafeInterval
= ProbeForReadLargeInteger(DelayInterval
);
790 DelayInterval
= &SafeInterval
;
794 /* Get SEH exception */
795 Status
= _SEH_GetExceptionCode();
798 if (!NT_SUCCESS(Status
)) return Status
;
801 /* Call the Kernel Function */
802 Status
= KeDelayExecutionThread(PreviousMode
,