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.
1128 lines
33 KiB
1128 lines
33 KiB
/*++
|
|
|
|
Copyright (c) 1989 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
dpcsup.c
|
|
|
|
Abstract:
|
|
|
|
This module contains the support routines for the system DPC objects.
|
|
Functions are provided to process quantum end, the power notification
|
|
queue, and timer expiration.
|
|
|
|
Author:
|
|
|
|
David N. Cutler (davec) 22-Apr-1989
|
|
|
|
Environment:
|
|
|
|
Kernel mode only, IRQL DISPATCH_LEVEL.
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "ki.h"
|
|
|
|
//
|
|
// Define DPC entry structure and maximum DPC List size.
|
|
//
|
|
|
|
#define MAXIMUM_DPC_TABLE_SIZE 16
|
|
|
|
typedef struct _DPC_ENTRY {
|
|
PRKDPC Dpc;
|
|
PKDEFERRED_ROUTINE Routine;
|
|
PVOID Context;
|
|
} DPC_ENTRY, *PDPC_ENTRY;
|
|
|
|
//
|
|
// Define maximum number of timers that can be examined or processed before
|
|
// dropping the dispatcher database lock.
|
|
//
|
|
|
|
#define MAXIMUM_TIMERS_EXAMINED 24
|
|
#define MAXIMUM_TIMERS_PROCESSED 4
|
|
|
|
VOID
|
|
KiExecuteDpc (
|
|
IN PVOID Context
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function is executed by the DPC thread for each processor. DPC
|
|
threads are started during kernel initialization after having started
|
|
all processors and it is determined that the host configuation should
|
|
execute threaded DPCs in a DPC thread.
|
|
|
|
Arguments:
|
|
|
|
Context - Supplies a pointer to the processor control block for the
|
|
processor on which the DPC thread is to run.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
PKDPC Dpc;
|
|
PVOID DeferredContext;
|
|
PKDEFERRED_ROUTINE DeferredRoutine;
|
|
PERFINFO_DPC_INFORMATION DpcInformation;
|
|
PLIST_ENTRY Entry;
|
|
PLIST_ENTRY ListHead;
|
|
LOGICAL Logging;
|
|
KIRQL OldIrql;
|
|
PKPRCB Prcb;
|
|
PVOID SystemArgument1;
|
|
PVOID SystemArgument2;
|
|
PKTHREAD Thread;
|
|
LARGE_INTEGER TimeStamp = {0};
|
|
|
|
//
|
|
// Get PRCB and set the DPC thread address.
|
|
//
|
|
|
|
Prcb = Context;
|
|
Thread = KeGetCurrentThread();
|
|
Prcb->DpcThread = Thread;
|
|
|
|
//
|
|
// Set the DPC thread priority to the highest level, set the thread
|
|
// affinity, and enable threaded DPCs on this processor.
|
|
//
|
|
|
|
KeSetPriorityThread(Thread, HIGH_PRIORITY);
|
|
KeSetSystemAffinityThread(Prcb->SetMember);
|
|
Prcb->ThreadDpcEnable = TRUE;
|
|
|
|
//
|
|
// Loop processing DPC list entries until the specified DPC list is empty.
|
|
//
|
|
// N.B. This following code appears to have a redundant loop, but it does
|
|
// not. The point of this code is to avoid as many dispatch interrupts
|
|
// as possible.
|
|
//
|
|
|
|
ListHead = &Prcb->DpcData[DPC_THREADED].DpcListHead;
|
|
do {
|
|
Prcb->DpcThreadActive = TRUE;
|
|
|
|
//
|
|
// If the DPC list is not empty, then process the DPC list.
|
|
//
|
|
|
|
if (Prcb->DpcData[DPC_THREADED].DpcQueueDepth != 0) {
|
|
Logging = PERFINFO_IS_GROUP_ON(PERF_DPC);
|
|
|
|
//
|
|
// Acquire the DPC lock for the current processor and check if
|
|
// the DPC list is empty. If the DPC list is not empty, then
|
|
// remove the first entry from the DPC list, capture the DPC
|
|
// parameters, set the DPC inserted state false, decrement the
|
|
// DPC queue depth, release the DPC lock, enable interrupts, and
|
|
// call the specified DPC routine. Otherwise, release the DPC
|
|
// lock and enable interrupts.
|
|
//
|
|
|
|
do {
|
|
KeRaiseIrql(HIGH_LEVEL, &OldIrql);
|
|
KeAcquireSpinLockAtDpcLevel(&Prcb->DpcData[DPC_THREADED].DpcLock);
|
|
Entry = ListHead->Flink;
|
|
if (Entry != ListHead) {
|
|
RemoveEntryList(Entry);
|
|
Dpc = CONTAINING_RECORD(Entry, KDPC, DpcListEntry);
|
|
DeferredRoutine = Dpc->DeferredRoutine;
|
|
DeferredContext = Dpc->DeferredContext;
|
|
SystemArgument1 = Dpc->SystemArgument1;
|
|
SystemArgument2 = Dpc->SystemArgument2;
|
|
Dpc->DpcData = NULL;
|
|
Prcb->DpcData[DPC_THREADED].DpcQueueDepth -= 1;
|
|
KeReleaseSpinLockFromDpcLevel(&Prcb->DpcData[DPC_THREADED].DpcLock);
|
|
KeLowerIrql(OldIrql);
|
|
|
|
//
|
|
// If event tracing is enabled, capture the start time.
|
|
//
|
|
|
|
if (Logging != FALSE) {
|
|
PerfTimeStamp(TimeStamp);
|
|
}
|
|
|
|
//
|
|
// Call the DPC routine.
|
|
//
|
|
|
|
(DeferredRoutine)(Dpc,
|
|
DeferredContext,
|
|
SystemArgument1,
|
|
SystemArgument2);
|
|
|
|
ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);
|
|
ASSERT(Thread->Affinity == Prcb->SetMember);
|
|
ASSERT(Thread->Priority == HIGH_PRIORITY);
|
|
|
|
//
|
|
// If event tracing is enabled, then log the start time
|
|
// and routine address.
|
|
//
|
|
|
|
if (Logging != FALSE) {
|
|
DpcInformation.InitialTime = TimeStamp.QuadPart;
|
|
DpcInformation.DpcRoutine = (PVOID)(ULONG_PTR)DeferredRoutine;
|
|
PerfInfoLogBytes(PERFINFO_LOG_TYPE_DPC,
|
|
&DpcInformation,
|
|
sizeof(DpcInformation));
|
|
}
|
|
|
|
} else {
|
|
|
|
ASSERT(Prcb->DpcData[DPC_THREADED].DpcQueueDepth == 0);
|
|
|
|
KeReleaseSpinLockFromDpcLevel(&Prcb->DpcData[DPC_THREADED].DpcLock);
|
|
KeLowerIrql(OldIrql);
|
|
}
|
|
|
|
} while (Prcb->DpcData[DPC_THREADED].DpcQueueDepth != 0);
|
|
}
|
|
|
|
Prcb->DpcThreadActive = FALSE;
|
|
Prcb->DpcThreadRequested = FALSE;
|
|
KeMemoryBarrier();
|
|
|
|
//
|
|
// If the thread DPC list is empty, then wait until the DPC event
|
|
// for the current processor is set.
|
|
//
|
|
|
|
if (Prcb->DpcData[DPC_THREADED].DpcQueueDepth == 0) {
|
|
KeWaitForSingleObject(&Prcb->DpcEvent,
|
|
Suspended,
|
|
KernelMode,
|
|
FALSE,
|
|
NULL);
|
|
|
|
}
|
|
|
|
} while (TRUE);
|
|
|
|
return;
|
|
}
|
|
|
|
VOID
|
|
KiQuantumEnd (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function is called when a quantum end event occurs on the current
|
|
processor. Its function is to determine whether the thread priority should
|
|
be decremented and whether a redispatch of the processor should occur.
|
|
|
|
N.B. This function is called at DISPATCH level and returns at DISPATCH
|
|
level.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
PKPRCB Prcb;
|
|
PKPROCESS Process;
|
|
PRKTHREAD Thread;
|
|
PRKTHREAD NewThread;
|
|
|
|
//
|
|
// If DPC thread activation is requested, then set the DPC event.
|
|
//
|
|
|
|
Prcb = KeGetCurrentPrcb();
|
|
Thread = KeGetCurrentThread();
|
|
if (InterlockedExchange(&Prcb->DpcSetEventRequest, FALSE) == TRUE) {
|
|
KeSetEvent(&Prcb->DpcEvent, 0, FALSE);
|
|
}
|
|
|
|
//
|
|
// Raise IRQL to SYNCH level, acquire the thread lock, and acquire the
|
|
// PRCB lock.
|
|
//
|
|
// If the quantum has expired for the current thread, then update its
|
|
// quantum and priority.
|
|
//
|
|
|
|
KeRaiseIrqlToSynchLevel();
|
|
KiAcquireThreadLock(Thread);
|
|
KiAcquirePrcbLock(Prcb);
|
|
if (Thread->Quantum <= 0) {
|
|
|
|
//
|
|
// If quantum runout is disabled for the thread's process and
|
|
// the thread is running at a realtime priority, then set the
|
|
// thread quantum to the highest value and do not round robin
|
|
// at the thread's priority level. Otherwise, reset the thread
|
|
// quantum and decay the thread's priority as appropriate.
|
|
//
|
|
|
|
Process = Thread->ApcState.Process;
|
|
if ((Process->DisableQuantum != FALSE) &&
|
|
(Thread->Priority >= LOW_REALTIME_PRIORITY)) {
|
|
|
|
Thread->Quantum = MAXCHAR;
|
|
|
|
} else {
|
|
Thread->Quantum = Process->ThreadQuantum;
|
|
|
|
//
|
|
// Compute the new thread priority and attempt to reschedule the
|
|
// current processor.
|
|
//
|
|
// N.B. The new priority will never be greater than the previous
|
|
// priority.
|
|
//
|
|
|
|
Thread->Priority = KiComputeNewPriority(Thread, 1);
|
|
if (Prcb->NextThread == NULL) {
|
|
if ((NewThread = KiSelectReadyThread(Thread->Priority, Prcb)) != NULL) {
|
|
NewThread->State = Standby;
|
|
Prcb->NextThread = NewThread;
|
|
}
|
|
|
|
} else {
|
|
Thread->Preempted = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Release the thread lock.
|
|
//
|
|
// If a thread was scheduled for execution on the current processor, then
|
|
// acquire the PRCB lock, set the current thread to the new thread, set
|
|
// next thread to NULL, set the thread state to running, release the PRCB
|
|
// lock, set the wait reason, ready the old thread, and swap context to
|
|
// the new thread.
|
|
//
|
|
|
|
KiReleaseThreadLock(Thread);
|
|
if (Prcb->NextThread != NULL) {
|
|
KiSetContextSwapBusy(Thread);
|
|
NewThread = Prcb->NextThread;
|
|
Prcb->NextThread = NULL;
|
|
Prcb->CurrentThread = NewThread;
|
|
NewThread->State = Running;
|
|
Thread->WaitReason = WrQuantumEnd;
|
|
KxQueueReadyThread(Thread, Prcb);
|
|
Thread->WaitIrql = APC_LEVEL;
|
|
KiSwapContext(Thread, NewThread);
|
|
|
|
} else {
|
|
KiReleasePrcbLock(Prcb);
|
|
}
|
|
|
|
//
|
|
// Lower IRQL to DISPATCH level and return.
|
|
//
|
|
|
|
KeLowerIrql(DISPATCH_LEVEL);
|
|
return;
|
|
}
|
|
|
|
#if DBG
|
|
|
|
VOID
|
|
KiCheckTimerTable (
|
|
IN ULARGE_INTEGER CurrentTime
|
|
)
|
|
|
|
{
|
|
|
|
ULONG Index;
|
|
PLIST_ENTRY ListHead;
|
|
PLIST_ENTRY NextEntry;
|
|
KIRQL OldIrql;
|
|
PKTIMER Timer;
|
|
|
|
//
|
|
// Raise IRQL to highest level and scan timer table for timers that
|
|
// have expired.
|
|
//
|
|
|
|
KeRaiseIrql(HIGH_LEVEL, &OldIrql);
|
|
Index = 0;
|
|
do {
|
|
ListHead = &KiTimerTableListHead[Index];
|
|
NextEntry = ListHead->Flink;
|
|
while (NextEntry != ListHead) {
|
|
Timer = CONTAINING_RECORD(NextEntry, KTIMER, TimerListEntry);
|
|
NextEntry = NextEntry->Flink;
|
|
if (Timer->DueTime.QuadPart <= CurrentTime.QuadPart) {
|
|
|
|
//
|
|
// If the timer expiration DPC is queued, then the time has
|
|
// been change and the DPC has not yet had the chance to run
|
|
// and clear out the expired timers.
|
|
//
|
|
|
|
if (*((volatile PKSPIN_LOCK *)(&KiTimerExpireDpc.DpcData)) == NULL) {
|
|
DbgBreakPoint();
|
|
}
|
|
}
|
|
}
|
|
|
|
Index += 1;
|
|
} while(Index < TIMER_TABLE_SIZE);
|
|
|
|
//
|
|
// Lower IRQL to the previous level.
|
|
//
|
|
|
|
KeLowerIrql(OldIrql);
|
|
return;
|
|
}
|
|
|
|
#endif
|
|
|
|
FORCEINLINE
|
|
VOID
|
|
KiProcessTimerDpcTable (
|
|
IN PULARGE_INTEGER SystemTime,
|
|
IN PDPC_ENTRY DpcTable,
|
|
IN ULONG Count
|
|
)
|
|
|
|
/**++
|
|
|
|
Routine Description:
|
|
|
|
This function processes the time DPC table which is a array of DPCs that
|
|
are to be called on the current processor.
|
|
|
|
N.B. This routine is entered with the dispatcher database locked.
|
|
|
|
N.B. This routine returns with the dispatcher database unlocked.
|
|
|
|
Arguments:
|
|
|
|
SystemTime - Supplies a pointer to the timer expiration time.
|
|
|
|
DpcTable - Supplies a pointer to an array of DPC entries.
|
|
|
|
Count - Supplies a count of the number of entries in the DPC table.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
PERFINFO_DPC_INFORMATION DpcInformation;
|
|
LOGICAL Logging;
|
|
LARGE_INTEGER TimeStamp = {0};
|
|
|
|
//
|
|
// Unlock the dispacher database and lower IRQL to dispatch level.
|
|
//
|
|
|
|
KiUnlockDispatcherDatabase(DISPATCH_LEVEL);
|
|
|
|
//
|
|
// Process DPC table entries.
|
|
//
|
|
|
|
Logging = PERFINFO_IS_GROUP_ON(PERF_DPC);
|
|
while (Count != 0) {
|
|
|
|
//
|
|
// Reset the debug DPC count to avoid a timeout and breakpoint.
|
|
//
|
|
|
|
#if DBG
|
|
|
|
KeGetCurrentPrcb()->DebugDpcTime = 0;
|
|
|
|
#endif
|
|
|
|
//
|
|
// If event tracing is enabled, capture the start time.
|
|
//
|
|
|
|
if (Logging != FALSE) {
|
|
PerfTimeStamp(TimeStamp);
|
|
}
|
|
|
|
//
|
|
// Call the DPC routine.
|
|
//
|
|
|
|
(DpcTable->Routine)(DpcTable->Dpc,
|
|
DpcTable->Context,
|
|
ULongToPtr(SystemTime->LowPart),
|
|
ULongToPtr(SystemTime->HighPart));
|
|
|
|
//
|
|
// If event tracing is enabled, then log the start time and
|
|
// routine address.
|
|
//
|
|
|
|
if (Logging != FALSE) {
|
|
DpcInformation.InitialTime = TimeStamp.QuadPart;
|
|
DpcInformation.DpcRoutine = (PVOID)(ULONG_PTR)DpcTable->Routine;
|
|
PerfInfoLogBytes(PERFINFO_LOG_TYPE_TIMERDPC,
|
|
&DpcInformation,
|
|
sizeof(DpcInformation));
|
|
}
|
|
|
|
DpcTable += 1;
|
|
Count -= 1;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
VOID
|
|
KiTimerExpiration (
|
|
IN PKDPC TimerDpc,
|
|
IN PVOID DeferredContext,
|
|
IN PVOID SystemArgument1,
|
|
IN PVOID SystemArgument2
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function is called when the clock interupt routine discovers that
|
|
a timer has expired.
|
|
|
|
Arguments:
|
|
|
|
TimerDpc - Not used.
|
|
|
|
DeferredContext - Not used.
|
|
|
|
SystemArgument1 - Supplies the starting timer table index value to
|
|
use for the timer table scan.
|
|
|
|
SystemArgument2 - Not used.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
ULARGE_INTEGER CurrentTime;
|
|
ULONG DpcCount;
|
|
PKDPC Dpc;
|
|
DPC_ENTRY DpcTable[MAXIMUM_TIMERS_PROCESSED];
|
|
KIRQL DummyIrql;
|
|
LONG HandLimit;
|
|
LONG Index;
|
|
LARGE_INTEGER Interval;
|
|
PLIST_ENTRY ListHead;
|
|
PLIST_ENTRY NextEntry;
|
|
KIRQL OldIrql;
|
|
LONG Period;
|
|
ULARGE_INTEGER SystemTime;
|
|
PKTIMER Timer;
|
|
ULONG TimersExamined;
|
|
ULONG TimersProcessed;
|
|
|
|
UNREFERENCED_PARAMETER(TimerDpc);
|
|
UNREFERENCED_PARAMETER(DeferredContext);
|
|
UNREFERENCED_PARAMETER(SystemArgument2);
|
|
|
|
//
|
|
// Capture the timer expiration time, the current interrupt time, and
|
|
// the low tick count.
|
|
//
|
|
// N.B. Interrupts are disabled to ensure that interrupt activity on the
|
|
// current processor does not cause the values read to be skewed.
|
|
//
|
|
|
|
_disable();
|
|
KiQuerySystemTime((PLARGE_INTEGER)&SystemTime);
|
|
KiQueryInterruptTime((PLARGE_INTEGER)&CurrentTime);
|
|
HandLimit = (LONG)KiQueryLowTickCount();
|
|
_enable();
|
|
|
|
//
|
|
// If the timer table has not wrapped, then start with the specified
|
|
// timer table index value, and scan for timer entries that have expired.
|
|
// Otherwise, start with the specified timer table index value and scan
|
|
// the entire table for timer entries that have expired.
|
|
//
|
|
// N.B. This later condition exists when DPC processing is blocked for a
|
|
// period longer than one round trip throught the timer table.
|
|
//
|
|
// N.B. The current instance of the timer expiration execution will only
|
|
// process the timer queue entries specified by the computed index
|
|
// and hand limit. If another timer expires while the current scan
|
|
// is in progress, then another scan will occur when the current one
|
|
// is finished.
|
|
//
|
|
|
|
Index = PtrToLong(SystemArgument1);
|
|
if ((ULONG)(HandLimit - Index) >= TIMER_TABLE_SIZE) {
|
|
HandLimit = Index + TIMER_TABLE_SIZE - 1;
|
|
}
|
|
|
|
Index -= 1;
|
|
HandLimit &= (TIMER_TABLE_SIZE - 1);
|
|
|
|
//
|
|
// Acquire the dispatcher database lock and read the current interrupt
|
|
// time to determine which timers have expired.
|
|
//
|
|
|
|
DpcCount = 0;
|
|
TimersExamined = MAXIMUM_TIMERS_EXAMINED;
|
|
TimersProcessed = MAXIMUM_TIMERS_PROCESSED;
|
|
KiLockDispatcherDatabase(&OldIrql);
|
|
do {
|
|
Index = (Index + 1) & (TIMER_TABLE_SIZE - 1);
|
|
ListHead = &KiTimerTableListHead[Index];
|
|
NextEntry = ListHead->Flink;
|
|
while (NextEntry != ListHead) {
|
|
Timer = CONTAINING_RECORD(NextEntry, KTIMER, TimerListEntry);
|
|
TimersExamined -= 1;
|
|
if (Timer->DueTime.QuadPart <= CurrentTime.QuadPart) {
|
|
|
|
//
|
|
// The next timer in the current timer list has expired.
|
|
// Remove the entry from the timer tree and set the signal
|
|
// state of the timer.
|
|
//
|
|
|
|
TimersProcessed -= 1;
|
|
KiRemoveTreeTimer(Timer);
|
|
Timer->Header.SignalState = 1;
|
|
|
|
//
|
|
// Capture the DPC and period fields from the timer object.
|
|
// Once wait test is called, the timer must not be touched
|
|
// again unless it is periodic. The reason for this is that
|
|
// a thread may allocate a timer on its local stack and wait
|
|
// on it. Wait test can cause that thread to immediately
|
|
// start running on another processor on an MP system. If
|
|
// the thread returns, then the timer will be corrupted.
|
|
//
|
|
|
|
Dpc = Timer->Dpc;
|
|
Period = Timer->Period;
|
|
if (IsListEmpty(&Timer->Header.WaitListHead) == FALSE) {
|
|
if (Timer->Header.Type == TimerNotificationObject) {
|
|
KiWaitTestWithoutSideEffects(Timer, TIMER_EXPIRE_INCREMENT);
|
|
|
|
} else {
|
|
KiWaitTestSynchronizationObject(Timer, TIMER_EXPIRE_INCREMENT);
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the timer is periodic, then compute the next interval
|
|
// time and reinsert the timer in the timer tree.
|
|
//
|
|
// N.B. Even though the timer insertion is relative, it can
|
|
// still fail if the period of the timer elapses in
|
|
// between computing the time and inserting the timer.
|
|
// If this happens, then the insertion is retried.
|
|
//
|
|
|
|
if (Period != 0) {
|
|
Interval.QuadPart = Int32x32To64(Period, - 10 * 1000);
|
|
do {
|
|
} while (KiInsertTreeTimer(Timer, Interval) == FALSE);
|
|
}
|
|
|
|
//
|
|
// If a DPC is specified, then insert it in the target
|
|
// processor's DPC queue or capture the parameters in
|
|
// the DPC table for subsequent execution on the current
|
|
// processor.
|
|
//
|
|
|
|
if (Dpc != NULL) {
|
|
|
|
#if defined(NT_UP)
|
|
|
|
DpcTable[DpcCount].Dpc = Dpc;
|
|
DpcTable[DpcCount].Routine = Dpc->DeferredRoutine;
|
|
DpcTable[DpcCount].Context = Dpc->DeferredContext;
|
|
DpcCount += 1;
|
|
|
|
#else
|
|
|
|
if (((Dpc->Number >= MAXIMUM_PROCESSORS) &&
|
|
(((ULONG)Dpc->Number - MAXIMUM_PROCESSORS) != KeGetCurrentProcessorNumber())) ||
|
|
((Dpc->Type == (UCHAR)ThreadedDpcObject) &&
|
|
(KeGetCurrentPrcb()->ThreadDpcEnable != FALSE))) {
|
|
|
|
KeInsertQueueDpc(Dpc,
|
|
ULongToPtr(SystemTime.LowPart),
|
|
ULongToPtr(SystemTime.HighPart));
|
|
|
|
} else {
|
|
DpcTable[DpcCount].Dpc = Dpc;
|
|
DpcTable[DpcCount].Routine = Dpc->DeferredRoutine;
|
|
DpcTable[DpcCount].Context = Dpc->DeferredContext;
|
|
DpcCount += 1;
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
//
|
|
// If the maximum number of timers have been processed or
|
|
// the maximum number of timers have been examined, then
|
|
// drop the dispatcher lock and process the DPC table.
|
|
//
|
|
|
|
if ((TimersProcessed == 0) || (TimersExamined == 0)) {
|
|
KiProcessTimerDpcTable(&SystemTime, &DpcTable[0], DpcCount);
|
|
|
|
//
|
|
// Initialize the DPC count, the scan counters, and
|
|
// acquire the dispatcher database lock.
|
|
//
|
|
// N.B. Control is returned with the dispatcher database
|
|
// unlocked.
|
|
//
|
|
|
|
DpcCount = 0;
|
|
TimersExamined = MAXIMUM_TIMERS_EXAMINED;
|
|
TimersProcessed = MAXIMUM_TIMERS_PROCESSED;
|
|
KiLockDispatcherDatabase(&DummyIrql);
|
|
}
|
|
|
|
NextEntry = ListHead->Flink;
|
|
|
|
} else {
|
|
|
|
//
|
|
// If the maximum number of timers have been scanned, then
|
|
// drop the dispatcher lock and process the DPC table.
|
|
//
|
|
|
|
if (TimersExamined == 0) {
|
|
KiProcessTimerDpcTable(&SystemTime, &DpcTable[0], DpcCount);
|
|
|
|
//
|
|
// Initialize the DPC count, the scan counters, and
|
|
// acquire the dispatcher database lock.
|
|
//
|
|
// N.B. Control is returned with the dispatcher database
|
|
// unlocked.
|
|
//
|
|
|
|
DpcCount = 0;
|
|
TimersExamined = MAXIMUM_TIMERS_EXAMINED;
|
|
TimersProcessed = MAXIMUM_TIMERS_PROCESSED;
|
|
KiLockDispatcherDatabase(&DummyIrql);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
} while(Index != HandLimit);
|
|
|
|
#if DBG
|
|
|
|
if (KeNumberProcessors == 1) {
|
|
KiCheckTimerTable(CurrentTime);
|
|
}
|
|
|
|
#endif
|
|
|
|
//
|
|
// If the DPC table is not empty, then process the remaining DPC table
|
|
// entries and lower IRQL. Otherwise, unlock the dispatcher database.
|
|
//
|
|
// N.B. Control is returned from the DPC processing routine with the
|
|
// dispatcher database unlocked.
|
|
//
|
|
|
|
if (DpcCount != 0) {
|
|
KiProcessTimerDpcTable(&SystemTime, &DpcTable[0], DpcCount);
|
|
if (OldIrql != DISPATCH_LEVEL) {
|
|
KeLowerIrql(OldIrql);
|
|
}
|
|
|
|
} else {
|
|
KiUnlockDispatcherDatabase(OldIrql);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
VOID
|
|
FASTCALL
|
|
KiTimerListExpire (
|
|
IN PLIST_ENTRY ExpiredListHead,
|
|
IN KIRQL OldIrql
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function is called to process a list of timers that have expired.
|
|
|
|
N.B. This function is called with the dispatcher database locked and
|
|
returns with the dispatcher database unlocked.
|
|
|
|
Arguments:
|
|
|
|
ExpiredListHead - Supplies a pointer to a list of timers that have
|
|
expired.
|
|
|
|
OldIrql - Supplies the previous IRQL.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
LONG Count;
|
|
PKDPC Dpc;
|
|
DPC_ENTRY DpcTable[MAXIMUM_DPC_TABLE_SIZE];
|
|
LARGE_INTEGER Interval;
|
|
KIRQL OldIrql1;
|
|
ULARGE_INTEGER SystemTime;
|
|
PKTIMER Timer;
|
|
LONG Period;
|
|
|
|
//
|
|
// Capture the timer expiration time.
|
|
//
|
|
|
|
KiQuerySystemTime((PLARGE_INTEGER)&SystemTime);
|
|
|
|
//
|
|
// Remove the next timer from the expired timer list, set the state of
|
|
// the timer to signaled, reinsert the timer in the timer tree if it is
|
|
// periodic, and optionally call the DPC routine if one is specified.
|
|
//
|
|
|
|
RestartScan:
|
|
Count = 0;
|
|
while (ExpiredListHead->Flink != ExpiredListHead) {
|
|
Timer = CONTAINING_RECORD(ExpiredListHead->Flink, KTIMER, TimerListEntry);
|
|
KiRemoveTreeTimer(Timer);
|
|
Timer->Header.SignalState = 1;
|
|
|
|
//
|
|
// Capture the DPC and period fields from the timer object. Once wait
|
|
// test is called, the timer must not be touched again unless it is
|
|
// periodic. The reason for this is that a thread may allocate a timer
|
|
// on its local stack and wait on it. Wait test can cause that thread
|
|
// to immediately start running on another processor on an MP system.
|
|
// If the thread returns, then the timer will be corrupted.
|
|
//
|
|
|
|
Dpc = Timer->Dpc;
|
|
Period = Timer->Period;
|
|
if (IsListEmpty(&Timer->Header.WaitListHead) == FALSE) {
|
|
if (Timer->Header.Type == TimerNotificationObject) {
|
|
KiWaitTestWithoutSideEffects(Timer, TIMER_EXPIRE_INCREMENT);
|
|
|
|
} else {
|
|
KiWaitTestSynchronizationObject(Timer, TIMER_EXPIRE_INCREMENT);
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the timer is periodic, then compute the next interval time
|
|
// and reinsert the timer in the timer tree.
|
|
//
|
|
// N.B. Even though the timer insertion is relative, it can still
|
|
// fail if the period of the timer elapses in between computing
|
|
// the time and inserting the timer. If this happens, then the
|
|
// insertion is retried.
|
|
//
|
|
|
|
if (Period != 0) {
|
|
Interval.QuadPart = Int32x32To64(Period, - 10 * 1000);
|
|
do {
|
|
} while (KiInsertTreeTimer(Timer, Interval) == FALSE);
|
|
}
|
|
|
|
//
|
|
// If a DPC is specified, then insert it in the target processor's
|
|
// DPC queue or capture the parameters in the DPC table for subsequent
|
|
// execution on the current processor.
|
|
//
|
|
|
|
if (Dpc != NULL) {
|
|
|
|
//
|
|
// If the DPC is explicitly targeted to another processor, then
|
|
// queue the DPC to the target processor. Otherwise, capture the
|
|
// DPC parameters for execution on the current processor.
|
|
//
|
|
|
|
#if defined(NT_UP)
|
|
|
|
DpcTable[Count].Dpc = Dpc;
|
|
DpcTable[Count].Routine = Dpc->DeferredRoutine;
|
|
DpcTable[Count].Context = Dpc->DeferredContext;
|
|
Count += 1;
|
|
if (Count == MAXIMUM_DPC_TABLE_SIZE) {
|
|
break;
|
|
}
|
|
|
|
#else
|
|
|
|
if (((Dpc->Number >= MAXIMUM_PROCESSORS) &&
|
|
(((ULONG)Dpc->Number - MAXIMUM_PROCESSORS) != KeGetCurrentProcessorNumber())) ||
|
|
((Dpc->Type == (UCHAR)ThreadedDpcObject) &&
|
|
(KeGetCurrentPrcb()->ThreadDpcEnable != FALSE))) {
|
|
|
|
KeInsertQueueDpc(Dpc,
|
|
ULongToPtr(SystemTime.LowPart),
|
|
ULongToPtr(SystemTime.HighPart));
|
|
|
|
} else {
|
|
DpcTable[Count].Dpc = Dpc;
|
|
DpcTable[Count].Routine = Dpc->DeferredRoutine;
|
|
DpcTable[Count].Context = Dpc->DeferredContext;
|
|
Count += 1;
|
|
if (Count == MAXIMUM_DPC_TABLE_SIZE) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
}
|
|
|
|
//
|
|
// Unlock the dispatcher database and process DPC list entries.
|
|
//
|
|
|
|
if (Count != 0) {
|
|
KiProcessTimerDpcTable(&SystemTime, &DpcTable[0], Count);
|
|
|
|
//
|
|
// If processing of the expired timer list was terminated because
|
|
// the DPC List was full, then process any remaining entries.
|
|
//
|
|
|
|
if (Count == MAXIMUM_DPC_TABLE_SIZE) {
|
|
KiLockDispatcherDatabase(&OldIrql1);
|
|
goto RestartScan;
|
|
}
|
|
|
|
KeLowerIrql(OldIrql);
|
|
|
|
} else {
|
|
KiUnlockDispatcherDatabase(OldIrql);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
FASTCALL
|
|
KiRetireDpcList (
|
|
PKPRCB Prcb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function processes the DPC list for the specified processor,
|
|
processes timer expiration, and processes the deferred ready list.
|
|
|
|
N.B. This function is entered with interrupts disabled and exits with
|
|
interrupts disabled.
|
|
|
|
Arguments:
|
|
|
|
Prcb - Supplies the address of the processor block.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
PKDPC Dpc;
|
|
PKDPC_DATA DpcData;
|
|
PVOID DeferredContext;
|
|
PKDEFERRED_ROUTINE DeferredRoutine;
|
|
PERFINFO_DPC_INFORMATION DpcInformation;
|
|
PLIST_ENTRY Entry;
|
|
PLIST_ENTRY ListHead;
|
|
LOGICAL Logging;
|
|
PVOID SystemArgument1;
|
|
PVOID SystemArgument2;
|
|
ULONG_PTR TimerHand;
|
|
LARGE_INTEGER TimeStamp = {0};
|
|
|
|
//
|
|
// Loop processing DPC list entries until the specified DPC list is empty.
|
|
//
|
|
// N.B. This following code appears to have a redundant loop, but it does
|
|
// not. The point of this code is to avoid as many dispatch interrupts
|
|
// as possible.
|
|
//
|
|
|
|
DpcData = &Prcb->DpcData[DPC_NORMAL];
|
|
ListHead = &DpcData->DpcListHead;
|
|
Logging = PERFINFO_IS_GROUP_ON(PERF_DPC);
|
|
do {
|
|
Prcb->DpcRoutineActive = TRUE;
|
|
|
|
//
|
|
// If the timer hand value is nonzero, then process expired timers.
|
|
//
|
|
|
|
if (Prcb->TimerRequest != 0) {
|
|
TimerHand = Prcb->TimerHand;
|
|
Prcb->TimerRequest = 0;
|
|
_enable();
|
|
KiTimerExpiration(NULL, NULL, (PVOID) TimerHand, NULL);
|
|
_disable();
|
|
}
|
|
|
|
//
|
|
// If the DPC list is not empty, then process the DPC list.
|
|
//
|
|
|
|
if (DpcData->DpcQueueDepth != 0) {
|
|
|
|
//
|
|
// Acquire the DPC lock for the current processor and check if
|
|
// the DPC list is empty. If the DPC list is not empty, then
|
|
// remove the first entry from the DPC list, capture the DPC
|
|
// parameters, set the DPC inserted state false, decrement the
|
|
// DPC queue depth, release the DPC lock, enable interrupts, and
|
|
// call the specified DPC routine. Otherwise, release the DPC
|
|
// lock and enable interrupts.
|
|
//
|
|
|
|
do {
|
|
KeAcquireSpinLockAtDpcLevel(&DpcData->DpcLock);
|
|
Entry = ListHead->Flink;
|
|
if (Entry != ListHead) {
|
|
RemoveEntryList(Entry);
|
|
Dpc = CONTAINING_RECORD(Entry, KDPC, DpcListEntry);
|
|
DeferredRoutine = Dpc->DeferredRoutine;
|
|
DeferredContext = Dpc->DeferredContext;
|
|
SystemArgument1 = Dpc->SystemArgument1;
|
|
SystemArgument2 = Dpc->SystemArgument2;
|
|
Dpc->DpcData = NULL;
|
|
DpcData->DpcQueueDepth -= 1;
|
|
|
|
#if DBG
|
|
|
|
Prcb->DebugDpcTime = 0;
|
|
|
|
#endif
|
|
|
|
KeReleaseSpinLockFromDpcLevel(&DpcData->DpcLock);
|
|
_enable();
|
|
|
|
//
|
|
// If event tracing is enabled, capture the start time.
|
|
//
|
|
|
|
if (Logging != FALSE) {
|
|
PerfTimeStamp(TimeStamp);
|
|
}
|
|
|
|
//
|
|
// Call the DPC routine.
|
|
//
|
|
|
|
(DeferredRoutine)(Dpc,
|
|
DeferredContext,
|
|
SystemArgument1,
|
|
SystemArgument2);
|
|
|
|
ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
|
|
|
|
//
|
|
// If event tracing is enabled, then log the start time
|
|
// and routine address.
|
|
//
|
|
|
|
if (Logging != FALSE) {
|
|
DpcInformation.InitialTime = TimeStamp.QuadPart;
|
|
DpcInformation.DpcRoutine = (PVOID) (ULONG_PTR) DeferredRoutine;
|
|
PerfInfoLogBytes(PERFINFO_LOG_TYPE_DPC,
|
|
&DpcInformation,
|
|
sizeof(DpcInformation));
|
|
}
|
|
|
|
_disable();
|
|
|
|
} else {
|
|
|
|
ASSERT(DpcData->DpcQueueDepth == 0);
|
|
|
|
KeReleaseSpinLockFromDpcLevel(&DpcData->DpcLock);
|
|
}
|
|
|
|
} while (DpcData->DpcQueueDepth != 0);
|
|
}
|
|
|
|
Prcb->DpcRoutineActive = FALSE;
|
|
Prcb->DpcInterruptRequested = FALSE;
|
|
KeMemoryBarrier();
|
|
|
|
//
|
|
// Process the deferred ready list if the list is not empty.
|
|
//
|
|
|
|
#if !defined(NT_UP)
|
|
|
|
if (Prcb->DeferredReadyListHead.Next != NULL) {
|
|
|
|
KIRQL OldIrql;
|
|
|
|
_enable();
|
|
OldIrql = KeRaiseIrqlToSynchLevel();
|
|
KiProcessDeferredReadyList(Prcb);
|
|
KeLowerIrql(OldIrql);
|
|
_disable();
|
|
}
|
|
#endif
|
|
|
|
} while (DpcData->DpcQueueDepth != 0);
|
|
|
|
return;
|
|
}
|