TinyOS Programación Orientada a componentes SISEM 2009

Preview:

Citation preview

TinyOSProgramación Orientada a

componentes

SISEM 2009

Objetivos

• Exponer los lineamientos generales de un lenguaje usado para SisEm -> RTOS

• Diferencias con otros lenguajes o sistemas.

• Razones que motivan la elección del mismo

• Algunos ejemplos de uso

División en temas:

1. Características Generales

Robustez (confiablilidad)

Jerarquía – Modularidad

2. Caraterísticas de tiempo real

Sincrónico - asíncrono

Tareas

Eventos

Conocimientos necesarios

• Esencialmente contenidos dentro de SisEm y DesaSoft

• C, y algo de C++, o Java

• Punteros

• “Algo” de sistemas operativos

• Conocer sobre concurrencia, interrupciones y preemption.

Programación orientada al componente

Programación en nesC / TinyOS es similar a programar en un lenguaje de descripción

de hardware.

TinyOS se basa en nesC

nesC (network embedded system C)

• Es un dialecto de C basado en componentes.

• Programación simple: – Todo lo que se requiere es implementar

algunos módulos e interconectarlos entre sí. – La dificultad se presenta al interconectar

aplicaciones complicadas (incorporar nuevos códigos a los ya existentes).

Modelo de enlazado (Linking)

• La diferencia esencial con C radica en el modelo de enlazado de los módulos

– Como funciona el enlazado en C, C++ (desde el punto del programador) y cómo esto afecta la estructura de un código.

– Al analizar esto mismo en nesC, se entenderán las diferencias

C

• Un solo namespace global para funciones y variables

• Tres formas de nombrar una variable:

Declaración (establece que existe una variable)Definición (crea efectivamente la variable) (*)Referencia (usa la variable)

(*) implementa la función o aloja la variable

C Alcance Global

• Variables globales y funciones no-estáticas poseen un alcance global

(cualquier código puede hacer referencia a las mismas)

Interdependencia

• Si dos archivos fuente hacen referencia al mismo nombre de función, están haciendo referencia a la misma función

• Esto puede dar lugar a dependencias no deseadas entre dos códigos

• Si quiero cambiar mi implementación le cambio también la implementación a los demás

Punteros a funciones

• Una forma de resolver en C esta interdependencia es con punteros a funciones

• En lugar de hacer referencia a una función específica, un código puede hacer referencia a una varible que almacena un puntero a una posible función

• Esto permite resolver vínculos en tiempo de ejecución al elegir qué puntero almacenar en dicha variable

Indirección

• Esto le permite al código C hacer referencia o llamar a nuevas extensiones que no existían en el momento en que fué compilado

• Elemento crítico en cualquier sistema que use este procedimiento.

Callback

• Un callback es un código ejecutable que se pasa como argumento a otro código.

• Permite que una capa de nivel bajo llame una subrutina (o función) definida en una capa de mayor nivel.

Función Callback Def. 2

• Una función callback, es una función que es llamada a través de un puntero a función.

• Si se pasa el puntero (dirección) de una función como argumento de otra, cuando tal puntero es usado para llamar la función apunta a lo que se denomina un call back.

Ejemplo: GUI en C

• Una herramienta GUI debe poder llamar funciones en respuesta a eventos generados por el usuario

• La función - con determinado comportamiento - es parte del código de la aplicación (ej. Botón llama función)

• La herramienta GUI precompilada debe poder llamar a esta función (cuyo nombre no conoce)

Ejemplo button

• Cuando una aplicación crea un botón le da un puntero a una función para que la llame cuando es “clickeado”

• La estructura botón almacena este puntero en RAM

• Este puntero debe ser asignado en tiempo de ejecución

C++

• C++ complica aún más las cosas agregando jerarquías de namespaces

• Una clase define un namespace

• Existen diferencias entre private de C++ y static de C, java también tiene lo suyo pero no entraremos en los detalles

Conclusiones del namespace:

• El espacio global de nombres de C, C++ o Java se traduce en composición dinámica

• Hacer referencia a una función significa refererirse a un nombre único en el espacio global

• Por eso se hace necesario usar punteros a funciones para desacoplar las implementaciones entre si.

Visión nesC

• nesC toma otro camino:1) El código se parte en componentes (unidades

discretas de funcionalidad)2) Un componente sólo puede hacer referencia a

las variables de su propio espacio de nombres (local)

3) Un componente no puede nombrar una variable de otro componente

4) En cambio, si puede declarar que usa una función definida por otro componente

5) También puede declarar que provee una función que otro componente podrá usar

Tipos de Componentes

• Existen dos tipos de componentes:– Modulos: Implementan comportamiento– Configuraciones: Interconectan componentes

entre si.(Estructura)

A un componente no le importa si otro componente es un módulo o una configuración

Un componente puede estar compuesto de otroscomponentes

Módulos

proveen código que implementa una o mas interfaces y el comportamiento interno del componente

Interface y módulo

Interface AFuncion XFuncion Y

Modulo MProvee interface AImplementa funciones X e Y

Configuraciones

Unen entre sí a los componentes para dar lugar a un nuevo componente

nesC vs C

• Los módulos TinyOS son análogos al código C

• En cambio las configuraciones

– que pegan los componentes entre si-

no lo son.

Componentes vs Objetos

• En cierta forma, los componentes nesC son similares a los objectos.

• encapsulan estados y los dotan con funcionalidad.

• La principal diferencia recae en el alcance de los nombres (naming scope)

Diferencia con objetos

• Los objetos en C++ y Java se refieren a funciones y variables en un espacio de nombres (namespace) global

• Las componentes nesC usan un namespace local

• Además de declarar las funciones que implementa, un componente debe también declarar las funciones que usa (o llama).

InterconexiónPara que un componente pueda usar una función que otro provee, es

necesario que previamente se interconecten explícitamente usuario con proveedor

USUARIO PROVEEDOR

Al ser una interconexión a un punto específico, el compilador puede optimizar el llamado de las mismas a través de las fronteras (interfaces)

Composición en tiempo de compilación

• Dado que esta composicion ocurre en tiempo de compilación, no require alojar memoria o almacenar punteros a funciones en RAM en tiempo de ejecución.

• Al no usar estos niveles de indirección, el

compilador nesC conoce el grafo completo de la llamada.

Interfaz

• Es raro que un componente declare funciones individuales en su especificación.

• Los componentes se unen mediante interfaces, las cuales constituyen un conjunto de comandos y eventos lógicamente relacionados entre sí

Contratos de Interface

Se han reportado casos de errores provenientes de restricciones en el uso de las interfaces.

Muy comunmente es necesario leer el código de los componentes para poder usarlos con éxito, lo cual contradice el principio de jerarquia.

Existen trabajos que pretenden resolver estos problemas mediante la especificación y el cumplimiento de contratos de interfaz.

¿Por qué esta forma extraña de hacer las cosas?

Características de nodos Sensores

Tamaño pequeño, bajo consumo

Operación altamente concurrente- Flujo múltiple, no se espera - respuesta - al

comando

ModularidadInterfaces simples

sensoresactuadores

red

almacen

Operación Robusta numerosos, no atendidos, critical

Timers

Computador Embebido

A diferencia de los computadoras personales que necesitan cargar programas dinámicamente en respuesta al requerimiento de los usuarios, las redes de sensores se componen de computardores embebidos, que tienen usos específicos bien definidos.

Visión Estatica

• Esto puede evolucionar y cambiar con el tiempo, pero esta evolución es lenta en comparación con la forma en que una PC carga programas.

• Adicionalmente los sistemas embebidos no requieren de una interacción permanente con el usuario como lo hacen las PC.

sistemas sin atención

• Si el servidor se pone loco lo reiniciamos.

• Si el procesador de texto enlentece lo cerramos y volvemos a abrir.

• Esto no pasa en sistemas embebidos que operan sin atención de usuario la mayor parte del tiempo.

Hardware

CPU

ADC

convertir

pronto

Split Phase

• El Hardware por lo general no bloquea, es casi siempre split-phase.

• Es split-phase en el sentido que al completar un pedido genera un callback(interrupción al sistema).

SoftwareComunicaciones entre procesos

El pasaje de mensajes puede ser:

Sincrónico (blocking): El transmisor espera hasta que el receptor haya recibido el mensaje.

Asíncrono (non-blocking): el transmisor no espera

• Si un conversor ADC interrumpe demasiado rápido el driver del mismo debería poder solucionarlo con un simple loop de espera a que se dispare.

• En cambio si la interrupción es lenta, esto gastaría mucha CPU y ciclos de energía.

• La solución tradicional para este último caso consiste en el uso de multiple threads

Caso RTOS (Sincrónico)

• Cuando un thread pide una muestra al ADC, el OS hace el pedido y coloca a dicho thread en cola de espera (WAIT), inicia la operación y luego pone a correr otro thread que estaba READY.

• Cuando el ADC interrumpe, el driver retoma el thread que estaba en WAIT (OS lo pasa a la cola READY).

threads en sistemas embebidos

• El problema es que requieren bastante uso de RAM.

• cada thread tiene un stack privado que debe ser guardado cuando el thread pasa a “waiting” o “idle”.

• P.ej. cuando thread muestrea un ADC y es colocado en cola de espera, toda la memoria de su stack de llamada debe permanecer intacta para poder continuar cuando retome la ejecución.

Solución

• TinyOS en lugar de hacer todo sincrónico a través de threads se opta por:

– Las operaciones que son split-phase en hardware se hacen split-phase en software.

– Esto significa que muchas operaciones comunes como muestrear sensores y enviar paquetes a la radio, son split-phase.

Interfaz bidireccionalUSER – PROVIDER

USER

PROVIDER

Down Call

Command

Up Call

Event

Interface

Bidireccional

comandos y eventos

• Una característica importante de la interfaz split-phase es que es bidireccional:

• Hay una “downcall” para iniciar la operación,• Y un “upcall” que significa que la operación se

ha completado. • En nesC, downcalls son generalmente

commands, mientras que upcalls son events

event

• Un event es una función mediante la cual la interfaz de la aplicación señala que ha tenido lugar cierto suceso.

• Una interfaz no solo provee commands que puedan ser llamados por sus usuarios (users), sino que también señala events, estos a su vez llaman “handlers” que deben ser implementados por el usuario.

• Debemos pensar al evento como una función callback que la interfaz de la aplicación invoca.

• Un módulo que usa (uses) una interfaz debe implementar los eventos (events) que dicha interfaz usa.

IMPLEMENTACIÓN DE LOS EVENTOS

• Los Componentes implementan los eventos que usan

y los comandos que proveen

Composición

Componente Lector

StdControl Timer

Read

provides

uses

provides

interface StdControl;

interface Timer<TMili>;

uses

interface Read<uint16_t>

Messaging Component

init

Po

we

r(m

od

e)

TX

_p

ack

et(

bu

f)

TX

_p

ack

et_

do

ne

(s

ucc

ess

)

RX

_p

ack

et_

do

ne

(b

uff

er)

Internal

State

init

po

we

r(m

od

e)

sen

d_

msg

(ad

dr,

typ

e,

da

ta)

msg

_re

c(ty

pe

, d

ata

)

msg

_se

nd

_d

on

e)

internal thread

Commands Events

Resumen Caracteríticas

• Scheduler + Grafo de Componentes– Modelo scheduling restringido a dos niveles: comandos +

eventos• Componente:

– Comandos, – Event Handlers– Frame (almacenamiento)– Tasks (concurrency)

• Modelo de almacenamiento restringido

Resumen

Data Memory Model

• STATIC memory allocation!– No heap (malloc)– No function pointers

• Variables - frame por componente

• Variables locales – Saved on the stack stack compartido– Declared within a method

Modelo de Programación

• Separación entre construcción y composición

• Programas hechos mediante componentes

• Cada componente se especifica mediante una interfaz

– Provee “hooks” para conectar componentes entre si.

• Los componentes se hallan conectados entre sí en forma estática basada en sus interfaces– Aumenta la eficiencia en tiempo de ejecución

Modelo de Concurrencia

• TinyOS ejecuta un solo programa formado por los componentes del sistema que hayamos elegido y por los componentes a medida hechos por nosotros para la aplicación específica.

• Existen dos threads de ejecución: “tasks” y “hardware event handlers”.

• Cada componente tiene una especificación, un bloque de código que declara las

• funciones que provee (implementa)

• y las funciones que usa (llama).

• Que un componente provea o use la interfaz Send interface es lo que define de qué lado de la operación split-phase se encuentre.

• Un proveedor de Send define las funciones send y cancel y puede señalizar el evento sendDone.

• Por el contrario un usuario de Send necesita definir el evento sendDone y llamar a los comandos send y cancel.

Ejemplos

interfaz StdControl

• Para controlar el consumo es necesario prender y apagar partes del circuito , tales como encender un sensor para tomar una lectura o la radio para escuchar si se reciben paquetes.

• La interfaz StdControl es la encargada de realizar estas operaciones.

• Un componente que representa un servicio que puede ser apagado debe proveer la interfaz StdControl

• Mientras que un componente que necesita encender o apagar a otros usa dicha interfaz.

ejemplo

• Una capa de ruteo necesita arrancar una capa de enlace de paquetes

• La cual a su vez necesita arrancar y parar la detección de canal libre:

Wiring

• El código RoutingLayerC llama a las funciones: SubControl.start() y SubControl.stop().

• A menos que SubControl se cablee a un proveedor, estas funciones son símbolos indefinidos, no están vinculados a ningún código existente.

• En cambio si SubControl se cablea al StdControl de PacketLayerC, entonces cuando RoutingLayerC llama a SubControl.start() está invocando a StdControl.start() de Packet- LayerC.

• Esto significa que la referencia RoutingLayerC.SubControl.start apunta a la definición:PacketLayerC.StdControl.start.

Encapsulado

• Las dos componentes RoutingLayerC y PacketLayerC se hallan completamente desacopladas y solamente se vinculan cuando se cablean entre sí.

Timer.nc

interface Timer {  command result_t start(char type, uint32_t

interval);  command result_t stop();  event result_t fired();}

Here we see that Timer interface defines the start() and stop() commands, and the fired() event.

• El comando start() se usa para especificar el tipo de timer y el intervalo en el cual el timer expirará.

• La unidad del argumento intervalo es milisegundo.

• Los tipoa válidos son: TIMER_REPEAT y TIMER_ONE_SHOT.

• Un timer one-shot termina al cumplirse su tiempo

• Un timer repeat solo se detiene con un comando stop().

• ¿Cómo sabe una aplicación cuando ha expirado su timer?

• Respuesta: Cuando recibe un evento que provee la interfaz Timer:

•   event result_t fired();

Configuration

configuration Blink {}implementation { components Main, BlinkM, SingleTimer, LedsC; Main.StdControl -> SingleTimer.StdControl; Main.StdControl -> BlinkM.StdControl; BlinkM.Timer -> SingleTimer.Timer; BlinkM.Leds -> LedsC;}

module

/** * Implementation for Blink application. Toggle the red LED when a * Timer fires. **/module BlinkM { provides { interface StdControl; } uses { interface Timer; interface Leds; }}// continua

StdControl.init()

implementation { /** * Initialize the component. * * @return Always returns <code>SUCCESS</code> **/

command result_t StdControl.init() { call Leds.init(); return SUCCESS; }

StdControl.start()

/** * Start things up. This just sets the rate for the clock

component. * * @return Always returns <code>SUCCESS</code> **/ command result_t StdControl.start() { // Start a repeating timer that fires every 1000ms return call Timer.start(TIMER_REPEAT, 1000); }

StdControl.stop()

/** * Halt execution of the application.

* This just disables the clock component.

*

* @return Always returns <code>SUCCESS</code>

**/

command result_t StdControl.stop() {

return call Timer.stop();

}

Timer.fired()

/** * Toggle the red LED in response to the

<code>Timer.fired</code> event. * * @return Always returns <code>SUCCESS</code> **/

event result_t Timer.fired() { call Leds.redToggle(); return SUCCESS; }

Makefile

COMPONENT=Blink

include ../Makerules

Tutorial

• TinyOS Tutorial  

• http://docs.tinyos.net/index.php/TinyOS_Tutorials

TinyOS Enhancement Proposals (TEPs)

• TinyOS 2.0 Core Working Group

• http://www.tinyos.net/scoop/special/working_group_tinyos_2-0

Recommended