Skip to content

Multithreading

Benoît Thébaudeau edited this page Sep 23, 2019 · 7 revisions

Contiki supports preemptive threads through its mt library. mt threads have private stack segments and program counters that are saved when switching thread context. The library is divided into an architecture-independent part and an architecture-dependent part. Application programmers only need to regard the architecture-independent part of the multithreading library, which is declared in sys/mt.h.

Each thread is represented by an object of type struct mt_thread, which is defined as follows.

 struct mt_thread {
   int state;
   process_event_t *evptr;
   process_data_t *dataptr;
   struct mtarch_thread thread;
 };

The figure to the right shows the state chart of threads. A thread can have three different states throughout its lifetime. Once a thread is first started by applying mt_start() on it, the mt library sets its state to MT_STATE_READY. This state specifies that the thread is ready to execute, which is done by applying the mt_exec() function on the thread. The ready state can be reset once control is yielded by using mt_yield(). MT_STATE_RUNNING is the state of a thread that is currently being executed. MT_STATE_WAITING and MT_STATE_PEEK are currently unused. MT_STATE_EXITED is the final state of a thread, specifying that it can not be executed anymore.

The thread variable of type struct mtarch_thread is designated for architectural state, which typically includes the execution context of the thread. For example, in the msp430 port of libmt, the architectural context stores a private stack, and a stack pointer that delineates the top of the private stack.

The variables evptr and dataptr are currently unused by all implementations of the multithreading library.

Table of Contents

Programming Threads

The Multithreading API

void mt_init(void) : Initializes the library.
void mt_remove(void) : Uninstalls the library.
void mt_start(struct mt_thread *thread, void (* function)(void *), void *data) : Starts a thread.
void mt_exit(void) : Exits a thread.
void mt_exec(struct mt_thread *thread) : Execute as thread.
void mt_yield(void) : Release control voluntarily.
void mt_stop(struct mt_thread *thread) : Stops a thread.
The multithreading API is shown above. The mt_init() function initialized the thread library. If the thread library is no longer needed, one can call mt_remove() to uninstall the library after allowing it to deallocate internal state.

Starting and Stopping

Threads are started by calling the mt_start() function. The first argument, thread, is a pointer to allocated, but uninitialized space in which mt_start() will put internal state used for the threading functions. The second argument is a function pointer of type void (*)(void *), which will be called when starting the execution of the thread. The third and final argument is a pointer to data that will be passed to the function when the thread is started. If no data is needed when calling the thread, the data argument can point to NULL, and the called function can just ignore the argument.

To stop a thread, a Contiki process can call mt_stop() with a pointer to the thread in question as the argument. This ensures that the thread is stopped and cleaned up properly. After this, the contents of the object of struct mt_thread are no longer used, and may be discarded by the calling process.

Scheduling

After starting a thread with mt_start(), the thread will continue executing until it has either been preempted by the system or yielded itself voluntarily. Preemption is controlled by the system, and therefore out of scope in this programming API. Yielding, on the other hand, is an action incurred by the threads themselves. By yielding, the thread releases control of the thread and allows the system to execute the main system thread. In order to do so, the thread calls mt_yield() without any argument.

When mt_exec() is called for the first time on a thread, it calls the function which was pointed to by the second argument of mt_start(). Subsequent calls to mt_exec() will restore the thread context and continue execution from the place where the thread was last yielded or preempted. The example below shows how to set up a process that in turn creates a thread.

Setup a thread from a Contiki process and execute it. We release control to the Contiki event scheduler by yielding from the thread, and then calling PROCESS_PAUSE in the process.

 void
 thread_entry(void *data)
 {
   for(;;) {
     printf("Looping in thread_entry\n");
     mt_yield();
   }
 }
 
 PROCESS_THREAD(mt_process, event, data)
 {
   static struct mt_thread thread;
   static int counter;
 
   PROCESS_BEGIN();
   mt_start(&thread, thread_entry, NULL);
   for(;;) {
     PROCESS_PAUSE();
     mt_exec(&thread);
     if(++counter == 10) {
       printf("Stopping the thread after %d calls\n",
              counter);
       mt_stop(&thread);
       break;
     }
   }
   PROCESS_END();
 }

Architecture Support

The multithreading module requires processor-dependent code for switching context between threads. Each architecture-agnostic function in the multithreading API has a corresponding architecture-dependent function. These implementations are located in the different subdirectories under cpu/() and named mtarch.c.

mtarch_init() is available to initialize the run-time support for multithreading, but this function is currently unused in most Contiki platforms. mtarch_remove() cleans up resources in an architecture-dependent way. mtarch_start(), mtarch_yield(), and mtarch_exec() implement the platform-dependent part of their corresponding mt library functions.

Contiki permits preemptive thread implementations in the functions mtarch_pstart() and mtarch_pstop(). Unlike the other functions in the underlying mt architecture API, these functions do not have corresponding platform-independent functions. The reason is that the place where it is suitable to place the preemption and the scheduling depends on the hardware architecture. If the platform supports only voluntary thread yielding, these functions may be left empty.

Conclusions

In this chapter we described an alternative method for concurrent programming in Contiki. Unlike protothreads, the mt library allows preemptive scheduling along with the possibility to yield from nested functions in a thread. mt threads cannot receive Contiki events, however, and are most suitable for freestanding computational units.

Clone this wiki locally