Application setup and init

Note

This guide is specific to Xenomai 3. Most of the features described here have no counterparts in Xenomai 2.

The description applies to both Cobalt and Mercury configurations indifferently, unless otherwise stated. The reader may assume that application always refers to any executable program linked against the Xenomai libraries, whatever its purpose.

Caution

This discussion exclusively refers to user-space programs forming an application, based on the Xenomai libraries. There is no reference whatsoever to any kernel-based context.

How to build an application

The most important rule of thumb is to always retrieve the compilation and link flags from a call to xeno-config --cflags and xeno-config --ldflags respectively, mentioning the list of Xenomai APIs your application uses. The complete usage of the xeno-config script can be found at this URL.

Invoking xeno-config from a Makefile

CFLAGS := $(shell xeno-config --posix --cflags)
LDFLAGS := $(shell xeno-config --posix --ldflags)

Warning

Failing to use xeno-config for retrieving the proper build flags for a Xenomai application will produce a bad executable, which simply can’t work.

Standard Xenomai command line options

Any application linked against the Xenomai libraries automatically recognizes a set of standard options that can be passed on the command line, which applies to any Xenomai-based program. There is nothing you need to add to your application code for these option switches to be interpreted and acted upon. For instance, the --dump-config switch is always recognized by an application, dumping the settings used for building the Xenomai libraries it is linked against:

Xenomai/mercury v3.0-rc5 -- #e91216a (2015-05-12 17:58:01 +0200)
CONFIG_MMU=1
CONFIG_SMP=1
CONFIG_XENO_ASYNC_CANCEL=1
CONFIG_XENO_BUILD_ARGS=" '-prefix=/home/rpm/install/xenomai/mercury' '--enable-async-cancel' '--enable-lores-clock' '--enable-debug=full' '--enable-smp' '--enable-tls' '--with-core=mercury' '--enable-registry' '--enable-pshared' '--enable-maintainer-mode'"
CONFIG_XENO_BUILD_STRING="x86_64-unknown-linux-gnu"
CONFIG_XENO_COMPILER="gcc version 4.9.2 20150212 (Red Hat 4.9.2-6) (GCC) "
CONFIG_XENO_DEBUG=1
CONFIG_XENO_DEBUG_FULL=1
CONFIG_XENO_DEFAULT_PERIOD=100000
CONFIG_XENO_FORTIFY=1
...
CONFIG_XENO_LIBS_DLOPEN is OFF
CONFIG_XENO_LORES_CLOCK_DISABLED is OFF
CONFIG_XENO_RAW_CLOCK_ENABLED is OFF
CONFIG_XENO_TLSF is OFF
CONFIG_XENO_WORKAROUND_CONDVAR_PI is OFF

The list of standard options is as follows:

–registry-root=<path>

Tells Xenomai to root the object registry at the given path, instead of the default root (see the --enable-registry switch from the configuration options).

–session=<label>

Name of the session the new process will be part of (or create if not present). If --enable-pshared was given when configuring the Xenomai libraries, this label allows multiple processes giving the same label at startup to share objects created by members of the same session.

All shared objects are allocated from a global heap backed by
the main memory pool (see --mem-pool-size).
–shared-registry

Exports the registry of the process to other users. If access is possible, also depends on permissions of the registry path. By default, the registry is only accessible for the user that started the Xenomai process.

–no-registry

This switch disables registry support at runtime. No real-time objects will be exported to the registry, despite the registry code was compiled in.

–mem-pool-size=<size[K|M|G]>

The size the main memory pool backing the session’s heap, which can be suffixed by a K(ilobyte), M(egabyte) or G(igabyte) binary multiplier. Most of the dynamic allocation requests issued from the Xenomai libraries are served from this pool. In absence of suffix, the value is normally interpreted as a count of bytes, except if lower than 65536, see below.

Note

If the value is lower than 65536 with no suffix, it is interpreted as a count of kilobytes for backward compatibility purpose with the former syntax. This work around may disappear anytime when transitioning to Xenomai 3.0 final, so make sure to fix any launch script(s) accordingly. Typically, suffixing the current value with a K multiplier would address the issue.

Caution

The shared heap is living in the /tmpfs filesystem, and therefore consumes RAM space.
–cpu-affinity=<cpu[,cpu]…>

The set of CPUs available for running Xenomai threads created by the application, in absence of explicit pinning of such threads to a particular CPU when they are spawned. Defaults to any online CPU.
The argument is a list of valid (i.e. online) CPU numbers separated by commas. This option may appear several times on the command line, cumulating the settings.

–main-prio=<priority>

Cobalt only. When xenomai_init() is called for bootstrapping the real-time services for the current process, the calling context is automatically turned into a Xenomai thread, managed by the Cobalt kernel. Normally, this call runs over the main() routine, hence the parameter name. By default, the current scheduling policy and priority are kept during the transition. + This parameter allows to force new scheduling parameters for the thread invoking xenomai_init() when it enters the Xenomai realm, according to the following rules:

  • if <priority> is zero, the calling thread will be assigned the SCHED_OTHER policy.
  • if <priority> is greater than zero, the calling thread will be assigned the SCHED_FIFO policy, and the given priority level.
  • if <priority> is negative, the scheduling parameters of the calling context will be kept unchanged (default case).
–print-buffer-size=<num-bytes>

Cobalt only. When symbol wrapping is in effect (default case for applications based on Cobalt’s POSIX API), Xenomai interposes on common output calls from the stdio support such as printf(3) fprintf(3) and puts(3), so that no delay or loss of real-time guarantee is incurred by the caller.
The underlying mechanism is based on relay buffers forming an output ring, filled in by real-time threads locklessly, which are periodically flushed by a regular (non real-time) Linux helper thread to the process’s destination stream (stdout, stderr or any other particular stream). + This parameter allows to set the size of a typical relay buffer. By default, a relay buffer is 16k large.

–print-buffer-count=<num-buffers>

Cobalt only. Use this parameter to set the total number of relay buffers (see –print-buffer-size). By default, 4 relay buffers are present in the output ring.

–print-sync-delay=<ms>

Use this parameter to set the longest delay (in milliseconds) before the output pending into the relay buffers is synchronized (see –print-buffer-size). By default, the output ring is flushed each 100 milliseconds by the helper thread.

–[no-]sanity

Turn on[/off] sanity checks performed by the Xenomai libraries, mostly during the application startup. Defaults to the value set by the –enable-sanity switch when configuring (which is enabled by default).
For instance, running Xenomai libraries built for a uni-processor system over a SMP kernel is detected by such checks.

–verbose[=level]

Set verbosity to the desired level, or 1 if unspecified. The level argument may be interpreted differently depending on the application, however –verbose=0 must mean fully quiet. The interpretation of higher levels is application-specific. Defaults to 1.

–silent
–quiet

Same as –verbose=0.

–trace[=level]

Set tracing to the desired level, or 1 if unspecified. The level argument may be interpreted differently depending on the application, however –trace=0 must disable tracing. Level 1 allows tracing the Xenomai library bootstrap code. The interpretation of higher levels is application-specific. Defaults to 0.

–version

Get the application and Xenomai version stamps. The program immediately exits with a success code afterwards.

–dump-config

Dump the settings used for building the Xenomai libraries the application is linked against.

–no-mlock

Mercury only. This switch disables the implicit mlock() call at init, normally used for locking the calling process’s virtual address space into RAM, in order to avoid the extra latency induced by virtual memory paging.

This option does not apply to the Cobalt core, which requires
memory locking from all clients unconditionally.
–help

Display the help strings.


Configuration and runtime tunables

Tunables are variables used as configuration values while setting up the system (e.g. size of the main memory heap), or controlling its runtime behavior (e.g. verbosity level).

Some of those tunables may be updated by the application’s early code until the system starts initializing the core data structures and bringing up the services, then become read-only from that point. These are called the configuration tunables.

Other tunables may be changed freely at any point in time during the application process lifetime. These are called the runtime tunables.

Accessing the tunables

There is a simple API for reading and updating the current value of any tunable, composed of the {get/set}_config_tunable(name) and {get/set}_runtime_tunable(name) C macros.

When --enable-assert is in effect for the Xenomai libraries, set_config_tunable() enforces read-only access after the configuration tuning phase, by raising an exception on attempt to change such tunable after that point.

Each tunable has an arbitrary name, which does not necessarily reflects the name of the variable actually storing its value. For instance, one could define tunable foo eventually handling the int bar C variable under the hood, or even a set of related variables.

Typically, updating and reading back the verbose_level tunable – which holds the current verbosity level of an application – would look like this:

#include <stdio.h>
#include <xenomai/tunables.h>

set_runtime_tunable(verbosity_level, 1);
printf("verbosity_level=%d\n", get_runtime_tunable(verbosity_level));

Note

When a configuration tunable is paired with a command line option updating the same variable(s), the command line has precedence over the set_config_tunable() call.

Xenomai’s built-in tunables

The Xenomai libraries define two sets of built-in tunables.

Table 1. Configuration tunables
NAME DESCRIPTION DEFAULT
cpu_affinity
same as –cpu-affinity option
any online CPU
no_mlock
same as –no-mlock option
off
no_sanity
same as –no-sanity option
!CONFIG_XENO_SANITY
no_registry
same as –no-registry option
off (i.e. enabled)
mem_pool_size
same as –mem-pool-size option
1Mb
session_label
same as –session option
none (i.e. anonymous)
registry_root
same as –registry-root option
CONFIG_XENO_REGISTRY_ROOT
shared_registry
same as –shared-registry option
off (i.e. private)
Table 2. Runtime tunables
NAME DESCRIPTION DEFAULT
verbosity_level
same as –verbose option
1
trace_level
same as –trace option
0

Defining your own tunables

You can add your own tunables using the define_{config/runtime}_tunable(name) and read_{config/runtime}_tunable(name) C macros.

The define_tunable syntax provides the helper code for updating the C value(s) matching the tunable name. Conversely, the read_tunable syntax provides the helper code for returning the C value(s) matching the tunable name.

The application code should use {get/set}_config_tunable(name) or {get/set}_runtime_tunable(name) for accessing the new tunable, depending on its scope.

Defining and using a simple tunable

/* Out of line definition. */

code.c:

int foo_runtime_variable;

define_runtime_tunable(foo, int, val)
{
        /*
         * The code to update the internal variable upon a call to
         * set_runtime_tunable(foo).
         */
        foo_runtime_variable = val;
}

read_runtime_tunable(foo, int)
{
        /*
         * The code to return the current tunable value upon a
         * call to get_runtime_tunable(foo).
         */
        return foo_runtime_variable;
}

header.h:

/* Declaration of the manipulation helpers */

define_runtime_tunable(foo, int, val);
read_runtime_tunable(foo, int);

/* Conversely, we could be using an inline definition */

header.h:

extern int foo_runtime_variable;

static inline define_runtime_tunable(foo, int, val)
{
        foo_runtime_variable = val;
}

static inline read_runtime_tunable(foo, int)
{
        return foo_runtime_variable;
}

/* Accessing the new tunable, inverting the value. */

int setting = get_runtime_tunable(foo);
set_runtime_tunable(foo, !setting);

Overriding factory default values of tunables

It may be convenient to provide your own factory default values for some configuration tunables, before they are considered for initializing the application.

For this early tuning to happen, you need to interpose your own handler in the chain of tuning handlers the Xenomai bootstrap code runs before starting the actual initialization process. This can be done with a Xenomai setup descriptor as follows:

#include <xenomai/init.h>
#include <xenomai/tunables.h>

static int foo_tune(void)
{
        /*
         * We need more than 1MB which is Xenomai's default, make
         * it 16MB before the Xenomai core starts initializing
         * the whole thing. --mem-pool-size=<size> may override
         * this value.
         */
        set_config_tunable(mem_pool_size, 16MB);

        /*
         * Also turn verbosity off by default. --verbose=<level> on the
         * command line may switch this to verbose mode.
         */
         set_runtime_tunable(verbosity_level, 0);

         return 0; /* Success, otherwise -errno */
}

/*
 * CAUTION: we assume that all omitted handlers are zeroed
 * due to the static storage class. Make sure to initialize them
 * explicitly to NULL if the descriptor belongs to the .data
 * section instead.
 */
static struct setup_descriptor foo_setup = {
        .name = "foo",
        .tune = foo_tune,
};

/* Register the setup descriptor. */
user_setup_call(foo_setup);

Application entry (C/C++)

From the user code standpoint, every application starts in the main() routine as usual.

Some link flags used for building the application have an influence on its initialization sequence; such flags are retrieved by a call to xeno-config --ldflags, passing along the list of Xenomai APIs the application depends on (e.g. xeno-config --posix --alchemy --ldflags).

The Xenomai libraries can take care of such sequence for bootstrapping their services appropriately (i.e. automatic bootstrap), or leave such duty to the application code (i.e. manual bootstrap).

Automatic bootstrap

If the automatic bootstrap mode is used, the application receives the set of command line switches passed on invocation in the main() routine, expunged from the Xenomai standard options. In this mode, all Xenomai services are readily available when main() is entered.

This is the default behavior most applications should use.

Manual bootstrap

In manual bootstrap mode, the application receives the original argument vector unaltered, with the Xenomai services not initialized. You should use this mode if you need some early initializations of your own to take place before the Xenomai services are started.

The manual bootstrap mode is enabled by linking with the flags received from xeno-config --ldflags --no-auto-init.

In such mode, the application code must call xenomai_init(&argc, &argv) at some point in the initialization step, to bootstrap the Xenomai services for the process. This routine handles all standard Xenomai options, then updates the arguments to point to a copy of the original vector, expunged from the Xenomai options.

Afterwards, the application code can collect and handle the unprocessed options from the command line, and use the Xenomai services with no restriction.

Dealing with C++ static constructors

The initialization sequence guarantees that C++ constructors – with default priorities – of objects instantiated within the main executable will run after the Xenomai services have been bootstrapped in automatic mode. This means that class constructors for static objects from the main executable may call Xenomai services.

Since Xenomai services are not available until xenomai_init() has been called in manual bootstrap mode, no such guarantee exists in this case, until that routine is invoked and returns successfully.

As described for the initialization process through the various software layers, C++ constructors of static objects instantiated by shared libraries will NOT have access to Xenomai services, since the Xenomai bootstrap code runs later.

Tip

If you have non-default static constructor priorities, or static objects instantiated in shared libraries your executable depends on, you may need to provide your own early bootstrap code, in case the constructors need to call Xenomai services. This code should eventually call xenomai_init() for bootstrapping the Xenomai services, before these C++ constructors may invoke them.

Handling application-defined arguments

You may want to provide for your own command line switches to be interpreted by the application code.

If the automatic bootstrap mode is used, the application directly receives the set of command line switches passed on invocation in the main() routine, expunged from the Xenomai standard options.

With a manual bootstrap, the application receives the original argument vector passed on the command line. Calling xenomai_init(&argc, &argv) processes then removes all standard Xenomai options from the vector before returning to the caller.

In both cases, the application-specific options present in the argument vector can be retrieved with the getopt(3) parser as usual.

Parsing application-specific arguments

#include <getopt.h>
#include <stdio.h>
#include <xenomai/init.h>

int foo_mode = 1; /* Default */

static const struct option options[] = {
        {
#define foo_mode1_option        0
                .name = "foo1",
                .flag = &foo,
                .val = 1,
        },
        {
#define foo_mode2_option        1
                .name = "foo2",
                .flag = &foo,
                .val = 2,
        },
        {
                /* sentinel */
        }
};

int main(int argc, char *const *argv)
{
        int lindex, ret, opt;

#ifdef MANUAL_BOOTSTRAP
        /* xeno-config --no-auto-init --ldflags was given. */
        ret = xenomai_init(&argc, &argc);
#endif
        for (;;) {
                lindex = -1;
                opt = getopt_long_only(argc, argv, "", options, &lindex);
                if (opt == EOF)
                        break;
                switch (lindex) {
                case foo_mode1_option:
                case foo_mode2_option:
                     break;
                default:
                        usage();
                        return 1;
                }
        }
        ...
}

Under the hood

Initialization sequence

The Xenomai-specific application initialization process bootstraps the software layers as follows:

Layered initialization sequence

Tip

lib/boilerplate/init/bootstrap.c implements the bootstrap handler which is glued as an object file to the main executable of the application, so that it is guaranteed to run after any shared library constructor the application depends on, regardless of the library constructor priorities.

In automatic bootstrap mode, The xenomai_main() routine interposes on the real main() entry point, for passing it the pre-processed argument vector, expunged from the Xenomai standard options. This trick is based on the linker’s symbol wrapping feature (–wrap). xeno-config issues the right set of linker flags to enable such interposition, unless --no-auto-init is given.

Adding your own setup code

Xenomai implements a flexible mechanism for running setup code in an orderly manner, on behalf of the bootstrap handler glued to the application.

This mechanism is based on setup descriptors (struct setup_descriptor), each defining a set of optional handlers, which shall be invoked by the Xenomai bootstrap code at different stages of the initialization process. A priority level is assigned to each descriptor, which defines the calling sequence among multiple descriptors implementing the same handler. The corresponding C definitions and types should be obtained by including <xenomai/init.h>.

There are three initialization stages, listed by order of precedence, each assigned a handler in the setup descriptor. NULL handlers can be used to skip a descriptor for a given phase.

  • Factory tuning step, via setup_descriptor→tune(). Each setup descriptor is given a chance to assign a value to standard or application-defined tunables. This is the handler we used for changing a default tunable value.
  • Command line option parsing, via setup_descriptor→parse_option(). If this handler is present, setup_descriptor→options must point to a getopt(3) option array, enumerating the valid options defined by this descriptor.

The →parse_option() handler is passed the position of each option found into the command line which matches an entry into the setup_descriptor→options array, along with the argument value received. If the option does not carry any argument, NULL is passed instead.

The →help() handler is called whenever the –help switch is passed on the command line. All help handlers from the descriptor chain will be called in turn, from low priority to high.

Option parsing happens after the tuning step, so that command line options affecting tunables can override the factory values defined by the →tune() handlers.

  • Initialization step proper, via setup_descriptor→init(). Based on the current settings obtained from the previous steps, this handler is called for bringing up the feature or service it provides.

All handlers should return zero on success, or a negated POSIX error code on error. Upon error, the bootstrap sequence is immediately terminated, and the application exits with a fatal message.

The example below introduces a custom code into the initialization sequence, for setting some base tunables then parsing a set of local command line options, before bringing up a specific feature. These custom handlers will be called after all Xenomai handlers from the same category have been fired. Such code could be part of an ancillary library attached to your application for instance, or implemented by your main executable.

Adding a custom setup code

#include <getopt.h>
#include <stdio.h>
#include <xenomai/init.h>
#include <xenomai/tunables.h>

int half_value;

static int foo_value = 16; /* Default */

static const struct option foo_options[] = {
        {
#define foo_option      0
                .name = "foo-value",
                .has_arg = 1,
        },
        {
                /* sentinel */
        }
};

static int foo_tune(void)
{
        /*
         * We want a 8MB heap by default. User may override
         * this with --mem-pool-size if need be.
         */
        set_config_tunable(mem_pool_size, 8MB);

        return 0;
}

static int foo_parse_option(int optnum, const char *optarg)
{
        /*
         * Xenomai's command line parser will call us only for
         * valid options we know about, we just need to evaluate
         * the argument.
         */
        switch (optnum) {
        case foo_option:
                foo_value = atoi(optarg);
                break;
        default:
                /* Paranoid, can't happen. */
                return -EINVAL;
        }

        return 0;
}

static void foo_help(void)
{
        fprintf(stderr, "--foo-value=<val>              some value for no usage\n");
}

static int foo_init(void)
{
        if (foo_value & 1)
            /* Uh, cannot deal with odd values. */
            return -EINVAL;

        half_value = foo_value / 2; /* Let's init stuff. */

        return 0;
}

static struct setup_descriptor foo_setup = {
        .name = "foo",
        .tune = foo_tune,
        .parse_options = foo_parse_option,
        .options = foo_options,
        .help = foo_help,
        .init = foo_init,
};

/* Register the setup descriptor. */
user_setup_call(foo_setup);

Extending the help strings

When –help is given on the command line, the Xenomai option handler intercepts it and issues information from outer to inner code, following the decreasing priorities of the registered setup descriptor. i.e. from the most application-specific code to the most generic libraries providing help strings. Typically, this should make your application-defined help strings appear first.

#include <xenomai/init.h>
#include <stdio.h>

/*
 * This is a routine with a weak binding we may override for
 * providing our own help message header. get_program_name()
 * is a Xenomai helper readily available when including
 * <xenomai/init.h>.
 */
void application_usage(void)
{
        fprintf(stderr, "usage: %s [options]:\n", get_program_name());
}

Tip

If you need to call the help string dumper manually (e.g. should a different option than --help trigger this), you can invoke void xenomai_usage(void).

Extending the version information

By default, the Xenomai core library issues its own version information when --version is found on the command line. The application can insert its own signature in the output message to stderr by implementing the void application_version(void) hook.

#include <xenomai/init.h>
#include <stdio.h>

/*
 * This is a routine with a weak binding we may override for
 * providing our own version header. The version message
 * would then be:
 *
 * $ ./foo --version
 * foo utility v1.2
 * based on Xenomai/mercury v3.0-rc4 -- #eaca2e7 (2015-05-13 12:17:21 +0200)
 */
void application_version(void)
{
        fprintf(stderr, "foo utility v1.2\n");
}

Tip

The current Xenomai version string is available from the const char *xenomai_version_string constant, exported by <xenomai/init.h>.