mirror of
https://github.com/reactos/reactos.git
synced 2024-11-18 21:13:52 +00:00
913 lines
29 KiB
C
913 lines
29 KiB
C
/*
|
|
* PROJECT: ReactOS Kernel
|
|
* LICENSE: GPL - See COPYING in the top level directory
|
|
* FILE: ntoskrnl/ke/wait.c
|
|
* PURPOSE: Manages waiting for Dispatcher Objects
|
|
* PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org)
|
|
* Gunnar Dalsnes
|
|
*/
|
|
|
|
/* INCLUDES ******************************************************************/
|
|
|
|
#include <ntoskrnl.h>
|
|
#define NDEBUG
|
|
#include <debug.h>
|
|
|
|
/* PRIVATE FUNCTIONS *********************************************************/
|
|
|
|
VOID
|
|
FASTCALL
|
|
KiWaitTest(IN PVOID ObjectPointer,
|
|
IN KPRIORITY Increment)
|
|
{
|
|
PLIST_ENTRY WaitEntry, WaitList;
|
|
PKWAIT_BLOCK WaitBlock;
|
|
PKTHREAD WaitThread;
|
|
PKMUTANT FirstObject = ObjectPointer;
|
|
NTSTATUS WaitStatus;
|
|
|
|
/* Loop the Wait Entries */
|
|
WaitList = &FirstObject->Header.WaitListHead;
|
|
WaitEntry = WaitList->Flink;
|
|
while ((FirstObject->Header.SignalState > 0) && (WaitEntry != WaitList))
|
|
{
|
|
/* Get the current wait block */
|
|
WaitBlock = CONTAINING_RECORD(WaitEntry, KWAIT_BLOCK, WaitListEntry);
|
|
WaitThread = WaitBlock->Thread;
|
|
WaitStatus = STATUS_KERNEL_APC;
|
|
|
|
/* Check the current Wait Mode */
|
|
if (WaitBlock->WaitType == WaitAny)
|
|
{
|
|
/* Easy case, satisfy only this wait */
|
|
WaitStatus = (NTSTATUS)WaitBlock->WaitKey;
|
|
KiSatisfyObjectWait(FirstObject, WaitThread);
|
|
}
|
|
|
|
/* Now do the rest of the unwait */
|
|
KiUnwaitThread(WaitThread, WaitStatus, Increment);
|
|
WaitEntry = WaitList->Flink;
|
|
}
|
|
}
|
|
|
|
VOID
|
|
FASTCALL
|
|
KiUnlinkThread(IN PKTHREAD Thread,
|
|
IN LONG_PTR WaitStatus)
|
|
{
|
|
PKWAIT_BLOCK WaitBlock;
|
|
PKTIMER Timer;
|
|
|
|
/* Update wait status */
|
|
Thread->WaitStatus |= WaitStatus;
|
|
|
|
/* Remove the Wait Blocks from the list */
|
|
WaitBlock = Thread->WaitBlockList;
|
|
do
|
|
{
|
|
/* Remove it */
|
|
RemoveEntryList(&WaitBlock->WaitListEntry);
|
|
|
|
/* Go to the next one */
|
|
WaitBlock = WaitBlock->NextWaitBlock;
|
|
} while (WaitBlock != Thread->WaitBlockList);
|
|
|
|
/* Remove the thread from the wait list! */
|
|
if (Thread->WaitListEntry.Flink) RemoveEntryList(&Thread->WaitListEntry);
|
|
|
|
/* Check if there's a Thread Timer */
|
|
Timer = &Thread->Timer;
|
|
if (Timer->Header.Inserted) KxRemoveTreeTimer(Timer);
|
|
|
|
/* Increment the Queue's active threads */
|
|
if (Thread->Queue) Thread->Queue->CurrentCount++;
|
|
}
|
|
|
|
/* Must be called with the dispatcher lock held */
|
|
VOID
|
|
FASTCALL
|
|
KiUnwaitThread(IN PKTHREAD Thread,
|
|
IN LONG_PTR WaitStatus,
|
|
IN KPRIORITY Increment)
|
|
{
|
|
/* Unlink the thread */
|
|
KiUnlinkThread(Thread, WaitStatus);
|
|
|
|
/* Tell the scheduler do to the increment when it readies the thread */
|
|
ASSERT(Increment >= 0);
|
|
Thread->AdjustIncrement = (SCHAR)Increment;
|
|
Thread->AdjustReason = AdjustUnwait;
|
|
|
|
/* Reschedule the Thread */
|
|
KiReadyThread(Thread);
|
|
}
|
|
|
|
VOID
|
|
FASTCALL
|
|
KiAcquireFastMutex(IN PFAST_MUTEX FastMutex)
|
|
{
|
|
/* Increase contention count */
|
|
FastMutex->Contention++;
|
|
|
|
/* Wait for the event */
|
|
KeWaitForSingleObject(&FastMutex->Event,
|
|
WrMutex,
|
|
KernelMode,
|
|
FALSE,
|
|
NULL);
|
|
}
|
|
|
|
VOID
|
|
FASTCALL
|
|
KiAcquireGuardedMutex(IN OUT PKGUARDED_MUTEX GuardedMutex)
|
|
{
|
|
ULONG BitsToRemove, BitsToAdd;
|
|
LONG OldValue, NewValue;
|
|
|
|
/* We depend on these bits being just right */
|
|
C_ASSERT((GM_LOCK_WAITER_WOKEN * 2) == GM_LOCK_WAITER_INC);
|
|
|
|
/* Increase the contention count */
|
|
GuardedMutex->Contention++;
|
|
|
|
/* Start by unlocking the Guarded Mutex */
|
|
BitsToRemove = GM_LOCK_BIT;
|
|
BitsToAdd = GM_LOCK_WAITER_INC;
|
|
|
|
/* Start change loop */
|
|
for (;;)
|
|
{
|
|
/* Loop sanity checks */
|
|
ASSERT((BitsToRemove == GM_LOCK_BIT) ||
|
|
(BitsToRemove == (GM_LOCK_BIT | GM_LOCK_WAITER_WOKEN)));
|
|
ASSERT((BitsToAdd == GM_LOCK_WAITER_INC) ||
|
|
(BitsToAdd == GM_LOCK_WAITER_WOKEN));
|
|
|
|
/* Get the Count Bits */
|
|
OldValue = GuardedMutex->Count;
|
|
|
|
/* Start internal bit change loop */
|
|
for (;;)
|
|
{
|
|
/* Check if the Guarded Mutex is locked */
|
|
if (OldValue & GM_LOCK_BIT)
|
|
{
|
|
/* Sanity check */
|
|
ASSERT((BitsToRemove == GM_LOCK_BIT) ||
|
|
((OldValue & GM_LOCK_WAITER_WOKEN) != 0));
|
|
|
|
/* Unlock it by removing the Lock Bit */
|
|
NewValue = OldValue ^ BitsToRemove;
|
|
NewValue = InterlockedCompareExchange(&GuardedMutex->Count,
|
|
NewValue,
|
|
OldValue);
|
|
if (NewValue == OldValue) return;
|
|
}
|
|
else
|
|
{
|
|
/* The Guarded Mutex isn't locked, so simply set the bits */
|
|
NewValue = OldValue + BitsToAdd;
|
|
NewValue = InterlockedCompareExchange(&GuardedMutex->Count,
|
|
NewValue,
|
|
OldValue);
|
|
if (NewValue == OldValue) break;
|
|
}
|
|
|
|
/* Old value changed, loop again */
|
|
OldValue = NewValue;
|
|
}
|
|
|
|
/* Now we have to wait for it */
|
|
KeWaitForGate(&GuardedMutex->Gate, WrGuardedMutex, KernelMode);
|
|
ASSERT((GuardedMutex->Count & GM_LOCK_WAITER_WOKEN) != 0);
|
|
|
|
/* Ok, the wait is done, so set the new bits */
|
|
BitsToRemove = GM_LOCK_BIT | GM_LOCK_WAITER_WOKEN;
|
|
BitsToAdd = GM_LOCK_WAITER_WOKEN;
|
|
}
|
|
}
|
|
|
|
//
|
|
// This routine exits the dispatcher after a compatible operation and
|
|
// swaps the context to the next scheduled thread on the current CPU if
|
|
// one is available.
|
|
//
|
|
// It does NOT attempt to scan for a new thread to schedule.
|
|
//
|
|
VOID
|
|
FASTCALL
|
|
KiExitDispatcher(IN KIRQL OldIrql)
|
|
{
|
|
PKPRCB Prcb = KeGetCurrentPrcb();
|
|
PKTHREAD Thread, NextThread;
|
|
BOOLEAN PendingApc;
|
|
|
|
/* Make sure we're at synchronization level */
|
|
ASSERT(KeGetCurrentIrql() == SYNCH_LEVEL);
|
|
|
|
/* Check if we have deferred threads */
|
|
KiCheckDeferredReadyList(Prcb);
|
|
|
|
/* Check if we were called at dispatcher level or higher */
|
|
if (OldIrql >= DISPATCH_LEVEL)
|
|
{
|
|
/* Check if we have a thread to schedule, and that no DPC is active */
|
|
if ((Prcb->NextThread) && !(Prcb->DpcRoutineActive))
|
|
{
|
|
/* Request DPC interrupt */
|
|
HalRequestSoftwareInterrupt(DISPATCH_LEVEL);
|
|
}
|
|
|
|
/* Lower IRQL and exit */
|
|
goto Quickie;
|
|
}
|
|
|
|
/* Make sure there's a new thread scheduled */
|
|
if (!Prcb->NextThread) goto Quickie;
|
|
|
|
/* Lock the PRCB */
|
|
KiAcquirePrcbLock(Prcb);
|
|
|
|
/* Get the next and current threads now */
|
|
NextThread = Prcb->NextThread;
|
|
Thread = Prcb->CurrentThread;
|
|
|
|
/* Set current thread's swap busy to true */
|
|
KiSetThreadSwapBusy(Thread);
|
|
|
|
/* Switch threads in PRCB */
|
|
Prcb->NextThread = NULL;
|
|
Prcb->CurrentThread = NextThread;
|
|
|
|
/* Set thread to running */
|
|
NextThread->State = Running;
|
|
|
|
/* Queue it on the ready lists */
|
|
KxQueueReadyThread(Thread, Prcb);
|
|
|
|
/* Set wait IRQL */
|
|
Thread->WaitIrql = OldIrql;
|
|
|
|
/* Swap threads and check if APCs were pending */
|
|
PendingApc = KiSwapContext(OldIrql, Thread);
|
|
if (PendingApc)
|
|
{
|
|
/* Lower only to APC */
|
|
KeLowerIrql(APC_LEVEL);
|
|
|
|
/* Deliver APCs */
|
|
KiDeliverApc(KernelMode, NULL, NULL);
|
|
ASSERT(OldIrql == PASSIVE_LEVEL);
|
|
}
|
|
|
|
/* Lower IRQl back */
|
|
Quickie:
|
|
KeLowerIrql(OldIrql);
|
|
}
|
|
|
|
/* PUBLIC FUNCTIONS **********************************************************/
|
|
|
|
BOOLEAN
|
|
NTAPI
|
|
KeIsWaitListEmpty(IN PVOID Object)
|
|
{
|
|
UNIMPLEMENTED;
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
KeDelayExecutionThread(IN KPROCESSOR_MODE WaitMode,
|
|
IN BOOLEAN Alertable,
|
|
IN PLARGE_INTEGER Interval OPTIONAL)
|
|
{
|
|
PKTIMER Timer;
|
|
PKWAIT_BLOCK TimerBlock;
|
|
PKTHREAD Thread = KeGetCurrentThread();
|
|
NTSTATUS WaitStatus;
|
|
BOOLEAN Swappable;
|
|
PLARGE_INTEGER OriginalDueTime;
|
|
LARGE_INTEGER DueTime, NewDueTime, InterruptTime;
|
|
ULONG Hand = 0;
|
|
|
|
if (Thread->WaitNext)
|
|
ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
|
|
else
|
|
ASSERT(KeGetCurrentIrql() <= APC_LEVEL);
|
|
|
|
/* If this is a user-mode wait of 0 seconds, yield execution */
|
|
if (!(Interval->QuadPart) && (WaitMode != KernelMode))
|
|
{
|
|
/* Make sure the wait isn't alertable or interrupting an APC */
|
|
if (!(Alertable) && !(Thread->ApcState.UserApcPending))
|
|
{
|
|
/* Yield execution */
|
|
return NtYieldExecution();
|
|
}
|
|
}
|
|
|
|
/* Setup the original time and timer/wait blocks */
|
|
OriginalDueTime = Interval;
|
|
Timer = &Thread->Timer;
|
|
TimerBlock = &Thread->WaitBlock[TIMER_WAIT_BLOCK];
|
|
|
|
/* Check if the lock is already held */
|
|
if (!Thread->WaitNext) goto WaitStart;
|
|
|
|
/* Otherwise, we already have the lock, so initialize the wait */
|
|
Thread->WaitNext = FALSE;
|
|
KxDelayThreadWait();
|
|
|
|
/* Start wait loop */
|
|
for (;;)
|
|
{
|
|
/* Disable pre-emption */
|
|
Thread->Preempted = FALSE;
|
|
|
|
/* Check if a kernel APC is pending and we're below APC_LEVEL */
|
|
if ((Thread->ApcState.KernelApcPending) && !(Thread->SpecialApcDisable) &&
|
|
(Thread->WaitIrql < APC_LEVEL))
|
|
{
|
|
/* Unlock the dispatcher */
|
|
KiReleaseDispatcherLock(Thread->WaitIrql);
|
|
}
|
|
else
|
|
{
|
|
/* Check if we have to bail out due to an alerted state */
|
|
WaitStatus = KiCheckAlertability(Thread, Alertable, WaitMode);
|
|
if (WaitStatus != STATUS_WAIT_0) break;
|
|
|
|
/* Check if the timer expired */
|
|
InterruptTime.QuadPart = KeQueryInterruptTime();
|
|
if ((ULONGLONG)InterruptTime.QuadPart >= Timer->DueTime.QuadPart)
|
|
{
|
|
/* It did, so we don't need to wait */
|
|
goto NoWait;
|
|
}
|
|
|
|
/* It didn't, so activate it */
|
|
Timer->Header.Inserted = TRUE;
|
|
|
|
/* Handle Kernel Queues */
|
|
if (Thread->Queue) KiActivateWaiterQueue(Thread->Queue);
|
|
|
|
/* Setup the wait information */
|
|
Thread->State = Waiting;
|
|
|
|
/* Add the thread to the wait list */
|
|
KiAddThreadToWaitList(Thread, Swappable);
|
|
|
|
/* Insert the timer and swap the thread */
|
|
ASSERT(Thread->WaitIrql <= DISPATCH_LEVEL);
|
|
KiSetThreadSwapBusy(Thread);
|
|
KxInsertTimer(Timer, Hand);
|
|
WaitStatus = (NTSTATUS)KiSwapThread(Thread, KeGetCurrentPrcb());
|
|
|
|
/* Check if were swapped ok */
|
|
if (WaitStatus != STATUS_KERNEL_APC)
|
|
{
|
|
/* This is a good thing */
|
|
if (WaitStatus == STATUS_TIMEOUT) WaitStatus = STATUS_SUCCESS;
|
|
|
|
/* Return Status */
|
|
return WaitStatus;
|
|
}
|
|
|
|
/* Recalculate due times */
|
|
Interval = KiRecalculateDueTime(OriginalDueTime,
|
|
&DueTime,
|
|
&NewDueTime);
|
|
}
|
|
|
|
WaitStart:
|
|
/* Setup a new wait */
|
|
Thread->WaitIrql = KeRaiseIrqlToSynchLevel();
|
|
KxDelayThreadWait();
|
|
KiAcquireDispatcherLockAtDpcLevel();
|
|
}
|
|
|
|
/* We're done! */
|
|
KiReleaseDispatcherLock(Thread->WaitIrql);
|
|
return WaitStatus;
|
|
|
|
NoWait:
|
|
/* There was nothing to wait for. Did we have a wait interval? */
|
|
if (!Interval->QuadPart)
|
|
{
|
|
/* Unlock the dispatcher and do a yield */
|
|
KiReleaseDispatcherLock(Thread->WaitIrql);
|
|
return NtYieldExecution();
|
|
}
|
|
|
|
/* Unlock the dispatcher and adjust the quantum for a no-wait */
|
|
KiReleaseDispatcherLockFromDpcLevel();
|
|
KiAdjustQuantumThread(Thread);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
KeWaitForSingleObject(IN PVOID Object,
|
|
IN KWAIT_REASON WaitReason,
|
|
IN KPROCESSOR_MODE WaitMode,
|
|
IN BOOLEAN Alertable,
|
|
IN PLARGE_INTEGER Timeout OPTIONAL)
|
|
{
|
|
PKTHREAD Thread = KeGetCurrentThread();
|
|
PKMUTANT CurrentObject = (PKMUTANT)Object;
|
|
PKWAIT_BLOCK WaitBlock = &Thread->WaitBlock[0];
|
|
PKWAIT_BLOCK TimerBlock = &Thread->WaitBlock[TIMER_WAIT_BLOCK];
|
|
PKTIMER Timer = &Thread->Timer;
|
|
NTSTATUS WaitStatus;
|
|
BOOLEAN Swappable;
|
|
LARGE_INTEGER DueTime = {{0}}, NewDueTime, InterruptTime;
|
|
PLARGE_INTEGER OriginalDueTime = Timeout;
|
|
ULONG Hand = 0;
|
|
|
|
if (Thread->WaitNext)
|
|
ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
|
|
else
|
|
ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL ||
|
|
(KeGetCurrentIrql() == DISPATCH_LEVEL &&
|
|
Timeout && Timeout->QuadPart == 0));
|
|
|
|
/* Check if the lock is already held */
|
|
if (!Thread->WaitNext) goto WaitStart;
|
|
|
|
/* Otherwise, we already have the lock, so initialize the wait */
|
|
Thread->WaitNext = FALSE;
|
|
KxSingleThreadWait();
|
|
|
|
/* Start wait loop */
|
|
for (;;)
|
|
{
|
|
/* Disable pre-emption */
|
|
Thread->Preempted = FALSE;
|
|
|
|
/* Check if a kernel APC is pending and we're below APC_LEVEL */
|
|
if ((Thread->ApcState.KernelApcPending) && !(Thread->SpecialApcDisable) &&
|
|
(Thread->WaitIrql < APC_LEVEL))
|
|
{
|
|
/* Unlock the dispatcher */
|
|
KiReleaseDispatcherLock(Thread->WaitIrql);
|
|
}
|
|
else
|
|
{
|
|
/* Sanity check */
|
|
ASSERT(CurrentObject->Header.Type != QueueObject);
|
|
|
|
/* Check if it's a mutant */
|
|
if (CurrentObject->Header.Type == MutantObject)
|
|
{
|
|
/* Check its signal state or if we own it */
|
|
if ((CurrentObject->Header.SignalState > 0) ||
|
|
(Thread == CurrentObject->OwnerThread))
|
|
{
|
|
/* Just unwait this guy and exit */
|
|
if (CurrentObject->Header.SignalState != (LONG)MINLONG)
|
|
{
|
|
/* It has a normal signal state. Unwait and return */
|
|
KiSatisfyMutantWait(CurrentObject, Thread);
|
|
WaitStatus = (NTSTATUS)Thread->WaitStatus;
|
|
goto DontWait;
|
|
}
|
|
else
|
|
{
|
|
/* Raise an exception */
|
|
KiReleaseDispatcherLock(Thread->WaitIrql);
|
|
ExRaiseStatus(STATUS_MUTANT_LIMIT_EXCEEDED);
|
|
}
|
|
}
|
|
}
|
|
else if (CurrentObject->Header.SignalState > 0)
|
|
{
|
|
/* Another satisfied object */
|
|
KiSatisfyNonMutantWait(CurrentObject);
|
|
WaitStatus = STATUS_WAIT_0;
|
|
goto DontWait;
|
|
}
|
|
|
|
/* Make sure we can satisfy the Alertable request */
|
|
WaitStatus = KiCheckAlertability(Thread, Alertable, WaitMode);
|
|
if (WaitStatus != STATUS_WAIT_0) break;
|
|
|
|
/* Enable the Timeout Timer if there was any specified */
|
|
if (Timeout)
|
|
{
|
|
/* Check if the timer expired */
|
|
InterruptTime.QuadPart = KeQueryInterruptTime();
|
|
if ((ULONGLONG)InterruptTime.QuadPart >=
|
|
Timer->DueTime.QuadPart)
|
|
{
|
|
/* It did, so we don't need to wait */
|
|
WaitStatus = STATUS_TIMEOUT;
|
|
goto DontWait;
|
|
}
|
|
|
|
/* It didn't, so activate it */
|
|
Timer->Header.Inserted = TRUE;
|
|
}
|
|
|
|
/* Link the Object to this Wait Block */
|
|
InsertTailList(&CurrentObject->Header.WaitListHead,
|
|
&WaitBlock->WaitListEntry);
|
|
|
|
/* Handle Kernel Queues */
|
|
if (Thread->Queue) KiActivateWaiterQueue(Thread->Queue);
|
|
|
|
/* Setup the wait information */
|
|
Thread->State = Waiting;
|
|
|
|
/* Add the thread to the wait list */
|
|
KiAddThreadToWaitList(Thread, Swappable);
|
|
|
|
/* Activate thread swap */
|
|
ASSERT(Thread->WaitIrql <= DISPATCH_LEVEL);
|
|
KiSetThreadSwapBusy(Thread);
|
|
|
|
/* Check if we have a timer */
|
|
if (Timeout)
|
|
{
|
|
/* Insert it */
|
|
KxInsertTimer(Timer, Hand);
|
|
}
|
|
else
|
|
{
|
|
/* Otherwise, unlock the dispatcher */
|
|
KiReleaseDispatcherLockFromDpcLevel();
|
|
}
|
|
|
|
/* Do the actual swap */
|
|
WaitStatus = (NTSTATUS)KiSwapThread(Thread, KeGetCurrentPrcb());
|
|
|
|
/* Check if we were executing an APC */
|
|
if (WaitStatus != STATUS_KERNEL_APC) return WaitStatus;
|
|
|
|
/* Check if we had a timeout */
|
|
if (Timeout)
|
|
{
|
|
/* Recalculate due times */
|
|
Timeout = KiRecalculateDueTime(OriginalDueTime,
|
|
&DueTime,
|
|
&NewDueTime);
|
|
}
|
|
}
|
|
WaitStart:
|
|
/* Setup a new wait */
|
|
Thread->WaitIrql = KeRaiseIrqlToSynchLevel();
|
|
KxSingleThreadWait();
|
|
KiAcquireDispatcherLockAtDpcLevel();
|
|
}
|
|
|
|
/* Wait complete */
|
|
KiReleaseDispatcherLock(Thread->WaitIrql);
|
|
return WaitStatus;
|
|
|
|
DontWait:
|
|
/* Release dispatcher lock but maintain high IRQL */
|
|
KiReleaseDispatcherLockFromDpcLevel();
|
|
|
|
/* Adjust the Quantum and return the wait status */
|
|
KiAdjustQuantumThread(Thread);
|
|
return WaitStatus;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
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,
|
|
OUT PKWAIT_BLOCK WaitBlockArray OPTIONAL)
|
|
{
|
|
PKMUTANT CurrentObject;
|
|
PKWAIT_BLOCK WaitBlock;
|
|
PKTHREAD Thread = KeGetCurrentThread();
|
|
PKWAIT_BLOCK TimerBlock = &Thread->WaitBlock[TIMER_WAIT_BLOCK];
|
|
PKTIMER Timer = &Thread->Timer;
|
|
NTSTATUS WaitStatus = STATUS_SUCCESS;
|
|
BOOLEAN Swappable;
|
|
PLARGE_INTEGER OriginalDueTime = Timeout;
|
|
LARGE_INTEGER DueTime = {{0}}, NewDueTime, InterruptTime;
|
|
ULONG Index, Hand = 0;
|
|
|
|
if (Thread->WaitNext)
|
|
ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
|
|
else if (KeGetCurrentIrql() == DISPATCH_LEVEL &&
|
|
(!Timeout || Timeout->QuadPart != 0))
|
|
{
|
|
/* HACK: tcpip is broken and waits with spinlocks acquired (CORE-6473) */
|
|
DPRINT("%s called at DISPATCH_LEVEL with non-zero timeout!\n",
|
|
__FUNCTION__);
|
|
}
|
|
else
|
|
ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);
|
|
|
|
/* Make sure the Wait Count is valid */
|
|
if (!WaitBlockArray)
|
|
{
|
|
/* Check in regards to the Thread Object Limit */
|
|
if (Count > THREAD_WAIT_OBJECTS)
|
|
{
|
|
/* Bugcheck */
|
|
KeBugCheck(MAXIMUM_WAIT_OBJECTS_EXCEEDED);
|
|
}
|
|
|
|
/* Use the Thread's Wait Block */
|
|
WaitBlockArray = &Thread->WaitBlock[0];
|
|
}
|
|
else
|
|
{
|
|
/* Using our own Block Array, so check with the System Object Limit */
|
|
if (Count > MAXIMUM_WAIT_OBJECTS)
|
|
{
|
|
/* Bugcheck */
|
|
KeBugCheck(MAXIMUM_WAIT_OBJECTS_EXCEEDED);
|
|
}
|
|
}
|
|
|
|
/* Sanity check */
|
|
ASSERT(Count != 0);
|
|
|
|
/* Check if the lock is already held */
|
|
if (!Thread->WaitNext) goto WaitStart;
|
|
|
|
/* Otherwise, we already have the lock, so initialize the wait */
|
|
Thread->WaitNext = FALSE;
|
|
/* Note that KxMultiThreadWait is a macro, defined in ke_x.h, that */
|
|
/* uses (and modifies some of) the following local */
|
|
/* variables: */
|
|
/* Thread, Index, WaitBlock, Timer, Timeout, Hand and Swappable. */
|
|
/* If it looks like this code doesn't actually wait for any objects */
|
|
/* at all, it's because the setup is done by that macro. */
|
|
KxMultiThreadWait();
|
|
|
|
/* Start wait loop */
|
|
for (;;)
|
|
{
|
|
/* Disable pre-emption */
|
|
Thread->Preempted = FALSE;
|
|
|
|
/* Check if a kernel APC is pending and we're below APC_LEVEL */
|
|
if ((Thread->ApcState.KernelApcPending) && !(Thread->SpecialApcDisable) &&
|
|
(Thread->WaitIrql < APC_LEVEL))
|
|
{
|
|
/* Unlock the dispatcher */
|
|
KiReleaseDispatcherLock(Thread->WaitIrql);
|
|
}
|
|
else
|
|
{
|
|
/* Check what kind of wait this is */
|
|
Index = 0;
|
|
if (WaitType == WaitAny)
|
|
{
|
|
/* Loop blocks */
|
|
do
|
|
{
|
|
/* Get the Current Object */
|
|
CurrentObject = (PKMUTANT)Object[Index];
|
|
ASSERT(CurrentObject->Header.Type != QueueObject);
|
|
|
|
/* Check if the Object is a mutant */
|
|
if (CurrentObject->Header.Type == MutantObject)
|
|
{
|
|
/* Check if it's signaled */
|
|
if ((CurrentObject->Header.SignalState > 0) ||
|
|
(Thread == CurrentObject->OwnerThread))
|
|
{
|
|
/* This is a Wait Any, so unwait this and exit */
|
|
if (CurrentObject->Header.SignalState !=
|
|
(LONG)MINLONG)
|
|
{
|
|
/* Normal signal state, unwait it and return */
|
|
KiSatisfyMutantWait(CurrentObject, Thread);
|
|
WaitStatus = (NTSTATUS)Thread->WaitStatus | Index;
|
|
goto DontWait;
|
|
}
|
|
else
|
|
{
|
|
/* Raise an exception (see wasm.ru) */
|
|
KiReleaseDispatcherLock(Thread->WaitIrql);
|
|
ExRaiseStatus(STATUS_MUTANT_LIMIT_EXCEEDED);
|
|
}
|
|
}
|
|
}
|
|
else if (CurrentObject->Header.SignalState > 0)
|
|
{
|
|
/* Another signaled object, unwait and return */
|
|
KiSatisfyNonMutantWait(CurrentObject);
|
|
WaitStatus = Index;
|
|
goto DontWait;
|
|
}
|
|
|
|
/* Go to the next block */
|
|
Index++;
|
|
} while (Index < Count);
|
|
}
|
|
else
|
|
{
|
|
/* Loop blocks */
|
|
do
|
|
{
|
|
/* Get the Current Object */
|
|
CurrentObject = (PKMUTANT)Object[Index];
|
|
ASSERT(CurrentObject->Header.Type != QueueObject);
|
|
|
|
/* Check if we're dealing with a mutant again */
|
|
if (CurrentObject->Header.Type == MutantObject)
|
|
{
|
|
/* Check if it has an invalid count */
|
|
if ((Thread == CurrentObject->OwnerThread) &&
|
|
(CurrentObject->Header.SignalState == (LONG)MINLONG))
|
|
{
|
|
/* Raise an exception */
|
|
KiReleaseDispatcherLock(Thread->WaitIrql);
|
|
ExRaiseStatus(STATUS_MUTANT_LIMIT_EXCEEDED);
|
|
}
|
|
else if ((CurrentObject->Header.SignalState <= 0) &&
|
|
(Thread != CurrentObject->OwnerThread))
|
|
{
|
|
/* We don't own it, can't satisfy the wait */
|
|
break;
|
|
}
|
|
}
|
|
else if (CurrentObject->Header.SignalState <= 0)
|
|
{
|
|
/* Not signaled, can't satisfy */
|
|
break;
|
|
}
|
|
|
|
/* Go to the next block */
|
|
Index++;
|
|
} while (Index < Count);
|
|
|
|
/* Check if we've went through all the objects */
|
|
if (Index == Count)
|
|
{
|
|
/* Loop wait blocks */
|
|
WaitBlock = WaitBlockArray;
|
|
do
|
|
{
|
|
/* Get the object and satisfy it */
|
|
CurrentObject = (PKMUTANT)WaitBlock->Object;
|
|
KiSatisfyObjectWait(CurrentObject, Thread);
|
|
|
|
/* Go to the next block */
|
|
WaitBlock = WaitBlock->NextWaitBlock;
|
|
} while(WaitBlock != WaitBlockArray);
|
|
|
|
/* Set the wait status and get out */
|
|
WaitStatus = (NTSTATUS)Thread->WaitStatus;
|
|
goto DontWait;
|
|
}
|
|
}
|
|
|
|
/* Make sure we can satisfy the Alertable request */
|
|
WaitStatus = KiCheckAlertability(Thread, Alertable, WaitMode);
|
|
if (WaitStatus != STATUS_WAIT_0) break;
|
|
|
|
/* Enable the Timeout Timer if there was any specified */
|
|
if (Timeout)
|
|
{
|
|
/* Check if the timer expired */
|
|
InterruptTime.QuadPart = KeQueryInterruptTime();
|
|
if ((ULONGLONG)InterruptTime.QuadPart >=
|
|
Timer->DueTime.QuadPart)
|
|
{
|
|
/* It did, so we don't need to wait */
|
|
WaitStatus = STATUS_TIMEOUT;
|
|
goto DontWait;
|
|
}
|
|
|
|
/* It didn't, so activate it */
|
|
Timer->Header.Inserted = TRUE;
|
|
|
|
/* Link the wait blocks */
|
|
WaitBlock->NextWaitBlock = TimerBlock;
|
|
}
|
|
|
|
/* Insert into Object's Wait List*/
|
|
WaitBlock = WaitBlockArray;
|
|
do
|
|
{
|
|
/* Get the Current Object */
|
|
CurrentObject = WaitBlock->Object;
|
|
|
|
/* Link the Object to this Wait Block */
|
|
InsertTailList(&CurrentObject->Header.WaitListHead,
|
|
&WaitBlock->WaitListEntry);
|
|
|
|
/* Move to the next Wait Block */
|
|
WaitBlock = WaitBlock->NextWaitBlock;
|
|
} while (WaitBlock != WaitBlockArray);
|
|
|
|
/* Handle Kernel Queues */
|
|
if (Thread->Queue) KiActivateWaiterQueue(Thread->Queue);
|
|
|
|
/* Setup the wait information */
|
|
Thread->State = Waiting;
|
|
|
|
/* Add the thread to the wait list */
|
|
KiAddThreadToWaitList(Thread, Swappable);
|
|
|
|
/* Activate thread swap */
|
|
ASSERT(Thread->WaitIrql <= DISPATCH_LEVEL);
|
|
KiSetThreadSwapBusy(Thread);
|
|
|
|
/* Check if we have a timer */
|
|
if (Timeout)
|
|
{
|
|
/* Insert it */
|
|
KxInsertTimer(Timer, Hand);
|
|
}
|
|
else
|
|
{
|
|
/* Otherwise, unlock the dispatcher */
|
|
KiReleaseDispatcherLockFromDpcLevel();
|
|
}
|
|
|
|
/* Swap the thread */
|
|
WaitStatus = (NTSTATUS)KiSwapThread(Thread, KeGetCurrentPrcb());
|
|
|
|
/* Check if we were executing an APC */
|
|
if (WaitStatus != STATUS_KERNEL_APC) return WaitStatus;
|
|
|
|
/* Check if we had a timeout */
|
|
if (Timeout)
|
|
{
|
|
/* Recalculate due times */
|
|
Timeout = KiRecalculateDueTime(OriginalDueTime,
|
|
&DueTime,
|
|
&NewDueTime);
|
|
}
|
|
}
|
|
|
|
WaitStart:
|
|
/* Setup a new wait */
|
|
Thread->WaitIrql = KeRaiseIrqlToSynchLevel();
|
|
KxMultiThreadWait();
|
|
KiAcquireDispatcherLockAtDpcLevel();
|
|
}
|
|
|
|
/* We are done */
|
|
KiReleaseDispatcherLock(Thread->WaitIrql);
|
|
return WaitStatus;
|
|
|
|
DontWait:
|
|
/* Release dispatcher lock but maintain high IRQL */
|
|
KiReleaseDispatcherLockFromDpcLevel();
|
|
|
|
/* Adjust the Quantum and return the wait status */
|
|
KiAdjustQuantumThread(Thread);
|
|
return WaitStatus;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
NtDelayExecution(IN BOOLEAN Alertable,
|
|
IN PLARGE_INTEGER DelayInterval)
|
|
{
|
|
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
|
|
LARGE_INTEGER SafeInterval;
|
|
NTSTATUS Status;
|
|
|
|
/* Check the previous mode */
|
|
if (PreviousMode != KernelMode)
|
|
{
|
|
/* Enter SEH for probing */
|
|
_SEH2_TRY
|
|
{
|
|
/* Probe and capture the time out */
|
|
SafeInterval = ProbeForReadLargeInteger(DelayInterval);
|
|
DelayInterval = &SafeInterval;
|
|
}
|
|
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
/* Return the exception code */
|
|
_SEH2_YIELD(return _SEH2_GetExceptionCode());
|
|
}
|
|
_SEH2_END;
|
|
}
|
|
|
|
/* Call the Kernel Function */
|
|
Status = KeDelayExecutionThread(PreviousMode,
|
|
Alertable,
|
|
DelayInterval);
|
|
|
|
/* Return Status */
|
|
return Status;
|
|
}
|
|
|
|
/* EOF */
|