Table of Contents Previous Chapter 9 Building an Application

9 Building an Application

This chapter describes how you can use the C Code Generator in SDT to generate applications and especially how to design the environment functions. These functions are the place where you connect the SDL system and the environment of the system. You should read chapter 34, The C Code Generator before reading this chapter to understand the general behavior of the C Code Generator. Much of what you need to know to generate an application may be found there, and that information is not repeated here.

Introduction

The Basic Idea

An application generated with the C Code Generator can be viewed as having three parts:

  1. The SDL system
  2. The physical environment of the system
  3. The environment functions, where you connect the SDL system with the environment of the system.
In the SDL system process transitions are executed in priority order, signals are sent from one process to another initiating new transitions, timer signals are sent, and so on. These are examples of internal actions that only affect the execution of the SDL system. An SDL system communicates with its environment by sending signals to the environment and by receiving signals from the environment.

The physical environment of an application consists of an operating system, a file system, the hardware, a network of computers, and so on. In this world other actions than just signal sending are required. Examples of actions that an application wants to perform are:

The environment functions are the place where the two worlds, the SDL system and the physical environment, meet. Here signals sent from the SDL system to the environment can induce all kind of events in the physical environment, and events in the environment might cause signals to be sent into the SDL system. You have to provide the environment functions, as the C Code Generator has no knowledge of the actions that should be performed.

Figure 185 : Structure of an Application. 
-----
(fig)  
       
-----
In a distributed system an application might consist of several communicating SDL systems. Each SDL system will become one executable program. It might execute either as an operating system process, communicating with other operating system processes, or it might execute in a processor of its own, communicating over a network with other processors. There may, of course, also be combinations of these cases. Let us for the sake of simplicity call the operating system processes or processors nodes communicating over a network. In the case of communicating operating system (OS) processes, the network will be the facilities for process communication provided by the OS.

There are no problems in building an application consisting of several nodes communicating over a network using the C Code Generator. However, you have to implement the communication between the nodes in the environment functions.

-------------------------------------------------------------------
Note:                                                                
All nodes in a network do not need to be programs generated by the   
C Code Generator from SDL systems. As long as a node can com         
municate with other nodes, it might be implemented using any tech    
nique.                                                               
-------------------------------------------------------------------
The PId values (references to process instances), are a problem in a distributed world containing several communicating SDL systems. We still want, for example, "Output To Sender" to work, even if Sender refers to a process instance in another SDL system. To cope with this kind of problem, a global node number has been introduced as a component in a PId value. The global node number, which is a unique integer value assigned to each node, identifies the node where the current process instance resides, while the other component in the PId value is a local identification of the process instance within the node (SDL system).

The partitioning of an application into an SDL system and the environment functions has additional advantages. It separates external actions into the logical decision to perform the action (the decision to send a signal to the environment) and the implementation details of the action (treating the signal in the environment functions). This kind of separation reduces the complexity of the problem and allows separate testing. It also allows parallel development of the logic (the SDL system) and the interface towards the environment (the environment functions). When the signal interface between the SDL system and its environment is settled, it is possible to continue both the activities in parallel.

Libraries

Two libraries, Application and ApplicationDebug, are provided to generate applications. Both use real time (see "Time" on page 1966 in chapter 34, The C Code Generator and perform calls to environment functions (see section "The Environment Functions" on page 453). The difference is that ApplicationDebug includes the monitor system while Application does not include the monitor system.

When an application is developed, it is usually appropriate to first simulate and debug the SDL system or systems without its environment. One of the libraries Simulation or RealTimeSimulation may then be used. You first simulate each SDL system on its own and can then simulate the systems together (if you have communicating systems) using the facility of communicating simulations. After that you probably want to debug the application with the environment functions. This may be performed with the library ApplicationDebug. You may then generate the application with the library Application.

The library Validation allows you to build validators from the code generated by the C Code Generator. A Validator has a user interface and executing principles that are similar to a Simulator. The technical approach is however different; a Validator is based on a technique called state space exploration and operates on structures called behavior trees. Its purpose is to validate an SDL system in order to find errors and to verify its consistency against Message Sequence Charts.

Conformance with SDT 2.3

For information about necessary changes when moving from SDT 2.3 to SDT 3.01, please see "Incompatibilities with SDT 2.3" on page 1965 in chapter 34, The C Code Generator.

Reference Section

Representation of Signals and Processes

In this first part the representation of signals and processes is presented. The symbol table, which is a representation of the static structure of the system, will also be discussed. The information given here will be used in the next part of this section where the environment functions, which should be provided by the user, are described.

Types Representing Signals

A signal is represented by a C struct containing general information about the signal followed by the parameters carried by the signal.

Figure 186 : Data Structure Representing a Signal 
-----
(fig)  
       
-----
A general typedef xSignalRec for a signal without parameters and for a pointer to such a signal, xSignalNode, are given below. These types may be found in the file scttypes.h. These types may be used for type casting of any signal to access the general components.

typedef struct xSignalRec *xSignalNode;
typedef struct xSignalRec {
  xSignalNode Pre;
  xSignalNode Suc;
  SDL_PId     Receiver;
  SDL_PId     Sender;
  xIdNode     NameNode;
  int         Prio;
} xSignalRec;
A xSignalRec contains the following components:

In the generated code there will be types to represent the parameters of the signals according to the following example:

Example 14 : Generated C Code for Signal Definition  
Assume the following signal definitions in SDL:

SIGNAL
 S1(Integer),
 S2,
 S3(Integer, Boolean, OwnType);
then the C code below will be generated:

typedef struct {
  SIGNAL_VARS
  SDL_Integer Param1;
} yPDef_z0f_S1;
typedef yPDef_z0f_S1 *yPDP_z0f_S1;
typedef struct {
  SIGNAL_VARS
  SDL_Integer Param1;
  SDL_Boolean Param2;
  z09_OwnType Param3;
} yPDef_z0h_S3;
typedef yPDef_z0h_S3 *yPDP_z0h_S3;
where SIGNAL_VARS is a macro defined in scttypes.h that is expanded to the common components in a signal struct.
  
For each signal with parameters there are two generated types, a struct type and a pointer type. The struct type contains one component for each parameter in the signal definition and the components will be named Param1, Param2 and so on. The components will be placed in the same order in the struct as the parameters are placed in the signal definition.

-------------------------------------------------------------
Note:                                                          
There are no generated types for a signal without parameters.  
-------------------------------------------------------------

Types Representing Processes

A PId value is a struct consisting of two components, a global node number, which is an integer (see also section "Function xGlobalNodeNumber" on page 466 and section "The Basic Idea" on page 445) and a local PId value, which is a pointer.

typedef xLocalPIdRec *xLocalPIdNode;
typedef struct {
  int GlobalNodeNr;
  xLocalPIdNode LocalPId;
} SDL_PId;
The global node number identifies the SDL system that the process instance belongs to, while the local PId value identifies the process instance within the system. The local PId pointer value should not be referenced outside the SDL system where it is defined.

By introducing a global node number in the PId values, these values are possible to interpret throughout an application consisting of several SDL systems. You can also define your own PId values in non-SDL defined parts of the application and still use communication with signals.

The variable SDL_NULL, which represents a null value for PIds and which is defined in the runtime library and available through the file scttypes.h, contains zero in both the global node number and the local PId component. Note that the global node number should be greater than zero in all PId values except SDL_NULL.

The Symbol Table

The symbol table is a tree built up during the initialization phase in the execution of the generated program and contains information about the static structure of the SDL system. The symbol table contains, for example, nodes which represent signal types, blocks, channels, process types, and procedures. The C type that are used to represent for example signals in the symbol table is given below.

typedef struct xSignalIdStruct *xSignalIdNode;
typedef struct xSignalIdStruct {
 /* components */
} xSignalIdRec;
It is the nodes that represent the signal types for signals sent to and from the environment of the SDL system, that are of major interest in connection with the environment functions. For each signal type there will be a symbol table node. That node may be referenced using the name ySigN_ followed by the signal name with prefix. Such references may be used in, for example, xOutEnv to find the signal type of the signal passed as parameter.

In some cases the symbol table nodes for channels from the environment to a block in the system are of interest to refer to. In a similar way as for signals such nodes may be referenced using the name yChaN_ followed by the channel name with prefix.

The Environment Functions

An SDL system communicates with its environment by sending signals to the environment and by receiving signals from the environment. As no information about the environment is given in the SDL system, the C Code Generator cannot generate the actions that should be performed when, for instance, a signal is sent to the environment. Instead you have to provide a function that performs this mapping between a signal sent to the environment and the actions that then should be performed. Examples of such actions are writing a bit pattern on a port, sending information over a network to another computer and sending information to another OS process using some OS primitive.

You should provide the following functions to represent the environment of the SDL system:

These functions are thoroughly discussed below, but first we will introduce the system interface header file which considerably simplifies writing the environment functions.

--------------------------------------------------------------------
Note:                                                                 
You may find guidelines for the environment functions in the file:    
$sdtdir/INCLUDE/sctenv.c                                              
Please make a copy of the file and use it to build your environment   
functions. This file also contains some trace mechanisms that may     
be used to trace the execution in a target computer.                  
This trace can, however, only be used if you have the source code     
for the run-time library (included in the C-Advanced Code Genera      
tor) and can produce a new object library with the appropriate        
switches.                                                             
--------------------------------------------------------------------

System Interface Header File

It is possible to generate a header file (.h file) which is suitable to use when implementing the environment functions. This file is generated if:

The system interface header file (also known as environment header file), which will have the name system_file_name.ifc, will contain all type definitions and other extern definitions that are necessary to implement the environment functions in a file that may be compiled on its own.

The system interface header file, which only contains code for objects defined in the system diagram, has the following structure:

Together with these definitions, macros that simplify the translation of SDL names to C names will also be generated. Normally there are problems when the name used in C code for an SDL object is to be determined, as prefixing is used to make the C identifiers unique (see section "Names and Prefixes in Generated Code" on page 2034 in chapter 34, The C Code Generator. In the system interface, header file macros are introduced to make the SDL names directly available in C.

Example 15 : Contents of an .ifc File  
If an SDL signal called Sig1 is defined in the system, then the .ifc file will contain:

extern XCONST struct xSignalIdStruct ySigR_z5_Sig1;
#ifndef Sig1
#define Sig1 (&ySigR_z5_Sig1)
#endif
  
This means that the xIdNode for the signal may be referred to using the signal name directly, and not only by a name containing a prefix that cannot be predicted in advance and that may change due to changes in the system diagram.

The strategy used means that you have to regenerate the system interface header file each time you regenerate code for the system, but also that the code in the environment functions need not to be changed (only recompiled).

The following SDL names will in the same way be available directly in C:

Example 16 : Contents of an .ifc File  
Suppose the following objects are defined in a system diagram:

synonym syn1 integer = 10;
synonym syn2 struct1 = (. 1, 2 .);
synonym syn3 boolean = external;
newtype sort1
  literals lit1, lit2, lit3;
endnewtype;
newtype struct1 struct
  a,b integer;
endnewtype;
signal
  sig1,
  sig2(integer),
  sig3(sort1, struct1);
channel chan1 ... endchannel;
channel chan2 ... endchannel;
Then the following system interface header file will be generated (assuming some appropriate prefixes):

/* ******** DECLARATIONS IN UNIT minimal ******** */
/*------------------ SYNONYMS -------------------*/
#define z8_syn1 SDL_INTEGER_LIT(10)
#define syn1 SDL_INTEGER_LIT(10)
/*-------------------- SORTS --------------------*/
typedef enum {z60_lit1, z61_lit2, z62_lit3
}  z6_sort1;
#define lit1  z60_lit1
#define lit2  z61_lit2
#define lit3  z62_lit3
#define sort1  z6_sort1
typedef struct z7_struct1_s {
    SDL_Integer  a;
    SDL_Integer  b;
} z7_struct1;
#define struct1  z7_struct1
/*------------------ SYNONYMS -------------------*/
#ifndef syn3
extern SDL_Boolean  syn3;
#endif
extern z7_struct1  z9_syn2;
#define syn2  z9_syn2
/*------------------- SIGNALS -------------------*/
/* sig1 IN */
extern XCONST struct xSignalIdStruct ySigR_z3_sig1;
#ifndef sig1
#define sig1 (&ySigR_z3_sig1)
#endif
/* sig2 IN */
typedef struct {
    SIGNAL_VARS
    SDL_Integer  Param1;
} yPDef_z4_sig2;
typedef yPDef_z4_sig2  *yPDP_z4_sig2;
#define yPDP_sig2  yPDP_z4_sig2
extern XCONST struct xSignalIdStruct ySigR_z4_sig2;
#ifndef sig2
#define sig2 (&ySigR_z4_sig2)
#endif
/* sig3 OUT */
typedef struct {
    SIGNAL_VARS
    z6_sort1  Param1;
    z7_struct1  Param2;
} yPDef_z5_sig3;
typedef yPDef_z5_sig3  *yPDP_z5_sig3;
#define yPDP_sig3  yPDP_z5_sig3
extern XCONST struct xSignalIdStruct ySigR_z5_sig3;
#ifndef sig3
#define sig3 (&ySigR_z5_sig3)
#endif
/*------------------ CHANNELS -------------------*/
#ifndef XOPTCHAN
extern struct xChannelIdStruct yChaR_z1_chan1;
#define chan1 (&yChaR_z1_chan1)
#endif
#ifndef XOPTCHAN
extern struct xChannelIdStruct yChaR_z2_chan2;
#define chan2 (&yChaR_z2_chan2)
#endif
  

Structure of File for Environment Functions

The file containing the environment functions should have the following structure:

#include "scttypes.h"
#include "file with macros for external synonyms"
#include "systemfilename.ifc"
void xInitEnv XPP((void))
{
}
void xCloseEnv XPP((void))
{
}
#ifndef XNOPROTO
void xOutEnv (xSignalNode *S)
#else
void xOutEnv (S)
  xSignalNode *S;
#endif
{
}
#ifndef XNOPROTO
void xInEnv (SDL_Time Time_for_next_event)
#else
void xInEnv (Time_for_next_event)
  SDL_Time Time_for_next_event;
#endif
{
}
int xGlobalNodeNumber XPP((void))
{
}
The last function, xGlobalNodeNumber, will be discussed later, see page 466. The usage of the macros XPP and XNOPROTO makes the code possible to compile both with compilers that can handle prototypes and with compilers that cannot. If you do not need this portability, you can reduce the complexity of the function headings somewhat. In the minor examples in the remaining part of this section only versions with prototypes are shown.

Functions xInitEnv and xCloseEnv

There are two functions among the environment functions that handle initialization and termination of the environment. These functions, as well as the other environment functions, should be provided by the user.

void xInitEnv ( void );
void xCloseEnv ( void );
In the implementation of these functions you can place the appropriate code needed to initialize and terminate the software and the hardware. The function xInitEnv will be called during the start up of the program as first action, while the xCloseEnv will be called in the function SDL_Halt. Calling SDL_Halt is the appropriate way to terminate the program. The easiest way to call SDL_Halt is to include the call in a #CODE directive in a TASK. SDL_Halt is part of the runtime library and has the following definition:

void SDL_Halt ( void );
----------------------------------------------------------------------
Note:                                                                   
xInitEnv will be called before the SDL system is initialized, which     
means that no references to the SDL system are allowed in this func     
tion. To, for example, send signals into the system during the initial  
ization phase, the #MAIN directive should be used (see "Initializa      
tion - Directive #MAIN" on page 2040 in chapter 34, The C Code          
Generator). Use of this directive code can be placed after the initial  
ization of the SDL system, but before any transitions are executed.     
----------------------------------------------------------------------

Function xOutEnv

Each time a signal is sent from the SDL system to the environment of the system, the function xOutEnv will be called.

void xOutEnv ( xSignalNode *S )
The xOutEnv function will have the current signal as parameter, so you have all the information contained in the signal at your disposal when you implement the actions that should be performed. The signal contains the signal type, the sending and receiving process instance and the parameters of the signal. For more information about the types used to represent signals and processes, see section "Types Representing Signals" on page 449 and "Types Representing Processes" on page 451.

Note that the parameter of xOutEnv is an address to xSignalNode, that is, an address to a pointer to a struct representing the signal. The reason for this is that the signal that is given as parameter to xOutEnv should be returned to the pool of available memory before return is made from the xOutEnv function. This is made by calling the function xReleaseSignal, which takes an address to an xSignalNode as parameter, returns the signal to the pool of available memory, and assigns 0 to the xSignalNode parameter. Thus, there should be a call

xReleaseSignal(S);
before returning from xOutEnv. The xReleaseSignal function is defined as follows:

void xReleaseSignal ( xSignalNode *S );
In the function xOutEnv you may use the information in the signal that is passed as parameters to the function. First it is usually suitable to determine the signal type of the signal. This is best performed by if statements containing expressions of the following form, assuming the use of the system interface header file and that the signal has the name Sig1 in SDL:

(*S)->NameNode == Sig1
Suitable expressions to reach the Receiver, the Sender, and the signal parameters are:

(*S)->Receiver
(*S)->Sender
((yPDP_Sig1)(*S)) -> Param1
((yPDP_Sig1)(*S)) -> Param2
(and so on)

Sender will always refer to the sending process instance, while Receiver is either a reference to a process in the environment or the value xEnv. xEnv is a PId value that refers to an environment process instance, which is used to represent the general concept of environment, without specifying an explicit process instance in the environment.

-----------------------------------------------------------------------
Note:                                                                    
It is not possible to calculate the PId value for a process in the envi  
ronment, the value has to be taken from an incoming signal (sender       
or signal parameter). This is the normal procedure in SDL to estab       
lish direct communication between two processes in the same SDL          
system.                                                                  
-----------------------------------------------------------------------
Receiver will refer to the process xEnv if the PId expression in an output TO refers to xEnv, or if the signal was sent in an output without a TO clause and the environment was the only possible receiver found in the scan for receivers.

Recommended Structure of the xOutEnv Function

You can of course write the xOutEnv function as you wish and the structure discussed below may be seen as an example, but also as a guideline of how to design xOutEnv functions.

Example 17 : Structure ox xOutEnv Function  
void xOutEnv ( xSignalNode *S )
{
 if ( (*S)->NameNode == Sig1 ) {
  /* perform appropriate actions */
  xReleaseSignal(S);
  return;
 }
 if ( (*S)->NameNode == Sig2 ){
  /* perform appropriate actions */
  xReleaseSignal(S);
  return;
 }
 /* and so on */
}
  

Function xInEnv

To make it possible to receive signals from the environment and to send them into the SDL system, the user provided function xInEnv is repeatedly called during the execution of the system (see section "Program Structure" on page 467). During such a call you should scan the environment to see if anything has occurred which should trigger a signal to be sent to a process within the SDL system.

void xInEnv (SDL_Time Time_for_next_event)
To implement the sending of a signal into the SDL system, two functions are available: xGetSignal, which is used to obtain a data area suitable to represent the signal, and SDL_Output, which sends the signal to the specified receiver according to the semantic rules of SDL. These functions will be described later in this subsection.

The parameter Time_for_next_event will contain the time for the next event scheduled in the SDL system. The parameter will either be 0, which indicates that there is a transition (or a timer output) that can be executed immediately, or be greater than Now, indicating that the next event is a timer output scheduled at the specified time, or be a very large number, indicating that there is no scheduled action in the system, that is, the system is waiting for an external stimuli.

You should scan the environment, perform the current outputs, and return as fast as possible if Time has past Time_for_next_event.

If Time has not past Time_for_next_event, you have a choice to either return from the xInEnv function at once and have repeated calls of xInEnv, or stay in the xInEnv until something triggers a signal output (a signal sent to the SDL system) or until Time has past Time_for_next_event.

------------------------------------------------------------------------
Note:                                                                     
We recommend always to return from the xInEnv function as fast            
as possible to ensure that it will work appropriately together with the   
monitor (during debugging). Otherwise, the keyboard polling, that         
is, typing <RETURN> in order to interrupt the execution, will not   
work.                                                                     
------------------------------------------------------------------------
The function xGetSignal, which is one of the service functions suitable to use when a signal should be sent, returns a pointer to a data area that represents a signal instance of the type specified by the first parameter.

xSignalNode xGetSignal
 ( xSignalIdNode SType,
   SDL_PId Receiver,
   SDL_PId Sender );
The components Receiver and Sender in the signal instance will also be given the values of the corresponding parameters.

The function SDL_Output takes a reference to a signal instance and outputs the signal according to the rules of SDL.

void SDL_Output
 ( xSignalNode S,
   xIdNode     ViaList[] );
We now have enough information to be able to write the code to send a signal. Suppose we want to send a signal S1, without parameters, from xEnv into the system without an explicit receiver (without TO). The code will then be:

Example 18 : C Code to Send a Signal to the Environment  
SDL_Output( xGetSignal(S1, xNotDefPId, xEnv),
  (xIdNode *)0 );
If S2, with two integer parameters, should be sent from xEnv to the process instance referenced by the variable P, the code will be:

xSignalNode OutputSignal; /* local variable */
...
OutputSignal = xGetSignal(S2, P, xEnv);
((yPDP_S2)OutputSignal)->Param1 = 1;
((yPDP_S2)OutputSignal)->Param2 = 2;
SDL_Output( OutputSignal, (xIdNode *)0 );
  
For the details of how to reference the parameters of a signal see the subsection "Types Representing Signals" on page 449.

To introduce a via list in the output requires a variable, which should be an array of xIdNode, that contains references to the symbol table nodes representing the current channels (or signal routes) in the via list. In more detail, we need a variable

ViaList xIdNode[N];
where N should be replaced by the length of the longest via list we want to represent plus one. The components in the variable should then be given appropriate values, such that component 0 is a reference to the first channel (its symbol table node) in the via list, component 1 is a reference to the second channel, and so on. The last component with a reference to a channel must be followed by a component containing a null pointer (the value (xIdNode)0). Components after the null pointer will not be referenced. Below is an example of how to create a via list of two channels, C1 and C2.

Example 19 : Via List of two Channels.  
ViaList xIdNode[4]; 
/* longest via has length 3 */
...
/* this via has length 2 */
ViaList[0] = (xIdNode)C1; 
ViaList[1] = (xIdNode)C2;
ViaList[2] = (xIdNode)0;
  
The variable ViaList may then be used as a ViaList parameter in a subsequent call to SDL_Output.

Guidelines for the xInEnv Function

It is more difficult to give a structure for the xInEnv function, than for the xOutEnv function discussed in the previous subsection. A xInEnv function will in principle consist of a number of if statements where the environment is investigated. When some information is found that means that a signal is to be sent to the SDL system, then the appropriate code to send a signal (see above) should be executed.

The structure given in the example below may serve as an idea of how to design the xInEnv function.

Example 20 : Structure of xInEnv Function  
void xInEnv (SDL_Time Time_for_next_event)
{
  xSignalNode S;
  if ( Sig1 should be sent to the system ) {
    SDL_Output (xGetSignal(Sig1, xNotDefPId,
      xEnv), (xIdNode *)0);
  }
  if ( Sig2 should be sent to the system ) {
    S = xGetSignal(Sig1, xNotDefPId, xEnv);
    ((xPDP_Sig2)S)->Param1 = 3;
    ((xPDP_Sig2)S)->Param2 = SDL_True;
    SDL_Output (S, (xIdNode *)0);
  }
  /* and so on */
}
  
This basic structure can be modified to suit your own needs. The if statements could, for example, be substituted for while statements. The signal types might be sorted in some "priority order" and a return can be introduced last in the if statements. This means that only one signal is sent during a xInEnv call, which reduces the latency.

Alternative to OutEnv - Directive #EXTSIG

To speed up an application it is sometimes possible to use the directive #EXTSIG instead of the xOutEnv function. The decision to use #EXTSIG or xOutEnv may be taken individually for each signal type.

The usage of the #EXTSIG directive is described in chapter 34, The C Code Generator in the section "Modifying Outputs - Directive #EXTSIG, #ALT, #TRANSFER" on page 2040. This information is not repeated here.

By using the #EXTSIG directive the following overhead can be avoided:

Including the Environment Functions in the SDL System Design

Apart from having the environment functions on a file of their own, it is of course possible to include these function directly into the system diagram in a #CODE directive.

Example 21 : Including Environment Functions in SDL System  
/*#CODE
#BODY
... code for the environment functions ...
*/
  
In this case you cannot use the system interface header file, but instead you have all the necessary declarations already at your disposal, as the functions will be part of the SDL system. The only problem you will encounter is the prefixing of SDL names when they are translated to C. The #SDL directive should be used to handle this problem (or the #NAME directive), see chapter 34, The C Code Generator, sections "Accessing SDL Names in C Code - Directive #SDL" on page 2027 and "Specifying Names in Generated Code - Directive #NAME" on page 2038. The following table shows how to obtain C names for some SDL objects of interest:

#(Synonym name)
#(Newtype or syntype name)
ySigN_#(Signal name)
yPDP_#(Signal name)
yChaN_#(Channel name)

Function xGlobalNodeNumber

You should also provide a function, xGlobalNodeNumber, with no parameters, which returns an integer that is unique for each executing system.

int xGlobalNodeNumber ( void )
The returned integer should be greater than zero and should be unique among the communicating SDL systems that constitutes an application. If the application consists of only one application then this number is of minor interest (it still has to be set). The global node number is used in PId values to identify the node (OS process / processor) that the process instance belongs to. PId values are thereby universally accessible and you may, for example, in a simple way make "Output To Sender" work between processes in different SDL systems (OS processes / processors).

When an application consisting of several communicating SDL systems is designed, you have to map the global node number to the current OS process or processor, to be able to transmit signals addressed to non-local PIds to the correct OS process or processor. This will be part of the xOutEnv function.

Program Structure

The generated code will contain two important types of functions, the initialization functions and the PAD functions. The PAD functions implement the actions performed by processes during transitions. There will be one initialization function in each generated .c file. In the file that represents the system this function will have the name yInit. Each process type in the system will be represented by a PAD function, which is called when a process instance of the current type is to execute a transition.

The function main will be generated into the .c file that represents the SDL system and will have the following structure:

Example 22 : Structure of Generated Program  
void main ( void )
{
 xMainInit(yInit);
 Code from #MAIN directive
 xMainLoop();
}
where xMainInit and xMainLoop are runtime library functions with the following structure:

void xMainInit ( void (*Init_System)() )
{
  xInitEnv();
  Init of internal data structures in the 
  runtime library;
  (*Init_System)();
}
void xMainLoop ( void )
{
  while (1) {
    xInEnv(...);
    if ( Timer output is possible )
      SDL_OutputTimerSignal();
    else if ( Process transition is possible )
      Call appropriate PAD function;
  }
}
  
The function xMainLoop contains an endless loop. The appropriate way to stop the execution of the program is to call the runtime library function SDL_Halt. The call of this C function should normally be included in an appropriate task using the directive #CODE. SDL_Halt has the following structure:

void SDL_Halt ( void )
{
  xCloseEnv();
  exit(0);
}
To complete this overview, which emphasizes the usage of the environment functions, we have to deal with the xOutEnv function. Within PAD functions, the runtime library function SDL_Output is called to implement outputs of signals. When SDL_Output identifies the receiver of a signal to be a process instance that is not part of the current SDL system, SDL_Output will call the xOutEnv function.

Dynamic Errors

In the library for applications SDL runtime errors will not be reported. The application will just perform some appropriate actions and continue to execute. These actions are in almost all cases the same as the actions at dynamic errors described in the "Dynamic Errors" on page 1833 in chapter 32, The Simulator.

Example Section

In this section a complete example of an application is presented. The application is simple but it still contains most of the problems that arise when the C Code Generator is used to generate applications. All source code for this example, together with the running application are delivered with the runtime libraries for application generation. The example is developed for SUN/UNIX.

The Example

We want to develop an application that consists of several communicating UNIX processes. Each UNIX process should also be connected to the keyboard and the screen. When a complete line is typed on the keyboard (when <Return> is pressed) in one of the UNIX processes, that line should be sent to and printed by all the UNIX processes, including the one where the line was entered. If a line starting with the character "." is entered in any UNIX process then all the UNIX processes should terminate immediately.

There are some observations we can make from this short description.

The SDL System

The SDL system with a behavior as outlined above is very simple. It contains, for example, only one process. The system can receive three types of signals, TermInput from the terminal, and Message and Terminate from the SDL system that is the previous node in the ring. The system will respond by sending Display to the terminal and Message and Terminate to the SDL system next in the ring. The signals TermInput and Display take a line (which is read from the terminal or should be printed on the terminal) as parameter. The signal Message takes a line and a PId value (the original sender in the ring) as parameter, while the signal Terminate takes a PId value (the original sender in the ring) as parameter.

The diagrams for the SDL system may be found in "Appendix A: The SDL System" on page 481. In the section "Where to find the Example" on page 479, references to where to find the source code for this example are given.

Simulating the Behavior

At this stage of the development of the application, when the SDL system is completed but the environment functions are not implemented, it is time to simulate the SDL system to debug it at the SDL level. The runtime library Simulation is appropriate in this case for simulation.

There are six cases that should be tested:

Let us now verify that the SDL system behaves according to this. In the two executions of the simulation shown below, the cases described above are tested in the same order as they are listed.

Example 23 : Execution Trace of Generated Application  
Start program Phone.sim.sct
Command : set-trace 6
Trace for System Phone set to 6
Command : next-transition
*** TRANSITION START
*      PId    : PhonePr:1
*      State  : start state 
*      Now    : 0.0000
*** NEXTSTATE  idle
Command : output-via
Signal name : TermInput
 Parameter 1 (charstring) : `hello'
Channel name : 
Signal TermInput was sent to PhonePr:1 from env:1
Command : next-transition
*** TRANSITION START
*      PId    : PhonePr:1
*      State  : idle 
*      Input  : TermInput
*      Sender : env:1
*      Now    : 0.0000
*      Parameter(s) : `hello'
*   DECISION  Value: true
*   DECISION  Value: false
*   OUTPUT of Message to env:1
*      Parameter(s) : `hello', PhonePr:1
*** NEXTSTATE  idle
Command : output-via TermInput `.' -
Signal TermInput was sent to PhonePr:1 from env:1
Command : next-transition
*** TRANSITION START
*      PId    : PhonePr:1
*      State  : idle 
*      Input  : TermInput
*      Sender : env:1
*      Now    : 0.0000
*      Parameter(s) : `.'
*   DECISION  Value: true
*   DECISION  Value: true
*   OUTPUT of Terminate to env:1
*      Parameter(s) : PhonePr:1
*** NEXTSTATE  idle
Command : output-via Message
 Parameter 1 (charstring) : `hello'
 Parameter 2 (pid) : env
Channel name : 
Signal Message was sent to PhonePr:1 from env:1
Command : next-transition
*** TRANSITION START
*      PId    : PhonePr:1
*      State  : idle 
*      Input  : Message
*      Sender : env:1
*      Now    : 0.0000
*      Parameter(s) : `hello', env:1
*   DECISION  Value: false
*   OUTPUT of Message to env:1
*      Parameter(s) : `hello', env:1
*   OUTPUT of Display to env:1
*      Parameter(s) : `hello'
*** NEXTSTATE  idle
Command : output-via Message
 Parameter 1 (charstring) : `hello'
 Parameter 2 (pid) : PhonePr:1
Channel name : 
Signal Message was sent to PhonePr:1 from env:1
Command : next-transition
*** TRANSITION START
*      PId    : PhonePr:1
*      State  : idle 
*      Input  : Message
*      Sender : env:1
*      Now    : 0.0000
*      Parameter(s) : `hello', PhonePr:1
*   DECISION  Value: true
*   OUTPUT of Display to env:1
*      Parameter(s) : `hello'
*** NEXTSTATE  idle
Command : output-via Terminate
 Parameter 1 (pid) : env
Channel name : 
Signal Terminate was sent to PhonePr:1 from env:1
Command : next-transition
*** TRANSITION START
*      PId    : PhonePr:1
*      State  : idle 
*      Input  : Terminate
*      Sender : env:1
*      Now    : 0.0000
*      Parameter(s) : env:1
*   DECISION  Value: false
*   OUTPUT of Terminate to env:1
*      Parameter(s) : env:1
*   TASK  Halt
  
Example 24   
Start program Phone.sim.sct
Command : set-trace 6
Trace for System Phone set to 6
Command : next-transition
*** TRANSITION START
*      PId    : PhonePr:1
*      State  : start state 
*      Now    : 0.0000
*** NEXTSTATE  idle
Command : output-via Terminate
 Parameter 1 (pid) : PhonePr:1
Channel name : 
Signal Terminate was sent to PhonePr:1 from env:1
Command : next-transition
*** TRANSITION START
*      PId    : PhonePr:1
*      State  : idle 
*      Input  : Terminate
*      Sender : env:1
*      Now    : 0.0000
*      Parameter(s) : PhonePr:1
*   DECISION  Value: true
*   TASK  Halt
  
By running the system with the SDL monitor, as in the examples above, you may debug the system at the SDL level. The overall behavior of the system can thus be tested.

If you find it meaningful, it is possible to start two instances of the simulation and have the simulators communicate with each other. Then Message and Terminate signals sent to the environment in one of the simulations will appear as signals coming from the environment in the other.

----------------------------------------------------------
Note:                                                       
Do not forget the monitor command StartSDTEnv to make the   
simulation programs start communicating.                    
----------------------------------------------------------

The Environment

In the environment functions we use the socket facility in UNIX to implement the communication between the executing programs. In the current example, the implementation is developed for SUN/UNIX.

To simplify the example we assume that each instance of the application is started in a window of its own (a shell tool window under for instance X Windows, where UNIX commands can be entered). This means that we will have no problems with the interpretation of stdin and stdout in the programs.

The name of the socket for incoming messages for a certain instance of the application will be the string "Phone" concatenated with the UNIX process number for the current program. The socket will be created in the directory /tmp. Each application instance will print this number during the initialization and will then ask for the process number of the application instance where it should send its messages. You have to enter these numbers in such a way as to form a ring among the applications.

The Environment Functions

The environment functions, which may be found in the file PhoneEnv.c, are shown in section "Appendix B: The Environment Functions" on page 484. The file is developed according to the structure discussed in the previous part of this chapter and uses the system interface header file generated from the SDL system.

As the PhoneEnv.c file includes scttypes.h and uses some C macros, it should be compiled using the same compiler options as the C file for the SDL system. For information how to extend the generated make file to handle also non-generated files, please see "Compile and Link Options" on page 1110 in chapter 22, The SDT Organizer.

In the code for the environment functions a number of UNIX functions are used. Their basic behavior is described below. For any details please see the UNIX manuals available from SUN Microsystems.

-------------------------------------------------------------------
Function name  Functionality                                         
-------------------------------------------------------------------
getpid         Returns the UNIX process number for the cur           
               rent program.                                         
socket         Returns a new, unnamed socket.                        
bind           Binds a socket to a name in the file system.          
listen         Starts listen for other programs trying to connect    
               to this socket.                                       
connect        Should be called by other programs that want to       
               establish a connection to the current socket.         
accept         Accepts a connection request.                         
select         Returns 1 if anything readable can be found in        
               any of the specified file descriptors, where a file   
               descriptor can represent a file, a socket, and the    
               terminal (stdin and stdout).                          
read, write    Reads or writes on a file (a file descriptor).        
close          Closes a file.                                        
unlink         Removes a file.                                       
-------------------------------------------------------------------
If we now look at the code for the environment functions (see "Appendix B: The Environment Functions" on page 484), we see that xInitEnv mainly performs the following actions:

In xCloseEnv the created sockets are closed and removed.

The xInEnv and xOutEnv functions follow the guidelines for these functions given in the reference section. In xInEnv the select function is used to determine if any messages are ready to be received from the terminal (stdin) or from the incoming socket. An available message is then read and the information is converted to an SDL signal, which is sent to the SDL system using the SDL_Output function. In xOutEnv a test on the NameNode in the signal is used to determine the signal type. Depending on the signal type the appropriate information is written either on the outgoing socket or on the terminal (stdout).

Debugging

The first part of the debugging activity is, of course, when the SDL system is simulated and examined through the monitor system. Now we also want to include the environment functions during debugging. The intention of the library ApplicationDebug is to use the monitor and the environment functions together.

When the environment functions (xInEnv) read information from the keyboard there is, however, a problem in using xInEnv together with the monitor. In our system, for instance, a line typed on the keyboard may either be a monitor command or a line typed to the SDL system. As both the monitor and xInEnv are polling for lines from stdin, the interpretation of a typed line depends on which one first finds the line.

A better way is to eliminate this undeterministic behavior by not polling for typed lines in xInEnv. Instead, you may use the monitor command:

Output-Via TermInput 'the line'
to simulate a line typed on the keyboard. In this way all the other parts of the environment functions can be tested under the monitor. If you enclose the sections in xInEnv handling keyboard polling between #ifndef XMONITOR and #endif this code is removed when the monitor is used; that is if the library ApplicationDebug is used (see the code for xInEnv in "Appendix B: The Environment Functions" on page 484).

A C source code debugger is of course also useful when debugging the environment functions. The initialization phase, xInitEnv, is probably the most difficult part to get working correctly in our system. All the source code for this function is available, and a C debugger can be used.

While debugging generated code from SDL at the C level, it is always easy to find the currently executing SDL symbol, by using the SDT references (see "Syntax" on page 2476 in chapter 41, References in SDT-3) in the C code and the Go To Source menu choice in the Edit menu in the SDT Organizer. For more details please see page 1094 in chapter 22, The SDT Organizer.

Running the Application

To have an application of the Phone system you now only need to make a new executing program with the library Application.

When you run the Phone system, start the program from two (or more) shell tools. Each instance of the program will then print:

My Pid: 2311
Connect me to: 
You should answer these questions in such a way that a ring is formed by the programs. When the initialization is completed for a program it prints:

******** Welcome to SDT Phone System ********
phone ->
The program is now ready to receive lines printed on the keyboard and messages sent from other programs. A Display signal received from another program is printed as follows:

display -> the line received in Display signal

Where to find the Example

All files concerning this example may be found in the directory:

$sdtrelease/examples/phone
$sdtrelease is an environment variable which is set up at installation of SDT, and designates the SDT installation root directory.

Use these files if you only want to look at the source files and try the executing versions of the program. Otherwise you should copy the files to one of your own directories. Please be sure not to change the original files.

In the directory you will find the following files:

------------------------------------------------------------
File name       Purpose                                       
------------------------------------------------------------
Phone.sys       Represents the SDL system                     
PhoneBl.blk     Represents the SDL block                      
PhonePr.spr     Represents the SDL process                    
Phone.pr        The generated PR file after GR to PR          
Phone.c         The generated C file after C code generation  
Phone.ifc       The generated .ifc file                       
PhoneEnv.c      Contains the environment functions            
Phone.m         The generated make file                       
Phone.sim.sct   An executable program made with the library   
                Simulation                                    
Phone.deb.sct   An executable program made with the library   
                ApplicationDebug                              
Phone.appl.sct  An executable program made with the library   
                Application                                   
------------------------------------------------------------
The executable programs containing the monitor, Phone.sim.sct and Phone.deb.sct, may either be executed from SDT or directly from a UNIX shell.

Appendix A: The SDL System

Figure 187 : The System Phone. 
-----
(fig)  
       
-----
Figure 188 : The Block PhoneBl. 
-----
(fig)  
       
-----
Figure 189 : The Process PhonePr. 
-----
(fig)  
       
-----

Appendix B: The Environment Functions

This section contains the environment functions included in the example.

/****+******************************************************
00   sctEnv.c for SimplePhoneSys
***********************************************************/
#include "scttypes.h"
#include "Phone.ifc"
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/un.h>
int Out_Socket, In_Socket;
struct sockaddr_un  Connection_Socket_Addr;
struct sockaddr_un  Connected_Socket_Addr;
#ifdef XENV
/*#if !defined(XPMCOMM) && defined(XENV)*/
/*---+-------------------------------------------------------
     xGlobalNodeNumber  extern
-----------------------------------------------------------*/
#ifndef XNOPROTO
 int 
xGlobalNodeNumber( void )
#else
 int 
xGlobalNodeNumber()
#endif
{
  static int ProcId = -1;
  if (ProcId < 0)        
    ProcId = getpid();
  return (ProcId);
}
/*#endif*/
/*---+-------------------------------------------------------
     xInitEnv  extern
-----------------------------------------------------------*/
#ifndef XNOPROTO
 void
xInitEnv( void )
#else
 void
xInitEnv()
#endif
{
  fd_set  readfds;
  int  addr_size;
  int  Connection_Socket;
  char  TmpStr[132];
  struct timeval  t;
  t.tv_sec = 60;
  t.tv_usec = 0;
   
  if ((Connection_Socket = socket(PF_UNIX,SOCK_STREAM,0)) < 0) {
    printf("\nError: No Connection_Socket available!\n");
    SDL_Halt();
  }
  if ( (Out_Socket = socket(PF_UNIX,SOCK_STREAM,0)) < 0 ) {
    printf("\nError: No Out_Socket available!\n");
    SDL_Halt();
  }
  sprintf(Connection_Socket_Addr.sun_path,
          "/tmp/Phone%d", xGlobalNodeNumber());
  Connection_Socket_Addr.sun_family = PF_UNIX;
  if ( 0 > bind(Connection_Socket, &Connection_Socket_Addr, 
           strlen(Connection_Socket_Addr.sun_path)+2) ) {
    printf("\nError: Bind did not succeed!\n");
    SDL_Halt();
  }
  listen(Connection_Socket, 3);
  printf("\nMy Pid: %d\n", xGlobalNodeNumber());
  printf("\nConnect me to: ");
  FD_ZERO(&readfds);
  FD_SET(1,&readfds);
  if ( 0 < select(getdtablesize(), &readfds,
                  (fd_set*)0, (fd_set*)0, &t) ) {
    if ( FD_ISSET(1, &readfds) ) {
      (void)gets(TmpStr);
      sscanf(TmpStr, "%s", TmpStr);
      sprintf(Connected_Socket_Addr.sun_path,
              "/tmp/Phone%s", TmpStr);
      Connected_Socket_Addr.sun_family = PF_UNIX;
      connect(Out_Socket, &Connected_Socket_Addr,
              strlen(Connected_Socket_Addr.sun_path)+2);
    }
  } else {
    printf("\nError: Timed out\n");
    SDL_Halt();
  }
  FD_ZERO(&readfds);
  FD_SET(Connection_Socket,&readfds);                 
  if ( 0 < select(getdtablesize(), &readfds, (fd_set*)0,
                  (fd_set*)0, &t) ) {
    if ( FD_ISSET(Connection_Socket, &readfds) ) {
      addr_size = strlen(Connection_Socket_Addr.sun_path)+2;
      In_Socket = accept(Connection_Socket,
          &Connection_Socket_Addr, &addr_size);
    }
  } else {
    printf("\nError: Timed out\n");
    SDL_Halt();
  }
  printf("\n\n******** Welcome to SDT Phone System ********\n");
  printf("\nphone -> ");
}    
/*---+------------------------------------------------------
     xCloseEnv  extern
-----------------------------------------------------------*/
#ifndef XNOPROTO
 void
xCloseEnv( void )
#else
 void
xCloseEnv()
#endif
{
  close(Out_Socket);
  close(In_Socket);
  unlink(Connected_Socket_Addr.sun_path);
  unlink(Connection_Socket_Addr.sun_path);
  printf("\nClosing this session.\n");
}
/*---+-------------------------------------------------------
     xInEnv  extern
-----------------------------------------------------------*/
#ifndef XNOPROTO
 void
xInEnv( SDL_Time  Time_for_next_event ) 
#else
 void
xInEnv( Time_for_next_event )
  SDL_Time  Time_for_next_event; 
#endif
{
  struct timeval t;
  fd_set         readfds;
  char          *Instr;
  int            NrOfReadChars; 
  char           SignalName;
  xSignalNode    OutputSignal;
      int i = 0;
      char chr;
   
  t.tv_sec = 0;
  t.tv_usec = 1000;
  FD_ZERO(&readfds);
#ifndef XMONITOR
  FD_SET(1,&readfds);
#endif
  FD_SET(In_Socket,&readfds);         
  if ( select(getdtablesize(),&readfds,0,0,&t) > 0 ) {
#ifndef XMONITOR
    /*SDL-signal TermInput */
    if FD_ISSET(1, &readfds) { 
      Instr = (char *)xAlloc(132);
      Instr[0]='L';
      Instr++;
      (void)gets(Instr);
      OutputSignal = xGetSignal(TermInput, xNotDefPId, xEnv);
      xAss_SDL_Charstring(
        &((yPDP_TermInput)OutputSignal)->Param1, --Instr,XASS);
      SDL_Output(OutputSignal, (xIdNode *)NIL);
      xFree((void**)&Instr);
    }
#endif
    if FD_ISSET(In_Socket, &readfds) {
      Instr = (char *)xAlloc(151);
      do {
        read(In_Socket, &chr, 1);
        Instr[i++] = chr;
      } while ( chr!='\0' );
      sscanf(Instr, "%c", &SignalName);
      if ( SignalName == `M' ) {
        /* SDL-signal Message */
        OutputSignal = xGetSignal(Message, xNotDefPId, xEnv);
        sscanf(
          Instr+1, 
          "%d %x%n", 
          &(((yPDP_Message)OutputSignal)->Param2.GlobalNodeNr), 
          &(((yPDP_Message)OutputSignal)->Param2.LocalPId), 
          &NrOfReadChars);
        xAss_SDL_Charstring(
          &((yPDP_Message)OutputSignal)->Param1,
          (Instr+NrOfReadChars+2),XASS);
        SDL_Output(OutputSignal, (xIdNode *)NIL);
      } 
      else if ( SignalName == `T' ) {
        /* SDL-signal Terminate */
        OutputSignal = xGetSignal(Terminate, xNotDefPId, xEnv);
        sscanf(
          Instr+1, 
          "%d %x", 
          &(((yPDP_Terminate)OutputSignal)->Param1.GlobalNodeNr), 
          &(((yPDP_Terminate)OutputSignal)->Param1.LocalPId));
        SDL_Output(OutputSignal, (xIdNode*)0);
      }  
      xFree((void**)&Instr); 
    }  
  }
}      
/*---+-------------------------------------------------------
     xOutEnv  extern
-----------------------------------------------------------*/
#ifndef XNOPROTO
 void
xOutEnv( xSignalNode  *S ) 
#else
 void
xOutEnv( S )
  xSignalNode  *S; 
#endif
{ 
  char   Outstr[150];
  /* SDL-signal Message */
  if ( (*S)->NameNode == Message ) {
    sprintf(Outstr,
            "M %d %x %.*s", 
            ((yPDP_Message)(*S))->Param2.GlobalNodeNr,
            ((yPDP_Message)(*S))->Param2.LocalPId,
            strlen(((yPDP_Message)(*S))->Param1), 
            ((yPDP_Message)(*S))->Param1);
    write(Out_Socket, Outstr, strlen(Outstr)+1);
    xReleaseSignal(S); 
    return;
  }
  /* SDL-signal Terminate */
  if ( (*S)->NameNode == Terminate) {
    sprintf(Outstr,
            "T %d %x",
            ((yPDP_Terminate)(*S))->Param1.GlobalNodeNr,
            ((yPDP_Terminate)(*S))->Param1.LocalPId);
    write(Out_Socket, Outstr, strlen(Outstr)+1);
    xReleaseSignal(S); 
    return;
  }
  /* SDL-signal Display */
  if ( (*S)->NameNode == Display ) {
    printf("\ndisplay ->%.*s", 
           strlen(((yPDP_Display)(*S))->Param1), 
           ++((yPDP_Display)(*S))->Param1);
    printf("\nphone -> ");         
    xReleaseSignal(S); 
    return;
  }    
}
#endif
 
Table of Contents Next Chapter