Gem #103: Code Archetypes for Real-Time Programming — Part 5

by Marco Panunzio—University of Padua

Introduction

 

In the previous Ada Gem we completed the creation of a complete sporadic task. In this Ada Gem that ends our mini-series, we want to complete the example by adding the realization of the communication between different tasks. In particular, we investigate how we can correctly manage the calls of operations outside a task. Those calls are performed from an OPCS and they have to be correctly routed to the endpoint of the communication.

Intertask communication -- A producer-consumer example

Suppose we want to realize the simple producer-consumer collaboration pattern depicted in the figure below.

Producer_Consumer.png

The Producer is a cyclic task which, after some processing, produces some data that is sent to a Consumer sporadic task. Data is passed as a parameter of operation Op1.

This implies that we have to equip the Producer cyclic task with the means to communicate with the Consumer sporadic task. However, the task structure that we created encapsulates the functional code inside a structure called the OPCS. Therefore, inside the functional code of the Producer we cannot directly call Op1 of the Consumer (i.e., the functional/sequential code), but we have to call the appropriate provided interface of the whole Consumer task.

Let us see how we can achieve this goal when creating the package Producer (analogous to the package Consumer that we presented in the previous Ada Gem of the series).

<b>package</b> Producer <b>is</b>
   <b>type</b> Producer_FC <b>is</b> <b>new</b> Controlled <b>with</b> <b>private</b>;
   <b>type</b> Producer_FC_Ref <b>is</b> <b>access</b> <b>all</b> Producer_FC'Class;   
   <b>type</b> Producer_FC_Static_Ref <b>is</b> <b>access</b> <b>all</b> Producer_FC;
   <em>-- [code omitted]</em>
   <b>overriding</b>
   <b>procedure</b> Initialize(This : <b>in</b> <b>out</b> Producer_FC);
   <b>procedure</b> Op0 (This : <b>in</b> <b>out</b> Producer_FC); 
   <b>procedure</b> Set_x(This : <b>in</b> <b>out</b> Producer_FC; 
                   v : <b>in</b> Consumer.Consumer_FC_Ref);
<b>private</b>
   <b>type</b> Producer_FC <b>is</b> <b>new</b> Controlled <b>with</b> <b>record</b>
      x : Consumer.Consumer_FC_Ref;
   <b>end</b> <b>record</b>;
<b>end</b> Producer;

We are adding a member x in the record of Producer_FC that represents a reference to the Consumer that provides Op1, which consumes the data produced by the Producer. The reader should note that the static type of this reference is the OPCS of the Consumer (which was called Consumer_FC).

<b>package</b> <b>body</b> Producer <b>is</b>
   <em>-- [procedure Initialize omitted]</em>
   <b>procedure</b> Op0(This : <b>in</b> <b>out</b> Producer_FC) <b>is</b>
   <b>begin</b>
      This.x.Op1([T1_VALUE]);  
   <b>end</b> Op1;
  
   <b>procedure</b> Set_x(This : <b>in</b> <b>out</b> Producer_FC; 
                   v : <b>in</b> Consumer.Consumer_FC_Ref)
   <b>is</b>
   <b>begin</b>
      This.x := v;
   <b>end</b> Set_x;
<b>end</b> Producer;

In the body of procedure Op0 (where the sequential code executed by the Producer task is specified), we insert the call to Op1 that is performed using reference x.

<em>-- Package spec</em>
<b>type</b> s1_T <b>is</b> <b>new</b> Consumer.Consumer_FC <b>with</b> <b>record</b>
   Op1_Ref : <b>access</b> <b>procedure</b> (a : <b>in</b> Types.T1; b : <b>in</b> Types.T2);
   Op2_Ref : <b>access</b> <b>procedure</b> (a : <b>in</b> Types.T1);
<b>end</b> <b>record</b>;
<b>overriding</b>
<b>procedure</b> Op1(This : <b>in</b> <b>out</b> s1_T; a : <b>in</b> Types.T1; b : <b>in</b> Types.T2);
<b>overriding</b>
<b>procedure</b> Op2(This : <b>in</b> <b>out</b> s1_T; a : <b>in</b> Types.T1);
<em>-- Package body</em>
<b>procedure</b> Op1(This : <b>in</b> <b>out</b> s1_T; a : <b>in</b> Types.T1; b : <b>in</b> Types.T2) <b>is</b>
<b>begin</b>
   This.Op1_Ref.<b>all</b>(a,b);
<b>end</b> Op1;

Above, in another package, we create a new type s1_T. This type extends Consumer_FC and adds a pointer to a procedure with the signature of Op1, the operation that we call at the Producer side, and Op2. We also override Op1 for s1_T, so that a call to Op1 is reissued to the pointer just defined.

Analogously, suppose we create a new type s0_T that extends Producer_FC.

In the few remaining code excerpts below, we complete the example with the instantiation of the cyclic task for the Producer and the sporadic task for the Consumer.

s0_Instance : <b>aliased</b> s0_T;
<b>package</b> My_Cyclic_Producer_Task <b>is</b> <b>new</b> 
     Op0_Cyclic_Producer.My_Sporadic_Factory(
           Thread_Priority => 1,
           Period => 2000,
           OPCS_Instance =>
               Producer.Producer_FC(s0_Instance)'<b>access</b>);
s1_Instance : <b>aliased</b> s1_T;
<b>package</b> My_Sporadic_Consumer_Task <b>is</b> <b>new</b>
     Op1_Op2_Sporadic_Consumer.My_Sporadic_Factory(
           Thread_Priority => 2,
           Ceiling => 2,
           MIAT => 500,
           OPCS_Instance =>
               Consumer.Consumer_FC(s1_Instance)'<b>access</b>);

Note that we pass a pointer to s0_Instance (respectively s1_Instance) as the OPCS during the instantiation of the Producer (respectively Consumer) cyclic (respectively sporadic) task.

s1_Instance.Op1_Ref := My_Sporadic_Consumer_Task.Op1'<b>access</b>;
s1_Instance.Op2_Ref := My_Sporadic_Consumer_Task.Op2'<b>access</b>;

Finally, with the assignment above, we are able to impose that whenever Op1 is called on s1_Instance, then the call is directed to the operation Op1 on the provided interface of the Consumer Sporadic Task, and from there it follows the correct delegation chain (redirection to the OBCS and reification of the request in the OBCS queue).

s0_Instance.Set_x(Consumer.Consumer_FC_Ref(s1_Instance'<b>access</b>));

Finally, we establish the binding between the Producer and the Consumer with the call above. In fact, it ensures that the call of Op1 inside the OPCS of the Producer (Producer_FC) is a call to the provided interface of the Consumer sporadic task.

Conclusion

In this Ada Gems miniseries we described a set of Ravenscar-compliant code archetypes for the realization of recurrent patterns in real-time systems. We presented two basic patterns for the realization of cyclic and sporadic tasks, commented on their drawbacks, and showed how to improve them to realize sporadic operations with parameters and an example of complex queuing policy. Finally, we showed how to perform intertask communication.

The code archetypes we described were used for the code generation in the HRT-UML/RCM track of the EU-funded ASSERT project.


Last Updated: 10/26/2017
Posted on: 4/11/2011