Skip to content
Permalink
main
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time

Protothreads

Learning outcomes

  • Understand why we need protothreads
  • Understand how they work
  • Be able to diagnose basic protothread errors

Motivation

  • Consider any task that involves blocking:
    • opening a file
    • reading a file
    • interacting with the i2c bus
    • sending a message over the radio
  • Without multi-tasking, we need to loop around and wait for completion
  • Modern operating systems have some form of `wait’ instruction that `blocks’ this task until a certain thing has happened.
  • In the meantime, other tasks can be run by `context switching’

Wait, what?

  • To multitask, we need to be able to do several things:
    1. save the context (program counter, registers)
    2. load a different context (same but for a different process)
    3. restart executing
  • To do it really well, we also want to protect process A from process B
    • we need memory protection / virtual memory addressing
    • we need supervisor mode to do special things like switch context
    • not all CPUs have support for all this!

State machines

  • Consider a simple control problem: toggling a light
  • When we press the button and the light is on, we turn it off
  • When we press the button and the light is off, we turn it on

How do we express this

  • First we enumerate all possible states as $q_1, q_2, \ldots$
  • In this case, we have 2 states (light on or off)
  • Then we define the conditions for transitions between states
  • The result can be drawn as a graph

figures/fsm.png

Alternatively to labelling the circles (states) with whether the light is on or off, we can label the arcs (transitions) with the action of turning on or off the light.

figures/fsm2.png

Representing a state machine as a table

The previous state machine can also be represented as a table with columns for

  1. what state we are in
  2. what additional condition causes a transition
  3. what new state to transition to
  4. actions to take when transitioning
state condition new state actions
$q_1$ push $q_2$ turn_on_light
$q_2$ push $q_1$ turn_off_light

Converting to code

void toggle() {
  while (1) {
    while (! get_button()) {}
    turn_on_light();
    while (! get_button()) {}
    turn_off_light();
  }
}
  • but this means we can’t do anything else while waiting for the button press

Just one state transition

  • Key idea: perform one state transition per function call
int fsm(int state) {
  button = get_button();
  if (state == 1 && button) {
    turn_on_light();
    state = 2;
  }
  else if (state == 2 && button) {
    turn_off_light();
    state = 1;
  }
  return state;
}

So what do protothreads do?

http://dunkels.com/adam/pt/expansion.html

Note: in Contiki, the macros start PROCESS, not PT

Exercise - Check out the macro expansion

$ cd contiki/examples/hello-world
$ make TARGET=native hello-world.e

Examine the result

  • can you see what each macro got substituted for?
static char process_thread_hello_world_process(struct pt *process_pt, process_event_t ev, process_data_t data)
{
  static struct etimer timer;
  { char PT_YIELD_FLAG = 1; if (PT_YIELD_FLAG) {;} switch((process_pt)->lc) { case 0:;
  etimer_set(&timer, 1000 * 3);
  while(1) {
    printf("Hello, James\n");
    do { PT_YIELD_FLAG = 0; (process_pt)->lc = 60; case 60:; if((PT_YIELD_FLAG == 0) || !(etimer_expired(&timer))) { return 1; } } while(0);
    etimer_reset(&timer);
  }
  }; PT_YIELD_FLAG = 0; (process_pt)->lc = 0;; return 3; };
}

Communicating between two processes

  • It seems like we can call one process from another
  • However, you should never do this!
    • Think about what process_pt struct you are passing in
  • Instead use PROCESS_POST to queue an event that is then received by the other process

Understanding PAUSE versus YIELD

  • As discussed in the wiki, PROCESS_PAUSE is not the same as PROCESS_YIELD
  • PAUSE expects to be called again as soon as possible
  • YIELD says - wait for the next event (and the processor can sleep)
  • This is why we typically want to use event timers and event waits, so that the processor can sleep while waiting
  • A nice exercise to try here is to compare an ordinary timer with an etimer
    • What sort of wait do we need for timer?
    • Do both operate in the same way?
    • Which one allows the processor to sleep?

Using event timer

The normal approach to sleeping (or delaying) for some duration is to use an event timer.

static struct etimer timer;
PROCESS_BEGIN ();
/* Setup a periodic timer that expires after 10 seconds. */
etimer_set (&timer, 10 * CLOCK_SECOND);
while (1)
  {
    printf ("etimer reading is %lu\n", clock_time());
    /* Wait for the periodic timer to expire and then restart the timer. */
    PROCESS_WAIT_EVENT_UNTIL (etimer_expired (&timer));
    etimer_reset (&timer);
  }
PROCESS_END ();

Using a normal timer

It is possible to use a normal timer but notice that we need to use PAUSE to ensure that the process is still considered active. This will have the negative side effect of not allowing the processor to sleep.

 static struct timer timer;
 PROCESS_BEGIN ();
 /* Setup a periodic timer that expires after 10 seconds. */
 timer_set (&timer, 10 * CLOCK_SECOND);
 while (1)
   {
     printf ("timer reading is %lu\n", clock_time());
     /* Wait for the periodic timer to expire and then restart the
	 timer. */
     do {
	PROCESS_PAUSE();
     } while (!timer_expired(&timer));
     timer_reset (&timer);
   }
 PROCESS_END ();

Things to watch for

  • process_pt is a structure with lc being the line counter
  • rather than loop and wait, set lc to the current line and return immediately
  • the switch and case causes a jump into the inside of the loop when lc is 60!

Key ideas

  • Protothreads are a super-lightweight way to get multiple processes to run concurrently.
  • PT use (tricky) macros to turn ordinary looking code into a state machine
  • Understanding how they work helps when diagnosing compilation problems

Summary

  • We’ve uncovered the heart of Contiki, which is concurrency through protothreads
  • Understanding PTs will help when trying to understand compiler errors

Additional reading

https://github.com/contiki-ng/contiki-ng/wiki/Documentation:-Multitasking-and-scheduling