Channels ▼

Embedded Systems

MiniRTL: A Minimal Real-Time Linux

Source Code Accompanies This Article. Download It Now.

Dec00: MiniRTL: A Minimal Real-Time Linux

An RTOS on a diskette

By Peter Wurmsdobler and Nicholas McGuire

Peter is a researcher at the Centre de Transfert des Microtechniques in France. He can be contacted at Nicholas is a member of the Computational Material Science Group at Vienna University. He can be reached at

An operating system or application is considered to be real time if time constraints imposed by the external world (deadlines) are met. However, two types of deadlines can be distinguished -- defined ones that are known a priori, and sudden deadlines, such as interrupts. As long as a system strictly meets all deadlines within a certain defined tolerance, it can be called "hard real time."

When it comes to multimedia applications, the constraints imposed by the human sensoric system, both in terms of time delay tolerated and consequences if a deadline is not strictly met, are not as rigid as they need to be for industrial control systems. For these applications, a real-time system must meet all deadlines and deliver a service within a certain period. For example, most digital control systems are working periodically at a certain control frequency. If the resulting sampling time constituting a deadline is not met, the controller can become unstable with vast consequences. Another part of a control system can be an interrupt handling for an emergency shut down within a given period from the point when the security sequence is triggered. In both cases, only a real-time operating system (RTOS) can guarantee both periodic and sudden deadlines.

Depending on the required time resolution, standard Linux supports real time to a limited extent. However, for Linux to become a hard real-time operation system, modifications to the Linux kernel must be made. This is the difference between regular Linux and hard real-time Linux implementations such as FSMLab's RTLinux ( or DIAPM's RTAI ( The core idea behind RTLinux is to run a full-featured operating system as one thread of a real-time executive. Since the nonreal-time applications do not need to meet any deadlines, the Linux thread is set to run as the lowest priority task, the idle task, of real- time Linux.

In a logical sense, both tasks and interrupts are separated into two classes -- the real-time ones running directly on behalf of the executive, and the nonreal-time ones being executed within the common operating system, with some means of communication between these two priority spaces. To accomplish this, the RTLinux modifications to the kernel puts a thin layer beneath Linux. This core hard real-time mechanism takes over control of the low-level interrupt handling from Linux: In some cases, Linux kernel code is modified to make this takeover work smoothly. The low-latency interrupt handlers cannot be preempted by Linux acting only on emulated interrupts. All other functionalities and communication means (schedulers, timers, POSIX I/O, FIFOs, shared memory, semaphores, and mutexes) can be added by inserting a kernel module at run time. With the core functionality, real-time interrupt service routines (ISRs) can be installed already as a simple real-time application. Once the modules for time and scheduling are inserted into the kernel, both task and interrupt priority spaces are created. From that point on, the thin, hard real-time layer constitutes a single process per CPU running on the bare computer hardware, with Linux being just a low-priority thread among real-time threads and real-time ISRs.

A Typical Real-Time Linux Application

To understand what's involved in writing a typical real-time application, consider a data-acquisition program that runs as a real-time thread. The periodic part of the thread reads the measured variable, calculates the control variable, and outputs it on a data-acquisition (DAQ) board. After this, both measured and control variables are stuffed into a FIFO to make them accessible from a user-space application. In case of an interrupt from the parallel port, everything should be stopped. Listing One is the code for the control thread (DAQ functions for a board are also provided), while Listing Two defines some shared memory (shm), the control thread with its function to be executed, and the interrupt handler. When programming the kernel module, add Listing Three to the usual code for init_module and cleanup_module, respectively. The real-time controller starts immediately once you compile the kernel module under real-time Linux and insert it into the kernel. A nonreal-time application can then write control parameters into shared memory and read both measured and control variable from the FIFOs.

Once applications such as this are written, they are usually implemented in embedded systems. However, this can be a problem because of the limited memory footprints and system size constraints that embedded systems require. In other words, RTLinux is often too big for the application platform. One solution to this problem is MiniRTL, short for "Minimum Real-Time Linux," which fits on a single floppy (see http:// minirtl.html and ftp://ftp.thinkingnerds .com/pub/projects/ minirtl/).

Minimum Real-Time Linux

Why take a full-featured Linux OS and drop it into an embedded system? Because Linux is a stable desktop system with many development tools. That means it is relatively easy to do development on the desktop (with minor restrictions, of course), then drop the results into a minimum (embedded) system. Debugging and optimization can be done on the desktop system, too. The decision to stick to the current kernels, although they are quite large, is because of the kernel features -- especially in the networking and hardware support area. Consequently, sticking with the mainstream kernel makes it easy to ensure compatibility to the mainstream desktop Linux systems.

Generally, real-time Linux for embedded platforms aim at high-end projects -- those built around PCs. These high-end embedded systems start somewhere in the range of 2 MB of RAM disk space and 2 MB RAM for a 1.0.9 ELF Kernels (see Paul Gortmaker's linux-lite,, or 2 MB of RAM disk and 4 MB RAM for 2.0.X/2.2.X. For a safe estimate, a 4-MB RAM disk and 4 MB RAM are the bottomline at which such a system can be identified as a Linux OS. Also, many jobs can be done by 386/486-based systems (even if a midi-tower is not normally thought of as an embedded system).

Consequently, MiniRTL, which fits on a single floppy, is a viable alternative for small footprint systems (not to mention that a small system is easier to understand than a 4-GB desktop one). Originally based on the Linux router project (see, MiniRTL is a stand-alone, networked Linux system that fits on a 1.44-MB diskette. That said, it is a little archaic and you should not expect emacs as the system default editor. For the most part, MiniRTL's features are the same as standard Linux features. However, there are some capabilities that ought to be highlighted when it comes to embedded-system design.

  • Hard real-time Linux. Kernel 2.2.13RTL2.0 (2.2.14RTL2.2).
  • glibc 2.0.7.

  • Full support of the x386 upward (including SMP).

  • Low latency on disk access and high disk bandwidth with RAM disk usage.

  • Support for most standard PC hardware.

  • Shell (ash) at the console and via telnet.

  • Secure access via SSH/SCP.

  • Full network support (inetd, SSHD, HTTP, DNS, NFS, dial-up connectivity, etc).

  • Off-site login via syslog.

  • mini_httpd (see software/mini_httpd) with cgi-bin support (ash scripts and command execution).

  • No specialized software required for developing your own projects.

  • Source-code availability.

Kernel Modifications

Of course, kernel modifications were necessary to implement MiniRTL, and as you might expect, the modifications were extensive. Those changes include:

  • initrd (see*). To boot the initial RAM disk, we needed to modify the standard kernel by replacing the hook in init/main.c that points to the regular root device to point to the initial RAM disk. This would have been relatively straightforward if we had raw images, but running raw images is not very comfortable during development and messy to modify because they require you to tell the kernel exactly where to jump. In other words, raw images have no "filesystem hook." To permit use of standard tar.gz archives, we made additional modifications to create an initial minix filesystem on the RAM disk, then let the kernel unpack all it needs into the filesystem. One problem with this is the initial console -- it does not exist at the time where the kernel actually is ready to boot up the system, because it has not been created yet. We solved this problem by having a special device called "linuxrc.tty" packed on the disk. linuxrc.tty is a minimalist tty, only used at boot time.
  • mkminixfs/untar (see ftp://ftp.psychosis .com/linux/initrd-archive/linuxrc-always*). In drivers/block/rd.c, the RAM disks are created by the kernel at boot time. This is where the code for creating the minix filesystem on /dev/ram0 and unpacking the root archive from tar.gz form are inserted. This is essential because the root image is no longer a raw image, but simply a standard tar.gz file. Because of this add-on, Linux users can create the modified archive files. All you need to do is pack everything you would like to have in a tar archive (provided you can get it on a 1.44-MB diskette) and gzip it.

  • rtl. Besides the initrd and mkminixfs/ untar modifications, we used the normal RTLinux patches. We made no modifications to these patches, as of Version 1.1 (Kernel 2.0.36) and 2.0/2.2 (Kernel 2.2.13/2.2.14). Care is to be taken with some of the example applications. The kernel modules that actually represent the real-time capabilities are of no concern, but the front-end application interfaces are restricted in the libs available on the MiniRTL system. For the standard demo applications, no modifications were required, but application design for a minimum system must take the lib restrictions into account.

Designing a Minimum System

Minimizing disk use in a RAM disk-based system is critical for performance. A 2.2.13 kernel will boot and run in 4 MB, but leaves little room for applications. Since Linux is sensitive to low-RAM setups, it is no exaggeration to state that doubling RAM on low-memory systems increases overall performance just as much as doubling CPU frequency. Because a RAM disk resides in a buffer cache, increasing RAM-disk use will reduce available memory. This shows how to optimize performance and also makes clear why dynamic disk usage during boot-up is not really a critical point (resulting in little need to reduce the image size of the booting system, as only run-time size is relevant).

Designing a minimal system is time-consuming, mainly because UNIX is highly redundant when it comes to executables (for instance, you can do the job of displaying a file on the console with cat, tail, more, less, grep, and even dd). So finding redundant executables and defining the scope is imperative. The first step is to ask yourself questions, such as:

  • What do I really need to do onsite and what can I move offsite (data analysis, system logging, and so on)?
  • Do I need all options available with ls, tar, and the like?

  • Can I substitute some executables by simple shell scripts?

  • Can I substitute system services (tftp for ftp, for example)?

  • What do I need at boot-up, and what can I delete/compress during run time?

A nonembedded system typically has executables in place that are used at the next system boot. Embedded systems that run in RAM disk are running an image copy, however, so they don't need to preserve files if they are not required for the systems operation. They can simply be removed. Candidates for this include the kernel (once it's loaded, you don't need it), initialization scripts, modules that you won't need to reload until a system reboot, and executables that only do initialization (sshd key generation, for instance).

Many other executables/modules may reside on the filesystem in compressed form because they will only be needed for specific purposes during maintenance. It is essential, though, to ensure that these compressed executables are not called from scripts without decompressing them first (this sounds obvious but the dependencies even in a 2-MB system can be complex, so it is no trivial task to ensure this). Candidates for this are all executables (editors, utilities, and maintenance programs, such as ping, ifconfig, and the like) that are only used by administrative personnel and not by scripts (after boot-up that is).

In short, modifications such as those described here can reduce system run-time size by 30-40 percent, and filesystem size by 40 percent. This reduction can result in a substantial increase of run time available RAM.

Size Reduction

Of course, footprint size can be reduced using compression. However, this is limited to the filesystem size of executables. Since the run-time size is not affected, we needed to reduce the run-time size as well.

The regular Linux kernel assumes full multiuser, multitasking operation without real limits to the number of processes and users. Consequently, the resources allocated are greater than the actual needs of a minimal system. Optimization to the Linux kernel of minimal systems is therefore the first step. This really only involves throwing out some resources (ptys, ttys, hdX, and hooks in /dev). Real optimization in the sense of modifying kernel behavior is not necessary (and probably not that easy either) and counter productive because new Linux kernels are out every few weeks. Removing resources is simple and requires no great kernel hacking. Starting points may be found in include/linux/*.h, but be careful with the limits defined there, not all may be reduced without side effects.

For example, there will rarely be the need for 63 console devices on an embedded system, so the maximum number of consoles in include/linux/tty.h can be reduced to three (giving you four consoles). This reduces kernel memory trace, although not necessarily the image size. Other good candidates are device files you will not need to create all hda*-hdh*. I can't imagine an embedded system running with eight IDE devices hooked up.

A significant drawback to compile-time modifications is that you have to dig around in the kernel source every time a new kernel comes out. That means you will have a job every few weeks if you want to keep up with the current kernel. Putting minimization flags in the config process would be a nice solution, but we doubt that it will find its way into the main stream kernel.

Also, many executables you use on a daily basis have multiple options -- most of which you rarely need. Stripping away options, error messages, and built-in help is an easy way of reducing such files.

Libs and the Busybox Concept

Libc is a large and powerful library, but for minimal systems it can be problematic because it is resource consuming. Nevertheless, we stuck with the complete libc implementation because reducing its size is not only complicated (you must figure out all function calls that are unused and cleanly remove them), but modifying it can introduce compatibility problems. Still, stripped-down libs are dramatically smaller and there is no need to include debug symbols on the minimal system since debugging can be done on the desktop. The same holds true for executables that can be stripped. To reduce the number of required libs, it is best to define and build on a set of libraries for the minimal system. This isn't a big problem because of the large amount of software/sources on the Internet -- it is easy to find editors, scripting languages, and the like that will not need any special libs. Table 1 lists the minimum required libs (libc-2.0.7pre6) that provide network support. If network support isn't important, libraries can be reduced even more.

A unique way to pack functionality onto a minimal system is the "busybox" concept (see The busybox is a monolithic collection of base functions in a single executable with function selection by the name of the calling process, which is handled by sym -- linking functions -- names the busybox executable. The advantage is that there is little overhead in the executable for argument handling, the ELF header, and the like, since there is only one parser for all linked executables. Also, in busybox, we reduced functionality to a minimum, which is at the price of some standard functions behaving a little different than one might expect (some options are missing, others are reduced in scope). Adding functions to busybox is straightforward, but requires recompiling the entire suite and modifications to the system initialization to set up the required links appropriately.

Table 2 compares the size of busybox-0.28 to the sum size of the corresponding standard Linux tools. This comparison might not seem fair, since many options available in the standard tools are not available in busybox. However, the point is that you can pack lots of functionality into a tiny executable if reduced to the minimum needs of administration. The real challenge here is to decide what is needed and what can be removed. With all the dependencies within an OS-like Linux and the use of shell scripts for many jobs, this is not easily decided. This, of course, leads back to one of the motivations for developing a minimal system -- reduced system complexity and the resulting ease of understanding the dependencies.

Table 3 compares a "hello world" program in C (compiled with egcs-2.91.66 as an ELF executable) and the size difference of a "hello world" function added to busybox (called via a link named "hello"). This shows how efficient the overhead reduction of having one executable for all functions is achieved by having one main routine, and calling everything else as a function via the name by which busybox is called.

Are statically linked executables an alternative if a program needs libXXX, which is not available on the system? As Table 4 illustrates, the answer is clearly no. While it may be unfair to make such a comparison with libc statically linked, it is still a telling example.

An Example MiniRTL Program

Although not fancy, sched_toggle.c (see Listing Four) is typical of MiniRTL applications. The program lets you measure the precision of the scheduler timing by periodically toggling pins D0-D3 of the parallel port. You can test the program by downloading the MiniRTL distribution onto a diskette and rebooting your PC. If nothing else, you can see how simple it is to write a real-time Linux program.


The hard-real-time variants of Linux presented here are under rapid development. To get a thorough understanding of the concepts and programming methods, we recommend Real-Time Linux Systems, by P.N. Daly et al. (O'Reilly & Associates, 2000). For additional information, go to Finally, to jumpstart your real-time Linux projects, check out projects/rtos-ws/rtos-ws.html.


Listing One

extern unsigned short int rt_daq_aget( void );
extern void rt_daq_aset(unsigned short int channel, unsigned short int value);

Back to Article

Listing Two

typedef struct
    int w; /* setpoint */
    int k; /* gain */
shm_t *shm; /* shm to user space */
pthread_t control_thread; /* control thread */
void *rt_control_function( void *t ) /* control thread's function */
    int Y, U, E;
    shm->k = 0;
    shm->w = 0;
    pthread_make_periodic_np( control_thread, gethrtime(), SAMPLE_TIME
    while(1) /* this is the periodic part */
        pthread_wait_np(); /* wait for next sample hit */
        Y = rt_daq_aget(); /* get DAQ input */
        E = (int) ( shm->w - Y ); /* control deviation */
        U = (unsigned short int)( shm->k * E ); /* P controller */
        rt_daq_aset( OUTPUT_CHANNEL, U ); /* output control variable */
        rtf_put( Y_FIFO, &Y, sizeof(Y) ); /* stuff Y into fifo */
        rtf_put( U_FIFO, &U, sizeof(U) ); /* stuff U into fifo */
/* interrupt service routine for "emergency shutdown" */
unsigned int rt_spp_isr( unsigned int irq_number, struct pt_regs *p )
    rt_daq_aset( OUTPUT_CHANNEL, 0 ); /* output 0 */
    pthread_make_periodic_np( control_thread, HRTIME_INFINITY, 0 );
    return 0;

Back to Article

Listing Three

int init_module(void)
    pthread_attr_t attr;      /* attributes */
    struct sched_param param; /* parameter */
    /* Get a hard IRQ for SPP */
    rtl_request_global_irq( SPP_IRQ, rt_spp_isr );
    /* Initialize shared memory */
    shm_allocate( SHM_NAME, SHM_SIZE, (void **) &shm );
    /* Initialize rt-fifos */
    rtf_create( Y_FIFO, FIFO_SIZE );
    rtf_create( U_FIFO, FIFO_SIZE );
    /* and now we initialize the kernel thread. */
    pthread_attr_init( &attr );
    pthread_attr_setcpu_np( &attr, 0 );
    sched_param.sched_priority = 1;
    pthread_attr_setschedparam( &attr, &param );
    pthread_create( &control_thread, &attr, rt_control_function, (void *)1 );
    return 0;
void cleanup_module(void)
    /* Release the SPP */
    rtl_free_global_irq( SPP_IRQ );
    /* Delete control thread */
    pthread_delete_np( control );
    /* Release rt-fifos */
    rtf_destroy( Y_FIFO );
    rtf_destroy( U_FIFO );
    /* Release shared memory */
    shm_deallocate( shm );

Back to Article

Listing Four

#include <rtl.h>
#include <time.h>
#include <pthread.h>
#include <asm/io.h>

/* set the paralell port address */
int lpt_port=0x378;

/* compiled in addresses are unpractical so lsets define a module parameter
 * that permits setting the parport address at module insertion time
 * insmod sched_toggle.o lpt_port=0x3bc would override 0x378 . */
pthread_t thread;
/* the runtime thread toggling D0-D3 of the paralell port  */
void * toggle(void *arg)
    int nibl;
    hrtime_t now;
    struct sched_param p;
    p. sched_priority = 1;
    /* set the attributes for the thread, this
     * defines the scheduling policy and priority */
    pthread_setschedparam (pthread_self(), SCHED_FIFO, &p);
    now = gethrtime(); /* get the current time in nano-seconds */
    /* make the thread periodic
     * starting now, with a period of 500000000 nano-seconds */
    pthread_make_periodic_np (pthread_self(), now, 500000000);
    nibl = 0x0f;
    while (1)
        outb(nibl,lpt_port); /* write it out to the parport*/
        nibl = ~nibl; /* two's complement of nibl*/
        pthread_wait_np (); /* put the thread on the wait queue */
    return 0;
/* init_module is called when the module is inserted it will do
 * the basic setup and register the module with the kernel */
int init_module(void)
    rtl_printf("sched_toggle.c: RTL thread starts on CPU%d:
    using LPT at 0x%x\n", rtl_getcpuid(),lpt_port);
    /* create the thread. attributes are "initialized" to NULL and 
     * set in toggle thread, attributes, function, ARG */
    return pthread_create (&thread, NULL , toggle , 0 );
/* when the module is removed with rmmod sched_toggle this is called
 * to cleanup any kernels trace of this module */
void cleanup_module(void)
    /* send a request to thread asking for cancelation */
    pthread_cancel (thread);
    /* wait until thread gave up and exited properly */
    pthread_join (thread, NULL);

Back to Article

Related Reading

More Insights

Currently we allow the following HTML tags in comments:

Single tags


<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags


<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

