RTLinux Application Development Tutorial

A Simple Example

Enough talk--let's go through a simple example, demonstrating real-time code, threading and usage of the real-time FIFOs. In order to run this example, you'll need to be running RTLinux, but configuration is detailed for you either in the RTLinux Open download or the RTLinux Professional distribution and won't be covered here. You can find both versions on-line at http://www.fsmlabs.com/products/download.htm.

At this point, we assume a running RTLinux kernel with the basic RTLinux modules loaded (those loaded with insrtl). Rather than dragging you through the normal ``hello world'', we'll do something slightly different. Here, a user-space process will communicate to real-time code, directing each one to perform a different step in running a real-time ``hello world''.

Before we dive into the code, let's briefly discuss the idea. As with most RTLinux applications, there are two components: a real-time module and a user-space management piece. In this case, the real-time code consists of two threads and a real-time FIFO handler. The FIFO handler watches a FIFO for commands from user space. As the control handler receives requests, it hands them off internally to the destination thread through another FIFO. The two destination threads generate the ``hello'' and ``world'' as periodic real-time code. The control commands that they receive from the FIFO handler direct each one to alternatively write ``hello'' and ``world'' to another FIFO. The user-space code simply reads the two FIFOs coming from the two hello world threads, and periodically pushes a command to the handler directing them to switch roles.

First, there is a simple header file that both the real-time and the user-space code share. This defines some of the values pushed across the FIFOs as commands:

#define NOOP                         0
#define HELLO                        1
#define WORLD                        2
#define STOP                         3
struct my_msg_struct {
       int command;
       int task;

The four states direct the actions of the real-time threads and are sent from user space to the real-time thread over the control FIFO, wrapped in the my_msg_struct structure. The task field directs whether the command should go to thread 1 or thread 2.

The Real-Time Code

First, we'll review the real-time component in this exercise. The fact that it is a kernel module shouldn't frighten you; this should look simple to anyone who has done POSIX code before:

#include <rtl.h>
#include <time.h>
#include <unistd.h>
#include <rtl_sched.h>
#include <rtl_fifo.h>
#include "control.h"
pthread_t tasks[2];
void *thread_code(void *t);
int my_handler(unsigned int fifo);

This is just like header information you might see in any other application. There are two real-time threads generating the ``hello'' and ``world'', so we store their thread IDs in the pthread_t tasks[2] array. This way we can use the IDs to cancel the threads during cleanup. The function declarations are standard forward declarations for our real-time thread and the control FIFO handler.

Listing 1. Initialization Code

Listing 1 shows all of the initialization code. As you can see, this is the method used in a normal Linux kernel module. In fact, all of the Linux kernel facilities are free for use at this point. As the real-time kernel does not directly support memory management, any preallocation should be done here. Real-time FIFO creation also needs to occur in this function, as after it completes, you are operating in real time.

The fact that we explicitly destroy existing FIFOs may come as a surprise, but it is a good idea in general. This makes sure that this code has explicit use of the FIFO, and that there are no resources left open from other modules. If two modules use the same FIFO by accident, the data will intermingle and appear to be garbage. So, we destroy the FIFOs and then create them as needed. The buffer size chosen here is arbitrary and should be tuned to your needs in practice. The FIFOs themselves each have a special use. The first is for user-space to real-time communication. The second two are for the control thread to push data to the real-time threads, and the last two allow the real-time threads to push data back to user space.

For those familiar with normal POSIX thread management, the next few steps should be straightforward. We initialize a thread attribute in order to set the thread priority and then pass it along with a call to pthread_create() in order to direct thread creation. We do this twice, as there are two real-time threads: one for ``hello'' and one for ``world''.

The last line is an RTLinux-specific call; this registers a function handler for use with the control FIFO. As data is pushed into the control FIFO, this function is called to read it, interpret the data and push it on to the correct thread:

void cleanup_module(void)
    pthread_cancel (tasks[0]);
    pthread_join (tasks[0], NULL);
    pthread_cancel (tasks[1]);
    pthread_join (tasks[1], NULL);

Like init_module(), this call is executed within the context of the Linux kernel. All we have to do here is cancel the two threads, join them and then destroy all of the real-time FIFOs we were using.

Listing 2. The Real-Time Thread in Its Entirety

Listing 2 shows the real-time thread in its entirety. We passed the task number along from the init_module() call, so we cast that from void. The FIFO this thread will use is going to be one up from the task id, as FIFO 0 is the control FIFO. In order to be safe, we don't want the threads filling the FIFO until user space is ready, so we stall the code by setting the command to NOOP. Following that, we make a call using an RTLinux extension to make the thread operate in periodic mode. This saves us the work of trying to schedule things in a more complicated fashion during the main loop. With this one call, the thread becomes periodic, getting scheduled every half-second (500,000,000 nanoseconds). On each scheduled wakeup, the code attempts to read from the control FIFO. As it was opened in nonblocking mode, it will return immediately if there is nothing to read, otherwise it will fill out the message structure. Based on the value present in the structure, it either writes a message to a FIFO or cleans up with the POSIX I/O calls and quits.

Listing 3. Real-Time Code--Handler for the Control FIFO

Listing 3 is the last bit of the real-time code and is the handler for the control FIFO. This is essentially a callback that is executed when there is pending data on a FIFO. In this case, the execution is simple. It reads a message from the control FIFO, and based on the task designated in the structure, it pushes the data on to the correct thread via another FIFO. The user-space application easily could have written to the thread directly over this second FIFO, but this step was taken in order to demonstrate the handlers and communication between real-time components. Note that we also have used the POSIX calls here, opening and closing the handler and interthread FIFO on each callback. While this adds overhead that could be avoided easily by opening the FIFOs once and tracking the open file descriptors, we use this approach to demonstrate the simplicity of the POSIX I/O calls.



Comment viewing options


doubt in rtlinux coding part

vsagar's picture


i will wait for ur valuable solutions.