RT Threading

The threading module is responsible for operations related to threads. One important concept is the current thread, some functions inherently operate or the thread executing the function.
The services of the threading module are:

  • Declaration.
  • Life Cycle.
  • Delays.
  • Threads Suspension.
  • Threads Queues.
  • Thread Time.
  • Priority Management.
  • Round Robin.

This chapter will only address static thread, the optional dynamic allocation of threads will be described in the “RT Memory Management” chapter.

Declaration

API

THD_WORKING_AREA() Statically allocates a working are for a thread.
THD_FUNCTION() Declares a thread function hiding eventual compiler-specific keywords.

Examples

Static Thread Declaration

/* MyThread Working Area.*/
static THD_WORKING_AREA(waMyThread, 128);
 
/* MyThread function.*/
static THD_FUNCTION(MyThread, arg) {
 
  /* Thread body code.*/
  ...;
}

Life Cycle

This service handles the creation and termination of threads.

API

chThdGetSelfX() Returns a pointer to the current thread.
chThdCreateStatic() Creates and starts a static thread.
chThdCreateI() Creates a thread without starting it.
chThdStart() Start a thread previously created using chThdCreateI().
chThdStartI() Start a thread previously created using chThdCreateI().
chThdExit() Terminates the current thread returning a message.
chThdExitS() Terminates the current thread returning a message.
chThdWait() Waits for the specified thread to terminate then returns its exit message. The thread can be again created if necessary.
chThdTerminate() Sets the termination flag in the destination thread. The thread is not deleted but just asked to exit. The termination is cooperative.
chThdShouldTerminateX() Returns true if the current thread has the termination flag set.
chThdTerminatedX() Returns true if the specified thread is terminated.

Examples

Spawning and Resynchronizing

A LED blinks while the system is busy with an operation. A thread is spawned in order to blink the LED and it is terminated when the operation is finished, the main thread resynchronizes with the result returned by the spawned thread.

/*
 * Blinker thread.
 */
static THD_WORKING_AREA(waBlinker, 128);
static THD_FUNCTION(Blinker, arg) {
  unsigned i = 0;
 
  while (!chThdShouldTerminateX()) {
    /* Toggling a LED while the main thread is busy.*/
    toggle_led();
 
    /* Delay of 250 milliseconds.*/
    chThdSleepMilliseconds(250);
 
    /* Counting the number of blinks.*/
    i++;
  }
 
  /* Returning the number of iterations.*/
  chThdExit((msg_t)i);
}
 
/*
 * Main application.
 */
int main(void) {
 
  /*
   * System initializations.
   * - Kernel initialization, the main() function becomes a thread and the
   *   RTOS is active.
   */
  chSysInit();
 
  /* Main code.*/
  ...;
 
  /* The blinker thread is spawned.*/
  thread_t *tp = chThdCreateStatic(waBlinker, sizeof(waBlinker),
                                   NORMALPRIO + 1, Blinker, NULL);
 
  /* Performing operations while the LED is blinking.*/
  ...;
 
  /* Stopping the blinker thread and retrieving the number of times
     the LED toggled.*/
  chThdTerminate(tp);
  msg_t n = chThdWait(tp);
 
  /* Continuing.*/
  ...;
}

Delays

One common problem is to insert delays into the execution of threads. ChibiOS/RT provides several solutions for this problem. Thread delays are characterized by:

  1. The achievable resolution depends on the system tick frequency, if the frequency is 1000Hz then the delays resolution is 1mS.
  2. The time spent into a delays is used to run other threads, there is not busy waiting involved.

API

chThdSleep() Inserts a delay specified as number of system ticks, the delay is approximated to the next tick boundary.
chThdSleepSeconds() Inserts a delays specified in seconds, the delay is approximated to the next tick boundary.
chThdSleepMilliseconds() Inserts a delays specified in milliseconds. Note that the real resolution depends on system tick, the delay is approximated to the next tick boundary.
chThdSleepMicroseconds() Inserts a delays specified in microseconds. Note that the real resolution depends on system tick, the delay is approximated to the next tick boundary.
chThdSleepUntil() Sleeps until the system time counter reaches the specified value.
chThdSleepUntilWindowed() Special case of chThdSleepUntil() where a time window is specified.

Examples

Fixed Intervals #1

This example shows a thread blinking a LED at fixed intervals of one second.

static THD_WORKING_AREA(waBlinker, 128);
static THD_FUNCTION(Blinker, arg) {
 
  while (true) {
    LED_on();
    chThdSleepMilliseconds(500);
 
    LED_off();
    chThdSleepMilliseconds(500);
  }
}

Note that in the above example there is the strong assumption that the functions LED_on() and LED_off() are executed in a finite time which must be less than the tick interval (let's say 1mS). If the function execution time exceeds the tick interval then an error accumulates after each cycle and the blinker period is not exactly one second.

Fixed Intervals #2

This solution does not suffer of the limitation described in the previous example however it is a bit more complex to understand.

static THD_WORKING_AREA(waBlinker, 128);
static THD_FUNCTION(Blinker, arg) {
 
  systime_t time = chVTGetSystemTime(); // Current system time.
  while (TRUE) {
    time += MS2ST(500);                 // From Milliseconds to Tick conversion.
    LED_on();
    chThdSleepUntil(time);
 
    time += MS2ST(500);                 // From Milliseconds to Tick conversion.
    LED_off();
    chThdSleepUntil(time);
  }
}

In this example the execution time of LED_on() and LED_off() no more matters as long it is less than 500mS, a much larger margin than the 1mS of the previous example.
In case the deadline is exceeded than the thread would go sleeping for a very long time because it would have to wait for the system time counter to wrap back to zero and reach the specified time value. Considering that the counter is usually 32 bits wide means that the thread would be woken after a very long time, it would be virtually locked.
This method is valid as long we know that the execution time between the calls to chThdSleepUntil() is strictly less than the required interval. This method is valid for hard realtime systems where deadlines are supposed to never be violated.

Fixed Intervals #3

The following method is tolerant to occasional violation of the calculated deadlines, it is suited to soft realtime use cases because the interval can occasionally exceed the designed interval but it is able to recover.

static THD_WORKING_AREA(waBlinker, 128);
static THD_FUNCTION(Blinker, arg) {
 
  systime_t prev = chVTGetSystemTime(); // Current system time.
  while (true) {
    LED_on();
    prev = chThdSleepUntilWindowed(prev, prev + MS2ST(500));
 
    LED_off();
    prev = chThdSleepUntilWindowed(prev, prev + MS2ST(500));
  }
}

This version of the blinker is tolerant to missed deadlines because the time window of the current cycle is fully specified. When the function is invoked, the system time must be within the specified interval or the function would just return immediately without sleeping.

Threads References

One common need is to have a thread suspended and waiting for a synchronous event. ChibiOS/RT offers a low level, lightweight mechanism called Thread References. Basically a thread reference is a pointer that can be NULL or point to a waiting thread.
There are two possible operations: suspend which makes a reference point to a thread and resume that wakes up a waiting thread resetting the reference to NULL. The thread reference variable type is thread_reference_t. Note that, only one thread can suspend on a reference, trying to suspend on a non-NULL reference is an error caught by an assertion.
One interesting additional feature is that it is possible to pass a message between the entity calling resume and the thread into suspend.

API

chThdSuspendS() Suspends the invoking thread on a reference variable.
chThdSuspendTimeoutS() Suspends the invoking thread on a reference variable with a timeout specification.
chThdResume() Resumes a suspended thread.
chThdResumeI() Resumes a suspended thread (I-Class variant).
chThdResumeS() Resumes a suspended thread (S-Class variant).

Examples

Thread Serving an IRQ

This is a common use case, we need a worker thread to be woken when a specific IRQ is triggered.

/*
 * The reference variable must be initially set to NULL.
 */
thread_reference_t uart_thread_ref = NULL;
 
/*
 * ISR serving the UART RX FIFO interrupt.
 */
CH_IRQ_HANDLER(UART_RX_IRQ) {
 
  CH_IRQ_PROLOGUE();
 
  /* If there is data available in the UART RX FIFO.*/
  if ((UART->SR & UART_SR_DATA_AVAILABLE) != 0) {
 
    /* Entering I-Locked state and resuming the thread, if suspended.*/
    chSysLockFromISR();
    chThdResumeI(&uart_thread_ref, MSG_OK);
    chSysUnlockFromISR();
 
    /* Resetting interrupt source.*/
    UART->SR &= ~UART_SR_DATA_AVAILABLE;
  }
 
  CH_IRQ_EPILOGUE();
}
 
/*
 * The thread operates within a critical zone except when:
 * 1) Data is being processed.
 * 2) Thread is waiting for data.
 * This is done in order to check for data availability atomically
 * considering that there is an ISR involved. The duration of the
 * critical zone is very short.
 */
static THD_WORKING_AREA(waUartReceiveThread, 128);
static THD_FUNCTION(UartReceiveThread, arg) {
 
  chSysLock();
  while (true) {
    /* Makes sure to empty the RX FIFO before suspending.*/
    while ((UART->SR & UART_SR_RXFIFO_CNT) > 0) {
 
      /* After exiting the critical zone more IRQs may occur but
         the thread is not suspended so the resume operation would
         do nothing because the uart_thread_ref reference is NULL.*/
      chSysUnlock();
      process_data(UART->DR);
      chSysLock();
    }
 
    /* The FIFO is now empty, waiting for more data or an interruption
       in the RX data stream of at least 500mS.*/
    msg_t msg = chThdSuspendTimeoutS(&uart_thread_ref, MS2ST(500));
    if (msg == MSG_TIMEOUT)
      handle_comm_timeout();
  }
}

Threads Queues

Thread queues are a special kind of FIFO object, he following operations are defined:

  • Enqueue himself and go to sleep.
  • Dequeue Next thread from the queue if any.
  • Dequeue All threads from the queue.

Dequeue operations are also possible from ISR context. Enqueue operations can have an optional timeout specification. The threads queue variable type is threads_queue_t.

API

chThdQueueObjectInit() Initializes a thread queue object.
chThdQueueIsEmptyI() Returns true if the queue is empty.
chThdEnqueueTimeoutS() Enqueues the calling thread in the queue.
chThdDoDequeueNextI() Dequeues the next thread in the queue, the queue is assumed to contain at least one element.
chThdDequeueNextI() Dequeues the next thread in the queue, if any.
chThdDequeueAllI() Dequeues all thread in the queue, if any.

Examples

Processing Frames

Lets assume to have some kind of networking peripheral, frames are received and are processed by one or more threads. The network interface can go down and the even must be notified to the processing threads.

/*
 * ISR serving the network interrupt.
 */
CH_IRQ_HANDLER(RX_FRAME_IRQ) {
 
  CH_IRQ_PROLOGUE();
 
  /* If the network is down all the waiting threads are notified.*/
  if ((NET->SR & NET_SR_ERROR) != 0 {
    /* Entering I-Locked state and waking up all threads with a
       special message.*/
    chSysLockFromISR();
    chThdDequeueAllI(&rx_frames_queue, MSG_RESET);
    chSysUnlockFromISR();
 
    /* Resetting interrupt source.*/
    NET->SR &= ~NET_SR_ERROR;
  }
 
  /* If there is a frame available in the network interface.*/
  if ((NET->SR & NET_SR_FRAME_AVAILABLE) != 0) {
 
    /* Entering I-Locked state and waking up one thread, if available,
       the frame is lost if there are no available threads.*/
    chSysLockFromISR();
 
    /* Getting the pointer to the frame buffer and passing it as message.*/
    void *frame_ptr = net_get_frame_buffer();
    if (chThdQueueIsEmptyI(&rx_frames_queue) {
      /* Frame dropped, no thread ready to serve it right now.*/
      process_dropped_frame(frame_ptr);
      net_return_frame_buffer(frame_ptr);
    }
    else
      chThdDequeueNextI(&rx_frames_queue, (msg_t)frame_ptr);
 
    chSysUnlockFromISR();
 
    /* Resetting interrupt source.*/
    NET->SR &= ~NET_SR_FRAME_AVAILABLE;
  }
 
  CH_IRQ_EPILOGUE();
}
 
/*
 * Frame processing thread, there can be more than one thread executing this code.
 * Note, 4 working areas are allocated, one for each thread.
 */
static THD_WORKING_AREA(waProcessFrameThread1, 128);
static THD_WORKING_AREA(waProcessFrameThread2, 128);
static THD_WORKING_AREA(waProcessFrameThread3, 128);
static THD_WORKING_AREA(waProcessFrameThread4, 128);
 
static THD_FUNCTION(ProcessFrameThread, arg) {
 
  while (true) {
    /* Waiting for a network event.*/
    chSysLock();
    msg_t msg = chThdEnqueueTimeoutS(&rx_frames_queue,
                                     TIME_INFINITE);
    chSysUnlock();
 
    /* Processing the event.*/
    if (msg == MSG_RESET) {
      /* Handling network failure.*/
      process_failure();
    }
    else {
      /* Process incoming frame.*/
      process_frame((void *)msg);
      net_return_frame_buffer((void *)msg);
    }
  }
}
 
/*
 * Initialization.
 */
void main(void) {
 
  /*
   * System initializations.
   * - Kernel initialization, the main() function becomes a thread and the
   *   RTOS is active.
   */
  chSysInit();
 
  /* Starting the frames processor threads.*/
  chThdCreateStatic(waProcessFrameThread1, sizeof(waProcessFrameThread1),
                    NORMALPRIO + 1, ProcessFrameThread, NULL);
  chThdCreateStatic(waProcessFrameThread2, sizeof(waProcessFrameThread2),
                    NORMALPRIO + 1, ProcessFrameThread, NULL);
  chThdCreateStatic(waProcessFrameThread3, sizeof(waProcessFrameThread3),
                    NORMALPRIO + 1, ProcessFrameThread, NULL);
  chThdCreateStatic(waProcessFrameThread4, sizeof(waProcessFrameThread4),
                    NORMALPRIO + 1, ProcessFrameThread, NULL);
 
  /* Networking subsystem initialization, processing starts.*/
  net_init();
 
  /* Continuing.*/
  ...;
}

Thread Time

The CPU time used by a thread is accumulated into an internal counter. Note that the time is estimated and approximated because the counter is increased when the system tick interrupts occurs. This feature is optionally enabled using the CH_DBG_THREADS_PROFILING switch.
A better measurement method is available through the statistics module which uses the realtime counter in order to perform cycle-accurate measurements.

API

chThdGetTicksX() Returns the consumed CPU time by the specified thread in system ticks.

Examples

CPU Pulse

This example makes the calling thread consume CPU time for the specified number of ticks. The pulse is not affected by other threads. Don not confuse it with a delay, the total execution time depends on the pulse duration and the time spent by other threads preempting it.

void cpu_pulse(unsigned duration) {
  systime_t start, end, current;
 
  start = chThdGetTicksX(chThdGetSelfX());
  end = start + MS2ST(duration);
  do {
    current = chThdGetTicksX(chThdGetSelfX());
  } while (current - start < end - start);
}

Priority Management

In ChibiOS/RT it is possible for a thread to change its own priority, this is normally not required but there are use cases.

API

chThdGetPriorityX() Returns the priority of the current thread.
chThdSetPriority() Changes the thread priority level, returns the old priority.

Examples

Priority Ceiling

This example implements mutual exclusion on a shared resource using a priority ceiling mechanism, this is commonly found in RTOSes implementing the OSEK specification.
The assumption is that the shared resource is given a priority higher than all threads trying to access it.

#define MY_RESOURCE_PRIORITY (NORMALPRIO + 10)
 
void access_resource(void) {
  prio_t oldprio;
 
  /* Checking for priority violations using an assertion.*/
  chDbgAssert(chThdGetPriorityX() < MY_RESOURCE_PRIORITY,
              "resource priority violation");
 
  /* Escalating priority in order to access the resource
     safely.*/
  oldprio = chThdSetPriority(MY_RESOURCE_PRIORITY);
 
  /* Safely accessing the resource while running at its
     priority level.*/
  ...;
 
  /* Priority return.*/
  chThdSetPriority(oldprio);
}

Round Robin

Usually round robin scheduling is only useful when there are several CPU-intensive threads placed at the same priority level. Round robin scheduling can work in two distinct ways:

  1. Preemptive Round Robin. This mode is activated by setting CH_CFG_TIME_QUANTUM to a value greater than zero. In this mode the thread using the CPU is preempted by its peers after its time slot has been used.
  2. Cooperative Round Robin. This mode is activated by setting CH_CFG_TIME_QUANTUM to zero. In this mode the switch between threads at the same priority level is always cooperative. Cooperative mode is preferable because the kernel becomes slightly more efficient because it does not have to handle time slots.

API

chThdYield() The current thread relinquishes its time slice to the next thread in the round robin chain. This function has not effect if the current thread is the only one at the current priority level.

Examples

Cooperative Round Robin

Several threads execute CPU-intensive code and voluntary relinquish the CPU to their peers after a while. Note that CPU-intensive threads are usually placed at the bottom of the priorities scale.

/*
 * Processing threads.
 */
static THD_WORKING_AREA(waProcessThread1, 128);
static THD_WORKING_AREA(waProcessThread2, 128);
static THD_WORKING_AREA(waProcessThread3, 128);
static THD_WORKING_AREA(waProcessThread4, 128);
 
static THD_FUNCTION(ProcessThread, arg) {
 
  while (true) {
 
    /* Doing some CPU-heavy processing.*/
    ...;
 
    /* Voluntarily passing control to the next peer.*/
    chThdYield();
  }
}
 
/*
 * Initialization.
 */
void main(void) {
 
  /*
   * System initializations.
   * - Kernel initialization, the main() function becomes a thread and the
   *   RTOS is active.
   */
  chSysInit();
 
  /* Starting the frames processor threads.*/
  chThdCreateStatic(waProcessThread1, sizeof(waProcessThread1), NORMALPRIO-10, ProcessThread, NULL);
  chThdCreateStatic(waProcessThread2, sizeof(waProcessThread2), NORMALPRIO-10, ProcessThread, NULL);
  chThdCreateStatic(waProcessThread3, sizeof(waProcessThread3), NORMALPRIO-10, ProcessThread, NULL);
  chThdCreateStatic(waProcessThread4, sizeof(waProcessThread4), NORMALPRIO-10, ProcessThread, NULL);
 
  /* Continuing on top of the round robin bunch.*/
  ...;
}