Real-time Linux (Xenomai)

 Radboud University Nijmegen

Exercise #5: Intertask Communication


Introduction

In Xenomai, the primary intertask communication mechanisms between tasks are message queues, synchronous message passing, and message pipes. They all have the same API for both user space as kernel space.


Objectives

The following are the primary objectives of this exercise:

  • To demonstrate the use of intertask communication in Xenomai.

Message queues

Message queueing is a method by which real-time tasks can exchange or pass data through a Xenomai-managed queue of messages. Messages can vary in length and be assigned different types or usages.

A message queue is a buffer managed by the operating system. Message queues allow a variable number of messages, each of variable length, to be queued. Once a message queue has been created by a task, any task can put a message into the queue and any task can get a message from the queue (if it is nonempty). Queues can use a  FIFO (First In, First Out) policy or it can be based on priorities.

Creating a mailbox

·             create a mailbox
      int rt_queue_create (RT_QUEUE *q, const char *name, size_t poolsize, size_t qlimit, int mode)
  • Two different types of messages queues can be created. The type is determined by the mode argument to rt_queue_create :
    • Q_FIFO makes tasks pend in FIFO order on the queue for consuming messages.
    • Q_PRIO makes tasks pend in priority order on the queue.

Sending and receiving from mailbox

  • Send a message 
int rt_queue_write (RT_QUEUE *q, const void *buf, size_t size, int mode)
  • Receive a message
ssize_t rt_queue_read (RT_QUEUE *q, void *buf, size_t size, RTIME timeout)

See the API of Message queue services of the Native Xenomai API Module of the Xenomai API.


Synchronous message passing

Xenomai supports synchronous message passing which is available in the Xenomai tasks API. It allows the caller to send a variable-sized message to another task,

Sending and receiving a message

  • Send a message to a task synchronously. Blocks until the receiver is ready to get the message.
ssize_t rt_task_send (RT_TASK *task, RT_TASK_MCB *mcb_s, RT_TASK_MCB *mcb_r, RTIME timeout)
  • Receive a message from a task (if the task parameter is 0 it accepts from any task). Blocks until a message is available.
int rt_task_receive (RT_TASK_MCB *mcb_r, RTIME timeout)

More details can be found in Task management services of the Native Xenomai API Module of the Xenomai API.


Message pipes

A message pipe is a two-way communication channel between Xenomai tasks and standard Linux processes or tasks using regular file I/O operations on a pseudo-device. Pipes can be operated in a message-oriented fashion so that message boundaries are preserved, and also in byte streaming mode for optimal throughput.

Creating a pipe 

  • On the real-time side, a pipe is created using:
int rt_pipe_create (RT_PIPE *pipe, const char *name, int minor, size_t poolsize)
  • On the Linux side, pipe numbers 0 to 31 are associated with character devices /dev/rtp0 through /dev/rtp31. These device files are created when you install Xenomai, so they are always visible. To "create" a pipe on the Linux side, use the standard Unix function 'open()', e.g.,
file_descriptor = open("/dev/rtp0", O_RDONLY);

The integer 'file_descriptor' is used to identify the pipe during subsequent read or write operations. 'O_RDONLY' is a constant signifying that you will only read the pipe. Other possibilities are O_WRONLY and O_RDWR, for write-only and read-write, respectively. These are from standard Unix.

Accessing a pipe

  • On the real-time side, reading and writing are done using the API functions :
ssize_t rt_pipe_read (RT_PIPE *pipe, void *buf, size_t size, RTIME timeout)
ssize_t rt_pipe_write (RT_PIPE *pipe, const void *buf, size_t size, int mode)
  • On the Linux side, reading and writing are done using the standard Unix 'read()' and 'write()' functions, e.g.,
num_read = read(read_descriptor, &buffer_in, sizeof(buffer_in));
num_written = write(write_descriptor, &buffer_out, sizeof(buffer_out));

where 'read_descriptor' and 'write_descriptor' are the file descriptors obtained from a previous call to 'open()'. Negative return values signify an error.

Blocking versus Non-Blocking Reads

  • In Unix, devices can be opened for either blocking- or non-blocking reads.
    • Using parameter TM_FINITE for the timeout in a read operation leads to a blocking read which does not return when there is no data to be read. The calling process goes to sleep ("blocks") until the 'read()' function returns later when some data is available.
    • In some situations this blocking behavior is not desirable (for instance, in an interrupt handler) and then timeout parameter TM_NONBLOCK can be use, leading to a  non-blocking read which returns immediately if no characters are available.

More details can be found in the API Message pipe services of the Native Xenomai API Module of the Xenomai API.


Example: Message Queue

In the example  program below, taskOne sends a string message ("Message from taskOne") to taskTwo using a message queue. Next taskTwo prints the message it received from taskOne and its length.

/* ex05example.c */

#include <stdio.h>

#include <signal.h>

#include <unistd.h>

#include <sys/mman.h>

 

#include <native/task.h>

#include <native/timer.h>

#include <native/sem.h>

#include <native/mutex.h>

#include <native/queue.h>

 

#include  <rtdk.h>

 

#define NTASKS 2

 

#define QUEUE_SIZE 255

#define MAX_MESSAGE_LENGTH 40

 

RT_TASK task_struct[NTASKS];

 

#define QUEUE_SIZE 255

RT_QUEUE myqueue;

 

void taskOne(void *arg)

{

    int retval;

    char message[] = "Message from taskOne";

 

    /* send message */

    retval = rt_queue_write(&myqueue,message,sizeof(message),Q_NORMAL);

 

    if (retval < 0 ) {

       rt_printf("Sending error\n");

    } else {

       rt_printf("taskOne sent message to mailbox\n");

    }

}

 

void taskTwo(void *arg)

{

    int retval;

    char msgBuf[MAX_MESSAGE_LENGTH];

 

    /* receive message */

    retval = rt_queue_read(&myqueue,msgBuf,sizeof(msgBuf),TM_INFINITE);

    if (retval < 0 ) {

          rt_printf("Receiving error\n");

    } else {

        rt_printf("taskTwo received message: %s\n",msgBuf);

        rt_printf("with length %d\n",retval);

    }

}

 

//startup code

void startup()

{

  int i;

  char  str[10] ;

 

  void (*task_func[NTASKS]) (void *arg);

  task_func[0]=taskOne;

  task_func[1]=taskTwo;

 

  rt_queue_create(&myqueue,"myqueue",QUEUE_SIZE,10,Q_FIFO);

 

  rt_timer_set_mode(0);// set timer to tick in nanoseconds and not in jiffies

  for(i=0; i < NTASKS; i++) {

    rt_printf("start task  : %d\n",i);

    sprintf(str,"task%d",i);

    rt_task_create(&task_struct[i], str, 0, 50, 0);

    rt_task_start(&task_struct[i], task_func[i], &i);

  }

}

 

void init_xenomai() {

  /* Avoids memory swapping for this program */

  mlockall(MCL_CURRENT|MCL_FUTURE);

 

  /* Perform auto-init of rt_print buffers if the task doesn't do so */

  rt_print_auto_init(1);

}

 

int main(int argc, char* argv[])

{

  printf("\nType CTRL-C to end this program\n\n" );

 

  // code to set things to run xenomai

  init_xenomai();

 

  //startup code

  startup();

 

  pause();

}


Exercises

Exercise 5a.

Run the example program and observe its output.

  1. Modify the example program such that also taskTwo first sends a string message ("Message from taskTwo") to taskOne (before receiving the message from taskOne) via a message queue, and let taskOne receive it (after it has sent its message) and print the message received from taskTwo. Try the use of a single message queue.
  2. Use two message queues and describe the difference with the program that uses one message queue.
  3. Send several messages from one task to another (and back). Is it possible to send all messages before receiving any messages?

Exercise 5b. 

  1. Modify the example program such that it uses synchronous message passing instead of message queues.
  2. Send several messages from one task to another (and back). Is it possible to send all messages before receiving any messages?

Exercise 5c.

  1. A pipe is a communication mechanisme between a real-time program, using the Xenomai API functions "rt_pipe_read" and "rt_pipe_write", and a non-real-time program, using the Linux API functions "write" and "read". Transform the example program into two programs: a real-time programming sending a message over a pipe to a non-real-time program reading it. The non-real-time program is just a C program with the code written in the "main()" function. 
    Notes:
    • In non-real-time programs, the pthreads library can be used to start none-realtime tasks, but for this exercise this is not needed.
    • In Xenomai it is not possible to use a pipe to communicate between two real-time tasks.
  2. Send several messages from the real-time program to the non-real-time program and back. Is it possible to send all messages before receiving any messages?

Exercise 5d.

Compare the three mechanisms (message queues, synchronous message passing, and pipes); what are the differences, when to use which mechanism?


Last Updated: 29 September 2009 (Jozef Hooman)
Created by: Harco Kuppens
h.kuppens@cs.ru.nl