If the Systems Programming Annex is supported, there are extra facilities which can be used to control shared variables betweenunsynchronisedtasks. They come in the form of extra pragmas which can be applied to certain objects or type decla- rations.
PragmaVolatile
The purpose of indicating that a shared data item is volatile is to ensure that the compiler does not optimise the code and keep the item in a register whilst it is manipulating it. PragmaVolatileensures that all reads and writes go directly to memory.
PragmaVolatilecan be applied to
• An object declaration, for example
I : Integer;
pragma Volatile(I); -- object I is volatile J : Some_Record_Type;
pragma Volatile(J); -- object J is volatile K : Some_Array_Type;
pragma Volatile(K); -- object K is volatile
• A non-inherited component declaration, for example
type Record_With_Volatile_Component is tagged record
Component_1 : Integer;
Component_2 : Float;
Component_3 : Some_Enumeration_Type;
end record;
pragma Volatile(Record_With_Volatile_Component.Component_2);
-- all Component_2 elements of all objects of
-- type Record_With_Volatile_Component will be volatile
7.13 Volatile and atomic data 157
type Inherited_Record is new
Record_With_Volatile_Component with record
Component_4 : Some_Type;
end record;
-- Note that the following is ILLEGAL
pragma Volatile(Inherited_Record.Component_3);
-- However, this is legal:
pragma Volatile(Inherited_Record.Component_4);
• A full type declaration (as opposed to a private type or an incomplete type), for example
type Volatile_Data is range 1 .. 100;
pragma Volatile(Volatile_Data);
-- all objects created from the type will be volatile type Volatile_Record is
record
Component_1 : Integer;
Component_2 : Float;
Component_3 : Some_Enumeration_Type;
end record;
pragma Volatile(Volatile_Record);
-- all objects created will be volatile, also all -- components will be volatile (similarly for arrays)
PragmaVolatile Components
PragmaVolatile Componentsapplies to components of an array, for exam- ple
type My_Array is array(1..10) of Integer;
pragma Volatile_Component(My_Array);
-- all array components are volatile for all -- objects of type My_Array
PragmaAtomicand pragmaAtomic Components
Whilst pragma Volatile indicates that all reads and writes must be directed straight to memory, pragmaAtomicimposes the further restriction that they must be indivisible. That is, if two tasks attempt to read and write the shared variable at the same time, then the result must be internally consistent. Consider the following example:
subtype Axis is Integer range 0 .. 100;
type Point is record
X : Axis;
Y : Axis;
end record;
Diagonal : Point := (0,0);
-- invariant X = Y
pragma Volatile(Diagonal);
procedure Draw(X : Point) is ...
function New_Point return Point is ...
task Plot_Point;
task Update_Point;
task body Plot_Point is begin
loop
Draw(Diagonal);
...
end loop;
end Plot_Point;
task body Update_Point is begin
loop ...
Diagonal := New_Point;
end loop;
end Update_Point;
In this example the two tasks both have access to the shared variableDiagonal.
Suppose the values returned from theNew Pointfunction are{(0,0), (1,1), (2,2), ...}. Although the shared variable has been declared as volatile, this does not stop the read and the write operations becoming interleaved. It is quite possible for the Plot Pointtask to draw a point (0,1), (2,1) and so on. Indeed, the program is erroneous.
To cure this problem, theDiagonalvariable must be made atomic:
pragma Atomic(Diagonal);
This ensures not only that all reads and writes are directed to memory (that is, the variable is volatile), but also that reads and writes are indivisible; they cannot be interleaved.
7.13 Volatile and atomic data 159
Important note:
An implementation is not required to support atomic operations for all types of variable; however, if not supported for a particular object, the pragma must be rejected by the compiler.
The same classes of item as supported by the Volatile and Volatile Components pragmas are potentially supportable as Atomic and Atomic Components.
Mutual exclusion and Simpson’s algorithm
Many implementations of the Systems Programming Annex will only support prag- ma Atomicon variables which can be read or written as atomic actions at the machine assembly language level. Typically, this restricts the size of an object to a single word in length. However, using just this basic property it is possible to construct an algorithm which will guarantee that any size data item can be ac- cessed without interference and without having to resort to busy-waiting. One such algorithm, due to Simpson (1990), is given here for two tasks:
generic
type Data is private;
Initial_Value : Data;
package Simpsons_Algorithm is
procedure Write(Item : Data); -- non-blocking procedure Read (Item : out Data); -- non-blocking end Simpsons_Algorithm;
package body Simpsons_Algorithm is type Slot is (First, Second);
Four_Slot : array (Slot, Slot) of Data :=
(First => (Initial_Value,Initial_Value), Second => (Initial_Value,Initial_Value));
pragma Volatile(Four_Slot);
Next_Slot : array(Slot) of Slot := (First, First);
pragma Volatile(Next_Slot);
Latest : Slot := First;
pragma Atomic(Latest);
Reading : Slot := First;
pragma Atomic(Reading);
procedure Write(Item : Data) is Pair, Index : Slot;
begin
if Reading = First then Pair := Second;
else
Pair := First;
end if;
if Latest = First then Index := Second;
else
Index := First;
end if;
Four_Slot(Pair, Index) := Item;
Next_Slot(Pair) := Index;
Latest := Pair;
end Write;
procedure Read(Item : out Data) is Pair, Index : Slot;
begin
Pair := Latest;
Reading := Pair;
Index := Next_Slot(Pair);
Item := Four_Slot(Pair, Index);
end Read;
end Simpsons_Algorithm;
The algorithm works by keeping four slots for the data: two banks of two slots.
The reader and the writer never access the same bank of slots at the same time. The atomic variableLatestcontains the index of the bank to which the last data item was written and theNext Slotarray indexed by this value indicates which slot in that bank contains the data.
Consider some arbitrary time when the latest value of the data item is inFour Slot(Second, First). In this case, Latest equals Second and Next (Second) = First. Assume also that this is the last value read. If another read request comes in and is interleaved with a write request, the write request will choose the first bank of slots and the first slot, and so on. Thus it is possible that the reader will obtain an old value but never an inconsistent one. If the write comes in again before the read has finished, it will write to the first bank and second slot, and then the first bank and first slot. When the reader next comes in, it will obtain the last value that was completely written (that is, the value written by the last full invocation ofWrite). A full proof of this algorithm is given by Simpson (1990).