Rationale for Ada 2005

John Barnes
Contents   Index   References   Search   Previous   Next 

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 found in Section 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 Section 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 log 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_Log("Something nasty happened"); ...
      when Unhandled_Exception =>
         Put_Log("Unhandled exception occurred"); ...
   end case;
end Last_Gasp;
Remember that we should not call potentially blocking operations such as Put to a file within a protected operation so we call some procedure Put_Log which buffers the messages for later analysis.
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 found in Section 5.3.

Contents   Index   References   Search   Previous   Next 
© 2005, 2006, 2007 John Barnes Informatics.
Sponsored in part by:
The Ada Resource Association and its member companies: ARA Members AdaCore Polyspace Technologies Praxis Critical Systems IBM Rational Sofcheck and   Ada-Europe:
Ada-Europe