REAL-TIME TASK MODEL and the SRMS API
Task threads can theoretically be any thread of execution that uses the SRMS API to communicate with the SRMS scheduler. In order to keep consistency between this implementation and the theory behind it, the design of the task model closely follows the model used in Atlas' KURT Linux implementation. Like that one, this implementation in Windows NT supports tasks that know the execution requirement of their jobs and unknowledgeable tasks that do not. In this project, the real-time tasks used for testing are single-threaded user processes programmed using the Win32 API. The major assumption made here is that all real-time task threads maintain their own local message queue separate from the application's predefined message queue. Most Windows applications have a main thread that monitors the message queue and dispatches messages to an appropriate handler. Instead, this implementation assumes that another sibling thread in the tasks process space is handling that job. Therefore, when receiving messages, all SRMS API functions refer to the tasks local thread message queue for simpler and faster communication.
The SRMS API is an application programming interface that hides the internals of the
SRMS service from the real-time programmer. Ideally, the SRMS API would seem even
more transparent in a shared dynamic link library, but for now, the
taskapi.h files must be included in the compilation of the task
executable. The following paragraphs details how a task should use the functions in the
SRMS API and also what goes on inside them the API. For a more information about the
real-time task executable file and related compilation settings, refer to the section on File Descriptions and Compilation Settings.
The first step to becoming a real-time thread is to register with the SRMS service.
To do this, the task needs to pass to the service its real-time parameters.
initialize_rtparams() which is an SRMS API call that gets a lock on
a special shared memory buffer for passing real-time parameters. This function returns a
pointer to that special memory mapped region. The task then writes all of its real-time
information to the buffer (real-time information in the
rtparams struct is
completely defined in
rt_task.h). Once finished, the task calls
and passes to the function a pointer to the shared memory buffer.
sends the SRMS message handler a
WM_RegistrationMsg and then waits for a
reply. In the mean time, the SRMS service copies the information from the buffer and
registers the task. When done, a reply is sent back to the task notifying it that
registration is successful
Next, the task must attempt QoS admission via one of two methods: either the
request_QoS_admission() or the blocking call
The non-blocking call returns regardless of whether or not its desired QoS value is met,
while the blocking call does not return until its QoS is satisfied. Internally, both
functions pass a message to the SRMS message handler indicating that the task wants to
attempt QoS admission. Since the service already has all of the task's real time
information in memory, it begins QoS negotiation. When done, it posts a
message back to the task thread's message queue along with the task's QoS value as the
argument of the message. If the task used the non-blocking call and this reply is
then that means that the SRMS service could not guarantee its QoS and the
function returns false. The only other time that either function would return false is if
the service sent it a
WM_UnregisterRTConfirmedMsg message, which is simply a
notice to the task that it has been unregistered, probably due the SRMS service exiting.
Once the task completes and passes QoS negotiation, it must establish mechanisms for
communicating with the SRMS scheduler. The
function prepares communication by creating the four task-specific event object handles
(as described in the Communication and Synchronization
Mechanisms section), a small shared memory buffer for transferring job execution
times, and a universally defined admit/reject message and job failed message. Next in the
setup process, the task must await another message from the SRMS service which is a
message that has the tick count timestamp of when the system started as the
value of the message. This is done for the task thread by the
API call. The task only uses this start value for debugging purposes so that it can
tag events with a timestamp relative to the SRMS service's time.
Finally, the task is ready to start executing jobs, so it enters its periodic loop.
First it waits for a new job to be released by calling the special API function
This function waits for the SRMS scheduler to signal the
Once this occurs, the task wakes from its waiting state and checks to see if there are any
SRMS-posted messages in its message queue. Typical messages that it may find are one
indicating that it must cleanup the last job or one telling it that it has been
unexpectedly unregistered. If the former is detected, then that message is removed and
ignored since the task is already at the top of its periodic loop. However, an unregister
message cannot be avoided and the function returns a negative value. If neither of these
messages is found, then the task flushes its message queue to get rid of any unwanted
messages. Otherwise, if no message is posted, the function returns the task's current job
Now the task needs to communicate its next job's execution requirement. It gets its
budget using the
get_RTbudget() API call and estimates its job's execution
time so long as it has enough time to calculate it. Once it has a value, it passes it to
admit_RTjob() API call who then writes the value to the shared memory
buffer, signals the SRMS scheduler, and then waits for a response. It then waits for
either an accept or reject response and returns the response to the task (it may also
receive an unregister message, in which case the function returns the special
flag and the real time task thread ends).
Next, the task calls
await_scheduling() to wait to be scheduled. That
function waits for one of two signals to occur:
If the first is signaled, then the task proceeds normally to perform its periodic work.
But if the second handle is signaled, that means that this job was not scheduled during
the period and that a new job has been released. In this case,
SRMS_JOB_FAILED to indicate to the task that it needs to clean
itself up and reset itself to the beginning of its periodic loop. And again, the function
also checks for any SRMS-posted messages and returns those if necessary.
hSchedEvent is signaled and there are no cleanup messages in
the task's queue, then the task finally has control of the processor. It immediately
calls the function
do_work() with pointer to a task-defined worker function.
The worker function has a prototype:
BOOL my_rt_work( VOID );
As solely a matter of design choice, the function takes no parameters, but it does have
a boolean return value indicating whether there is still more real-time work to complete.
It is suggested that the worker function operate in a tight loop that frequently checks
the message queue for a
WM_RTJobFailedMsg message from the SRMS scheduler. By
catching this message and returning from the worker function as quickly as possible, the
task thread will stay synchronized with the scheduler. When the worker function returns to
do_work(), the message queue is again checked for any cleanup or unregister
messages, and then it returns to the task the value returned by the worker function.
This procedure repeats itself indefinitely until either the SRMS service unregisters
the task or the
do_work() function returns false, meaning that there is no
more real-time work to be done. In that case, the task calls the
API function with its real-time ID. The service does some work on its end to unregister
the task with the SRMS system, and the task thread closes all communication objects that
it opened during setup. Lastly, the task thread returns and the real-time task exits.
Note: A potential problem exists with multi-threaded processes that designate one thread to become a real-time task thread. In order to be able to schedule the task threads, the SRMS scheduler needs to boost the priority level of the real-time task thread to be equal to that of all other threads in the real-time task set. If the task thread's process is not in the same NT priority class as the other threads in the task set, then changing one thread's priority class will also unintentionally change the priority class of all its sibling threads in its process to the new priority class. This poses a serious potential problem, especially since task sets are usually scheduled in the REAL-TIME priority range. If one or more sibling threads of the real-time task are scheduled at a higher thread priority level than the SRMS scheduler's, then the boost to the REAL-TIME class may cause those threads to preempt the SRMS scheduler thread, causing unpredictable results.
BOOL my_rt_work( VOID );
LONG execTime = SRMS_EXEC_UNSURE;
int main(int argc, char *argv)
BOOL haveWork = TRUE; // true if there's work to do, else false
BOOL accepted; // true if QoS admission is successful, false if not
LONG jobnum = 0, // this job's numeric identifier
budget = 0, // time remaining in budget (from srms)
admitted = 0, // holds return value from admit_RTjob()
result = 0; // holds return value from await_scheduling()
DWORD rt_id; // holds this task's NT thread id
DOUBLE myQos; // QoS expected to be delivered by SRMS for this task
STARTUPINFO si; // startup info structure for this task
rt_id = GetCurrentThreadId();
my_rtparams = initialize_rtparams();
// fill in the rt_params structure:
my_rtparams->rt_id = rt_id; // NT assigned thread ID
my_rtparams->rt_pid = GetCurrentProcessId(); // NT assigned process ID
my_rtparams->rt_importance = 0; // scheduler assign default importance
my_rtparams->period = MY_PERIOD; // period in milliseconds
my_rtparams->sample_execs); // sample execution times array
my_rtparams->num_samples = NUM_OF_SAMPLE_EXECS; // number of samples in the array
my_rtparams->desiredQoS = MY_DESIRED_QOS;
my_rtparams->minimumQoS = MY_MINIMUM_QOS;
GetStartupInfo( &si );
// duplicate the this thread's handle with all access privileges to give it to the scheduler
THREAD_ALL_ACCESS, FALSE, 0);
set_rtparams( my_rtparams );
accepted = request_QoS_admission ( rt_id, &myQos );
// check if Qos is sufficient for this task's minimum qos
if ( !accepted || myQos < MY_MINIMUM_QOS )
initialize_SRMS_communication( rt_id );
jobnum = await_new_job_release();
if (jobnum < 0)
// This next section supports tasks which have accurate knowledge of their jobs'
// execution times and of the time it will take to compute those execution times.
// get the budget that was returned by the OS.
budget = get_RTbudget();
admitted = admit_RTjob( myExecTime );
if ( admitted == SRMS_JOB_FAILED )
if (admitted == SRMS_UNREGISTER_TASK )
// Waiting to be scheduled.
// Wait for either a schedule event signal or a clean-up job signal from the OS
result = await_scheduling();
if ( result == SRMS_JOB_FAILED )
if ( result == SRMS_UNREGISTER_TASK )
haveWork = do_work(my_rt_work); // Task is all set to execute
if ( haveWork == SRMS_UNREGISTER_TASK )
// Unregister this task as a real-time task
unregisterRT( rt_id );
BOOL my_rt_work( VOID )
extern UINT WM_RTJobFailedMsg;
start = timeGetTime();
while ( (timeGetTime() < start + myExecTime) && (msg.message != WM_RTJobFailedMsg))
if (PeekMessage ( &msg, NULL, 0, 0, PM_NOREMOVE))
TranslateMessage( &msg );