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.