Rationale for Ada 2012

John Barnes
Contents   Index   References   Search   Previous   Next 

1.3.4 Overview: Tasking and real-time facilities

There are a number of improvements regarding scheduling and dispatching in the Real-Time Systems annex.
A small addition concerns non-preemptive dispatching. In Ada 2005, a task wishing to indicate that it is willing to be preempted has to execute
delay 0.0;
(or delay until Ada.Real_Time.Time_First in Ravenscar). This is ugly and so a procedure Yield is added to the package Ada.Dispatching.
A further addition is the ability to indicate that a task is willing to be preempted by a task of higher priority (but not the same priority). This is done by calling Yield_To_Higher which is declared in a new child package with specification
package Ada.Dispatching.Non_Preemptive is
   pragma Preelaborate(Non_Preemptive);
   procedure Yield_To_Higher;
   procedure Yield_To_Same_Or_Higher renames Yield;
end Ada.Dispatching.Non_Preemptive;
Another low-level scheduling capability concerns suspension objects; these were introduced in Ada 95. Recall that we can declare an object of type Suspension_Object and call procedures to set it True or False. By calling Suspend_Until_True a task can suspend itself until the state of the object is true.
Ada 2005 introduced Earliest Deadline First (EDF) scheduling. The key feature here is that tasks are scheduled according to deadlines and not by priorities. A new facility introduced in Ada 2012 is the ability to suspend until a suspension object is true and then set its deadline sometime in the future. This is done by calling the aptly named procedure Suspend_Until_True_And_Set_Deadline in a new child package Ada.Synchronous_Task_Control.EDF.
A new scheduling feature is the introduction of synchronous barriers in a new child package Ada.Synchronous_Barriers. The main features are a type Synchronous_Barrier with a discriminant giving the number of tasks to be waited for.
type Synchronous_Barrier(Release_Threshold: Barrier_Limit)is limited private;
There is also a procedure
procedure Wait_For_Release(
               The_Barrier: in out Synchronous_Barrier;
               Notified: out Boolean);
When a task calls Wait_For_Release it gets suspended until the number waiting equals the discriminant. All the tasks are then released and just one of them is told about it by the parameter Notified being True. The general idea is that this one task then does something on behalf of all the others. The count of tasks waiting is then reset to zero so that the synchronous barrier can be used again.
A number of other changes in this area are about the use of multiprocessors and again concern the Real-Time Systems annex.
A new package System.Multiprocessors is introduced as follows
package System.Multiprocessors is
   type CPU_Rangeis range 0..implementation-defined;
   Not_A_Specific_CPU: constant CPU_Range := 0:
   subtype CPU is CPU_Range range 1 .. CPU_Range'Last;
   function Number_Of_CPUs return CPU;
end System.Multiprocessors;
A value of subtype CPU denotes a specific processor. Zero indicates don't know or perhaps don't care. The total number of CPUs is determined by calling the function Number_Of_CPUs. This is a function rather than a constant because there could be several partitions with a different number of CPUs on each partition.
Tasks can be allocated to processors by an aspect specification. If we write
task My_Task
   with CPU => 10;
then My_Task will be executed by processor number 10. In the case of a task type then all tasks of that type will be executed by the given processor. The expression giving the processor for a task can be dynamic. The aspect can also be set by a corresponding pragma CPU. (This is an example of a pragma born obsolescent.) The aspect CPU can also be given to the main subprogram in which case the expression must be static.
Further facilities are provided by the child package System.Multiprocessors.Dispatching_Domains. The idea is that processors are grouped together into dispatching domains. A task may then be allocated to a domain and it will be executed on one of the processors of that domain.
Domains are of a type Dispatching_Domain. They are created by a function Create
function Create(First, Last: CPU) return Dispatching_Domain;
that takes the first and last numbered CPU of the domain and then returns the domain. All CPUs are initially in the System_Dispatching_Domain. If we attempt to do something silly such as create overlapping domains, then Dispatching_Domain_Error is raised.
Tasks can be assigned to a domain in two ways. One way is to use an aspect
task My_Task
   with Dispatching_Domain => My_Domain;
The other way is by calling the procedure Assign_Task whose specification is
procedure Assign_Task(
    Domain: in out Dispatching_Domain;
    CPU: in CPU_Range := Not_A_Specific_CPU;
    T: in Task_Id := Current_Task);
There are a number of other subprograms for manipulating domains and CPUs. An interesting one is Delay_Until_And_Set_CPU which delays the calling task until a given real time and then sets the processor.
The Ravenscar profile is now defined to be permissible with multiprocessors. However, there is a restriction that tasks may not change CPU. Accordingly the definition of the profile now includes the following restriction
No_Dependence =>
In order to clarify the use of multiprocessors with group budgets the package Ada.Execution_Time.Group_Budgets introduced in Ada 2005 is slightly modified. The type Group_Budget (which in Ada 2005 is just tagged limited private) has a discriminant in Ada 2012 giving the CPU thus
type Group_Budget(
               CPU: System.Multiprocessors.CPU :=
                        is tagged limited private;
This means that a group budget only applies to a single processor. If a task in a group is executed on another processor then the budget is not consumed. Note that the default value for CPU is CPU'First which is always 1.
Another improvement relating to times and budgets concerns interrupts. Two Boolean constants are added to the packageAda.Execution_Time
        constant Boolean := implementation-defined;
        constant Boolean := implementation-defined;
The constant Interrupt_Clocks_Supported indicates whether the time spent in interrupts is accounted for separately from the tasks and then Separate_Interrupt_Clocks_Supported indicates whether it is accounted for each interrupt individually. There is also a function
function Clocks_For_Interrupts return CPU_Time;
This function gives the time used over all interrupts. Calling it if Interrupt_Clocks_Supported is false raises Program_Error.
A new child package accounts for the interrupts separately if Separate_Interrupt_Clocks_Supported is true.
package Ada.Execution_Time.Interrupts is
   function Clock(Interrupt: Ada.Interrupts.Interrupt_Id)
        return CPU_Time;
   function Supported(Interrupt: Ada.Interrupts.Interrupt_Id)
        return Boolean;
end Ada.Execution_Time.Interrupts;
The function Supported indicates whether the time for a particular interrupt is being monitored. If it is then Clock returns the accumulated time spent in that interrupt handler (otherwise it returns zero). However, if the overall constant Separate_Interrupt_Clocks_Supported is false then calling Clock for a particular interrupt raises Program_Error.
Multiprocessors have an impact on shared variables. The existing pragma Volatile (now the aspect Volatile) requires access to be in memory but this is strictly unnecessary. All we need is to ensure that reads and writes occur in the right order. A new aspect Coherent was considered but was rejected in favour of simply changing the definition of Volatile.
The final improvement in this section is in the core language and concerns synchronized interfaces and requeue. The procedures of a synchronized interface may be implemented by a procedure or entry or by a protected procedure. However, in Ada 2005 it is not possible to requeue on a procedure of a synchronized interface even if it is implemented by an entry. This is a nuisance and prevents certain high level abstractions.
Accordingly, Ada 2012 has an aspect Synchronization that takes one of By_Entry, By_Protected_Procedure, and Optional. So we might write
type Server is synchronized interface;
procedure Q(S: in out Server; X: in Item);
   with Synchronization => By_Entry;
and then we are assured that we are permitted to perform a requeue on any implementation of Q.
As expected there are a number of consistency rules. The aspect can also be applied to a task interface or to a protected interface. But for a task interface it obviously cannot be By_Protected_Procedure.
In the case of inheritance, any Synchronization property is inherited. Naturally, multiple aspect specifications must be consistent. Thus Optional can be overridden by By_Entry or by By_Protected_Procedure but other combinations conflict and so are forbidden.
A related change is that if an entry is renamed as a procedure then we can do a requeue using the procedure name. This was not allowed in Ada 95 or Ada 2005.

Contents   Index   References   Search   Previous   Next 
© 2011, 2012, 2013 John Barnes Informatics.
Sponsored in part by:
The Ada Resource Association:


and   Ada-Europe: