6. PolyORB-HI/C¶
6.1. About¶
PolyORB-HI/C is a middleware for High-Integrity Systems, it inherits most concepts of the schizophrenic middleware PolyORB while being based on a complete new source code base, compatible with the Ravenscar profile and the restrictions for High-Integrity systems.
PolyORB-HI/C acts as an execution runtime for the AADL language. In this context, Ocarina acts as a compiler, turning an AADL model into C code that uses low-level constructs provided by PolyORB-HI/C.
The generated code is in charge of allocating all required ressources (threads, buffers, message queue), configure communication stacks, marshallers and concurrency structures.
6.2. Supported Platforms¶
PolyORB-HI-C has been compiled and sucessfully tested on
Native platforms
- Linux
- Mac OS X
- FreeBSD
- Windows
Embedded platforms
- AIR Hypervisor
- FreeRTOS (alpha stage)
- RTEMS
- Xenomai
- XtratuM
Note
when using RTEMS operating system, you have to define the RTEMS_MAKEFILE_PATH environment variable. See RTEMS documentation for more details.
6.3. Tree structure¶
PolyORB-HI-C source directory has the following tree structure:
doc/
: documentation,examples/
: set of examples to test PolyORB-HI-Cshare/
: common files (aadl files used by Ocarina, makefiles, …)src/
: core of PolyORB-HIsrc/drivers
: device drivers supported by PolyORB-HI-Ctools/
: some script to handle the packaging and a verification tool to check if the binaries are compliant with the POSIX restrictionsCOPYING3
andCOPYING.RUNTIME
: licence informationREADME
: short description of the distribution
When installed with Ocarina, in $OCARINA_PATH
directory:
- documentation is in
$OCARINA_PATH/share/doc/ocarina
- examples are in
$OCARINA_PATH/examples/ocarina/polyorb-hi-c/
- runtime files are in
$OCARINA_PATH/include/ocarina/runtime/polyorb-hi-c/
6.4. Generating code from an AADL model¶
To build your own system, you have to select the PolyORB-HI/C backend, either using a scenario file or the command line.
- To use a scenario file, refer to Scenario files
- To use command line, you have to select the
polyorb_hi_c
backend, e.g. by using the command ocarina -g polyorb_hi_c <list-of-aadl-files>. Refer to Ocarina command-line for more details .
6.5. Code generation towards PolyORB-HI/C¶
6.5.1. The ping
example¶
In the following, and for each component of the distributed application, we give the AADL entities that are used to model this component, and then the transformation rules used by the code generator to generate C code from these entities.
The mapping rules will be illustrated using the following simple example of a distributed application:
@image{fig/ping, 12cm}
The figure above shows the architecture of the ping
example: a
client, which is a process containing one single periodic thread,
sends a message to the server which is a process containing one
sporadic thread that handles incoming ping messages from the
client. Each node of the ping
application runs on a different
machine.
In this chapter, we first present the AADL modeling patterns used to define a distributed application. Then, we give the rules applied to map AADL entities onto instances of PolyORB-HI/C elements.
In the following, we detail only the rules that are directly related to the distributed application as a whole system. The rules that are specific to the components of the distributed application are explained in the sections that deals with these respective components.
6.5.2. Mapping AADL system
¶
A full system is captured using a root system. The system implementation shown on the following example models such system.
system PING
end PING;
system implementation PING.Impl
subcomponents
-- Nodes
Node_A : process A.Impl;
Node_B : process B.Impl {ARAO::port_number => 12002;};
-- Processors
-- ...
connections
-- ...
properties
-- ...
end PING.Impl;
For each node (process) of the system, we instantiate a subcomponent
in the system implementation, and bind it to a processor
component. This processor will configure the build target.
We use the properties
section of the AADL system
(see
@ref{Hosts` for more details) to map the different nodes on the
different platforms of the distributed application. The
connections
section of the system implementation models the
connections between the different nodes of the application.
An AADL system
is mapped into a hierarchy of directories:
- the root directory of the distributed application has the same name as the root system implementation, in lower case, all dot being converted into underscores. This directory is the root of the directory hierarchy of the generated C code.
- for each node of the distributed application, a child directory having the same name as the corresponding process subcomponent (in lower case) is created inside the root directory. This child directory will contain all the code generated for the particular node it was created for (see @ref{Distributed application nodes} for more details).
6.5.3. Mapping AADL process
¶
We use the process
component category to model an application
node (e.g. an element that would ultimately become a Unix process, an
embedded application or an ARINC653 partition). The process
implementation shown in the listing below shows such system.
process A
features
Out_Port : out event data port Simple_Type;
end A;
process implementation A.Impl
subcomponents
Pinger : thread P.Impl;
connections
C1 : event data port Pinger.Data_Source -> Out_Port;
end A.Impl;
For each thread that belongs to a node of the distributed application,
we instantiate a subcomponent in the process implementation. For each
connection between a node and another, a port
feature has to be
added to both nodes with the direction out
for the source and
in
for the destination (see @ref{Connections} for more details
on connections mapping).
Elements associated to this process (threads, subprograms, data types, etc) are generated in a child directory of the root system directory. This directory has the same name as the process subcomponent instance relative to the handled node in the system implementation that model the distributed application, in lower case.
For example, all the entities relative to the process A.Impl
of the Ping
example are generated in the directory
ping_impl/node_a
.
The following paragraphs list the C compilation units that are created for each node of the distributed application.
6.5.3.1. Marshallers functions¶
The marshallers functions are used to put all request and types values
in a message in order to send them through a network connections. All
marshalling functions are declared in the file marshallers.c
.
However, PolyORB-HI-C can also use third-party marshallers. It can rely on the marshallers generated for ASN1 encoding. Details about ASN1 marshallers are provided in the next section.
@subsubsection Using ASN1 marshallers
With the ASN1 tools from Semantix (see. @url{http://www.semantix.gr/assert/}), you can convert ASN1 declarations into AADL models. Then, these models can be used with AADL components and PolyORB-HI-C relies on Semantix tools to automatically generates C code that implements the ASN1 types.
For that purpose, you need to install the program asn2aadlPlus
and asn1cc
. These programs are freely available on
@url{http://www.semantix.gr/assert/}. Then, when you use ASN1 types
with your AADL model (with the AADL files generated with
asn2aadlPlus
), PolyORB-HI-C uses the generated code from ASN1
descriptions and integrate it to marshall data.
6.5.3.2. Node activity¶
We denote activity the set of the actions performed by one particular node which are not triggered by other nodes. All the periodic threads of a node are part of the node activity.
The code related to the node activity is generated in an C file
with the name activity.c
. An example is shown below :
#include <po_hi_types.h>
#include <po_hi_gqueue.h>
#include <request.h>
#include <deployment.h>
#include <types.h>
#include <subprograms.h>
#include <po_hi_task.h>
#include <po_hi_main.h>
#include <marshallers.h>
extern __po_hi_entity_t __po_hi_port_global_to_entity[__PO_HI_NB_PORTS];
extern __po_hi_port_t __po_hi_port_global_to_local[__PO_HI_NB_PORTS];
__po_hi_int8_t __po_hi_data_source_local_destinations[1] = {ping_me_global_data_sink};
__po_hi_uint8_t __po_hi_pinger_woffsets[__po_hi_pinger_nb_ports];
__po_hi_uint8_t __po_hi_pinger_offsets[__po_hi_pinger_nb_ports];
__po_hi_uint8_t __po_hi_pinger_used_size[__po_hi_pinger_nb_ports];
__po_hi_uint8_t __po_hi_pinger_empties[__po_hi_pinger_nb_ports];
__po_hi_uint8_t __po_hi_pinger_first[__po_hi_pinger_nb_ports];
__po_hi_uint8_t __po_hi_pinger_recent[__po_hi_pinger_nb_ports * sizeof(__po_hi_request_t)];
__po_hi_uint8_t __po_hi_pinger_queue[0 * sizeof(__po_hi_request_t)];
__po_hi_uint16_t __po_hi_pinger_total_fifo_size = 0;
__po_hi_port_t __po_hi_pinger_history[0];
__po_hi_uint8_t __po_hi_pinger_n_dest[__po_hi_pinger_nb_ports] = {1};
__po_hi_int8_t __po_hi_pinger_fifo_size[__po_hi_pinger_nb_ports] = {__PO_HI_GQUEUE_FIFO_OUT};
__po_hi_uint8_t* __po_hi_pinger_destinations[__po_hi_pinger_nb_ports] = {__po_hi_data_source_local_destinations};
/* Periodic task : Pinger*/
/****************/
/* pinger_job */
/****************/
void* pinger_job ()
{
simple_type data_source_request_var;
__po_hi_request_t data_source_request;
__po_hi_gqueue_init(node_a_pinger_k,__po_hi_pinger_nb_ports,__po_hi_pinger_queue,__po_hi_pinger_fifo_size,__po_hi_pinger_first,__po_hi_pinger_offsets,__po_hi_pinger_woffsets,__po_hi_pinger_n_dest,__po_hi_pinger_destinations,__po_hi_pinger_used_size,__po_hi_pinger_history,__po_hi_pinger_recent,__po_hi_pinger_empties,__po_hi_pinger_total_fifo_size);
__po_hi_wait_initialization();
while (1)
{
/* Call implementation*/
do_ping_spg(&(data_source_request_var));
/* Set the OUT port values*/
data_source_request.vars.pinger_global_data_source.pinger_global_data_source = data_source_request_var;
data_source_request.port = data_source_request_var;
__po_hi_gqueue_store_out(node_a_pinger_k,pinger_local_data_source,&(data_source_request));
/* Send the OUT ports*/
__po_hi_gqueue_send_output(node_a_pinger_k,pinger_global_data_source);
__po_hi_wait_for_next_period(node_a_pinger_k);
}
}
/**************************/
/* __po_hi_main_deliver */
/**************************/
void __po_hi_main_deliver
(__po_hi_msg_t* message)
{
__po_hi_request_t request;
__po_hi_entity_t entity;
__po_hi_unmarshall_request(&(request),message);
entity = __po_hi_port_global_to_entity[request.port];
switch (entity)
{
default:
{
break;
}
}
}
All the naming rules explained in @ref{Whole distributed application}
are also applied to map the package name. This file contains all the
routines mapped from the periodic threads that belong to the handled
node (see @ref{Threads} for more details on thread mapping). This
package contains also the instances of shared objects used in this
node (see @ref{Data} for more details). If the node does not contain
any periodic thread nor shared objects, there is no
activity.c
file generated for this node. Thus, the node
B
in the Ping
example does not have a
activity.c
package.
6.5.3.3. Data types¶
All the data types mapped from AADL data components and used by a
particular node of a distributed application are gathered in a
separate C file called types.h
.
For more detail on the mapping of data components, see @ref{Data}.
6.5.3.4. Subprograms¶
The mapping of all AADL subprogram components used by a particular
node is generated in a separate file called subprograms.c
.
The content of the file is shown in the following example:
For more detail on the mapping of subprogram components, see @ref{Subprograms}.
6.5.3.5. Deployment information¶
The deployment information is the information each node has on the
other nodes in the distributed applications. This information is used,
to send a request to another node or to receive a request from
another node. The deployment information is generated for each node in
two C files : deployment.h
and deployment.c
.
The file deployment.h
contains the following types
- a first type called
__po_hi_node_t
. For each node in the application we create an enum whose name is mapped from the node instance declared in the system implementation to which we concatenate the string _k. All the naming rules listed in @ref{Whole distributed application} have to be respected. - a second type called
__po_hi_entity_t
. For each thread in the the application, we declare an enum. - a third type called
__po_hi_task_id
. For each thread that run on the current node. - a fourth type called
__po_hi_entity_server_t
. For each node that may communicate with the current node, we add a value in this enum. It will be used by the transport layer. Please note that at least one server is declared : the valueinvalid_server
. - a fifth type called
__po_hi_port_t
that contains all global port identifier.
More, this file contains the following maccros :
__PO_HI_NB_ENTITIES
is the number of entities in the whole distributed system.__PO_HI_NB_TASKS
is the number of the tasks that will be started on the current node__PO_HI_NB_NODES
is the number of nodes in the distributed system.__PO_HI_PROTECTED
is the number of protected objects use on the current node.__PO_HI_NB_PORTS
that represent the total number of ports in the whole distributed system.__PO_HI_NB_DEVICES
that represent the total number of devices in the whole distributed system.
The file deployment.c
contains the following variables :
mynode
variable which has the value of the handled node.__po_hi_entity_table
variable is used to know on which node an entity runs.__po_hi_port_global_to_local
variable is used to convert a global port identifier to a local port identifier__po_hi_port_global_to_entity
variable is used to know on which entity a given port is. This table is used convert a global port identifier to an entity identifier.__po_hi_uint8_t __po_hi_deployment_endiannesses
variable details which the endianess of each node. It is an array which size is__PO_HI_NB_NODES
.__po_hi_port_to_device
is an array which size is__PO_HI_NB_PORTS
. For each port, it indicates the value of the device identifier that handles it.__po_hi_port_global_model_names
is an array which size is__PO_HI_NB_PORTS
. For each port, it contains the name of the port.__po_hi_port_global_names
is an array which size is__PO_HI_NB_PORTS
. For each port, it contains the name generated by the code generator.__po_hi_devices_naming
is an array which size is__PO_HI_NB_DEVICES
. For each deivce, it contains all relevant information for their configuration. The configuration string is deduced from theConfiguration
property associated with the device.
The following example shows the Deployment
package relative to
the node A
of the Ping
example:
#ifndef __DEPLOYMENT_H_
#define __DEPLOYMENT_H_
#include <po_hi_protected.h>
typedef enum
{
pinger_local_data_source = 0
} __po_hi_pinger_t;
#define __po_hi_pinger_nb_ports 1
typedef enum
{
ping_me_local_data_sink = 0
} __po_hi_ping_me_t;
#define __po_hi_ping_me_nb_ports 1
/* For each node in the distributed application add an enumerator*/
typedef enum
{
node_a_k = 0,
node_b_k = 1
} __po_hi_node_t;
/* For each thread in the distributed application nodes, add an enumerator*/
typedef enum
{
node_a_pinger_k_entity = 0,
node_b_ping_me_k_entity = 1
} __po_hi_entity_t;
typedef enum
{
node_a_pinger_k = 0
} __po_hi_task_id;
#define __PO_HI_NB_TASKS 1
/* For each thread in the distributed application nodes THAT MAY COMMUNICATE*/
/* with the current node, add an enumerator*/
typedef enum
{
invalid_server = -1
} __po_hi_entity_server_t;
#define __PO_HI_NB_SERVERS 0
#define __PO_HI_NB_PROTECTED 0
#define __PO_HI_NB_NODES 2
#define __PO_HI_NB_ENTITIES 2
#define __PO_HI_NB_PORTS 2
typedef enum
{
pinger_global_data_source = 0,
ping_me_global_data_sink = 1
} __po_hi_port_t;
#endif
6.5.3.6. OS Configuration¶
A host is the set formed by a processor and an operating system (or real-time kernel).
To model both the processor and the OS, we use the processor
AADL component. The characteristics of the processor are defined using
AADL properties. For example, if our distributed application uses
an IP based network to make its node communicate, then each host must
have an IP address. Each host must also precise its platform (native,
LEON…). The listing following example shows how to express this
using a custom property set.
processor the_processor
properties
ARAO::location => "127.0.0.1";
ARAO::Execution_Platform => Native;
end the_processor;
To map an application node (processor) to a particular host, we use
the Actual_Processor_Binding
property. The following example
shows how the node Node_A
is mapped to the processor
Proc_A
in the Ping
example.
system PING
end PING;
system implementation PING.Impl
subcomponents
-- Nodes
Node_A : process A.Impl;
Node_B : process B.Impl {ARAO::port_number => 12002;};
-- Processors
CPU_A : processor the_processor;
CPU_B : processor the_processor;
connections
-- ...
properties
-- Processor bindings
actual_processor_binding => reference CPU_A applies to Node_A;
actual_processor_binding => reference CPU_B applies to Node_B;
end PING.Impl;
The C generated code concerning the code generation to model host
mapping is located in the naming.c
file. More precisely, the
node_addr
and node_port
contains, for each node, the
information related to its host. These information are dependant on
the transport mechanism used in the distributed application.
6.5.4. Mapping AADL threads
¶
The threads are the active part of the distributed application. A node must contain at least one thread and may contain more than one thread. In this section, we give the AADL entities used to model threads. Then, we give the mapping rule to generate C code corresponding to the periodic and aperiodic threads.
The rules are listed relatively to the packages generated for the nodes and for the distributed application (see @ref{Distributed application nodes} and @ref{Whole distributed application}). Only rules that are related directly to a thread as a whole subsystem are listed here.
6.5.4.1. AADL entities¶
AADL thread
components are used to model execution flows.
The features
subclause of the thread component declaration
describes the thread interface. The ports that may be connected to the
ports of other threads, enclosing process, etc.
The properties
subclause of the thread implementation lists
the properties of the thread such as its priority, its nature
(periodic, sporadic) and many other properties ares expressed using
AADL properties.
The calls
subclause of the thread implementation contains the
sequences of subprograms the thread may call during its job (see
@ref{Subprograms} for more details on the subprogram mapping). If the
thread job consist of calling more than one subprogram, it is
mandatory to encapsulate these calls inside a single subprogram
which will consist the thread job.
The connections
section of a thread implementation connects
the parameters of the subprograms called by the thread to the ports of
the threads or to the parameters of other called subprograms in the
same thread.
thread P
features
Data_Source : event out data port Simple_Type;
end P;
thread implementation P.Impl
calls {
-- ...
};
connections
-- ...
properties
Dispatch_Protocol => Periodic;
Period => 1000 Ms;
end P.Impl;
The listing above shows the thread P
which belongs to the
process A.impl
in the Ping
example. We can see that
P
is a periodic thread with a period of 1000ms, that this
thread has a unique out event data port
and that at each
period, the thread performs a call to the Do_Ping_Spg
subprogram whose out parameter
is connected to the thread port.
6.5.4.2. Mapping rules for periodic threads¶
Periodic threads are cyclic threads that are triggered by and only by a periodic time event. between two time events the periodic threads do a non blocking job and then they sleep waiting for the next time event.
The majority of the code generated for the periodic threads is put in
the activity.c
file generated for the application node
containing the handled thread. Each periodic thread is created in the
main function (main.c
file) with the
__po_hi_create_periodic_task
function-call.
The generated code in the activity.c
file is a parameterless
function that represents the thread job. The defining identifier of
the function is mapped from the thread instance name in the process
that models the node, to which we append the string
_job
. All the naming rules listed in @ref{Whole distributed
application} have to be respected. The body of this subprogram calls
the subprograms mapped from the subprogram calls the thread
performs. Then, it sends the request to the remote threads it may be
connected to. Finally, at the end of the function, we make a call to
the __po_hi_wait_next_period()
with the task identifier as
parameter. This call ensure that we wait the next period before we
start the function again.
The generated code in main.c
file is a function call that creates
a periodic task. The task is created with the function
__po_hi_create_periodic_task
. This creates a periodic task with
the wanted properties at the elaboration time of the node. The package
instantiation name is mapped from the thread instance name in the process
that model the node, to which we append the string _k
. All the
naming rules listed in @ref{Whole distributed application} have to be
respected. The function-call takes the following parameters:
- the enumerator corresponding to the thread
- the task period,
- the task priority. If the user did not specify a priority, then
__PO_HI_DEFAULT_PRIORITY
is used, - the task job which corresponds to the subprogram
<Thread_Name>_job
.
The following example shows the generated code for the periodic thread
Pinger
from the node Node_A
of the Ping
example:
#include <po_hi_types.h>
#include <po_hi_gqueue.h>
#include <request.h>
#include <deployment.h>
#include <types.h>
#include <subprograms.h>
#include <po_hi_task.h>
#include <po_hi_main.h>
#include <marshallers.h>
extern __po_hi_entity_t __po_hi_port_global_to_entity[__PO_HI_NB_PORTS];
extern __po_hi_port_t __po_hi_port_global_to_local[__PO_HI_NB_PORTS];
__po_hi_int8_t __po_hi_data_source_local_destinations[1] = {ping_me_global_data_sink};
__po_hi_uint8_t __po_hi_pinger_woffsets[__po_hi_pinger_nb_ports];
__po_hi_uint8_t __po_hi_pinger_offsets[__po_hi_pinger_nb_ports];
__po_hi_uint8_t __po_hi_pinger_used_size[__po_hi_pinger_nb_ports];
__po_hi_uint8_t __po_hi_pinger_empties[__po_hi_pinger_nb_ports];
__po_hi_uint8_t __po_hi_pinger_first[__po_hi_pinger_nb_ports];
__po_hi_uint8_t __po_hi_pinger_recent[__po_hi_pinger_nb_ports * sizeof(__po_hi_request_t)];
__po_hi_uint8_t __po_hi_pinger_queue[0 * sizeof(__po_hi_request_t)];
__po_hi_uint16_t __po_hi_pinger_total_fifo_size = 0;
__po_hi_port_t __po_hi_pinger_history[0];
__po_hi_uint8_t __po_hi_pinger_n_dest[__po_hi_pinger_nb_ports] = {1};
__po_hi_int8_t __po_hi_pinger_fifo_size[__po_hi_pinger_nb_ports] = {__PO_HI_GQUEUE_FIFO_OUT};
__po_hi_uint8_t* __po_hi_pinger_destinations[__po_hi_pinger_nb_ports] = {__po_hi_data_source_local_destinations};
/* Periodic task : Pinger*/
/****************/
/* pinger_job */
/****************/
void* pinger_job ()
{
simple_type data_source_request_var;
__po_hi_request_t data_source_request;
__po_hi_gqueue_init(node_a_pinger_k,__po_hi_pinger_nb_ports,__po_hi_pinger_queue,__po_hi_pinger_fifo_size,__po_hi_pinger_first,__po_hi_pinger_offsets,__po_hi_pinger_woffsets,__po_hi_pinger_n_dest,__po_hi_pinger_destinations,__po_hi_pinger_used_size,__po_hi_pinger_history,__po_hi_pinger_recent,__po_hi_pinger_empties,__po_hi_pinger_total_fifo_size);
__po_hi_wait_initialization();
while (1)
{
/* Call implementation*/
do_ping_spg(&(data_source_request_var));
/* Set the OUT port values*/
data_source_request.vars.pinger_global_data_source.pinger_global_data_source = data_source_request_var;
data_source_request.port = data_source_request_var;
__po_hi_gqueue_store_out(node_a_pinger_k,pinger_local_data_source,&(data_source_request));
/* Send the OUT ports*/
__po_hi_gqueue_send_output(node_a_pinger_k,pinger_global_data_source);
__po_hi_wait_for_next_period(node_a_pinger_k);
}
}
/**************************/
/* __po_hi_main_deliver */
/**************************/
void __po_hi_main_deliver
(__po_hi_msg_t* message)
{
__po_hi_request_t request;
__po_hi_entity_t entity;
__po_hi_unmarshall_request(&(request),message);
entity = __po_hi_port_global_to_entity[request.port];
switch (entity)
{
default:
{
break;
}
}
}
6.5.4.3. Mapping rules for sporadic threads¶
Sporadic threads are cyclic threads that are triggered by the arrival of a sporadic event. The minimum inter-arrival time between two sporadic events is called the period of the sporadic thread.
The majority of the code generated for the sporadic threads is put in
the activity.c
file generated for the application node
containing the handled thread. Each periodic thread is created in the
main function (main.c
file) with the
__po_hi_create_sporadic_task
function-call.
The generated code in the activity.c
file is a parameterless function
that represents the thread job. The defining identifier of the function
is mapped from the thread instance name in the process that models the node,
to which we append the string _job
. All the naming rules listed
in @ref{Whole distributed application} have to be respected. In the body
of the function, the thread will wait for an event (most of the time : a
message from another entity).
The generated code in main.c
file is a function-call that creates
the sporadic task. The task is created with the function
__po_hi_create_sporadic_task
. This creates a sporadic task with
the wanted properties at the elaboration time of the node. The package
instantiation name is mapped from the thread instance name in the process
that model the node, to which we append the string _k
. All the
naming rules listed in @ref{Whole distributed application} have to be
respected. The function-call takes the following parameters:
- the enumerator corresponding to the thread
- the task priority. If the user did not specify a priority, then
__PO_HI_DEFAULT_PRIORITY
is used, - the task job which corresponds to the subprogram
<Thread_Name>_job
.
The following example shows the generated code for the sporadic thread
Ping_Me
from the node Node_B
of the Ping
example.
#include <po_hi_gqueue.h>
#include <po_hi_types.h>
#include <request.h>
#include <deployment.h>
#include <po_hi_task.h>
#include <subprograms.h>
#include <po_hi_main.h>
#include <marshallers.h>
extern __po_hi_entity_t __po_hi_port_global_to_entity[__PO_HI_NB_PORTS];
extern __po_hi_port_t __po_hi_port_global_to_local[__PO_HI_NB_PORTS];
__po_hi_uint8_t __po_hi_ping_me_woffsets[__po_hi_ping_me_nb_ports];
__po_hi_uint8_t __po_hi_ping_me_offsets[__po_hi_ping_me_nb_ports];
__po_hi_uint8_t __po_hi_ping_me_used_size[__po_hi_ping_me_nb_ports];
__po_hi_uint8_t __po_hi_ping_me_empties[__po_hi_ping_me_nb_ports];
__po_hi_uint8_t __po_hi_ping_me_first[__po_hi_ping_me_nb_ports];
__po_hi_uint8_t __po_hi_ping_me_recent[__po_hi_ping_me_nb_ports * sizeof(__po_hi_request_t)];
__po_hi_uint8_t __po_hi_ping_me_queue[16 * sizeof(__po_hi_request_t)];
__po_hi_uint16_t __po_hi_ping_me_total_fifo_size = 16;
__po_hi_port_t __po_hi_ping_me_history[16];
__po_hi_uint8_t __po_hi_ping_me_n_dest[__po_hi_ping_me_nb_ports] = {0};
__po_hi_int8_t __po_hi_ping_me_fifo_size[__po_hi_ping_me_nb_ports] = {16};
__po_hi_uint8_t* __po_hi_ping_me_destinations[__po_hi_ping_me_nb_ports] = {NULL};
/*********************/
/* ping_me_deliver */
/*********************/
void ping_me_deliver
(__po_hi_request_t* request)
{
switch (request->port)
{
case ping_me_global_data_sink:
{
__po_hi_gqueue_store_in(node_b_ping_me_k,ping_me_local_data_sink,request);
break;
}
default:
{
break;
}
}
}
/* Sporadic task : Ping_Me*/
/* Get the IN ports values*/
/*****************/
/* ping_me_job */
/*****************/
void* ping_me_job ()
{
__po_hi_port_t port;
__po_hi_request_t data_sink_request;
__po_hi_gqueue_init(node_b_ping_me_k,__po_hi_ping_me_nb_ports,__po_hi_ping_me_queue,__po_hi_ping_me_fifo_size,__po_hi_ping_me_first,__po_hi_ping_me_offsets,__po_hi_ping_me_woffsets,__po_hi_ping_me_n_dest,__po_hi_ping_me_destinations,__po_hi_ping_me_used_size,__po_hi_ping_me_history,__po_hi_ping_me_recent,__po_hi_ping_me_empties,__po_hi_ping_me_total_fifo_size);
__po_hi_wait_initialization();
while (1)
{
__po_hi_gqueue_wait_for_incoming_event(node_b_ping_me_k,&(port));
__po_hi_compute_next_period(node_b_ping_me_k);
if (__po_hi_gqueue_get_count(node_b_ping_me_k,ping_me_local_data_sink))
{
__po_hi_gqueue_get_value(node_b_ping_me_k,ping_me_local_data_sink,&(data_sink_request));
__po_hi_gqueue_next_value(node_b_ping_me_k,ping_me_local_data_sink);
}
/* Call implementation*/
ping_spg(data_sink_request.vars.ping_me_global_data_sink.ping_me_global_data_sink);
__po_hi_wait_for_next_period(node_b_ping_me_k);
}
}
/**************************/
/* __po_hi_main_deliver */
/**************************/
void __po_hi_main_deliver
(__po_hi_msg_t* message)
{
__po_hi_request_t request;
__po_hi_entity_t entity;
__po_hi_unmarshall_request(&(request),message);
entity = __po_hi_port_global_to_entity[request.port];
switch (entity)
{
case node_b_ping_me_k_entity:
{
ping_me_deliver(&(request));
break;
}
default:
{
break;
}
}
}
6.5.5. Deployment information¶
As said in @ref{Distributed application nodes}, the files
deployment.h
and deployment.c
are generated for each
node in the distributed application. For each thread port in the whole
distributed application, we declare an enumerator in this type. The
defining identifier of the enumerator is mapped from the process
subcomponent name and the thread subcomponent name as follows:
<Node_Name>_<Thread_Name>_K
.
For each that that may communicate, we generate the following elements
- A variable called
__po_hi_<thread_name>_local_to_global
(indeployment.c
) that is used to convert a local port identifier of the thread to a global one. - A type
__po_hi_<thread_name>_t
that will contain on local port identifier. - A macro
__po_hi_<thread_name>_nb_ports
that will contain the number of ports for the thread.
For these elements, all the naming rules listed in @ref{Whole distributed application} must be respected.
#ifndef __DEPLOYMENT_H_
#define __DEPLOYMENT_H_
#include <po_hi_protected.h>
typedef enum
{
pinger_local_data_source = 0
} __po_hi_pinger_t;
#define __po_hi_pinger_nb_ports 1
typedef enum
{
ping_me_local_data_sink = 0
} __po_hi_ping_me_t;
#define __po_hi_ping_me_nb_ports 1
/* For each node in the distributed application add an enumerator*/
typedef enum
{
node_a_k = 0,
node_b_k = 1
} __po_hi_node_t;
/* For each thread in the distributed application nodes, add an enumerator*/
typedef enum
{
node_a_pinger_k_entity = 0,
node_b_ping_me_k_entity = 1
} __po_hi_entity_t;
typedef enum
{
node_a_pinger_k = 0
} __po_hi_task_id;
#define __PO_HI_NB_TASKS 1
/* For each thread in the distributed application nodes THAT MAY COMMUNICATE*/
/* with the current node, add an enumerator*/
typedef enum
{
invalid_server = -1
} __po_hi_entity_server_t;
#define __PO_HI_NB_SERVERS 0
#define __PO_HI_NB_PROTECTED 0
#define __PO_HI_NB_NODES 2
#define __PO_HI_NB_ENTITIES 2
#define __PO_HI_NB_PORTS 2
typedef enum
{
pinger_global_data_source = 0,
ping_me_global_data_sink = 1
} __po_hi_port_t;
#endif
#include <deployment.h>
__po_hi_entity_server_t server_entity_table[__PO_HI_NB_ENTITIES] = {invalid_server,invalid_server};
__po_hi_node_t entity_table[__PO_HI_NB_ENTITIES] = {node_a_k,node_b_k};
__po_hi_node_t mynode = node_a_k;
The listing above shows the generated __po_hi_entity_server_t
and
entity_table
for the nodes B
from the
Ping
example.
6.5.6. Mapping of AADL ports
¶
Threads can contain one or several ports. To handle them, we declared several
arrays in the activity.c
__po_hi_<port_name>_destinations
: array for each port of- the thread which contains all destinations of the port.
__po_hi_<thread_name>_woffsets
: array (size = number of- ports in the thread) used by pohic for the global queue of the thread.
__po_hi_<thread_name>_offsets
: array (size = number of ports in the thread) used by pohic for the global queue of the thread.__po_hi_<thread_name>_used_size
: array (size = number of ports in the thread) used by pohic for the global queue of the thread.__po_hi_<thread_name>_empties
: array (size = number of ports in the thread) used by pohic for the global queue of the thread.__po_hi_<thread_name>_first
: array (size = number of ports in the thread) used by pohic for the global queue of the thread.__po_hi_<thread_name>_recent
: array (size = number of ports in the thread) used by pohic for the global queue of the thread.__po_hi_<thread_name>_queue
: array (size = size of the global queue for the thread) used by pohic to handle the global queue.__po_hi_<thread_name>_total_fifo_size
: variable that contains the size of the global queue. It is the sum of all port size for the thread.__po_hi_<thread_name>_history
: array (size = number of ports in the thread) used by pohic for the global queue of the thread.__po_hi_<thread_name>_n_dest
: array (size = number of ports in the thread) used by pohic for the global queue of the thread. It contains the number of destinations for each port of the thread.__po_hi_<thread_name>_fifo_size
: array (size = number of ports in the thread) used by pohic for the global queue of the thread.__po_hi_<thread_name>_destinations
: array (size = number of ports in the thread) that contains all destinations for each port.
6.5.7. Mapping of AADL Connections
¶
The connections are entities that support communication between the application nodes. In this section, we present the AADL entities used to model connection between nodes.
A connection between two nodes of the system is modeled by:
- The
ports
features that exist on each one of the nodes. Ports can be declared inside processes or threads. The direction of the port (in
,out
orin out
) indicates the direction of the information flow. - The
connections
section in the system implementation relative to the distributed application and in the process and thread implementations.
system PING
end PING;
system implementation PING.Impl
subcomponents
-- Nodes
Node_A : process A.Impl;
Node_B : process B.Impl {ARAO::port_number => 12002;};
-- Processors
CPU_A : processor the_processor;
CPU_B : processor the_processor;
connections
-- Port connections
event data port Node_A.Out_Port -> Node_B.In_Port;
properties
-- Processor bindings
actual_processor_binding => reference CPU_A applies to Node_A;
actual_processor_binding => reference CPU_B applies to Node_B;
end PING.Impl;
The listing above shows the connection between the node A
and
B
in the system implementation.
The nature of the port
(event port, data port or event
data port) depends on the nature of the connection between the two
nodes:
- if the message sent from one node to another node is only a triggering event and contains no data, we create an event port.
- if the message sent from one node to another node is a data message but it does not trigger the receiver thread, we create a data port.
- if the message sent from one node to another node is a data message that triggers the receiver thread, we create an @i{event data} port.
In a distributed system, when we send any data to a node, we need to put them in a stream. We call that the marshall operation. On the other hand, find data in a stream is called the unmarshall operation. In each distributed application, we generate marshallers for each types and request. These functions will marshall/unmarshall data in/from a message.
All marshallers functions are generated in a file called
marshallers.c
. The marshall (or unmarshall) functions for
request are prefixed by the string __po_hi_marshall_request_
(or __po_hi_unmarshall_request_
). Marshall (or unmarshall)
functions for types are prefixed by the string
__po_hi_marshall_type_
(or
__po_hi_unmarshall_type_
). Each function has the name of the
type or the request it marshalls.
Finally, a function __po_hi_marshall_request
and
__po_hi_unmarshall_request
is generated to handle all
requests. Then, is called the appropriate function to call to marshall
or unmarshall the data.
#include <types.h>
#include <po_hi_types.h>
#include <po_hi_marshallers.h>
/***************************************/
/* __po_hi_marshall_type_simple_type */
/***************************************/
void __po_hi_marshall_type_simple_type
(simple_type value,
__po_hi_msg_t* message,
__po_hi_uint16_t* offset)
{
__po_hi_marshall_int(value,message,offset);
}
/*****************************************/
/* __po_hi_unmarshall_type_simple_type */
/*****************************************/
void __po_hi_unmarshall_type_simple_type
(simple_type* value,
__po_hi_msg_t* message,
__po_hi_uint16_t* offset)
{
__po_hi_unmarshall_int(value,message,offset);
}
/************************************************/
/* __po_hi_marshall_request_ping_me_data_sink */
/************************************************/
void __po_hi_marshall_request_ping_me_data_sink
(__po_hi_request_t* request,
__po_hi_msg_t* message,
__po_hi_uint16_t* offset)
{
__po_hi_marshall_type_simple_type(request->vars.ping_me_global_data_sink.ping_me_global_data_sink,message,offset);
}
/**************************************************/
/* __po_hi_unmarshall_request_ping_me_data_sink */
/**************************************************/
void __po_hi_unmarshall_request_ping_me_data_sink
(__po_hi_request_t* request,
__po_hi_msg_t* message,
__po_hi_uint16_t* offset)
{
__po_hi_unmarshall_type_simple_type(&(request->vars.ping_me_global_data_sink.ping_me_global_data_sink),message,offset);
}
/*************************************************/
/* __po_hi_marshall_request_pinger_data_source */
/*************************************************/
void __po_hi_marshall_request_pinger_data_source
(__po_hi_request_t* request,
__po_hi_msg_t* message,
__po_hi_uint16_t* offset)
{
__po_hi_marshall_type_simple_type(request->vars.pinger_global_data_source.pinger_global_data_source,message,offset);
}
/***************************************************/
/* __po_hi_unmarshall_request_pinger_data_source */
/***************************************************/
void __po_hi_unmarshall_request_pinger_data_source
(__po_hi_request_t* request,
__po_hi_msg_t* message,
__po_hi_uint16_t* offset)
{
__po_hi_unmarshall_type_simple_type(&(request->vars.pinger_global_data_source.pinger_global_data_source),message,offset);
}
/******************************/
/* __po_hi_marshall_request */
/******************************/
void __po_hi_marshall_request
(__po_hi_request_t* request,
__po_hi_msg_t* message)
{
__po_hi_uint16_t offset;
offset = 0;
__po_hi_marshall_port(request->port,message);
switch (request->port)
{
case ping_me_global_data_sink:
{
__po_hi_marshall_request_ping_me_data_sink(request,message,&(offset));
break;
}
case pinger_global_data_source:
{
__po_hi_marshall_request_pinger_data_source(request,message,&(offset));
break;
}
default:
{
break;
}
}
}
/********************************/
/* __po_hi_unmarshall_request */
/********************************/
void __po_hi_unmarshall_request
(__po_hi_request_t* request,
__po_hi_msg_t* message)
{
__po_hi_uint16_t offset;
offset = 0;
__po_hi_unmarshall_port(&(request->port),message);
switch (request->port)
{
case ping_me_global_data_sink:
{
__po_hi_unmarshall_request_ping_me_data_sink(request,message,&(offset));
break;
}
case pinger_global_data_source:
{
__po_hi_unmarshall_request_pinger_data_source(request,message,&(offset));
break;
}
default:
{
break;
}
}
}
6.5.8. Mapping of AADL Subprograms
¶
Subprograms are used to encapsulate behavioural aspects of the application.
To model a subprogram, we use the subprogram
AADL component
category. The parameters of the subprogram are specified in the
features
subclausen of the component declaration. If the
subprogram does only the job of calling other declared subprograms,
then the calls
subclause of the subprogram implementation has
to contain such calls. To point to the actual implementation of the
subprogram, we use the AADL properties.
The following example shows the AADL model for the Do_Ping_Spg
from the Ping
example. It precises that the C implementation of
the subprogram is located in the function user_ping
. The file
which contains this function must be stored with the aadl model.
Subprograms are generally called by threads or by other subprograms.
To express this, we use the calls
subclause of a component
implementation. Then we perform all the connections between the called
subprograms parameters and the caller components ports (or parameters
if the caller is a subprogram).
The following listing shows the calls and connections sections of the
periodic thread P
in the Ping
example.
subprogram Do_Ping_Spg
features
Data_Source : out parameter Simple_Type;
properties
source_language => C;
source_name => "user_ping";
end Do_Ping_Spg;
Each subprogram instance model a hand-written function. In the
subprograms.c
file, we declare the definition of this function
and we generate a new one that will call the one provided by the user.
The following listing shows the calls and connections sections of the
subprogram ping_spg
in the Ping
example.
#include <types.h>
#include <subprograms.h>
void user_do_ping_spg
(simple_type* data_source);
/*****************/
/* do_ping_spg */
/*****************/
void do_ping_spg
(simple_type* data_source)
{
user_do_ping_spg(data_source);
}
For each subprogram call in a thread, we generate an C subprogram call to the subprogram implementing the thread and given by mean of the AADL properties.
On the client side, the function sth_Job
begins by calling the
subprogram in its call sequence. then it calls the stubs of all the
subprogram it is connected to.
On the server side, and in the function of the
process_request
, the subprogram implementation corresponding
to the operation (coded in the message) is called.
6.5.9. Mapping of AADL data
¶
The data are the messages exchanged amongst the nodes of the application.
AADL data
components are used to model data exchanged in the
distributed application. Properties are used to precise the nature of
the data. To model a data structure (which contains fields of others
data types) we use data component implementation and we add a
subcomponent for each field of the structure.
The simple data types that can be modeled using AADL are
- boolean,
- integer,
- fixed point types,
- characters,
- wide characters
-- Boolean type
data Boolean_Data
properties
ARAO::Data_Type => Boolean;
end Boolean_Data;
-- Integer type
data Integer_Data
properties
ARAO::Data_Type => Integer;
end Integer_Data;
-- Fixed point type
data Fixed_Point_Type
properties
ARAO::Data_Type => Fixed;
ARAO::Data_Digits => 10;
-- The total number of digits is 10
ARAO::Data_Scale => 4;
-- The precision is 10**(-4)
end Fixed_Point_Type;
-- Character type
data Character_Data
properties
ARAO::Data_Type => Character;
end Character_Data;
-- Wide character type
data W_Character_Data
properties
ARAO::Data_Type => Wide_Character;
end W_Character_Data;
The complex data types that can be modeled using AADL are
- Bounded strings
- Bounded wide strings
- Bounded arrays of a type that can be modeled
- Structure where the fields types are types that can be modeled
-- Bounded string type
data String_Data
properties
ARAO::Data_Type => String;
ARAO::Max_Length => <User_Defined_Length>;
end String_Data;
-- Bounded wide string type
data W_String_Data
properties
ARAO::Data_Type => Wide_String;
ARAO::Max_Length => <User_Defined_Length>;
end W_String_Data;
-- Bounded array type: Only the component implementation should be
-- used in the ports or parameters!
data Data_Array
properties
ARAO::Length => <User_Defined_Length>;
end Data_Array;
data implementation Data_Array.i;
subcomponents
-- Only one subcomponent
Element : data String_Data;
end Data_Array.i;
-- Data structure type: Only the component implementation should be
-- used in the ports or parameters!
data Data_Structure
end Data_Structure;
data implementation Data_Structure.i;
subcomponents
Component_1 : data String_Data;
Component_2 : data W_String_Data;
Component_3 : data Data_Array.i;
end Data_Structure.i;
Data components may also contain subprogram features. Depending on the AADL properties given by the user. These component may denote a protected object or a non protected object. In either case, they are used to model a data structure that can be handled only by the subprograms it exports (which are the feature of the data structure).
@include protected_object_types.texi
The example above shows an example of a protected data component (@code{Protected_Object.Impl}). The object has a single field (subcomponent) which is a simple data component. Note that the description of the feature subprograms of these data component is a little bit different from the description of classic subprograms: each feature subprogram must have a full access to the internal structure of the object type. To achieve this, we use the @code{require data access} facility of AADL. To model a non protected data component, user should simply change the @code{ARAO::Object_Kind => Protected;} into @code{ARAO::Object_Kind => Non_Protected;} in the implementation of data component.
@subsection C mapping rules
Data component declaration are mapped into C type declaration in the file @code{types.h}. In the following we give the C type corresponding to each data component type that could be modeled.
@subsubsection Simple types
Simple data components are mapped into an C type definition whose defining identifier is mapped from the component declaration identifier (with respect to the naming rules listed in @ref{Whole distributed application}) and whose parent subtypes is: @itemize @bullet
@item @code{int} for boolean data types @item @code{int} for integer data types @item @code{float} for fixed point types @item @code{chat} for character data types
@end itemize
@subsubsection Bounded strings and wide strings
Bounded strings and wide strings are not supported in the C generator at this time.
@subsubsection Bounded arrays
Bounded arrays and wide strings are not supported in the C generator at this time.
@subsubsection Data structures
Data structures are mapped into a C structure defined in the file @file{types.h}. The identifier of the record type is mapped from the data component name with respect to the naming rules given in @ref{Whole distributed application}. Each field defining identifier is mapped from the subcomponent name given in the data component implementation with the same naming rules. The type of the field is the C type mapped from the data corresponding component. The following example shows the C mapping of the data structure defined given earlier in this part.
@include data_struct.h.texi
@subsubsection Object types
Protected object types are mapped into an a C structure. We add automatically a member in the structure with the type @code{__po_hi_protected_id} and the name @code{protected_id}. This member will identify the protected type in the distributed system. All other members of the object are declared as in Data Structures (see previous subsection). The features subprograms of the object types are declared in the @file{types.h} file, whereas the body of these functions are defined in the @file{types.c} file. Moreover, the value of the @code{protected_id} must be initialized. This is done in the main function (@file{main.c}), before the initialization. All the naming conventions given in @ref{Whole distributed application} have to be respected. The following example shows the specification of the protected type mapped from the @code{Protected_Object.Impl} shown earlier in this part. We show the files @file{types.h}, @file{types.c} and @file{main.c} (that initialize the @code{protected_id} member of the structure.
@include toy_types.h.texi @include toy_types.c.texi @include toy_main.c.texi
Non protected object types are mapped similarly to protected object types. The only difference, is that instead of creating a protected type, we create a generic parameterless nested package.