NT4/private/ntos/ke/wait.c

1777 lines
54 KiB
C
Raw Normal View History

2001-01-01 00:00:00 +01:00
/*++
Copyright (c) 1989 Microsoft Corporation
Module Name:
wait.c
Abstract:
This module implements the generic kernel wait routines. Functions
are provided to wait for a single object, wait for multiple objects,
wait for event pair low, wait for event pair high, release and wait
for semaphore, and to delay thread execution.
N.B. This module is written to be a fast as possible and not as small
as possible. Therefore some code sequences are duplicated to avoid
procedure calls. It would also be possible to combine wait for
single object into wait for multiple objects at the cost of some
speed. Since wait for single object is the most common case, the
two routines have been separated.
Author:
David N. Cutler (davec) 23-Mar-89
Environment:
Kernel mode only.
Revision History:
--*/
#include "ki.h"
//
// Test for alertable condition.
//
// If alertable is TRUE and the thread is alerted for a processor
// mode that is equal to the wait mode, then return immediately
// with a wait completion status of ALERTED.
//
// Else if alertable is TRUE, the wait mode is user, and the user APC
// queue is not empty, then set user APC pending, and return immediately
// with a wait completion status of USER_APC.
//
// Else if alertable is TRUE and the thread is alerted for kernel
// mode, then return immediately with a wait completion status of
// ALERTED.
//
// Else if alertable is FALSE and the wait mode is user and there is a
// user APC pending, then return immediately with a wait completion
// status of USER_APC.
//
#define TestForAlertPending(Alertable) \
if (Alertable) { \
if (Thread->Alerted[WaitMode] != FALSE) { \
Thread->Alerted[WaitMode] = FALSE; \
WaitStatus = STATUS_ALERTED; \
break; \
} else if ((WaitMode != KernelMode) && \
(IsListEmpty(&Thread->ApcState.ApcListHead[UserMode])) == FALSE) { \
Thread->ApcState.UserApcPending = TRUE; \
WaitStatus = STATUS_USER_APC; \
break; \
} else if (Thread->Alerted[KernelMode] != FALSE) { \
Thread->Alerted[KernelMode] = FALSE; \
WaitStatus = STATUS_ALERTED; \
break; \
} \
} else if ((WaitMode != KernelMode) && (Thread->ApcState.UserApcPending)) { \
WaitStatus = STATUS_USER_APC; \
break; \
}
NTSTATUS
KeDelayExecutionThread (
IN KPROCESSOR_MODE WaitMode,
IN BOOLEAN Alertable,
IN PLARGE_INTEGER Interval
)
/*++
Routine Description:
This function delays the execution of the current thread for the specified
interval of time.
Arguments:
WaitMode - Supplies the processor mode in which the delay is to occur.
Alertable - Supplies a boolean value that specifies whether the delay
is alertable.
Interval - Supplies a pointer to the absolute or relative time over which
the delay is to occur.
Return Value:
The wait completion status. A value of STATUS_SUCCESS is returned if
the delay occurred. A value of STATUS_ALERTED is returned if the wait
was aborted to deliver an alert to the current thread. A value of
STATUS_USER_APC is returned if the wait was aborted to deliver a user
APC to the current thread.
--*/
{
LARGE_INTEGER NewTime;
PLARGE_INTEGER OriginalTime;
PKPRCB Prcb;
KPRIORITY Priority;
PRKQUEUE Queue;
PRKTHREAD Thread;
PRKTIMER Timer;
PKWAIT_BLOCK WaitBlock;
NTSTATUS WaitStatus;
//
// If the dispatcher database lock is not already held, then set the wait
// IRQL and lock the dispatcher database. Else set boolean wait variable
// to FALSE.
//
Thread = KeGetCurrentThread();
if (Thread->WaitNext) {
Thread->WaitNext = FALSE;
} else {
KiLockDispatcherDatabase(&Thread->WaitIrql);
}
//
// Start of delay loop.
//
// Note this loop is repeated if a kernel APC is delivered in the middle
// of the delay or a kernel APC is pending on the first attempt through
// the loop.
//
OriginalTime = Interval;
WaitBlock = &Thread->WaitBlock[TIMER_WAIT_BLOCK];
do {
//
// Test to determine if a kernel APC is pending.
//
// If a kernel APC is pending and the previous IRQL was less than
// APC_LEVEL, then a kernel APC was queued by another processor just
// after IRQL was raised to DISPATCH_LEVEL, but before the dispatcher
// database was locked.
//
// N.B. that this can only happen in a multiprocessor system.
//
if (Thread->ApcState.KernelApcPending && (Thread->WaitIrql < APC_LEVEL)) {
//
// Unlock the dispatcher database and lower IRQL to its previous
// value. An APC interrupt will immediately occur which will result
// in the delivery of the kernel APC if possible.
//
KiUnlockDispatcherDatabase(Thread->WaitIrql);
} else {
//
// Test for alert pending.
//
TestForAlertPending(Alertable);
//
// Initialize wait block, insert wait block in timer wait list,
// insert timer in timer queue, put thread in wait state, select
// next thread to execute, and context switch to next thread.
//
// N.B. The timer wait block is initialized when the respective
// thread is initialized. Thus the constant fields are not
// reinitialized. These include the wait object, wait key,
// wait type, and the wait list entry link pointers.
//
Thread->WaitBlockList = WaitBlock;
Thread->WaitStatus = (NTSTATUS)0;
Timer = &Thread->Timer;
WaitBlock->NextWaitBlock = WaitBlock;
Timer->Header.WaitListHead.Flink = &WaitBlock->WaitListEntry;
Timer->Header.WaitListHead.Blink = &WaitBlock->WaitListEntry;
//
// If the timer is inserted in the timer tree, then place the
// current thread in a wait state. Otherwise, attempt to force
// the current thread to yield the processor to another thread.
//
if (KiInsertTreeTimer(Timer, *Interval) == FALSE) {
//
// If the thread is not a realtime thread, then drop the
// thread priority to the base priority.
//
Prcb = KeGetCurrentPrcb();
Priority = Thread->Priority;
if (Priority < LOW_REALTIME_PRIORITY) {
if (Priority != Thread->BasePriority) {
Thread->PriorityDecrement = 0;
KiSetPriorityThread(Thread, Thread->BasePriority);
}
}
//
// If a new thread has not been selected, the attempt to round
// robin the thread with other threads at the same priority.
//
if (Prcb->NextThread == NULL) {
Prcb->NextThread = KiFindReadyThread(Thread->NextProcessor,
Thread->Priority);
}
//
// If a new thread has been selected for execution, then
// switch immediately to the selected thread.
//
if (Prcb->NextThread != NULL) {
//
// Give the current thread a new qunatum and switch
// context to selected thread.
//
// N.B. Control is returned at the original IRQL.
//
ASSERT(KeIsExecutingDpc() == FALSE);
ASSERT(Thread->WaitIrql <= DISPATCH_LEVEL);
Thread->Preempted = FALSE;
Thread->Quantum = Thread->ApcState.Process->ThreadQuantum;
KiReadyThread(Thread);
WaitStatus = KiSwapThread();
goto WaitComplete;
} else {
WaitStatus = (NTSTATUS)STATUS_SUCCESS;
break;
}
}
//
// If the current thread is processing a queue entry, then attempt
// to activate another thread that is blocked on the queue object.
//
Queue = Thread->Queue;
if (Queue != NULL) {
KiActivateWaiterQueue(Queue);
}
//
// Set the thread wait parameters, set the thread dispatcher state
// to Waiting, and insert the thread in the wait list.
//
Thread->Alertable = Alertable;
Thread->WaitMode = WaitMode;
Thread->WaitReason = DelayExecution;
Thread->WaitTime= KiQueryLowTickCount();
Thread->State = Waiting;
KiInsertWaitList(WaitMode, Thread);
//
// Switch context to selected thread.
//
// N.B. Control is returned at the original IRQL.
//
ASSERT(KeIsExecutingDpc() == FALSE);
ASSERT(Thread->WaitIrql <= DISPATCH_LEVEL);
WaitStatus = KiSwapThread();
//
// If the thread was not awakened to deliver a kernel mode APC,
// then return the wait status.
//
WaitComplete:
if (WaitStatus != STATUS_KERNEL_APC) {
if (WaitStatus == STATUS_TIMEOUT) {
WaitStatus = STATUS_SUCCESS;
}
return WaitStatus;
}
//
// Reduce the time remaining before the time delay expires.
//
Interval = KiComputeWaitInterval(Timer, OriginalTime, &NewTime);
}
//
// Raise IRQL to DISPATCH_LEVEL and lock the dispatcher database.
//
KiLockDispatcherDatabase(&Thread->WaitIrql);
} while (TRUE);
//
// The thread is alerted or a user APC should be delivered. Unlock the
// dispatcher database, lower IRQL to its previous value, and return the
// wait status.
//
KiUnlockDispatcherDatabase(Thread->WaitIrql);
return WaitStatus;
}
NTSTATUS
KeReleaseWaitForSemaphore (
IN PKSEMAPHORE Server,
IN PKSEMAPHORE Client,
IN ULONG WaitReason,
IN ULONG WaitMode
)
/*++
Routine Description:
This function releases a semaphore and waits on another semaphore. The
wait is performed such that an optimal switch to the waiting thread
occurs if possible. No timeout is associated with the wait, and thus,
the issuing thread will wait until the semaphore is signaled or an APC
is delivered.
Arguments:
Server - Supplies a pointer to a dispatcher object of type semaphore.
Client - Supplies a pointer to a dispatcher object of type semaphore.
WaitReason - Supplies the reason for the wait.
WaitMode - Supplies the processor mode in which the wait is to occur.
Return Value:
The wait completion status. A value of STATUS_SUCCESS is returned if
the specified object satisfied the wait. A value of STATUS_USER_APC is
returned if the wait was aborted to deliver a user APC to the current
thread.
--*/
{
PRKTHREAD NextThread;
LONG OldState;
PRKQUEUE Queue;
PRKTHREAD Thread;
PKWAIT_BLOCK WaitBlock;
PLIST_ENTRY WaitEntry;
//
// Raise the IRQL to dispatch level and lock the dispatcher database.
//
Thread = KeGetCurrentThread();
ASSERT(Thread->WaitNext == FALSE);
KiLockDispatcherDatabase(&Thread->WaitIrql);
//
// If the client semaphore is not in the Signaled state and the server
// semaphore wait queue is not empty, then attempt a direct dispatch
// to the target thread.
//
if ((Client->Header.SignalState == 0) &&
(IsListEmpty(&Server->Header.WaitListHead) == FALSE)) {
//
// Get the address of the first waiting server thread.
//
WaitEntry = Server->Header.WaitListHead.Flink;
WaitBlock = CONTAINING_RECORD(WaitEntry, KWAIT_BLOCK, WaitListEntry);
NextThread = WaitBlock->Thread;
//
// Remove the wait block from the semaphore wait list and remove the
// target thread from the system wait list.
//
RemoveEntryList(&WaitBlock->WaitListEntry);
RemoveEntryList(&NextThread->WaitListEntry);
//
// If the next thread is processing a queue entry, then increment
// the current number of threads.
//
Queue = NextThread->Queue;
if (Queue != NULL) {
Queue->CurrentCount += 1;
}
//
// Attempt to switch directly to the target thread.
//
return KiSwitchToThread(NextThread, WaitReason, WaitMode, Client);
} else {
//
// If the server sempahore is at the maximum limit, then unlock the
// dispatcher database and raise an exception.
//
OldState = Server->Header.SignalState;
if (OldState == Server->Limit) {
KiUnlockDispatcherDatabase(Thread->WaitIrql);
ExRaiseStatus(STATUS_SEMAPHORE_LIMIT_EXCEEDED);
}
//
// Signal the server semaphore and test to determine if any wait can be
// satisfied.
//
Server->Header.SignalState += 1;
if ((OldState == 0) && (IsListEmpty(&Server->Header.WaitListHead) == FALSE)) {
KiWaitTest(Server, 1);
}
//
// Continue the semaphore wait and return the wait completion status.
//
// N.B. The wait continuation routine is called with the dispatcher
// database locked.
//
return KiContinueClientWait(Client, WaitReason, WaitMode);
}
}
NTSTATUS
KeWaitForMultipleObjects (
IN ULONG Count,
IN PVOID Object[],
IN WAIT_TYPE WaitType,
IN KWAIT_REASON WaitReason,
IN KPROCESSOR_MODE WaitMode,
IN BOOLEAN Alertable,
IN PLARGE_INTEGER Timeout OPTIONAL,
IN PKWAIT_BLOCK WaitBlockArray OPTIONAL
)
/*++
Routine Description:
This function waits until the specified objects attain a state of
Signaled. The wait can be specified to wait until all of the objects
attain a state of Signaled or until one of the objects attains a state
of Signaled. An optional timeout can also be specified. If a timeout
is not specified, then the wait will not be satisfied until the objects
attain a state of Signaled. If a timeout is specified, and the objects
have not attained a state of Signaled when the timeout expires, then
the wait is automatically satisfied. If an explicit timeout value of
zero is specified, then no wait will occur if the wait cannot be satisfied
immediately. The wait can also be specified as alertable.
Arguments:
Count - Supplies a count of the number of objects that are to be waited
on.
Object[] - Supplies an array of pointers to dispatcher objects.
WaitType - Supplies the type of wait to perform (WaitAll, WaitAny).
WaitReason - Supplies the reason for the wait.
WaitMode - Supplies the processor mode in which the wait is to occur.
Alertable - Supplies a boolean value that specifies whether the wait is
alertable.
Timeout - Supplies a pointer to an optional absolute of relative time over
which the wait is to occur.
WaitBlockArray - Supplies an optional pointer to an array of wait blocks
that are to used to describe the wait operation.
Return Value:
The wait completion status. A value of STATUS_TIMEOUT is returned if a
timeout occurred. The index of the object (zero based) in the object
pointer array is returned if an object satisfied the wait. A value of
STATUS_ALERTED is returned if the wait was aborted to deliver an alert
to the current thread. A value of STATUS_USER_APC is returned if the
wait was aborted to deliver a user APC to the current thread.
--*/
{
ULONG Index;
LARGE_INTEGER NewTime;
PRKTHREAD NextThread;
PKMUTANT Objectx;
PLARGE_INTEGER OriginalTime;
PRKQUEUE Queue;
PRKTHREAD Thread;
PRKTIMER Timer;
PRKWAIT_BLOCK WaitBlock;
BOOLEAN WaitSatisfied;
NTSTATUS WaitStatus;
PKWAIT_BLOCK WaitTimer;
//
// If the dispatcher database lock is not already held, then set the wait
// IRQL and lock the dispatcher database. Else set boolean wait variable
// to FALSE.
//
Thread = KeGetCurrentThread();
if (Thread->WaitNext) {
Thread->WaitNext = FALSE;
} else {
KiLockDispatcherDatabase(&Thread->WaitIrql);
}
//
// If a wait block array has been specified, then the maximum number of
// objects that can be waited on is specified by MAXIMUM_WAIT_OBJECTS.
// Otherwise the builtin wait blocks in the thread object are used and
// the maximum number of objects that can be waited on is specified by
// THREAD_WAIT_OBJECTS. If the specified number of objects is not within
// limits, then bug check.
//
if (ARGUMENT_PRESENT(WaitBlockArray)) {
if (Count > MAXIMUM_WAIT_OBJECTS) {
KeBugCheck(MAXIMUM_WAIT_OBJECTS_EXCEEDED);
}
} else {
if (Count > THREAD_WAIT_OBJECTS) {
KeBugCheck(MAXIMUM_WAIT_OBJECTS_EXCEEDED);
}
WaitBlockArray = &Thread->WaitBlock[0];
}
//
// Start of wait loop.
//
// Note this loop is repeated if a kernel APC is delivered in the middle
// of the wait or a kernel APC is pending on the first attempt through
// the loop.
//
OriginalTime = Timeout;
do {
//
// Set address of wait block list in thread object.
//
Thread->WaitBlockList = WaitBlockArray;
//
// Test to determine if a kernel APC is pending.
//
// If a kernel APC is pending and the previous IRQL was less than
// APC_LEVEL, then a kernel APC was queued by another processor just
// after IRQL was raised to DISPATCH_LEVEL, but before the dispatcher
// database was locked.
//
// N.B. that this can only happen in a multiprocessor system.
//
if (Thread->ApcState.KernelApcPending && (Thread->WaitIrql < APC_LEVEL)) {
//
// Unlock the dispatcher database and lower IRQL to its previous
// value. An APC interrupt will immediately occur which will result
// in the delivery of the kernel APC if possible.
//
KiUnlockDispatcherDatabase(Thread->WaitIrql);
} else {
//
// Construct wait blocks and check to determine if the wait is
// already satisfied. If the wait is satisfied, then perform
// wait completion and return. Else put current thread in a wait
// state if an explicit timeout value of zero is not specified.
//
Thread->WaitStatus = (NTSTATUS)0;
WaitSatisfied = TRUE;
for (Index = 0; Index < Count; Index += 1) {
//
// Test if wait can be satisfied immediately.
//
Objectx = (PKMUTANT)Object[Index];
ASSERT(Objectx->Header.Type != QueueObject);
if (WaitType == WaitAny) {
//
// If the object is a mutant object and the mutant object
// has been recursively acquired MINLONG times, then raise
// an exception. Otherwise if the signal state of the mutant
// object is greater than zero, or the current thread is
// the owner of the mutant object, then satisfy the wait.
//
if (Objectx->Header.Type == MutantObject) {
if ((Objectx->Header.SignalState > 0) ||
(Thread == Objectx->OwnerThread)) {
if (Objectx->Header.SignalState != MINLONG) {
KiWaitSatisfyMutant(Objectx, Thread);
WaitStatus = (NTSTATUS)(Index) | Thread->WaitStatus;
KiUnlockDispatcherDatabase(Thread->WaitIrql);
return WaitStatus;
} else {
KiUnlockDispatcherDatabase(Thread->WaitIrql);
ExRaiseStatus(STATUS_MUTANT_LIMIT_EXCEEDED);
}
}
//
// If the signal state is greater than zero, then satisfy
// the wait.
//
} else if (Objectx->Header.SignalState > 0) {
KiWaitSatisfyOther(Objectx);
KiUnlockDispatcherDatabase(Thread->WaitIrql);
return (NTSTATUS)(Index);
}
} else {
//
// If the object is a mutant object and the mutant object
// has been recursively acquired MAXLONG times, then raise
// an exception. Otherwise if the signal state of the mutant
// object is less than or equal to zero and the current
// thread is not the owner of the mutant object, then the
// wait cannot be satisfied.
//
if (Objectx->Header.Type == MutantObject) {
if ((Thread == Objectx->OwnerThread) &&
(Objectx->Header.SignalState == MINLONG)) {
KiUnlockDispatcherDatabase(Thread->WaitIrql);
ExRaiseStatus(STATUS_MUTANT_LIMIT_EXCEEDED);
} else if ((Objectx->Header.SignalState <= 0) &&
(Thread != Objectx->OwnerThread)) {
WaitSatisfied = FALSE;
}
//
// If the signal state is less than or equal to zero, then
// the wait cannot be satisfied.
//
} else if (Objectx->Header.SignalState <= 0) {
WaitSatisfied = FALSE;
}
}
//
// Construct wait block for the current object.
//
WaitBlock = &WaitBlockArray[Index];
WaitBlock->Object = (PVOID)Objectx;
WaitBlock->WaitKey = (CSHORT)(Index);
WaitBlock->WaitType = WaitType;
WaitBlock->Thread = Thread;
WaitBlock->NextWaitBlock = &WaitBlockArray[Index + 1];
}
//
// If the wait type is wait all, then check to determine if the
// wait can be satisfied immediately.
//
if ((WaitType == WaitAll) && (WaitSatisfied)) {
WaitBlock->NextWaitBlock = &WaitBlockArray[0];
KiWaitSatisfyAll(WaitBlock);
WaitStatus = Thread->WaitStatus;
break;
}
//
// Test for alert pending.
//
TestForAlertPending(Alertable);
//
// The wait cannot be satisifed immediately. Check to determine if
// a timeout value is specified.
//
if (ARGUMENT_PRESENT(Timeout)) {
//
// If the timeout value is zero, then return immediately without
// waiting.
//
if (!(Timeout->LowPart | Timeout->HighPart)) {
WaitStatus = (NTSTATUS)(STATUS_TIMEOUT);
break;
}
//
// Initialize a wait block for the thread specific timer,
// initialize timer wait list head, insert the timer in the
// timer tree, and increment the number of wait objects.
//
// N.B. The timer wait block is initialized when the respective
// thread is initialized. Thus the constant fields are not
// reinitialized. These include the wait object, wait key,
// wait type, and the wait list entry link pointers.
//
WaitTimer = &Thread->WaitBlock[TIMER_WAIT_BLOCK];
WaitBlock->NextWaitBlock = WaitTimer;
WaitBlock = WaitTimer;
Timer = &Thread->Timer;
InitializeListHead(&Timer->Header.WaitListHead);
if (KiInsertTreeTimer(Timer, *Timeout) == FALSE) {
WaitStatus = (NTSTATUS)STATUS_TIMEOUT;
break;
}
}
//
// Close up the circular list of wait control blocks.
//
WaitBlock->NextWaitBlock = &WaitBlockArray[0];
//
// Insert wait blocks in object wait lists.
//
WaitBlock = &WaitBlockArray[0];
do {
Objectx = (PKMUTANT)WaitBlock->Object;
InsertTailList(&Objectx->Header.WaitListHead, &WaitBlock->WaitListEntry);
WaitBlock = WaitBlock->NextWaitBlock;
} while (WaitBlock != &WaitBlockArray[0]);
//
// If the current thread is processing a queue entry, then attempt
// to activate another thread that is blocked on the queue object.
//
Queue = Thread->Queue;
if (Queue != NULL) {
KiActivateWaiterQueue(Queue);
}
//
// Set the thread wait parameters, set the thread dispatcher state
// to Waiting, and insert the thread in the wait list.
//
Thread->Alertable = Alertable;
Thread->WaitMode = WaitMode;
Thread->WaitReason = WaitReason;
Thread->WaitTime= KiQueryLowTickCount();
Thread->State = Waiting;
KiInsertWaitList(WaitMode, Thread);
//
// Switch context to selected thread.
//
// Control is returned at the original IRQL.
//
ASSERT(KeIsExecutingDpc() == FALSE);
ASSERT(Thread->WaitIrql <= DISPATCH_LEVEL);
WaitStatus = KiSwapThread();
//
// If the thread was not awakened to deliver a kernel mode APC,
// then the wait status.
//
if (WaitStatus != STATUS_KERNEL_APC) {
return WaitStatus;
}
if (ARGUMENT_PRESENT(Timeout)) {
//
// Reduce the amount of time remaining before timeout occurs.
//
Timeout = KiComputeWaitInterval(Timer, OriginalTime, &NewTime);
}
}
//
// Raise IRQL to DISPATCH_LEVEL and lock the dispatcher database.
//
KiLockDispatcherDatabase(&Thread->WaitIrql);
} while (TRUE);
//
// The thread is alerted, a user APC should be delivered, or the wait is
// satisfied. Unlock dispatcher database, lower IRQL to its previous value,
// and return the wait status.
//
KiUnlockDispatcherDatabase(Thread->WaitIrql);
return WaitStatus;
}
NTSTATUS
KeWaitForSingleObject (
IN PVOID Object,
IN KWAIT_REASON WaitReason,
IN KPROCESSOR_MODE WaitMode,
IN BOOLEAN Alertable,
IN PLARGE_INTEGER Timeout OPTIONAL
)
/*++
Routine Description:
This function waits until the specified object attains a state of
Signaled. An optional timeout can also be specified. If a timeout
is not specified, then the wait will not be satisfied until the object
attains a state of Signaled. If a timeout is specified, and the object
has not attained a state of Signaled when the timeout expires, then
the wait is automatically satisfied. If an explicit timeout value of
zero is specified, then no wait will occur if the wait cannot be satisfied
immediately. The wait can also be specified as alertable.
Arguments:
Object - Supplies a pointer to a dispatcher object.
WaitReason - Supplies the reason for the wait.
WaitMode - Supplies the processor mode in which the wait is to occur.
Alertable - Supplies a boolean value that specifies whether the wait is
alertable.
Timeout - Supplies a pointer to an optional absolute of relative time over
which the wait is to occur.
Return Value:
The wait completion status. A value of STATUS_TIMEOUT is returned if a
timeout occurred. A value of STATUS_SUCCESS is returned if the specified
object satisfied the wait. A value of STATUS_ALERTED is returned if the
wait was aborted to deliver an alert to the current thread. A value of
STATUS_USER_APC is returned if the wait was aborted to deliver a user
APC to the current thread.
--*/
{
LARGE_INTEGER NewTime;
PRKTHREAD NextThread;
PKMUTANT Objectx;
PLARGE_INTEGER OriginalTime;
PRKQUEUE Queue;
PRKTHREAD Thread;
PRKTIMER Timer;
PKWAIT_BLOCK WaitBlock;
NTSTATUS WaitStatus;
PKWAIT_BLOCK WaitTimer;
//
// Collect call data.
//
#if defined(_COLLECT_WAIT_SINGLE_CALLDATA_)
RECORD_CALL_DATA(&KiWaitSingleCallData);
#endif
//
// If the dispatcher database lock is not already held, then set the wait
// IRQL and lock the dispatcher database. Else set boolean wait variable
// to FALSE.
//
Thread = KeGetCurrentThread();
if (Thread->WaitNext) {
Thread->WaitNext = FALSE;
} else {
KiLockDispatcherDatabase(&Thread->WaitIrql);
}
//
// Start of wait loop.
//
// Note this loop is repeated if a kernel APC is delivered in the middle
// of the wait or a kernel APC is pending on the first attempt through
// the loop.
//
OriginalTime = Timeout;
WaitBlock = &Thread->WaitBlock[0];
do {
//
// Test to determine if a kernel APC is pending.
//
// If a kernel APC is pending and the previous IRQL was less than
// APC_LEVEL, then a kernel APC was queued by another processor just
// after IRQL was raised to DISPATCH_LEVEL, but before the dispatcher
// database was locked.
//
// N.B. that this can only happen in a multiprocessor system.
//
if (Thread->ApcState.KernelApcPending && (Thread->WaitIrql < APC_LEVEL)) {
//
// Unlock the dispatcher database and lower IRQL to its previous
// value. An APC interrupt will immediately occur which will result
// in the delivery of the kernel APC if possible.
//
KiUnlockDispatcherDatabase(Thread->WaitIrql);
} else {
//
// Test if the wait can be immediately satisfied.
//
Objectx = (PKMUTANT)Object;
Thread->WaitStatus = (NTSTATUS)0;
ASSERT(Objectx->Header.Type != QueueObject);
//
// If the object is a mutant object and the mutant object has been
// recursively acquired MINLONG times, then raise an exception.
// Otherwise if the signal state of the mutant object is greater
// than zero, or the current thread is the owner of the mutant
// object, then satisfy the wait.
//
if (Objectx->Header.Type == MutantObject) {
if ((Objectx->Header.SignalState > 0) ||
(Thread == Objectx->OwnerThread)) {
if (Objectx->Header.SignalState != MINLONG) {
KiWaitSatisfyMutant(Objectx, Thread);
WaitStatus = (NTSTATUS)(0) | Thread->WaitStatus;
break;
} else {
KiUnlockDispatcherDatabase(Thread->WaitIrql);
ExRaiseStatus(STATUS_MUTANT_LIMIT_EXCEEDED);
}
}
//
// If the signal state is greater than zero, then satisfy the wait.
//
} else if (Objectx->Header.SignalState > 0) {
KiWaitSatisfyOther(Objectx);
WaitStatus = (NTSTATUS)(0);
break;
}
//
// Construct a wait block for the object.
//
Thread->WaitBlockList = WaitBlock;
WaitBlock->Object = Object;
WaitBlock->WaitKey = (CSHORT)(STATUS_SUCCESS);
WaitBlock->WaitType = WaitAny;
//
// Test for alert pending.
//
TestForAlertPending(Alertable);
//
// The wait cannot be satisifed immediately. Check to determine if
// a timeout value is specified.
//
if (ARGUMENT_PRESENT(Timeout)) {
//
// If the timeout value is zero, then return immediately without
// waiting.
//
if (!(Timeout->LowPart | Timeout->HighPart)) {
WaitStatus = (NTSTATUS)(STATUS_TIMEOUT);
break;
}
//
// Initialize a wait block for the thread specific timer, insert
// wait block in timer wait list, insert the timer in the timer
// tree.
//
// N.B. The timer wait block is initialized when the respective
// thread is initialized. Thus the constant fields are not
// reinitialized. These include the wait object, wait key,
// wait type, and the wait list entry link pointers.
//
Timer = &Thread->Timer;
WaitTimer = &Thread->WaitBlock[TIMER_WAIT_BLOCK];
WaitBlock->NextWaitBlock = WaitTimer;
Timer->Header.WaitListHead.Flink = &WaitTimer->WaitListEntry;
Timer->Header.WaitListHead.Blink = &WaitTimer->WaitListEntry;
WaitTimer->NextWaitBlock = WaitBlock;
if (KiInsertTreeTimer(Timer, *Timeout) == FALSE) {
WaitStatus = (NTSTATUS)STATUS_TIMEOUT;
break;
}
} else {
WaitBlock->NextWaitBlock = WaitBlock;
}
//
// Insert wait block in object wait list.
//
InsertTailList(&Objectx->Header.WaitListHead, &WaitBlock->WaitListEntry);
//
// If the current thread is processing a queue entry, then attempt
// to activate another thread that is blocked on the queue object.
//
Queue = Thread->Queue;
if (Queue != NULL) {
KiActivateWaiterQueue(Queue);
}
//
// Set the thread wait parameters, set the thread dispatcher state
// to Waiting, and insert the thread in the wait list.
//
Thread->Alertable = Alertable;
Thread->WaitMode = WaitMode;
Thread->WaitReason = WaitReason;
Thread->WaitTime= KiQueryLowTickCount();
Thread->State = Waiting;
KiInsertWaitList(WaitMode, Thread);
//
// Switch context to selected thread.
//
// Control is returned at the original IRQL.
//
ASSERT(KeIsExecutingDpc() == FALSE);
ASSERT(Thread->WaitIrql <= DISPATCH_LEVEL);
WaitStatus = KiSwapThread();
//
// If the thread was not awakened to deliver a kernel mode APC,
// then return wait status.
//
if (WaitStatus != STATUS_KERNEL_APC) {
return WaitStatus;
}
if (ARGUMENT_PRESENT(Timeout)) {
//
// Reduce the amount of time remaining before timeout occurs.
//
Timeout = KiComputeWaitInterval(Timer, OriginalTime, &NewTime);
}
}
//
// Raise IRQL to DISPATCH_LEVEL and lock the dispatcher database.
//
KiLockDispatcherDatabase(&Thread->WaitIrql);
} while (TRUE);
//
// The thread is alerted, a user APC should be delivered, or the wait is
// satisfied. Unlock dispatcher database, lower IRQL to its previous value,
// and return the wait status.
//
KiUnlockDispatcherDatabase(Thread->WaitIrql);
return WaitStatus;
}
NTSTATUS
KiSetServerWaitClientEvent (
IN PKEVENT ServerEvent,
IN PKEVENT ClientEvent,
IN ULONG WaitMode
)
/*++
Routine Description:
This function sets the specified server event and waits on specified
client event. The wait is performed such that an optimal switch to
the waiting thread occurs if possible. No timeout is associated with
the wait, and thus, the issuing thread will wait until the client event
is signaled or an APC is delivered.
Arguments:
ServerEvent - Supplies a pointer to a dispatcher object of type event.
ClientEvent - Supplies a pointer to a dispatcher object of type event.
WaitMode - Supplies the processor mode in which the wait is to occur.
Return Value:
The wait completion status. A value of STATUS_SUCCESS is returned if
the specified object satisfied the wait. A value of STATUS_USER_APC is
returned if the wait was aborted to deliver a user APC to the current
thread.
--*/
{
PKTHREAD NextThread;
KPRIORITY NewPriority;
LONG OldState;
PKQUEUE Queue;
PKTHREAD Thread;
PKWAIT_BLOCK WaitBlock;
PLIST_ENTRY WaitEntry;
//
// Raise the IRQL to dispatch level and lock the dispatcher database.
//
Thread = KeGetCurrentThread();
ASSERT(Thread->WaitNext == FALSE);
KiLockDispatcherDatabase(&Thread->WaitIrql);
//
// If the client event is not in the Signaled state and the server
// event wait queue is not empty, then attempt to do a direct dispatch
// to the target thread.
//
if ((ClientEvent->Header.SignalState == 0) &&
(IsListEmpty(&ServerEvent->Header.WaitListHead) == FALSE)) {
//
// Get the address of the first waiting server thread.
//
WaitEntry = ServerEvent->Header.WaitListHead.Flink;
WaitBlock = CONTAINING_RECORD(WaitEntry, KWAIT_BLOCK, WaitListEntry);
NextThread = WaitBlock->Thread;
//
// Remove the wait block from the event wait list and remove the
// target thread from the system wait list.
//
RemoveEntryList(&WaitBlock->WaitListEntry);
RemoveEntryList(&NextThread->WaitListEntry);
//
// If the next thread is processing a queue entry, then increment
// the current number of threads.
//
Queue = NextThread->Queue;
if (Queue != NULL) {
Queue->CurrentCount += 1;
}
//
// Attempt to switch directly to the target thread.
//
return KiSwitchToThread(NextThread, WrEventPair, WaitMode, ClientEvent);
} else {
//
// Signal the server event and test to determine if any wait can be
// satisfied.
//
OldState = ServerEvent->Header.SignalState;
ServerEvent->Header.SignalState = 1;
if ((OldState == 0) && (IsListEmpty(&ServerEvent->Header.WaitListHead) == FALSE)) {
KiWaitTest(ServerEvent, 1);
}
//
// Continue the event pair wait and return the wait completion status.
//
// N.B. The wait continuation routine is called with the dispatcher
// database locked.
//
return KiContinueClientWait(ClientEvent, WrEventPair, WaitMode);
}
}
NTSTATUS
KiContinueClientWait (
IN PVOID ClientObject,
IN ULONG WaitReason,
IN ULONG WaitMode
)
/*++
Routine Description:
This function continues a wait operation that could not be completed by
a optimal switch from a client to a server.
N.B. This function is entered with the dispatcher database locked.
Arguments:
ClientEvent - Supplies a pointer to a dispatcher object of type event
or semaphore.
WaitReason - Supplies the reason for the wait operation.
WaitMode - Supplies the processor mode in which the wait is to occur.
Return Value:
The wait completion status. A value of STATUS_SUCCESS is returned if
the specified object satisfied the wait. A value of STATUS_USER_APC is
returned if the wait was aborted to deliver a user APC to the current
thread.
--*/
{
PKEVENT ClientEvent;
PRKTHREAD NextThread;
PRKQUEUE Queue;
PRKTHREAD Thread;
PKWAIT_BLOCK WaitBlock;
NTSTATUS WaitStatus;
//
// Start of wait loop.
//
// Note this loop is repeated if a kernel APC is delivered in the middle
// of the wait or a kernel APC is pending on the first attempt through
// the loop.
//
ClientEvent = (PKEVENT)ClientObject;
Thread = KeGetCurrentThread();
WaitBlock = &Thread->WaitBlock[0];
do {
//
// Set address of wait block list in thread object.
//
Thread->WaitBlockList = WaitBlock;
//
// Test to determine if a kernel APC is pending.
//
// If a kernel APC is pending and the previous IRQL was less than
// APC_LEVEL, then a kernel APC was queued by another processor just
// after IRQL was raised to DISPATCH_LEVEL, but before the dispatcher
// database was locked.
//
// N.B. that this can only happen in a multiprocessor system.
//
if (Thread->ApcState.KernelApcPending && (Thread->WaitIrql < APC_LEVEL)) {
//
// Unlock the dispatcher database and lower IRQL to its previous
// value. An APC interrupt will immediately occur which will result
// in the delivery of the kernel APC if possible.
//
KiUnlockDispatcherDatabase(Thread->WaitIrql);
} else {
//
// Test if a user APC is pending.
//
if ((WaitMode != KernelMode) && (Thread->ApcState.UserApcPending)) {
WaitStatus = STATUS_USER_APC;
break;
}
//
// Initialize the event\semaphore wait block and check to determine
// if the wait is already satisfied. If the wait is satisfied, then
// perform wait completion and return. Otherwise, put current thread
// in a wait state.
//
Thread->WaitStatus = (NTSTATUS)0;
WaitBlock->Object = ClientEvent;
WaitBlock->NextWaitBlock = WaitBlock;
WaitBlock->WaitKey = (CSHORT)(STATUS_SUCCESS);
WaitBlock->WaitType = WaitAny;
//
// If the signal state is not equal to zero, then satisfy the wait.
//
if (ClientEvent->Header.SignalState != 0) {
KiWaitSatisfyOther(ClientEvent);
WaitStatus = (NTSTATUS)(0);
break;
}
//
// Insert wait block in object wait list.
//
InsertTailList(&ClientEvent->Header.WaitListHead,
&WaitBlock->WaitListEntry);
//
// If the current thread is processing a queue entry, then attempt
// to activate another thread that is blocked on the queue object.
//
Queue = Thread->Queue;
if (Queue != NULL) {
KiActivateWaiterQueue(Queue);
}
//
// Set the thread wait parameters, set the thread dispatcher state
// to Waiting, and insert the thread in the wait list.
//
Thread->Alertable = FALSE;
Thread->WaitMode = (KPROCESSOR_MODE)WaitMode;
Thread->WaitReason = (UCHAR)WaitReason;
Thread->WaitTime= KiQueryLowTickCount();
Thread->State = Waiting;
KiInsertWaitList(WaitMode, Thread);
//
// Switch context to selected thread.
//
// Control is returned at the original IRQL.
//
WaitStatus = KiSwapThread();
//
// If the thread was not awakened to deliver a kernel mode APC,
// then return wait status.
//
if (WaitStatus != STATUS_KERNEL_APC) {
return WaitStatus;
}
}
//
// Raise IRQL to DISPATCH_LEVEL and lock the dispatcher database.
//
KiLockDispatcherDatabase(&Thread->WaitIrql);
} while (TRUE);
//
// The thread is alerted, a user APC should be delivered, or the wait is
// satisfied. Unlock dispatcher database, lower IRQL to its previous value,
// and return the wait status.
//
KiUnlockDispatcherDatabase(Thread->WaitIrql);
return WaitStatus;
}
PLARGE_INTEGER
FASTCALL
KiComputeWaitInterval (
IN PRKTIMER Timer,
IN PLARGE_INTEGER OriginalTime,
IN OUT PLARGE_INTEGER NewTime
)
/*++
Routine Description:
This function recomputes the wait interval after a thread has been
awakened to deliver a kernel APC.
Arguments:
Timer - Supplies a pointer to a dispatcher object of type timer.
OriginalTime - Supplies a pointer to the original timeout value.
NewTime - Supplies a pointer to a variable that receives the
recomputed wait interval.
Return Value:
A pointer to the new time is returned as the function value.
--*/
{
//
// If the original wait time was absolute, then return the same
// absolute time. Otherwise, reduce the wait time remaining before
// the time delay expires.
//
if (Timer->Header.Absolute != FALSE) {
return OriginalTime;
} else {
KiQueryInterruptTime(NewTime);
NewTime->QuadPart -= Timer->DueTime.QuadPart;
return NewTime;
}
}
#if !defined(_MIPS_) && !defined(_ALPHA_) && !defined(_PPC_) && !defined(_X86_)
NTSTATUS
KiSwitchToThread (
IN PKTHREAD NextThread,
IN ULONG WaitReason,
IN ULONG WaitMode,
IN PKEVENT WaitObject
)
/*++
Routine Description:
This function performs an optimal switch to the specified target thread
if possible. No timeout is associated with the wait, thus the issuing
thread will wait until the wait event is signaled or an APC is deliverd.
N.B. This routine is called with the dispatcher database locked.
N.B. The wait IRQL is assumed to be set for the current thread and the
wait status is assumed to be set for the target thread.
N.B. It is assumed that if a queue is associated with the target thread,
then the concurrency count has been incremented.
N.B. Control is returned from this function with the dispatcher database
unlocked.
Arguments:
NextThread - Supplies a pointer to a dispatcher object of type thread.
WaitReason - Supplies the reason for the wait operation.
WaitMode - Supplies the processor wait mode.
WaitObject - Supplies a pointer to a dispatcher object of type event
or semaphore.
Return Value:
The wait completion status. A value of STATUS_SUCCESS is returned if
the specified object satisfied the wait. A value of STATUS_USER_APC is
returned if the wait was aborted to deliver a user APC to the current
thread.
--*/
{
KPRIORITY NewPriority;
PKPRCB Prcb;
PKPROCESS Process;
ULONG Processor;
PKQUEUE Queue;
PKTHREAD Thread;
PKWAIT_BLOCK WaitBlock;
PLIST_ENTRY WaitEntry;
PKEVENT WaitForEvent = (PKEVENT)WaitObject;
NTSTATUS WaitStatus;
//
// If the target thread's kernel stack is resident, the target
// thread's process is in the balance set, the target thread can
// can run on the current processor, and another thread has not
// already been selected to run on the current processor, then
// do a direct dispatch to the target thread bypassing all the
// general wait logic, thread priorities permiting.
//
Prcb = KeGetCurrentPrcb();
Process = NextThread->ApcState.Process;
Thread = KeGetCurrentThread();
#if !defined(NT_UP)
Processor = Thread->NextProcessor;
#endif
if ((NextThread->KernelStackResident != FALSE) &&
#if !defined(NT_UP)
((NextThread->Affinity & (1 << Processor)) != 0) &&
(Prcb->NextThread == NULL) &&
#endif
(Process->State == ProcessInMemory)) {
//
// Compute the new thread priority and check if a direct switch
// to the target thread can be made.
//
if (Thread->Priority < LOW_REALTIME_PRIORITY) {
if (NextThread->Priority < LOW_REALTIME_PRIORITY) {
//
// Both the current and target threads run at a variable
// priority level. If the target thread is not running
// at a boosted level, then attempt to boost its priority
// to a level that is equal or greater than the current
// thread.
//
if (NextThread->PriorityDecrement == 0) {
//
// The target thread is not running at a boosted level.
//
NewPriority = NextThread->BasePriority + 1;
if (NewPriority >= Thread->Priority) {
if (NewPriority >= LOW_REALTIME_PRIORITY) {
NextThread->Priority = LOW_REALTIME_PRIORITY - 1;
} else {
NextThread->Priority = (SCHAR)NewPriority;
}
} else {
if (NextThread->BasePriority >= BASE_PRIORITY_THRESHOLD) {
NextThread->PriorityDecrement =
Thread->Priority - NextThread->BasePriority;
NextThread->DecrementCount = ROUND_TRIP_DECREMENT_COUNT;
NextThread->Priority = Thread->Priority;
} else {
NextThread->Priority = (SCHAR)NewPriority;
goto LongWay;
}
}
} else {
//
// The target thread is running at a boosted level.
//
NextThread->DecrementCount -= 1;
if (NextThread->DecrementCount == 0) {
NextThread->Priority = NextThread->BasePriority;
NextThread->PriorityDecrement = 0;
goto LongWay;
}
if (NextThread->Priority < Thread->Priority) {
goto LongWay;
}
}
} else {
//
// The current thread runs at a variable priority level
// and the target thread runs at a realtime priority
// level. A direct switch to the target thread can be
// made.
//
NextThread->Quantum = Process->ThreadQuantum;
}
} else {
//
// The current thread runs in at a realtime priority level.
// If the priority of the current thread is less than or
// equal to the priority of the target thread, then a direct
// switch to the target thread can be made.
//
if (NextThread->Priority < Thread->Priority) {
goto LongWay;
}
NextThread->Quantum = Process->ThreadQuantum;
}
//
// Set the next processor number.
//
#if !defined(NT_UP)
NextThread->NextProcessor = (CCHAR)Processor;
#endif
//
// Initialization the event wait block and insert the wait block
// in the wait for event wait list.
//
WaitBlock = &Thread->WaitBlock[0];
Thread->WaitBlockList = WaitBlock;
Thread->WaitStatus = (NTSTATUS)0;
WaitBlock->Object = WaitForEvent;
WaitBlock->NextWaitBlock = WaitBlock;
WaitBlock->WaitKey = (CSHORT)(STATUS_SUCCESS);
WaitBlock->WaitType = WaitAny;
InsertTailList(&WaitForEvent->Header.WaitListHead,
&WaitBlock->WaitListEntry);
//
// If the current thread is processing a queue entry, then attempt
// to activate another thread that is blocked on the queue object.
//
Queue = Thread->Queue;
Prcb->NextThread = NextThread;
if (Queue != NULL) {
KiActivateWaiterQueue(Queue);
}
//
// Set the current thread wait parameters, set the thread state
// to Waiting, and insert the thread in the wait list.
//
// N.B. It is not necessary to increment and decrement the wait
// reason count since both the server and the client have
// the same wait reason.
//
Thread->Alertable = FALSE;
Thread->WaitMode = (KPROCESSOR_MODE)WaitMode;
Thread->WaitReason = (UCHAR)WaitReason;
Thread->WaitTime= KiQueryLowTickCount();
Thread->State = Waiting;
KiInsertWaitList(WaitMode, Thread);
//
// Switch context to target thread.
//
// Control is returned at the original IRQL.
//
WaitStatus = KiSwapThread();
//
// If the thread was not awakened to deliver a kernel mode APC,
// then return wait status.
//
if (WaitStatus != STATUS_KERNEL_APC) {
return WaitStatus;
}
//
// Raise IRQL to DISPATCH_LEVEL and lock the dispatcher database.
//
KiLockDispatcherDatabase(&Thread->WaitIrql);
goto ContinueWait;
}
//
// Ready the target thrread for execution and wait on the specified
// object.
//
LongWay:
KiReadyThread(NextThread);
//
// Continue the wait and return the wait completion status.
//
// N.B. The wait continuation routine is called with the dispatcher
// database locked.
//
ContinueWait:
return KiContinueClientWait(WaitForEvent, WaitReason, WaitMode);
}
#endif