ChorusOS 5.0 Application Developer's Guide

Chapter 6 Building Applications for ChorusOS Systems

This chapter demonstrates the use of imake macros to build different types of ChorusOS applications written in the C and C++ languages. The examples in this chapter are installed with the ChorusOS product as part of the SUNWewp examples package.

As previously discussed, ChorusOS applications can be POSIX processes or actors. This chapter provides the build information for both types of applications.

After completing the examples provided in this chapter, you will understand how to use imake to build an application with the ChorusOS product.

The default installation directory for all examples used in this book is install-dirchorus_family/src/opt/examples/. You can use the SolarisTM tool pkginfo(1)as follows:


% pkginfo | grep SUNWewp
application SUNWewps Sun Embedded Workshop for Solaris/SPARC Examples

Using make and imake

To build a ChorusOS component, use the make and imake tools. All development tools are provided in the install_dir/5.0/chorus-family/tools directory of your ChorusOS installation.

The make Environment

The make environment is defined by a file containing variable definitions and rules. This file provides the rules for compiling C, C++, and assembly language. The rules are specific to your compiler - the name of the file indicates the compiler used. For example, when using the gcc compiler, the make environment file is named tgt-make/gcc-devsys.mk. This file contains the variables and rules required for building the component. The following variables are defined:

The make environment includes the following commands:

The imake Environment

The ChorusOS imake environment enhances the make environment by providing template rules for common ChorusOS build operations using generic names. When using the predefined imake rules, it is not necessary to know which libraries, crt files, or entry points should be used to build an application, because they are selected automatically.

Instead of creating Makefiles you create Imakefiles, and imake generates Makefiles from them.

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

imake Variable Definitions

The Imake.tmpl file contains the following definitions:

imake Build Rules

The Imake.rules file contains macros known as Imake build rules. A list of Imake build rules and their functions is displayed in Table 6-4.

Table 6-4 imake Build Rules
 Macro name Function
MakeDir(dir)Creates a 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 or process.
SupActorTarget(prog, objs, libs) Creates a supervisor C actor or process.
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 or process named prog, and passes options, crt0 and libs to the linker.
CXXUserActorTarget(prog, objs, libs) Creates a user C++ actor or process.
CXXSupActorTarget(prog, objs, libs) Creates a supervisor C++ actor or process.
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 process.
DynamicSupTarget(prog, objs, libs, dynamicLibs, dlDeps, options) Creates a dynamic supervisor C process.
DynamicCXXUserTarget(prog, objs, libs, dynamicLibs, dlDeps, options) Creates a dynamic user C++ process.
DynamicCXXSupTarget(prog, objs, libs, dynamicLibs, dlDeps, options) Creates a dynamic supervisor C++ process.
DynamicLibraryTarget(dlib, objs, staticLibs, dynamicLibs, dlDeps, options) Creates a dynamic library.
SharedUserTarget(prog, shobjs, staticLibs, sharedLibs, slDeps, options) Creates a shared user C process.
SharedSupTarget(prog, objs, staticLibs, sharedLibs, slDeps, options) Creates a shared supervisor C process.
SharedCXXUserTarget(prog, objs, staticLibs, sharedLibs, slDeps, options) Creates a shared user C++ process.
SharedCXXSupTarget(prog, objs, staticLibs, sharedLibs, slDeps, options) Creates a shared supervisor C++ process.
SharedLibraryTarget(shlib, shobjs, sharedLibs, staticLibs, slDeps, options)  Creates a shared library.

The rules for building actors and processes use the following common arguments:

The rules used to build dynamic or shared libraries for actors and processes are described in more detail in "Building a Dynamic Process".

imake Packaging Rules

The Package.rules file contains macros known as Imake packaging rules which are used for building a binary distribution. Their names and functions are listed in Table 6-5.

Table 6-5 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.

imake Examples

The following examples demonstrate how to create an Imakefile using single and multiple source files.

Using imake

The application in this example is composed of a single C source file, myprog.c, in the directory myprog. Writing an Imakefile is fairly straightforward.

  1. Set the SRCS variable to the list of source files.

    In this case there is only one source file:

    SRCS = myprog.c
  2. 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 in the following file extract. The first argument is the name of the executable and the second lists the object files. The third argument enables you to specify which libraries your program requires. In this example there is no library, therefore the argument is empty (you could also pass a NullParameter).

    UserActorTarget(myprog_u,myprog.o,) 

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

    SupActorTarget(myprog_s.r,myprog.o,)  
  3. Use the Depend() macro to generate the Makefile dependencies.

    Depend($(SRCS))

    The Imakefile is now complete and looks as follows:

    SRCS = myprog.c
    UserActorTarget(myprog_u,myprog.o,)
    Depend($(SRCS))
  4. Generate the Makefile with the ChorusOSMkMf tool.

    See the ChorusOSMkMf(1CC) man page for details of how to do this. In the myprog directory, type:


    % ChorusOSMkMf build_dir
    

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

  5. Generate the make dependencies

    To do this type:


    % make depend
    
  6. Compile and link the program

    To do this type:


    % make
    

    The compiled program is now in your myprog directory and ready to be executed.

Using 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 make Makefiles, make depend, and then make.


% make Makefiles   
% make depend   
% make

The compiled program is now ready to be executed.


Note -

Examples of Imakefiles which can be modified and used to build your own applications are provided in install_dir/chorus_family/src/opt/examples.


Libraries, Actors and Processes

Actors and processes are defined according to the types of libraries they use. This section describes the different library types in the ChorusOS operating system and illustrates the kinds of actors and processes that would use each library type.

Library Types

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

Both dynamic and shared library names are suffixed by .so. However, each library type is built in a different way and is readily distinguished. The imake tool does not check that library components are PIC format binary object files. The resulting library may contain .o objects, which are not in PIC format, that can still be dynamically loaded but can no longer be shared.

It is possible to check whether or not a library is shared. To check whether or not the libfoo.so library is shared, for example, run the following GNU command:


% objdump -x libfoo.so | grep TEXTREL

Note -

objdump is located in the chorus_family/tools tree. The specific path includes a family-dependent prefix.


If the library is shared, no output is generated by this command. Only dynamic library object files contain references to TEXTREL.

Actors, Processes and Libraries

Actors or processes can be divided into two groups, according to the types of libraries they use.

When you build a process or actor other than an embedded actor, you must specify one of the following macros in your Imakefile:

Each of these rules implicitly calls the libc.a or libc.so libraries. Therefore, when creating the Imakefile for a process or actor, there is no need to think about the libc.a or libc.so libraries because this is taken care of when you select the Imakefile rule. The C++ library is automatically included by specifying CXX in the relevant Imakefile rule.

Static and Dynamic Linking

The static linker runs on the development host and the runtime linker (dynamic linker) runs on the target.

The following table summarizes the actions performed by the static linker and by the runtime linker.

 Link Relocatable executable Dynamic executable
 Static Linker.a Static linker adds the required objects (.o) of a static library (.a) to the executable. .a - Static linker adds the required objects (.o) of a static library (.a) to the executable. .so - Static linker adds the library to the list of libraries to be loaded at actor start-up.
 Runtime Linker n/a.so - At actor start-up, libraries are loaded and linked by the runtime linker. Libraries to be loaded are defined either as static links, or in the LD_PRELOAD environment variable. The runtime linker uses a library search path to locate dynamic and shared libraries.
Runtime Linker (dlopen) n/a.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 libraries it uses are also loaded.

Runtime Linker

Dynamic applications consist of one or more dynamic objects. The dynamic application is typically a dynamic executable and associated 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 enable the application to extend its address space by adding 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, proceeded 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 that are to be searched ahead of the default directories.


Note -

The runtime linker requires a file system to load dynamic objects. This file system can be on a host and can be 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

Dynamic Programming

In a dynamic program, an actor can link a dynamic or shared library explicitly during its execution. This on-demand object linking has several advantages:

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

  1. A dynamic object is located and added to the address space of a running application using dlopen(). Any dependencies this dynamic object has are also located and added at this time.

  2. The added dynamic object and its dependencies are relocated, and any initialization sections within these objects are called.

  3. The application locates symbols within the added objects using dlsym(). The application is then able to reference the data or call the functions defined by these new symbols.

  4. After the application has finished with the objects, the address space can be freed using dlclose(). Any termination section within the objects being freed will be called at this time.

  5. Any error conditions that occur as a result of using these runtime linker interface routines can be displayed using dlerror().

Both dynamic and shared libraries can be used in dynamic programs. The only difference is that shared libraries are not duplicated in memory.

Building a Dynamic Library

The following imake macro is used to build dynamic libraries:

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

This macro includes the following arguments:

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, , )

Note -

Dynamic libraries are supported with the gcc compiler only.


Building a Dynamic Process

The following imake macros are used to build dynamic applications:

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)

For details of the functions that each macro performs, refer to "imake Build Rules".

The prog argument is the name of the resulting process. Other arguments are similar to those in the DynamicLibraryTarget() macro. For the options argument, the following options are particularly useful:

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

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

Building a Shared Library

The following imake macro is used to build shared libraries:

SharedLibraryTarget(shlib, shobjs, sharedLibs, staticLibs, slDeps, options)

The following example builds a shared library named libfoo.so from the PIC binary objects files, a.o and b.o. When this library is loaded dynamically, the runtime linker will also load the libshared.so.

SharedLibraryTarget(
        libfoo.so, 
        a.o  b.o, ,
        libshared.so, , )

Building a Shared Process

The following imake macros are used to build shared applications:

SharedUserTarget(prog, shobjs, staticLibs, 
        sharedLibs, slDeps, options)
SharedSupTarget(prog, objs, staticLibs, 
        sharedLibs, slDeps, options)
SharedCXXUserTarget(prog, objs, staticLibs, 
        sharedLibs, slDeps, options)
SharedCXXSupTarget(prog, objs, staticLibs, 
        sharedLibs, slDeps, options)

For more information on each of these macros, refer to "imake Build Rules".

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

The following example builds a shared application named shr_app from the PIC binary object files a.o and b.o. When this process is started, the runtime linker loads the shared libraries libshared.so and libcx.u.so (if required). In the target file system, this library is located in the /libraries directory because this directory will be added to the search path of the runtime linker.

SharedUserTarget(
        shr_app, 
        a.o  b.o, ,
        libshared.so, ,
        -Xlinker -rpath -Xlinker /libraries)

As with shared libraries, it is possible to build shared applications which do not contain .o objects in PIC format. The applications are still able to use shared libraries, however the application code is not shared.

Examples of Dynamic and Shared Applications

This section discusses two examples of dynamic and shared applications. In 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).

In these examples, 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 Application Start-up

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

The following is the dynamic application progdyn.c:

#include <chorus.h> 

extern void foo();

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

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

The following 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 application */
}
Building the Dynamic Library
  1. Create the directory and the Imakefile.

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

    SRCS = libdyn.c
    DynamicLibraryTarget (libdyn.so, libdyn.o, , , ,)
    Depend(libdyn.c)
  2. Build the dynamic library.

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

Building the Dynamic Application
  1. Create the directory and the Imakefile.

    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(progdyn.c)
  2. Build the dynamic application.

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


    % ChorusOSMkMf $WORK
    % make depend
    % make
    
Running the Dynamic Application
  1. Copy the dynamic application.

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


    % cp $WORK/progdyndir/progdyn chorus_root_directory/bin 
    
  2. Copy the dynamic library.

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


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

    The following command will notify 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 application macro (-rpath /lib).

  3. Start the application.

    Start the application and dynamically load the libdyn.so library, using the following command:


    % rsh jericho /bin/progdyn
    

Explicit Link of a Dynamic Library Using dlopen

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

The following 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)();
}

The following 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 discussed in the previous section are built in the same way as the previous example using two Imakefiles:

  1. Create the first directory.

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

    SRCS = libdyn2.c
    DynamicLibraryTarget (libdyn2.so, libdyn2.o, , , , )
    Depend(libdyn2.c)
  2. Create the second directory.

    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
  1. Copy the program.

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


    % cp $WORK/progdyn2dir/progdyn2 chorus_root_directory/bin 
    
  2. Copy the library.

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


    % cp $WORK/libdyn2dir/libdyn2.so chorus_root_directory/lib 
    
  3. Notify the runtime linker.

    Use the following command to notify the runtime linker where to find the libdyn2.so dynamic library:


    % rsh jericho setenv LD_LIBRARY_PATH /lib
    
  4. Start the program.

    Use the following command to start the program:


    % rsh jericho arun /bin/progdyn2 
    

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

Dynamic Link of a Shared Library at Application Start-up

A small modification to the previous example enables a shared application to load a shared library dynamically.

Loading a Shared Library Dynamically
  1. Create the shared library file.

    Copy the libdyn.c file into the libshareddir directory and rename it libshared.c. The ensuing Imakefile will build a PIC binary object file called libshared.o and a shared library named libshared.so as follows:

    FPIC = ON
    SRCS = libshared.c
    SharedLibraryTarget (libshared.so, libshared.o, , , , )
    Depend($(SRCS))

  2. Create the shared program file.

    Copy the progdyn.c file into the progshareddir directory and rename it progshared.c. The ensuing Imakefile will build a PIC binary object file called progshared.o and an executable file called progshared:

    FPIC = ON
    SRCS = progshared.c
    LIBPATH = <pathname of the shared library directory>
    SharedUserTarget (progshared_u, progshared.o,
       $(UTILS_LIB) $(CLX_UTILS_LIB), -L$(LIBPATH)
       -Xlinker -rpath -Xlinker/shared -lshared,,)
    Depend($(SRCS))

  3. Copy the shared application.

    Copy the shared application into the /bin subdirectory of the chorus_root_directory directory:


    % cp $WORK/progshareddir/progshared_u chorus_root_directory/bin 
    

  4. Copy the shared library.

    Copy the shared library into the /shared subdirectory of the chorus_root_directory directory:


    % cp $WORK/libshareddir/libshared.so chorus_root_directory/shared
    


    Note -

    There is no need to set LD_LIBRARY_PATH as the path for the runtime linker. This path was specified in the Imakefile by setting the -Xlinker -rpath option.


  5. Start the application.

    The following command starts the application and loads the libshared.so and libc.so libraries (if they have not already been loaded by another application):


    % rsh jericho /bin/progshared_u
    

Explicit Linking of a Shared Library Using dlopen

The following procedure enables a shared program to load a shared library explicitly.

Linking a Shared Library Using dlopen
  1. Create the library file.

    Copy the libdyn2.c file into the libshared2dir directory and rename it libshared2.c. The following Imakefile will build a shared library called libshared2.so:

    FPIC = ON
    SRCS = libshared2.c
    SharedLibraryTarget (libshared2.so, libshared2.o,,,,)
    Depend($(SRCS))

  2. Create the program file.

    Copy the progdyn2.c file into the progshared2dir directory and rename it progshared2.c. The following Imakefile will build a PIC binary object file called progshared2.o and an executable file called progshared2

    FPIC = ON
    SRCS = progshared2.c
    SharedUserTarget (progshared2_u, progshared2.o,
        $(UTILS_LIB) $(CLX_UTILS_LIB),,,)
    Depend($(SRCS))

  3. Copy the application.

    Copy the shared application into the /bin subdirectory of the chorus_root_directory directory:


    % cp $WORK/progshared2dir/progshared2_u chorus_root_directory/bin 
    

  4. Copy the shared library.

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


    % cp $WORK/libshared2dir/libshared2.so chorus_root_directory/lib 
    

  5. Notify the runtime linker.

    The following command will notify the runtime linker where to find the libshared2.so shared library:


    % rsh jericho setenv LD_LIBRARY_PATH /lib/shared:/lib 
    

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

  6. Start the program.

    The following command will start the program and load the libshared2.so and libc.so libraries (if they have not already been loaded by another actor):


    % rsh jericho /bin/progshared2