Differences

This shows you the differences between two versions of the page.

Link to this comparison view

chibios:book:kernel_threading [2014/09/26 17:13] (current)
Line 1: Line 1:
 +====== 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 ===
 +
 +<code c>
 +/* MyThread Working Area.*/
 +static THD_WORKING_AREA(waMyThread,​ 128);
 +
 +/* MyThread function.*/
 +static THD_FUNCTION(MyThread,​ arg) {
 +
 +  /* Thread body code.*/
 +  ...;
 +}
 +</​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.
 +
 +<code c>
 +/*
 + * 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.*/​
 +  ...;
 +}
 +</​code>​
 +
 +===== 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:
 +  - The achievable resolution depends on the system tick frequency, if the frequency is 1000Hz then the delays resolution is 1mS.
 +  - 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.
 +
 +<code c>
 +static THD_WORKING_AREA(waBlinker,​ 128);
 +static THD_FUNCTION(Blinker,​ arg) {
 +
 +  while (true) {
 +    LED_on();
 +    chThdSleepMilliseconds(500);​
 +
 +    LED_off();
 +    chThdSleepMilliseconds(500);​
 +  }
 +}
 +</​code>​
 +
 +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.
 +
 +<code c>
 +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);​
 +  }
 +}
 +</​code>​
 +
 +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.
 +
 +<code c>
 +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));​
 +  }
 +}
 +</​code>​
 +
 +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.
 +
 +<code c>
 +/*
 + * 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();​
 +  }
 +}
 +</​code>​
 +
 +===== 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.
 +
 +<code c>
 +/*
 + * 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.*/​
 +  ...;
 +}
 +</​code>​
 +
 +===== 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.
 +
 +<code c>
 +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);
 +}
 +</​code>​
 +
 +===== 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.
 +
 +<code c>
 +#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);​
 +}
 +</​code>​
 +
 +===== 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:
 +  - **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.
 +  - **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.
 +
 +<code c>
 +/*
 + * 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.*/
 +  ...;
 +}
 +</​code>​