This chapter describes how you can use the Cadvanced 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 The Cadvanced/Cbasic Code Generator before reading this chapter to understand the general behavior of the Cadvanced Code Generator. Much of what you need to know to generate an application may be found there, and that information is not repeated here.
An application generated with the Cadvanced Code Generator can be viewed as having three parts:
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 kinds 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 Cadvanced Code Generator has no knowledge of the actions that should be performed.
Figure 483 : Structure of an application
|
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 for 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 Cadvanced Code Generator. However, you have to implement the communication between the nodes in the environment functions.
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.
Two libraries, Application and ApplicationDebug, are provided to generate applications. Both use real time (see Time and perform calls to environment functions (see section The Environment Functions). 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 Cadvanced 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.
In this first section, 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.
A signal is represented by a C struct containing general information about the signal followed by the parameters carried by the signal.
Figure 484 : Data structure representing a signal
|
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:
Pre
and Suc
. These components are used to link the signal in the input port list of the receiving process instance. The input port is implemented as a double linked list. When a signal has been consumed and the information contained in the signal is no longer needed, the signal will be returned to an avail list to be re-used in future outputs. The component Suc
is used to link the signal into the avail list, while Pre
will be (xSignalNode)0
as long as the signal is in the avail list.Receiver
. The receiving process instance.Sender
. The sending process instance.NameNode
. This component is a pointer to the node in the symbol table that represents the signal type. The symbol table is a tree with information about the SDL system and contains, among other things, one node for each signal type that is defined within the SDL system. Prio
. This component represents the priority of the signal and is used in connection with continuous signals.In the generated code there will be types to represent the parameters of the signals according to the following example:
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. |
A PId value is a struct consisting of two components, a global node number, which is an integer (see also section Function xGlobalNodeNumber and section The Basic Idea) 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 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.
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 Cadvanced 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:
xInitEnv
and xCloseEnv
, which are called during initialization and termination of the application xOutEnv
which should treat signals sent to the environmentxInEnv
which should treat signals sent into the SDL system from the environmentThere are two ways to get a skeleton for the env functions:
sctenv.c
from the directory/sdt/sdtdir/
<machine dependent dir>/INCLUDE
where <machine dependent dir> is for example sunos5sdtdir
on SunOS 5, hppasdtdir
on HP, and wini386
in Windows. (In Windows, /
should be replaced by \
in the path above.)An advantage with the generated env functions is that the code generator knows about the signal interface to be implemented in the env functions, and can therefore insert code or macros for all signals in the interface. To calculate this information is not that easy, especially if partitioning (generating code for a part of a system) is used.
The env functions are thoroughly discussed below, but first we will introduce the system interface header file which considerably simplifies writing the environment functions.
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 both:
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:
#TYPE
and #HEADING
sections in #ADT
directives and in #CODE
directives.xSignalIdRec
variable representing the signal.yPDef_SignalName
and yPDP_SignalName
, i.e. of the types used to represent the signal.pCALL_procedurename
and pREPLY_procedurename
.xChannelIdRec
representing the channel.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. In the system interface, header file macros are introduced to make the SDL names directly available in C.
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:
xSignalIdNode
representing signals. (Note, no ySigN_
prefix).xChannelIdNode
representing channels. (Note, use prefix xIN_
or xOUT_
to access the incoming or outgoing direction of the channel).yPDP_SignalName
pointer type. This type may be referred to using the name yPDP_
SignalName
, where SignalName
is the SDL name. .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):
#ifndef X_IFC_z_minimal #define X_IFC_z_minimal /* ******* DECLARATIONS IN SYSTEM minimal ******* */ /*---------------- INCLUDE FILES ----------------*/ /*------------------- SYNONYMS -------------------*/ #define z8_syn1 SDL_INTEGER_LIT(10) #define syn1 SDL_INTEGER_LIT(10) /*-------------------- SORTS --------------------*/ /***** * NEWTYPE sort1 (TYPE section) * <<SYSTEM minimal>> * #SDTREF(TEXT,ifc.pr,7) ******/ typedef int z6_sort1; #define z60_lit1 0 #define lit1 0 #define z61_lit2 1 #define lit2 1 #define z62_lit3 2 #define lit3 2 #ifdef XPREFIX_NAMES_IN_IFC #define pTYPE_sort1 z6_sort1 #else #define sort1 z6_sort1 #endif #ifndef XOPTSORT extern XCONST struct xSortIdStruct ySrtR_z6_sort1; #define ySrtN_z6_sort1 (&ySrtR_z6_sort1) #define ySrtN_sort1 (&ySrtR_z6_sort1) #endif /***** * NEWTYPE struct1 (TYPE section) * <<SYSTEM minimal>> * #SDTREF(TEXT,ifc.pr,10) ******/ typedef struct z7_struct1_s { SDL_Integer a; SDL_Integer b; } z7_struct1; #ifdef XPREFIX_NAMES_IN_IFC #define pTYPE_struct1 z7_struct1 #else #define struct1 z7_struct1 #endif #ifndef XOPTSORT extern XCONST struct xSortIdStruct ySrtR_z7_struct1; #define ySrtN_z7_struct1 (&ySrtR_z7_struct1) #define ySrtN_struct1 (&ySrtR_z7_struct1) #endif /***** * NEWTYPE sort1 (HEADING section) * <<SYSTEM minimal>> * #SDTREF(TEXT,ifc.pr,7) ******/ #define yAssF_z6_sort1(V,E,A) V = E #define yDef_z6_sort1(yVar) *(yVar) = z60_lit1 #define yEqF_z6_sort1(Expr1,Expr2) (Expr1)==(Expr2) #define yNEqF_z6_sort1(Expr1,Expr2) (Expr1)!=(Expr2) #ifndef ANY_z6_sort1 #define ANY_z6_sort1 \ (z6_sort1)(0+GETINTRAND%((int)(z62_lit3)+1)) #endif /***** * NEWTYPE struct1 (HEADING section) * <<SYSTEM minimal>> * #SDTREF(TEXT,ifc.pr,10) ******/ #define yAssF_z7_struct1(V,E,A) V = E extern void yDef_z7_struct1 XPP(( z7_struct1 * )); extern SDL_Boolean yEq_z7_struct1 XPP(( z7_struct1, z7_struct1 )); #define yEqF_z7_struct1(Expr1,Expr2) \ yEq_z7_struct1(Expr1,Expr2) #define yNEqF_z7_struct1(Expr1,Expr2) \ ( ! yEq_z7_struct1(Expr1,Expr2) ) extern z7_struct1 yMake_z7_struct1 XPP(( SDL_Integer, SDL_Integer )); #ifndef ANY_z7_struct1 #define ANY_z7_struct1 \ yMake_z7_struct1(ANY_SDL_Integer, ANY_SDL_Integer) #endif /*------------------- SYNONYMS -------------------*/ #ifndef syn3 extern SDL_Boolean syn3; #endif extern z7_struct1 z9_syn2; #define syn2 z9_syn2 /*------------------ SIGNALS --------------------*/ /* sig1 IN */ #ifndef sig1 extern XCONST struct xSignalIdStruct ySigR_z3_sig1; #ifdef XPREFIX_NAMES_IN_IFC #define pSIGNAL_sig1 (&ySigR_z3_sig1) #else #define sig1 (&ySigR_z3_sig1) #endif #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 #define yPDef_sig2 yPDef_z4_sig2 #ifndef sig2 extern XCONST struct xSignalIdStruct ySigR_z4_sig2; #ifdef XPREFIX_NAMES_IN_IFC #define pSIGNAL_sig2 (&ySigR_z4_sig2) #else #define sig2 (&ySigR_z4_sig2) #endif #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 #define yPDef_sig3 yPDef_z5_sig3 #ifndef sig3 extern XCONST struct xSignalIdStruct ySigR_z5_sig3; #ifdef XPREFIX_NAMES_IN_IFC #define pSIGNAL_sig3 (&ySigR_z5_sig3) #else #define sig3 (&ySigR_z5_sig3) #endif #endif /*------------------ CHANNELS -------------------*/ #ifndef XOPTCHAN extern struct xChannelIdStruct yChaR_z1_chan1; extern struct xChannelIdStruct yChaRR_z1_chan1; #define chan1 (&yChaR_z1_chan1) #define xOUT_chan1 (&yChaRR_z1_chan1) #define xIN_chan1 (&yChaR_z1_chan1) #endif #ifndef XOPTCHAN extern struct xChannelIdStruct yChaR_z2_chan2; extern struct xChannelIdStruct yChaRR_z2_chan2; #define chan2 (&yChaR_z2_chan2) #define xOUT_chan2 (&yChaR_z2_chan2) #define xIN_chan2 (&yChaRR_z2_chan2) #endif #endif /* X_IFC_z_minimal */
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 Function xGlobalNodeNumber. 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.
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:
|
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 and Types Representing Processes.
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.
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 selected as receiver in the scan for receivers.
Remote procedure calls to or from the environment should in the environment functions be treated a two signals, a pCALL_procedurename
and a pREPLY_procedurename
signal.
You can, of course, write the xOutEnv
function as you wish -- the structure discussed below may be seen as an example -- but also as a guideline of how to design xOutEnv
functions.
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 */ }
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). 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. This large value can be found in the variable xSysD.xMaxTime
.
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
.
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.
SType
. This parameter should be a reference to the symbol table node that represents the current signal type. Using the system interface header file, such a symbol table node may be referenced using the signal name directly.Receiver
. This parameter should either be a PId value for a process instance within the system, or the value xNotDefPId
. The value xNotDefPId
is used to indicate that the signal should be sent as an output without TO clause, while if a PId value is given the output, it is treated as an output with TO clause. Note that PId values for process instances in an SDL system cannot be calculated, but have to be captured from the information (sender or parameter) carried by signals coming from the system. This is the normal procedure in SDL to establish direct communication.Sender
. Sender should either be a PId value representing a process instance in the environment of the current SDL system 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 the SDL environment, without specifying an explicit process instance in the environment.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[] );
S
. This parameter should be a reference to a signal instance with all components filled in.ViaList
. This parameter is used to specify if a VIA clause is or is not part of the output statement. The value (xIdNode *)0
(a null pointer), is used to represent that no VIA clause is present. For information about how to build a via list, please see below.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:
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.
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.
ViaList xIdNode[4]; /* longest via has length 3 */ ... /* this via has length 2 */ ViaList[0] = (xIdNode)xIN_C1; ViaList[1] = (xIdNode)xIN_C2; ViaList[2] = (xIdNode)0;
The variable ViaList
may then be used as a ViaList
parameter in a subsequent call to SDL_Output
.
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.
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.
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 the section Modifying Outputs -- Directive #EXTSIG, #ALT, #TRANSFER. This information is not repeated here.
By using the #EXTSIG
directive the following overhead can be avoided:
SDL_Output
(the library function for outputs)SDL_Output
determines that the signal is to be sent to the environmentSDL_Output
calls xOutEnv
xOutEnv
executes nested "if" statements to determine the signal type.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.
/*#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 sections Accessing SDL Names in C Code -- Directive #SDL and Specifying Names in Generated Code -- Directive #NAME. 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)
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.
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 in the system will be represented by a PAD function, which is called when a process instance of the current instance set is to execute a transition.
The example below shows the structure of the main
, MainInit
, and MainLoop
functions.
void main ( void ) { xMainInit(); xMainLoop(); } void xMainInit ( void ) { xInitEnv();Init of internal data structures in the
runtime library
; yInit(); } 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
which 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.
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.
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 Cadvanced 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 SunOS 5 and HP-UX.
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 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. In the section Where to Find the Example, references to where to find the source code for this example are given.
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.
Start program Phone.sim.sct
:
Command : set-trace 6 Default trace 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 Process scope : PhonePr: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 Process Scope : PhonePr: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 Process scope : PhonePr: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 Process scope : PhonePr: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 Process scope : PhonePr: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
Start program Phone.sim.sct
:
Command : set-trace 6 Default trace 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 Process scope : PhonePr: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.
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 Start-SDT-Env to make the simulation programs start communicating. |
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 SunOS 5 and HP-UX.
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, which may be found in the file PhoneEnv.c
, are shown in section Appendix B: The Environment Functions. 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 about how to extend the generated make file to handle also non-generated files, please see Makefile Options.
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.
If we now look at the code for the environment functions (see Appendix B: The Environment Functions), we see that xInitEnv
mainly performs the following actions:
Connection_Socket
) and one for messages out (Out_Socket
).Connection_Socket
to the file system (in the /tmp
directory) and starts listen for connections.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
).
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 indeterministic 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 pollingbetween #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).
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) in the C code and the Go To Source menu choice in the Edit menu in the Organizer. For more details please see Go To Source.
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 (on UNIX). 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
All files concerning this example may be found in the directory:
<SDT installation directory>/sdt/examples/phone
Use these files if you only want to look at the source files and if you are using a Sun workstation you could 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:
|
Figure 486 : The block PhoneBl
|
Figure 487 : The process PhonePr
|
This section contains the environment functions included in the example.
/****+******************************************************* 00 sctEnv.c for SimplePhoneSys ************************************************************/ #include "scttypes.h" #include <stdio.h> #include "phone.ifc" #include <sys/types.h> #include <sys/time.h> #include <sys/socket.h> #ifdef AIXV3CC #include <sys/select.h> #endif #include <sys/un.h> #include <unistd.h> #define getdtablesize() ( (int) sysconf(_SC_OPEN_MAX) ) int Out_Socket, In_Socket; struct sockaddr_un Connection_Socket_Addr; struct sockaddr_un Connected_Socket_Addr; #ifdef ULTRIXCC #define PRINTF(s) \ printf(s); \ /* flush output to get a prompt */ \ fflush( stdout) #else #define PRINTF(s) printf(s) #endif #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); sprintf(TmpStr, "\nMy Pid: %d\n", xGlobalNodeNumber()); PRINTF(TmpStr); PRINTF("\nConnect me to: "); FD_ZERO(&readfds); FD_SET(1,&readfds); FD_SET(Connection_Socket,&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; if (connect(Out_Socket, (struct sockaddr *)&Connected_Socket_Addr, strlen(Connected_Socket_Addr.sun_path)+2) < 0) { PRINTF("Error from connect\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(); } } else 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); 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; if (connect(Out_Socket, (struct sockaddr *)&Connected_Socket_Addr, strlen(Connected_Socket_Addr.sun_path)+2) < 0) { PRINTF("Error from connect\n"); SDL_Halt(); } } } else { PRINTF("\nError: Timed out\n"); SDL_Halt(); } } } 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 = '\0'; xSignalNode yOutputSignal; int i = 0; char chr = '\0'; 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); yOutputSignal = xGetSignal(TermInput, xNotDefPId, xEnv); xAss_SDL_Charstring( &((yPDP_TermInput)(OUTSIGNAL_DATA_PTR))->Param1, --Instr,XASS); SDL_Output(yOutputSignal, (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 */ yOutputSignal = xGetSignal(Message, xNotDefPId, xEnv); sscanf( Instr+1, "%d %x%n", &(((yPDP_Message)(OUTSIGNAL_DATA_PTR))->Param2.GlobalNodeNr), &(((yPDP_Message)(OUTSIGNAL_DATA_PTR))->Param2.LocalPId), &NrOfReadChars); xAss_SDL_Charstring( &((yPDP_Message)(OUTSIGNAL_DATA_PTR))->Param1, (Instr+NrOfReadChars+2),XASS); SDL_Output(yOutputSignal, (xIdNode *)NIL); } else if ( SignalName == 'T' ) { /* SDL-signal Terminate */ yOutputSignal = xGetSignal(Terminate, xNotDefPId, xEnv); sscanf( Instr+1, "%d %x", &(((yPDP_Terminate)(OUTSIGNAL_DATA_PTR))->Param1.GlobalNodeNr), &(((yPDP_Terminate)(OUTSIGNAL_DATA_PTR))->Param1.LocalPId)); SDL_Output(yOutputSignal, (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 ) { sprintf(Outstr, "\ndisplay ->%.*s", strlen(((yPDP_Display)((*S)))->Param1), ((yPDP_Display)((*S)))->Param1+1); PRINTF(Outstr); PRINTF("\nphone -> "); xReleaseSignal(S); return; } } #endif