Introducing Real-Time Linux


If you wanted to control a camera or a robot or a scientific instrument from a PC, it would be natural to think of using Linux so you could take advantage of the development environment, the X Window System, and all the networking support. However, Linux cannot reliably run these kinds of hard real-time applications. A simple experiment can illustrate the problem. Take a speaker and hook it up to one of the pins from the parallel port; then run a program that toggles the pin. If your program is the only one running, the speaker will produce a nice, somewhat steady tone—not completely steady, but not bad. When Linux updates the file system every couple of seconds, you might notice a small change in tone. If you move the mouse over a couple of windows the tone becomes irregular. If you start Netscape, you hear intervals of silence as your program waits for higher priority processes to run.

The problem is that Linux, like most general purpose operating systems, is designed to optimize average performance and to try to give every process a fair share of compute time. This is great for general purpose computing, but for real-time programming, precise timing and predictable performance are more important than average performance. For example, if a camera fills a buffer every millisecond, a momentary delay in the process reading that buffer can cause data loss. If a stepper motor in a lithography machine must be turned on and off at precise intervals in order to minimize vibration and to move a wafer into position at the correct time, a momentary delay may cause an unrecoverable failure. Consider what might happen if the task that causes an emergency shutdown of a chemistry experiment must wait to run until Netscape redraws the window.

It turns out redesigning Linux to provide guaranteed performance would take an enormous amount of work. And taking on such a job would defeat our original purpose. Instead of having an off the shelf general purpose OS, we would have a custom-made special purpose OS that would not be riding the wave of the main Linux development effort. So we slipped a small, simple, real-time operating system underneath Linux. Linux becomes a task run only when there is no real-time task to run, and we pre-empt Linux whenever a real-time task needs the processor. The changes needed in Linux itself are minimal. Linux is mostly unaware of the real-time operating system as it goes about its business of running processes, catching interrupts, and controlling devices. Real-time tasks can run at quite a high level of precision. In our test P120 system, we can schedule tasks to run within a precision of about 20 microseconds.

Real-time Linux is a research project with two goals. First, we want a practical, non-proprietary tool we can use to control scientific instruments and robots. Our other goal is to use RT-Linux for research in real and non-real-time OS design. We'd like to be able to learn something about how to make operating systems efficient and reliable. For example, even a non-real-time operating system should be able to determine whether it can guarantee timing needed for its I/O devices. We're also interested in what types of scheduling disciplines actually turn out to be the most useful for real-time applications. Following this dual purpose, in this paper we discuss both how to use RT-Linux and how it works.

Using RT-Linux 2.0.RT.1

Let us consider an example. Suppose we want to write an application that polls a device for data in real-time and stores this data in a file. The main design philosophy behind RT-Linux is the following:

Real-time programs should be split into small, simple parts, with hard real-time constraints, and larger parts that do more sophisticated processing.

Following this principle, we split our application into two parts. The hard-real-time part will execute as a real-time task and copy data from the device into a special I/O interface called real-time fifo. The main part of the program will execute as an ordinary Linux process. This part will read data from the other end of the real-time fifo and display and store the data in a file.

The real-time component will be written as a kernel module. Linux allows us to compile and load kernel modules without rebooting the system. Code for a module always starts with a define of MODULE and an include of the module.h file. After that, we include the real-time header files rt_sched.h and rt_fifo.h and declare an RT_TASK structure.

#define MODULE
#include <linux/module.h<
/* always needed for real-time tasks */
#include <linux/rt_sched.h>
#include <linux/rt_fifo.h>
RT_TASK mytask;

The real-time task structure will contain pointers to code, data, and scheduling information for this task. The task structure is defined in the first include file. Currently, RT-Linux has only one fairly simple scheduler. In the future, the schedulers will also be loadable modules. Currently, the only way for real-time tasks to communicate with Linux processes is through special queues called real-time fifos. Real-time fifos have been designed so that the real-time task will never be blocked when it reads or writes data. Figure 1 illustrates real-time fifos.

Figure 1. Real-time fifos

The example program will simply loop, reading data from the device, writing the data to an RT-fifo, and waiting for a fixed amount of time.

/* this is the main program */
void mainloop(int fifodesc) {
        int data;
        /* in this loop we obtain data from */
        /* the device and put it into the */
        /* fifo number 1 */
        while (1) {
                data = get_data();
                rt_fifo_put(fifodesc, (char *)
                  &data, sizeof(data));
                  /* give up the CPU till the */
                  /* next period              */

All modules must contain an initialization routine. The initialization routine for the example real-time task will:

  • record the current time,

  • initialize the real-time task structure,

  • and put the task on the periodic schedule.

The rt_task_init routine initializes the task structure and arranges for an argument to be passed to the task. In this case, the argument is a fixed descriptor for a real-time fifo. The rt_make_periodic routine puts the new task on the periodic scheduling queue. Periodic scheduling means the task is scheduled to run at certain intervals in time units. The alternative is to make the task run only when an interrupt causes it to become active.

This function is needed in any module. It */
/* will be invoked when the module is loaded. */
int init_module(void)
        #define RTfifoDESC  1
        /* get the current time */
        RTIME now = rt_get_time();
        /* `rt_task_init' associates a function */
        /* with the RT_TASK structure and sets  */
        /* several execution parameters:        */
        /* priority level = 4,                  */
        /* stack size = 3000 bytes,             */
        /* pass 1 to `mainloop' as an argument  */
        rt_task_init(&mytask, mainloop,
            RTfifoDESC, 3000, 4);
        /* Mark `mytask' as periodic. */
        /* It could be interrupt-driven as well */
        /* mytask will have period of 25000 */
        /* time units. The first run is in  */
        /* 1000 time units from now         */
        rt_task_make_periodic(&mytask, now +
            1000, 25000);
        return 0;

Linux also requires that every module have a cleanup routine. For a real-time task, we want to make sure a dead task is no longer scheduled.

/* cleanup routine. It is invoked when the */
/* module is unloaded.                     */
void cleanup_module(void)
        /* kill the realxi--time task */

That's the end of the module. We also need a program which runs as an ordinary Linux process. In this example, the process will just read data from the fifo and write (copy) the data to stdout.

#include <rt_fifo.h>
#include <stdio.h>
#define RTfifoDESC  1
#define BUFSIZE 10
int buf[BUFSIZE];
int main()
        int i;
        int n;
        /* create fifo number 1 with size of */
        /* 1000 bytes                        */
        rt_fifo_create(1, 1000);
                /* read the data from the fifo */
                /* and print it                */
                rt_fifo_read(1, (char *) buf,
                        BUFSIZE * sizeof(int));
                        printf("%d ", buf[i]);
        /* destroy fifo number 1 */
        return 0;

The main program could also display data on the screen, send it over the network, etc. All these activities are assumed to be non-real-time. The fifo size must be big enough to avoid overflows. Overflows can be detected, and another fifo can be used to inform the main program about them.