Processes

From Contiki
Jump to: navigation, search

Back to Contiki Tutorials

Introduction

Contiki shares the hardware among different applications and users through processes and (proto)threads. It is important to understand how processes are scheduled and how communication between processes work because the development of new application or even new low-level protocols are made using processes framework. A detailed description of processes in Contiki can be found here.

Overview

Below we summarize the main concepts of Processes and Protothread that are useful for application developers in Contiki.

Contiki has two different execution contexts: cooperative and preemptive. The first one is the context of regular execution, where functions are sequentially called and executed in the micro-controller. The preemptive context correspond to the execution of interruptions, due to I/O or timers. When executing in cooperative context all tasks are executed until their completion. Preemptive activities may interrupt the cooperative code at any time.

The cooperative context is the execution of user code through processes. All programs in Contiki are processes, so if you want to create a new application you should create a new process to execute it. Since processes execute in cooperative context, it means that Contiki's process scheduler does not preempt them and they may execute continuously until the moment that it cooperatively yields the micro-controller to the next scheduled process.

The preemptive context is the execution of interruptions. Interruptions happen due to general I/O (serial communication, SPI, etc.) as well as timers. When preemptive code is to be executed, the cooperative context is stopped and the interruption is called. Cooperative context is resumed from the point where it has been interrupted after completion of preemptive execution. In addition to I/O, preemptive context is important to enable the execution of real-time Timers. In Contiki it is possible to schedule the execution of a piece of code preemptively, which means that it will be executed regardless of which application is running. It is useful for implementation of algorithms based on precise timing.

The image below illustrates how cooperative and preemptive contexts are switched.

The structure of a process and protothread

A Contiki process consists of two parts: a process control block and a process thread. The process control block contains information about each process, such as the state of the process, a pointer to the process' thread, and the textual name of the process. A process thread contains the code of the process.

A processes control block is an internal structure used by the scheduler to invoke the process' thread and start its execution. The definition (declaration) of a process control block is done using the following macro:

PROCESS(name, strname);

where,

  • name - The variable name of the process structure.
  • strname - The string representation of the process' name.

A process' thread contains the execution code of the process. Thus, each process in Contiki can only have one thread of execution. Contiki's threads are not real threads as we learn in Operating Systems course, but they are what is called protothreads. Protothreads were introduced by Contiki's developers in a SenSys '06 paper (Protothreads: simplifying event-driven programming of memory-constrained embedded systems). Protothread are stackless threads optimized for event-driven OS's.

A protothread is declared using the following macro:

PROCESS_THREAD(name, ev, data)

where,

  • name - The variable name of the process structure.
  • ev - Event identifier sent to the thread during invokation.
  • data - Pointer to data passed to the thread during invokation.

The macro above only declares the beginning of a process' thread body. Every process must start with the PROCESS_BEGIN() macro and end with the PROCESS_END() macro, as shown below:

{
PROCESS_BEGIN();
printf("Hello, world\n");
PROCESS_END();
}

Processes are scheduled by Contiki based on events. A process should execute its tasks an at some point of execution yield the micro-controller and wait for a particular event (or for any event). When a process is waiting an event is is queued and another process is scheduled for execution (if there is any process ready to be executed).

Below it is shown the macros that can be used to control the execution of process in Contiki:

  • PROCESS_BEGIN() : Declares the beginning of a process' protothread.
  • PROCESS_END() : Declares the end of a process' protothread.
  • PROCESS_EXIT() : Exit the process.
  • PROCESS_WAIT_EVENT() : Wait for any event.
  • PROCESS_WAIT_EVENT_UNTIL() : Wait for an event, but with condition.
  • PROCESS_YIELD() : Wait for any event, equivalent to PROCESS_WAIT_EVENT().
  • PROCESS_WAIT_UNTIL() : Wait for a given condition; may not yield the micro-controller.
  • PROCESS_PAUSE() : Temporarily yield the micro-controller.

All process-related macros and processes functions can be found in the file core/sys/process.h and core/sys/process.c

Finally, an important concept is the Autostarting Process. Contiki provides a mechanism where processes can be automatically started either when the system is booted, or when a module that contains the processes is loaded. Autostarted processes are kept on a list, that is used by the autostart module to start the processes. The processes are started in the order they appear on the list.

All processes that are to be started at system boot-up must be contained in a single, system-wide list. This list is supplied by the user, typically in one of the user modules that are compiled with the Contiki kernel. When the module is used as a loadable module, the same list is used to know what processes to start when the module is loaded.

In Contiki, the autostart mechanism is the most common way through which user processes are started. To specify the processes that should autostarted user must use the following macro:

AUTOSTART_PROCESSES(&name1, &name2, ...)

where,

  • name1, name2, ... - The name of processes to be autostarted.

An example of process and thread declaration

Here you have a complete example of a simple process and its thread:

#include "contiki.h"
#include <stdio.h>

PROCESS(example_process, "Example process");
AUTOSTART_PROCESSES(&example_process);
 
PROCESS_THREAD(example_process, ev, data)
{
  PROCESS_BEGIN();
 
  while(1) {
    PROCESS_WAIT_EVENT();
    printf("Got event number %d\n", ev);
  }
 
  PROCESS_END();
}

In this example, a process called example_process is declared and it is set to autostart. The thread of example_process is also declared. The code of example_process consists of an infinite loop that yields the micro-controller until any event happen and then prints out the number of the received event.

This example is not illustrative because in fact there is no event being executed and the process may not be rescheduled. If a simple timer is added we can see our simple processes being called continuously. We just need to change the process thread:

static struct etimer et;
PROCESS_BEGIN();
 
/* Delay 1 second */
etimer_set(&et, CLOCK_SECOND);

while(1) {
  PROCESS_WAIT_EVENT();
  printf("Got event number %d\n", ev);

  /* Reseting the timer */
  etimer_reset(&et);
}

PROCESS_END();


Back to Contiki Tutorials