182.704 Building Reliable Distributed Systems LU Matthias Fgger, - - PowerPoint PPT Presentation
182.704 Building Reliable Distributed Systems LU Matthias Fgger, - - PowerPoint PPT Presentation
182.704 Building Reliable Distributed Systems LU Matthias Fgger, Alexander Kler, Ulrich Schmid WS 2014 course Wireless Nodes, nesC, TinyOS The target systems Wireless motes cluster ATAVRRZ200 demonstration kit from Atmel >= 4 motes
The target systems Wireless motes cluster ATAVRRZ200 demonstration kit from Atmel >= 4 motes a single mote AT86RF230 radio transceiver 2450 MHz band ATmega 1281V MC
First steps
- 1. get your accounts
- 2. login to a PC in ECS lab and get a copy of TinyOS
git clone ssh://trac/ecs/repo/git/lva/brds/tinyos
- 3. set environment variables (.bashrc)
export TOSROOT="/homes/koe/tinyos" export TOSDIR="$TOSROOT/tos" export MAKERULES="$TOSROOT/support/make/Makerules"
- 4. in apps/Blink/ execute
make rz200
- 5. connect mote via programmer and execute
make rz200 install
Binary is generated and downloaded via avrdude
ncc – nesc compiler for tinyos
> make iris
ncc -o build/iris/main.exe -Os -Wall -Wshadow -Wnesc-all -target=iris
- fnesc-cfile=build/iris/app.c -board=micasb
- DDEFINED_TOS_AM_GROUP=0x22
- -param max-inline-insns-single=100000 -DIDENT_APPNAME=\"BlinkAppC\"
- DIDENT_USERNAME=\"fuegger\" -DIDENT_HOSTNAME=\"ecslab6\„
- DIDENT_USERHASH=0x894ea284L -DIDENT_TIMESTAMP=0x4bc706feL
- DIDENT_UIDHASH=0xee40a21fL -fnesc-dump=wiring
- fnesc-dump='interfaces(!abstract())' -fnesc-dump='referenced(interfacedefs,
components)' -fnesc-dumpfile=build/iris/wiring-check.xml BlinkAppC.nc -lm compiled BlinkAppC to build/iris/main.exe 2268 bytes in ROM 51 bytes in RAM avr-objcopy --output-target=srec build/iris/main.exe build/iris/main.srec avr-objcopy --output-target=ihex build/iris/main.exe build/iris/main.ihex writing TOS image ncc calls nescc, which calls gcc
Memory requirements
ATmega1281/V usage: Flash: 128KB EEPROM: 4KB RAM: 8KB
2268 bytes in ROM = 2.3 KB (1.7%) [.text segment size] 51 bytes in RAM = 0.05 KB (0.4%) [.bss segment size] This is quite bad for a blinking application, but good for an operating system!
A threaded version in apps/tosthreads/apps/Blink/ execute
> make rz200 threads
tells nesc to use thread lib instead
5358 bytes in ROM = 5.3 KB (4.1%) 993 bytes in RAM= 1.0 KB (12.1%) The latter is problematic (only static variables, no dynamic varibles yet). Context switching needs quite some memory!
Where to find code? apps/ example applications apps/tosthreads/apps/ example TOSthreads applications tos/interfaces all the interfaces tos/system/ major hardware independent code, implementing interfaces tos/chips + tos/platforms hardware dependent code tos/lib/ TinyOS add-ons (timer, radio transceiver, TOSthreads, …)
Example Blink
BlinkC.nc
#include "Timer.h" module BlinkC { uses interface … provides interface … }
BlinkC
Example Blink
BlinkC.nc
#include "Timer.h" module BlinkC { uses interface Timer<TMilli> as Timer0; uses interface Timer<TMilli> as Timer1; uses interface Timer<TMilli> as Timer2; } interface Timer<precision_tag> { command void startPeriodic(uint32_t dt); command void startOneShot(uint32_t dt); command void stop(); event void fired(); command bool isRunning(); command bool isOneShot(); command void startPeriodicAt(uint32_t t0, uint32_t dt); command void startOneShotAt(uint32_t t0, uint32_t dt); command uint32_t getNow(); command uint32_t gett0(); command uint32_t getdt(); }
interfaces can be typed
- general interfaces
- for compile-time wiring checking
more than 1 interface per module possible! BlinkC Timer0 command event
Command/event model
Command/event model functions, commands, events, tasks function (inside Module)
uint8_t myFunction(uint8_t x) {…} y = myFunction(x);
command (between Modules)
(async) command uint8_t myInterface.myCommand(uint8_t x) {…} y = call myInterface.myCommand(x);
event (between Modules)
(async) event void myInterface.mySignal(uint8_t x) {…} signal myInterface.mySignal(x);
task (inside Module – main execution primitive)
task void myTask() {…} post myTask();
Command/event model
Split phase (call back) a longer computation (1) (2)
f(x) call f(x) returns; y = f(x) start f(x) call f(x) signals f(x); y = f(x) returns f(x) done
Command/event versus task
What to do and what not (deferred procedure calls) (1) (2)
f(x) call f(x); signal/call g(y); g(y) etc... f(x) call f(x); post g(); g(y)
Command/event versus task
What to do and what not (deferred procedure calls)
in some module’s implementation that provides the interface Someone:
x:= 0; // in implementation scope command error_t Someone.doThis() { // do it and obtain x x := f(.); signal Someone.done(x); }
some other module which uses Someone to repeatedly do this:
.... for (uint8_t i=0; i++; i < 10) { Someone.doThis(); } .... event void Someone.done(uint8_t x) { // obtain x }
Command/event versus task
What to do and what not (deferred procedure calls)
in some module’s implementation that provides the interface Someone:
command error_t Someone.doThis() { // dot it and obtain x x = 1; signal Someone.done(x); }
a “clever” solution:
... i:= 0; Someone.doThis(); ... event void Someone.done(uint8_t x) { // obtain x i++; if (i < 10) { Someone.doThis(); } }
Command/event versus task
What to do and what not (deferred procedure calls)
in some module’s implementation that provides the interface Someone:
- riginal implementation
task implementation now we can use:
... i:= 0; Someone.doThis(); ... event void Someone.done(uint8_t x) { // obtain x i++; if (i < 10) { Someone.doThis(); } } command error_t Someone.doThis() { // dot it and obtain x x = 1; signal Someone.done(x); } command error_t Someone.doThis() { // dot it and obtain x x = 1; return post Someone.doneTask; } task void doneTask() { signal Someone.done(x); }
Example Blink
BlinkC.nc #include "Timer.h" module BlinkC { uses interface Timer<TMilli> as Timer0; uses interface Timer<TMilli> as Timer1; uses interface Timer<TMilli> as Timer2; uses interface Leds; uses interface Boot; } implementation { event void Boot.booted() { call Timer0.startPeriodic( 250 ); call Timer1.startPeriodic( 500 ); call Timer2.startPeriodic( 1000 ); } event void Timer0.fired() { call Leds.led0Toggle(); } event void Timer1.fired() { call Leds.led1Toggle(); } event void Timer2.fired() { call Leds.led2Toggle(); } }
BlinkC Timer0 Timer1 Timer2 Leds Boot uses
Example Blink
BlinkC.nc
#include "Timer.h" module BlinkC { uses interface Timer<TMilli> as Timer0; uses interface Timer<TMilli> as Timer1; uses interface Timer<TMilli> as Timer2; uses interface Leds; uses interface Boot; }
BlinkC Timer0 Timer1 Timer2 Leds Boot
BlinkAppC.nc
configuration BlinkAppC { } implementation { components MainC, BlinkC, LedsC; components new TimerMilliC() as Timer0; components new TimerMilliC() as Timer1; components new TimerMilliC() as Timer2; BlinkC -> MainC.Boot; BlinkC.Timer0 -> Timer0; BlinkC.Timer1 -> Timer1; BlinkC.Timer2 -> Timer2; BlinkC.Leds -> LedsC; }
TimerMilliC TimerMilliC TimerMilliC MainC LedsC uses provides
- > wiring
from user to provider
long version: BlinkC.Boot -> MainC.Boot; BlinkC.Timer0 -> Timer0.Timer; BlinkC.Timer1 -> Timer1.Timer; BlinkC.Timer2 -> Timer2.Timer; BlinkC.Leds -> LedsC.Leds;
Booting
tos/system/MainC
#include "hardware.h" configuration MainC { provides interface Boot; uses interface Init as SoftwareInit; } implementation { components PlatformC, RealMainP, TinySchedulerC; RealMainP.Scheduler -> TinySchedulerC; RealMainP.PlatformInit -> PlatformC; // Export the SoftwareInit and Booted for applications SoftwareInit = RealMainP.SoftwareInit; Boot = RealMainP; }
Booting
tos/system/RealMainC
module RealMainP @safe() { provides interface Boot; uses interface Scheduler; uses interface Init as PlatformInit; uses interface Init as SoftwareInit; } implementation { int main() @C() @spontaneous() { atomic { platform_bootstrap(); call Scheduler.init(); call PlatformInit.init(); while (call Scheduler.runNextTask()); call SoftwareInit.init(); while (call Scheduler.runNextTask()); } __nesc_enable_interrupt(); signal Boot.booted(); call Scheduler.taskLoop(); return -1; } default command error_t PlatformInit.init() { return SUCCESS; } default command error_t SoftwareInit.init() { return SUCCESS; } default event void Boot.booted() { } }
TinyOS Scheduling Policies
- Non-Preemptive FIFO
- Small
- Easy
- Fast
- Thread Based Priority Queues using Preemptive Jobs
- Bigger
- Not that easy
- „Slow“
Concurrency in nesC
- Execution model in nesC is „run-to-completion“ tasks
- No preemption
- Atomic with respect to other tasks
- Not atomic with respect to interrupt handlers
- Code divided into two parts:
- Syncronous Code: functions, commands, events, tasks that are only
reachable from tasks
- Asyncronous Code: used in interrupt handlers (must be marked async)
- Race conditions:
- No race conditions between tasks
- Avoid race conditions by protection through an atomic statement
- Calls to functions are only protected if every call is protected
- Compiler detects race conditions
nesC Tasks in TinyOS 2.x
post processTask(); … task void processTask() { //do work if (moreToProcess){ post processTask(); } } post will only fail iff the task is already posted but execution has not started yet
Another Push Task Example
module BlinkTaskC { … } implementation { task void toggle() { call Leds.led0Toggle(); } event void Boot.booted(){ call Timer0.startPeriodic( 1000 ); } event void Timer0.fired(){ post toggle(); } }
What Really Happens
command void Scheduler.init() { atomic { memset( (void *)m_next, NO_TASK, sizeof(m_next) ); m_head = NO_TASK; m_tail = NO_TASK; } } bool pushTask( uint8_t id ) { if( !isWaiting(id) ) { if( m_head == NO_TASK ) { m_head = id; m_tail = id; } else { m_next[m_tail] = id; m_tail = id; } return TRUE; } else { return FALSE; } } async command error_t TaskBasic.postTask[uint8_t id]() { atomic { return pushTask(id) ? SUCCESS : EBUSY; } } command void Scheduler.taskLoop() { for (;;) { uint8_t nextTask; atomic { while ((nextTask = popTask()) == NO_TASK) { call McuSleep.sleep(); } } signal TaskBasic.runTask[nextTask](); } }
Example of Splitting Computation
task void computeTask() { uint32_t i; for (i = 0; i < 400001; i++) { … } } task void computeTask() { static uint32_t i; uint32_t start = i; for (;i < start + 10000 && i < 400001; i++) { … } if (i >= 400000) { i = 0; } else { post computeTask(); } }
Non-Preemptive FIFO Scheduler
- Every task is posted into the task queue
- Queue is processed in FIFO ordering
- Every task can only be posted once
- Every task consumes only 1 byte in the task queue
- Limited to 255 tasks
- Task queue length is evaluated at compile time [ unique() ]
- Every task runs „to-completion“
- Dispatch long operations in multiple seperate tasks
- What about context switches?
Thread Model
- ${TINYOSBASE}/tos/lib/tosthreads holds nearly the
complete tinyos structure again …
- … but now with thread support
- 2 Priorities:
- High Priority: Tasks
- Low Priority: Threads
- Switching between done by timer interrupt every 5ms
Thread Implementation
- Platform independent part:
- Thread queue (implemented as linked list)
- Threads
- Thread context (GPR, SP, SREG)
struct thread { //***** next_thread must be at first position in struct for casting purposes ******* volatile struct thread* next_thread; //Pointer to next thread for use in queues when blocked thread_id_t id; //id of this thread for use by the thread scheduler init_block_t* init_block;
//Pointer to an initialization block from which this thread was spawned
stack_ptr_t stack_ptr;
//Pointer to this threads stack
volatile uint8_t state;
//Current state the thread is in
volatile uint8_t mutex_count;
//A reference count of the number of mutexes held by this thread
void (*start_ptr)(void*);
//Pointer to the start function of this thread
void* start_arg_ptr;
//Pointer to the argument passed as a parameter to the start function of this thread
syscall_t* syscall;
//Pointer to an instance of a system call
thread_regs_t regs;
//Contents of the GPRs stored when doing a context switch
};
Thread Implementation (2)
Platform dependent part (the „real“ context switching)
//Save stack pointer #define SAVE_STACK_PTR(t) \ __asm__("in %A0, __SP_L__\n\t„ \ "in %B0, __SP_H__\n\t" \ :"=r"((t)->stack_ptr) : ); //Save status register #define SAVE_STATUS(t) \ __asm__("in %0,__SREG__ \n\t" : "=r" ((t)->regs.status) : ); #define SAVE_TCB(t) \ SAVE_GPR(t); \ SAVE_STATUS(t); \ SAVE_STACK_PTR(t) #define RESTORE_TCB(t) \ RESTORE_STACK_PTR(t); \ RESTORE_STATUS(t); \ RESTORE_GPR(t) #define SWITCH_CONTEXTS(from, to) \ SAVE_TCB(from); \ RESTORE_TCB(to) //Save General Purpose Registers #define SAVE_GPR(t) \ __asm__("mov %0,r0 \n\t" : "=r" ((t)->regs.r0) : ); \ __asm__("mov %0,r1 \n\t" : "=r" ((t)->regs.r1) : ); \ __asm__("mov %0,r2 \n\t" : "=r" ((t)->regs.r2) : ); \ __asm__("mov %0,r3 \n\t" : "=r" ((t)->regs.r3) : ); \ __asm__("mov %0,r4 \n\t" : "=r" ((t)->regs.r4) : ); \ __asm__("mov %0,r5 \n\t" : "=r" ((t)->regs.r5) : ); \ __asm__("mov %0,r6 \n\t" : "=r" ((t)->regs.r6) : ); \ __asm__("mov %0,r7 \n\t" : "=r" ((t)->regs.r7) : ); \ __asm__("mov %0,r8 \n\t" : "=r" ((t)->regs.r8) : ); \ __asm__("mov %0,r9 \n\t" : "=r" ((t)->regs.r9) : ); \ __asm__("mov %0,r10 \n\t" : "=r" ((t)->regs.r10) : ); \ __asm__("mov %0,r11 \n\t" : "=r" ((t)->regs.r11) : ); \ __asm__("mov %0,r12 \n\t" : "=r" ((t)->regs.r12) : ); \ __asm__("mov %0,r13 \n\t" : "=r" ((t)->regs.r13) : ); \ __asm__("mov %0,r14 \n\t" : "=r" ((t)->regs.r14) : ); \ __asm__("mov %0,r15 \n\t" : "=r" ((t)->regs.r15) : ); \ __asm__("mov %0,r16 \n\t" : "=r" ((t)->regs.r16) : ); \ __asm__("mov %0,r17 \n\t" : "=r" ((t)->regs.r17) : ); \ __asm__("mov %0,r18 \n\t" : "=r" ((t)->regs.r18) : ); \ __asm__("mov %0,r19 \n\t" : "=r" ((t)->regs.r19) : ); \ __asm__("mov %0,r20 \n\t" : "=r" ((t)->regs.r20) : ); \ __asm__("mov %0,r21 \n\t" : "=r" ((t)->regs.r21) : ); \ __asm__("mov %0,r22 \n\t" : "=r" ((t)->regs.r22) : ); \ __asm__("mov %0,r23 \n\t" : "=r" ((t)->regs.r23) : ); \ __asm__("mov %0,r24 \n\t" : "=r" ((t)->regs.r24) : ); \ __asm__("mov %0,r25 \n\t" : "=r" ((t)->regs.r25) : ); \ __asm__("mov %0,r26 \n\t" : "=r" ((t)->regs.r26) : ); \ __asm__("mov %0,r27 \n\t" : "=r" ((t)->regs.r27) : ); \ __asm__("mov %0,r28 \n\t" : "=r" ((t)->regs.r28) : ); \ __asm__("mov %0,r29 \n\t" : "=r" ((t)->regs.r29) : ); \
wireless communication IEEE 802.15.4 (ZigBee)
“Wireless Medium Access Control (MAC) and Physical Layer (PHY) Specifications for Low-Rate Wireless Personal Area Networks (WPANs)” Data rates 250, 100, 40 and 20 kb/s Communication graph star (PAN coordianator) p2p (also PAN coordinator, arb. comm.) Device types FFD = fully functional device RFD = reduced functional device (can talk to FFD only)
wireless communication IEEE 802.15.4 (ZigBee)
PHY and MAC layer specified PHY: In unlicensed 2.4 GHz band and others modulation depends, in 2.4 GHz band: O-QPSK MAC: Provides services to application:
- beacon management
- channel access (CSMA-CA)
- ACKed messages
- CRC
wireless communication (optional) superframe structure
Frame beacons sent by PAN coordinator
from [4]
wireless communication Non beacon mode
CSMA-CA random backoff time
from [4]
wireless communication
PHY & MAC packet
from [5]
wireless communication
IEEE 802.15.4 (ZigBee) In RZ200: Atmel AT86RF230
from [4]
wireless communication
The controller issuing commands
from [5]
wireless communication
The controller accessed in tos/chips/rf230/RF230LayerP.nc
from [5]
wireless communication
Typical Communication Scenario
wireless communication
Typical Communication Scenario (with ACKs enabled) Retransmission wait for timeout, retransmit if no ACK received, and not max count exceeded.
wireless communication
In TinyOS uses only the basic features of RF230
- no ACKed messages
- no beacon/slotted transfer
ACKs are implemented in software, e.g. in
tos/chips/rf230/SoftwareAckLayerP
wireless communication
In TinyOS
initially
call ActiveMessageC.start();
sending a message
message_t packet; if (locked) { return; } else { msg_t* msg; msg = (msg_t*)call Packet.getPayload(&packet, sizeof(msg_t)); if (msg == NULL) { return; } msg->data = 42; if (call AMSend.send(AM_BROADCAST_ADDR, &packet, sizeof(msg_t)) == SUCCESS) { locked = TRUE; } }