Gem #94: Code Archetypes for Real-Time Programming - Part 3

by Marco Panunzio —University of Padua

Introduction

In the previous Ada Gem we described a code archetype for a simple sporadic task. Nevertheless, we recognized that the archetype is not completely satisfactory for at least two reasons: (i) it is not possible to pass parameters to the sporadic operation; (ii) the synchronization agent (OBCS) is a simple counter of pending requests.

In this Ada Gem we illustrate a more complex archetype that supports the invocation of a sporadic operation with parameters. Additionally, we want to explore how it is possible to enrich the OBCS to support complex queueing policies for the incoming requests.

Sporadic task

The archetype of a sporadic task that we wish to illustrate is depicted in the figure below.

Suppose that we want to create a sporadic task that at each release can execute either operation Op1 or operation Op2, according to incoming requests by clients. Executing different operations with the same task is not an unusual need in real-time systems, especially when the execution platform has scarce computational power and memory resources, and the excessive proliferation of tasks may tax the system too much.

Furthermore, in this archetype we may also want to establish some relative ordering of importance between Op1 and Op2. We consider Op1 as the nominal operation of the sporadic task (the operation normally called by clients), and we call it the START operation; in contrast, we consider Op2 to be a modifier operation, called the ATC. Then, we stipulate that pending requests for execution of Op1 are served by the sporadic task in FIFO ordering, but requests for Op2 take precedence over pending requests of Op1. This choice implies that modifier operations are allowed to cause a one-time (as opposed to permanent) modification of the nominal execution behavior of the task.

OBCS for a sporadic task

OBCS for a sporadic task

We want to encapsulate the implementation of this policy and simply expose to clients of this sporadic task a set of procedures with the signatures of Op1 and Op2; the role of these procedures is to reify the corresponding execution requests. The invocation (type and actual parameters) is recorded in a language-level structure and stored in the OBCS. When the sporadic task fetches a request, it decodes the original request and calls the appropriate operation with the correct parameters.

Sporadic Task -- System Types and Task Type

Let us now have a look at the set of types we need to implement this archetype. They are declared in a modified version of the package System_Types that we also used in the preceding Ada Gem.

with System;
with Ada.Real_Time; use Ada.Real_Time;
with System_Time;
with Ada.Finalization; use Ada.Finalization;

package System_Types is

   -- Abstract parameter type --
   type Param_Type is abstract tagged record
      In_Use : Boolean := False;
   end record;

   -- Abstract functional procedure --
   procedure My_OPCS (Self : in out Param_Type) is abstract;

   type Param_Type_Ref is access all Param_Type'Class;
   type Param_Arr is array(Integer range <>) of Param_Type_Ref;
   type Param_Arr_Ref is access all Param_Arr;

   -- Request type --
   type Request_T is (NO_REQ, START_REQ, ATC_REQ);

   -- Request descriptor to reify an execution request
   type Request_Descriptor_T is
      record
         Request : Request_T;
         Params : Param_Type_Ref;
      end record;

   -- Parameter buffer
   type Param_Buffer_T(Size : Integer) is
      record
         Buffer : aliased Param_Arr(1..Size);
         Index : Integer := 1;
      end record;

   type Param_Buffer_Ref is access all Param_Buffer_T;

   procedure Increase_Index(Self : in out Param_Buffer_T);

We have declared a set of types to represent parameters, a type describing the kinds of requests (START_REQ, ATC_REQ, and an additional kind NO_REQ just for the sake of the explanation), and a request descriptor type to encapsulate the information about invocations of Op1 and Op2. We also declare procedure My_OPCS(..), which is an abstract procedure that represents all possible operations that can be invoked by the sporadic task.

We now continue on with the remainder of the specification of package System_Types:

   -- Abstract OBCS --
   type OBCS_T is abstract new Controlled with null record;
   type OBCS_T_Ref is access all OBCS_T'Class;

   procedure Put(Self : in out OBCS_T; Req : Request_T; P : Param_Type_Ref)
      is abstract;

   procedure Get(Self : in out OBCS_T; R : out Request_Descriptor_T)
      is abstract;

   -- Sporadic OBCS --
   type Sporadic_OBCS(Size : Integer) is new OBCS_T with
      record
         START_Param_Buffer : Param_Arr(1..Size);
         START_Insert_Index : Integer;
         START_Extract_Index : Integer;
         START_Pending : Integer;
         ATC_Param_Buffer : Param_Arr(1..Size);
         ATC_Insert_Index : Integer;
         ATC_Extract_Index : Integer;
         ATC_Pending : Integer;
         Pending : Integer;
      end record;

   overriding
   procedure Initialize(Self : in out Sporadic_OBCS);

   overriding
   procedure Put(Self : in out Sporadic_OBCS; Req : Request_T; P : Param_Type_Ref);

   overriding
   procedure Get(Self : in out Sporadic_OBCS; R : out Request_Descriptor_T);

end System_Types;

Above, we declare a root type to represent an abstract OBCS (OBCS_T) and a Sporadic_OBCS type that implements the queueing policy we previously described. START_Param_Buffer and ATC_Param_Buffer are two distinct circular buffers that are used to store the invocations of the respective types of operation. In addition, we create a buffer for parameters.

The package body follows:

package body System_Types is

   -- Sporadic OBCS --
   procedure Initialize (Self : in out Sporadic_OBCS) is
   begin
      Self.START_Pending       := 0;
      Self.START_Insert_Index  := Self.START_Param_Buffer'First;
      Self.START_Extract_Index := Self.START_Param_Buffer'First;
      Self.ATC_Pending         := 0;
      Self.ATC_Insert_Index    := Self.ATC_Param_Buffer'First;
      Self.ATC_Extract_Index   := Self.ATC_Param_Buffer'First;
   end Initialize;

   procedure Put(Self : in out Sporadic_OBCS; Req : Request_T; P : Param_Type_Ref) is
   begin
      case Req is
         when START_REQ =>
            Self.START_Param_Buffer (Self.START_Insert_Index) := P;
            Self.START_Insert_Index := Self.START_Insert_Index + 1;
            if Self.START_Insert_Index > Self.START_Param_Buffer'Last then
               Self.START_Insert_Index := Self.START_Param_Buffer'First;
            end if;
            -- Increase the number of pending requests, but do not overcome
            -- the number of buffered ones
            if Self.START_Pending < Self.START_Param_Buffer'Last then
               Self.START_Pending := Self.START_Pending + 1;
            end if;
         when ATC_REQ =>
            Self.ATC_Param_Buffer (Self.ATC_Insert_Index) := P;
            Self.ATC_Insert_Index := Self.ATC_Insert_Index + 1;
            if Self.ATC_Insert_Index > Self.ATC_Param_Buffer'Last then
               Self.ATC_Insert_Index := Self.ATC_Param_Buffer'First;
            end if;

            if Self.ATC_Pending < Self.ATC_Param_Buffer'Last then
               -- Increase the number of pending requests, but do not overcome
               -- the number of buffered ones
               Self.ATC_Pending := Self.ATC_Pending + 1;
            end if;

         when others => null;
      end case;
      Self.Pending := Self.START_Pending + Self.ATC_Pending;
   end Put;

   procedure Get(Self : in out Sporadic_OBCS; R : out Request_Descriptor_T) is
   begin
      if Self.ATC_Pending > 0 then
         R := (ATC_REQ, Self.ATC_Param_Buffer(Self.ATC_Extract_Index));
         Self.ATC_Extract_Index := Self.ATC_Extract_Index + 1;
         if Self.ATC_Extract_Index > Self.ATC_Param_Buffer'Last then
            Self.ATC_Extract_Index := Self.ATC_Param_Buffer'First;
         end if;
         Self.ATC_Pending := Self.ATC_Pending - 1;
         else
            if Self.START_Pending > 0 then
               R := (START_REQ, Self.START_Param_Buffer(Self.START_Extract_Index));
               Self.START_Extract_Index := Self.START_Extract_Index + 1;
               if Self.START_Extract_Index > Self.START_Param_Buffer'Last then
                  Self.START_Extract_Index := Self.START_Param_Buffer'First;
               end if;
               Self.START_Pending := Self.START_Pending - 1;
            end if;
      end if;
      R.Params.In_Use := True;
      Self.Pending := Self.START_Pending + Self.ATC_Pending;
   end Get;

   procedure Increase_Index(Self : in out Param_Buffer_T) is
   begin
      Self.Index := Self.Index + 1;
      if Self.Index > Self.Buffer'Last then
         Self.Index := Self.Buffer'First;
      end if;
   end Increase_Index;
end System_Types;

In the package body we implement the desired queuing policy. Procedure Put(..) simply inserts the representation of the incoming request in the queue of the requested operation kind (START_REQ or ATC_REQ). The ordering among requests of the same operation kind is FIFO.

Procedure Get(..) is used to extract a request descriptor. We can see that as long as there are pending ATC requests, they are selected based on their arrival order. When the ATC queue is empty, requests for START operations are fetched.

The task that uses this sporadic OBCS has a specification almost identical to the "simple sporadic task" we presented in the preceding Ada Gem. The only difference is that Get_Request now also fetches a request descriptor.

with System_Types; use System_Types;
with System; use System;
with Ada.Real_Time; use Ada.Real_Time;

generic
   with procedure Get_Request(Req : out Request_Descriptor_T;
                              Release : out Time);
package Sporadic_Task is

   task type Thread_T (Thread_Priority : Any_Priority;
                      MIAT : Integer) is
      pragma Priority (Thread_Priority);
   end Thread_T;

end Sporadic_Task;

The body for the task type follows:

with System_Time; use System_Time;
package body Sporadic_Task is

   task body Thread_T is
      Req_Desc : Request_Descriptor_T;
      Release : Time;
      Next_Time : Time := System_Start_Time;
   begin
      loop
         delay until Next_Time;
         Get_Request (Req_Desc, Release);
         Next_Time := Release + Milliseconds (MIAT);
         case Req_Desc.Request is
            when NO_REQ =>
                    null;
            when START_REQ | ATC_REQ =>
                    My_OPCS (Req_Desc.Params.all);
            when others =>
                    null;
         end case;
      end loop;
   end Thread_T;

end Sporadic_Task;

Notice that the descriptor of the fetched request can be used to discriminate the action to perform according to the type of operation (this is done with the case statement). In our case, if we fetch a request of kind START_REQ or ATC_REQ, we simply execute My_OPCS, that will dynamically dispatch to the requested operation. This mechanism will be clear when, in a later Gem, we complete the picture with the declaration of Op1 and Op2 as seen by their clients.