[Top] [Prev] [TOC] [Next] [Bottom]

Porting the TargetRTS for C


This chapter has been split into several major sections, including

C TargetRTS configuration definitions

Much of the configurability of the C TargetRTS is done with compilation dependencies by having target-specific source files override common source files. This is illustrated in "Platform-specific implementation" on page 61. However, configurability is also available within source files using C preprocessor definitions. The configuration is set in two C header files:

Makefile fragments

Some of the TargetRTS configuration is done at the makefile level. When a developer compiles an update, the generated compilation makefile requires two files:

Default.mk

This makefile fragment defines some common make macros. These macros are interpreted first and may be overridden in any of the three RTS-specific makefile fragments or even the update-specific Make Overrides file that will be included later. Consequently, these macros may serve one of several purposes:

Libset.mk

This file is intended to list items specific to correctly linking with a particular library, and/or typically configuring the compiler. These configuration items include

Target.mk

Some targets will require further configuration without respect to which compiler is being used. For example Wind River System's Tornado targets require their linkers to be invoked with the -r option (to facilitate run-time linking).

Config.mk

In cases where a configuration item should only affect a particular library-set/target pairing, configurations can be put in the config.mk makefile fragment.

Availability of Perl on compilation host

If the compilation host does not have Perl (version 5.002 or greater), it is highly recommended to obtain and compile the source from http://www.perl.com.

Since obtaining Perl may take some time, some limited functionality will still be available on the compilation host:

Platform-specific implementation

The implementation of the C TargetRTS is contained in the $RTS_HOME/src directory. In this directory, there is a subdirectory for each major run-time area. For example, the sub-directory C
TargetRTS contains all of the standard run-time library functions. Other sub-directories, for example, TCP, MONITOR, INITSTOP, and TRANSPRT contain target observability functionality.

To port the C TargetRTS to a new platform, it may be necessary to update or replace some of these files. The target specific source is placed in a subdirectory of $RTS_HOME/src/target/<target_base>, where <target_base> is the target name without the `S' or `T'. For the remainder of this section, the target directory is referred to as $TARGET_SRC. For example, the target source directory for an example <target> PSOS2T is $RTS_HOME/src/target/PSOS2. This directory provides an overlay to the $RTS_HOME/src directory. When the C TargetRTS loadbuild tools search for a source file, it searches first in the $TARGET_SRC directory then in RTS_HOME/src.

Note: There is only a single source directory for all configurations of the C TargetRTS for a given platform. C preprocessor macros, such as RSLMULTITHREADED, may be used to differentiate code for specific configurations.

For a single-threaded port without target observability, it is entirely conceivable that no source code modifications would be required, as the intent was to make the C TargetRTS completely portable to any ANSI C compiler, which should compile single-threaded C code without modification.

However, if the target is multi-threaded, modifications will be required to specify how certain RTOS services (for example, semaphore creation) are performed in that target environment. The remainder of this section discusses the most common required implementation code required for a new multi-threaded target.

Main function

In order for the execution of the C TargetRTS to begin, code must be provided to call cRSL_entryPoint( int argc, const char * const * argv ) passing in the arguments to the program. This code is placed in the file $TARGET_SRC/MAIN/main.c.

The default code is likely to be suitable for a new target platform.

Target observability startup and shutdown

If any special initialization and/or shut-down code is required for target observability, the files TGTinit.c, TGTstop.c, TOinit.c, and TOstop.c which are located in the directory $TARGET_SRC/INITSTOP contain code to perform these functions, in conjunction with the C TargetRTS initialization and shutdown.

The default code is likely to be suitable for a new target platform.

Memory management

All memory allocation performed by the C TargetRTS (this does not apply to any dynamic memory allocation performed by the application) is performed through a single routine:

void *RSLAllocateMemory(RSLMemorySize size)

The C TargetRTS only allocates dynamic memory during its initialization phase. Thus, once the system has initialized, the C TargetRTS will not perform any dynamic memory allocations. Since the C
TargetRTS does not support any implicit recovery capabilities, the C TargetRTS does not support the freeing of any dynamically allocated memory, which was allocated by it. Thus, users must exercise caution when using this memory management routine at an application level.

The default implementation of this routine is to invoke the standard C "malloc" run-time routine, to allocate a large chunk of memory, whereupon requests are serviced efficiently from within this block (this works particularly efficiently in certain RTOS environments where there are minimal sizes, for example, 64 bytes for any malloc request). If standard C run-time malloc support is not available—or alternatively, you want to have the C TargetRTS allocate memory from a specific memory region—you should modify this routine (in file memory.c) as appropriate for your target environment.

Multi-threaded RTOS interface

All of the RTOS interface mapping routines used by the C TargetRTS should be contained in the RTThread.c file located in the directory $TARGET_SRC/target/<target>/THREAD.

Figure 3 shows the relationships between an application, the C TargetRTS, and underlying RTOS primitives required to support a multi-threaded C TargetRTS.

Required relationships

RTThread.c contents

This file is split into three general sections:

Target header files include statements

This section should specify the list of header files that are required for compilation for the TargetRTS (specifically, those header files that are required for the multi-threaded RTOS interface). An example follows:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>
Type definitions for base RTOS types

This section specifies RTOS-equivalent types for three C TargetRTS base types:

C TargetRTS function mappings to TargetRTS

The final section in the RTThread.c file is used to define a set of C TargetRTS functions providing a direct interface from the C TargetRTS to the underlying RTOS capability.

These functions are organized as:

Adding new files to the C TargetRTS

If you create a new file for the C TargetRTS, then you must add the new file name to a manifest file for the C TargetRTS. This must be done in order for the dependency calculations to include the new file and thus include it into the C TargetRTS.

The MANIFEST.c file

This file lists all the elements of the run-time system. There is one file entry per line. Each entry has three or more fields separated by whitespace. The first field names a make variable that specifies which library to associate the file with. The second field is a directory name. The third field is the base name of a file. The fourth and subsequent fields, if present, give an expression that evaluates to zero when the element should be excluded. Note that the expression is evaluated by Perl and so should be of a form that it can handle.

Regenerating make dependencies

If a file has been overridden in $(RTS_HOME)/target/src/<target_name> directory or a new file has been added to the MANIFEST.c you must regenerate the make dependencies in order for the modification to be included in the new TargetRTS. This is done by removing the depend.mk file in the build directory ($(RTS_HOME)/build-<platform_name>). This causes the dependencies to be recalculated and a new depend.mk file to be created. Note that include files must not be enclosed in #ifdef/#endif or similar macros because the preprocessor may not have the needed definitions to satisfy these macros. Consequently, the include file may be ignored during dependency checking.

C TargetRTS run-time semantics

Tasks, processes, and threads

The C TargetRTS does not distinguish between a task, process, or thread. In fact, some RTOSs—which support multiple tasks—represent a thread as a task. Instead, the C TargetRTS requires the simple notion of a "thread of execution", which (depending upon the customer RTOS capability) might be a thread, a task, or a process. However, the C TargetRTS does require that if multiple threads of execution are required, they share a common "shared" memory space.

If multiple threads are supported in the target environment, the C TargetRTS, in conjunction with the ObjecTime toolset, provides the customer with the ability to split up an application into multiple threads. The decision to split the application into multiple threads of execution is an important architectural decision. The C TargetRTS provides simple capabilities to define which top-most ObjecTime actors are to be assigned to which threads. Thus it is possible to "code-generate" different thread topologies easily, without necessarily modifying any user code in the ObjecTime model.

The mainloop

Currently, most systems are comprised of one or more separate threads of execution. Each of these threads typically has a main routine that is responsible for servicing the IPC (Inter-Process Communication) messages or signals that are received by that system. These IPC routines are usually blocking RTOS IPC calls; that is, they do not return execution to the main routine until a message/signal has been received. When a message/signal is received, an appropriate subroutine is invoked to handle the actual processing associated with the arrival of that particular message/signal. This message processing occurs for the duration of the execution of the system.

Similarly, each thread of execution in the C TargetRTS also has a "mainloop". This is in recognition of the fact that the main C TargetRTS routine processes many IPC messages, in a serial fashion, via a looping mechanism. After receiving each external message, it processes it, that is, it "walks' the state machines of one or more actors.

The following pseudo-code describes this notion of a main-loop:

<Initialize IPC>
<Do Forever>
{
<Process Actor Messages>
<Block for IPC Message>
}

To support user-defined messaging interfaces, each of these threads of execution requires a application-specified routine, which in conjunction with the C TargetRTS mainloop, implements how that particular task is to interface to the user's IPC. It is possible to have many different interface routines—a different one for every thread. It is also possible for different threads to use different IPC mechanisms. Note that a thread blocked waiting for external messages from the IPC will not unblock to process actor messages.

RSLThreadMap

In an ObjecTime design, the top-most actor is assigned its own thread; in fact, by default, every program that is run has at least one thread of execution. Unless directed otherwise by that top-most actor, all contained actors will also "run" on that same thread.

However, if the user application is multi-threaded (that is, at least two or more threads of execution), then these additional threads are contained by this top-level actor. These contained actors are assigned a different thread at run-time through the top-level actor's function RSLThreadMap, which is a "special" function, with a non-C specification syntax; that is, it cannot be compiled by a C compiler. It is called a "special" function, since there is special code-generation handling the particular function name of "RSLThreadMap".

This RSLThreadMap function specifies the run-time characteristics of each of the threads, except the top-thread. The top-thread's characteristics are not defined by the C TargetRTS, as the user's RTOS specifies how the first thread of the C TargetRTS is to be started (and with which characteristics, for example, priority, stack size, and so forth).

The RSLThreadMap, in conjunction with the logical/physical thread configuration stipulated in the ObjecTime toolset (see the C Language Guide for additional information), specify which logical threads are mapped to which physical threads. In those instances where multiple logical threads are mapped to a single physical thread, the resource requirements of the physical thread are determined by summing all of the resource requirements of each of the logical threads together, that is, the number of internal messages and the number of timer control blocks. Each logical thread name specified in the RSLThreadMap must have a corresponding logical thread definition in the thread configuration browser in the ObjecTime toolset.

The characteristics that can be specified, on a per thread basis (for more information see "C TargetRTS constants/macros and their default values" on page 55), include

Message priority

The C TargetRTS message priority, defines six RSL levels of priority (from highest to lowest):

Message queues

Each thread of execution has six incoming internal message queues (five to be used by customers), one for each message priority, as defined in the previous section.

At run-time, based on the specified SAP/Endport in a message send request, the C TargetRTS determines whether the recipient of a message is within the same thread as the sender, or alternatively, is in a different thread. Once this is known, the C TargetRTS automatically selects an appropriate message enqueueing technique for the message send.

Single-threaded C TargetRTS

In the single-threaded C TargetRTS, there is no distinguishing run-time characteristic made between "internal" and "external" C TargetRTS messages, since there is only one thread of execution.

Thus, in a single-threaded C TargetRTS, there is

  1. A "free" message is allocated from the free message queue (also referred to as the `free list').
    If no message is available, a false result (a value of 0) is returned by the send function. Under the assumption that the port has been bound, this is the only error that the C TargetRTS can detect on a send.
  2. The message's structure fields are populated with the parameters passed in the send request (the `flags' and `dest SAP' fields are set automatically by the C TargetRTS send routine).
  3. The message is enqueued for internal processing at the appropriate priority.
Intra-thread inter-actor send

Multi-threaded C TargetRTS

In the multi-threaded C TargetRTS, the C TargetRTS distinguishes between "internal" and "external" C TargetRTS messages. Internal messages are inter-actor intra-thread messages, where each actor runs in the same thread of execution. External messages are inter-actor inter-thread messages, where the sending and receiving actors run in a different thread of execution.

In the multi-threaded C TargetRTS, each thread has

  1. A free message is popped from the intended receiver's external free queue/list, if available (1A).
    If none are available, a free message is popped from the sender's external free queue (1B).
    Once again, if not available, a free message is popped from the global free queue (1C).
    If that also fails, the C TargetRTS will then move all free external messages from every thread and put them on the global free queue (then it tries to pop from the global queue again).
    Finally, if no message is available, a false result (a value of 0) is returned by the send function. Under the assumption that the port has been bound, this is the only error that the C TargetRTS can detect on an inter-thread send.
  2. The message data fields are filled in with the passed parameters in the send request.
  3. The message is enqueued on the receiving thread's external incoming message queue, the receiving thread's `InPriority' is checked and updated to the current message priority (if used, and if the current message priority is higher than the current posted `InPriority'), and the thread's semaphore is posted `ready to run'.
  4. TargetRTS then processes a message in its internal queue, and dispatches the appropriate actor behavior.
Inter-thread sends

The C TargetRTS ensures system integrity by locking a global mutex prior to accessing any global shared data (shared amongst multiple threads), and releasing the lock once the shared data updates have been completed.

The `InPriority' field is used by a thread to determine at what point an external incoming message is of higher priority than that which is currently being processed internally. Thus, this allows the C
TargetRTS to "interrupt" normal actor message processing (on a per-message basis) and service a higher priority actor message. Note, however, that this does not mean that the processing of one or more user code transitions is interrupted, as new messages are selected for processing only after the processing of the behavior of the current message has been completed.

Message processing

Single-threaded C TargetRTS message processing

As indicated below, in Figure 6, single-threaded C TargetRTS actor message processing is implemented by:

  1. Selecting the highest priority queued message.
  2. Invoking the actor behavior associated with the arrival of that signal and data.
  3. "Returning" the used message frame to the free message Queue.
Single-threaded C TargetRTS message processing

If the invocation of actor behavior causes one or more messages to be sent to other actors, the C
TargetRTS mainloop may continue to invoke actor behaviors until there are no further messages to be processed. By default, all of these messages are processed prior to the C TargetRTS directing the invocation of the user-registered external interface (IPC) routine.

Multi-threaded C TargetRTS message processing

As indicated in Figure 7, multi-threaded C TargetRTS actor message processing is implemented by:

  1. Checking if a higher-priority (as compared to internal messages) externally queued message exists, all external messages are dequeued from the external message queue and enqueued on the internal message queue.
  2. Selecting the highest priority queued internal message.
  3. Invoking the actor behavior associated with the arrival of that signal and data.
  4. "Returning" the used message frame to the free message Queue. If the message was internal, it is put back onto the internal free queue (4a). If the message was external, it is put back on its external free queue (4b), and if a certain message count threshold is exceeded, it (along with other freed messages) may be put back (4c) on the global external free message queue. (See "C TargetRTS constants/macros and their default values" on page 55 for the definition of RSLTHRESHHOLD_EXTERNAL_MSGS.)
Multi-threaded C TargetRTS message processing

If the initial invocation of actor behavior causes one or more messages to be sent to other intra-thread actors, the C TargetRTS mainloop may continue to invoke actor behaviors until there are no further messages to be processed. By default, if both intra-thread and inter-thread messages are enqueued, all of these messages are processed prior to the C TargetRTS invoking the user-registered external interface/IPC routine.

Run-to-completion

This default "run-to-completion" C TargetRTS message scheduling policy is implemented in the C
TargetRTS mainloop as

while(RSLDispatch(RSLBACKGROUND));

where "RSLBACKGROUND" represents the lowest priority message to be processed. The RSLDispatch routine is responsible for invoking the behavior associated with the single highest-priority message on the input queue. It returns true if a message was processed, or false if no internal messages were available for processing at the specified, or higher priority.

Thus, the "while" clause causes the C TargetRTS to continue processing these "internally" queued messages, until no further messages are enqueued, at which point, the C TargetRTS will call the user-defined IPC interface routine to block for external stimuli (for example, IPC messages, timeouts, and so forth). If the C TargetRTS is multi-threaded, the C TargetRTS will also check the incoming external queue for higher priority messages than those in the internal message queue. If there are higher priority external messages, these messages are moved to the internal queue, and C TargetRTS actor message processing continues.

If no user-defined IPC routine was specified, the multi-threaded C TargetRTS will wait until its `ready-to-run' semaphore has been signalled by another thread (for example, an inter-thread inter-actor message send). In the single-threaded C TargetRTS, if there is no other source of potential inputs, the thread will exit.

Also, in the multi-threaded C TargetRTS, an external C TargetRTS message is moved from the thread's external incoming message queue to its internal message queue (based on priority). At that point, message processing within the thread proceeds in a similar fashion in both the single and multi-threaded C TargetRTS.

RSLRegisterExternalInterface

The C TargetRTS was designed so that it requires no inherent notion of how customers' IPC mechanisms work. Instead, it provides the "internal" C TargetRTS actor message processing portion of the mainloop, and allows customers to register their own IPC external interface routines, which are invoked by the C TargetRTS once no further internal message processing is possible. Note that a blocked thread waiting for external messages from the IPC will not unblock to process any new actor messages.

It is possible to have many different interface routines—a different one for every thread—and it is possible for different threads to use different IPC mechanisms.

The C TargetRTS provides support for each thread to register its own interface function by using the following routine to dynamically perform this registration, an example of which might appear as

RSLRegisterExternalInterface(_actor,MyActorIPCInterface);

The first parameter `_actor' of type RSLActorIndex should be set to the internal C TargetRTS variable automatically defined for all actor transitions. `MyActorIPCInterface' would be the name of the user-defined actor function, which implements how the thread interfaces to IPC and/or timers.

Alternative implementations can be contemplated whereby an actor implements the IPC interface directly in the actor behavior, but this technique would likely require that this IPC actor run with the lowest priority messaging to ensure that all internal actor messages have been processed before the IPC actor blocks on an IPC call.

RSLRegisterExternalInterface is used for all IPC and timer implementations. For example, the code fragment shown below was taken from an actor's initialization transition. Once this initialization transition is executed, the thread that the actor is executing in will have a registered external interface routine called 'ExternalInterfaceRoutine'.

RSLRegisterExternalInterface(_actor,ExternalInterfaceRoutine);
printf("Example thread: Alive!\n");

RSLRegisterMessageSignallingInterface

The C TargetRTS was designed to support different types of IPC and timing mechanisms, suitable for a particular target environment. In some instances, it may be necessary to have the C TargetRTS signal another C TargetRTS thread that it is in the process of sending that thread a message (for example, to interrupt a blocking RTOS function called from within a registered external interface function).

The C TargetRTS provides support for each thread to register a signalling function via the following example:

RSLRegisterMessageSignallingInterface(_actor,
&MyCV,MySignallingInterface);

The first parameter `_actor' of type RSLActorIndex should be set to the internal C TargetRTS variable automatically defined for all actor transitions. The second parameter is the address of the RTOS object to be signalled. "MySignallingInterface" is the name of a user-defined actor function, which would be responsible for signalling the passed RTOS object MyCV.

At run-time, when an inter-thread message is delivered to a thread that had previously registered a signalling interface routine, the C TargetRTS calls the specified procedure, along with the address of the specified RTOS object, via the pre-registered procedure variable.

Enqueueing external events

To allow customer-registered external interface/IPC routines to enqueue messages that represent the arrival of an external IPC message or a timeout event (discussed later), the C TargetRTS provides the following routine:

RSLMessage *RSLPortEnqueue(RSLActorIndex _actor,
RSLPortIndex portOffset,
RSLSignalIndex signal,
RSLMessagePriority priority,
void *data);

RSLGetFirstTimeout

This C TargetRTS function is used to get the RTTimerId (same as RSLTimerReference) of the first timer control block on the sorted list of timers for the thread associated with the specified actor. An example follows:

RSLTimerReference RSLGetFirstTimeout(_actor)

RSLGetFirstTimeout is used for all timer implementations (RSLTIMERS set to RSLTRUE).

RSLCancelTimer

This C TargetRTS function is used to cancel a pending timer request by marking the timer control block associated with that timer reference as cancelled. Thus, at a later time, the timer control block could be released and reused.

RSLBool RSLCancelTimer(RSLActorIndex,RSLTimerReference);

RSLCancelTimer is used for all timer implementations (RSLTIMERS set to RSLTRUE).

RSLDequeueTimer

Since the TCBs are managed by one or more actors or registered actor functions (as opposed to automatic control via the C TargetRTS), the C TargetRTS also provides a routine to dequeue a timer, and possibly free the timer control block:

RSLDequeueTimer(_actor,&TimerControlBlock,Free)

The first parameter `_actor' indicates which context we are executing. The second parameter supplies the address of the timer control block. The final parameter is a Boolean value that instructs the C
TargetRTS to return the timer control block to the original user thread.

RSLRegisterTimerServices

The C TargetRTS supports actor-based implementations of timing services via the following routine:

RSLRegisterTimerServices(_actor,sap,
&MyCV,MySignallingInterface);

The first parameter `_actor' of type RSLActorIndex should be set to the internal C TargetRTS variable automatically defined for all actor transitions. The second parameter is the SAP upon which service requests are to be received. The third parameter is the address of the RTOS object to be signalled. "MySignallingInterface" is the name of a user-defined actor function, which would be responsible for signalling the passed RTOS object MyCV.

RSLRegisterTimerServices is only used for actor timer implementations (RSLTIMERS set to RSLTRUE; RSLACTOR_TIMERS set to RSLTRUE).

RSLGetTimerServiceActor

This C TargetRTS function is used to get the run-time actor Id of the timer actor that has registered as the provider of all timing services for the C TargetRTS. An example invocation follows:

RSLActorIndex RSLGetTimerServiceActor(_actor)

RSLGetTimerServiceActor is used for timer actor implementations (RSLTIMERS set to RSLTRUE; RSLACTOR_TIMERS set to RSLTRUE).

Implementing timer services in the C TargetRTS

Often, timing events are required by system tasks to determine that some function was not completed in a timely manner, or to schedule activities. Depending upon the customer's application requirements and RTOS timing capabilities, two different timer implementation approaches can be considered:

Local timers

Local timers implement timing services on a per-thread basis by using an RTOS capability to block for some sort of a signal/message for a specified period of time. Thus, once the RTOS primitive returns execution, a return code can be checked to see if either a signal/message was received or the RTOS call timed out.

Typically, the blocking RTOS call that forms the basis for implementation for local timers is either a message read request (for example, on a message queue), or a signal wait request (for example, on a semaphore). By specifying an appropriate timeout parameter each time this RTOS function is invoked, a simple and efficient timing mechanism can be implemented. Since this same approach can be used in multiple threads, each thread is responsible for managing its external interface (message queue, semaphore, and so forth) and timer requests.

By using the RSLRegisterExternalInterface C TargetRTS API function, it is possible to define an RTOS-specific actor interface function, which is registered by an actor executing in each thread. These registered functions would be responsible for implementing timer services for all actors in that thread, and possibly interfacing to a proprietary RTOS IPC (Inter-Process Communication) mechanism.

Since the C TargetRTS provides an efficient inter-thread inter-actor messaging mechanism via port bindings, if an actor provides a proprietary RTOS IPC interface, it is likely that it will interfere with normal C TargetRTS inter-thread communication. This is because it is not possible to service multiple communication mechanisms simultaneously (proprietary C TargetRTS communication and RTOS IPC), unless you consider the use of a signalling function. For more information, see "RSLRegisterMessageSignallingInterface" on page 75.

With the local timer approach, there are two inter-thread messaging alternatives:

Integrated timers

RTTarget.h should specify that RSLTIMERS is set to RSLTRUE; RSLACTOR_TIMERS is set to RSLFALSE.

No special timing actor/packages are required.

Two actor functions must be implemented.

Integrated IPC and timers

RTTarget.h should specify that RSLTIMERS is set to RSLTRUE; RSLACTOR_TIMERS is set to RSLFALSE.

No special timing actor/packages are required.

One actor function must be implemented.

Actor timers

RTTarget.h should specify that RSLTIMERS is set to RSLTRUE; RSLACTOR_TIMERS is set to RSLTRUE.

A special timers package should be used as a basis for implementation. An example of this type of timer implementation can be found under $(OBJECTIME_HOME)/ModelExamples/C, in an update called "C_Timers". A detailed description of this update can be found in "Compliance Suite & Examples" on page 219 of the C Language Guide.

In this approach, a timer actor registers itself as the provider of timing services for all timing requests in the C TargetRTS. If multi-threaded, this actor should be configured to run on its own thread. The C
TargetRTS will handle all inter-actor inter-thread and intra-thread messaging to and from the timing actor.

At least two timing actor functions must be implemented. They are as follows:



[Top] [Prev] [TOC] [Next] [Bottom]

1
common.nmk is required when using Microsoft's nmake utility. Slight formatting differences exist between this utility and most other make utilities. Specifically, when a make macro must be evaluated in order to process a makefile include statement, nmake requires the included file's filename to be enclosed in angle brackets. Hence, include $(FILE_MK) becomes include <$(FILE_MK)>. Structurally, common.nmk and common.mk are intended to be equivalent, and this document refers to the latter for brevity. No other makefile fragments are dependent upon which make utility you are using.
support@objectime.com
Copyright © 1998, ObjecTime Limited. All rights reserved.