![]() |
![]() |
![]() |
![]() |
![]() |
The SDL Model
Signals and Timers
Data Structure Representing Signals and Timers
A signal is represented by a struct type. The xSignalRec struct, defined in scttypes.h, is a struct containing general information about a signal except from the signal parameters. In scttypes.h the following information about signals can be found:
#ifdef XMSCE#define GLOBALINSTID int GlobalInstanceId;#else#define GLOBALINSTID#endif#if defined(XSIGPATH) && defined(XMSCE)#define ENVCHANNEL xChannelIdNode EnvChannel;/* Used if env split into channels in MSC trace */#else#define ENVCHANNEL#endif#ifdef XENV_CONFORM_2_3#define XSIGNAL_VARP void * VarP;#else#define XSIGNAL_VARP#endifdefine SIGNAL_VARS \xSignalNode Pre; \xSignalNode Suc; \int Prio; \SDL_PId Receiver; \SDL_PId Sender; \xSignalIdNode NameNode; \GLOBALINSTID \ENVCHANNEL \XSIGNAL_VARPtypedef struct xSignalStruct *xSignalNode;typedef struct xSignalStruct {SIGNAL_VARS} xSignalRec;The xSignalNode type is thus a pointer type which is used to refer to allocated data areas of type xSignalRec. The components in the xSignalRec struct are used as follows:
- Pre and Suc. These pointers are used to link a signal into the input port of the receiving process instance.
- Prio is used to represent the priority of the signal instance. Signal priorities are used by continuous signals and by ordinary signals if signal priorities are defined (signal priority is a possible extension provided in the product).
- Receiver is used to reference the receiver of the signal. It is either set in the output statement (OUTPUT TO), or calculated (OUTPUT without TO).
- Sender is the PId value of the sending process instance. This value is necessary to provide the SDL function SENDER.
- NameNode is a reference to the xSignalIdNode representing the signal type and thus defines the signal type of this signal instance.
- VarP is a pointer introduced via the macro XSIGNAL_VARP to make signal compatible with SDT 2.3. Normally this components is not present.
- EnvChannel is used to identify the outgoing channel in MSCE trace.
- GlobalInstanceId is used in the MSCE trace as a unique identification of the signal instance.
A signal without parameters are represented by a xSignalStruct, while for signals with parameters a struct type named yPDef_SignalName and a pointer type referencing this struct type (yPDP_SignalName) are defined in generated code. The struct type will start with the SIGNAL_VARS macro and then have one component for each signal parameter, in the same order as the signal parameters are defined. The components will be named Param1, Param2, and so on.
typedef struct {SIGNAL_VARSSDL_Integer Param1;SDL_Boolean Param2;} yPDef_sig;typedef yPDef_sig *yPDP_sig;These types would represent a signal sig(Integer, Boolean).
As all signals starts with the components defined in SIGNAL_VARS it is possible to type cast a pointer to a signal, to the xSignalNode type, if only the components in SIGNAL_VARS is to be accessed.
Allocation of Data Areas for Signals
In sctos.c there are two functions, xGetSignal and xReleaseSignal, where data areas for signal are handled:
xSignalNode xGetSignal(xSignalIdNode SType,SDL_PId Receiver,SDL_PId Sender )void xReleaseSignal( xSignalNode *S )xGetSignal takes a reference to the SignalIdNode identifying the signal type and two PId values (sending and receiving process instance) and returns a signal instance. xGetSignal first looks in the avail list for the signal type (the component AvailSignalList in the SignalIdNode for the signal type) and reuses any available signal there. Only if the avail list is empty new memory is allocated. The component VarSize in the SignalIdNode for the signal type provides the size information needed to correctly allocate the yPDef_SignalName even though the type is unknown for the xGetSignal function.
The function xReleaseSignal takes the address of an xSignalNode pointer and returns the referenced signal to the avail list for the signal type. The xSignalNode pointer is then set to 0.
The function xGetSignal is used:
- In generated code (output, set, reset)
- In a number of places in the library:
SDL_Create
SDL_SimpleReset
SDL_Nextstate (to handle continuous signals)- In the postmaster communication section and in the monitor to obtain signal instances.
The function xReleaseSignal is used by:
Overview of Output and Input of Signals
In this subsection the signal handling operation is only outlined. More details will be given in the section treating processes. See Output and Input of Signals.
Signal instances are sent using the function SDL_Output. That function takes a signal instance and inserts it into the input port of the receiving process instance.
If the receiver is not already in the ready queue (the queue containing the processes that can perform a transition, but which have not yet been scheduled to do so) and the current signal may cause an immediate transition, the process instance is inserted into the ready queue.
If the receiver is already in the ready queue or in a state where the current signal should be saved, the signal instance is just inserted into the input port.
If the signal instance can neither cause a transition nor should be saved, it is immediately discarded (the data area for the signal instance is returned to the avail list).
The input port is scanned during nextstate operations, according the rules of SDL, to find the next signal in the input port that can cause a transition. Signal instances may then be saved or discarded.
There is no specific input function, instead this behavior is distributed both in the runtime library and in the generated code. The signal instance that should cause the next transition to be executed is removed from the input port in the main loop (the scheduler), immediately before the PAD function for the current process is called. The PAD function is the function where the behavior of the process is implemented and is part of the generated code. The assignment of the signal parameters to local SDL variables is one of the first actions performed by the PAD function.
The signal instance that caused a transition is released and returned to the avail list in the nextstate or stop action that ends the current transition.
Timers and Operations on Timers
A timer with parameters is represented by a type definition, where the timer parameters are defined, in exactly the same way as for a signal definition, see Data Structure Representing Signals and Timers. At runtime, all timers that are set and where the timer time has not expired, are represented by a xTimerRec struct and a signal instance:
#define TIMER_VARS \xSignalNode Pre; \xSignalNode Suc; \int Prio; \SDL_PId Receiver; \SDL_PId Sender; \xSignalIdNode NameNode; \GLOBALINSTID \ENVCHANNEL \SDL_Time TimerTime;typedef xTimerRec *xTimerNode;typedef struct xTimerStruct {TIMER_VARS} xTimerRec;The TIMER_VARS is and must be identical to the SIGNAL_VARS macro, except for the TimerTime component last in the macro. A timer with parameters have yPDef_timername and yPDP_timername types in generated code exactly as a signal (see previous section), except that SIGNAL_VARS is replaced by TIMER_VARS.
During its life-time a timer have two different appearances. First it is a timer waiting for the timer time to expire. In that phase the timer is inserted in the xTimerQueue. When the timer time expires the timer becomes a signal and is inserted in the input port of the receiver just like any other signal. Due to the identical typedefs for xSignalRec and xTimerRec, there are no problems with type casting between xTimerNode and xSignalNode types.
When a timer is treated as a signal the components in the xTimerRec are used in the same ways as for a xSignalRec. While the timer is in the timer queue, the components are used as follows:
- Pre and Suc are pointers used to link the xTimerRec into the timer queue (the queue of active timers, see below).
- TimerTime is the time given in the Set operation.
The queue mentioned above, the timer queue for active timers is represented by the component xTimerQueue in the variable xSysD:
xTimerNode xTimerQueue;The variable is initialized in the function xInitKernel in sctsdl.c. xTimerQueue is initialized it refers to the queue head of the timer queue.
The queue head is an extra element in the timer queue that does not represent a timer, but is introduced as it simplifies the algorithms for queue handling. The TimerTime component in the queue head is set to a very large time value (xSysD.xMaxTime).
The timer queue is thus a doubly linked list with a list head and it is sorted according to the timer times, so that the timer with lowest time is at the first position.
To optimize the case where there are many active timers, one extra pointer is introduced for each timer in the data area for the process instances. This pointer is used to refer to the active timer and to the timer signal up to the point where the signal is received in an input. A reset or active operation is then trivial, as there is a pointer to the timer object. If the pointer is NULL no timer is set.
For timers with parameters this model is extended, so a list of timers can be accessed from the process.
The xTimerRec structs are allocated and reused in the same way as signal.
From the SDL point of view, timers are handled in:
The timer output is the event when the timer time has expired and the timer signal is sent. After that, a timer signal is treated as an ordinary signal. These operations are implemented as follows:
void SDL_Set(SDL_Time T,xSignalNode S )This function, which represents the Set operation, takes the timer time and a signal instance as parameters. It first uses the signal instance to make an implicit reset (see reset operation below) It then updates the TimerTime component in S and inserts S into the timer queue at the correct position.
The SDL_Set operation is used in generated code, together with xGetSignal, in much the same way as SDL_Output. First a signal instance is created (by xGetSignal), then timer parameters are assigned their values, and finally the Set operation is performed (by SDL_Set).
void SDL_Reset( xSignalNode *TimerS )void SDL_SimpleReset(xPrsNode P,xSignalIdNode TimerId )Two functions are used to represent the SDL action reset. SDL_SimpleReset is used for timers without parameters and SDL_Reset for timers with parameters.
SDL_Reset uses the two functions xRemoveTimer and xRemoveTimerSignal to remove a timer in the timer queue and to remove a signal instance in the input port of the process. It then releases the signal instance given as parameter. This signal is only used to carry the parameter values given in the reset action.
The function SDL_SimpleReset is implemented in the same way as SDL_Reset, except that it creates its own signal instance (without parameters).
At a reset action the possibly found timer is removed from the timer queue and returned to the avail list. A found signal instance (in the input port) is removed from the input port and returned to the avail list for the current signal type.
static void SDL_OutputTimerSignal( xTimerNode T )The SDL_OutputTimerSignal is called from the main loop (the scheduler) when the timer time has expired for the timer first in the timer queue. The corresponding signal instance is then sent.
SDL_OutputTimerSignal takes a pointer to an xTimerRec as parameter, removes it from the timer queue and sends as an ordinary output using the function SDL_Output.
It can be checked if timer is active by using a call to the function SDL_Active. This function is used in generated code to represent the SDL operator active.
SDL_Boolean SDL_Active (xSignalIdNode TimerId,xPrsNode P )
Only timers without parameters can be tested. This is a restriction in the Cadvanced/Cbasic SDL to C Compiler.
There is one more place where timers are handled. When a process instance performs a stop action all timers in the timer queue connected to this process instance are removed. This is performed by calling the function xRemoveTimer with the first parameter equal to 0.
Processes
Data Structure Representing Processes
A process instance is represented by two structs, an xLocalPIdRec and a struct containing both the general process data and the local variables and formal parameters of the process (yVDef_ProcessName), see also Figure 553. The reason for having both the -xLocalPIdRec and the yVDef_ProcessName will be discussed under Create and Stop Operations.
The corresponding type definitions, which can be found in scttypes.h, are:
#ifdef XPRSSENDER#define XPRSSENDERCOMP SDL_PId Sender;#else#define XPRSSENDERCOMP#endif#ifdef XTRACE#define XTRACEDEFAULTCOMP int Trace_Default;#else#define XTRACEDEFAULTCOMP#endif#ifdef XGRTRACE#define XGRTRACECOMP int GRTrace;#else#define XGRTRACECOMP#endif#ifdef XMSCE#define XMSCETRACECOMP int MSCETrace;#else#define XMSCETRACECOMP#endif#if defined(XMONITOR) || defined(XTRACE)#define XINTRANSCOMP xbool InTransition;#else#define XINTRANSCOMP#endif#ifdef XMONITOR#define XCALL_ADDR int CallAddress;#else#define XCALL_ADDR#endif#ifndef XNOUSEOFSERVICE#define XSERVICE_COMP \xSrvNode ActiveSrv; xSrvNode SrvList;#else#define XSERVICE_COMP#endif#define PROCESS_VARS \xPrsNode Pre; \xPrsNode Suc; \int RestartAddress; \xPrdNode ActivePrd; \void (*RestartPAD) (xPrsNode VarP); \XCALL_ADDR \XSERVICE_COMP \xPrsNode NextPrs; \SDL_PId Self; \xPrsIdNode NameNode; \int State; \xSignalNode Signal; \xInputPortRec InputPort; \SDL_PId Parent; \SDL_PId Offspring; \int BlockInstNumber; \XSIGTYPE pREPLY_Waited_For; \xSignalNode pREPLY_Signal; \XPRSSENDERCOMP \XTRACEDEFAULTCOMP \XGRTRACECOMP \XMSCETRACECOMP \XINTRANSCOMPtypedef struct {xPrsNode PrsP;int InstNr;int GlobalInstanceId;} xLocalPIdRec;typedef xLocalPIdRec *xLocalPIdNode;typedef struct {int GlobalNodeNr;xLocalPIdNode LocalPId;} SDL_PId;typedef struct xPrsStruct *xPrsNode;typedef struct xPrsStruct {PROCESS_VARS} xPrsRec;A PId value is thus a struct containing two components:
The use of the global node number is discussed in the Building an Application.
A xLocalPIdRec contains the following three components:
- PrsP of type xPrsNode. This component is a pointer to the xPrsRec struct that is part of the representation of the process instance.
- InstNr of type int. This is the instance number of the current process instance, which is used in the communication with the user in the monitor and in dynamic error messages.
- GlobalInstanceId is used in MSCE traces to have a unique identification of the process instance.
A xPrsRec struct contains the following components described below. As each yVDef_ProcessName struct contains the PROCESS_VARS macro as first item, it is possible to cast pointer values between a pointer to xPrsRec and a pointer to a yVDef_ProcessName struct.
- Pre and Suc of type xPrsNode. These components are used to link the process instance in the ready queue (see below).
- RestartAddress of type int. This component is used to find the appropriate SDL symbol to continue execute from.
- ActivePrd of type xPrdNode. This is a pointer to the xPrdRec that represents the currently executing procedure called from this process instance. The pointer is 0 if no procedure is currently called.
- RestartPAD, which is a pointer to a PAD function. This component refers to the PAD function where to execute the sequence of SDL symbols. RestartPAD is used to handle inheritance between process types.
- CallAddress of type int. This component contains the symbol number of the procedure call currently executed by this process.
- ActiveSrv of type xSrvNode. This component contains a reference to the currently active service (or latest active service) in this process.
- SrvList of type xSrvNode. This component contains a reference to the first service contained in this process. The component NextSrv in the struct representing a service can be used to find next active service in the process.
- NextPrs of type xPrsNode. This component is used to link the process instance either in the active list or in the avail list for this process type. The start of these two lists are the components ActivePrsList and AvailPrsList in the IdNode representing the current process type.
- Self of type SDL_PId. This is the PId value of the current process instance.
- NameNode of type xPrsIdNode. This is a pointer to the PrsIdNode representing the current process or process instantiation.
- State of type int. This component contains the int value used to representing the current state of the process instance.
- Signal of type xSignalNode. This is a pointer to a signal instance. The referenced signal is the signal that will cause the next transition by the current process instance, or that caused the transition that is currently executed by the process instance.
- InputPort of type xInputPortRec. This is the queue head in the doubly linked list that represents the input port of the process instance. The signals are linked in this list using the Pre and Suc components in the xSignalRec struct.
- Parent of type SDL_PId. This is the PId value of the parent process (according to the rules of SDL). A static process instance has parent equal to NULL.
- Offspring of type SDL_PId. This is the PId value of the latest created process instance (according to the rules of SDL). A process instance that has not created any processes has offspring equal to NULL.
- BlockInstNumber of type int. If the process is part of a block instance set, this component indicates which of the blocks that the process belongs to.
- pREPLY_Waited_For of type xSignalIdNode. When a process is waiting in the implicit state for the pREPLY signal in a RPC call, this components is used to store the IdNode for the expected pREPLY signal.
- pREPLY_Signal of type xSignalNode. When a process receives a pCALL signal, i.e. accepts a RPC, it immediately creates the return signal, the pREPLY signal. This component is used to refer to this pREPLY signal until it is sent.
- Sender of type SDL_PId. This component represents the SDL concept Sender.
- Trace_Default of type int. This component contains the current value of the trace defined for the process instance.
- GRTrace of type int. This component contains the current value of the GR trace defined for the process instance.
- MSCETrace of type int. This component contains the current MSCE trace value for the process instance.
- InTransition of type xbool. This component is true while the process is executing a transition and it is false while the process is waiting in a state. The monitor system needs this information to be able to print out relevant information.
The Ready Queue, Scheduling
The ready queue is a doubly linked list with a head. It contains the process instances that can execute an immediate transition, but which has not been allowed to complete that transition. Process instances are inserted into the ready queue during output operations and nextstate operations and are removed from the ready queue when they execute the nextstate or stop operation that ends the current transition. The head in the ready queue, which is an object in the queue that does not represent any process but is inserted only to simplify the queue operations, is referenced by the xSysD component:
xPrsNode xReadyQueue;This component is initiated in the function xInitKernel and used throughout the runtime library to reference the ready queue.
Scheduling of events is performed by the function xMainLoop, which is called from the main function after the initialization is performed.
void xMainLoop()The strategy to have all interesting queues (the ready queue, the timer queue, and the input ports) sorted in the correct order is used in the library. Sorting is thus performed when an object is inserted into a queue, which means that scheduling is a simple task: select the first object in the timer queue or in the ready queue and submit it for execution.
There are several versions of the body of the endless loop in the function xMainLoop, which are used for different combinations of compilation switches. When it comes to scheduling of transitions and timer outputs they all have the following outline:
while (1) {if ( xTimerQueue->Suc->TimerTime <= SDL_Now() )SDL_OutputTimerSignal( xTimerQueue->Suc );else if ( xReadyQueue->Suc != xReadyQueue) {xRemoveFromInputPort(xReadyQueue->Suc->Signal);xReadyQueue->Suc->Sender =xReadyQueue->Suc->Signal->Sender;(*xReadyQueue->Suc->RestartPAD)(xReadyQueue->Suc);}}while (1) {if ( there is a timer that has expired )send the corresponding timer signal;else if ( there is a process that can executea transition ) {remove the signal causing the transitionfrom input port;set up Sender in the process to Sender ofthe signal;execute the PAD function for the process;}}The different versions of the main loop handle different combinations of compilation switches. Other actions necessary in the main loop are dependent of the compilation switches. Example of such actions are:
- Handling of the monitor
- Calling the xInEnv function
- Handling real time or simulated time
- Delay execution up to the next scheduled event
- Handling enabling conditions and continuous signals that need to be recalculated.
Create and Stop Operations
A process instance is, while it is active, represented by the two structs:
These two structs are dynamically allocated. A PId value is also a struct (not allocated) containing two components, GlobalNodeNr and LocalPId, where LocalPId is a pointer to the xLocalPIdRec. Figure 554 shows how the xLocalPIdRec and the yVDef_ProcessName structs representing a process instance are connected.
When a process instance performs a stop action, the memory used for the process instance should be reclaimed and it should be possible to reuse in subsequent create actions. After the stop action, old (invalid) PId values might however be stored in variables in other process instances.
If a signal is sent to such an old PId value, that is, to a stopped process instance, it should be possible to find and perform appropriate actions. If the complete representation of a process instance is reused then this will not be possible. There must therefore remain some little piece of information and thus some memory for each process instance that has ever existed. This is the purpose of the xLocalPIdRec. These structs will never be reused. Instead the following (see Figure 555) will happen when the process instance in Figure 554 performs a stop action.
Figure 555 : The memory structure after the process in
Figure 554 has performed a stop actionA new xLocalPIdRec is allocated and its PrsP references the yVDef_ProcessName (InstNr is 0). The Self component in the yVDef_ProcessName is changed to reference this new xLocalPIdRec. The old xLocalPIdRec still references the yVDef_ProcessName. The yVDef_ProcessName is entered into the avail list for this process type.
To reuse the data area for a process instance at a create operation it is only necessary to remove the yVDef_ProcessName from the avail list and update the InstNr component in the xLocalPIdRec referenced by Self.
Using this somewhat complicated structure to represent process instances allows a simple test to see if a PId value refers to an active or a stopped instance:
If P is a PId variable then the following expression:
P.LocalPId == P.LocalPId->PrsP->Self.LocalPIdis true if the process instance is active and false if it is stopped.
The basic behavior of the create and stop operations is performed by the functions SDL_Create and SDL_Stop.
void SDL_Create(xSignalNode StartUpSig,xPrsIdNode PrsId )void SDL_Stop( xPrsNode PrsP )To create a process instance takes three steps performed in generated code:
- Call xGetSignal to obtain the start-up signal.
- Assign the actual process parameters to the start up signal parameters.
- Call SDL_Create with the start-up signal as parameter, together with the PrsIdNode representing the process to be created.
In xGetProcess the process instance is removed from the avail list of the process instance set (the component AvailPrsList in the PrsIdNode representing the process instance set), or if the avail list is empty new memory is allocated.
The process instance is linked into the list of active process instances (the component ActivePrsList in the PrsIdNode representing the process instance set). Both the avail list and the active list are single linked lists (without a head) using the component NextPrs in the yVDef_ProcessName struct as link.
To have an equal treatment of the initial transition and other transitions, the start state is implemented as an ordinary state with the name "start state" It is represented by 0. To execute the initial transition a "startup" signal is sent to the process. The start state can thus be seen as a state with one input of the startup signal and with save for all other signals. This implementation is completely transparent in the monitor, where startup signals are never shown in any way.
The actual values for FPARs are passed in the startup signal.
Two IdNodes that are not part of the symbol table tree are created to represent a start state and a startup signal.
xStateIdNode xStartStateId;xSignalIdNode xStartUpSignalId;These xSysD components are initialized in the function xInitSymbolTable, which is part of sctsdl.c.
At a stop operation the function SDL_Stop is called. This function will release the signal that caused the current transition and all other signals in the input port. It will also remove all timers in the timer queue that are connected to this process instance by calling xRemoveTimer with the first parameter equal to 0. It then removes the process executing the stop operation from the ready queue and from the active list of the process type and returns the memory to the avail list of the current process instance set.
Output and Input of Signals
There are three actions performed in generated code to send a signal. First xGetSignal is called to obtain a data area that represents the signal instance, then the signal parameters are assigned their values and finally the function SDL_Output is called to actually send the signal. First in the SDL_Output function there are a number of dynamic tests (check if receiver in TO-clause is not NULL and not stopped, check if there is a path to the receiver). If the output does not contain any TO-clause and the Cadvanced/Cbasic SDL to C Compiler has not been able to calculate the receiver, the xFindReceiver function is called to calculate the receiver according to the rules of SDL.
Next, in SDL_Output signals to the environment are handled. Three cases can be identified here:
- The environment function xOutEnv is called.
- The corresponding function that sends signals via the SDL Suite communication mechanism (xOutPM) is called.
- The signal is inserted into the input port of the process representing the environment (xEnv).
Finally, internal signals in the SDL system are treated. Here also three cases can be identified (how this is evaluated is described last in this subsection):
- The signal can cause an immediate transition by the receiver.
- The signal should be saved.
- The signal should be immediately discarded.
If the signal can cause an immediate transition, the signal is inserted into the input port of the receiver, and the receiving process instance is inserted into the ready queue.
If the signal should be saved, the signal is just inserted into the input port of the receiver.
If the signal should be discarded, the function xReleaseSignal is called to reused the data area for the signal.
When a signal is identified to be the signal that should cause the next transition by the current process instance (at an Output or Nextstate operation), the component Signal in the yVDef_ProcessName for the process is set to refer to the signal. The signal is still part of the input port list.
When the transition is to be executed, the signal is removed from the input port in the main loop (see The Ready Queue, Scheduling) immediately before the PAD function for the process is called.
First in the PAD function, the parameters of the signal are copied to the local variables according to the input statement. In the ending Nextstate or Stop operation of the transition the signal instance is returned to the avail list.
Evaluating How To Handle a Received Signal
There are two places in the run-time kernel where it is necessary to evaluate how to handle signals (input, save, discard,...):
- At an Output operation to a currently idle process.
- At a Nextstate operation, when the process have signals in the input port.
This calculation is implemented in the run-time kernel function xFindInputAction.
typedef unsigned char xInputAction;#define xDiscard (xInputAction)0#define xInput (xInputAction)1#define xSave (xInputAction)2#define xEnablCond (xInputAction)3#define xPrioInput (xInputAction)4static xInputAction xFindInputAction(xSignalNode SignalId,xPrsNode VarP,xbool CheckPrioInput )The parameters of this function is:
- SignalId, which is a pointer to a signal.
- VarP, which is a pointer to a process instance.
- CheckPrioInput, which is a boolean value indicating is the function should check only for priority inputs or for ordinary inputs.
As a result the function should return:
- The action that should be performed for this signal (input, save,...), taking all information about this process into account, like inheritance between processes, virtual - redefined transitions and so on.
- If the function result is xInput or xPrioInput, then the RestartPAD and RestartAddr components in the VarP struct should be updated with information about where this input can be found.
After this last update the correct transition can be started by the scheduler by just calling the function referenced by RestartPAD, which the as first action performs switch RestartAddr and starts execute the input symbol.
The algorithm to find the InputAction, the RestartAddr, and the RestartPAD is as follows:
- Let ProcessId become yVarP->NameNode and let StateId become ProcessId->StateList[yVarP->State].
- In ProcessId->SignalSet find the index (Index) where SignalId->NameNode is found. If the signal is not found, this signal is not in the signal set of the process, and the algorithm terminates returning the result xDiscard.
- StateId->SignalHandlArray[Index] now gives the action to be performed. If this value is xEnablCond, then the function StateId->EnablCond_Function is called. This function returns either xInput or xSave.
- If the result from step 3 is xInput, the algorithm terminates returning this value. yVarP->RestartAddr is also updated to
StateId->InputRef[Index], while yVarP->RestartPAD is updated to ProcessId->PAD_Function.
- If the result from step 3 is xSave, the algorithm terminates returning this value.
- If the result from step 3 is xDiscard and ProcessId->Super equal to NULL, then the algorithm terminates returning this value.
- If the result from step 3 is xDiscard and ProcessId->Super not equal to NULL, then we are in a process type that inherits from another process type. We then have to perform step 2 - 4 again, with ProcessId assigned the value ProcessId->Super and StateId assigned the value StateId->Super.
Nextstate Operations
The nextstate operation is implemented by the SDL_Nextstate function, where the following actions are performed:
- The signal that caused the current transition (component Signal in the yVDef_ProcessName) is released and the state variable (component State in the yVDef_ProcessName) is updated to the new state.
- Then the input port of the process is scanned for a signal that can cause a transition. During the scan signals might be saved or discarded until a signal specified in an input is found. Priority inputs are treated according to the rules of SDL.
- If no signal that can cause a transition is found, a check is made if any continuous signal can cause a transition (see Enabling Conditions and Continuous Signals). The process is thereafter removed from the ready queue.
- If any signal (or continuous signal) can cause a transition then the process is re-inserted into the ready queue again at a position determined by its priority, else if the new state contains any continuous signal or enabling condition with an expression that might change its value during the time the process is in the state (view, import...), the process is inserted into the check list (see also Enabling Conditions and Continuous Signals).
Decision and Task Operations
Decision and Task operations are implemented in generated code, except for the Trace-functions implemented in the sctutil.c and sctmon.c files and for informal and any decisions that uses some support functions in sctmon.c. A Decision is implemented as a C if-statement, while the assignments in a Task are implemented as assignments or function calls in C.
Compound Statements
A compound statement without variable declarations is translated just to the sequence of action it contains, while a compound statement with variable declarations is translated in the same way as an SDL procedure (without parameters). Statements within a compound statement are translated according to the normal rules. The new statement types in compound statements are translated as:
- if in SDL is translated to if in C
- decision in compound statements is translated as ordinary decisions.
- for loops, continue, and break are all translated using goto in C.
Enabling Conditions and Continuous Signals
The expressions involved in continuous signals and enabling conditions are implemented in generated code in functions called yCont_StateName and yEnab_StateName. These functions are generated for each state containing continuous signals respectively enabling conditions. The functions are referenced through the components ContSig_Function and EnablCond_Function in the StateIdNode for the state. These components are 0 if no corresponding functions are generated.
The EnablCond_Functions are called from the function xFindInputAction, which is called from SDL_Output and SDL_Nextstate. If the enabling condition expression for the current signal is true then xInput is returned else xSave is returned. This information is then used to determine how to handle the signal in this state.
The ContSig_Functions are called from SDL_Nextstate, if the component ContSig_Function is not 0 and no signal that can cause an immediate transition is found during the input port scan. A ContSig_Function has the following prototype:
void ContSig_Function_Name (void *, int *, xIdNode *, int *);where the first parameter is the pointer to the yVDef_ProcessName. The remaining parameters are all out parameters; the second contains the priority of the continuous signal with highest priority (=lowest value) that has an expression with the value true. Otherwise <0 is returned here. The third and fourth is only defined the second parameter >=0; the third is the IdNode for the process/procedure where the actual continuous signal can be found and the fourth is the RestartAddress connected to this continuous signal.
If a continuous signal expression with value true is found, a signal instance representing the continuous signal is created and inserted in the input port, and is thereafter treated as an ordinary signal. The signal type is continuous signal and is represented by an SignalIdNode (referenced by the variable xContSigId).
The check list is a list that contains the processes that wait in a state where enabling conditions or continuous signals need to be repeatedly recalculated.
A process is inserted into the check list if:
- It enters a state containing enabling conditions and/or continuous signals and
- No signal or continuous signal can cause an immediate transition and
- One or several of the expressions in the enabling conditions or continuous signals can change its value while the process is in the state (view, import, now, ...)
The component StateProperties in the StateIdNode reflects if any such expression is present in the state.
The check list is represented by the xSysD component:
xPrsNode xCheckList;The behavior of enabling conditions and continuous signals is in SDL modeled by letting the process repeatedly send signals to itself, thereby to repeatedly entering the current state. In the implementation chosen here, nextstate operations are performed "behind the scene" for all processes in the check list directly after a call to a PAD function is completed, that is directly after a transition is ended and directly after a timer output. This is performed by calling the function xCheckCheckList in the main loop of the program.
View and Reveal
A view expression is part of an expression in generated code and implemented by calling the function SDL_View.
void * SDL_View (xViewListRec *VList,SDL_PId P,xbool IsDefP,xPrsNode ViewingPrs,char * Reveal_Varint SortSize);
- VList is a list of all revealed variables in this block.
- P is the PId expression given in the view statement.
- IsDefP is 1 is the view expression contained a PId value, 0 otherwise.
- ViewingPrs is the process instance performing the view operation.
- Reveal_Var is the name of the revealed variable as a string. The Reveal_Var parameter is only used in error messages and is remove under certain conditions.
- SortSize is the size of the data type of the viewed variable.
The SDL_View function performs a test that the view expression is not NULL, refers to a process in the environment, or to a stopped process instance. If no errors are found the address of the revealed variable is returned as result from the SDL_View function. Otherwise the address of a variable containing only zeros is returned.
Import, Export, and Remote Variables
For an exported variable there are two components in the yVDef_ProcessName struct. One for the current value of the variable and one for the currently exported value of the variable. For each exported variable there will also be a struct that can be linked into a list in the corresponding RemoteVarIdNode. This list is then used to find a suitable exporter of a variable in an import action.
An export action is a simple operation. The current value of the variable is copied to the component representing the exported value. This is performed in generated code.
An import action is more complicated. It involves mainly a call of the function xGetExportAddr:
void * xGetExportAddr (xRemoteVarIdNode RemoteVarNode,SDL_PId P,xbool IsDefP,xPrsNode Importer )RemoteVarNode is a reference to the RemoteVarIdNode representing the remote variable (implicit or explicit), P is the PId expression given in the import action and IsDef is 0 or 1 depending on if any PId expression was given in the import action or not, Importer is the importing process instance. The xGetExportAddr will check the legality of the import action and will, if no PId expression is given, calculate which process it should be imported from.
If no errors are found the function will return the address where the exported value can be found. This address is then casted to the correct type (in generated code) and the value is obtained. If no process possible to import from is found, the address of a variable containing only zeros is returned by the xGetExportAddr function.
Services
Data Structure Representing Services
A service is represented by a struct type. The xSrvRec struct defined in scttypes.h, is, just like xPrsRec for processes, a struct containing general information about a service, while the parameters and variables of the service are defined in generated code in the same way as for processes.
In scttypes.h the following types concerning procedures can be found:
#ifdef XMONITOR#define XCALL_ADDR int CallAddress;#else#define XCALL_ADDR#endif#define SERVICE_VARS \xSrvNode NextSrv; \xPrsNode ContainerPrs; \int RestartAddress; \xPrdNode ActivePrd; \void (*RestartPAD) (xPrsNode VarP); \XCALL_ADDR \xSrvIdNode NameNode; \int State; \XSIGTYPE pREPLY_Waited_For; \xSignalNode pREPLY_Signal; \XINTRANSCOMP#ifndef XNOUSEOFSERVICEtypedef struct xSrvStruct *xSrvNode;#endif#ifndef XNOUSEOFSERVICEtypedef struct xSrvStruct {SERVICE_VARS} xSrvRec;#endifIn generated code yVDef_ProcedureName structs are defined according to the following:
typedef struct {SERVICE_VARScomponents for FPAR and DCL} yVDef_ServiceName;The components in the xSrvRec are used as follows:
- NextSrv of type xSrvNode. Reference to next service contained in this process.
- ContainerPrs of type xPrsNode. Reference to the process instance containing this service.
- RestartAddress of type int. This component is used to find the appropriate SDL symbol to continue execution from.
- ActivePrd of type xPrdNode. This is a pointer to the xPrdRec that represents the currently executing procedure called from this service instance. The pointer is 0 if no procedure is currently called.
- RestartPAD, which is a pointer to a PAD function. This component refers to the PAD function where to execute the sequence of SDL symbols. RestartPAD is used to handle inheritance between service types.
- CallAddress of type int. This component contains the symbol number of the procedure call performed from this procedure (if any).
- NameNode of type xSrvIdNode. This is a pointer to the IdNode representing the service or service instantiation.
- State of type int. This component contains the int value used to represent the current state of the service instance.
- pREPLY_Waited_For of type xSignalIdNode. When a service is waiting in the implicit state for the pREPLY signal in a RPC call, this components is used to store the IdNode for the expected pREPLY signal.
- pREPLY_Signal of type xSignalNode. When a service receives a pCALL signal, i.e. accepts a RPC, it immediately creates the return signal, the pREPLY signal. This component is used to refer to this pREPLY signal until it is sent.
- InTransition of type xbool. This component is true while the service is executing a transition and it is false while the service is waiting in a state. The monitor system needs this information to be able to print out relevant information.
Executing Transitions in Services
From the scheduler's point view, it is not of interest if a process contains services or not. It is still the process instance that is scheduled in the ready queue and the PAD function of the process that is to be called to execute a transition. The PAD function for a process containing services performs three different actions:
- Assign default value to variables declared at the process level
- Create one service instance for each service or service instantiation in the process.
- Calls the proper PAD function for a service to execute transitions.
The structure for a PAD function for a process with services are as follows:
YPAD_FUNCTION(yPAD_z00_P1){YPAD_YSVARPYPAD_YVARP(yVDef_z00_P1)YPRSNAME_VAR("P1")LOOP_LABEL_SERVICEDECOMPCALL_SERVICE/*-----* Initialization (no START symbol)------*/BEGIN_START_TRANSITION(yPDef_z00_P1)yAssF_SDL_Integer(yVarP->z002_Global,SDL_INTEGER_LIT(10), XASS);START_SERVICES}where LOOP_LABEL_SERVICEDECOMP and BEGIN_START_TARNSITION are empty macros, i.e. expanded to no code. The yAss_SDL_Integer statement in an assignment of a default value to a process variable.
The macro CALL_SERVICE is expanded to:
if (yVarP->ActiveSrv != (xSrvNode)0) {(*yVarP->ActiveSrv->RestartPAD)(VarP);return; \}that is to a call of the PAD function of service reference by ActiveSrv.
The macro START_SERVICE is expanded to a call to the function xStart_Services, which can be found in sctsdl.c.The function creates the service instances, sets up the ActiveSrv pointer for the process to the first service, and then schedules the process for a new transition. This means that the next action performed by the system will be the start transition by the first service instance. When the first service executes a nextstate or stop action in the end of its start transition, the process will be scheduled again to execute the start transition of the second service, and so on until all services in the process has executed its start transitions.
For ordinary transitions, i.e. reception of a signal, it is obvious from the code above that the ActiveSrv pointer is essential. It should refer to the service instance that is to be executed. When a signal is to be received by a process, it is the function xFindInputAction (in sctsdl.c) that determines how to handle the signal and if it is to be received, where is the code for that transition. This function now also determines and sets up the ActiveSrv pointer.
Procedures
Data Structure Representing Procedures
A procedure is represented by a struct type. The xPrdRec struct defined in scttypes.h, is, just like xPrsRec for processes, a struct containing general information about a procedure, while the parameters and variables of the procedure are defined in generated code in the same way as for processes.
In scttypes.h the following types concerning procedures can be found:
#define PROCEDURE_VARS \xPrdIdNode NameNode; \xPrdNode StaticFather; \xPrdNode DynamicFather; \int RestartAddress; \XCALL_ADDR \void (*RestartPAD) (xPrsNode VarP); \xSignalNode pREPLY_Signal; \int State;typedef struct xPrdStruct *xPrdNode;typedef struct xPrdStruct {PROCEDURE_VARS} xPrdRec;In generated code yVDef_ProcedureName structs are defined according to the following:
typedef struct {PROCEDURE_VARScomponents for FPAR and DCL} yVDef_ProcedureName;The components in the xPrdRec are used as follows:
- NameNode of type xPrdIdNode. This is a pointer to the IdNode representing the procedure type.
- StaticFather of type xPrdNode. This is a pointer that represents the scope hierarchy of procedures (and the process at the top), which is used when a procedure instance refers to non-local variables. An example is shown in Figure 557. StaticFather == 0 means that the static father is the process.
- DynamicFather of type xPrdNode. This is a pointer that represents that this procedure is called by the referenced procedure. DynamicFather == 0 means that this procedure was called from the process. This component is also used to link the xPrdRec in the avail list for the procedure type.
- RestartAddress of type int. This component is used to find the appropriate SDL symbol to continue execution from.
- CallAddress of type int. This component contains the symbol number of the procedure call performed from this procedure (if any).
- RestartPRD is a pointer to a procedure function. This component refers to the PRD function where to execute the next sequence of SDL symbols. RestartPRD is used to handle inheritance between procedures.
- pREPLY_Signal of type xSignalNode. When a process receives a pCALL signal, i.e. accepts a RPC, it immediately creates the return signal, the pREPLY signal. This component is used to refer to this pREPLY signal until it is sent.
- State of type int. This is the value representing the current state of the procedure instance.
In Figure 557 an example of the structure of yVDef_ProcedureName after four nested procedure calls are presented. Note that procedure Q is declared in the process, procedure R and S in Q and T in S.
The SDL procedures are partly implemented using C functions and partly using the structure shown above. Each SDL procedure is represented by a C function, which is called to execute actions defined in the procedure. This function corresponds to the PAD function for processes. The formal parameters and the variables are however implemented using a struct defined in generated code. The procedure stack for nested procedure calls is implemented using the components StaticFather and DynamicFather, and does not use the C function stack.
Calling and Returning from Procedures
Procedure calls and procedure returns are handled by three functions, one handling allocation of the data areas for procedures:
xPrdNode xGetPrd( xPrdIdNode PrdId )and two functions called from generated code at a procedure call and a procedure return:
void xAddPrdCall(xPrdNode R,xPrsNode VarP,int StaticFatherLevel,int RestartAddress )void xReleasePrd (xPrsNode VarP)A procedure call in SDL is in C represented by the following steps:
- Calling xGetPrd to obtain a data area for the procedure.
- Assigning procedure parameters to the data area.
- Calling xAddPrdCall to link the procedure into the static and dynamic chains.
- Calling the C function modeling the SDL procedure, i.e. the yProcedureName function.
The parameters to xAddPrdCall are as follows:
- R. A reference to the xPrdNode obtained from the call of xGetPrd.
- VarP. A reference to the yVDef_ProcessName, i.e. the data area for variables and parameters of the process (even if it is a procedure that performed the procedure call).
- StaticFatherLevel. This is the difference in declaration levels between the caller and the called procedure. This information is used to set up the StaticFather component correctly.
- RestartAddress. This is the symbol number of the SDL symbol directly after the procedure call. The symbol number is the switch case label generated for all symbols.
The xGetPrd returns a pointer to an xPrdRec, which can then be used to assign the parameter values directly to the components in the data area representing the formal parameters and variables of the procedure. Note that IN/OUT parameters are represented as addresses in this struct.
A procedure return is in generated code represented by calling the xReleasePrd followed by return 0, whereby the function representing the behavior of the SDL procedure is left.
The function representing the behavior of the SDL procedure is returned in two main situations:
- When an SDL Return is reached (the function returns 0)
- When a Nextstate is reached (the function returns 1).
If 0 is returned then the execution should continue with the next SDL symbol after the procedure call, while if 1 is returned the execution of the process instance should be terminated and the scheduler (main loop) should take control. This could mean that a number of nested SDL procedure calls should be terminated.
To continue to execute at the correct symbol when a procedure should be resumed after a nextstate operation, the following code is introduced in the PAD function for processes containing procedure calls:
while ( yVarP->ActivePrd != (xPrdNode)0 )if ((*yVarP->ActivePrd->RestartPRD)(VarP))return;This means that uncompleted procedures are resumed one after one from the bottom of the procedure stack, until all procedures are completed or until one of them returns 1, i.e. executes a nextstate operation, at which the process is left for the scheduler again.
Channels and Signal Routes
The ChannelIdNodes for channels, signal routes, and gates are used in the functions xFindReceiver and xIsPath, which are both called from SDL_Output, to find the receiving process when there is no TO clause in the Output statement, respectively to check that there is a path to the receiver in the case of a TO clause in the Output statement. In both cases the paths built up using the ToId components in the IdNodes for processes, channels and signal routes are followed. To show the structure of these paths we use the small SDL system given in Figure 558.
During the initialization of the system, the symbol table is built up. The part of the symbol table starting with the system will then have the structure outlined in Figure 559. As we can see in this example the declarations in the SDL system are directly reflected by IdNodes.
Figure 559 : The symbol table tree for the system in Figure 558
Each IdNode representing a process, a signal route, or a channel will have a component ToId. A ToId component is an address to an array of references to IdNodes. The size of this array is dependent on the number of items this object is connected to. A process that has three outgoing signal routes will have a ToId array which can represent three pointers plus an ending 0 pointer.
In the example in Figure 558 and Figure 559 there is no branching, so all ToId arrays will be of the size necessary for two pointers. Figure 560 shows how the IdNodes for the processes, signal routes and channels are connected to form paths, using the components ToId. In this case only simple paths are found (one from P1, via SR1, C, SR2, to P2, and one in the reverse direction). The generalization of this structure to handle branches is straightforward and discussed in the previous paragraph.
Figure 560 : The connection of ToId for the system in
Figure 558 and Figure 559The Type Concept in SDL-92
The probably most important new feature in SDL-92 is the introduction of the object oriented features, such as TYPE, INHERITS, VIRTUAL, and REDEFINED. Here we start by discussing process types.
For each process type the Cadvanced/Cbasic SDL to C Compiler will generate:
In the PrsIdNode there is one component (Super) that will refer to the PrsIdNode for the process type inherited by this process type. As sons to a PrsIdNode, IdNodes for declaration that are common for all instantiation of the process type can be found. Examples of such IdNodes are: nodes for variables, formal parameters, signals, timers, procedures, states, newtypes, and syntypes. Any typedefs or help functions for such units are also treated in the process type.
The PAD function will be independent of the PAD function for a inherited type, each PAD function just implementing the action described in its process type.
A yVDef_ProcessName struct will on the other hand include all variables and formal parameters from the top of the inheritance chain and downwards. Example:
process type P1;fpar f1 integer;dcl d1 integer;...endprocess;process type P2 inherits P1;fpar f2 integer;dcl d2 integer;...endprocess;This will generate the following principle yVDef_... structs:
typedef struct {PROCESS_VARSSDL_Integer f1;SDL_Integer d1;} yVDef_P1;typedef struct {PROCESS_VARSSDL_Integer f1;SDL_Integer d1;SDL_Integer f2;SDL_Integer d2;} yVDef_P2;A pointer to yVDef_P2 can thus be casted to a pointer to yVDef_P1, if only the common component (in PROCESS_VARS) or the variables in P1 is to be accessed. This possibility is used every time the PAD function for an inherited process type is called.
Each process instantiation will all be implemented as a xPrsIdNode. The Super component in such an object refers to the process type that is instantiated. No PAD function or yVDef_... struct will be generated. As sons to the PrsIdNode for a process instantiation, only such object are inserted that are different in different instantiations. For a process instantiation this is the gates. For other types of information the process instantiation uses the information given for its process type.
A very similar structure when it comes to IdNodes generated for block types and block instantiations are used by the code generator. There will be a BlockIdNode for both a block type and for a block instantiation. As sons to a block type, nodes that are the same in each block instantiation can be found (example: signal, newtype, syntype, block type, process type, procedure). As sons to a block instantiation, nodes that are needs to be represented in each block instantiation can be found (example: block instantiation, process instantiation, channel, signal route, gate, remote definitions).
A block or process (according to SDL-88), that is contained in a block type or a system type, is translated as if it was a type and instantiation at the same place.
A way to look at the structure of IdNodes in a particular system is to use the command Symboltable in the monitor system. This command prints the IdNode structure as an indented list of objects.
http://www.ibm.com/rational |
![]() |
![]() |
![]() |
![]() |