Rationale for Ada 2005
1.3.4 Overview: Tasking and real-time facilities
Unless mentioned otherwise all the changes in this
section concern the Real-Time Systems annex.
First, the well-established
Ravenscar profile is included in Ada 2005 as directed by WG9. A profile
is a mode of operation and is specified by the pragma
Profile
which defines the particular profile to be used. Thus to ensure that
a program conforms to the Ravenscar profile we write
pragma Profile(Ravenscar);
The purpose of Ravenscar
is to restrict the use of many of the tasking facilities so that the
effect of the program is predictable. This is very important for real-time
safety-critical systems. In the case of Ravenscar the pragma is equivalent
to the joint effect of the following pragmas
pragma Task_Dispatching_Policy(FIFO_Within_Priorities);
pragma Locking_Policy(Ceiling_Locking);
pragma Detect_Blocking;
plus a pragma Restrictions
with a host of arguments such as No_Abort_Statements
and No_Dynamic_Priorities.
The pragma
Detect_Blocking
plus many of the
Restrictions identifiers
are new to Ada 2005. Further details will be given in a later chapter
(see
5.4).
Ada 95 allows the priority
of a task to be changed but does not permit the ceiling priority of a
protected object to be changed. This is rectified in Ada 2005 by the
introduction of an attribute
Priority for
protected objects and the ability to change it by a simple assignment
such as
My_PO'Priority := P;
inside a protected operation of the object My_PO.
The change takes effect at the end of the protected operation.
The monitoring and
control of execution time naturally are important for real-time programs.
Ada 2005 includes packages for three different aspects of this
Ada.Execution_Time
–
This is the root package and enables the monitoring of execution time
of individual tasks.
Ada.Execution_Time.Timers
–
This provides facilities for defining and enabling timers and for establishing
a handler which is called by the run time system when the execution time
of the task reaches a given value.
Ada.Execution_Time.Group_Budgets
–
This allows several tasks to share a budget and provides means whereby
action can be taken when the budget expires.
The execution time
of a task or CPU time, as it is commonly called, is the time spent by
the system executing the task and services on its behalf. CPU times are
represented by the private type CPU_Time.
The CPU time of a particular task is obtained by calling the following
function Clock in the package Ada.Execution_Time
function Clock(T: Task_Id := Current_Task) return CPU_Time;
A value of type CPU_Time
can be converted to a Seconds_Count plus residual
Time_Span by a procedure Split
similar to that in the package Ada.Real_Time.
Incidentally we are guaranteed that the granularity of CPU times is no
greater than one millisecond and that the range is at least 50 years.
In order to find out
when a task reaches a particular CPU time we use the facilities of the
child package Ada.Execution_Time.Timers. This
includes a discriminated type Timer and a
type Handler thus
type Timer(T: not null access constant Task_Id) is tagged limited private;
type Timer_Handler is access protected procedure (TM: in out Timer);
Note how the access discriminant illustrates the
use of both not null and constant.
We can then set the
timer to expire at some absolute time by
Set_Handler(My_Timer, Time_Limit, My_Handler'Access);
and then when the CPU time of the task reaches Time_Limit
(of type CPU_Time), the protected procedure
My_Handler is executed. Note how the timer
object incorporates the information regarding the task concerned using
an access discriminant and that this is passed to the handler via its
parameter. Another version of Set_Handler
enables the timer to be triggered after a given interval (of type Time_Span).
In order to program
various aperiodic servers it is necessary for tasks to share a CPU budget.
This can be done using the child package
Ada.Execution_Time.Group_Budgets.
In this case we have
type Group Budget is tagged limited private;
type Group_Budget_Handler is access protected procedure (GB: in out Group_Budget);
The type Group_Budget
both identifies the group of tasks it belongs to and the size of the
budget. Various subprograms enable tasks to be added to and removed from
a group budget. Other procedures enable the budget to be set and replenished.
A procedure Set_Handler
associates a particular handler with a budget.
Set_Handler(GB => My_Group_Budget, Handler => My_Handler'Access);
When the group budget expires the associated protected
procedure is executed.
A somewhat related
topic is that of low level timing events. The facilities are provided
by the package
Ada.Real_Time.Timing_Events.
In this case we have
type Timing_Event is tagged limited private;
type Timing_Event_Handler is access protected procedure(Event: in out Timing_Event);
The idea here is that
a protected procedure can be nominated to be executed at some time in
the future. Thus to ring a pinger when our egg is boiled after four minutes
we might have a protected procedure
protected body Egg is
procedure Is_Done(Event: in out Timing_Event) is
begin
Ring_The_Pinger;
end Is_Done;
end Egg;
and then
Egg_Done: Timing_Event;
Four_Min: Time_Span := Minutes(4);
...
Put_Egg_In_Water;
Set_Handler(Event => Egg_Done, In_Time => Four_Min, Handler => Egg.Is_Done'Access);
-- now read newspaper whilst waiting for egg
This facility is of course very low level and does
not involve Ada tasks at all. Note that we can set the event to occur
at some absolute time as well as at a relative time as above. Incidentally,
the function Minutes is a new function added
to the parent package Ada.Real_Time. Otherwise
we would have had to write something revolting such as 4*60*Milliseconds(1000).
A similar function Seconds has also been added.
There is a minor flaw in the above example. If we
are interrupted by the telephone between putting the egg in the water
and setting the handler then our egg will be overdone. We will see how
to cure this in a later chapter (see
5.6).
Readers will recall the old problem of how tasks
can have a silent death. If something in a task goes wrong in Ada 95
and an exception is raised which is not handled by the task, then it
is propagated into thin air and just vanishes. It was always deemed impossible
for the exception to be handled by the enclosing unit because of the
inherent asynchronous nature of the event.
This is overcome in
Ada 2005 by the package
Ada.Task_Termination
which provides facilities for associating a protected procedure with
a task. The protected procedure is invoked when the task terminates with
an indication of the reason. Thus we might declare a protected object
Grim_Reaper
protected Grim_Reaper is
procedure Last_Gasp(C: Cause_Of_Termination; T: Task_Id; X: Exception_Occurrence);
end Grim_Reaper;
We can then nominate
Last_Gasp as the protected procedure to be
called when task T dies by
Set_Specific_Handler(T'Identity, Grim_Reaper.Last_Gasp'Access);
The body of the protected
procedure Last_Gasp might then output various
diagnostic messages
procedure Last_Gasp(C: Cause_Of_Termination; T: Task_Id; X: Exception_Occurrence) is
begin
case C is
when Normal => null;
when Abnormal =>
Put("Something nasty happened"); ...
when Unhandled_Exception =>
Put("Unhandled exception occurred"); ...
end case;
end Last_Gasp;
There are three possible reasons for termination,
it could be normal, abnormal, or caused by an unhandled exception. In
the last case the parameter X gives details
of the exception occurrence.
Another area of increased flexibility in Ada 2005
is that of task dispatching policies. In Ada 95, the only predefined
policy is FIFO_Within_Priorities although
other policies are permitted. Ada 2005 provides further pragmas, policies
and packages which facilitate many different mechanisms such as non-preemption
within priorities, the familiar Round Robin using timeslicing, and the
more recently acclaimed Earliest Deadline First (EDF) policy. Moreover,
it is possible to mix different policies according to priority level
within a partition.
Various facilities
are provided by the package Ada.Dispatching
plus two child packages
Ada.Dispatching
–
This is the root package and simply declares an exception Dispatching_Policy_Error.
Ada.Dispatching.Round_Robin
–
This enables the setting of the time quanta for time slicing within one
or more priority levels.
Ada.Dispatching.EDF
–
This enables the setting of the deadlines for various tasks.
A policy can be selected
for a whole partition by one of
pragma Task_Dispatching_Policy(Non_Preemptive_FIFO_Within_Priorities);
pragma Task_Dispatching_Policy(Round_Robin_Within_Priorities);
pragma Task_Dispatching_Policy(EDF_Across_Priorities);
In order to mix different
policies across different priority levels we use the pragma Priority_Specific_Dispatching
with various policy identifiers thus
pragma Priority_Specific_Dispatching(Round_Robin_Within_Priorities, 1, 1);
pragma Priority_Specific_Dispatching(EDF_Across_Priorities, 2, 10);
pragma Priority_Specific_Dispatching(FIFO_Within_Priorities, 11, 24);
This sets Round Robin at priority level 1, EDF at
levels 2 to 10, and FIFO at levels 11 to 24.
The final topic in this section concerns the core
language and not the Real-Time Systems annex. Ada 2005 introduces a means
whereby object oriented and real-time features can be closely linked
together through inheritance.
Recall from Section
1.3.1 that we can declare an interface to
be limited thus
type LI is limited interface;
We can also declare
an interface to be synchronized, task, or protected thus
type SI is synchronized interface;
type TI is task interface;
type PI is protected interface;
A task interface or protected interface has to be
implemented by a task type or protected type respectively. However, a
synchronized interface can be implemented by either a task type or a
protected type. These interfaces can also be composed with certain restrictions.
Detailed examples will be given in a later chapter (see
5.3).
© 2005, 2006 John Barnes Informatics.
Sponsored in part by: