323 lines
8.6 KiB
C
323 lines
8.6 KiB
C
|
/*++
|
||
|
|
||
|
Copyright (c) 1989 Microsoft Corporation
|
||
|
|
||
|
Module Name:
|
||
|
|
||
|
timer.c
|
||
|
|
||
|
Abstract:
|
||
|
|
||
|
This module implements POSIX timer related services.
|
||
|
|
||
|
Author:
|
||
|
|
||
|
Mark Lucovsky (markl) 08-Aug-1989
|
||
|
|
||
|
Revision History:
|
||
|
|
||
|
--*/
|
||
|
|
||
|
#include "psxsrv.h"
|
||
|
|
||
|
typedef struct _ALARM_WORK_ITEM {
|
||
|
LIST_ENTRY Links;
|
||
|
PPSX_PROCESS Process;
|
||
|
LARGE_INTEGER Time;
|
||
|
} ALARM_WORK_ITEM, *PALARM_WORK_ITEM;
|
||
|
|
||
|
static LIST_ENTRY AlarmWorkList;
|
||
|
static RTL_CRITICAL_SECTION AlarmWorkListMutex;
|
||
|
HANDLE AlarmThreadHandle;
|
||
|
|
||
|
HANDLE AlarmInitEvent;
|
||
|
|
||
|
|
||
|
VOID
|
||
|
AlarmApcRoutine(
|
||
|
IN PVOID TimerContext,
|
||
|
IN ULONG TimerLowValue,
|
||
|
IN LONG TimerHighValue
|
||
|
)
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This function is called when a process alarm timer expires. Its purpose
|
||
|
is to send a SIGALRM signal to the appropriate process.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
TimerContext - Specifies the process that is to be signaled.
|
||
|
|
||
|
TimerLowValue - Ignored.
|
||
|
|
||
|
TimerHighValue - Ignored.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
None.
|
||
|
|
||
|
--*/
|
||
|
|
||
|
{
|
||
|
PPSX_PROCESS p;
|
||
|
|
||
|
p = (PPSX_PROCESS)TimerContext;
|
||
|
|
||
|
//
|
||
|
// Get process table lock to see if process still has an alarm timer. If
|
||
|
// it does, then signal the process. Otherwise, drop alarm on the floor.
|
||
|
//
|
||
|
|
||
|
AcquireProcessStructureLock();
|
||
|
|
||
|
if (p->AlarmTimer) {
|
||
|
PsxSignalProcess(p, SIGALRM);
|
||
|
}
|
||
|
|
||
|
ReleaseProcessStructureLock();
|
||
|
|
||
|
}
|
||
|
|
||
|
BOOLEAN
|
||
|
PsxAlarm(
|
||
|
IN PPSX_PROCESS p,
|
||
|
IN PPSX_API_MSG m
|
||
|
)
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This function implements the alarm() API. The problem we're having
|
||
|
here is that in order for the APC to be processed, the thread (who
|
||
|
calls NtSetTimer) must be waiting in an alertable state. This is not
|
||
|
the case for the Api Request Threads.
|
||
|
|
||
|
So we have a thread dedicated to processing alarm requests. When he's
|
||
|
not doing that, he waits on his own thread handle. The Api Request
|
||
|
Thread puts a work request on a queue and wakes the alarm thread. He
|
||
|
processes the work request and then waits again.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
p - Supplies the address of the calling process
|
||
|
|
||
|
m - Supplies the address of the related message
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
TRUE - Always succeeds and generates a reply
|
||
|
|
||
|
--*/
|
||
|
|
||
|
{
|
||
|
PPSX_ALARM_MSG args;
|
||
|
TIMER_BASIC_INFORMATION TimerInfo;
|
||
|
NTSTATUS st;
|
||
|
PALARM_WORK_ITEM pItem;
|
||
|
HANDLE Timer;
|
||
|
|
||
|
args = &m->u.Alarm;
|
||
|
|
||
|
args->PreviousSeconds.LowPart = 0;
|
||
|
args->PreviousSeconds.HighPart = 0;
|
||
|
|
||
|
if (args->CancelAlarm) {
|
||
|
|
||
|
// Cancel the timer.
|
||
|
|
||
|
AcquireProcessStructureLock();
|
||
|
|
||
|
Timer = p->AlarmTimer;
|
||
|
p->AlarmTimer = NULL;
|
||
|
|
||
|
//
|
||
|
// After this point no alarms will be delivered to the process.
|
||
|
//
|
||
|
|
||
|
ReleaseProcessStructureLock();
|
||
|
|
||
|
if (NULL != Timer) {
|
||
|
|
||
|
//
|
||
|
// Query timer to determine signaled state and time
|
||
|
// remaining. If timer is already signaled, then
|
||
|
// return 0; otherwise, return the reported remaining
|
||
|
// time
|
||
|
//
|
||
|
|
||
|
st = NtQueryTimer(Timer, TimerBasicInformation,
|
||
|
&TimerInfo, sizeof(TIMER_BASIC_INFORMATION),
|
||
|
NULL);
|
||
|
if (!NT_SUCCESS(st)) {
|
||
|
KdPrint(("PSXSS: QueryTimer: 0x%x\n", st));
|
||
|
m->Error = ENOMEM;
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
if (FALSE == TimerInfo.TimerState) {
|
||
|
|
||
|
//
|
||
|
// Timer is still active
|
||
|
//
|
||
|
|
||
|
args->PreviousSeconds.LowPart =
|
||
|
TimerInfo.RemainingTime.LowPart;
|
||
|
args->PreviousSeconds.HighPart =
|
||
|
TimerInfo.RemainingTime.HighPart;
|
||
|
|
||
|
st = NtCancelTimer(Timer,
|
||
|
&TimerInfo.TimerState);
|
||
|
ASSERT(NT_SUCCESS(st));
|
||
|
|
||
|
ASSERT(FALSE == TimerInfo.TimerState);
|
||
|
}
|
||
|
st = NtClose(Timer);
|
||
|
ASSERT(NT_SUCCESS(st));
|
||
|
|
||
|
} else {
|
||
|
//
|
||
|
// The timer was already NULL, so we were cancelling
|
||
|
// a timer that had not been set.
|
||
|
//
|
||
|
}
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Set a timer.
|
||
|
//
|
||
|
|
||
|
if (p->AlarmTimer) {
|
||
|
|
||
|
//
|
||
|
// Query timer to determine signaled state and time remaining
|
||
|
// If timer is already signaled, then return 0; otherwise,
|
||
|
// return the reported remaining time.
|
||
|
//
|
||
|
|
||
|
st = NtQueryTimer(p->AlarmTimer, TimerBasicInformation,
|
||
|
&TimerInfo, sizeof(TIMER_BASIC_INFORMATION), NULL);
|
||
|
|
||
|
ASSERT(NT_SUCCESS(st));
|
||
|
|
||
|
if (TimerInfo.TimerState == FALSE) {
|
||
|
|
||
|
//
|
||
|
// Timer is still active
|
||
|
//
|
||
|
|
||
|
args->PreviousSeconds.LowPart =
|
||
|
TimerInfo.RemainingTime.LowPart;
|
||
|
args->PreviousSeconds.HighPart =
|
||
|
TimerInfo.RemainingTime.HighPart;
|
||
|
|
||
|
st = NtCancelTimer(p->AlarmTimer,
|
||
|
&TimerInfo.TimerState);
|
||
|
ASSERT(NT_SUCCESS(st));
|
||
|
}
|
||
|
} else {
|
||
|
|
||
|
//
|
||
|
// Process does not have a timer, so create one for it.
|
||
|
// The timer will not be deallocated until the process exits.
|
||
|
//
|
||
|
|
||
|
st = NtCreateTimer(&p->AlarmTimer,
|
||
|
TIMER_ALL_ACCESS,
|
||
|
NULL,
|
||
|
NotificationTimer);
|
||
|
|
||
|
if (!NT_SUCCESS(st)) {
|
||
|
m->Error = ENOMEM;
|
||
|
return TRUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Arrange for the alarm thread to set the timer.
|
||
|
//
|
||
|
|
||
|
st = NtResetEvent(AlarmInitEvent, NULL);
|
||
|
ASSERT(NT_SUCCESS(st));
|
||
|
|
||
|
pItem = (PVOID)RtlAllocateHeap(PsxHeap, 0, sizeof(*pItem));
|
||
|
if (NULL == pItem) {
|
||
|
m->Error = ENOMEM;
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
pItem->Process = p;
|
||
|
pItem->Time = args->Seconds;
|
||
|
|
||
|
RtlEnterCriticalSection(&AlarmWorkListMutex);
|
||
|
|
||
|
InsertTailList(&AlarmWorkList, &pItem->Links);
|
||
|
|
||
|
RtlLeaveCriticalSection(&AlarmWorkListMutex);
|
||
|
|
||
|
//
|
||
|
// Wake up the Alarm Thread to process the work item.
|
||
|
//
|
||
|
|
||
|
st = NtAlertThread(AlarmThreadHandle);
|
||
|
ASSERT(NT_SUCCESS(st));
|
||
|
|
||
|
//
|
||
|
// Block until the work item has been processed. If we don't do
|
||
|
// this, there can be cases where an alarm is queried before the
|
||
|
// alarm thread has actually initialized the timer.
|
||
|
//
|
||
|
|
||
|
st = NtWaitForSingleObject(AlarmInitEvent, FALSE, NULL);
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
VOID
|
||
|
AlarmThreadRoutine(VOID)
|
||
|
{
|
||
|
NTSTATUS Status;
|
||
|
PALARM_WORK_ITEM pItem;
|
||
|
|
||
|
RtlInitializeCriticalSection(&AlarmWorkListMutex);
|
||
|
InitializeListHead(&AlarmWorkList);
|
||
|
|
||
|
Status = NtCreateEvent(&AlarmInitEvent, EVENT_ALL_ACCESS,
|
||
|
NULL, NotificationEvent, TRUE);
|
||
|
ASSERT(NT_SUCCESS(Status));
|
||
|
|
||
|
for (;;) {
|
||
|
(void)NtWaitForSingleObject(NtCurrentThread(), TRUE, NULL);
|
||
|
|
||
|
RtlEnterCriticalSection(&AlarmWorkListMutex);
|
||
|
|
||
|
while (!IsListEmpty(&AlarmWorkList)) {
|
||
|
|
||
|
pItem = (PVOID)RemoveHeadList(&AlarmWorkList);
|
||
|
|
||
|
Status = NtSetTimer(pItem->Process->AlarmTimer,
|
||
|
&pItem->Time,
|
||
|
AlarmApcRoutine,
|
||
|
pItem->Process,
|
||
|
FALSE,
|
||
|
0,
|
||
|
NULL);
|
||
|
|
||
|
if (!NT_SUCCESS(Status)) {
|
||
|
KdPrint(("PSXSS: AlarmThread: NtSetTime: 0x%x\n", Status));
|
||
|
}
|
||
|
|
||
|
RtlFreeHeap(PsxHeap, 0, pItem);
|
||
|
}
|
||
|
|
||
|
RtlLeaveCriticalSection(&AlarmWorkListMutex);
|
||
|
|
||
|
Status = NtSetEvent(AlarmInitEvent, NULL);
|
||
|
ASSERT(NT_SUCCESS(Status));
|
||
|
}
|
||
|
}
|