IBM
Contents Index Previous Next



Threaded Integration


Introduction

The first part of this section is about the Threaded integration. The second part is about the TCP/IP feature. With the Threaded and TCP/IP features you can partition an SDL system into several threads that can execute in a distributed environment.

The New threaded integrations described last has been developed by IBM Rational in the IBM Rational Tau product. Our intention, however, is that the integrations should be possible to use both in SDL Suite (Cadvanced) and Tau/Developer (AgileC and C Code Generator) products.

Implementation Details for Threaded

The main areas where a Threaded differs from a Light integration are:

Symbol Table Structures and Global Variables

Each thread has a global variable of type xSystemData in Threaded. In Light, there is only one global variable of this type for the entire system. In Threaded this variable is initialized in yInit() and is one of the parameters in the macro for creating a new thread.

The data structure xSystemData has a couple of new entries:

...
#ifdef THREADED
xInputPortRec xNewSignals;
THREADED_THREAD_VARS
#endif
...

The entry xNewSignals is used when a new signal is received. The signal is first linked into the receiver's xNewSignals queue before it is handled in the receiver's xMainLoop().

The entry THREADED_THREAD_VARS is a macro that contains different thread variables like the semaphore handles xInputPort.

Global variables are declared in the macro THREADED_GLOBAL_VARS:

#elif THREADSOLARIS
.....
#define THREADED_GLOBAL_VARS \
pthread_mutex_t xListMutex; \
pthread_mutex_t xExportMutex; \
sem_t xInitSem; \
int xNumberOfThreads; \
int QueueCounter; \
int xInitPhase; \
pthread_attr_t Attributes ;
#endif /* THREADED_ALTERNATIVE_SIGNAL_SENDING */
#endif

Process Creation

There are three macros related to the creation of threads in a Threaded:

Parameters Explanation

F

The entry point of the thread, i.e. the SDL kernel function xMainLoop()

SYSD

A variable of type xSystemData

THREAD_STACKSIZE

The stacksize for the thread is specified in the Deployment Editor

THREAD_PRIO

The thread priority specified in the Deployment Editor

THREAD_MAXQUEUESIZE

The maximum number of messages/signals in the input queue of the thread, as specified in the Deployment Editor. This parameter is only used if the alternate signal sending model is used.

THREAD_MAXMESSIZE

The maximum size of a message/signal. This parameter is ignored at the moment in both implementation models. The maximum size of a message/signal in the alternate signal sending model is always the size of the pointer to an SDL signal (sizeof(xSignalNode).

Note:

Thread parameters for individual threads can be specified in the Deployment Editor. If no values are specified, default values will be used.

The xMainLoop() function is the entry point for the thread. First in this function is the macro

THREADED_THREAD_BEGINNING(SYSD)

The main purpose of this macro is to wait for the start-up semaphore xInitSem. No thread is allowed to start executing until ALL static processes/threads have been created.

Sending Signals

In the default model, signals are sent in the same way as for a Light integration except that they are first linked into the xNewSignals queue.

In the xMainLoop() function, the xNewSignals queue is checked for new entries. If a new signal is available, it is sent to the process itself with the SDL_Output() function. See the following extract from the xMainLoop() function:

........

THREADED_LOCK_INPUTPORT(((xSystemData *)SysD))
while (((xSystemData *)SysD)->xNewSignals.Suc != (xSignalNode)&((xSystemData *)SysD)->xNewSignals)
SDL_Output(((xSystemData *)SysD)->xNewSignals.Suc xSigPrioPar(((xSystemData *)SysD)->xNewSignals.Suc->Prio), 0);
THREADED_UNLOCK_INPUTPORT(((xSystemData *)SysD))

.........

The Alternative Signal Sending Model.

In the alternative signal sending model, an OS message/signal is sent containing a pointer to the SDL signal. For this, a message queue must be created for each thread at thread creation. How this is done differs from OS to OS. See : Signalling in Threaded Integration.

The sender sends the pointer to the SDL signal with the OS_Send primitive. The receiver receives the message, extracts the pointer to the SDL signal and links it into the xNewSignal queue. After this, the signal is handled in the same way as in the default model.

New Macros

The same source files are used in a Threaded integration as in a Light. The only SDL kernel source files that are affected by the Threaded integration are scttypes.h, sctsdl.c and sctos.c.

Most of the OS specific code is found in the scttypes.h file. The following macros are new for Threaded:

MACRO NAME Explanation

THREADWIN32

Main macro for Threaded Windows integration

THREADSOLARIS

Main macro for Threaded Solaris integration

THREADVXWORKS

Main macro for Threaded VxWorks integration

THRAEDOSE

Main macro for Threaded OSE integration

THREADED_ALTERNATIVE_SIGNAL_SENDING

Main macro for using the alternative signal sending model.

THREADED_POSIX_THREADS

Main macro for the Threaded integration model defining the kernel specifics

THREADED

Internal macro used only in the kernel source files

THREADED_TRACE

Enabling textual execution trace

THREADED_OSTRACE

This macro is mapped to a printf statement.

THREADED_ERROR

Enabling textual error printout when calling OS primitives.

THREADED_ERROR_VAR

Defines a variable used in THREADED_ERROR_RESULTS.

THREADED_ERROR_RESULT

Stores the return result after calling an OS primitive.

THREADED_ERROR_REPORT

Checks the value of the return variable and if ERROR makes a printout.

THREADED_ERROR_REPORT_NULL

Checks the value of the return variable and if ERROR makes a printout.

THREADED_ERROR_REPORT_NEG

Checks the value of the return variable and if ERROR makes a printout.

THREADED_ERROR_REPORT_OPEN_NEG

Checks the value of the return variable and if ERROR makes a printout.

THREADED_ERROR_REPORT_WAIT_NEG

Checks the value of the return variable and if ERROR makes a printout.

THREADED_GLOBAL_VARS

Global variable defines.

THREADED_GLOBAL_INIT

Initialization of global variables like semaphores.

THREADED_THREAD_VARS

Definitions of thread variables.

THREADED_THREAD_INIT

Initialization of thread variables.

THREADED_THREAD_BEGINNING

Wait for xInitSem to be released.

THREADED_LOCK_INPUTPORT

Protect the input queue by taking the semaphore.

THREADED_UNLOCK_INPUTPORT

Releasing the semaphore for the input queue.

THREADED_WAIT_AND_UNLOCK_INPUTPORT

Wait for next message/signal to arrive or the next internal timer to expire.

THREADED_SIGNAL_AND_UNLOCK_INPUTPORT

Send a signal and release the semaphore for the input queue.

THREADED_LISTREAD_START

Protect global active and available lists with a semaphore before reading it.

THREADED_LISTWRITE_START

Protect global active and available lists with a semaphore before writing to it.

THREADED_LISTACCESS_END

Release the semaphore protecting a global active or available list.

THREADED_EXPORT_START

Protect export and import actions by taking a semaphore

THREADED_EXPORT_END

Release the semaphore after export and import actions.

THREADED_START_THREAD

Start a new thread.

THREADED_STOP_THREAD

Terminate a thread.

THREADED_AFTER_THREAD_START

Synchronize the start-up of newly created threads.

THREADED_SEND_OUTPUT

Send messages/signals. Used in both models.

Textual and MSC Trace in Threaded

Textual SDL Trace, similar to the Textual Trace in the SDL Suite Simulator, can be turned on by selecting the flag SDL trace (will set the flag THREADED_XTRACE) in the Target Library/Kernel window in Targeting Expert.

On-line MSC trace is possible when running an application under a softkernel on Windows or UNIX. Select the flag MSC trace (will set the flag THREADED_MSCTRACE) in the Target Library/Kernel window in Targeting Expert.

API for interfacing a Threaded Integration

An API is available with a number of useful functions/MACROS that will facilitate the work of sending/receiving signals between external processes/threads and an SDL Threaded Application.

The following functions and MACROS are available:

SDL_PId xThreadedRegExtTask()

This function will return an SDL PId representing the calling External process/thread. It works in the following way:

  1. Get a ThreadId for the calling process/thread.
  2. Allocate and assign an SDL struct (xPrsIdRec) representing the external process/thread.
  3. Call the SDL kernel function xGetPId() to get an SDL_PId.
  4. If MSC trace is on it will generate an entry for the process/thread in the MSC diagram.
  5. Allocate and initialize an SDL "system data record" for the external process/thread.
  6. Create a queue for the calling process/thread.
  7. Assign the "system data record" entry in the SDL_PId.
  8. Return the SDL_PId.
xSignalNode xThreadedReceiveSDLSig(SDL_PId)

This function will wait for an SDL signal indefinitely. When a signal arrives the signal will be taken out of the queue and return the signal. If MSC trace is on the signal will also be traced in the MSC diagram.

xSignalNode xThreadedReceiveSDLSig_WithTimeOut(SDL_PId, SDL_Duration)

This function will work in the same way as xThreadedReceiveSDLSig() except that it will only wait for the specified time.

THREADED_ASSIGN_SDL_SIG_PARAMS(sigptr,signame,paramno,param)

This macro will use the macro token operator ## to concatenate the sigptr, signame, paramno into a simple assignment of the signal parameter with the specified number.

Note:

This macro should only be used with simple datatypes where assignments can be done using the simple assignment operator "="

THREADED_GET_SDL_SIG_PARAM(sigptr,signame,paramno,param)

This macro will assign the user's param the value of the signal parameter with the specified number.

Note:

This macro should only be used with simple datatypes where assignments can be done using the simple assignment operator "="

SDL_Output()

This is the standard SDL kernel function that should be used when sending signal into a Threaded Application. The following must be done before a signal can be sent from an external process/thread into a Threaded Application.

  1. Use the xThreadedRegExtTask/xThreadedRegExtTask_WithQueue function to get an SDL_PId.
  2. Call the SDL kernel function xGetSignal with the following parameters: signalId, Receiver, Sender.
  3. Assign signal parameters using the API macro THREADED_ASSIGN_SDL_SIG_PARAMS()
  4. Call the SDL_Output function with the SDL signal.

For an example see Annex 6: Building a Threaded Integration

THREADED_START_EXTTASK

This macro is normally empty. It is called after all static SDL threads are created in the THREADED_AFTER_THREAD_START macro (last in generated c file for application).

By defining this macro to user can make the application start external tasks.

THREADED_SIMPLE_EXAMPLE

If this flag is defined the external tasks in the simple example will be started.

Implementation Details for Different RTOS

VxWorks

The Threaded integration for VxWorks has been developed using a Windows Softkernel.

The following VxWorks header files are used:

#ifdef THREADVXWORKS

#include "errno.h"
#include "vxWorks.h"
#include "semLib.h"
#include "msgQLib.h" /* msgQCreate msgQDelete msgQSend msgQReceive *//* msgQNumMsgs */
#include "taskLib.h" /* taskSpawn */
#include "semaphore.h" /* POSIX semaphores */
#endif

The following VxWorks primitives have been used:

VxWorks primitives Explanation

sem_init(..),

sem_wait(..), sem_post(..),

sem_destroy(..),

POSIX semaphores are frequently used, e.g. to protect the input queue, to synchronize start-up...

msgQCreate(..),

msgQReceive(..),

msgQSend(..),

msgQDelete(..)

Message queues are used in both models in VxWorks. The message queue is created in the THREADED_START_THREAD MACRO.

taskSpawn(..),

taskDelete(..),

taskSuspend(..)

taskSpawn is used for creating a thread. The name entry is not used.

taskSuspend is only used by the "Main" thread. It is called from the macro THREADED_AFTER_THREAD_START.

taskIdSelf()

Used in taskDelete() and taskSuspend()

Thread Parameters Default values

DEFAULT_STACKSIZE

800

DEFAULT_PRIO

100

DEFAULT_MAXQUEUESIZE

250

DEFAULT_MAXMESSIZE

sizeof(xSignalNode)

Solaris

The Threaded integration for Solaris has been developed in the following environment:

Sun Solaris 2.6 with Sun WorkShop compiler cc version 5.0.

Included header files for Solaris:

#elif THREADSOLARIS
#include <pthread.h>
#include <semaphore.h>
#ifdef THREADED_ALTERNATIVE_SIGNAL_SENDING
#include <mqueue.h>
#include <signal.h>
#include <time.h>
#endif /* THREADED_ALTERNATIVE_SIGNAL_SENDING */
#endif

The following Solaris primitives have been used:

Solaris primitives Explanation

sem_init(..),

sem_wait(..), sem_post(..),

pthread_mutex_init(..),

pthread_mutex_lock(..),

pthread_mutex_unlock(..),

pthread_mutex_destroy(..)

pthread_cond_init(..),

pthread_cond_wait(..),

pthread_cond_timedwait(..),

pthread_cond_signal(..),

pthread_cond_destroy(..)

The sem_.. primitives are only used in the start-up to synchronize static processes/threads.


The pthread_mutex_.. primitives are used to protect the input queues and other queues.


The pthread_cond_... primitives are used to synchronize sender and receiver in the default model.

pthread_attr_init(..),

pthread_attr_setstacksize(..),

pthread_attr_setschedpolicy(..),

thread_attr_setdetachstate(..),

pthread_attr_setscope(..)

The pthread_attr_.. primitives are used to set the attributes of a thread before it is created.

mq_open(..),

mq_close(..),

mq_receive(..),

mq_unlink(..),

mq_send(..)

These primitive are only used in the alternative signal sending model.

pthread_create(..),

pthread_exit(..),


These primitives are used when a thread is created and when it terminates.

timer_settime(..),

timer_delete(..)

These primitives are used for setting a timer before the mq_receive() is called in the alternative signal sending model. The time-out for the timer is the duration for the next internal SDL timer to expire. When the timer expires the signal_handler function sets the error message to EINTR.

Thread Parameters Default values

DEFAULT_STACKSIZE

15000

DEFAULT_PRIO

10

DEFAULT_MAXQUEUESIZE

128

DEFAULT_MAXMESSIZE

sizeof(xSignalNode)

OSE

The Threaded integration for OSE has been developed with an OSE Softkernel (version 4.3) on Solaris 2.6 using the gcc compiler (version 2.95).

The following OSE header file is include:

#ifdef THREADOSE
#include "ose.h"
#endif /* THREADOSE */

The following OSE primitives have been used:

OSE Primitives Explanation

create_sem(..),

wait_sem(..),

signal_sem(..),

These primitives are used for protecting input queues, other queues and for synchronizing start-up

receive(..),

receive_w_tmo(..),

send(..)

These primitive are used for receiving/sending signals in both models.

Receive/send is also used to pass the start-up parameter xSysD to a new thread, since OSE does not support start-up parameters in the create_process primitive.

alloc(..),

free_buf(..)

These primitives are used for allocating and returning OSE signals.

start(..)

This primitive is used for starting a newly created thread.

kill_proc(..)

This primitive terminates a thread

stop(..)

This primitive stop the execution of a thread. Used in the THREADED_AFTER_THREAD_START macro.

current_process()

Used in the kill_proc() and stop() calls.

create_process(..)

This primitive is used for creating Threads.

Thread Parameters Default values

DEFAULT_STACKSIZE

1024

DEFAULT_PRIO

8

DEFAULT_MAXQUEUESIZE

1024

DEFAULT_MAXMESSIZE

sizeof(xSignalNode)

Declaration of xMainLoop().

The declaration and definition of the thread's entry point (xMainLoop()) is special in OSE:

Declaration in scttypes.h;

.......
#elif THREADOSE
extern OSENTRYPOINT xMainLoop;
#else
extern void xMainLoop XPP((xSystemData *));
#endif

Definition in sctsdl.c:
......
#elif THREADOSE
OS_PROCESS(xMainLoop)
#else
#ifndef XNOPROTO
void xMainLoop (xSystemData * SysD)
#else
void xMainLoop (SysD)
xSystemData * SysD;
#endif
#endif
..............

Definition of NIL.

NIL is defined in the OSE kernel and must not be redefined in the SDL kernel.
#ifndef THREADOSE
#define NIL 0
#endif /* THREADOSE */

Forward declaration of xSignalNode.

The xSignalNode must have forward declaration very early in the scttypes.h file since it is used in the union SIGNAL definition.

.............
typedef struct xSignalStruct *xSignalNode;
union SIGNAL
{
SIGSELECT sigNo;
xSignalNode SDLSig;
xSystemDataPtr SysD;
};

.....
Further down in the scttypes.h file.

.....
#ifndef THREADOSE
typedef struct xSignalStruct *xSignalNode;
#endif /* THREADOSE */
.....

Windows

The Threaded integration for Windows has been developed on Windows XP with the Microsoft C++ compiler.

The following header files for Windows are included:

#ifdef THREADWIN32
#include "limits.h"
#include "windows.h"
#include "dos.h"
......

The following Windows primitives are used in the Threaded integration:

Windows primitives Explanation

CreateSemaphore(..),

ReleaseSemaphore(..),

WaitForSingleObject(..),

CloseHandle(..)

These primitives are used for protecting input queues, other queues and synchronization in start-up.

One extra semaphore, xInitQueue, is used when a newly created thread is creating his own input queue.

PeekMessage(..)

This primitive is used in the THREADED_THREAD_BEGINNING macro in the alternative signal sending model. This primitive force the thread to create a message queue. It is used together with the xInitQueue semaphore.

GetMessage(..),

PostThreadMessage(..),

These primitives are used for receiving/sending messages in the alternative signal sending model.

SetTimer(..),

KillTimer(..)

These primitives are used to set an OS timer that will signal the thread when it expires. The timeout of this timer is the duration until the next internal SDL timer expires for this thread.
The window message will be set to WM_TIMER if the OS timer expires.

CreateThread(..),

ExitThread(..)

These primitives are used for creating and terminating threads.

SetThreadPriority(..)

This primitive is used for setting the priority of the thread.

SuspendThread(..)

Called by the "main" thread in the macro THREADED_AFTER_THREAD_START

GetCurrentThread()

Used in the SuspendThread macro.

Thread Parameters Default values

DEFAULT_STACKSIZE

0 (Automatically resized by OS)

DEFAULT_PRIO

THREAD_PRIORITY_NORMAL

DEFAULT_MAXQUEUESIZE

1024

DEFAULT_MAXMESSIZE

sizeof(xSignalNode)

Signal Sending over TCP/IP

Introduction

For applications using the Threaded integration model, a plug-in module for TCP/IP communication is available. The module supports signal sending between distributed SDL applications via a TCP/IP connection. ASCII Encoding/Decoding is used for the conversion between signal and transport format. The module is delivered as C source code which is integrated and built together with code generated by the CAdvanced SDL to C Compiler.

The TCP/IP adapter supports the four operating systems for which Threaded integrations are available. These are Windows, Solaris, VxWorks and OSE.

Architecture

The TCP/IP functions are called from the environment functions xInitEnv and xOutEnv. The functions are included by setting the XENV_INC flag to "tcpipcomm.h". The tcpipcomm.h file contains #define directives that translates macros in the environment file to function calls in the TCP/IP adapter.

When a signal is sent to the environment using the xOutEnv function, xSendSignal is called. A connection is set up with a remote server. The signal destination is specified using a user-implemented function. When the connection is accepted, the signal is encoded into ASCII format and sent via a TCP/IP socket. The session is then closed.

From xInitEnv, a thread is started which polls a socket for incoming connections. When a connection from a remote client is accepted, a new thread is started, which receives and decodes data for one signal. When the signal is decoded, it is inserted in the signal queue of the SDL system. The thread finishes its execution after the connection is closed.

An executing SDL system thus acts as a server when signals are received from the environment and as a client when a signal should be sent to the environment.

The environment file of an SDL application may not be modified if the TCP/IP adapter is to be used. Making modifications may override the TCP/IP signal sending functionality. If you want to use the TCP/IP adapter together with other external code from an environment file, please consult Configuration.

File Structure

The TCP/IP adapter consists of four files, located in $sdtdir/tcpip/.

The tcpip directory is referenced relative to the $sdtdir variable.

Hint: Using TCP/IP with a new $sdtdir

If you use an $sdtdir that is different from the default $sdtdir (in the SDL Suite installation directory), be sure to copy the tcpip directory to the new location. Otherwise, you may encounter problems in finding the TCP/IP files at compilation.

Note: Pointers as Signal Parameters

Distributed components execute in separate memory spaces. Care must be taken so that pointers are not sent as signal parameters over TCP/IP and used in a remote component.

Routing of Signals

For each signal that is sent from your SDL application, you must specify a destination in the form of an IP address (or host name) and a TCP port number. This information should be accessed from a routing function which is called when a signal is sent from the SDL application.

The routing function is made accessible from the TCP/IP adapter by setting the flag XROUTING_INC to the name of a routing header file. This is a plain C header file where the macro XFINDDEST(OUTSIG, SIGNAME, IP, PORT) is defined as a function. OUTSIG and SIGNAME are in parameters. IP and PORT are out parameters.

OUTSIG should be declared as xSignalNode*. From this parameter, signal data such as parameters can be accessed. SIGNAME holds the name of the signal and is declared as char*. In many cases, the signal name is sufficient routing information. The IP and PORT variables should be set to the host address and port number. IP should be declared as char* and PORT as int*.

The routing function should be implemented in a C file which is compiled and linked together with the application. The server IP address should be given as char*, e.g. "255.255.255.255" (dotted decimal notation) or "server.the_company.com" (hostname notation). The server port number should be given as int, e.g. 8888.

On the server side, the IP address is inherent to the host that the application resides on. The port number on which a server should listen for incoming connections should be specified using the flag XSERVPORT.

Note: Usage of Port Numbers

Generally, TCP port numbers below 1024 are reserved by operating system services or internet applications. For instance, the port numbers 21 and 23 are used by FTP and port number 80 is used by HTTP. If a port number is occupied, you will get an error message at start-up and the server thread will not start. It is recommended that you select port numbers larger than 1024 for your SDL application.

Example 557 : TCP/IP Adapter Routing Settings

This example shows a situation where different signals should be directed to different recipients. An SDL system is partitioned into three components (i.e. executable files) using the Threaded integration model. From component 1, two different signals can be sent. Sig1 should be sent to component 2 and Sig2 should be sent to component 3.

Component 2 resides on a host called "host2". It uses port number 7001 for listening for incoming connections, which means that XSERVPORT is set to 7001 (XSERVPORT=7001). Component 3 resides on "host3" and listens on port number 7001 (XSERVPORT=7001). Please note that the ports are not in conflict since the hosts are different.

For component 1, a routing function is implemented. The flag XROUTING_INC is set to "router.h". The routing header file has the following contents:

#define XFINDDEST(OUTSIG, SIGNAME, IP, PORT)\ 
xGetDestination(OUTSIG, SIGNAME, IP, PORT)

The routing C file, router.c, contains the implemented function:

#include "stdlib.h"

void xGetDestination(xSignalNode *sig, char 
*sigName, char *IPAddr, int *Port)
{
  if (strcmp(sigName, "Sig1") == 0)
  {
    strcpy(IPAddr, "host2\0");
    (*Port)= 7001; /* Dereferencing */
  }
  else if (strcmp(sigName, "Sig2") == 0)
  {
    strcpy(IPAddr, "host3\0");
    (*Port)= 7001;
  }

  return;
}

router.c is then compiled and linked together with the generated code, the coder library and the TCP/IP adapter. This example shows a fairly trivial routing scenario. The routing is based only on the name of the signal. The xSignalNode pointer is not used.

Error Handling

The TCP/IP adapter contains basic error handling. Error checks are performed when encoding/decoding, socket and thread functions are invoked. If THREADED_ERROR is defined, an error message is printed on stdout with the name of the function where the error occurred. A platform-dependent error code is included in the error message. For a description of the error code, consult the User's Manual of your target operating system.

An error implies that the function where the error occurred exits. Clean-up is performed, which means that the application can continue its execution.

A special case to consider is when an error occurs in the server thread function, which runs statically during the execution. The thread exits and must be restarted manually.

If THREADED_TRACE is defined, the execution of the TCP/IP adapter is logged onto stdout when signals are encoded, sent, received and decoded.

Example 558 : TCP/IP Adapter Error Message

An error occurs when a signal should be sent via the TCP/IP adapter. The following is logged on stdout:

ERROR xSendSignal/SCM_CONNECT: 146

The target platform is Solaris. The error code indicates that the connection was refused, probably because a server can not be found at the specified address. xSendSignal exits and the application continues its execution.

Configuration

The TCP/IP adapter is configured using compilation flags. The basic configuration can be done using the TCP/IP Connection Wizard in the Targeting Expert.

Including the TCP/IP adapter

The TCP/IP adapter requires environment files, environment header files and ASCII encoding/decoding for correct operation. These options are activated when the TCP/IP Signal Sending check box is enabled in the Targeting Expert's TCP/IP Connection Wizard.

Server Port Number

For a component that receives signals, a TCP port number must be specified. The server thread uses this port number to listen for incoming connections.

The port number is set in a text box in the TCP/IP Connection Wizard. You can also set the port manually by setting the flag XSERVPORT to the desired value. If no port number is specified, the port number is set to 5000 by default.

Routing

You must manually implement a routing function, so that a destination is specified for every SDL signal that is sent to the environment. The function must be declared in a C header file. The file is specified in a text box in the TCP/IP Connection Wizard or by setting the flag XROUTING_INC to the header file name.

The routing function implementation should be placed in a C file that is compiled and linked with the other code. It is specified using either the text box in the TCP/IP Connection Wizard or by including it in the compiler settings manually. See Example 557 for an example of how a routing function is implemented.

Using the TCP/IP Adapter with Other Environment Functionality

The TCP/IP adapter header file (tcpipcomm.h) is included from the environment file of a component. The file tcpipcomm.h contains #define statements for macros in the environment functions that invoke TCP/IP functions.

If you want to use other functionality in parallel with the TCP/IP adapter, these macros can be defined outside tcpipcomm.h. By setting the flag XEXTENV_INC to the header file you wish to use, your header file is included from tcpipcomm.h. This enables you to insert your code without modifying tcpipcomm.h. Please note that the XEXTENV flag can not be set in the TCP/IP Connection Wizard. It must be set manually.

Before using the XEXTENV_INC flag, look carefully at the #define statements in tcpipcomm.h. These must be valid for proper operation of the TCP/IP adapter.

Hint: Using External env Code with the TCP/IP Adapter

When combining the TCP/IP adapter with other environment functions, always preprocess the environment file to verify that the code is expanded as expected.

Example 559 : External Code in Combination with the TCP/IP Adapter

A file called mycomm.h contains declarations of functions that should be used in parallel with the TCP/IP adapter. From the xInitEnv function, an initialization function should be called (InitComm()). From the xInEnv function, a function for polling communication (ReadComm()) should be invoked.

In mycomm.h, the following is inserted:

#define XENV_INIT Initcomm();\
                  xInitSignalSender();\
                  xInitSignalReceiver();
#define XENV_IN_START ReadComm();

xInitsignalSender and xInitSignalReceiver are taken from tcpipcomm.h, which contains the following:

#ifndef XENV_INIT 
#define XENV_INIT xInitSignalSender();\
                  xInitSignalReceiver();
#endif
#ifndef XENV_IN_START
#define XENV_IN_START
#endif

The #defines in tcpipcomm.h are overridden. Still, the original calls are invoked, which ensures that the TCP/IP adapter executes properly.

Using Thread Parameters

Some thread parameters in the TCP/IP adapter can be set to fine-tune performance of your Threaded SDL application. The TCP/IP adapter threads do not use OS queues. Two parameters can be set: Thread Priority and Thread Stack Size. These are set manually using flags. They can not be set using the TCP/IP Communication Wizard.

The server wait thread uses the following flags:

XSERVTHRPRIO
XSERVTHRSTACK

The signal receiver threads use the following flags:

XRECVTHRPRIO
XRECVTHRSTACK

Set the flags to values that are specific to the target platform used.

New threaded integrations

Overview

The threaded integrations with Real-time operating systems described in this document has been developed by IBM Rational in the IBM Rational Tau product. Our intention, however, is that the integrations should be possible to use both in SDL Suite (Cadvanced) and Tau/Developer (AgileC and C Code Generator) products.

The difference between the currently existing threaded integrations and new ones presented here is not that large. The integration principles are almost exactly the same. The major difference is that the current integrations are expressed using macros, while the new ones uses a functional interface. Another difference is that all the current integrations provided by IBM Rational are mixed into one .h file, while in the new integration, each RTOS integration is implemented using one .h and one .c file. Both these changes are made to increase the readability and to simplify debugging. The change in file structure makes it also easier to implement new integrations and for a customer to modify an integration.

The currently available integrations are listed below.

Current integrations:

The new integrations:

Each RTOS integration consists of two files with the names rtapidef.h and rtapidef.c. The rtapidef.h file is included in the scttypes.h file, while the rtapidef.c file is included in the sctsdl.c file. As rtapidef.c is included in sctsdl.c there are no new files to compile and the make files are not effected, except for some compilation options discussed below.

When it comes to compilation none of the compilation switches used for the current threaded integrations should be defined. To use the new integrations the following should be given:

Example 560

cc -DSCTAPPLCLENV -DTHREADED -I/some/suitable/path

In the remaining part of this document the new threaded integrations are described in detail. This documentation is provided for customer who want to understand and possibly modify the integrations or to make new integrations themselves. The documentation was initially written for the AgileC code generator in Tau/Developer and has been somewhat adopted to Cadvanced in SDL Suite.

Threaded integrations

To implement a new integration or to understand an existing one it is recommended to use this manual together with the code for some existing integration(s). There are some major aspects that have to be handled to implement an integration with real-time operating system.

To explain the details in these integration aspects the POSIX integration will be used as an example. Apart from the code mentioned below the rtapidef.h should include the necessary system include files to be able to access the concepts needed.

Example 561 : Includes in rtapidef.h for POSIX

#include <pthread.h>
#include <sched.h>
#include <semaphore.h>
#include <time.h>
#include <sys/time.h>

If the RTOS has any requirements on the main function, which might be the case, it is possible to rename the main function included in uml_kern.c by defining XMAIN_NAME to for example:

#define XMAIN_NAME agilec_main

Then the user has to implement a proper main function that calls the agilec_main function.

The clock function

To support the SDL concept of timers, a clock function is necessary. The generated code and the kernel assumes that there is a clock function called xNow that returns the current time. Time values are represented by values of type SDL_Time which is defined as:

typedef struct {
  s, ns xint32;
} SDL_Time;

xint32 is implemented as a 32-bit int. The components s and ns represent the number of seconds and nanoseconds passed from some time in the past depending on the implementation of the clock function.

There are two standard implementations of the clock function, one for UNIX like systems and one for Windows. In Windows the standard function _ftime is used to read the system clock, while on UNIX like systems the standard function clock_gettime is used.

To implement a clock function you should include code in your own rtapidef.h and rtapidef.c files according to the details below.

If timers are not used and the clock is not explicitly accessed in SDL or C, there is no need for a clock implementation. Just include the macro definition:

#define xInitSystime()

in rtapidef.h.

If a clock implementation is needed then include the following prototypes in rtapidef.h:

extern void xInitSystime(void);
extern SDL_Time xNow (void);

If no initialization function is needed then the xInitSystime function can be replaced by the macro.

#define xInitSystime()

In the file rtapidef.c the implementation of these functions should be provided. The implementations will depend a lot on the support in software and hardware for the underlying architecture.

Protection of shared data

It is necessary to protect the list of available signals, the list of available timers, and a few other things. For this four global mutexes or binary semaphores are needed. These variables should be defined extern in rtapidef.h and declared in rtapidef.c. The names of the variables should be the same as in the example given below.

Example 562 : In rtapidef.h:

extern pthread_mutex_t xFreeSignalMutex;
extern pthread_mutex_t xFreeTimerMutex;
extern pthread_mutex_t xCreateMutex;
#ifdef USER_CFG_USE_MEMORY_PACK
  extern pthread_mutex_t xMemoryMutex;
#endif

Example 563 : In rtapidef.c:

pthread_mutex_t xFreeSignalMutex;
pthread_mutex_t xFreeTimerMutex;
pthread_mutex_t xCreateMutex;
#ifdef USER_CFG_USE_MEMORY_PACK
  pthread_mutex_t xMemoryMutex;
#endif

These four variables should be initialized during the startup of the application to an unlocked state. The function xThreadInit is a proper place for this initialization. Note that the names of the variables are chosen for AgileC in Tau/Developer, and do not fully reflect their usage in Cadvanced.

Example 564 : xThreadInit

void xThreadInit (void)
{
  (void)pthread_mutex_init(&xFreeSignalMutex, 0);
  (void)pthread_mutex_init(&xFreeTimerMutex, 0);
  (void)pthread_mutex_init(&xCreateMutex, 0);
  #ifdef USER_CFG_USE_MEMORY_PACK
    (void)pthread_mutex_init(&xMemoryMutex, 0);
  #endif
  ....
}

The lock and unlock operation must also be implemented for mutexes or binary semaphores. The following two functions should be implemented.

Example 565

In rtapidef.h:

extern void xThreadLock (pthread_mutex_t *);
extern void xThreadUnlock (pthread_mutex_t *);

In rtapidef.c:

void xThreadLock (pthread_mutex_t *M)
{
  (void)pthread_mutex_lock(M);
}

void xThreadUnlock (pthread_mutex_t *M)
{
  (void)pthread_mutex_unlock(M);
}

Startup phase - creating the threads

After some basic initialization the kernel will in the main function start the specified threads. For each thread the functions xThreadInitOneThread and xThreadStartThread will be called, where xThreadInitOneThread should perform some thread specific initialization and xThreadStartThread should start the thread. Each thread should run the function xMainLoop declared in the kernel. This is performed by using a wrapper function, xThreadEntryFunc, which is defined in the integration and is the function really started in the thread.

After all the threads have been started the function xThreadGo is called in the function main. Some more information on these functions are given below.

It is important that the started threads do not execute any SDL transitions before all threads are created. Therefore the xThreadEntryFunc will as first action wait on a semaphore. The xThreadGo function will when all threads are created release this semaphore.

Many functions has a pointer to type xSystemData as parameter. This contains the local information for the thread. Among other things it contains a field of type xThreadVars, which should be defined in the RTOS integration.

Example 566

xThreadVars type in rtapidef.h

typedef struct {
  pthread_mutex_t SignalQueueMutex;
  pthread_cond_t  SignalQueueCond;
  pthread_t       ThreadId;
} xThreadVars;

where the two first fields will be discussed in the next section, and the ThreadId will be used during the startup phase to store the identity of the threads.

The code for the behavior described in this section should look something like the following example.

Example 567

In rtapidef.h:

extern sem_t xInitSem;
#if !defined(USER_CFG_USE_xInEnv) && !defined(XENV)
  extern sem_t xMainThreadSem;
#endif

extern void xThreadInitOneThread (
   struct _xSystemData *);
extern void xThreadStartThread (
   struct _xSystemData *,
   unsigned int, unsigned int,
   unsigned int, unsigned int);

In rtapidef.c:

sem_t xInitSem;
#if !defined(USER_CFG_USE_xInEnv) && !defined(XENV)
  sem_t xMainThreadSem;
#endif

void xThreadInit (void)
{
  ....
  (void)sem_init(&xInitSem, 0, 0);
}

void xThreadInitOneThread(struct _xSystemData *xSysDP)
{
  (void)pthread_mutex_init(
    &xSysDP->ThreadVars.SignalQueueMutex, 0);
  (void)pthread_cond_init(
    &xSysDP->ThreadVars.SignalQueueCond, 0);
}


static void *xThreadEntryFunc (void *xSysDP)
{
  (void)sem_wait(&xInitSem);
  (void)sem_post(&xInitSem);
  xMainLoop((xSystemData *)xSysDP);
}


void xThreadStartThread(struct _xSystemData *xSysDP,
                        unsigned int StackSize,
                        unsigned int Prio,
                        unsigned int User1,
                        unsigned int User2)
{
  pthread_attr_t Attributes;
  ....
  (void)pthread_create(&xSysDP->ThreadVars.ThreadId,
                       &Attributes, xThreadEntryFunc,
                       (void *)xSysDP);
  ....
}

void xThreadGo(void)
{
  (void)sem_post(&xInitSem);

  #if defined(USER_CFG_USE_xInEnv)
    xInEnv();  /* AgileC */
  #elif defined(XENV)
    xInEnv(xNow());  /* Cadvanced */
  #else
    (void)sem_init(&xMainThreadSem, 0, 0);
    (void)sem_wait(&xMainThreadSem);
  #endif
}

The xInitSem semaphore is used for synchronization of the threads. It is initialized in the beginning of xThreadInit to 0, that is to a blocking state. After that the xThreadStartThread once for each thread that is to be started. The function pthread_create will call the function given as third parameter (xThreadEntryFunc) with the void * parameter given as fourth parameter (the xSysD pointer) as parameter. pthread_create will also store the identity of the thread in the variable passed as first parameter. The second parameter is the properties of the thread. This will be discussed later in this section.

If any of the threads get a chance to execute before all the threads are created, these threads will hang on the sem_wait call in xThreadEntryFunc, until the main thread calls xThreadGo that will post the semaphore xInitSem once. One of the threads waiting on this semaphore will then be able to execute and will immediately post the semaphore again. This will continue until all threads are free to execute.

After that all threads are running and depending on the OS and the application properties, the main thread can perform different things. Our recommendation is to call the xInEnv function and let that function run in this thread. For more details see the discussion on xInEnv. Another alternative is to hang the main thread on a semaphore, as shown above using the semaphore xMainThreadSem (if xInEnv is not used). In this case you can post the xMainThreadSem semaphore anywhere to restart the execution of the main thread.

When the main thread returns from the function xThreadStart, the program will continue to execute in the main function and will perform a call to exit. The behavior of a threaded program when the main thread performs exit, is OS dependent. In POSIX pthreads all threads are stopped at such an action. That is the reason it is important to hang the main thread at the end of the xThreadStart function.

Now to the properties of the threads. In most RTOS, properties like stack size and priority can be set for individual threads. Four integer values can be specified.

The currently predefined integrations only makes use of the first and second values. These values are passed as parameters to the xTreadStartThread function.

How the properties are set up in detail depend on the RTOS. Please see the available integrations, in the function xThreadStartThread, for examples.

In rtapidef.h proper default values for the four xThreadData fields should be set up. These default values are used if no value is specified in the thread definition.

Example 568

#define DEFAULT_STACKSIZE     1024
#define DEFAULT_PRIO             0
#define DEFAULT_USER1            0
#define DEFAULT_USER2            0

Suspending and waking up threads

When a thread finds out that it has nothing more to do, at least just for the moment, it should suspend itself to make the processor available for other threads. The thread should then wake up again either when a timer has expired and needs to be handled, or when some other thread (including xInEnv) sends a signal that should be treated by the suspended thread.

To implement these features one mutex or binary semaphore is used together with some sort of conditional variable. We need the possibility to perform a condition wait, with or without a timeout. We need also a way to signal to a thread to wake up again. These two entities are needed for each thread and is therefore included in the xThreadVars struct mentioned earlier:


typedef struct {
  pthread_mutex_t SignalQueueMutex;
  pthread_cond_t  SignalQueueCond;
  pthread_t       ThreadId;
} xThreadVars;

The purpose of the SignalQueueMutex is to protect the signal queue where signals from the outside of the thread are inserted. The SignalQueueCond should facilitate the conditional wait.

The SignalQueueMutex should be initialized in xThreadInitOneThread. If SignalQueueCond needs to be initialized it could be performed at the same place.

Example 569

void xThreadInitOneThread(struct _xSystemData *xSysDP)
{
 (void)pthread_mutex_init(&xSysDP-
>ThreadVars.SignalQueueMutex, 0);
 (void)pthread_cond_init(&xSysDP->ThreadVars.SignalQueueCond, 
0);
}

The SignalQueueMutex is locked by using the function xThreadLock, discussed above. It is then unlocked in three different ways:

The xThreadWaitUnlock is called by the thread itself when it has come to the conclusion that it should suspend itself, while xThreadSignalUnlock is called by another thread that wants to wake up the current thread. Both functions are passed the xSysD pointer for the thread that the operation should be performed on.

Example: In rtapidef.h:

extern void xThreadWaitUnlock (struct _xSystemData *);
extern void xThreadSignalUnlock (struct _xSystemData 
*);

Example: In rtapidef.c:

void xThreadWaitUnlock (struct _xSystemData *xSysDP)
{
  #if defined(CFG_USED_TIMER) || defined(THREADED)
  #ifdef THREADED
      /* Cadvanced */
    if (xSysDP->xTimerQueue->Suc==xSysDP->xTimerQueue) 
{ 
  #else
      /* AgileC */
    if (! xSysDP->TimerQueue) {
  #endif
      (void)pthread_cond_wait(
        &xSysDP->ThreadVars.SignalQueueCond,
        &xSysDP->ThreadVars.SignalQueueMutex);
    } else {
      struct timespec timeout;
      #ifdef THREADED
          /* Cadvanced */
        timeout.tv_sec =
          ((xTimerNode)xSysDP->xTimerQueue->Suc)->
            TimerTime.s;
        timeout.tv_nsec =
          ((xTimerNode)xSysDP->xTimerQueue->Suc)->
            TimerTime.ns;
      #else
          /* AgileC */
        timeout.tv_sec = xSysDP->TimerQueue->Time.s;
        timeout.tv_nsec = xSysDP->TimerQueue->Time.ns;
      #endif
      (void)pthread_cond_timedwait(
        &xSysDP->ThreadVars.SignalQueueCond,
        &xSysDP->ThreadVars.SignalQueueMutex,
        &timeout);
      
    }
  #else
    (void)pthread_cond_wait(
      &xSysDP->ThreadVars.SignalQueueCond,
      &xSysDP->ThreadVars.SignalQueueMutex);
  #endif
  (void)pthread_mutex_unlock(
    &xSysDP->ThreadVars.SignalQueueMutex);
}

void xThreadSignalUnlock (struct _xSystemData *xSysDP)
{
  (void)pthread_cond_signal(
    &xSysDP->ThreadVars.SignalQueueCond);
  (void)pthread_mutex_unlock(
    &xSysDP->ThreadVars.SignalQueueMutex);
}

At the time when xThreadWaitUnlock or xThreadSignalUnlock is called the SignalQueueMutex will be locked and must therefore be unlocked at the end of both functions.

In xThreadWaitUnlock the thread wants to suspend itself. If timers are used and there is a timer active in the timer queue, it should wait until the timer expires or until some other thread tells it to wake up. In POSIX pthreads the function pthread_cond_wait performs exactly this. If timers are not used or there is no timer active, the thread should be suspended until someone else wakes it up. In POSIX pthreads this can be achieved with the function pthread_cond_wait.

In xThreadSignalUnlock the thread given by the parameter should be waken up. Here the pthread function pthread_cond_signal can be used.

The integrations described here are also used when the Cadvanced is used to generate threaded applications. This adds a few requirements in the implementation of a threaded integration. First a function that can stop a thread is needed.

In rtapidef.h:

#if defined(THREADED) || defined(CFG_USED_DYNAMIC_THREADS)
extern void xThreadStopThread(struct _xSystemData *);
#endif
In rtapidef.c:
#if defined(THREADED) || defined(CFG_USED_DYNAMIC_THREADS)
void xThreadStopThread(struct _xSystemData *xSysDP)
{
  pthread_mutex_destroy(&xSysDP->ThreadVars.SignalQueueMutex);
  pthread_cond_destroy(&xSysDP->ThreadVars.SignalQueueCond);
  pthread_exit(0);
}
#endif

The xThreadStopThread function should clean up the thread specific semaphores and stop the thread. It is always the thread that should be stopped that will call this function to stop itself.

Another difference is the way timers are accessed for the two code generators. This effects the details in the xThreadWaitUnlock function. Please see this function above and especially the sections under #ifdef THREADED.

In the case of the Cadvanced the RTOS integrations are accessed through a macro layer. The macros in this layer is used in the Cadvanced kernel files and in the generated code.

Example 570 : Defines in scttypes.h

The following defines are relevant (from scttypes.h):

#define THREADED_GLOBAL_VARS
#define THREADED_GLOBAL_INIT \
     xThreadInit();
#define THREADED_THREAD_VARS \
     xThreadVars ThreadVars;
#define THREADED_THREAD_INIT(SYSD) \
     xThreadInitOneThread(SYSD);
#define THREADED_THREAD_BEGINNING(SYSD)
#define THREADED_AFTER_THREAD_START \
     xThreadGo();
#define THREADED_START_THREAD(F, SYSD, STACKSIZE, 
PRIO, USER1, USER2) \
xThreadStartThread(SYSD, STACKSIZE, PRIO, USER1, 
USER2);
#define THREADED_STOP_THREAD(SYSD) \
     xThreadStopThread(SYSD);
#define THREADED_LOCK_INPUTPORT(SYSD) \
     xThreadLock(&SYSD-
>ThreadVars.SignalQueueMutex);
#define THREADED_UNLOCK_INPUTPORT(SYSD) \
     xThreadUnlock(&SYSD-
>ThreadVars.SignalQueueMutex);
#define THREADED_WAIT_AND_UNLOCK_INPUTPORT(SYSD) \
     xThreadWaitUnlock(SYSD);
#define THREADED_SIGNAL_AND_UNLOCK_INPUTPORT(SYSD) \
     xThreadSignalUnlock(SYSD);
#define THREADED_LISTREAD_START  
xThreadLock(&xFreeSignalMutex);
#define THREADED_LISTWRITE_START 
xThreadLock(&xFreeSignalMutex);
#define THREADED_LISTACCESS_END  
xThreadUnlock(&xFreeSignalMutex);
#define THREADED_EXPORT_START    
xThreadLock(&xCreateMutex);
#define THREADED_EXPORT_END      
xThreadUnlock(&xCreateMutex);


http://www.ibm.com/rational
Contents Index Previous Next