martes, 8 de abril de 2014

Sistemas Operativos Vs Programación PFI. Un tutorial para iniciarte en el mundo de OS para micros.

A continuación presento un resumen de un material o tutorial de un curso de RTOS para FreeRtos que considero excelente y mejora sustancialmente a muchos tutoriales en la red si con algunas observaciones en cuanto a gráficos. (Ing. Reinier Torres Labrada)

FreeRTOS tiene características comunes con el resto de sus parientes, y algunas propias, que poco a poco iremos viendo y analizando cada vez con mayor nivel de detalle, hasta lograr dominar cadauna de sus poderosas características.

Características Generales:
Núcleo de tiempo real minimalista, de código abierto,de libre distribución y que puede ser utilizado en aplicaciones comerciales sin que el desarrollador requiera una licencia por su utilización.
FreeRTOS está protegido por la licencia GPL, e incluye una excepción para permitir su uso en  aplicaciones comerciales sin la obligatoriedad de que el código escrito para aplicación tenga que ser liberado bajo GPL
Amplia disponibilidad de puertos para diferentes arquitecturas de procesadores y herramientas de desarrollo. Con cada distribución se incluye un conjunto de aplicaciones demostrativas para ayudar al principiante a introducirse en el uso del núcleo. Soporte gratuito a través de una amplia y activa comunidad de usuarios. Adicionalmente existe soporte comercial que incluye asistencia en la asimilación y desarrollo de aplicaciones.

Características Técnicas:
Núcleo configurabe en tres modalidades:
De tiempo compartido:
El núcleo siempre otorgará el procesador a la tarea de mayor prioridad que esté lista para ser ejecutada. Si varias tareas están listas para ejecutarse y comparten el mismo nivel de prioridad; se aplica la técnica Round-Robin para determinar cuál de las disponibles obtendrá el procesador por el próximo período de ejecución. Es el modo
de trabajo por defecto.

De tiempo cooperativo:
Es responsabilidad de las tareas entregar el procesador para que el planificador la entregue a la tarea de mayor prioridad que esté lista para ser ejecutada. Las interrupciones no pueden provocar un cambio de contexto.
 

Híbrido:
Las interrupciones pueden provocar el cambio de contexto hacia una tarea, pero el cambio de contexto entre tareas se realiza mediante la técnica de tiempo cooperativo

  • Diseño minimalista que necesita muy pocos recursos para su implementación. Dependiendo de la arquitectura del procesador se puede acomodar una implementación mínima en el espacio de memoria requerido para 2000 instrucciones.
  • Diseño modular, personalizable y fácil de utilizar.
  • La mayor parte del código está escrito en C estándar. Habitualmente existe una parte implementada en ensamblador, dedicada a las funcionalidades específicas de cada arquitectura del procesador.
  • Funcionalidades para el seguimiento de tareas y estadísticas de desempeño.
  • Soporte de tareas y co-rutinas.
  • Detección de desbordamiento de pila.
  • Sin restricciones en el número de tareas.
  • Sin restricciones en el número de prioridades.
  • Sin restricciones en la asignación de prioridades, tareas distintas pueden compartir el mismo nivel de prioridad con planificación Round-Robin.
  • API del núcleo que incluye el manejo de: Colas, Semáforos binarios de conteo y recursivos de Exclusión mutua.
  • Herramientas de desarrollo amparadas por software libre.
  • Código fuente amparado por licencia de software libre.
  • Libre de cargos comerciales, aún cuando se utilice en aplicaciones comerciales.
  • Aplicaciones de ejemplo disponibles para todas las arquitecturas soportadas y respaldadas en tarjetas comerciales, principalmente de bajo costo.
¿Por qué es una buena opción?
  • Es una única solución que cubre diferentes arquitecturas.
  • Es reconocido por su buen desempeño.
  • Soportado por una comunidad muy activa y creciente.
  • Demanda pocos recursos de memoria RAM y ROM.
  • El kernel está contenido en sólo tres ficheros de C.
  • Usted tiene acceso al código fuente y pude modificarlo y adaptarlo a sus necesidades si así lo requiere.
¿Y cómo trabaja?

La misión de un sistema operativo es proveer a desarrolladores y usuarios de funcionalidades para el uso eficiente de los recursos del sistema de cómputo.


Programar bajo este nuevo paradigma incluye un mecanismo estructural de diseño escalable, ventaja para sistema de desarrollo con integración futura.

Es conocido que el recurso más preciado de un sistema con microprocesador es el procesador, los microcontroladores no escapan a esta realidad. Para complicar más las cosas, el procesador consume tiempo para ejecutar código así que en la mayoría de los casos hacer las cosas a tiempo constituye el principal cuello de botella durante el diseño de sistemas empotrados.

Además de ayudarnos a gestionar eficientemente el procesador; FreeRTOS es una poderosa herramienta para enfrentar el problema de la complejidad de las aplicaciones, el acceso a los periféricos del microcontrolador, liberar y reservar memoria en función de la carga del sistema. Para ello disponede tres elementos esenciales comunes a cualquier sistema operativo.
  • Planificador de tareas (Scheduller)
  • Interfaz de Aplicación para el Programador (API)
  • Manipuladores de dispositivos (Device Drivers)
Estas tres funcionalidades se implementan mediante funciones de librería de C estándar y código específico para cada arquitectura de procesador, de este modo se garantiza la reutilización del código del núcleo y el aprovechamiento de cualquier arquitectura que cuente con los recursos necesarios para soportar al sistema operativo.

Scheduller
Es el encargado de asignar el procesador a quién en ese momento le corresponda utilizarlo, para ello FreeRTOS tiene implementada una de las más conocidas políticas de asignación de recursos que se concen en el mundo de los sistemas operativos. Más adelante entraremos en detalles acerca de cómo funciona el scheduller de FreeRTOS.

API
FreeRTOS no puede darse el lujo de implementar muchas funcionalidades porque su misión fundamental es ser ligero y rápido, así que lo que más encontraremos acá serán las funcionalidades para sincronización de tareas y alguna que otra función para el trazado y recopilación de estadísticas.
Los sistemas operativos de propósito general acostumbran tener una API grande y compleja, pero en un microcontrolador los recursos son limitados en todo sentido, así que para suerte nuestra tendremos pocas funciones que dominar, y para desgracia, tendremos que aprender a sacarles el máximo.

OS vs Programacion de funciones o programación standar.
Si piensa en el programa mas complejo que allas realizado muy probablemente el programa consta de lo siguiente:
Programa principal. conformado por un lazo infinito en donde conviven funciones o procesos que se activan mediante un swiche lógico. Dicho swiche lógico o If se encuentra consultado el resultados de un macro contador para selección. Como lo anterior, esas funciones o subrutinas poseen llamados de interrupciones compactas de configuración, acción y salida (como para ser claro y juste en temas de prioridades)

Si nunca ha trabajado con un Sistema Operativo, es muy probable que todos los programas desarrollados por usted trabajen de la misma filosofía, en lo que podríamos llamar modelo de programación principal, las funciones y las Interrupciones; yo les llamo "PFI".Por lo anterior todos los programas bajo este concepto tienen un elementos común que les permite trabajar armónicamente y nada desordenado la memoria de datos y llevar un control de las funciones activar o desactivar.


La Figura siguiente se muestra claramente este modelo de desarollo de aplicaciones estandard y ampliamente utilizado en el diseño con Microcontroladores y también en otros ámbitos. A muchos podría parecerle magnífico, y en muchos casos es así, simplemente porque es sencillo, Pseudo-escalable. Veamos, sí aplica una codición habilita una función que modifica la memoria interna o externa del microprocesador lo mismo con las interrupciones.
Si pensamos en realizar un reloj con display de leds. Se propone el uso de un Timer1 en formato Interrupción para barrido de leds con salida correspondiente de lectura de memoria y puerto; esto con el fin
de hacer independiente la salida visual vs el programa principal en donde se calcula problemas de tiempos e incrementos con funciones (de baja prioridad). Una vez finalizada la condición se devuelve al programa principal y en espera de algún cambio de los macrocontadores que activen dichas condiciones.
En este caso el programa principal decide, mediante encuesta que función se ejecutará, pero una vez que la función está ejecutándose el programa debe esperar pacientemente a que termine, antes de seguir encuestando las condiciones para evaluar si otras funciones deben ser llamadas o no. El único caso en que una función llamada o el programa principal pueden ser interrumpidos es cuando se produce una interrupción.

Este modelo, sin embargo, tiene una desventaja intrínseca que se manifiesta más en la medida que aumenta la cantidad de “cosas” que el sistema debe hacer. Si además, esas “cosas” tienen que hacerse respetando restricciones de tiempo en la ejecución misma del código de la “cosa” o en el tiempo en que la “cosa” debe ejecutarse, estaremos ante un problema al estilo Nudo Gordiano.

Ahora imagine que cada “cosa” de este programa que estamos analizando fuese la única por hacer, evidentemente el programa sería muy simple. Ese es precisamente el concepto tras un sistema operativo, hacerle creer al sistema que tiene una única tarea que hacer en cada momento, pero hacerlo de forma tal que no se note que estamos conmutando entre las distintas “cosas”. Por lo tanto llamaremos tarea
a eso indefinido que he llamado “cosa”.

Este cambio de concepto nos permite implementar las funcionalidades como si fuesen la única actividad a la que estará dedicado el procesador del microcontrolador, es algo así como que cada tarea es un programa principal de allí sale el concepto de programación pseudo paralelo. Sin embargo, sabemos que eso no puede ser, puesto que una aplicación cualquiera debe hacer varias tareas diferentes, incluso relacionadas entre ellas y cada una debe pasar por un tiempo en el microcontrolador. Entonces  ¿si cada tarea es como un programa principal? ¿cómo se ejecutarán todas? ¿y cuando? ¿y si están relacionadas entre sí?.

Las respuestas a estas y otras preguntas irán llegando poco a poco en la medida que descubrimos un nuevo modelo de programación.


Ahora veamos la figura superior. Note como ha desaparecido el programa principal, sin embargo sí que se mantienen las Interrupciones, pero hay dos nuevos elementos, las tareas y el núcleo del sistema operativo. Las interrupciones forma parte de un proceso casi a la par de las Tareas.
Observe que el sistema operativo es el encargado de decidir que tarea se ejecuta. Note además, que las tareas intercambian información entre ellas a través de la memoria y del núcleo del sistema operativo. Asimismo, observe que existen algunas tares desactuvadas mientras hay tareas en ejecución. Y todo absolutamente gobernado por el Sistema Operativo.


En este caso pueden estar en ejecución una tarea, el programa principal o un ISR. Sin embargo, usted no programa nada en el núcleo del sistema operativo, sino que se concentra en hacer bien el código de las tareas. El núcleo se encargará de decidir que tarea obtendrá el procesador y además de eso, como veremos más adelante, el núcleo puede “interrumpir” a una tarea que se esté ejecutando. Como en el caso anterior las interrupciones pueden entrar en contexto cuando el núcleo o una tarea están ejecutando código.


Una vez mostrado las diferencias en programación sobre estas dos filosofía de programación lo invito adentarnos un poco mas sobre los conceptos de OS y su empleo en los sistemas embebidos.

Conceptos de Sistemas Operativos

Tareas
Entidad que encapsula en sí misma código y memoria de datos y que actúa como si fuese el única programa en ejecución en un sistema de cómputo. Note el esquema de una Tarea en la figura derecha, existe en el espacio de datos un elemento muy especial el TCB.
El TCB (Bloque de Control de Tarea) es el espacio de memoria donde el núcleo del sistema Operativo y la tarea del mismo almacenan parte de la información que permitirá, entre otras cosas, decidir si la tarea puede o no ejecutarse. Recordemos que las tareas actúan como si ellas fuesen el único programa en el sistema. Esto es cierto si la tarea está en ejecución, pero si no es así, es como si la tarea estuviese “suspendida” o “congelada”. De modo que la mayor parte del tiempo nuestro sistema de cómputo debe tener una tarea en ejecución y seguramente otras tantas “suspendidas” o “congeladas” y es por ello que sus espacios de memoria de datos deben estar protegidos de posibles ataques externos, por eso un bloque protector verde en la memoria. Poniendo todo junto y suponiendo que tenemos un sistema con un procesador; podríamos ver un escenario como el de la Figura siguiente. Observe como en un sistema con un único procesador solamente una tarea puede estar ajecutando su código y accediendo a su memoria, el resto está en estado de “no ejecución” o “ejecución suspendida”.

Ejecución Suspendida

Se define ejecución suspendida como un “super-estado” no nos sirve de mucho puesto que una tarea puede estar aquí por diferentes razones, veamos algunas:
  • Porque aún estando lista para ejecutar código, una tarea más importante está ejecutando su código.
  • Porque la tarea misma necesita esperar por un evento.
  • Porque la tarea terminó de ejecutar su código y no necesita volver a ejecutarlo hasta pasado un tiempo.
  • Porque la tarea ha intentado acceder a un recurso compartido y este está ocupado.
Pero en resumen se puede listar las dos razones mas importante:
  • La tarea está lista para ejecutar código pero no puede.
  • La tarea espera por algo.
El primer caso se conoce como “tarea lista” mientras que el segundo es conocido como “tarea bloqueda” y si añadimos el estado “en ejecución” tenemos los tres estados básicos en que puede estar una tarea.

Observe que una tarea puede pasar directamente del estado “en ejecución” a los estados “lista” o “bloqueada” y que los cambios de “bloqueada” a “en ejecución” o de “lista” a “bloqueada” estan prohibidos. Respetando estas reglas podemos obtener un modelo de trabajo simple que nos podría permitir gestionar eficazmente el acceso de las tareas al procesador.

El caso de que una tarea se bloquea a sí misma porque deba esperar por un evento de alguna clase, es muy fácil de comprender. Sin embargo, no queda claro cómo es que una tarea puede pasar del estado “en ejecución” a “lista” ya que si está lista para ejecutar código, no es lógico que la tarea entregue el procesador???
Las tareas son egoístas en muchas ocasiones es un simil. Si tienen el procesador y pueden estar ejecutando código, ellas no lo entregarán, es por ello que se requiere la “intervención divina” o "Interrupción divina" para quitarle el procesador al egoismo de la taréa y darlo a otra que lo necesita y que también está lista para tiempo de proceso.

Planificador de tareas

Ya hemos visto que si una tarea está en condiciones de ejecutar código no querrá ceder el procesador, por lo que se requiere la intervención de otro agente en este juego que le retire el procesador y se lo pase a otra.
Ese agente es el planificador de tareas (scheduller) y su trabajo consiste en repartir el tiempo de procesamiento del sistema de forma que todos tengan el procesador cuando lo necesiten para hacer bien su trabajo y que nadie se adueñe de él indiscriminadamente.

Pero si el scheduller es un programa ¿cómo se las arreglará para interrumpir a una tarea que está utilizando al procesador?.

Esta es una pregunta que usted se hará sin lugar a dudas, y es una de las cosas que siempre veo quedan sin explicar adecuadamente cuando se presenta un sistema operativo.

El scheduller está compuesto de dos elementos esenciales para su funcionamiento:

El código que actualiza los TCB de las tareas y que además determina qué tarea entrará en ejecución.
Una Interrupción que permite al scheduller entrar en contexto.

El segundo punto nos da la clave para comprender el mecanismo que permite al scheduller interrumpir la ejecución de cualquier tarea que se encuentra ejecutando código. Una vez lanzada la interrupción, se transfiere el control del procesador al código principal del scheduller y este después de actualizar los TCB determinará qué tarea debe obtener el procesador.

Analizando lo anterior nos damos cuenta que el scheduller consume tiempo de procesamiento como cualquier tarea, pero eso no debe preocuparnos demasiado. Si este código está bien diseñado y usted ha escogido el procesador adecuado o lo ha configurado para que corra a la velocidad correcta, su carga sobre el sistema no debe superar el 2 % del tiempo total disponible de procesamiento.

Métodos de planificación de tareas

Ya sabemos los elementos básicos que le permiten al scheduller entrar en contexto, pero llegado el momento de decidir que tarea obtendrá el procesador es cosa difícil. Por ello han aparecido una buena cantidad de métodos de planificación. Y en muchos casos se combinan varios de ellos para lograr un sistema con ciertas características de desempeño.

Atendiendo a la forma en que se implementan, estos métodos se pueden clasificar en dos grandes grupos:

Decisión mediante una política establecida sólo en el código del planificador.
Decisión mediante una política establecida en la implementación de la aplicación.

Usted puede dejar que el planificador decida por sí solo, hay varios tipos de métodos de decisión en este caso, como por ejemplo:
  • Tiempo menor para completar la ejecución.
  • Tiempo mayor para completar la ejecución.
  • Lista circular (Round-Robin).
  • La que lleva el mayor tiempo sin ejecutar.
El problema con todas estas políticas de planificación está en que deja fuera la habilidad del programador para influir en el mecanismo de decisión de cuales tareas son más o menos importantes y eso puede afectar seriamente el desempeño del sistema. Dentro de las políticas de planificación, en la implementación de la aplicación, la asignación de prioridades es, sin lugar a dudas, la más utilizada por ser simple y efectiva.

FreeRTOS utiliza este método de planificación en el primer nivel de decisión, por lo que el equipo de FreeRTOS cosidera que usted, desarrollador, es el que sabe que debe ir primero o después.

Si FreeRTOS utilizara únicamente el mecanismo de asignación de prioridades, estaría obligado a tener una sola tarea en cada nivel de prioridades, y por consiguiente, deberíamos asignar una prioridad única a cada tarea. Este método es ampliamente utilizado cuando se puede determinar la prioridad de cada tarea, pero no es práctico en muchos casos, dado que muchas tareas pueden compartir el mismo nivel de prioridad en un sistema. FreeRTOS utiliza una combinación de políticas de planificación, la primera es la asignación de prioridad, y la segunda es el método Round-Robin para las tareas que comparten la misma prioridad.

Con esta combinación se consigue un adecuado balance entre la experiencia del desarrollador y la repartición justa del procesador entre tareas que tienen el mismo nivel de importancia. Resuelto el problema de repartir el procesador entre tareas que están listas para ejecutarse podríamos preguntarnos

¿y que pasa cuando una tarea decide que debe esperar por un evento o recurso?.

En estos casos la tarea hace una llamada explícita al scheduller a través de su API, el scheduller entra en contexto y realiza su trabajo otorgando el procesador a la tarea que le corresponde en ese momento. Un método similar se sigue cuando una Interrupción entra en contexto y gracias a ello una tarea que estaba esperando por el evento vinculado a la Interrupción debe comenzar a ejecutar código.

Todos estos casos serán expuestos mediante ejemplos prácticos posteriormente.


Marcapasos

Como habíamos visto el scheduller debe entrar en contexto, cuando una tarea se ha quedado con el procesador pero este debe ser entregado a otra tarea. Eso está claro, pero:
¿cuando es el momento de hacer que el scheduller entre en su contexto?

Para lograrlo podríamos pensar en un buen número de métodos, sin embargo, existe uno muy simple y eficaz. Si hacemos que el sistema obligue al scheduller a ejecutar código a intervalos regulares de tiempo, podremos asegurar que nadie se apodere del procesador por tiempo indefinido. Para lograr lo anterior basta con asociar la Interrupción de un temporizador con el mecanismo que asegura la entrada en contexto del scheduller. En el caso de los puertos de FreeRTOS para microcontroladores PIC, lo anterior se logra reservando un temporizador para esta función, de modo que usted obtiene con ello una especie de “marca pasos“ para su sistema y con ello garantiza la entrada del scheduller en contexto cada cierto tiempo.

Si usted es de los que gusta de utilizar el TIMER1 de sus microcontroladores, puede ir renunciando a esa práctica, ya que en todos los casos este temporizador está dedicado a garantizar el “marca pasos” del sistema.

Podría parecernos que sacrificar un temporizador para que FreeRTOS pueda hacer su trabajo es demasiado, pero yo puedo asegurarle que es un sacrificio que bien vale la pena y se lo demostraré posteriormente.

Hago este comentario porque si usted es de los que gusta echar mano al TIMER1 le sugiero replanificar su estrategia de uso de temporizadores.

No hay comentarios:

Publicar un comentario