TIMING AND EVENT SCHEDULING
Unlike Atlas' KURT Linux
implementation, Windows NT does not support microsecond timer resolution. By default,
Windows uses a lower resolution counter that records the number of milliseconds since
Windows started. This value is stored in a 32-bit word and it wraps after 49.7 days of
continuous running. Win32s function call GetTickCount() returns this
value. To set scheduled events, Win32 also supplies the SetTimer() function
which allows the scheduling of a timer in millisecond intervals.
There exists an alternative to this
low-resolution counter known as a performance monitor counter. With the additional
hardware of this high-resolution performance counter, the value stored in the counter is
expanded to 64-bits wide, thus increasing the precision of the counter. This greater
precision is typically useful when determining performance of a system where many
transactions can occur within a millisecond. The frequency of the counter is determined by
the hardware; on Intel-based CPUs it is about 0.8 microseconds (0.838 on the computer used
in this implementation). The high-performance counters are available for performing
time-sensitive calculations, but there is no support yet for high-resolution timed events.
This basically means that all timing boundaries must be made on millisecond boundaries
leaving programmers with not many alternatives other than the SetTimer()
function.
Therefore, the scope of this project is limited
to millisecond event scheduling. However, additional problems now surface. First of
all, there are substantial timing discrepancies when using the SetTimer()
function. That function uses the same lower-resolution timer that GetTickCount()
uses above. Although SetTimer() boasts the ability to fire on
millisecond boundaries, it is limited by the resolution of the timer. A simple accuracy test
performed by Chih-Hao Tsai at the University of Illinois at Urbana-Champaign showed that
the average error of this timer was about 10 milliseconds. Additional tests that I
performed and my own experience with timers in previous versions of this project proved
exactly the same.
The timing solution implemented in the SRMS scheduling service introduces the use of
Window's multimedia timer. This timer is not part of the Win32 API and instead is
hidden as part of Window's multimedia library (winmm.lib). This timer
uses a counter whose resolution is much more precise, but still not perfect, giving an
average error of 1 millisecond. The multimedia timer's tick value is returned by the
function timeGetTime(). The multimedia library also has an event timer
with the same accuracy as timeGetTime(). The problem with that timer is
that it is not implemented in the NT kernel like the SetTimer() function
which sends WM_TIMER messages to the calling application's message
handler. Instead, the timeSetEvent() function schedules a thread at the
highest possible NT system priority that executes in a tight loop until it reaches the
scheduled time, and when reached, it executes a pre-defined callback function.

When the SRMS scheduler calls SetNextSchedulerEvent(), that function calls
timeSetEvent() to set a timer to fire after a given time elapses.
Behind the scenes, timeSetEvent() creates a single-threaded process executing
at the highest possible NT system priority that efficiently polls the system counter until
the time elapses. When reached, the timer thread executes the callback function SrmsSchedulerThreadProc()
to immediately send a WM_TimerFired message to the SRMS scheduler
thread. After sending it, the timer thread terminates and the SRMS scheduler wakes
from its waiting state because of the arrival of the timer fired message. The
scheduler proceeds to determine the next job to run and repeats this procedure by calling SetNextSchedulerEvent()
to set another interrupt timer.
When the SRMS service first starts, part of its initialization process includes
recording the current system time. When events are logged in the progress window,
their message includes a timestamp relative to this system start value. The current
system time is again recorded when the SRMS service enters its 'started' state, signifying
the start of the SRMS scheduler. All tasks receive this value as their system start
time when the await_system_start() SRMS API function is called. Also,
all start, end, and period times recorded in the execution history log are relative to
this value. For clarification, the service's start time is stored in the DWORD
startTime, and the scheduler's start time is stored in the DWORD systemStart,
both of which are defined in mainapp.c.
[NOTE: One problem not addressed in this implementation that should be
mentioned is the potential errors that may occur with the multimedia timer and the timeGetTime()
function. Its return value wraps around to 0 every 2^32 milliseconds, which is about
49.71 days. This can cause problems in code that directly uses the timeGetTime
return value in computations, particularly where the value is used to control code
execution. To account for this, the code should always use the difference between two timeGetTime
return values in computations.]