Rationale for Ada 2012
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 =>
System.Multiprocessors.Dispatching_Domains
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 :=
System.Multiprocessors.CPU'First)
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 package
Ada.Execution_Time
Interrupt_Clocks_Supported:
constant Boolean := implementation-defined;
Separate_Interrupt_Clocks_Supported:
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.
© 2011, 2012, 2013 John Barnes Informatics.
Sponsored in part by: