ADMITTING THE NEXT JOB OF A TASK
Upon reaching the a task's deadline, the SRMS scheduler
records the results of the job, cleans up the job if it remained unfinished, and then
tries to admit the next job for the ensuing period. All three chores are the performed
when the SRMS scheduler calls the record_job_results() function. After all
the record keeping and cleaning is finished, admit_next_job() is used to get
the task's job requirements for the next period.
The most critical function of admit_next_job() is to communicate with the
task so that SRMS scheduler can receive its job's estimated execution requirements.
Communication consists of the scheduler giving the task its remaining time budget and the
task replying with its expected execution requirement. The function get_next_exec()
performs the actual communication between the scheduler and task. Remember, however, that
at this point in the scheduling process, all tasks are suspended in waiting states. In
order to perform communication, the SRMS scheduler needs to move the task to a ready
state. Therefore, the first thing that record_job_results() does even before
record keeping or cleaning up the last job is it calls Win32's ResumeThread()on
the task that it needs to wake up. [Note that the call to ResumeThread()
is made at the beginning of record_job_results(), but communication actually
occurs at the end of the function when admit_next_job() is called. Logically,
ResumeThread() should be called just before communication should occur, but
it was found in practice that calling ResumeThread() in admit_next_job()
just prior to communication caused unpredictable delays. Therefore, the call is made
earlier to give the NT kernel sufficient time to transfer the task thread to a ready
state.]
Communication begins in get_next_exec() with the scheduler copying the
task's remaining budget to a shared memory buffer and then signaling the task's hJobReleasedEvent handle. Following this, the
scheduler immediately waits for the task to reply by signaling the its hExecCalcEvent handle. Note that the scheduler limits
its wait time to SRMS_COMMUNICATION_WAIT which by default is set to 10
milliseconds in srms.h. This limitation acts as a failsafe in case the task
thread does not respond immediately. If the timeout period elapses, the time spent gets
subtracted from the task's budget and the process repeats itself one more time. If the
timeout period elapses again, then a job failed signal is sent to the task and the
scheduler moves on. Otherwise, the task processed the budget value and replaced it with
its next job's execution requirement. The scheduler reads the value from the shared memory
buffer and assigns that value to the task's task_struct exec and
exec_left entries. [On a side note, it also records the amount of time that
is wasted during communication with the task in the task's execution history array entry.]
Upon returning from get_next_exec(), admit_next_job()
recomputes the job's ready time and deadline. Next, the function checks to see if the task
has reached its super-deadline and replenishes its budget if so. If not, then the job may
overlap the super-deadline which means that its ready time comes before its super-deadline
and its deadline comes after it. In that case, the scheduler renews the budget and accepts
or rejects the job based on if the job of the next lower-priority task has completed or
not. Also, two heuristics adapted from Atlas' SRMS Simulator are used
to improve the overall system performance. The first heuristic checks if the task has
enough allowance and time to run on this super-period's allowance and if so, accepts the
job. Another performance heuristic checks if the job will be able to complete after the
next task's job is finished and if so accepts the job at a lower priority so that it will
complete after the next task's job completes.
In the event that the last task did not overlap a super-deadline, then the task can use
leftover time, assuming that the user has permitted time inheritance (the boolean variable
do_inherit in rt.c is set to true if permitted). Otherwise, the
time needs to be passed on. Finally, the job is accepted so long as its execution
requirement is less than or equal to its remaining budget. By accepting the job,
SRMS guarantees its completion before its deadline and therefore sets the job's guaranteed
flag to true and assigns it a "high" priority. Otherwise, a rejected job
is not guaranteed to finish, and it is set to run at a lower priority so that it does not
use up any processor resources guaranteed to other tasks. In a dual priority system,
lower priority jobs will execute only if there are no other higher priority tasks with
work to finish. It also records the admit/reject status of the job in its execution
history array entry and then sends it an accept/reject message via the send_job_accept_message()
or send_job_reject_message() functions.
On a side note, admit_next_job() is also prepared to accept or reject jobs
from tasks that do not know their execution requirement for the next job. Tasks in
this situation respond to the SRMS scheduler with an SRMS_EXEC_UNSURE value
instead of an estimated execution time. The scheduler will always reject any job that is
unsure of its execution requirement, and that job will be run at a lower priority than all
other accepted real-time jobs.