Real-Time Programming with Xenomai 3 - Part 2: Writing a simple periodic task.

Xenomai gets tasks to run in real-time by having a co-kernel running alongside the regular linux kernel handling all the time critical tasks. The Xenomai co-kernel is able to do this because of the i-pipe patch that the custom kernel is compiled with. This patch adds an interrupt pipeline that sits between the hardware of the computer and any kernels running on the hardware. The interrupt pipeline has domains which can be assigned a priority. When any interrupt, system call or processor fault comes in, the domain with the higher priority is allowed to process them first. The Xenomai co-kernel has the higher priority in an ipipe patched kernel. The Xenomai website has a more detailed explanation of how it works.

Before I start with the explanation, here’s the full code and Makefile for those who just want to compile some code and get started. The documentation for all the functions used in the code and more can be found here.

The code:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/mman.h>
#include <alchemy/task.h>
#include <alchemy/timer.h>
#include <math.h>


#define CLOCK_RES 1e-9 //Clock resolution is 1 ns by default
#define LOOP_PERIOD 1e7 //Expressed in ticks
//RTIME period = 1000000000;
RT_TASK loop_task;

void loop_task_proc(void *arg)
{
  RT_TASK *curtask;
  RT_TASK_INFO curtaskinfo;
  int iret = 0;

  RTIME tstart, now;

  curtask = rt_task_self();
  rt_task_inquire(curtask, &curtaskinfo);
  int ctr = 0;

  //Print the info
  printf("Starting task %s with period of 10 ms ....\n", curtaskinfo.name);

  //Make the task periodic with a specified loop period
  rt_task_set_periodic(NULL, TM_NOW, LOOP_PERIOD);

  tstart = rt_timer_read();

  //Start the task loop
  while(1){
    printf("Loop count: %d, Loop time: %.5f ms\n", ctr, (rt_timer_read() - tstart)/1000000.0);
    ctr++;
    rt_task_wait_period(NULL);
  }
}

int main(int argc, char **argv)
{
  char str[20];

  //Lock the memory to avoid memory swapping for this program
  mlockall(MCL_CURRENT | MCL_FUTURE);
    
  printf("Starting cyclic task...\n");

  //Create the real time task
  sprintf(str, "cyclic_task");
  rt_task_create(&loop_task, str, 0, 50, 0);

  //Since task starts in suspended mode, start task
  rt_task_start(&loop_task, &loop_task_proc, 0);

  //Wait for Ctrl-C
  pause();

  return 0;
}

The Makefile

SKIN=alchemy
MAIN_SRC=cyclic_test
TARGET=cyclic_test

LM=-lm

CFLAGS := $(shell xeno-config --skin=alchemy --cflags)
LDFLAGS := $(LM) $(shell xeno-config --skin=alchemy --ldflags)
CC := $(shell xeno-config --cc)

$(TARGET): $(MAIN_SRC).c
	$(CC) -o [email protected] $< $(CFLAGS) $(LDFLAGS)

First, the headers, defines and global variables:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/mman.h>
#include <alchemy/task.h>
#include <alchemy/timer.h>
#include <math.h>


#define CLOCK_RES 1e-9 //Clock resolution is 1 ns by default
#define LOOP_PERIOD 1e7 //Expressed in ticks
//RTIME period = 1000000000;
RT_TASK loop_task;

The includes are fairly standard. The Xenomai libraries are included by the alchemy/task.h and the alchemy/timer.h statements. I’ve deifined CLOCK_RES (The resolution of the clock) and LOOP_PERIOD (The period with which I want the periodic task to run) for convenience. The variable RT_TASK loop_task will hold an address to a task descriptor for a Real-Time task/thread that Xenomai will create.

Jumping ahead to main(), the first line that you come across that might be unfamiliar is:

  //Lock the memory to avoid memory swapping for this program
  mlockall(MCL_CURRENT | MCL_FUTURE);

The mlockall() function is actually a function provided by linux rather than Xenomai and is provided by the <sys/mman.h> include. In Part one, I talked about how a real-time task can miss its deadlines if the task is swapped out of memory by the operating system. This line of code makes sure that the memory that is currently mapped to the address space of the process as well as any memory that gets mapped into the address space of the process in the future is “locked” into RAM and cannot get swapped out.

In the next few lines of code, a new real-time task is created.

  //Create the real time task
  sprintf(str, "cyclic_task");
  rt_task_create(&loop_task, str, 0, 50, 0);

The rt_task_create() function creates a new real-time task using Xenoma’s Alchemy API. The first argument is the RT_TASK variable that holds the address of the task descriptor. The second is a string that holds a name for the task. You can give it a descriptive name. The third argument is the size of the stack for the new task. Passing a zero makes the function use a system dependent default. The next argument is the priority of the task. This tells the real-time scheduler how important the task is. Higher priority tasks can interrupt lower priority tasks. The last argument is the task creation mode into which you can pass bitwise OR’ed flags. For example, passing the T_JOINABLE flag allows you to call the rt_task_join() function to wait on the task to finish. In this code sample, I’m just passing in zero, which is the default mode. The function returns a 0 if the task is successfully created. Ideally, you should check for this and print an error if the return value is not zero. However, for this simple example, I’m omitting this.

A real-time task created using rt_task_create() starts off dormant. To begin the execution of the task, you need to call the rt_task_start() function.

  //Since task starts in suspended mode, start task
  rt_task_start(&loop_task, &loop_task_proc, 0);

The first two arguments are the task descriptor and a pointer to the function that implements the real-time task. The last argument is a pointer to a user defined struct that will be passed on as arguments to the real-time task function.

Finally we call the pause() function and wait for a Ctrl-C signal from the terminal.

Once rt_task_start() is called, the real-time task starts executing. To make a Xenomai task periodic, you need to call the rt_task_set_periodic() function.

 //Make the task periodic with a specified loop period
  rt_task_set_periodic(NULL, TM_NOW, LOOP_PERIOD);

If you’re calling this function from outside a real-time task, you need to pass in an RT_TASK as the first argument. However, you can also call this function from inside a real-time task with a NULL first argument. TM_NOW tells Xenomai to start timing the task right away and LOOP period is the period of the task in ticks of the clock. Since the default resolution of the clock is 1 nanosecond, this argument is the same as the period you want for the task expressed in nanoseconds.

Now we can start the infinite loop of the task.

//Start the task loop
  while(1){
    printf("Loop count: %d, Loop time: %.5f ms\n", ctr, (rt_timer_read() - tstart)/1000000.0);
    ctr++;
    rt_task_wait_period(NULL);
  }

In the loop I increment a simple counter and also use the rt_timer_read() function to get the current system time so I can print to the terminal and check if the task is running in real-time. The rt_task_wait_period() blocks the loop till the start of the next period.

When I started out trying to compile and run Xenomai with no prior experience, it seemed like quite a daunting task. The Xenomai documentation although excellent is written for programmers and as a result, it can be difficult to write your very first program. However, once you do write your very first program and you get a good idea for how it works, things go very smoothly. This post, like the one before is a bit long but hopefully, someone trying to get started with Xenomai for the first time will find it useful!

Related

comments powered by Disqus