TinyOS Programación Orientada a componentes SISEM 2009.
-
Upload
jorge-iglesias-torres -
Category
Documents
-
view
229 -
download
0
Transcript of TinyOS Programación Orientada a componentes SISEM 2009.
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