Reimplemented Kernel and Executive Timers. Use Microsoft documented structures, added locks to protect against race conditions, use dispatcher database, implement new features like wake timers, use thread timer lists, simplify timer list, add more comments and debug info, actually add apc/dpc functions which do what the should do, fix a couple of bugs, simplfiy absolute vs relative timers. I will do more testing and see wether an experimental Timer Tree patch (which greatly speeds up timer processing) is necessary.

svn path=/trunk/; revision=13554
This commit is contained in:
Alex Ionescu 2005-02-14 05:36:04 +00:00
parent 44b4c1125e
commit 3f700c132c
10 changed files with 1458 additions and 1292 deletions

View file

@ -246,6 +246,12 @@ KeQueryTimeIncrement (
VOID
);
ULONGLONG
STDCALL
KeQueryInterruptTime(
VOID
);
VOID
STDCALL
KeRaiseIrql (

View file

@ -96,6 +96,7 @@ OBJECTS_KE = \
ke/apc.o \
ke/bug.o \
ke/catch.o \
ke/clock.o \
ke/critical.o \
ke/dpc.o \
ke/device.o \

View file

@ -5,7 +5,8 @@
* FILE: ntoskrnl/ex/timer.c
* PURPOSE: User-mode timers
*
* PROGRAMMERS: David Welch (welch@mcmail.com)
* PROGRAMMERS: Alex Ionescu (alex@relsoft.net) - Reimplemented
* David Welch (welch@mcmail.com)
*/
/* INCLUDES *****************************************************************/
@ -13,121 +14,176 @@
#include <ntoskrnl.h>
#include <internal/debug.h>
/* TYPES ********************************************************************/
typedef struct _NTTIMER
{
KTIMER Timer;
KDPC Dpc;
KAPC Apc;
BOOLEAN Running;
} NTTIMER, *PNTTIMER;
/* Executive Timer Object */
typedef struct _ETIMER {
KTIMER KeTimer;
KAPC TimerApc;
KDPC TimerDpc;
LIST_ENTRY ActiveTimerListEntry;
KSPIN_LOCK Lock;
LONG Period;
BOOLEAN ApcAssociated;
BOOLEAN WakeTimer;
LIST_ENTRY WakeTimerListEntry;
} ETIMER, *PETIMER;
/* GLOBALS ******************************************************************/
/* Timer Object Type */
POBJECT_TYPE ExTimerType = NULL;
KSPIN_LOCK ExpWakeListLock;
LIST_ENTRY ExpWakeList;
/* Timer Mapping */
static GENERIC_MAPPING ExpTimerMapping = {
STANDARD_RIGHTS_READ | TIMER_QUERY_STATE,
STANDARD_RIGHTS_WRITE | TIMER_MODIFY_STATE,
STANDARD_RIGHTS_EXECUTE | SYNCHRONIZE,
TIMER_ALL_ACCESS};
TIMER_ALL_ACCESS
};
static const INFORMATION_CLASS_INFO ExTimerInfoClass[] =
{
ICI_SQ_SAME( sizeof(TIMER_BASIC_INFORMATION), sizeof(ULONG), ICIF_QUERY ), /* TimerBasicInformation */
/* Timer Information Classes */
static const INFORMATION_CLASS_INFO ExTimerInfoClass[] = {
/* TimerBasicInformation */
ICI_SQ_SAME( sizeof(TIMER_BASIC_INFORMATION), sizeof(ULONG), ICIF_QUERY ),
};
/* FUNCTIONS *****************************************************************/
NTSTATUS STDCALL
ExpCreateTimer(PVOID ObjectBody,
PVOID Parent,
PWSTR RemainingPath,
POBJECT_ATTRIBUTES ObjectAttributes)
{
DPRINT("ExpCreateTimer(ObjectBody %x, Parent %x, RemainingPath %S)\n",
ObjectBody, Parent, RemainingPath);
if (RemainingPath != NULL && wcschr(RemainingPath+1, '\\') != NULL)
{
return(STATUS_UNSUCCESSFUL);
}
return(STATUS_SUCCESS);
}
VOID STDCALL
VOID
STDCALL
ExpDeleteTimer(PVOID ObjectBody)
{
KIRQL OldIrql;
PNTTIMER Timer = ObjectBody;
PETIMER Timer = ObjectBody;
DPRINT("ExpDeleteTimer()\n");
DPRINT("ExpDeleteTimer(Timer: %x)\n", Timer);
OldIrql = KeRaiseIrqlToDpcLevel();
/* Lock the Wake List */
KeAcquireSpinLock(&ExpWakeListLock, &OldIrql);
KeCancelTimer(&Timer->Timer);
KeRemoveQueueDpc(&Timer->Dpc);
KeRemoveQueueApc(&Timer->Apc);
Timer->Running = FALSE;
/* Check if it has a Wait List */
if (!IsListEmpty(&Timer->WakeTimerListEntry)) {
KeLowerIrql(OldIrql);
/* Remove it from the Wait List */
DPRINT("Removing wake list\n");
RemoveEntryList(&Timer->WakeTimerListEntry);
}
/* Release the Wake List */
KeReleaseSpinLock(&ExpWakeListLock, OldIrql);
VOID STDCALL
/* Tell the Kernel to cancel the Timer */
DPRINT("Cancelling Timer\n");
KeCancelTimer(&Timer->KeTimer);
}
VOID
STDCALL
ExpTimerDpcRoutine(PKDPC Dpc,
PVOID DeferredContext,
PVOID SystemArgument1,
PVOID SystemArgument2)
{
PNTTIMER Timer;
PETIMER Timer;
KIRQL OldIrql;
DPRINT("ExpTimerDpcRoutine()\n");
DPRINT("ExpTimerDpcRoutine(Dpc: %x)\n", Dpc);
Timer = (PNTTIMER)DeferredContext;
/* Get the Timer Object */
Timer = (PETIMER)DeferredContext;
if ( Timer->Running )
{
KeInsertQueueApc(&Timer->Apc,
/* Lock the Timer */
KeAcquireSpinLock(&Timer->Lock, &OldIrql);
/* Queue the APC */
if(Timer->ApcAssociated) {
DPRINT("Queuing APC\n");
KeInsertQueueApc(&Timer->TimerApc,
SystemArgument1,
SystemArgument2,
IO_NO_INCREMENT);
}
/* Release the Timer */
KeReleaseSpinLock(&Timer->Lock, OldIrql);
}
VOID STDCALL
VOID
STDCALL
ExpTimerApcKernelRoutine(PKAPC Apc,
PKNORMAL_ROUTINE* NormalRoutine,
PVOID* NormalContext,
PVOID* SystemArgument1,
PVOID* SystemArguemnt2)
{
DPRINT("ExpTimerApcKernelRoutine()\n");
PETIMER Timer;
PETHREAD CurrentThread = PsGetCurrentThread();
KIRQL OldIrql;
/* We need to find out which Timer we are */
Timer = CONTAINING_RECORD(Apc, ETIMER, TimerApc);
DPRINT("ExpTimerApcKernelRoutine(Apc: %x. Timer: %x)\n", Apc, Timer);
/* Lock the Timer */
KeAcquireSpinLock(&Timer->Lock, &OldIrql);
/* Lock the Thread's Active Timer List*/
KeAcquireSpinLockAtDpcLevel(&CurrentThread->ActiveTimerListLock);
/*
* Make sure that the Timer is still valid, and that it belongs to this thread
* Remove it if it's not periodic
*/
if ((Timer->ApcAssociated) &&
(&CurrentThread->Tcb == Timer->TimerApc.Thread) &&
(!Timer->Period)) {
/* Remove it from the Active Timers List */
DPRINT("Removing Timer\n");
RemoveEntryList(&Timer->ActiveTimerListEntry);
/* Disable it */
Timer->ApcAssociated = FALSE;
/* Release spinlocks */
KeReleaseSpinLockFromDpcLevel(&CurrentThread->ActiveTimerListLock);
KeReleaseSpinLock(&Timer->Lock, OldIrql);
/* Dereference the Timer Object */
ObDereferenceObject(Timer);
return;
}
/* Release spinlocks */
KeReleaseSpinLockFromDpcLevel(&CurrentThread->ActiveTimerListLock);
KeReleaseSpinLock(&Timer->Lock, OldIrql);
}
VOID INIT_FUNCTION
VOID
INIT_FUNCTION
ExpInitializeTimerImplementation(VOID)
{
ASSERT(!ExTimerType)
DPRINT("ExpInitializeTimerImplementation()\n");
/* Allocate Memory for the Timer */
ExTimerType = ExAllocatePool(NonPagedPool, sizeof(OBJECT_TYPE));
/* Create the Executive Timer Object */
RtlpCreateUnicodeString(&ExTimerType->TypeName, L"Timer", NonPagedPool);
ExTimerType->Tag = TAG('T', 'I', 'M', 'T');
ExTimerType->PeakObjects = 0;
ExTimerType->PeakHandles = 0;
ExTimerType->TotalObjects = 0;
ExTimerType->TotalHandles = 0;
ExTimerType->PagedPoolCharge = 0;
ExTimerType->NonpagedPoolCharge = sizeof(NTTIMER);
ExTimerType->NonpagedPoolCharge = sizeof(ETIMER);
ExTimerType->Mapping = &ExpTimerMapping;
ExTimerType->Dump = NULL;
ExTimerType->Open = NULL;
@ -137,196 +193,232 @@ ExpInitializeTimerImplementation(VOID)
ExTimerType->Security = NULL;
ExTimerType->QueryName = NULL;
ExTimerType->OkayToClose = NULL;
ExTimerType->Create = ExpCreateTimer;
ExTimerType->Create = NULL;
ExTimerType->DuplicationNotify = NULL;
ObpCreateTypeObject(ExTimerType);
/* Initialize the Wait List and Lock */
KeInitializeSpinLock(&ExpWakeListLock);
InitializeListHead(&ExpWakeList);
}
NTSTATUS STDCALL
NTSTATUS
STDCALL
NtCancelTimer(IN HANDLE TimerHandle,
OUT PBOOLEAN CurrentState OPTIONAL)
{
PNTTIMER Timer;
KPROCESSOR_MODE PreviousMode;
PETIMER Timer;
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
NTSTATUS Status = STATUS_SUCCESS;
PreviousMode = ExGetPreviousMode();
BOOLEAN State;
KIRQL OldIrql;
BOOLEAN KillTimer = FALSE;
PETHREAD TimerThread;
DPRINT("NtCancelTimer(0x%x, 0x%x)\n", TimerHandle, CurrentState);
if(CurrentState != NULL && PreviousMode != KernelMode)
{
_SEH_TRY
{
/* Check Parameter Validity */
if(CurrentState != NULL && PreviousMode != KernelMode) {
_SEH_TRY {
ProbeForWrite(CurrentState,
sizeof(BOOLEAN),
sizeof(BOOLEAN));
}
_SEH_HANDLE
{
} _SEH_HANDLE {
Status = _SEH_GetExceptionCode();
}
_SEH_END;
} _SEH_END;
if(!NT_SUCCESS(Status))
{
if(!NT_SUCCESS(Status)) {
return Status;
}
}
/* Get the Timer Object */
Status = ObReferenceObjectByHandle(TimerHandle,
TIMER_ALL_ACCESS,
ExTimerType,
PreviousMode,
(PVOID*)&Timer,
NULL);
if(NT_SUCCESS(Status))
{
BOOLEAN State;
KIRQL OldIrql = KeRaiseIrqlToDpcLevel();
State = KeCancelTimer(&Timer->Timer);
KeRemoveQueueDpc(&Timer->Dpc);
KeRemoveQueueApc(&Timer->Apc);
Timer->Running = FALSE;
/* Check for success */
if(NT_SUCCESS(Status)) {
KeLowerIrql(OldIrql);
DPRINT("Timer Referencced: %x\n", Timer);
/* Lock the Timer */
KeAcquireSpinLock(&Timer->Lock, &OldIrql);
/* Check if it's enabled */
if (Timer->ApcAssociated) {
/*
* First, remove it from the Thread's Active List
* Get the Thread.
*/
TimerThread = CONTAINING_RECORD(Timer->TimerApc.Thread, ETHREAD, Tcb);
DPRINT("Removing from Thread: %x\n", TimerThread);
/* Lock its active list */
KeAcquireSpinLockAtDpcLevel(&TimerThread->ActiveTimerListLock);
/* Remove it */
RemoveEntryList(&TimerThread->ActiveTimerListHead);
/* Unlock the list */
KeReleaseSpinLockFromDpcLevel(&TimerThread->ActiveTimerListLock);
/* Cancel the Timer */
KeCancelTimer(&Timer->KeTimer);
KeRemoveQueueDpc(&Timer->TimerDpc);
KeRemoveQueueApc(&Timer->TimerApc);
Timer->ApcAssociated = FALSE;
KillTimer = TRUE;
} else {
/* If timer was disabled, we still need to cancel it */
DPRINT("APC was not Associated. Cancelling Timer\n");
KeCancelTimer(&Timer->KeTimer);
}
/* Read the old State */
State = KeReadStateTimer(&Timer->KeTimer);
/* Dereference the Object */
ObDereferenceObject(Timer);
if(CurrentState != NULL)
{
_SEH_TRY
{
/* Unlock the Timer */
KeReleaseSpinLock(&Timer->Lock, OldIrql);
/* Dereference if it was previously enabled */
if (KillTimer) ObDereferenceObject(Timer);
DPRINT1("Timer disabled\n");
/* Make sure it's safe to write to the handle */
if(CurrentState != NULL) {
_SEH_TRY {
*CurrentState = State;
}
_SEH_HANDLE
{
} _SEH_HANDLE {
Status = _SEH_GetExceptionCode();
}
_SEH_END;
} _SEH_END;
}
}
/* Return to Caller */
return Status;
}
NTSTATUS STDCALL
NTSTATUS
STDCALL
NtCreateTimer(OUT PHANDLE TimerHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN TIMER_TYPE TimerType)
{
PNTTIMER Timer;
PETIMER Timer;
HANDLE hTimer;
KPROCESSOR_MODE PreviousMode;
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
NTSTATUS Status = STATUS_SUCCESS;
DPRINT("NtCreateTimer()\n");
DPRINT("NtCreateTimer(Handle: %x, Type: %d)\n", TimerHandle, TimerType);
PreviousMode = ExGetPreviousMode();
if(PreviousMode != KernelMode)
{
_SEH_TRY
{
/* Check Parameter Validity */
if (PreviousMode != KernelMode) {
_SEH_TRY {
ProbeForWrite(TimerHandle,
sizeof(HANDLE),
sizeof(ULONG));
}
_SEH_HANDLE
{
} _SEH_HANDLE {
Status = _SEH_GetExceptionCode();
}
_SEH_END;
} _SEH_END;
if(!NT_SUCCESS(Status))
{
if(!NT_SUCCESS(Status)) {
return Status;
}
}
/* Create the Object */
Status = ObCreateObject(PreviousMode,
ExTimerType,
ObjectAttributes,
PreviousMode,
NULL,
sizeof(NTTIMER),
sizeof(ETIMER),
0,
0,
(PVOID*)&Timer);
if(NT_SUCCESS(Status))
{
KeInitializeTimerEx(&Timer->Timer,
TimerType);
KeInitializeDpc(&Timer->Dpc,
&ExpTimerDpcRoutine,
Timer);
/* Check for Success */
if(NT_SUCCESS(Status)) {
Timer->Running = FALSE;
/* Initialize the Kernel Timer */
DPRINT("Initializing Timer: %x\n", Timer);
KeInitializeTimerEx(&Timer->KeTimer, TimerType);
/* Initialize the Timer Lock */
KeInitializeSpinLock(&Timer->Lock);
/* Initialize the DPC */
KeInitializeDpc(&Timer->TimerDpc, ExpTimerDpcRoutine, Timer);
/* Set Initial State */
Timer->ApcAssociated = FALSE;
InitializeListHead(&Timer->WakeTimerListEntry);
Timer->WakeTimer = FALSE;
/* Insert the Timer */
Status = ObInsertObject((PVOID)Timer,
NULL,
DesiredAccess,
0,
NULL,
&hTimer);
ObDereferenceObject(Timer);
DPRINT("Timer Inserted\n");
if(NT_SUCCESS(Status))
{
_SEH_TRY
{
/* Make sure it's safe to write to the handle */
_SEH_TRY {
*TimerHandle = hTimer;
}
_SEH_HANDLE
{
} _SEH_HANDLE {
Status = _SEH_GetExceptionCode();
}
_SEH_END;
}
} _SEH_END;
}
/* Return to Caller */
return Status;
}
NTSTATUS STDCALL
NTSTATUS
STDCALL
NtOpenTimer(OUT PHANDLE TimerHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes)
{
HANDLE hTimer;
KPROCESSOR_MODE PreviousMode;
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
NTSTATUS Status = STATUS_SUCCESS;
DPRINT("NtOpenTimer()\n");
DPRINT("NtOpenTimer(TimerHandle: %x)\n", TimerHandle);
PreviousMode = ExGetPreviousMode();
if(PreviousMode != KernelMode)
{
_SEH_TRY
{
/* Check Parameter Validity */
if (PreviousMode != KernelMode) {
_SEH_TRY {
ProbeForWrite(TimerHandle,
sizeof(HANDLE),
sizeof(ULONG));
}
_SEH_HANDLE
{
} _SEH_HANDLE {
Status = _SEH_GetExceptionCode();
}
_SEH_END;
} _SEH_END;
if(!NT_SUCCESS(Status))
{
if(!NT_SUCCESS(Status)) {
return Status;
}
}
/* Open the Timer */
Status = ObOpenObjectByName(ObjectAttributes,
ExTimerType,
NULL,
@ -334,36 +426,39 @@ NtOpenTimer(OUT PHANDLE TimerHandle,
DesiredAccess,
NULL,
&hTimer);
if(NT_SUCCESS(Status))
{
_SEH_TRY
{
/* Check for success */
if(NT_SUCCESS(Status)) {
/* Make sure it's safe to write to the handle */
_SEH_TRY {
*TimerHandle = hTimer;
}
_SEH_HANDLE
{
} _SEH_HANDLE {
Status = _SEH_GetExceptionCode();
}
_SEH_END;
} _SEH_END;
}
/* Return to Caller */
return Status;
}
NTSTATUS STDCALL
NTSTATUS
STDCALL
NtQueryTimer(IN HANDLE TimerHandle,
IN TIMER_INFORMATION_CLASS TimerInformationClass,
OUT PVOID TimerInformation,
IN ULONG TimerInformationLength,
OUT PULONG ReturnLength OPTIONAL)
{
PNTTIMER Timer;
KPROCESSOR_MODE PreviousMode;
PETIMER Timer;
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
NTSTATUS Status = STATUS_SUCCESS;
PTIMER_BASIC_INFORMATION BasicInfo = (PTIMER_BASIC_INFORMATION)TimerInformation;
PreviousMode = ExGetPreviousMode();
DPRINT("NtQueryTimer(TimerHandle: %x, Class: %d)\n", TimerHandle, TimerInformationClass);
/* Check Validity */
DefaultQueryInfoBufferCheck(TimerInformationClass,
ExTimerInfoClass,
TimerInformation,
@ -371,169 +466,212 @@ NtQueryTimer(IN HANDLE TimerHandle,
ReturnLength,
PreviousMode,
&Status);
if(!NT_SUCCESS(Status))
{
if(!NT_SUCCESS(Status)) {
DPRINT1("NtQueryTimer() failed, Status: 0x%x\n", Status);
return Status;
}
/* Get the Timer Object */
Status = ObReferenceObjectByHandle(TimerHandle,
TIMER_QUERY_STATE,
ExTimerType,
PreviousMode,
(PVOID*)&Timer,
NULL);
if(NT_SUCCESS(Status))
{
switch(TimerInformationClass)
{
case TimerBasicInformation:
{
PTIMER_BASIC_INFORMATION BasicInfo = (PTIMER_BASIC_INFORMATION)TimerInformation;
_SEH_TRY
{
/* FIXME - interrupt correction */
BasicInfo->TimeRemaining.QuadPart = Timer->Timer.DueTime.QuadPart;
BasicInfo->SignalState = (BOOLEAN)Timer->Timer.Header.SignalState;
/* Check for Success */
if(NT_SUCCESS(Status)) {
if(ReturnLength != NULL)
{
/* Return the Basic Information */
_SEH_TRY {
/* FIXME: Interrupt correction based on Interrupt Time */
DPRINT("Returning Information for Timer: %x. Time Remaining: %d\n", Timer, Timer->KeTimer.DueTime.QuadPart);
BasicInfo->TimeRemaining.QuadPart = Timer->KeTimer.DueTime.QuadPart;
BasicInfo->SignalState = KeReadStateTimer(&Timer->KeTimer);
ObDereferenceObject(Timer);
if(ReturnLength != NULL) {
*ReturnLength = sizeof(TIMER_BASIC_INFORMATION);
}
}
_SEH_HANDLE
{
} _SEH_HANDLE {
Status = _SEH_GetExceptionCode();
}
_SEH_END;
break;
}
default:
Status = STATUS_NOT_IMPLEMENTED;
break;
}
ObDereferenceObject(Timer);
} _SEH_END;
}
/* Return Status */
return Status;
}
NTSTATUS STDCALL
NTSTATUS
STDCALL
NtSetTimer(IN HANDLE TimerHandle,
IN PLARGE_INTEGER DueTime,
IN PTIMER_APC_ROUTINE TimerApcRoutine OPTIONAL,
IN PVOID TimerContext OPTIONAL,
IN BOOLEAN ResumeTimer,
IN BOOLEAN WakeTimer,
IN LONG Period OPTIONAL,
OUT PBOOLEAN PreviousState OPTIONAL)
{
PNTTIMER Timer;
BOOLEAN Result;
PETIMER Timer;
KIRQL OldIrql;
BOOLEAN KillTimer;
BOOLEAN State;
LARGE_INTEGER TimerDueTime;
KPROCESSOR_MODE PreviousMode;
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
PETHREAD CurrentThread = PsGetCurrentThread();
NTSTATUS Status = STATUS_SUCCESS;
LARGE_INTEGER TimerDueTime;
PETHREAD TimerThread;
DPRINT("NtSetTimer()\n");
DPRINT("NtSetTimer(TimerHandle: %x, DueTime: %d, Apc: %x, Period: %d)\n", TimerHandle, DueTime->QuadPart, TimerApcRoutine, Period);
PreviousMode = ExGetPreviousMode();
if(PreviousMode != KernelMode)
{
_SEH_TRY
{
/* Check Parameter Validity */
if (PreviousMode != KernelMode) {
_SEH_TRY {
ProbeForRead(DueTime,
sizeof(LARGE_INTEGER),
sizeof(ULONG));
TimerDueTime = *DueTime;
if(PreviousState != NULL)
{
if(PreviousState != NULL) {
ProbeForWrite(PreviousState,
sizeof(BOOLEAN),
sizeof(BOOLEAN));
}
}
_SEH_HANDLE
{
Status = _SEH_GetExceptionCode();
}
_SEH_END;
if(!NT_SUCCESS(Status))
{
} _SEH_HANDLE {
Status = _SEH_GetExceptionCode();
} _SEH_END;
if(!NT_SUCCESS(Status)) {
return Status;
}
}
/* Get the Timer Object */
Status = ObReferenceObjectByHandle(TimerHandle,
TIMER_ALL_ACCESS,
ExTimerType,
PreviousMode,
(PVOID*)&Timer,
NULL);
if (!NT_SUCCESS(Status))
{
return Status;
/* Check status */
if (NT_SUCCESS(Status)) {
/* Lock the Timer */
DPRINT("Timer Referencced: %x\n", Timer);
KeAcquireSpinLock(&Timer->Lock, &OldIrql);
/* Cancel Running Timer */
if (Timer->ApcAssociated) {
/*
* First, remove it from the Thread's Active List
* Get the Thread.
*/
TimerThread = CONTAINING_RECORD(Timer->TimerApc.Thread, ETHREAD, Tcb);
DPRINT("Thread already running. Removing from Thread: %x\n", TimerThread);
/* Lock its active list */
KeAcquireSpinLockAtDpcLevel(&TimerThread->ActiveTimerListLock);
/* Remove it */
RemoveEntryList(&TimerThread->ActiveTimerListHead);
/* Unlock the list */
KeReleaseSpinLockFromDpcLevel(&TimerThread->ActiveTimerListLock);
/* Cancel the Timer */
KeCancelTimer(&Timer->KeTimer);
KeRemoveQueueDpc(&Timer->TimerDpc);
KeRemoveQueueApc(&Timer->TimerApc);
Timer->ApcAssociated = FALSE;
KillTimer = TRUE;
} else {
/* If timer was disabled, we still need to cancel it */
DPRINT("No APCs. Simply cancelling\n");
KeCancelTimer(&Timer->KeTimer);
}
State = KeReadStateTimer(&Timer->Timer);
/* Read the State */
State = KeReadStateTimer(&Timer->KeTimer);
if (Timer->Running == TRUE)
{
/* cancel running timer */
const KIRQL OldIrql = KeRaiseIrqlToDpcLevel();
KeCancelTimer(&Timer->Timer);
KeRemoveQueueDpc(&Timer->Dpc);
KeRemoveQueueApc(&Timer->Apc);
Timer->Running = FALSE;
KeLowerIrql(OldIrql);
/* Handle Wake Timers */
DPRINT("Doing Wake Semantics\n");
KeAcquireSpinLockAtDpcLevel(&ExpWakeListLock);
if (WakeTimer) {
/* Insert it into the list */
InsertTailList(&ExpWakeList, &Timer->WakeTimerListEntry);
} else {
/* Remove it from the list */
RemoveEntryList(&Timer->WakeTimerListEntry);
Timer->WakeTimerListEntry.Flink = NULL;
}
KeReleaseSpinLockFromDpcLevel(&ExpWakeListLock);
if (TimerApcRoutine)
{
KeInitializeApc(&Timer->Apc,
KeGetCurrentThread(),
OriginalApcEnvironment,
/* Set up the APC Routine if specified */
if (TimerApcRoutine) {
/* Initialize the APC */
DPRINT("Initializing APC: %x\n", Timer->TimerApc);
KeInitializeApc(&Timer->TimerApc,
&CurrentThread->Tcb,
CurrentApcEnvironment,
&ExpTimerApcKernelRoutine,
(PKRUNDOWN_ROUTINE)NULL,
(PKNORMAL_ROUTINE)TimerApcRoutine,
PreviousMode,
TimerContext);
/* Lock the Thread's Active List and Insert */
KeAcquireSpinLockAtDpcLevel(&CurrentThread->ActiveTimerListLock);
InsertTailList(&CurrentThread->ActiveTimerListHead,
&Timer->ActiveTimerListEntry);
KeReleaseSpinLockFromDpcLevel(&CurrentThread->ActiveTimerListLock);
}
Result = KeSetTimerEx(&Timer->Timer,
/* Enable and Set the Timer */
DPRINT("Setting Kernel Timer\n");
KeSetTimerEx(&Timer->KeTimer,
TimerDueTime,
Period,
TimerApcRoutine ? &Timer->Dpc : 0 );
if (Result)
{
ObDereferenceObject(Timer);
DPRINT1( "KeSetTimer says the timer was already running, this shouldn't be\n" );
return STATUS_UNSUCCESSFUL;
}
TimerApcRoutine ? &Timer->TimerDpc : 0);
Timer->ApcAssociated = TimerApcRoutine ? TRUE : FALSE;
Timer->Running = TRUE;
/* Unlock the Timer */
KeReleaseSpinLock(&Timer->Lock, OldIrql);
/* Dereference the Object */
ObDereferenceObject(Timer);
if (PreviousState != NULL)
{
_SEH_TRY
{
/* Unlock the Timer */
KeReleaseSpinLock(&Timer->Lock, OldIrql);
/* Dereference if it was previously enabled */
if (!TimerApcRoutine) ObDereferenceObject(Timer);
if (KillTimer) ObDereferenceObject(Timer);
DPRINT("Finished Setting the Timer\n");
/* Make sure it's safe to write to the handle */
if(PreviousState != NULL) {
_SEH_TRY {
*PreviousState = State;
}
_SEH_HANDLE
{
} _SEH_HANDLE {
Status = _SEH_GetExceptionCode();
} _SEH_END;
}
_SEH_END;
}
/* Return to Caller */
return Status;
}

View file

@ -142,6 +142,8 @@ VOID STDCALL KiDeleteProfile(PVOID ObjectBody);
VOID STDCALL KeUpdateSystemTime(PKTRAP_FRAME TrapFrame, KIRQL Irql);
VOID STDCALL KeUpdateRunTime(PKTRAP_FRAME TrapFrame, KIRQL Irql);
VOID STDCALL KiExpireTimers(PKDPC Dpc, PVOID DeferredContext, PVOID SystemArgument1, PVOID SystemArgument2);
KIRQL KeAcquireDispatcherDatabaseLock(VOID);
VOID KeAcquireDispatcherDatabaseLockAtDpcLevel(VOID);
VOID KeReleaseDispatcherDatabaseLock(KIRQL Irql);
@ -193,7 +195,7 @@ VOID KeInitTimer(VOID);
VOID KeInitDpc(struct _KPCR* Pcr);
VOID KeInitDispatcher(VOID);
VOID KeInitializeDispatcher(VOID);
VOID KeInitializeTimerImpl(VOID);
VOID KiInitializeSystemClock(VOID);
VOID KeInitializeBugCheck(VOID);
VOID Phase1Initialization(PVOID Context);

410
reactos/ntoskrnl/ke/clock.c Normal file
View file

@ -0,0 +1,410 @@
/*
* COPYRIGHT: See COPYING in the top level directory
* PROJECT: ReactOS kernel
* FILE: ntoskrnl/ke/clock.c
* PURPOSE: Handle System Clock
*
* PROGRAMMERS: Alex Ionescu (alex@relsoft.net) - Created
* David Welch & Phillip Susi - Implementation (?)
*/
/* NOTES ******************************************************************/
/*
* System time units are 100-nanosecond intervals
*/
/* INCLUDES ***************************************************************/
#include <ntoskrnl.h>
#define NDEBUG
#include <internal/debug.h>
/* GLOBALS ****************************************************************/
/*
* Current time
*/
#if defined(__GNUC__)
LARGE_INTEGER SystemBootTime = (LARGE_INTEGER)0LL;
#else
LARGE_INTEGER SystemBootTime = { 0 };
#endif
CHAR KiTimerSystemAuditing = 0;
static KDPC KiExpireTimerDpc;
static BOOLEAN KiClockSetupComplete = FALSE;
/*
* Number of timer interrupts since initialisation
*/
volatile ULONGLONG KeTickCount = 0;
volatile ULONG KiRawTicks = 0;
extern LIST_ENTRY KiTimerListHead;
/*
* The increment in the system clock every timer tick (in system time units)
*
* = (1/18.2)*10^9
*
* RJJ was 54945055
*/
#define CLOCK_INCREMENT (100000)
#ifdef __GNUC__
ULONG EXPORTED KeMaximumIncrement = 100000;
ULONG EXPORTED KeMinimumIncrement = 100000;
#else
/* Microsoft-style declarations */
EXPORTED ULONG KeMaximumIncrement = 100000;
EXPORTED ULONG KeMinimumIncrement = 100000;
#endif
#define MICROSECONDS_PER_TICK (10000)
#define TICKS_TO_CALIBRATE (1)
#define CALIBRATE_PERIOD (MICROSECONDS_PER_TICK * TICKS_TO_CALIBRATE)
/* FUNCTIONS **************************************************************/
/*
* FUNCTION: Initializes timer irq handling
* NOTE: This is only called once from main()
*/
VOID
INIT_FUNCTION
KiInitializeSystemClock(VOID)
{
TIME_FIELDS TimeFields;
DPRINT1("KiInitializeSystemClock()\n");
InitializeListHead(&KiTimerListHead);
KeInitializeDpc(&KiExpireTimerDpc, (PKDEFERRED_ROUTINE)KiExpireTimers, 0);
/* Calculate the starting time for the system clock */
HalQueryRealTimeClock(&TimeFields);
RtlTimeFieldsToTime(&TimeFields, &SystemBootTime);
/* Set up the Used Shared Data */
SharedUserData->TickCountLowDeprecated = 0;
SharedUserData->TickCountMultiplier = 167783691; // 2^24 * 1193182 / 119310
SharedUserData->InterruptTime.High2Time = 0;
SharedUserData->InterruptTime.LowPart = 0;
SharedUserData->InterruptTime.High1Time = 0;
SharedUserData->SystemTime.High2Time = SystemBootTime.u.HighPart;
SharedUserData->SystemTime.LowPart = SystemBootTime.u.LowPart;
SharedUserData->SystemTime.High1Time = SystemBootTime.u.HighPart;
KiClockSetupComplete = TRUE;
DPRINT1("Finished KiInitializeSystemClock()\n");
}
VOID
KiSetSystemTime(PLARGE_INTEGER NewSystemTime)
{
LARGE_INTEGER OldSystemTime;
LARGE_INTEGER DeltaTime;
KIRQL OldIrql;
ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);
OldIrql = KeAcquireDispatcherDatabaseLock();
do
{
OldSystemTime.u.HighPart = SharedUserData->SystemTime.High1Time;
OldSystemTime.u.LowPart = SharedUserData->SystemTime.LowPart;
}
while (OldSystemTime.u.HighPart != SharedUserData->SystemTime.High2Time);
/* Set the new system time */
SharedUserData->SystemTime.LowPart = NewSystemTime->u.LowPart;
SharedUserData->SystemTime.High1Time = NewSystemTime->u.HighPart;
SharedUserData->SystemTime.High2Time = NewSystemTime->u.HighPart;
/* Calculate the difference between the new and the old time */
DeltaTime.QuadPart = NewSystemTime->QuadPart - OldSystemTime.QuadPart;
/* Update system boot time */
SystemBootTime.QuadPart += DeltaTime.QuadPart;
/* Update absolute timers */
DPRINT1("FIXME: TIMER UPDATE NOT DONE!!!\n");
KeReleaseDispatcherDatabaseLock(OldIrql);
/*
* NOTE: Expired timers will be processed at the next clock tick!
*/
}
/*
* @implemented
*/
ULONG
STDCALL
KeQueryTimeIncrement(VOID)
/*
* FUNCTION: Gets the increment (in 100-nanosecond units) that is added to
* the system clock every time the clock interrupts
* RETURNS: The increment
*/
{
return KeMaximumIncrement;
}
/*
* @implemented
*/
VOID
STDCALL
KeQueryTickCount(PLARGE_INTEGER TickCount)
/*
* FUNCTION: Returns the number of ticks since the system was booted
* ARGUMENTS:
* TickCount (OUT) = Points to storage for the number of ticks
*/
{
TickCount->QuadPart = KeTickCount;
}
/*
* FUNCTION: Gets the current system time
* ARGUMENTS:
* CurrentTime (OUT) = The routine stores the current time here
* NOTE: The time is the number of 100-nanosecond intervals since the
* 1st of January, 1601.
*
* @implemented
*/
VOID
STDCALL
KeQuerySystemTime(PLARGE_INTEGER CurrentTime)
{
do {
CurrentTime->u.HighPart = SharedUserData->SystemTime.High1Time;
CurrentTime->u.LowPart = SharedUserData->SystemTime.LowPart;
} while (CurrentTime->u.HighPart != SharedUserData->SystemTime.High2Time);
}
ULONGLONG
STDCALL
KeQueryInterruptTime(VOID)
{
LARGE_INTEGER CurrentTime;
do {
CurrentTime.u.HighPart = SharedUserData->InterruptTime.High1Time;
CurrentTime.u.LowPart = SharedUserData->InterruptTime.LowPart;
} while (CurrentTime.u.HighPart != SharedUserData->InterruptTime.High2Time);
return CurrentTime.QuadPart;
}
/*
* @implemented
*/
VOID
STDCALL
KeSetTimeIncrement(
IN ULONG MaxIncrement,
IN ULONG MinIncrement)
{
/* Set some Internal Variables */
/* FIXME: We use a harcoded CLOCK_INCREMENT. That *must* be changed */
KeMaximumIncrement = MaxIncrement;
KeMinimumIncrement = MinIncrement;
}
/*
* @unimplemented
*/
VOID
FASTCALL
KeSetTimeUpdateNotifyRoutine(
IN PTIME_UPDATE_NOTIFY_ROUTINE NotifyRoutine
)
{
UNIMPLEMENTED;
}
/*
* NOTE: On Windows this function takes exactly one parameter and EBP is
* guaranteed to point to KTRAP_FRAME. The function is used only
* by HAL, so there's no point in keeping that prototype.
*
* @implemented
*/
VOID
STDCALL
KeUpdateRunTime(
IN PKTRAP_FRAME TrapFrame,
IN KIRQL Irql
)
{
PKPCR Pcr;
PKTHREAD CurrentThread;
PKPROCESS CurrentProcess;
#if 0
ULONG DpcLastCount;
#endif
Pcr = KeGetCurrentKPCR();
/* Make sure we don't go further if we're in early boot phase. */
if (Pcr == NULL || Pcr->PrcbData.CurrentThread == NULL)
return;
DPRINT("KernelTime %u, UserTime %u \n", Pcr->PrcbData.KernelTime, Pcr->PrcbData.UserTime);
CurrentThread = Pcr->PrcbData.CurrentThread;
CurrentProcess = CurrentThread->ApcState.Process;
/*
* Cs bit 0 is always set for user mode if we are in protected mode.
* V86 mode is counted as user time.
*/
if (TrapFrame->Cs & 0x1 ||
TrapFrame->Eflags & X86_EFLAGS_VM)
{
InterlockedIncrementUL(&CurrentThread->UserTime);
InterlockedIncrementUL(&CurrentProcess->UserTime);
Pcr->PrcbData.UserTime++;
}
else
{
if (Irql > DISPATCH_LEVEL)
{
Pcr->PrcbData.InterruptTime++;
}
else if (Irql == DISPATCH_LEVEL)
{
Pcr->PrcbData.DpcTime++;
}
else
{
InterlockedIncrementUL(&CurrentThread->KernelTime);
InterlockedIncrementUL(&CurrentProcess->KernelTime);
Pcr->PrcbData.KernelTime++;
}
}
#if 0
DpcLastCount = Pcr->PrcbData.DpcLastCount;
Pcr->PrcbData.DpcLastCount = Pcr->PrcbData.DpcCount;
Pcr->PrcbData.DpcRequestRate = ((Pcr->PrcbData.DpcCount - DpcLastCount) +
Pcr->PrcbData.DpcRequestRate) / 2;
#endif
if (Pcr->PrcbData.DpcData[0].DpcQueueDepth > 0 &&
Pcr->PrcbData.DpcRoutineActive == FALSE &&
Pcr->PrcbData.DpcInterruptRequested == FALSE)
{
HalRequestSoftwareInterrupt(DISPATCH_LEVEL);
}
/* FIXME: Do DPC rate adjustments */
/*
* If we're at end of quantum request software interrupt. The rest
* is handled in KiDispatchInterrupt.
*/
if ((CurrentThread->Quantum -= 3) <= 0)
{
Pcr->PrcbData.QuantumEnd = TRUE;
HalRequestSoftwareInterrupt(DISPATCH_LEVEL);
}
}
/*
* NOTE: On Windows this function takes exactly zero parameters and EBP is
* guaranteed to point to KTRAP_FRAME. Also [esp+0] contains an IRQL.
* The function is used only by HAL, so there's no point in keeping
* that prototype.
*
* @implemented
*/
VOID
STDCALL
KeUpdateSystemTime(
IN PKTRAP_FRAME TrapFrame,
IN KIRQL Irql
)
/*
* FUNCTION: Handles a timer interrupt
*/
{
LARGE_INTEGER Time;
ASSERT(KeGetCurrentIrql() == PROFILE_LEVEL);
KiRawTicks++;
if (KiClockSetupComplete == FALSE) return;
/*
* Increment the number of timers ticks
*/
KeTickCount++;
SharedUserData->TickCountLowDeprecated++;
Time.u.LowPart = SharedUserData->InterruptTime.LowPart;
Time.u.HighPart = SharedUserData->InterruptTime.High1Time;
Time.QuadPart += CLOCK_INCREMENT;
SharedUserData->InterruptTime.High2Time = Time.u.HighPart;
SharedUserData->InterruptTime.LowPart = Time.u.LowPart;
SharedUserData->InterruptTime.High1Time = Time.u.HighPart;
Time.u.LowPart = SharedUserData->SystemTime.LowPart;
Time.u.HighPart = SharedUserData->SystemTime.High1Time;
Time.QuadPart += CLOCK_INCREMENT;
SharedUserData->SystemTime.High2Time = Time.u.HighPart;
SharedUserData->SystemTime.LowPart = Time.u.LowPart;
SharedUserData->SystemTime.High1Time = Time.u.HighPart;
/* FIXME: Here we should check for remote debugger break-ins */
/* Update process and thread times */
KeUpdateRunTime(TrapFrame, Irql);
/*
* Queue a DPC that will expire timers
*/
KeInsertQueueDpc(&KiExpireTimerDpc, (PVOID)TrapFrame->Eip, 0);
}
/*
* @implemented
*/
ULONG
STDCALL
NtGetTickCount(VOID)
{
LARGE_INTEGER TickCount;
KeQueryTickCount(&TickCount);
return TickCount.u.LowPart;
}
NTSTATUS
STDCALL
NtQueryTimerResolution(OUT PULONG MinimumResolution,
OUT PULONG MaximumResolution,
OUT PULONG ActualResolution)
{
UNIMPLEMENTED;
return STATUS_NOT_IMPLEMENTED;
}
NTSTATUS
STDCALL
NtSetTimerResolution(IN ULONG DesiredResolution,
IN BOOLEAN SetResolution,
OUT PULONG CurrentResolution)
{
UNIMPLEMENTED;
return STATUS_NOT_IMPLEMENTED;
}
/* EOF */

View file

@ -341,7 +341,7 @@ KeInit2(VOID)
KeInitializeBugCheck();
KeInitializeDispatcher();
KeInitializeTimerImpl();
KiInitializeSystemClock();
if (Pcr->PrcbData.FeatureBits & X86_FEATURE_PAE)
{

View file

@ -72,6 +72,23 @@ KeQueryPriorityThread (
return Thread->Priority;
}
/*
* @implemented
*/
ULONG
STDCALL
KeQueryRuntimeThread(
IN PKTHREAD Thread,
OUT PULONG UserTime
)
{
/* Return the User Time */
*UserTime = Thread->UserTime;
/* Return the Kernel Time */
return Thread->KernelTime;
}
NTSTATUS
KeReleaseThread(PKTHREAD Thread)
/*
@ -405,3 +422,45 @@ KeTerminateThread(IN KPRIORITY Increment)
/* Call our own internal routine */
PsTerminateCurrentThread(0);
}
NTSTATUS
STDCALL
NtDelayExecution(IN BOOLEAN Alertable,
IN PLARGE_INTEGER DelayInterval)
{
KPROCESSOR_MODE PreviousMode;
LARGE_INTEGER SafeInterval;
PreviousMode = ExGetPreviousMode();
if(PreviousMode != KernelMode)
{
NTSTATUS Status = STATUS_SUCCESS;
_SEH_TRY
{
ProbeForRead(DelayInterval,
sizeof(LARGE_INTEGER),
sizeof(ULONG));
/* make a copy on the kernel stack and let DelayInterval point to it so
we don't need to wrap KeDelayExecutionThread in SEH! */
SafeInterval = *DelayInterval;
DelayInterval = &SafeInterval;
}
_SEH_HANDLE
{
Status = _SEH_GetExceptionCode();
}
_SEH_END;
if(!NT_SUCCESS(Status))
{
return Status;
}
}
return KeDelayExecutionThread(PreviousMode,
Alertable,
DelayInterval);
}

View file

@ -284,3 +284,59 @@ KeSetProfileIrql(
{
UNIMPLEMENTED;
}
NTSTATUS STDCALL
NtQueryPerformanceCounter(OUT PLARGE_INTEGER PerformanceCounter,
OUT PLARGE_INTEGER PerformanceFrequency OPTIONAL)
{
LARGE_INTEGER PerfCounter;
LARGE_INTEGER PerfFrequency;
KPROCESSOR_MODE PreviousMode;
NTSTATUS Status = STATUS_SUCCESS;
PreviousMode = ExGetPreviousMode();
if(PreviousMode != KernelMode)
{
_SEH_TRY
{
ProbeForWrite(PerformanceCounter,
sizeof(LARGE_INTEGER),
sizeof(ULONG));
if(PerformanceFrequency != NULL)
{
ProbeForWrite(PerformanceFrequency,
sizeof(LARGE_INTEGER),
sizeof(ULONG));
}
}
_SEH_HANDLE
{
Status = _SEH_GetExceptionCode();
}
_SEH_END;
if(!NT_SUCCESS(Status))
{
return Status;
}
}
PerfCounter = KeQueryPerformanceCounter(&PerfFrequency);
_SEH_TRY
{
*PerformanceCounter = PerfCounter;
if(PerformanceFrequency != NULL)
{
*PerformanceFrequency = PerfFrequency;
}
}
_SEH_HANDLE
{
Status = _SEH_GetExceptionCode();
}
_SEH_END;
return Status;
}

File diff suppressed because it is too large Load diff

View file

@ -397,6 +397,32 @@ BOOLEAN KiDispatcherObjectWake(DISPATCHER_HEADER* hdr, KPRIORITY increment)
return(FALSE);
}
/*
* @implemented
*/
NTSTATUS STDCALL
KeDelayExecutionThread (KPROCESSOR_MODE WaitMode,
BOOLEAN Alertable,
PLARGE_INTEGER Interval)
/*
* FUNCTION: Puts the current thread into an alertable or nonalertable
* wait state for a given internal
* ARGUMENTS:
* WaitMode = Processor mode in which the caller is waiting
* Altertable = Specifies if the wait is alertable
* Interval = Specifies the interval to wait
* RETURNS: Status
*/
{
PKTHREAD Thread = KeGetCurrentThread();
KeSetTimer(&Thread->Timer, *Interval, NULL);
return (KeWaitForSingleObject(&Thread->Timer,
(WaitMode == KernelMode) ? Executive : UserRequest, /* TMN: Was unconditionally Executive */
WaitMode, /* TMN: Was UserMode */
Alertable,
NULL));
}
/*
* @implemented
@ -585,13 +611,13 @@ KeWaitForMultipleObjects(ULONG Count,
{
Abandoned = KiSideEffectsBeforeWake(hdr, CurrentThread) ? TRUE : Abandoned;
KeReleaseDispatcherDatabaseLock(OldIrql);
if (Timeout != NULL && Timeout->QuadPart != 0)
{
KeCancelTimer(&CurrentThread->Timer);
}
KeReleaseDispatcherDatabaseLock(OldIrql);
DPRINT("One object is (already) signaled!\n");
if (Abandoned == TRUE)
{
@ -612,12 +638,13 @@ KeWaitForMultipleObjects(ULONG Count,
Abandoned = KiSideEffectsBeforeWake(hdr, CurrentThread) ? TRUE : Abandoned;
}
KeReleaseDispatcherDatabaseLock(OldIrql);
if (Timeout != NULL && Timeout->QuadPart != 0)
{
KeCancelTimer(&CurrentThread->Timer);
}
KeReleaseDispatcherDatabaseLock(OldIrql);
DPRINT("All objects are (already) signaled!\n");
if (Abandoned == TRUE)
@ -641,8 +668,8 @@ KeWaitForMultipleObjects(ULONG Count,
if (Timeout != NULL && KiIsObjectSignalled(&CurrentThread->Timer.Header, CurrentThread))
{
KiSideEffectsBeforeWake(&CurrentThread->Timer.Header, CurrentThread);
KeCancelTimer(&CurrentThread->Timer);
KeReleaseDispatcherDatabaseLock(OldIrql);
KeCancelTimer(&CurrentThread->Timer);
return (STATUS_TIMEOUT);
}