ChorusOS 4.0 Introduction

Chapter 4 Programming Overview

This chapter introduces the steps involved in developing applications, also called actors, that run on the ChorusOS operating system. It includes the following sections:

System development and advanced programming topics are not covered.


Note -

The source code for many of the examples shown in this book is provided in the examples directory. By default this directory is /opt/SUNWconn/SEW/4.0/chorus-<target>/src/opt/examples.


ChorusOS Applications

The ChorusOS operating system provides an environment for applications running on a network of target machines, controlled by a remote host.

Programming Conventions

Services provided by the ChorusOS operating system are accessed as C routines. C header files provide the required constants, types and prototypes definitions. As the ChorusOS operating system is highly modular, header files reflect this modularity. However, in the following examples a global header file, named chorus.h, which collects most of the required header files, has been used for simplicity. Please refer to the man pages to get the actual minimum header file required for each service.

Most ChorusOS operating system constants start with K_. ChorusOS operating system error codes start with K_E. Constants and error codes are all written in uppercase.

Most specific data types are prefixed by Kn. When type names are composed of several lexems, the first letter of each lexem is written in uppercase while other letters are in lowercase, as in KnRgnDesc (region descriptor).

General Principles

In order to compile and link an application, the following information is needed:

Program Entry Point

In order to initialize the libraries correctly before starting the execution of the application code, the program entry point must be set to _start. After the initialization of libraries is completed, _start calls the _main routine which initializes variables in C++ programs. The main() routine is then called.

The _main routine manages any double calling at program initialization; some C++ compilers force a call to _main at the beginning of main().

Depending on the development system, it may be necessary to use specific linker directives to force the linker to extract the _start and _main routines from the libraries.

Libraries

In order to choose which ChorusOS operating system libraries to use, the following points need to be considered:

Supervisor Actor Binaries

As supervisor actors share the same supervisor address space, they are built as relocatable binaries, leaving the choice of the final link addresses to either the system configuration utility building the system image (for the basic environment) or the Actor Manager (for the extended environment).

Care must be taken when programming supervisor actors: no memory protection is provided between supervisor actors. A badly written supervisor actor accessing addresses outside its own address space can corrupt any supervisor region and cause unexpected behavior such as a system crash or reboot.

User Actor Binaries

User actors are also built as relocatable binaries, even though they use private address spaces. The link address of the user actors and the size of the user address space are board dependent. For a given board, all user actors are linked at the same address.

The final link is done by the Actor Manager when actors are loaded dynamically on the target.

Application Programming Interfaces

This section provides an overview of all programming interfaces available for applications developed for the ChorusOS operating system. The programming interface may differ from one program to another depending on:

Naming Conventions

Library names in the ChorusOS operating system use the following conventions with regard to their suffixes:

.u.a These libraries can only be used to build actors that will be loaded in a user address space.
.s.a These libraries can only be used to build actors that will be loaded in the supervisor address space.
.a These libraries can be used to build any type of actor.


Note -

When a library has both a user and supervisor version, it will be referred to using the .a suffix only.


All header file and library pathnames listed in the next subsections are related to the installation path of your ChorusOS delivery, typically /opt/SUNWconn/SEW/4.0/chorus-<target>.

Basic Environment APIs

The programming environment of basic actors consists of the following interfaces:

All routines implementing these APIs have been grouped into two libraries:

kernel/lib/embedded/libebd.u.a for user actors
kernel/lib/embedded/libebd.s.a  for supervisor actors

ChorusOS actors using the Basic Environment API are called embedded actors.

Extended Environment API

The programming environment of extended actors consists of the following interfaces:

All routines implementing these APIs have been grouped into one library:

os/lib/classix/libcx.a for user and supervisor actors


Note -

An extended supervisor actor should not use the svExcHandler() call as an extended actor inherits the Actor Manager exception handler.


Other APIs

Other APIs are provided with the ChorusOS operating system. Depending on their nature, they may be available to both basic and extended environments or restricted to a single environment. The following subsections give a description of the libraries implementing these APIs.

POSIX Micro Real-time Profile API

Routines implementing the MRTP (Micro Real-time Profile) API are included within the libcx and libebd libraries. They are available to both basic and extended actors.

Mathematical API

Routines implementing the Mathematical API are packaged in an independent library kernel/lib/libm/libm.a. This library is available to both basic and extended actors.

Sun RPC API

Routines implementing the Sun RPC API are packaged in an independent library os/lib/classix/librpc.a which is not thread-safe. This API is restricted to extended actors.

GNU 2.7.1 C++ API

The C++ library os/lib/CC/libC.a provides support for C++ applications with a complete and thread-safe library package. Every service offered by libC.a ensures that shared data is only accessed after signaling the relevant synchronization objects.

To allow atomic manipulation of any stream class (iostream or fstream for example), the API of libC.a has been extended with the following two services:

The ios::lock()service is used to lock any stream class object. The ios::unlock() service is used to unlock any stream class object. All services called upon a given stream object StrObj which are preceded by StrObj.lock() and followed by StrObj.unlock() are executed in an atomic way. It is guaranteed that no other thread can access StrObj as long as the lock is on.

An I/O stream object can be locked in two ways. For example, if cout is an I/O stream object:

cout.lock();  
cout << "atomic " << "output";  
... (any other operation on cout)  
cout.unlock(); 
In this case the member function ios::lock() is called.

The following syntax could also be used:

cout << lock << "atomic " << "output" << unlock;

Embedded C++ actors can be linked with os/lib/CC/libC.a if they do not make use of the iostream and fstream packages.

Multithreading

The libebd.a, libcx.a, libm.a and libC.a libraries have been made thread-safe in order to support multithreaded actors. This is managed by the library in the following way:

Defining errno as one global variable for the actor is not suitable for multithreaded actors as situations can arise where a thread, examining errno on return from a failed system call, concludes that the call failed for the wrong reason because the global errno was changed by another system call in another thread in the meantime. Some programs also test errno rather than system call return values to detect errors.

To avoid this, the header file errno.h, exported by the extended environment, should be included in any source file using errno. This will result in a separate value for errno for each thread.

Header Files

The ChorusOS operating system header files are packaged in five different directories:

Typical ChorusOS operating system applications use header files of the include/chorus and include/posix directories (and also include/CC for applications using the GNU 2.7.1 C++ API).

Developing personality servers (such as servers implementing a UNIX personality) on a ChorusOS operating system needs extra care in order to avoid conflicts between data types declared by ChorusOS operating system header files, and data types declared by the server's header files. These servers should be restricted to header files of the include/chorus and include/stdc directories and use the _CHO_POSIX_TYPES_NOTDEF compile option.

Developing ChorusOS Applications

This section explains how to build a component to be included in a ChorusOS operating system. The component could be an application, a device driver, or a BSP. To build a ChorusOS component, you use the make and imake tools. All development tools are provided in the tools directory of your delivery.

make Environment

The make environment is defined by a file containing variable definitions and rules. Rules for compiling C, C++, and assembly language are provided. The rules are specific to the compiler you use, and the name of the file indicates the compiler. For example, if you are using the gcc compiler, the make environment file is called tgt-make/gcc-devsys.mk. The file contains the variables and rules required for building the component. The following variables are defined:

The make environment includes the following commands: cc, ld, as, and mkactors.

imake Environment

The ChorusOS imake environment extends the make environment by providing template rules for common ChorusOS build operations through generic names. When using those predefined imake rules, you do not need to know which libraries, crt files, or entry points you should use to build an application, as they are automatically selected for you.

Instead of creating Makefiles you must create Imakefiles, as imake will generate Makefiles from them.

The imake environment is defined by four files containing sets of variables and rules, located in the tools/imake directory. The rules are independent of the compiler you use.

imake Variable Definitions

The file Imake.tmpl contains the following definitions:

imake Build Rules

The file Imake.rules contains macros known as Imake build rules. Their name and function are described in Table 4-2.

Table 4-2 Imake build rules
 Macro name Function
MakeDir(dir)Creates the directory named dir.
LibraryTarget(lib, objs)Adds the objects indicated by objs into the library lib.
Depend(srcs)Computes the dependencies of srcs and adds them to the dependency list in the Makefile (using makedepend).
ActorTarget(prog, objs, options, crt0, libs)Uses objs to create a C actor called prog, and passes options, crt0 and libs to the linker.
UserActorTarget(prog, objs, libs) Creates a user C actor.
SupActorTarget(prog, objs, libs) Creates a supervisor C actor.
EmbeddedUserActorTarget(prog, objs, libs) Creates an embedded user C actor.
EmbeddedSupActorTarget(prog, objs, libs) Creates an embedded supervisor C actor.
BuiltinDriver(prog, objs, libs) Creates a ChorusOS operating system driver.
BspProgtarget(prog, entry, objs, libs) Creates a BSP program.
CXXActorTarget(prog, objs, options, crt0, libs)Uses objs to create a C++ actor called prog, and passes options, crt0 and libs to the linker.
CXXUserActorTarget(prog, objs, libs) Creates a user C++ actor.
CXXSupActorTarget(prog, objs, libs) Creates a supervisor C++ actor.
CXXEmbeddedUserActorTarget(prog, objs, libs) Creates an embedded user C++ actor.
CXXEmbeddedSupActorTarget(prog, objs, libs) Creates an embedded supervisor C++ actor.
DynamicUserTarget(prog, objs, libs, dynamicLibs, dlDeps, options) Creates a dynamic user C actor.
DynamicSupTarget(prog, objs, libs, dynamicLibs, dlDeps, options) Creates a dynamic user C actor.
DynamicCXXUserTarget(prog, objs, libs, dynamicLibs, dlDeps, options) Creates a dynamic user C actor.
DynamicCXXSupTarget(prog, objs, libs, dynamicLibs, dlDeps, options) Creates a dynamic user C actor.
DynamicLibraryTarget(dlib, objs, staticLibs, dynamicLibs, dlDeps, options) Creates a dynamic library.

Rules used to build actors use the following common arguments:

The rules used to build dynamic actors are described in more detail in "Building a Dynamic Program".

imake Packaging Rules

The file Package.rules contains macros known as Imake packaging rules for building a binary distribution. Their name and function are described in Table 4-3.

Table 4-3 Imake packaging rules
 Macro name Function
DistLibrary(lib, dir)Creates the directory dir and copies the library lib into it.
DistActor(actor, dir)Creates the directory dir and copies the actor actor into it.
DistFile(file, dir)Creates the directory dir and copies the file file into it.
DistRenFile(file, nFile, dir)Creates the directory dir, copies file into it, changing the name of file to nFile.
DistProgram(program, dir)Creates the directory dir and copies program into it.

Examples

Simple imake Example

The application in this example is composed of a single C source file, for example myprog.c in the directory myprog. Writing an Imakefile is quite straightforward. First, you must set the SRCS variable to the list of source files (in this case only one).

SRCS = myprog.c

Then, you must specify how to build the executable. The macro you use depends on the type of binary you want. If you want to build a user-mode binary (for example myprog_u), use the UserActorTarget() macro, as illustrated below. The first argument is the name of the executable. The second argument lists the object files. The third argument allows you to specify which libraries your program depends on. In this example there is no library, hence the empty argument (you could also pass NullParameter).

UserActorTarget(myprog_u,myprog.o,) 

If you want to build a supervisor-mode binary (for example, myprog_s.r), use the SupActorTarget() as shown below. The arguments are the same as for UserActorTarget().

SupActorTarget(myprog_s.r,myprog.o,)  

Finally, use the Depend() macro to generate the Makefile dependencies.

Depend($(SRCS))

The Imakefile is complete. It looks like this:

SRCS = myprog.c
UserActorTarget(myprog_u,myprog.o,)
SupActorTarget(myprog_s.r,myprog.o,)
Depend($(SRCS))

Next, generate the Makefile with the ChorusOSMkMf tool (see the ChorusOSMkMf(1CC) manpage for details). In the myprog directory, type:


% ChorusOSMkMf build_dir

Where build_dir is the directory where you have built a ChorusOS system image on which your application will run.

Next, generate the make dependencies by typing the following command:


% make depend

Finally, compile and link the program by typing:


% make

The program is now ready to be executed, and can be run on your target by following the steps in "Running the "Hello World" Example".

imake with Multiple Source Files

If an application used source files located in several subdirectories, you need to create a root Imakefile in the root directory, containing only the following:

#define IHaveSubdirs
SUBDIRS = subdir1 subdir2 ...

where subdir1, subdir2, ... are the subdirectories containing the source files (or other intermediate root Imakefile files). Next, create an Imakefile in each subdirectory containing source files. To generate the first Makefile, go to the root directory and type:


% ChorusOSMkMf build_dir

Next, populate the tree with Makefile files, generate dependencies and finally compile the programs by typing the make Makefiles, make depend, and then make commands.


% make Makefiles   
% make depend   
% make

The program is now ready to be executed.


Note -

Examples of Imakefiles which you can modify and use to build your own applications are provided in /opt/SUNWconn/SEW/4.0/chorus-<target>/src/opt/examples.


Using Dynamic Libraries

There are two types of library in the ChorusOS operating system:

Relocatable code can be contained in two types of executable:

An actor that uses dynamic libraries is called a dynamic actor. A relocatable actor uses only static libraries.

Static and Dynamic Linking

The following table summarizes the actions performed by the static linker, which runs on the development host, and by the runtime linker, which runs on the target.

 Link Relocatable Executable Dynamic executable
 Static Linker.a Static linker adds necessary objects (.o) of a static library (.a) to the executable. .a Static linker adds necessary objects (.o) of a static library (.a) to the executable. .so Static linker adds the library to the list of libraries to load at actor start-up (afexec).
Runtime Linker (afexec) -.so At actor start-up, libraries are loaded and linked by the runtime linker. Libraries to load are defined either at static link, or in the LD_PRELOAD environment variable. The runtime linker uses a library search path to find dynamic and shared libraries.
Runtime Linker (dlopen) -.so Application explicitly asks the runtime linker to dynamically load and link a dynamic library, using the dlopen() function of the dynamic linking API.

Dynamic linking of libraries applies recursively to library dependencies: when a library is loaded, all the libraries it uses are also loaded.

Building a Dynamic Library

This section describes how to build dynamic and shared programs using the imake tool. See "Developing ChorusOS Applications" for more general information about using imake.

The following imake macro builds dynamic libraries:

DynamicLibraryTarget(dlib, objs, staticLibs, dynamicLibs, dlDeps, options)

The following example builds a dynamic library named libfoo.so from the binary objects files a.o and b.o. When this library is loaded dynamically, the runtime linker will also load the dynamic library libdyn.so, which must be in its search path.

DynamicLibraryTarget(
        libfoo.so, 
        a.o  b.o, ,
        libdyn.so, , )

Building a Dynamic Program

The following imake macros build dynamic executables:

DynamicUserTarget(prog, objs, staticLibs, 
        dynamicLibs, dlDeps, options)
DynamicSupTarget(prog, objs, staticLibs, 
        dynamicLibs, dlDeps, options)
DynamicCXXUserTarget(prog, objs, staticLibs, 
        dynamicLibs, dlDeps, options)
DynamicCXXSupTarget(prog, objs, staticLibs, 
        dynamicLibs, dlDeps, options)
DynamicLibraryTarget(prog, objs, staticLibs, 
        dynamicLibs, dlDeps, options)

The prog argument is the name of the resulting program. Other arguments are the same as the DynamicLibraryTarget() macro. For the options argument, the following options are particularly useful:

The following example builds a dynamic program named prog from the binary object files a.o and b.o. The program is statically linked with the static ChorusOS operating system library. When this program is started, the runtime linker will load the dynamic library libdyn.so. In the target file system, this library can be located in the /libraries directory, as this directory is added to the search path of the runtime linker.

DynamicUserTarget(
        prog, 
        a.o  b.o, ,
        libdyn.so, ,
        -Xlinker -rpath -Xlinker /libraries)

Dynamic Programming

The previous sections described how to use dynamic objects (dynamic and shared libraries) that are loaded by the runtime linker at actor start-up. In addition to this mechanism, an actor can also bind a dynamic or shared library explicitly during its execution. This on-demand object binding has several advantages:

Typically, an application performs the following sequence to access an additional dynamic object, using the dynamic library API:

Runtime Linker

This section describes the functions performed by the runtime linker, as well as the features it supports for dynamic applications.

Dynamic applications consist of one or more dynamic objects. They are typically a dynamic executable and its dynamic object dependencies. As part of the initialization of a dynamic application, the runtime linker completes the binding of the application to its dynamic object dependencies.

In addition to initializing an application, the runtime linker provides services that allow the application to extend its address space by mapping additional dynamic objects and binding to symbols within them.

The runtime linker performs the following functions:

The runtime linker uses a prescribed search path for locating the dynamic dependencies of an object. The default search paths are the runpath recorded in the object, followed by /usr/lib. The runpath is specified when the dynamic object is constructed using the -rpath option of the linker. The environment variable LD_LIBRARY_PATH can be used to indicate directories to be searched before the default directories.


Note -

The runtime linker needs a file system to load dynamic objects. This file system can be on a host and accessed through NFS from the target. In embedded systems without a network connection, a bank of the system image can be used. For example: /image/sys_bank.


Environment Variables

The following environment variables are used by the runtime linker:

Supported Features

Examples

This section contains two examples of dynamic programs. In all these examples, it is assumed that a standard development environment has been set up: system build tree, search path, boot and initialization of target machine. It also assumes that the chorus_root_directory is the path of the target root directory on the NFS host (for example /home/chorus/root), the name of the target is jericho, and the environment variable WORK refers to the directory used for building these examples

Dynamic Link at Actor Start-up

The following dynamic program uses a custom dynamic library which will be loaded and linked at actor start-up. It uses a function foo() which is defined in the dynamic library. This function calls a function bar() defined in the main program.

This is the dynamic program progdyn.c:

#include <chorus.h> 

extern void foo();

main() {
    foo();              /* calling foo defined in the library */
}

void bar() {
    printf ("bar called\n");
}

This is the dynamic library libdyn.c:

#include <chorus.h> 

extern void bar();

void foo() {
    printf ("Calling bar\n");
    bar();              /* calling bar defined in the main program */
}

Building the Dynamic Library

Create a directory libdyndir in $WORK, containing libdyn.c and the following Imakefile:

SRCS = libdyn.c
DynamicLibraryTarget (libdyn.so, libdyn.o, , , ,-Xlinker -soname=libdyn.so )
Depend(libdyn.c)

In the libdyndir directory, build the dynamic library libdyn.so using the ChorusOSMkMf, make depend, and make commands.

Building the Dynamic Program

Create a directory progdyndir in $WORK, containing progdyn.c and the following Imakefile:

SRCS = progdyn.c
DynamicUserTarget (progdyn, progdyn.o, , 
        $(WORK)/libdyndir/libdyn.so,
        $(WORK)/libdyndir/libdyn.so, )
Depend()

In the progdyndir directory, build the dynamic program progdyn using the ChorusOSMkMf, make depend and make commands.


% ChorusOSMkMf $WORK
% make depend
% make

Running the Dynamic Program

Copy the dynamic program into the /bin subdirectory of the chorus_root_directory directory:


% cp $WORK/progdyndir/progdyn chorus_root_directory/bin 

Copy the dynamic library into the /lib subdirectory of the chorus_root_directory directory:


% cp $WORK/libdyndir/libdyn.so chorus_root_directory/lib 

Then, the following command will tell the runtime linker where to find the libdyn.so dynamic library:


% rsh jericho setenv LD_LIBRARY_PATH /lib 

Alternatively, set the runpath to /lib in the ldopts argument of the program macro (-rpath /lib).

Finally, the following command will start the program and dynamically load the libdyn.so library:


% rsh jericho arun /bin/progdyn

Explicit Link Using dlopen

The following program explicitly loads a dynamic library at runtime, using the function dlopen(). It searches for the address of the dynfunc() function defined in the library and calls this function.

This is the dynamic program progdyn2.c:

#include <chorus.h>
#include <cx/dlfcn.h>

int main()
{
    void    (*funcptr)();       /* pointer to function to search */
    void    *handle;            /* handle to the dynamic library */

        /* finding the library */
    handle = dlopen ("libdyn2.so", RTLD_NOW);
    if !(handle) { printf ("Cannot find library libdyn2.so\n"); exit(1); } 

        /* finding the function in the library */
    funcptr = (void (*)()) dlsym (handle, "dynfunc");
    if !(funcptr) { printf ("Cannot find function dynfunc\n"); exit(1); }

        /* calling library function */
    (*funcptr)();
}

This is the dynamic library libdyn2.c:

#include <chorus.h> 

void dynfunc() {
    printf ("Calling dynfunc\n");
}

Building the Program and the Library

The program and library above are built in the same way as in the previous example, using two Imakefiles:

Create a directory libdyn2dir in $WORK, containing libdyn2.c and the following Imakefile:

SRCS = libdyn2.c
DynamicLibraryTarget (libdyn2.so, libdyn2.o, , , , )
DependTarget(libdyn2.c)

Create a directory progdyn2dir in $WORK, containing progdyn2.c and the following Imakefile:

SRCS = progdyn2.c
DynamicUserTarget (progdyn2, progdyn2.o, , , , )
Depend(progdyn2.c)

Running the Dynamic Program

Copy the dynamic program into the /bin subdirectory of the chorus_root_directory directory:


% cp $WORK/progdyn2dir/progdyn2 chorus_root_directory/bin 

Copy the dynamic library into the /lib subdirectory of the chorus_root_directory directory:


% cp $WORK/libdyn2dir/libdyn2.so chorus_root_directory/lib 

Then, the following command will tell the runtime linker where to find the libdyn2.so dynamic library:


% rsh jericho setenv LD_LIBRARY_PATH /lib

Finally, the following command will start the program:


% rsh jericho arun /bin/progdyn2 

At program start-up, the runtime linker will only load the executable progdyn2. The libdyn2.so library will be loaded when the dlopen() function is called.