Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Overview

The DAT group has divided RTEMS and DAT support code into a number of "dynamically shared objects" which are structurally the same as the shared libraries one uses under Linux. Under such a system there are two kinds of linking, or binding pieces of compiled code into a single functioning whole. The first kind of linking, "static" linking, creates the shared objects from object files produced by the compiler. The second kind of linking, "dynamic" linking, loads shared objects into memory on demand while the program is already running. As the name implies a single copy of a shared object's code and data may be used by a number of other shared objects which reduces the use of memory. There is also a potential reduction gained from loading only what's actually needed.

The system boot loader loads and starts the principle shared object containing RTEMS and the DAT dynamic linker. Then the application initialization code written by the DAT group uses the dynamic linker to load the remaining shared objects specified by the system configuration, itself contained in several shared objects.xxx

Using the dynamic linker

xxxThe dynamic linker is available for use in applications as well as in system start-up.

From the RTEMS shell

The shell provides the "run" command in order to let you run a special kind of shared object called a Task. What makes a Task special is the way its code is organized and the name of the entry point. Most of the organization is taken care of by including a special Task-stub library when you create the shared object.

The "run" command allows you to specify the  Task name (up to four characters), scheduling priority and the size of the stack (in bytes).xxx

From C or C++ code

xxxThe C function lnk_load() is the main entry point of the dynamic linker. You give it the name of the shared object you want to load. The linker will attempt to find it and any other shared objects it needs according to the rules laid out in the next section.

SO names, dependencies, namespaces and installation

...

Though each shared object contains a list of the sonames of all the other shared objects it depends on, the so-called needed-list, this doesn't tell the dynamic linker just what the object requires from the other objects. It could be user data, functions, classes, etc. Each of these items has a symbolic name which appears in a symbol table built into the shared object that defines them. The object that needs to use them also has the symbolic names in its table, though marked as "undefined", that is to say "not defined in this shared object". There is no indication of which shared object is the definer. Instead, for each undefined symbol in a newly-loaded object the dynamic linker searches for a definition in each of the objects on the first object's needed-list, taking the first definition that it finds. It doesn't check for duplication. The linker then puts the address of the definition it found into all the places in the first shared object that require it.

Info
titleSearch scope

For any given shared object the set of objects searched to satisfy its undefined references is called its scope. For DAT code the scope is the original shared object followed by the objects named in its needed-list. It may seem strange to search an object for its own undefined but it's necessary to handle some unusual cases. This scope rule is much simpler than the one employed by a Linux dynamic linker.

Initialization of newly loaded shared objects

The following initializations are performed in the order listed here.

Uninitialized variables

A shared object may define statically allocated variables that are given no explicit initial values in source code. Such variables take up no space in the shared object file; there's only a count of how much space they take up.  Dynamic linking has to allocate space for these variables and, in accordance with the C and C++ standards, initialize that space to all zeros.

The .init section and C++ static constructors

A shared object is divided into many  named "sections" some of which have special meaning for the dynamic linker. One of these is named ".init" and contains pointers to functions that must be called before the shared object can be considered usable. Normally the .init section contains pointers to functions that run static C++ constructors but with the "section" attribute you can place pointers of your own in the .init section.

Info
titleAttributes

Attributes are a language extension offered by GCC.

Installation

If the shared object was loaded as a dependency, contains a global variable named lnk_options and that variable's (integer) value has the bit LNK_INSTALL set then the shared object's soname and location are recorded in the table of installed objects.

The prelude and preferences functions

Shared objects to be run on a DAT system can have two optional initialization functions which if present are called by the dynamic linker. The first, lnk_preferences(), returns a 32-bit preference datum which may be an int or a pointer and is passed as an argument to lnk_prelude(). The latter function is a general initialization function designed to be more easily used than the .init section. Since most other initialization for the shared object has been done, lnk_prelude() can do most anything: start tasks, load other shared objects, print messages, etc.

Memory access permissions

A shared object file's loadable content is divided into a small number of "segments". Each segment has a set of permission flags: (R)eadable, (W)riteable ans e(X)ecutable. There's normally one RX segment containing instructions and read-only data and one RW section containing non-constant data. The dynamic linker uses the CPU's memory management unit (MMU) to set the access type of the memory allocated to each segment to match the segment's permission flags.