IBM
Contents Index Previous Next



Integration with Compiler and Operating System


This section describes how to set up a compiler and target platform in order to get an application running.

Integration with a new compiler

There are mainly two aspects when a new compiler should be used. Compiler name and switches, and what include files are needed.

Compiler name and switches

Specifying compiler name and suitable compiler switches are part of the build process. These topics are discussed in the previous section - see Compile and Link an Application.

Include files

The other major aspect is the include files needed for the kernel code and for the generated code, but also include files used for user specific code. If no integration is specified a default section including the .h file needed by the kernel and the generated code is used. This follows ISO C specifications on system include files. In this case the following is included (compare with the kernel file extreme_kern.h):

#if defined(USER_CFG_COMPHDEF)
  #include "comphdef.h"
#else
  /* Use default (ISO-C) */
  #include <string.h>
  #include <stdlib.h>
  #include <limits.h>
  #include <stdarg.h>
  #ifdef CFG_ADD_STDIO
    #include <stdio.h>
  #endif

  #if defined(__cplusplus) && defined(_MSC_VER)
    #include <cstddef> /* C++ and Microsoft compiler 
*/
  #else
    #include <stddef.h>
  #endif
#endif

That is if USER_CFG_COMPHDEF is not defined the include files given above is included.

Note: stdio.h is only needed if printing is used.

If the compiler/run-time library with the compiler does not provide the functions defined in string.h, the kernel (extreme_kern.c) includes implementations of the function that is used from this file. The functions are:

memset, memcpy, strlen, strcpy, strncpy, and strcmp

By inserting defines from the list below to the extreme_user_cfg.h file generated by the Targeting Expert, it is possible to use the kernel implementations of these functions:

#define USER_CFG_USE_memset
#define USER_CFG_USE_memcpy
#define USER_CFG_USE_strlen
#define USER_CFG_USE_strcpy
#define USER_CFG_USE_strncpy
#define USER_CFG_USE_strcmp

To customize the list of included system files (and possibly other .h files needed in your application), the macro USER_CFG_COMPHDEF should be defined. This can be done in the Targeting Expert.

Another option, used in the predefined kernels, is to include this definition on the command line to the compiler using a -D option or something similar.

In this case the file comphdef.h will be included. It is then also necessary to adopt the build process, specifically the list of directories where the compiler looks for include file, so the compiler finds the correct comphdef.h. Usually this is performed by -I options to the compiler.

Integration with the run-time system

There are a number of aspects that is important in the integration with the run-time system provided by the underlying hardware and software:

Just as for the compiler integration you can enable your own integration by including a define of the macro USER_CFG_RTAPIDEF in extreme_user_cfg.h (or as a compiler option):

#define USER_CFG_RTAPIDEF

In that case the file rtapidef.h will be included in extreme_kern.h and rtapidef.c will be included in extreme_kern.c. Appropriate options must be given to the compiler so it finds the correct rtapidef.* files.

If USER_CFG_RTAPIDEF is not defined then a standard integration is used. The algorithm used for this selection is:

if (threading is used)
  if (Microsoft or Borland compiler is used)
    Use a Win32 threaded integration
  else
    Use a POSIX pthreads integration
  endif
else
  Use a non-threaded integration
endif

Note: POSIX pthreads integration and non-threaded integrations will likely work on most UNIX systems/compilers. This is however tested only on supported systems/compilers.

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.

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 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.

Memory management

In some cases dynamic memory is needed by a generated application. To support this an alloc and a free function must be provided. You have three possibilities:

Note: If the application does not use dynamic memory, there is no need to implement these functions.

The xAlloc function should allocate memory of the size given as parameter and return a reference to the memory. It is assumed that the memory is set to 0 by the xAlloc function. Example:

void *xAlloc (unsigned int Size)  {
  void * Ptr;
  Ptr = (void *)malloc(Size);
  if (Ptr) 
    (void)memset(Ptr, 0, Size);
  return Ptr;  
}

The xFree function takes a pointer to a pointer to some memory to be returned to the pool of free memory. The function should free the memory and set the pointer to 0. Example:

void xFree (void ** Ptr) 
{
  if (*Ptr) {
    free(*Ptr);
    *Ptr = (void *)0;
  }
}

The xAlloc and xFree functions must be thread safe. The functions in the built-in memory package are protected by a semaphore or by turning off and on interrupts. In case two, when using the OS calloc and free it is assumed that these functions are thread safe.

Disable and enable interrupts

In a non-threaded application where you want to be able to send signals into the system in interrupt routines, some important data structures for signals must be protected from simultaneous access. To achieve this it is necessary to disable interrupts while executing certain operations in the kernel.

To implement disabling and enabling of interrupts, you should in define two macros in rtapidef.h with the structure given below.

#define XBEGIN_CRITICAL_PATH \
        UserCodeToDisableInterrupts;
#define XEND_CRITICAL_PATH \
        UserCodeToEnableInterrupts;

where UserCodeToDisableInterrupts and UserCodeToEnableInterrupts should be replaced by code performing these actions for the hardware and software platform that is used.

Threaded integrations

To implement a new integration 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 632 : 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 extreme_kern.c by defining XMAIN_NAME to for example:

#define XMAIN_NAME cextreme_main

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

The clock function

The clock function should be implemented according to the description found in a previous section.

Protection of shared data

It is necessary to protect the list of available signals, the list of available timers, the list of free parts (for create actions), and, if the memory package is used, the memory used by the package.

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 633 : 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 634 : 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.

Example 635 : 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 636 : Functions for lock and unlock

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 Cextreme Code Generator kernel will start the specified threads in the main() function.

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 that is actually 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.

The global data structure xSysD is an array with components of type xSystemData, with one component per thread. xSysD contains global information about what is going on just now in the threads. Details about the information in xSysD can be found in the section Overview of Important Data Structures. In the context of RTOS integrations two aspects are important. Each thread (the xMainLoop function) must know the address for the component in xSysD representing the thread. Each xSysD component contains a field of type xThreadVars, which should be defined in the RTOS integration.

Example 637 : 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 638 :

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();       /* Cextreme  */
  #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, i.e. 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. The recommendation is to call the xInEnv function and let that function run in this thread. 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 why 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.

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. Compare with 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 639 :

#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 (binary semaphore) is used together with some sort of conditional variable. You need the possibility to perform a condition wait, with or without a time-out. You also need 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 (ExternSignalQueue in the xSysD array). 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 640 :

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 641 :

In rtapidef.h

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

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      
      /* Cextreme */
      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
          /* Cextreme */
          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 C Code Generator 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

THREADED is defined when using the C Code Generator but not when using the Cextreme Code Generator. 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 affects the details in the xThreadWaitUnlock function.

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

Example 642 : 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