Ir al contenido principal

Modelos y frameworks en embedded software de manera simple - Parte I "Usando modelos"

El artículo, que se compone de tres partes, explora las características y los beneficios de utilizar modelos y frameworks para disminuir el costo, la complejidad y el tiempo de desarrollo del software de un embedded system.


La primera parte aborda el modelo de comportamiento dinámico que define la funcionalidad del sistema y en donde frecuentemente se producen las mayores complicaciones. Por ser de los más utilizados en este tipo de sistemas, el artículo se enfoca en los diagramas de comportamiento del tipo máquinas de estados, en especial aquellas de estados anidados o Statecharts [11], como así también en el modelo de ejecución Objeto Activo [1,2], el cual permite la ejecución simultánea de múltiples máquinas, colaborando entre sí y con el resto del sistema, de manera segura y transportable. Adicionalmente presenta los tópicos actuales más importantes para lograr un desarrollo de software realmente efectivo. La segunda parte analiza el uso de los frameworks más tradicionales del mercado, para lograr que los conceptos anteriores se materialicen y se conviertan rápidamente en parte de nuestro software. Por último, la tercera parte, presenta y describe el framework RKH [3], sus principales características y su estructura. La intención del artículo es presentar los modelos de software más utilizados y simples de aplicar en la gran mayoría de los embedded systems, al menos como primera aproximación, como así también los paquetes de software que hacen realizables todos estos conceptos.

Introducción

La creciente proliferación de embedded systems en todo ámbito, con más y mejores prestaciones, más inteligentes y sofisticados, a costos muy bajos y con tiempo de lanzamiento al mercado muy acotado, provoca que el software que los gobierna sea más complejo de lograr. Además, si a esta situación se suma el hecho que desarrollar embedded software es una tarea realmente compleja, la industria requiere inexorablemente de herramientas, procesos y métodos que mitiguen estos factores para mantener la competitividad de las compañías.

¿Qué hay de especial con los embedded systems (incluyendo aquellos de tiempo-real)?

En primer lugar, su software interactúa directamente con dispositivos electrónicos e indirectamente con algunos mecánicos, químicos, entre otros. Frecuentemente, el software dependiente del hardware se diseña y escribe aún cuando este no existe. Algunos son de naturaleza reactiva, cuya respuesta a eventos es estricta en tiempo (sistemas reactivos). Usualmente, se construyen con las computadoras más baratas. Muchos, son sensibles al costo de fabricación recurrente, con lo cual utilizan procesadores con escasos recursos. Las herramientas para su depuración no son lo suficientemente sofisticadas y estables. Por lo general, es difícil visualizar mensajes de error y diagnóstico. Algunos, deben lidiar con limitaciones en el consumo de energía. Otros no sólo deben cumplir con las restricciones temporales, sino también con la robustez y la fiabilidad. Estas restricciones obligan a lidiar con software concurrente, en contexto de interrupción como de aplicación. En ciertos casos, si fallan tienen el potencial de causar mucho daño. Usualmente, deben funcionar ininterrumpidamente, durante largos períodos de tiempo, y puede que se desempeñen en entornos adversos y hostiles. Todas estas dificultades se traducen en mayor complejidad, lo que implica más tiempo, más esfuerzo y (a menos que tengamos cuidado) más defectos. 

El mayor problema: el comportamiento

Si en el contexto expuesto, se le suma que tradicionalmente, la descripción de lo que hace o debería hacer este tipo de sistemas, se extrae desde el mismo código fuente o bien de extensos e incompletos documentos, indefectiblemente el desarrollo de embedded software es aún más difícil de lograr y mantener. 

Esto sugiere que se necesita una manera clara y precisa de especificar su comportamiento, no sólo para obtener la documentación correcta y sincronizada con el código, sino también para aumentar la capacidad de mantenimiento y de prueba, disminuir los tiempos al agregar de manera segura nuevas funcionalidades, mejoras o corrección de errores, entre otras cuestiones.

Por lo tanto, se requiere de herramientas y enfoques que mitiguen estos inconvenientes y así disminuir los tiempos, el costo y la complejidad del desarrollo del embedded software, en todas las etapas de su ciclo productivo.

Usando modelos

Una de las alternativas más potentes que se utilizan en el desarrollo efectivo de software, son los modelos. Los modelos permiten, clara y explícitamente, expresar el propósito de la funcionalidad y del diseño, como una abstracción de mayor nivel que el código fuente. 

Respecto del comportamiento dinámico, las máquinas de estados son modelos ampliamente utilizados en los embedded systems. Las cuales son abstracciones que se focalizan en mostrar visualmente qué hace o debería hacer el sistema que representan. Básicamente, estas permiten capturar eficientemente y en tiempo de diseño el comportamiento del sistema. De esta manera, gran parte del desarrollo transcurre en tiempo de diseño, utilizando los modelos visuales, para luego representarlos de alguna manera mediante un lenguaje de programación tradicional, que finalmente se pondrá en ejecución.

Adicionalmente, en la industria del embedded software también se utilizan otros tipos de modelos para representar tanto la estructura como la interacción del sistema. En el primer caso se utiliza el diagrama de clases y en el otro el diagrama de secuencias, que permite representar visualmente la interacción de ciertas entidades del sistema en un escenario particular.

Ejemplo: diagrama de clases
Ejemplo: diagrama de secuencias

Así, hoy en día, el modelado de software se vuelve imprescindible en sistemas complejos, ya que la abstracción que proveen, genera un aumento notable en la productividad y la eficiencia del desarrollo del software.

Limitaciones de las máquinas de estados tradicionales

En sistemas complejos de gran envergadura, las máquinas de estados tradicionales, también conocidas como planas (Finite State Machine, FSM), pierden rápidamente sus bondades, y su uso se vuelve frustrante, no sólo debido al número de estados, que obviamente, aumenta exponencialmente a medida que crece la complejidad del sistema, sino también a la cantidad de transiciones entre estos. Este efecto se lo conoce como explosión de estados y transiciones.

Statecharts

Por tal motivo, se requiere una extensión a este formalismo, tal que mantenga la representación visual, clara e intuitiva del comportamiento, pero mitigue dichos inconvenientes. Las máquinas de estados Statecharts [11] constituyen un formalismo visual que cumple con estas premisas. Si bien no existe una definición oficial de su sintaxis y semántica, la provista por UML[4] es una de la más difundidas y de uso global, basada en la declaración original de su creador Dr. David Harel. Respecto de su antecesora, sus principales características son la jerarquía y la ortogonalidad, aunque incorpora algunas otras cuestiones como, los pseudoestados, las acciones de entrada y salida de estados, las actividades, las transiciones condicionadas, entre otras. Estos modelos también se los conoce como máquinas de estados jerárquicas o de estados anidados. 

Ejemplo: diagrama de estados anidados

Programación dirigida por eventos

Un sistema complejo del mundo real, que representa su comportamiento dinámico mediante uno o más diagramas de estados, generalmente se compone de varios Statecharts en ejecución simultánea que colaboran uno con otro, para lograr la funcionalidad deseada. Este esquema es altamente eficiente en los sistemas reactivos, donde es natural el uso de máquinas de estados y la aplicación del paradigma de la programación dirigida por eventos (event-driven programming o simplemente programación reactiva), donde la ejecución de la aplicación sucede al recibir un evento (en este contexto, podríamos llamarlo mensaje asincrónico o simplemente señal), con lo cual permanece en reposo mientras no reciba eventos, esta es una condición útil para ahorrar energía. De esta manera, su comportamiento dinámico tiene la forma "cuando ocurre el evento A y la condición C es verdadera entonces se ejecuta la acción A", esta construcción indudablemente tiene la forma de una transición de estados.

Junto a esto último, es importante destacar el uso del concepto de objeto activo, como la unidad de concurrencia del sistema, y la manera en que estos suelen comunicarse, de forma simple y segura, utilizando el patrón de intercambio de mensajes asincrónicos, también conocido como “send and forget”, que ciertamente evita algunas trampas de los sistemas concurrentes, como son los interbloqueos, la inversión de prioridades ilimitada, entre otras.

Este tema será abordado en otra publicación con mayor grado de detalle.

Objeto activo

Un objeto activo [1,2,5,6,7,8,9,10] es aquel que controla su propio hilo de ejecución, y permite la recepción tanto de mensajes sincrónicos como asincrónicos. Dado que se ejecutan en su propio contexto, están mapeados a hilos de ejecución de la plataforma subyacente, encapsulando así las cuestiones de bajo nivel dependientes de esta última.

Run-to-completion

Es importante resaltar que el procesamiento de los mensajes asincrónicos respeta el paradigma “Run-To-Completion” o RTC, es decir, se procesan de a uno por vez. Para lo cual el objeto activo tiene asociado un mecanismo para almacenar momentáneamente dichos mensajes hasta su posterior procesamiento, usualmente se trata de una estructura de datos tipo cola, donde el orden de almacenamiento es FIFO, LIFO o por prioridad, lo cual a su vez define el orden de consumo y procesamiento. Por lo general, el paradigma RTC se implementa en un lazo de eventos, el cual lee los eventos para luego procesarlos de a uno por vez, en el orden que fueron almacenados. Usualmente, dicho procesamiento lo realiza una máquina de estados, aunque esto último tiene sus ventajas no siempre es necesario.

Frecuentemente el paradigma RTC se mal interpreta, en el sentido de que el procesamiento no puede interrumpirse, lo cual, por supuesto daría lugar a problemas ocasionados con la inversión de prioridad, en sistemas sensibles al tiempo. Sin embargo, esto no ocurre, ya que en una implementación particular, un hilo de ejecución que por ejemplo maneja una máquina de estados, puede suspenderse, permitiendo la ejecución de hilos de mayor prioridad, y una vez que se le asigne el procesador nuevamente, por el planificador de hilo de ejecución subyacente, puede reanudar de manera segura su ejecución y así completar el procesamiento del evento.

Objetos activos no reactivos

En ciertos casos el procesamiento de un objeto activo no está gobernado por eventos asincrónicos, sino más bien por un evento temporal periódico, o algún otro mecanismo de sincronización. En ambos casos el objeto activo permanece en reposo hasta que es señalizado para comenzar su ejecución. En algunas situaciones, el objeto activo se ejecuta periódicamente, pero en cada ciclo de ejecución consume los eventos asincrónicos recibidos mientras se encontraba en reposo, es decir, su comportamiento se vuelve reactivo de a períodos. Esto es útil para asegurar la planificabilidad de un sistema reactivo.

En otros casos, el objeto activo se ejecuta periódicamente para filtrar una señal o controlar una planta, en cuyo caso no recibe mensajes asincrónicos , y probablemente se comunique con el resto del sistema mediante mensajes sincrónicos. En un sistema concurrente y en caso que exista una región crítica que deba protegerse, el procesamiento puede convertirse en un potencial problema de inversión de prioridad ilimitada, u otra cuestión similar.

Desventaja del patrón “send and forget”

Si bien el patrón de intercambio de mensajes asincrónicos evita ciertas complicaciones de los sistemas concurrentes, en especial aquellos relacionados con la exclusión mutua, tiene algunas desventajas, como ser: el procesamiento del mensaje puede que no comience inmediatamente después de enviado, esto no está relacionado directamente con las prioridades de ejecución ni del productor ni del receptor, si no más bien con el hecho que el receptor puede estar procesando un mensaje anterior, respetando justamente el concepto RTC. Por lo tanto, esta consecuencia deberá tenerse en cuenta si el mensaje necesita procesamiento urgente para cumplir con las restricciones temporales. Otra cuestión que en ciertos casos puede ocasionar algún esfuerzo adicional, ocurre cuando el productor del mensaje espera una respuesta de su receptor y esta es obviamente asincrónica.

Referencias

[1] L. Francucci, “El paradigma de la programación dirigida por eventos” , August 10, 2016.
[2] Benowitz, “Auto-coding UML Statecharts for Flight Software” , July, 2006.
[3] RKH, “RKH Sourceforge download site,”, http://sourceforge.net/projects/rkh-reactivesys/, August 7, 2010.
[4] Object Management Group, “Unified Modeling Language 2.5,” formal/2015-03-01, 2015.
[5] Herb Sutter, “Prefer Using Active Objects Instead of Naked Threads” , Dr.Dobb’s, June 14, 2010.
[6] Herb Sutter, “Use Threads Correctly = Isolation + Asynchronous Messages” , Dr.Dobb’s, March 16, 2009.
[7] David Cummings, “Managing Cuncurrency in Complex Embedded Systems” , Fujitsu Laboratories Workshop on Cyber-Physical Systems, June 10, 2010.
[8] R. Greg Lavender, Douglas C. Schmidt, “An Object Behavioral Pattern for Concurrent Programming” , 1996.
[9] B. P. Douglass, “Real-Time UML: Advances in the UML for Real-Time Systems (3rd Edition),”, Elseiver, October 4, 2006.
[10] M. Samek, “Practical UML Statecharts in C/C++, Second Edition: Event-Driven Programming for Embedded Systems,” Elsevier, October 1, 2008.
[11] D. Harel, "Statecharts: A Visual Formalism for Complex Systems", Sci. Comput. Programming 8 (1987), pp. 231–274.

Comentarios