ISR, IPC and Timer Example for ObjecTime Developer for C version 5.2.1
Category :
Modeling.
Purpose :
To demonstrate the integration of Interrupt Service Routines (ISR), Inter-process Communication (IPC) and Timers with ObjecTime Developer for C.
Intended Audience :
Software developers using the ObjecTime Developer for C product.
Applicable To :
ObjecTime Developer for C version 5.2.1.
Description :
Overview
This technical note describes how to integrate external event handling with your ObjecTime C model. There are several approaches to this but this technical note describes the easiest and more elegant solution. A fundamental issue with integrating external event handling in a thread running within an ObjecTime model is being able to wait for external events and still process ObjecTime messages in a normal fashion. For example, if you decided to block on a semaphore within transition code in an ObjecTime actor you would effectively stop all actors on that thread from running until your transition code became unblocked. This approach will work if you can accept the periodic blocking of the model but it is not desirable for most real world applications (consider what would happen if the semaphore is never signaled and the model is blocked indefinitely.) There is an API provided in the C TargetRTS to help integrate blocking mechanisms with the operation of an ObjecTime thread.
Extending the main loop of a thread : RSLRegisterExternalInterface()
The C TargetRTS provides a function, RSLRegisterExternalInterface, that allows you to specify a function that will be called once every cycle through an ObjecTime thread. This function effectively extends the main loop of the thread and allows you to add functionality but still maintain the correct operation of the thread (i.e. to continue to process ObjecTime messages.)
This function is typically an actor function that belongs to an actor designated to handle this external event. For example a timer actor is designated to handle timer services and it would have an actor function that is registered with the TargetRTS as an external interface.
extern RSLBool RSLRegisterExternalInterface(RSLActorIndex,
void(*Mainloop)(void*,RSLActorIndex));
The first argument is the actor registering the external interface, usually the current actor which can be referenced using the _actor variable. The second argument is the function pointer itself. Your main loop extension function will have the following signature :
void MainLoopExtension( <Actor Name>_InstanceData *this,
RSLActorIndex actorIndex );
The "this" pointer will allow access to the ESVs for your actor and the actorIndex parameter will be useful in further API calls. Within this function you can add calls to the operating system (OS) or I/O library that maybe blocking. Be sure that the function does not loop indefinitely and that you handle timeout or error return values from OS calls. If you plan to handle ObjecTime messages while blocking you will need to be able to determine if the "wake up" originates from ObjecTime or from an external event. This is typically done by assigning a unique wake-up message to indicate an ObjecTime message.
Note that actors assigned to handle external events (such as an ISR, IPC or Timer actor) are typically assigned to their own physical thread. The RSLRegisterExternalInterface should only be called once for each of these threads.
Getting notification of ObjecTime messages : RSLRegisterMessageSignallingInterface()
If you plan to block on I/O or some OS call within your main loop extension function you still need to be able to handle normal actor-to-actor messages within your model. The C TargetRTS provides an API function that allows you to provide a function pointer to a function that will be called each time a message is delivered to any of the actors on the current thread.
void RSLRegisterMessageSignallingInterface( RSLActorIndex ,
void( * sigfunc )( void * ) );
The first argument is the actor registering the external interface, usually the current actor which can be referenced using the _actor variable. The second argument is the function pointer itself. Your main loop extension function will have the following signature :
void MyWakeUpFunction( <Actor Name>_InstanceData *this );
The this pointer will allow access to the ESVs for your actor. This function is usually used to signal the blocking mechanism used in your main loop extension. For example, if your main loop is currently blocked on a semaphore, the wake up function will signal the semaphore and "wake up" the blocked thread. Note that this function is called by the sending thread.
Registering a Timer Server : RSLRegisterTimerServices()
A timer actor is unique in that it shares the implementation of timer services with the C TargetRTS. Requests for timer services actually go through the C TargetRTS and are then forwarded to the timer actor. For this reason, we do not use the RSLRegisterMessageSignallingInterface() with timer actors. Instead, we use the RSLRegisterTimerServices function. This function has two purposes, it registers the actor with C TargetRTS indicating that it is the sole provider of timing services for the model and it allows the provision of a wake-up function for the timer actor.
void RSLRegisterTimerServices( RSLActorIndex , RSLPortIndex ,
void(*sigfunc)( void * ) );
The first argument is the actor providing the service (i.e., the timer actor), usually defined by the _actor variable. The second argument is the port that timer requests are to be sent to. The third argument is the wake-up or signal function, which has the same signature as described above.
Timers
A timer service is not provided in the C TargetRTS as it is in the C++ TargetRTS. Timing services must be provided by the user with the help of API calls within the C TargetRTS to help manage the data structures associated with the timers. For supported platforms these timers, in the form of timer actors, are already implemented and provided in the Model Examples directory shipped with ObjecTime Developer for C. If you are not using one of the supported platforms, and have ported the TargetRTS to a new operating system, then you will need to also provide an actor timer implementation for your platform. Although daunting to a new user, timers are usually implemented by copying one of the pre-existing timer actors and tailoring them to your specific platform. In the model example provided with this technical note there are two timer actors, one for Solaris (TimerSolarisMT) which was taken verbatim from the model examples and one for Tornado-VxWorks (TimerTornadoMT) which is not in the model examples. These actors are very similar and differ only in details necessary for their respective operating systems.
Initialization
Timer actors typically perform the required initialization on their initial transitions. This is important because timer services should be in place before other actors try to use them. In order to enforce this the C TargetRTS will start all actors with name "*time*" first so they are initialized before the rest of the model. Be sure to name your timer actor "Timer" or something similar.
Initialization usually consists of calling the RSLRegisterExternalInterface() and RSLRegisterTimerServices() functions. Initialization of any operating system objects may be required as well. For example the TimerTornadoMT actor provided in this technical note initializes the semaphore which is used as part of the timer implementation.
Operation
After initialization of the Timer actor, all timer requests are routed by the C TargetRTS to the timer actor. These requests arrive at the port specified during registration. A timer actor usually has one state with one self-transition. All timer requests trigger this self-transition (message is timeout, data is RSLTimerControlBlock.) This transition calls the function SetupTCB() which is used to adjust the timer request to absolute time (which is OS dependent). The transition also calls the function RSLEnqueueTimer() which places the timer control block (TCB) on the TCB list. The TCB contains all the information needed by the timer actor :
typedef struct _RSLTimerControlBlock {
struct _RSLTimerControlBlock *Next;
RSLTimeoutSize timeoutSec;
RSLTimeoutSize timeoutNSec;
RSLTimeoutSize repeatTimeout;
void *data;
RSLMessage *timeoutMessage;
RSLMemorySize counter;
RSLActorIndex actor;
RSLPortIndex sap;
RSLFlags flags;
} RSLTimerControlBlock;
The main loop extension function is responsible for the interface with the operating system calls needed to implement the timing service. Usually a blocking call with some sort of time out parameter is required. For example, the VxWorks function to block on a semaphore semTake() has a time out parameter. The higher the resolution of the system timer on the operating system, the higher the resolution the timer service can provide. The minimum resolution depends on the time out parameter on the blocking mechanism (i.e., semTake) and the internal clock of the OS, whichever is greater. A typically value is 10 ms.
A main loop function (called TimerFunction() in the examples provided) has the following set of operations :
Integrating with Interrupt Service Routines
It is sometime necessary to notify an ObjecTime application of external events. Interrupts are commonly used in embedded and real-time systems to indicate such events. Interrupts are usually handled by specialized code called Interrupt Service Routines (ISR). These routines are typically small and fast and have limited operating system access. You cannot send messages directly from an ISR to an ObjecTime model. An intermediate mechanism is needed to signal the ObjecTime application that the interrupt has occurred. A typical mechanism is a semaphore, which is posted by the ISR and waited on and read by the ObjecTime application. In this situation we must provide a mechanism to allow an actor to wait on an external event (the semaphore) and still handle internal ObjecTime messages. As in the timer actor, we must provide a main loop extension and message signaling (wake-up) function.
The examples provided show how an interrupt-like mechanism can be handled in Solaris and Tornado (VxWorks). In the Solaris example the C_ISR_Layer_Solaris actor waits on a semaphore that is signaled by external code. The external code is a signal handler function awoken by the itimer signal which posts to a shared semaphore. The C_ISR_Layer_Solaris actor is waiting for this semaphore and wakes up. A shared global data element is used to indicate that an interrupt has occurred and another is used to indicate how many have occurred.
In the Tornado (VxWorks) example the external event is the watchdog timer which calls a function every time it expires. This watchdog function then signals a semaphore which, in turn, signals the C_ISR_Layer_Tornado actor. Although the interrupt mechanism is different in each example the way they are handled at the model level is similar. We present a generic description of the initialization and operation here, for more detailed analysis the example models provide a good base for customization to your needs.
Note that like the timer actor, described in the preceding section, it is recommended that an ISR interface actor such as the C_ISR_Layer actors from the example model be placed on their own physical thread. This is a good idea because of the blocking nature of its operation and to assign the appropriate priority to the thread performing the service. Timer, ISR and IPC threads are generally run at a higher priority than other threads in the application.
Initialization
The following diagram shows the initialization state machine for the C_ISR_Layer actor. This technique is a good modeling example and is used in the C_IPC_Layer actor as well.

The initialize transition is used to initialize Extended State Variables (ESVs) and call any necessary internal initialization routines. In the C_ISR_Layer actors this is used to initialize the semaphore and save the identifier in this->semaForSync. An ESV, this->internalSetupSucceeded is set if initialization is successful. The "internalOk" choice point is used to check this value. Any failure results in a transition to the Reset state. If the internal initialization is successful then external initialization is performed, global variables and other necessary initialization are performed in the true transition from the "internalOk" choice point. An ESV, this->externalSetupSucceeded is used to indicate successful initialization. Any failure results in a transition to the Reset state. Successful initialization ends up in the Operational state. It is at this time that RSLRegisterExternalInterface() and RSLRegisterMessageSignallingInterface() are called. In the case of the C_ISR_Layer actor the main loop extension function is called waitForSema() and the message signaling function is called wakeUp().
Operation
Once in the operational state, the C_ISR_Layer actor accepts requests from the client actor to forward interrupt notifications or to ignore them. The Operational super-state is shown below:

The function waitForSema() is called each time through the main loop of the thread containing the C_ISR_Layer actor. This function blocks on the semaphore which can be signalled by one of two methods. If the interrupt occurs the ISR will set a global flag and post to the semaphore thus waking up the blocked actor. The second method will be when another ObjecTime thread sends a message to the C_ISR_Layer actor and the wakeUp() function is called. The state of the global flag (in the example it is ISRFired) will determine if an interrupt has occurred. If this flag is set then a message is posted to the C_ISR_Layer actor (in effect, a message to self) using the RSLPortEnqueue() function. This triggers the incomingISRNot or the incomingISR transition. This is forwared to the client actor as appropriate.
Integrating with Inter-Process Communication services
Inter-Process Communication can be in the form of BSD sockets, OS message queues, pipes, remote procedure calls, etc. We have chosen sockets for the IPC mechanism in the model example but the techniques used are applicable to other IPC mechanisms. The fundamental problem with integrating ObjecTime with an IPC mechanism is the blocking nature of reading from sockets, pipes or message queues. You can usually send data from transition code in an actor since writing to a socket, pipe or message queue is usually non-blocking, i.e., the call returns almost immediately. Writing to an IPC mechanism does not interfere with the ObjecTime messaging mechanism nor the run-to-completion requirement of transition code. Reading from sockets, pipes and message queues is usually a blocking call. Blocking on an OS or I/O call during transition code interferes with the correct operation of an actor. There are three possible methods to handle this :
Options 1 and 2, above, are valid techniques but are usually unacceptable due to performance constraints. Option 3, using the same technique described in the previous sections, is the method of choice. The C_IPC_Layer actor and its sub-classes, C_IPC_Layer_Solaris and C_IPC_Layer_Tornado from the model example demonstrate a simple IPC interface to BSD sockets. The following sections will describe the generic interface, the details of the BSD socket implementation can be found in the model examples.
Initialization
The initialization of the C_IPC_Layer actor follows the technique described in the previous section for the C_ISR_Layer actor. Internal initialization is performed first such as registering the SPP interface and initializing ESVs for the actor. External initialization is performed next, this requires the creation and connection of the client socket (connects to external server) and the internal socket (used internally to wake up the actor). The final step is the calls to RSLRegisterExternalInterface() and RSLRegisterMessageSignallingInterface().
Operation
The C_IPC_Layer actor provides an SPP interface to the rest of the model that allows multiple actors to send and receive data from the IPC layer. Messages coming from the application actors out to the IPC layer are sent directly from transition code since writes to the IPC layer are usually non-blocking. To read data from the IPC layer the C_IPC_Layer actor uses the main loop extension function in a similar fashion to the timer and C_ISR_Layer actors. To block on multiple sockets (one socket for connection to another node and one for internal use) the select() function is used. The select() function allows I/O multiplexing of files and sockets. The select() function will return if any event occurs on the file descriptors that being monitored. In order for the C_IPC_Layer actor to receive messages it uses the message signaling function wakeUp() to send data on the internal socket which will unblock the C_IPC_Layer actor (it will be blocked in select() function call) and allow it to process ObjecTime messages.
The use of other IPC mechanisms such as a message queue will require the use of a unique message identifier for the wake up mechanism. The wakeUp() function in this case will post a unique message to the queue which will wake up the C_IPC_Layer actor. The C_IPC_Layer actor can detect this unique message to determine if it was woken up because of an ObjecTime message or an external IPC message. In the model examples the C_IPC_Layer actor is using sockets which are bound at each end, the wakeUp() function cannot send data on the external socket so a second internal socket is required.
Using ISR, IPC or Timer actor in your model
In many cases the example actors provided with this technical note can be merged into your ObjecTime model and used with a little modification. The typical changes required for timer actors are in the detailed code for initialization of the waiting mechanism (e.g., semaphores) and the calculation of the current time and the amount of time to wait for the next timeout (e.g., semaphore timeout period.) If you are using one of the supported platforms such as Solaris you can drag and drop the TimerSolarisMT (multithreaded timer actor) or the TimerSolarisST (single threaded timer actor) into your model and start using it right away.
The ISR actors will require modification in the detail code for setting up the ISR (initializeISR function), enabling interrupts (enableInterrupt function), and disabling interrupts (disableInterrupt() function.) Modifications will be required depending on the signaling mechanism used, for example, the OS calls needed to initialize, post and pend (wait) on a semaphore.
The IPC actor is fairly portable to other implementations of BSD sockets and can probably be used with little modification. To use a different IPC technique, such as OS message queues, a fair amount of modification will be required but the basic technique is the same.
It is recommended that in a multithreaded model, an IPC, ISR and timer actors should be placed on their own physical thread. This means these actors must be placed at the top-level of the application structure and be assigned to a logical thread via the RSLThreadMap() function. These logical threads must then be assigned to individual physical threads using the Thread Browser. Here is the RSLThreadMap function from the model example:
/*
thread ISR_Logical {
NumberOfInternalMessages 10;
NumberOfTCBs 0;
} c_ISRLayer_Solaris;
thread TimerLogical {
NumberOfInternalMessages 10;
NumberOfTCBs 0;
} timerSolarisMT;
thread IPCLogical {
NumberOfInternalMessages 10;
NumberOfTCBs 0;
} c_IPCLayer_Solaris;
*/
The thread mapping from logical to physical threads is as follows :

Summary
This technical note describes how to design and integrate timer services, ISR notification and IPC mechanisms into your ObjecTime Developer for C application. The fundamental technique is to use the main loop extension function to isolate blocking calls within a thread and the message signaling function to wake up from the blocking call in order to receive ObjecTime messages. The various support functions provided in the C TargetRTS API are also described and model examples are provided as a base for implementation.
Example Model :
The example model referenced in this document is available for download. A "readme" file is provided in the downloaded tar or zip file. Read this file before trying to compile and link the model
Download this file for Solaris