NT4/private/posix/psxss/timer.c
2020-09-30 17:12:29 +02:00

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));
}
}