This chapter introduces the steps involved in developing applications, also called actors, that run on the ChorusOS operating system. It includes the following sections:
"ChorusOS Applications" is a summary of the general principles of developing an application that runs on the ChorusOS operating system.
"Application Programming Interfaces" contains a summary of the APIs available.
"Developing ChorusOS Applications" explains how to build a component to be included in the ChorusOS system image.
"Using Dynamic Libraries" presents the two types of library in the ChorusOS operating system, and how to use them.
System development and advanced programming topics are not covered.
For information about porting ChorusOS software to another target, see the ChorusOS 4.0 Porting Guide.
For information about adding a device driver, see the ChorusOS 4.0 Device Driver Framework Guide.
For information about the tools used to build ChorusOS, see the ChorusOS 4.0 Production Guide.
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.
The ChorusOS operating system provides an environment for applications running on a network of target machines, controlled by a remote host.
The target system runs the ChorusOS operating system and provides the execution environment.
The host machine provides the development and debugging environment. The user can develop the applications on the host and, from the host, start and debug these applications on the targets.
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).
In order to compile and link an application, the following information is needed:
The header files and compilation flags
The program entry point
The libraries to be linked with the program according to the services used by the application, the environment present on the target system, and the actor type (user or supervisor)
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.
In order to choose which ChorusOS operating system libraries to use, the following points need to be considered:
Which APIs are used by the application program. For example, a program using the mathematical API has to be linked with the libm.a library.
What type of system is running on the target. For example, the librpc.a library cannot be used if you are using the basic environment and no additional features.
The address space in which the program will execute. For example a program loaded as a user extended actor must be linked with the libcx.a library.
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 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.
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:
Its execution environment: basic or extended environment.
Its execution mode: running in user or supervisor space.
Its execution structure: containing one or more ChorusOS operating system threads.
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. |
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>.
The programming environment of basic actors consists of the following interfaces:
The Microkernel API
The Private Data API
The Standard-C API
The Console Input/Output API
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.
The programming environment of extended actors consists of the following interfaces:
The Microkernel API
The Private Data API
The Standard-C API
The POSIX Input/Output API
The POSIX Network API
The Actor Management API
All routines implementing these APIs have been grouped into one library:
os/lib/classix/libcx.a | for user and supervisor actors |
An extended supervisor actor should not use the svExcHandler() call as an extended actor inherits the Actor Manager exception handler.
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.
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.
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.
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.
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.
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:
by protecting shared variables with mutexes and using threadOnce() to initialize these mutexes
by using the Private Data library to maintain private variables per thread (for errno management, see the next section)
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.
The ChorusOS operating system header files are packaged in five different directories:
kernel/include/chorus. Header files in this directory export the following APIs:
The Microkernel API
The Private Data API
The Actor Management API
kernel/include/stdc. Header files in this directory export the following APIs:
The Standard-C API
The Mathematical API
The Console Input/Output API
Some BSD specific APIs
include/posix. Header files in this directory export the following APIs:
The Standard-C API
The Mathematical API
The POSIX Input/Output API
The POSIX MRTP API
The POSIX Network API
The Sun RPC API
Some BSD specific APIs
include/CC. Header files in this directory export the GNU 2.7.1 C++ API.
include/X11. Header files in this directory export the following libraries: libX11.a, libXaw.a, libXext.a, libXmu.a, libXt.a.
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.
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.
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:
CFLAGS and CXXFLAGS specify the compilation options for C and C++ files, respectively. The compilation options are shown in Table 4-1.
Table 4-1 Compilation OptionsOption | Possible Settings | Default Setting |
---|---|---|
WARN | WARN_ON, WARN_OFF | WARN_ON |
DEBUG | DEBUG_ON, DEBUG_OFF | DEBUG_OFF |
PROF | PROF_ON, PROF_OFF | PROF_OFF |
OPT | OPT_ON, OPT_OFF | OPT_ON |
INCLUDES and DEPENDS specify include and depend values. These variables can be overloaded at the application level. They are grouped into the CPPFLAGS flag, which is used in compilation and to compute dependencies. Both INCLUDES and DEPENDS can be initialized at the application level.
LD_UCRT0, LD_SCRT0, LD_LCRT0, LD_CRTI, LD_CRTN, and LD_CRTXT are used to manage different types of crt object files.
LD_U_ACTOR and LD_S_ACTOR specify link information for user and supervisor actors.
CLX_U_LIBS, CLX_S_LIBS, EBD_U_LIBS, EBD_S_LIBS, and CXX_LIBS are provided to manage libraries.
The make environment includes the following commands: cc, ld, as, and mkactors.
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.tmpl contains definitions of variables.
Imake.rules contains build rules. See "imake Build Rules".
Package.rules contains the rules used to build a binary distribution.
The file Imake.tmpl contains the following definitions:
FAMILY, indicating the target family (x86, usparc, ppc60x, mpc860, mpc8260).
COMPILER, indicating the compiler to be used (gcc, for example).
REL_DIR, indicating the path of the current directory. This variable is automatically set in subdirectories by imake.
HOSTOS, indicating the host operating system (solaris, win32).
DEVTOOLS_DIR, indicating the path of the ChorusOS tools.
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 rulesMacro 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:
obj is the list of binary objects included in the actor.
prog is the name of the actor.
libs is the list of additional libraries used to build the actor. The actor is linked by default with the library to provide either the basic or extended environment.
The rules used to build dynamic actors are described in more detail in "Building a Dynamic Program".
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 rulesMacro 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. |
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".
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.
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.
There are two types of library in the ChorusOS operating system:
Static library names are suffixed by .a. A static library is a collection of binary object files (.o). The linker concatenates all needed binary objects of the static libraries into the executable program file.
Dynamic
Dynamic library names are suffixed by .so. They can be linked with a program at runtime. Dynamic linking is supported by a ChorusOS operating system component called the runtime linker. It occurs in two cases:
At actor start-up: in order to build the executable, the runtime linker loads and links a list of libraries. These libraries are called the dependencies of the executable.
During actor execution: with the dynamic linking API, an actor can explicitly load and link dynamic libraries, using the dlopen() function. This allows dynamic programming.
Dynamic libraries are loaded at runtime and are not included in the executable. This reduces the size of executable files. Dynamic libraries use relocatable code format. This code is turned into absolute code by the runtime linker.
In the ChorusOS operating system, both user and supervisor actors (but not boot actors) can use dynamic libraries.
Relocatable code can be contained in two types of executable:
Relocatable executable: at actor start-up, the runtime linker loads the executable and performs the necessary relocations.
Dynamic executable: at actor start-up, the runtime linker loads the executable and performs the necessary relocations. It also loads and links the executable dependencies, that is, the dynamic libraries used by the executable.
An actor that uses dynamic libraries is called a dynamic actor. A relocatable actor uses only static libraries.
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.
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)
dlib: name of resulting dynamic library (must be suffixed by .so).
objs: library components: list of binary object files (suffixed by .o).
staticLibs: list of static libraries (.a) that will be statically linked.
dynamicLibs: list of dependencies: dynamic libraries that must be loaded together with the resulting library. Each library can be defined in one of two ways:
-L path -l name : on the host, the linker will look for library path/libname.so. On the target, the runtime linker will look for libname.so in the library search path.
path: this is an absolute or relative library path used on the host by the linker, and on the target by the runtime linker. A relative path containing a / is interpreted as relative to the current directory by the runtime linker. A path without / is searched in the library search path by the runtime linker.
dlDeps: list of dynamic libraries the library depends upon. If these libraries are changed, the resulting library dlib will be rebuilt. Each library must be defined as a path on the host. Generally dlDeps duplicates the libraries described in dynamicLibs. This allows, when the -L path -l name syntax is used, to express the dependency without embedding a path in the executable.
options: any linker options, preceded by -Xlinker. This must be used to supply system-specific linker options which GNU C does not know how to recognize. If you want to pass an option that takes an argument, you must use -Xlinker twice, once for the option and once for the argument.
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, , )
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:
-Xlinker -soname=<name> within a DynamicLibraryTarget() rule sets the internal so-name of the library. If a library used as a dependency in a rule that builds a dynamic executable has a so-name defined, the executable records the so-name instead of the dynamicLibs argument.
-Xlinker -rpath -Xlinker <dir>: defines the runpath directory that is added to the library search path (see "Runtime Linker").
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)
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:
By processing a dynamic object when it is required rather than during the initialization of an application, start-up time can be greatly reduced. In fact, the object might not be required if its services are not needed during a particular run of the application.
The application can choose between several different dynamic objects depending on the exact services required. For example, if different libraries implement the same driver interface, the application can choose one driver implementation and load it dynamically.
Any dynamic object added to the actor address space during execution can be freed after use, thus reducing the overall memory consumption.
Typically, an application performs the following sequence to access an additional dynamic object, using the dynamic library API:
A dynamic object is located and added to the address space of a running application using dlopen(). Any dependencies this dynamic object has are located and added at this time.
The added dynamic object and its dependencies are relocated, and any initialization sections within these objects are called.
The application locates symbols within the added objects using dlsym(). The application can then reference the data or call the functions defined by these new symbols.
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.
Any error conditions that occur as a result of using these runtime linker interface routines can be displayed using dlerror().
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:
It analyzes the executable's dynamic information section and determines which dynamic libraries are required.
It locates and loads these dynamic libraries, and then it analyzes their dynamic information sections to determine whether any additional dynamic library dependencies are required.
Once all dynamic libraries are located and loaded, the runtime linker performs any necessary relocations to bind these dynamic libraries in preparation for actor execution.
It calls any initialization functions provided by the dynamic libraries. These are called in the reverse order of the topologically sorted dependencies. Should cyclical dependencies exist, the initialization functions are called using the sorted order with the cycle removed.
It passes control to the application.
It calls any finalization functions on deletion of dynamic objects from the actor. These are called in the order of the topologically sorted dependencies.
The application can also call upon the runtime linker's services to acquire additional dynamic objects with dlopen() and bind to symbols within these objects with dlsym().
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.
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.
The following environment variables are used by the runtime linker:
LD_LIBRARY_PATH specifies a colon (:) separated list of directories that are to be searched before the default directories defined above. This is used to enhance the search path that the runtime linker uses to find dynamic and shared libraries.
LD_PRELOAD provides a dynamic object name that is linked after the program is loaded but before any other dynamic objects that the program references.
LD_DEBUG is a column-separated list
of tokens for debugging the runtime linking of an application. Each token
is associated with a set of traces which are displayed on the system console
during runtime linking. The supported tokens are: file-ops
, reloc
, symbol-resolution
, malloc
, segment-alloc
, dependancy
, misc
, linking
, dynamic-map-op
, group
, and error
. Wildcard substitutions
are also allowed, so that s*
, for example, matches
both symbol-resolution
and segment-alloc
, and *
matches all traces.
Immediate binding:
The runtime linker performs both data reference and function reference relocations during process initialization, before transferring control to the application. This behavior is equivalent to the LD_BIND_NOW behavior in the Solaris operating environment (lazy binding is not supported).
No version checking:
The runtime linker performs no version dependency checking. When looking for a library, the runtime linker looks for a file name matching the library name exactly. This behavior is equivalent to the LD_NOVERSION behavior in the Solaris operating environment.
Weak symbols and aliases:
During symbol resolution, weak symbols will be silently overridden by any global definition with the same name. Weak symbols can be defined alone or as aliases to global symbols. Weak symbols are defined with pragma definitions.
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
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 */ }
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.
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 |
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 |
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"); }
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)
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.