QOS ADMISSION
QoS admission is the result of the negotiation process performed by the SRMS service's
QoS negotiator, also known as the QoS admission thread. Once a task is registered, it is
ready to request QoS admission into the system. The task has the option of calling either
a blocking or non-blocking function, and the SRMS API
supports both methods with the functions request_QoS_admission() and await_QoS_admission()
respectively. Depending on the call, these functions will send either a WM_Request_QoS_AdmissionMsg
or WM_Await_Qos_AdmissionMsg along with the task's thread id in a message to
the SRMS service's message queue. Upon receiving that message, the service's message
handler calls the SRMS internal functions process_request_QoS_admission()or process_await_QoS_admission()
respectively to handle the request. These functions use the task's thread id that is sent
with the message to locate the task's entry in the registered task list. After removing
the entry from that list, the function adds it to the end of the request_queue
or the await_queue. Both queues are FIFO queues that contain a doubly-linked
list of task_struct entries. The need for two queues becomes more apparent
with the explanation of the QoS admission thread later
in this section.
Finally, the last job remaining for the process functions is to create the QoS
admission thread if it is not already active. The createQosAdmissionThread()
function handles the small task of creating the QoS admission thread and setting its
priority. The function creates a child thread and directs it to start its work with the QosAdmissionThreadProc()
function. It also assigns the QoS admission thread to run at the same priority as
the message handler thread, which is at a level lower than the SRMS scheduler and
real-time task set. By operating at that level, the thread should not steal any CPU
cycles away from the SRMS scheduler or the task set. This basically means that the
QoS admission thread only gets a control of the processor when the SRMS scheduler is idle
and the tasks have no real-time work to do.
The primary goal of the QoS admission thread is to determine if the system can meet the
requested quality of service of a particular task. The system must process all
non-blocking requests first so that a task waiting for a response from a non-blocking call
is delayed as little as possible. SRMS facilitates the non-blocking request via the request_queue.
Ideally, the QoS admission thread could be processed by a second processor that speeds up
the time to calculate a quality of service estimate, so a request queue to handle
relatively fast calculations seems unnecessary. However, a uni-processor implementation as
seen here can rely heavily on a request queue. Since QoS calculations grow exponentially
with the number of phases between two consecutive, rate-monotonically ordered tasks, a
task set with large differences between consecutive periods can cripple a processor with
lengthy calculations. This, coupled with the fact that the lower priority QoS admission
thread is frequently preempted by the SRMS scheduler and task set, can lead to
unpredictable QoS calculation times. Uncertainty and unpredictability such as this
contributed to the creation of a request queue.
Therefore, the goal of the QoS admission thread is to satisfy all requests from the reqeust_queue
on a first come, first serve basis. If the system can meet the task's requested QoS, then
the task is admitted. Regardless of the result, the system sends the task its estimated
QoS value and removes it from the request_queue. Once the request_queue
is empty, the QoS admission thread tries to satisfy requests from the blocking call, which
are waiting in the await_queue. This queue is also serviced in a FIFO manner,
but if a request cannot be met, then the thread leaves the task in the await_queue
and terminates itself. That queue will be serviced later when the thread gets recreated
due to a change in the active task set, either by a new task entering or an old task
leaving the system. Instead, if a task from the await_queue can be guaranteed
its requested quality of service, then the task is added to the system and notified,
removed from the queue, and the QoS admission thread continues to service the next task
waiting in the queue until it is empty.
The QoS admission thread can also play a secondary role depending on the value of the
data that is passed to from createQosAdmissionThread() when it is first
created. Notice that the function QosAdmissionThreadProc() takes a parameter
called lpQosThreadParam as input. If the value that it points to is equal to SRMS_RECOMPUTE_SYSTEM,
then the first job of the thread is to recompute the allowance and expected QoS values for
the entire SRMS system after an active task has left the system. Upon completion, it
services the request and await queues as described above.
As described above, there are three main responsibilities of the QoS admission thread.
First, if an active task leaves the system, then it must recompute the QoS and allowance
values for the new task system. Then it services all tasks in the request queue and
finally services as many tasks as possible from the await queue until either it is empty
or a task's QoS cannot be satisfied. The function QosAdmissionThreadProc()
contains three sections with these goals in mind.
Note that the QoS admission thread does not place a lock on the SRMS system's task_list
or qos_list while processing QoS and allowance calculations. Doing so would
cause very unpredictable timing results since both the SRMS scheduler and QoS admission
threads would be competing for access to a shared list. Instead, all computations are
performed on a copy of the qos_list, and the only time a list is locked by
the QoS admission thread is during the reading and writing of that list.
The thread only recomputes the system if the parameter lpQosThreadParam
equals SRMS_RECOMPUTE_SYSTEM. In this case, a registered task must have
unregistered, so the unregister_task() function calls createQosAdmissionThread()
with a parameter signaling it to recompute the system. The first thing that the QoS
admission thread does is it makes a copy of the qos_list using the CopyQosList()
function. CopyQosList() waits to obtain a lock on the qos_list,
and when it finally does, it returns a pointer to an exact replica of the list. This copy
is then passed to SpecifySystem() which either returns true if the new system
met all of its tasks' QoS requirements or false if it failed any of them. In this section,
the return value will always be true since an exiting task will only free up system
resources, not take an away. Next, it checks the dirty bit of all entries in the qos_list
to see if any changes had been made to the system task_list since the copy
was made. If a dirty bit is set, then a new copy of the qos_list needs to be
made and the calculations redone. Otherwise, the QoS admission thread can proceed to
confirm the new values in the system's task_list.
The next section of the QoS admission thread handles all non-blocking admission
requests found in the request_list. With a call to CopyQosInfoFromTask(),
the thread makes a copy of all pertinent QoS information from the task entry at the head
of the request_list and returns that information in a qos_task_info_struct
pointed to by myQosInfo. After making a copy of the qos_list,
the thread inserts the myQosInfo structure into the copy using the InsertQosInfoIntoList()
function. When this function finishes, the qos_list_copy points to a new rate
monotonically ordered task set that includes the task being tested. This new task set is
given to SpecifySystem() and it returns either true or false depending on
whether the new system is supportable or not. If the new system passes, then the task can
be admitted into the system. In that case, all new QoS and allowance values need to be
confirmed in the system's task_list and qos_list. Before making
any changes, however, the thread first verifies that no dirty bits are set in the qos_list
and then it copies all new information from the qos_list_copy to the qos_list.
If any entry is missing or has its dirty bit set, then that means that the active task set
has changed since the copy was made and all calculations need to be redone. Once all
values are updated in the system lists, then the QoS admission thread sends the SRMS
scheduler thread a WM_AdmitNewRTtask message and waits for it to process that
message and set the hNewTaskAdmittedEvent handle. (See the section on admitting a new task). While the QoS admission thread is
waiting, the SRMS scheduler removes the task from the request_list, adds it
to the task_list, and notifies the task that it has been admitted into the
system. Finally, once the QoS admission thread wakes from its wait state, its last job is
to add the myQosInfo structure into the qos_list, which makes
all changes final and the addition of the new task complete. However, if the new system
generated with this new task returns a false value from the SpecifySystem()
function, then task being tested is sent a WM_QoS_Admission_FailedMsg and the
task is removed from the request_queue. This process is repeated for all
tasks in the queue until it is empty.
Finally, once the QoS admission thread finishes servicing the request_queue,
it moves on to the final section servicing as many of the requests as it can satisfy in
the await_queue. The procedure is exactly the same as described above for the
non-blocking requests except that the QoS admission thread terminates itself when a a
false value is returned from the SpecifySystem() function. QoS admission
ceases until an active system task unregisters and frees up resources. When this happens,
the QoS admission thread is restarted and the task that failed admittance at the head of
the await_queue will be tested again under different conditions.
Eventually, once enough tasks unregister, tasks that fail admittance can finally gain
admittance into the SRMS system.