1 /* $Id: timer.c,v 1.93 2004/12/24 17:06:58 navaraf Exp $
3 * COPYRIGHT: See COPYING in the top level directory
4 * PROJECT: ReactOS kernel
5 * FILE: ntoskrnl/ke/timer.c
6 * PURPOSE: Handle timers
7 * PROGRAMMER: David Welch (welch@mcmail.com)
10 * 12/3/99: Phillip Susi: enabled the timers, fixed spin lock
13 /* NOTES ******************************************************************/
15 * System time units are 100-nanosecond intervals
18 /* INCLUDES ***************************************************************/
23 #include <internal/debug.h>
26 /* GLOBALS ****************************************************************/
32 LARGE_INTEGER SystemBootTime
= (LARGE_INTEGER
)0LL;
34 LARGE_INTEGER SystemBootTime
= { 0 };
37 CHAR KiTimerSystemAuditing
= 0;
40 * Number of timer interrupts since initialisation
42 volatile ULONGLONG KeTickCount
= 0;
43 volatile ULONG KiRawTicks
= 0;
46 * The increment in the system clock every timer tick (in system time units)
52 #define CLOCK_INCREMENT (100000)
55 ULONG EXPORTED KeMaximumIncrement
= 100000;
56 ULONG EXPORTED KeMinimumIncrement
= 100000;
58 /* Microsoft-style declarations */
59 EXPORTED ULONG KeMaximumIncrement
= 100000;
60 EXPORTED ULONG KeMinimumIncrement
= 100000;
66 * PURPOSE: List of timers
68 static LIST_ENTRY AbsoluteTimerListHead
;
69 static LIST_ENTRY RelativeTimerListHead
;
70 static KSPIN_LOCK TimerListLock
;
71 static KDPC ExpireTimerDpc
;
73 /* must raise IRQL to PROFILE_LEVEL and grab spin lock there, to sync with ISR */
75 extern HANDLE PsIdleThreadHandle
;
77 #define MICROSECONDS_PER_TICK (10000)
78 #define TICKS_TO_CALIBRATE (1)
79 #define CALIBRATE_PERIOD (MICROSECONDS_PER_TICK * TICKS_TO_CALIBRATE)
80 #define SYSTEM_TIME_UNITS_PER_MSEC (10000)
82 static BOOLEAN TimerInitDone
= FALSE
;
84 /* FUNCTIONS **************************************************************/
88 NtQueryTimerResolution(OUT PULONG MinimumResolution
,
89 OUT PULONG MaximumResolution
,
90 OUT PULONG ActualResolution
)
93 return STATUS_NOT_IMPLEMENTED
;
98 NtSetTimerResolution(IN ULONG DesiredResolution
,
99 IN BOOLEAN SetResolution
,
100 OUT PULONG CurrentResolution
)
103 return STATUS_NOT_IMPLEMENTED
;
108 NtQueryPerformanceCounter(IN PLARGE_INTEGER Counter
,
109 IN PLARGE_INTEGER Frequency
)
111 LARGE_INTEGER PerfCounter
;
112 LARGE_INTEGER PerfFrequency
;
115 PerfCounter
= KeQueryPerformanceCounter(&PerfFrequency
);
119 Status
= MmCopyToCaller(&Counter
->QuadPart
, &PerfCounter
.QuadPart
, sizeof(PerfCounter
.QuadPart
));
120 if (!NT_SUCCESS(Status
))
126 if (Frequency
!= NULL
)
128 Status
= MmCopyToCaller(&Frequency
->QuadPart
, &PerfFrequency
.QuadPart
, sizeof(PerfFrequency
.QuadPart
));
129 if (!NT_SUCCESS(Status
))
135 return(STATUS_SUCCESS
);
140 NtDelayExecution(IN ULONG Alertable
,
144 LARGE_INTEGER Timeout
;
146 Status
= MmCopyFromCaller(&Timeout
, Interval
, sizeof(Timeout
));
147 if (!NT_SUCCESS(Status
))
152 Timeout
= *((PLARGE_INTEGER
)Interval
);
153 DPRINT("NtDelayExecution(Alertable %d, Internal %x) IntervalP %x\n",
154 Alertable
, Internal
, Timeout
);
156 DPRINT("Execution delay is %d/%d\n",
157 Timeout
.u
.HighPart
, Timeout
.u
.LowPart
);
158 Status
= KeDelayExecutionThread(UserMode
, (BOOLEAN
)Alertable
, &Timeout
);
167 KeDelayExecutionThread (KPROCESSOR_MODE WaitMode
,
169 PLARGE_INTEGER Interval
)
171 * FUNCTION: Puts the current thread into an alertable or nonalertable
172 * wait state for a given internal
174 * WaitMode = Processor mode in which the caller is waiting
175 * Altertable = Specifies if the wait is alertable
176 * Interval = Specifies the interval to wait
180 PKTHREAD Thread
= KeGetCurrentThread();
182 KeSetTimer(&Thread
->Timer
, *Interval
, NULL
);
183 return (KeWaitForSingleObject(&Thread
->Timer
,
184 (WaitMode
== KernelMode
) ? Executive
: UserRequest
, /* TMN: Was unconditionally Executive */
185 WaitMode
, /* TMN: Was UserMode */
195 KeQueryTimeIncrement(VOID
)
197 * FUNCTION: Gets the increment (in 100-nanosecond units) that is added to
198 * the system clock every time the clock interrupts
199 * RETURNS: The increment
202 return(CLOCK_INCREMENT
);
207 * FUNCTION: Gets the current system time
209 * CurrentTime (OUT) = The routine stores the current time here
210 * NOTE: The time is the number of 100-nanosecond intervals since the
211 * 1st of January, 1601.
216 KeQuerySystemTime(PLARGE_INTEGER CurrentTime
)
220 CurrentTime
->u
.HighPart
= SharedUserData
->SystemTime
.High1Time
;
221 CurrentTime
->u
.LowPart
= SharedUserData
->SystemTime
.LowPart
;
223 while (CurrentTime
->u
.HighPart
!= SharedUserData
->SystemTime
.High2Time
);
227 KeQueryInterruptTime(VOID
)
229 LARGE_INTEGER CurrentTime
;
233 CurrentTime
.u
.HighPart
= SharedUserData
->InterruptTime
.High1Time
;
234 CurrentTime
.u
.LowPart
= SharedUserData
->InterruptTime
.LowPart
;
236 while (CurrentTime
.u
.HighPart
!= SharedUserData
->InterruptTime
.High2Time
);
238 return CurrentTime
.QuadPart
;
248 LARGE_INTEGER TickCount
;
249 KeQueryTickCount(&TickCount
);
250 return TickCount
.u
.LowPart
;
257 KeSetTimer (PKTIMER Timer
,
258 LARGE_INTEGER DueTime
,
261 * FUNCTION: Sets the absolute or relative interval at which a timer object
262 * is to be set to the signaled state and optionally supplies a
263 * CustomTimerDpc to be executed when the timer expires.
265 * Timer = Points to a previously initialized timer object
266 * DueTimer = If positive then absolute time to expire at
267 * If negative then the relative time to expire at
268 * Dpc = If non-NULL then a dpc to be called when the timer expires
269 * RETURNS: True if the timer was already in the system timer queue
273 return(KeSetTimerEx(Timer
, DueTime
, 0, Dpc
));
280 KeSetTimerEx (PKTIMER Timer
,
281 LARGE_INTEGER DueTime
,
285 * FUNCTION: Sets the absolute or relative interval at which a timer object
286 * is to be set to the signaled state and optionally supplies a
287 * CustomTimerDpc to be executed when the timer expires.
289 * Timer = Points to a previously initialized timer object
290 * DueTimer = If positive then absolute time to expire at
291 * If negative then the relative time to expire at
292 * Dpc = If non-NULL then a dpc to be called when the timer expires
293 * RETURNS: True if the timer was already in the system timer queue
299 BOOLEAN AlreadyInList
;
301 DPRINT("KeSetTimerEx(Timer %x), DueTime: \n",Timer
);
303 ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL
);
305 KeAcquireSpinLock(&TimerListLock
, &oldlvl
);
308 if (DueTime
.QuadPart
< 0)
310 Timer
->Header
.Absolute
= 0;
311 Timer
->DueTime
.QuadPart
= KeQueryInterruptTime() - DueTime
.QuadPart
;
315 KeQuerySystemTime(&Time
);
316 Timer
->Header
.Absolute
= 1;
317 if (DueTime
.QuadPart
>= Time
.QuadPart
)
319 Timer
->DueTime
.QuadPart
= DueTime
.QuadPart
;
323 Timer
->DueTime
.QuadPart
= Time
.QuadPart
;
326 Timer
->Period
= Period
;
327 Timer
->Header
.SignalState
= FALSE
;
328 AlreadyInList
= (Timer
->TimerListEntry
.Flink
== NULL
) ? FALSE
: TRUE
;
329 ASSERT((Timer
->TimerListEntry
.Flink
== NULL
&& Timer
->TimerListEntry
.Blink
== NULL
) ||
330 (Timer
->TimerListEntry
.Flink
!= NULL
&& Timer
->TimerListEntry
.Blink
!= NULL
));
333 RemoveEntryList(&Timer
->TimerListEntry
);
335 if (Timer
->Header
.Absolute
)
337 InsertAscendingList(&AbsoluteTimerListHead
,
346 InsertAscendingList(&RelativeTimerListHead
,
354 KeReleaseSpinLock(&TimerListLock
, oldlvl
);
356 return AlreadyInList
;
363 KeCancelTimer (PKTIMER Timer
)
365 * FUNCTION: Removes a timer from the system timer list
367 * Timer = timer to cancel
368 * RETURNS: True if the timer was running
374 DPRINT("KeCancelTimer(Timer %x)\n",Timer
);
376 KeAcquireSpinLock(&TimerListLock
, &oldlvl
);
378 if (Timer
->TimerListEntry
.Flink
== NULL
)
380 KeReleaseSpinLock(&TimerListLock
, oldlvl
);
383 if (Timer
->Header
.Absolute
)
385 ASSERT(&Timer
->TimerListEntry
!= &AbsoluteTimerListHead
);
389 ASSERT(&Timer
->TimerListEntry
!= &RelativeTimerListHead
);
391 ASSERT(Timer
->TimerListEntry
.Flink
!= &Timer
->TimerListEntry
);
392 RemoveEntryList(&Timer
->TimerListEntry
);
393 Timer
->TimerListEntry
.Flink
= Timer
->TimerListEntry
.Blink
= NULL
;
394 KeReleaseSpinLock(&TimerListLock
, oldlvl
);
403 KeReadStateTimer (PKTIMER Timer
)
405 return (BOOLEAN
)(Timer
->Header
.SignalState
);
412 KeInitializeTimer (PKTIMER Timer
)
414 * FUNCTION: Initalizes a kernel timer object
416 * Timer = caller supplied storage for the timer
417 * NOTE: This function initializes a notification timer
420 KeInitializeTimerEx(Timer
, NotificationTimer
);
427 KeInitializeTimerEx (PKTIMER Timer
,
430 * FUNCTION: Initializes a kernel timer object
432 * Timer = caller supplied storage for the timer
433 * Type = the type of timer (notification or synchronization)
434 * NOTE: When a notification type expires all waiting threads are released
435 * and the timer remains signalled until it is explicitly reset. When a
436 * syncrhonization timer expires its state is set to signalled until a
437 * single waiting thread is released and then the timer is reset.
442 if (Type
== NotificationTimer
)
444 IType
= InternalNotificationTimer
;
446 else if (Type
== SynchronizationTimer
)
448 IType
= InternalSynchronizationTimer
;
456 KeInitializeDispatcherHeader(&Timer
->Header
,
458 sizeof(KTIMER
) / sizeof(ULONG
),
460 Timer
->TimerListEntry
.Flink
= Timer
->TimerListEntry
.Blink
= NULL
;
467 KeQueryTickCount(PLARGE_INTEGER TickCount
)
469 * FUNCTION: Returns the number of ticks since the system was booted
471 * TickCount (OUT) = Points to storage for the number of ticks
474 TickCount
->QuadPart
= KeTickCount
;
482 KeQueryRuntimeThread(
487 /* Return the User Time */
488 *UserTime
= Thread
->UserTime
;
490 /* Return the Kernel Time */
491 return Thread
->KernelTime
;
500 IN ULONG MaxIncrement
,
501 IN ULONG MinIncrement
504 /* Set some Internal Variables */
505 /* FIXME: We use a harcoded CLOCK_INCREMENT. That *must* be changed */
506 KeMaximumIncrement
= MaxIncrement
;
507 KeMinimumIncrement
= MinIncrement
;
511 * We enter this function at IRQL DISPATCH_LEVEL, and with the
512 * TimerListLock held.
515 HandleExpiredTimer(PKTIMER Timer
)
517 DPRINT("HandleExpiredTime(Timer %x)\n", Timer
);
518 if (Timer
->Dpc
!= NULL
)
520 DPRINT("Timer->Dpc %x Timer->Dpc->DeferredRoutine %x\n",
521 Timer
->Dpc
, Timer
->Dpc
->DeferredRoutine
);
522 KeInsertQueueDpc(Timer
->Dpc
,
525 DPRINT("Finished dpc routine\n");
528 ASSERT_IRQL_EQUAL(DISPATCH_LEVEL
);
530 KeAcquireDispatcherDatabaseLockAtDpcLevel();
531 Timer
->Header
.SignalState
= TRUE
;
532 KiDispatcherObjectWake(&Timer
->Header
);
533 KeReleaseDispatcherDatabaseLockFromDpcLevel();
535 if (Timer
->Period
!= 0)
537 Timer
->DueTime
.QuadPart
+=
538 Timer
->Period
* SYSTEM_TIME_UNITS_PER_MSEC
;
539 if (Timer
->Header
.Absolute
)
541 InsertAscendingList(&AbsoluteTimerListHead
,
549 InsertAscendingList(&RelativeTimerListHead
,
559 KeExpireTimers(PKDPC Dpc
,
564 PLIST_ENTRY current_entry
= NULL
;
565 PKTIMER current
= NULL
;
566 ULONG Eip
= (ULONG
)Arg1
;
567 LARGE_INTEGER InterruptTime
;
568 LARGE_INTEGER SystemTime
;
569 LIST_ENTRY TimerList
;
571 DPRINT("KeExpireTimers()\n");
573 ASSERT_IRQL_EQUAL(DISPATCH_LEVEL
);
575 InitializeListHead(&TimerList
);
577 KeAcquireSpinLockAtDpcLevel(&TimerListLock
);
579 InterruptTime
.QuadPart
= KeQueryInterruptTime();
580 KeQuerySystemTime(&SystemTime
);
582 current_entry
= RelativeTimerListHead
.Flink
;
583 ASSERT(current_entry
);
584 while (current_entry
!= &RelativeTimerListHead
)
586 current
= CONTAINING_RECORD(current_entry
, KTIMER
, TimerListEntry
);
588 ASSERT(current_entry
!= &RelativeTimerListHead
);
589 ASSERT(current_entry
->Flink
!= current_entry
);
590 if ((ULONGLONG
)InterruptTime
.QuadPart
< current
->DueTime
.QuadPart
)
594 current_entry
= current_entry
->Flink
;
595 RemoveEntryList(¤t
->TimerListEntry
);
596 InsertTailList(&TimerList
, ¤t
->TimerListEntry
);
599 current_entry
= AbsoluteTimerListHead
.Flink
;
600 ASSERT(current_entry
);
601 while (current_entry
!= &AbsoluteTimerListHead
)
603 current
= CONTAINING_RECORD(current_entry
, KTIMER
, TimerListEntry
);
605 ASSERT(current_entry
!= &AbsoluteTimerListHead
);
606 ASSERT(current_entry
->Flink
!= current_entry
);
607 if ((ULONGLONG
)SystemTime
.QuadPart
< current
->DueTime
.QuadPart
)
611 current_entry
= current_entry
->Flink
;
612 RemoveEntryList(¤t
->TimerListEntry
);
613 InsertTailList(&TimerList
, ¤t
->TimerListEntry
);
616 while (!IsListEmpty(&TimerList
))
618 current_entry
= RemoveHeadList(&TimerList
);
619 current
= CONTAINING_RECORD(current_entry
, KTIMER
, TimerListEntry
);
620 current
->TimerListEntry
.Flink
= current
->TimerListEntry
.Blink
= NULL
;
621 HandleExpiredTimer(current
);
624 KiAddProfileEvent(ProfileTime
, Eip
);
626 KeReleaseSpinLockFromDpcLevel(&TimerListLock
);
631 KeInitializeTimerImpl(VOID
)
633 * FUNCTION: Initializes timer irq handling
634 * NOTE: This is only called once from main()
637 TIME_FIELDS TimeFields
;
639 DPRINT("KeInitializeTimerImpl()\n");
640 InitializeListHead(&AbsoluteTimerListHead
);
641 InitializeListHead(&RelativeTimerListHead
);
642 KeInitializeSpinLock(&TimerListLock
);
643 KeInitializeDpc(&ExpireTimerDpc
, KeExpireTimers
, 0);
645 * Calculate the starting time for the system clock
647 HalQueryRealTimeClock(&TimeFields
);
648 RtlTimeFieldsToTime(&TimeFields
, &SystemBootTime
);
650 SharedUserData
->TickCountLowDeprecated
= 0;
651 SharedUserData
->TickCountMultiplier
= 167783691; // 2^24 * 1193182 / 119310
652 SharedUserData
->InterruptTime
.High2Time
= 0;
653 SharedUserData
->InterruptTime
.LowPart
= 0;
654 SharedUserData
->InterruptTime
.High1Time
= 0;
655 SharedUserData
->SystemTime
.High2Time
= SystemBootTime
.u
.HighPart
;
656 SharedUserData
->SystemTime
.LowPart
= SystemBootTime
.u
.LowPart
;
657 SharedUserData
->SystemTime
.High1Time
= SystemBootTime
.u
.HighPart
;
659 TimerInitDone
= TRUE
;
660 DPRINT("Finished KeInitializeTimerImpl()\n");
668 KeSetTimeUpdateNotifyRoutine(
669 IN PTIME_UPDATE_NOTIFY_ROUTINE NotifyRoutine
677 * NOTE: On Windows this function takes exactly one parameter and EBP is
678 * guaranteed to point to KTRAP_FRAME. The function is used only
679 * by HAL, so there's no point in keeping that prototype.
686 IN PKTRAP_FRAME TrapFrame
,
691 PKTHREAD CurrentThread
;
692 PKPROCESS CurrentProcess
;
697 Pcr
= KeGetCurrentKPCR();
699 /* Make sure we don't go further if we're in early boot phase. */
700 if (Pcr
== NULL
|| Pcr
->PrcbData
.CurrentThread
== NULL
)
703 DPRINT("KernelTime %u, UserTime %u \n", Pcr
->PrcbData
.KernelTime
, Pcr
->PrcbData
.UserTime
);
705 CurrentThread
= Pcr
->PrcbData
.CurrentThread
;
706 CurrentProcess
= CurrentThread
->ApcState
.Process
;
709 * Cs bit 0 is always set for user mode if we are in protected mode.
710 * V86 mode is counted as user time.
712 if (TrapFrame
->Cs
& 0x1 ||
713 TrapFrame
->Eflags
& X86_EFLAGS_VM
)
715 InterlockedIncrementUL(&CurrentThread
->UserTime
);
716 InterlockedIncrementUL(&CurrentProcess
->UserTime
);
717 Pcr
->PrcbData
.UserTime
++;
721 if (Irql
> DISPATCH_LEVEL
)
723 Pcr
->PrcbData
.InterruptTime
++;
725 else if (Irql
== DISPATCH_LEVEL
)
727 Pcr
->PrcbData
.DpcTime
++;
731 InterlockedIncrementUL(&CurrentThread
->KernelTime
);
732 InterlockedIncrementUL(&CurrentProcess
->KernelTime
);
733 Pcr
->PrcbData
.KernelTime
++;
738 DpcLastCount
= Pcr
->PrcbData
.DpcLastCount
;
739 Pcr
->PrcbData
.DpcLastCount
= Pcr
->PrcbData
.DpcCount
;
740 Pcr
->PrcbData
.DpcRequestRate
= ((Pcr
->PrcbData
.DpcCount
- DpcLastCount
) +
741 Pcr
->PrcbData
.DpcRequestRate
) / 2;
744 if (Pcr
->PrcbData
.DpcData
[0].DpcQueueDepth
> 0 &&
745 Pcr
->PrcbData
.DpcRoutineActive
== FALSE
&&
746 Pcr
->PrcbData
.DpcInterruptRequested
== FALSE
)
748 HalRequestSoftwareInterrupt(DISPATCH_LEVEL
);
751 /* FIXME: Do DPC rate adjustments */
754 * If we're at end of quantum request software interrupt. The rest
755 * is handled in KiDispatchInterrupt.
757 if ((CurrentThread
->Quantum
-= 3) <= 0)
759 Pcr
->PrcbData
.QuantumEnd
= TRUE
;
760 HalRequestSoftwareInterrupt(DISPATCH_LEVEL
);
766 * NOTE: On Windows this function takes exactly zero parameters and EBP is
767 * guaranteed to point to KTRAP_FRAME. Also [esp+0] contains an IRQL.
768 * The function is used only by HAL, so there's no point in keeping
776 IN PKTRAP_FRAME TrapFrame
,
780 * FUNCTION: Handles a timer interrupt
785 ASSERT(KeGetCurrentIrql() == PROFILE_LEVEL
);
789 if (TimerInitDone
== FALSE
)
794 * Increment the number of timers ticks
797 SharedUserData
->TickCountLowDeprecated
++;
799 Time
.u
.LowPart
= SharedUserData
->InterruptTime
.LowPart
;
800 Time
.u
.HighPart
= SharedUserData
->InterruptTime
.High1Time
;
801 Time
.QuadPart
+= CLOCK_INCREMENT
;
802 SharedUserData
->InterruptTime
.High2Time
= Time
.u
.HighPart
;
803 SharedUserData
->InterruptTime
.LowPart
= Time
.u
.LowPart
;
804 SharedUserData
->InterruptTime
.High1Time
= Time
.u
.HighPart
;
806 Time
.u
.LowPart
= SharedUserData
->SystemTime
.LowPart
;
807 Time
.u
.HighPart
= SharedUserData
->SystemTime
.High1Time
;
808 Time
.QuadPart
+= CLOCK_INCREMENT
;
809 SharedUserData
->SystemTime
.High2Time
= Time
.u
.HighPart
;
810 SharedUserData
->SystemTime
.LowPart
= Time
.u
.LowPart
;
811 SharedUserData
->SystemTime
.High1Time
= Time
.u
.HighPart
;
813 /* FIXME: Here we should check for remote debugger break-ins */
815 /* Update process and thread times */
816 KeUpdateRunTime(TrapFrame
, Irql
);
819 * Queue a DPC that will expire timers
821 KeInsertQueueDpc(&ExpireTimerDpc
, (PVOID
)TrapFrame
->Eip
, 0);
826 KiSetSystemTime(PLARGE_INTEGER NewSystemTime
)
828 LARGE_INTEGER OldSystemTime
;
829 LARGE_INTEGER DeltaTime
;
831 PLIST_ENTRY current_entry
= NULL
;
832 PKTIMER current
= NULL
;
834 ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL
);
836 OldIrql
= KeAcquireDispatcherDatabaseLock();
840 OldSystemTime
.u
.HighPart
= SharedUserData
->SystemTime
.High1Time
;
841 OldSystemTime
.u
.LowPart
= SharedUserData
->SystemTime
.LowPart
;
843 while (OldSystemTime
.u
.HighPart
!= SharedUserData
->SystemTime
.High2Time
);
845 /* Set the new system time */
846 SharedUserData
->SystemTime
.LowPart
= NewSystemTime
->u
.LowPart
;
847 SharedUserData
->SystemTime
.High1Time
= NewSystemTime
->u
.HighPart
;
848 SharedUserData
->SystemTime
.High2Time
= NewSystemTime
->u
.HighPart
;
850 /* Calculate the difference between the new and the old time */
851 DeltaTime
.QuadPart
= NewSystemTime
->QuadPart
- OldSystemTime
.QuadPart
;
853 /* Update system boot time */
854 SystemBootTime
.QuadPart
+= DeltaTime
.QuadPart
;
856 /* Update all relative timers */
857 current_entry
= RelativeTimerListHead
.Flink
;
858 ASSERT(current_entry
);
859 while (current_entry
!= &RelativeTimerListHead
)
861 current
= CONTAINING_RECORD(current_entry
, KTIMER
, TimerListEntry
);
863 ASSERT(current_entry
!= &RelativeTimerListHead
);
864 ASSERT(current_entry
->Flink
!= current_entry
);
866 current
->DueTime
.QuadPart
+= DeltaTime
.QuadPart
;
868 current_entry
= current_entry
->Flink
;
871 KeReleaseDispatcherDatabaseLock(OldIrql
);
874 * NOTE: Expired timers will be processed at the next clock tick!