Coordinar para no dañarHasta ahora hemos visto al RTOS como un elemento que nos ayuda a “simplificar” el código de nuestra aplicación. Por una parte, dejándole parte de la temporización de las tareas que debe realizar nuestra aplicación y por otra utilizando el tiempo de las demoras ocupadas para hacer que otras tareas ejecuten su código en ese tiempo. Sin embargo esas ventajas no son nada comparado con dos herramientas fundamentales que nos ofrecen los SO: la coordinación y la sincronización, de ellas hoy vamos a ver solamente una de ellas la coordinación.
La coordinación es un término en la programación para SO, que se basa en la protección de recursos compartidos, es un concepto orientado a evitar que diferentes tareas puedan acceder a los datos o recursos y poner al sistema en un estado inestable o inseguro.
Ejemplos que ponen de manifiesto el problema de la coordinación hay muchísimos pero en aras de mantener el curso lo más sencillo posible y adecuarlo un poco más a las aplicaciones que normalmente se nos presentan yo utilizaré un problema más cercano a nuestro entorno.
El problemas de la coordinación también se conoce como el problema de la concurrencia o acceso concurrente a recursos, a mi me gusta llamarlo “coordinación” para establecer una mejor diferencia con respecto al otro mecanismo; el de la “sincronización” que veremos en la próxima entrega.
Destripando un poco más a la coordinación diremos que:
la coordinación es el mecanismo que debe implementar un SO para asegurar el acceso seguro a recursos compartidos del sistema, y que no tiene en cuenta restricciones temporales. Con esto queda claro que proteger a los recursos de un acceso no seguro es lo más importante en la coordinación, no importa durante que tiempo alguien (una tarea) esté utilizando el recurso, hasta que no lo libere nadie más podrá utilizarlo.
Hay mecanismos de coordinación que implementan también el problema de la sincronización, que si tiene en cuenta el factor tiempo, pero el RTOS de CCS no los implementa. Esto puede considerarse una limitante o una ventaja, según el tipo de aplicación en que se vaya a utilizar.
Un RTOS que implementa un mecanismo de coordinación con sincronización es el LMOS de Darukur, que veremos dentro de algún tiempo en este foro, debidamente documentado gracias a un proyecto que Darukur y un servidor, llevaremos a ustedes. Por el momento este simple cursillo es un buen método (no el único) para acercarse al mundo de los RTOS.
Veamos la coordinación con un ejemplo sencillo pero bien claro.
Supongamos que una madre ha comprado una camisa muy bonita, ella quería comprar dos, pero en la tienda solo había una así que decidió comprarla de todas formas. Cuando llegó a casa llama a sus hijos (ambos usan la misma talla de camisa), y les dice: “he comprado esta camisa, pero en la tienda solamente había una, así que deben compartirla como buenos hermanos”.
Las palabras de la madre no son alentadoras porque a ambos les gusta mucho la camisa y sin embargo deben compartirla, entonces la decisión de ambos es colocar la camisa en una percha, y cada vez que uno de los dos decida utilizarla se la ponga (dejando el perchero vacío). Pero hay una regla adicional, si cuando uno de los dos va a utilizar la camisa el otro ya se la llevó dejará una marca para indicarle al otro hermano que no podrá utilizar la camisa hasta que el que la marcó haya hecho uso de ella.
Este es un mecanismo en que los hermanos se han puesto de acuerdo para utilizar un recurso (la camisa), de manera compartida (porque es la única), de forma coordinada (para eso se pusieron de acuerdo e hicieron unas reglas simples).
Para implementar las reglas mostradas en el ejemplo anterior, el RTOS de CCS tiene dos funciones
rtos_wait() y
rtos_signal().
Para utilizar estas funciones primero hay que crear una variable entera que hará las funciones de percha y, que hablando con propiedad, se llama
semáforo. El semáforo es el elemento que le permite a la tarea reclamar el recurso compartido o esperar por él si ya está en uso. Las funciones
rtos_wait() y
rtos_signal() se utilizan para marcar el momento de inicio y fin del código que utiliza el recurso compartido. A la sección de código que utiliza el recurso compartido se le conoce como
sección crítica.
Veamos como funciona esto en términos de programación:
Usted crea una variable entera que será su semáforo o marcador de uso del recurso compartido.
El recurso compartido puede ser una o varias variables del sistema, en este caso el recurso compartido es un recurso de memoria. O puede ser un periférico del sistema, como es el caso del puerto serie o la memoria EEPROM, o cualquier otro.
rtos_wait() y
rtos_signal() son los marcadores de inicio y fin del código que hace uso de nuestro recurso compartido.
Cuando se inicia el programa usted inicializa el semáforo en algún valor positivo que determina la cantidad de tareas que pueden utilizar el recurso al mismo tiempo, normalmente es uno para las tareas que modificarán el recurso compartido, mientras que para tareas que solamente leen datos puede que no se usen secciones críticas o se permita más de una tarea que acceda simultáneamente al recurso compartido.
Cuando una tarea llegue a la sección de código que hace uso del recurso compartido, debe, primero que nada, ejecutar la función
rtos_wait(sem). Si el semáforo es mayor que cero, el RTOS decrementará la variable y permitirá que la tarea continúe su ejecución, sin embargo si el semáforo está en cero, el RTOS le quitará el procesador a la tarea y se lo cederá a otra que le toque ejecutarse. Cuando le corresponda nuevamente a la tarea que pidió el acceso al recurso compartido, el RTOS comprobará el estado del semáforo, si éste es mayor que cero, lo decrementará y le dará el procesador a la tarea para que se siga ejecutando, si no, volverá a dormir a la tarea hasta el próximo turno y así sucesivamente.
Al final del código de la sección crítica hay que colocar un
rtos_signal(sem) para que el RTOS incremente el semáforo permitiendo que otra tarea pueda utilizar el recurso compartido.
El ejemplo de hoy es el siguiente:
Elabore un programa para un PIC16F877 que permita mantener actualizada la cantidad de botellas que hay en un tramo de cinta transportadora en una embotelladora. La cinta es alimentada desde un almacén de botellas que tiene un robot que incrementa la variable Cantidad cada vez que coloca una botella en la cinta, mientras que dos robots llenadores de cajas, decrementan en 12 la variable cantidad cada vez que toman 12 botellas de la cinta transportadora. Como es de suponer el robot despachador debe llenar la cinta más rápido de lo que los robots llenadores la vacían. En la próxima entrega utilizaremos la sincronización para resolver el problema de que la cinta se quede vacía o se llene demasiado rápido. Como datos adicionales suponga que el robot despachador despacha una botella cada 250ms y que los robots llenadores llenan una caja cada 6 segundos. El robot despachador está conectado a RB0 y cada vez que pone una botella en la cinta transportadora le da un pulso al microcontrolador para indicárselo. Los robots llenadores están conectados uno a RB1 y el otro a RB2 e igualmente dan un pulso al microcontrolador cada vez que despachan una caja. La duración del pulso es de 100ms.
Como es de suponer este problema lo podemos resolver de muchas maneras y lógicamente sin el uso de los RTOS, pero eso se los dejo de tarea. Además el mecanismo de notificación de cada robot es un poco deficiente, pero ese no es el tema de este curso, aunque en su momento pondremos un ejemplo mejor al respecto, cuando rescatemos a las interrupciones del olvido.
Analicemos un poco en detalle el problema: Supongamos que la tarea asociada al robot despachador comienza a ejecutarse, lee el valor de la variable Cantidad, 100 botellas, y se va adormir esperando que el robot le notifique que ha puesto una botella en la cinta, en ese momento el RTOS le da permiso a la tarea del robot llenador 1 para que ejecute su código, ésta se da cuenta que el robot llenó una caja por lo que lee la variable Cantidad y le resta 12 botellas, quedando Cantidad = 88 botellas. Ahora le toca de nuevo a la tarea del robot despachador ejecutarse y como ya se despachó una botella le suma uno al valor previamente leído y actualiza la variable Cantidad, quedando 101 botellas, lo cual es falso.
Este ejemplo puede mejorarse semánticamente y evitarnos el uso de la sección crítica, todo ello gracias a que nuestro RTOS es cooperativo y en este caso, mientras la tarea esté ejecutándose tiene todos los recursos para ella sola hasta que entregue el procesador.
Por lo tanto podemos escribir el código de modo que no haya ningún
rtos_yield() intercalado con el código que accede a un recurso compartido, de esta forma la tarea se asegura el uso exclusivo del recurso compartido… a no ser que aparezcan las interrupciones en la palestra y las cosa se complique. Sin embargo en un sistema de tiempo compartido (los hay para PIC, ejemplo de ellos es el FreeRTOS), donde no sabemos cuando el RTOS nos quitará el procesador el uso de secciones críticas es
OBLIGATORIO, para evitar problemas como los mostrados, y por eso es importante que dominemos esta técnica.
Por otro lado el uso de Periféricos puede complicar más las cosas que el uso simple de la memoria y puede ocurrir que para ser eficientes estemos obligados a poner un
rtos_yield() dentro del código de la sección crítica. Moraleja: aprenda a programar con secciones críticas o no programe con RTOS.
El código:
#include "D:\Documentos\Projects\RTOS\RTOS.h"
#use rs232(baud=9600,parity=N,xmit=PIN_C6,rcv=PIN_C7,bits=9)
#use RTOS(timer=0, minor_cycle=10ms)
int8 semaphore; //Este es nuestro semáforo
int16 iCantidad; //Esta es la cantidad de botellas en la estera
//constituye nuestro recurso compartido
#task (rate=50ms, max=10ms)
void R_Despachador();
#task (rate=50ms, max=10ms)
void R_Llenador1();
#task (rate=50ms, max=10ms)
void R_Llenador2();
void main()
{
semaphore = 1; //Solo una tarea puede utilizar el recurso cada vez
iCantidad = 100; //Inicializamos esta variable para tener algunas botellas en
//la estera.
setup_timer_0(RTCC_INTERNAL|RTCC_DIV_1);
rtos_run();
}
void R_Despachador()
{
int Botellas;
rtos_wait(semaphore); //Reclamamos el recurso y aquí comienza la secc crítica
Botellas = iCantidad; //Leemos la cantidad de botellas a una variable temporal
if(input(PIN_B0)==1)
{
Botellas++; //Ya sabemos que este código no es eficiente pero
iCantidad = Botellas; //sí es didáctico y por eso lo he utilizado así.
}
rtos_signal(semaphore); //Liberamos el semáforo y aquí se acaba la sec crítica
rtos_yield(); // A dormir por otros 100ms para evitar poner dos veces la misma botella
}
void R_Llenador1()
{
rtos_wait(semaphore);
if(input(PIN_B1)==1)
iCantidad -= 12; //Este sí es un código lógico, pero entonces el despachador
//no nos daría problemas aunque nos vayamos a dormir dentro de
//la sección crítica.
rtos_signal(semaphore);
rtos_yield();
}
void R_Llenador2()
{
rtos_wait(semaphore);
if(input(PIN_B2)==1)
iCantidad -= 12;
rtos_signal(semaphore);
rtos_yield();
}
Este programa lo simulé en Proteus poniendo en RB0 una fuente digital de tipo pattern con los siguientes parámetros:
First Edge at (Secs)=1
Desmarcar el check mark:
Equal Mark/Space Timing?‘Mark’ Time (Secs) = 100m
‘Sapce’ Time(Secs) = 150m
Marcar el check mark:
Continuos Secuence of PulsesMarcar el check mark:
Standard Hig-Low Pulse TrainPara RB1 y RB2 se utiliza una fuente del mismo tipo con los siguientes parárametros cambiados
Para RB1:
First Edge at (Secs)=5
‘Mark’ Time (Secs) = 100m
‘Sapce’ Time(Secs) = 5.9
Para RB2:
First Edge at (Secs)=7
‘Mark’ Time (Secs) = 100m
‘Sapce’ Time(Secs) = 5.9
Simulen y vean como cada vez que se pasa por
rtos_wait() y si el semáforo es mayor que cero, se decrementa y se entra en la sección crítica, si el semáforo es 0 entonces la tarea espera a que esté disponible el recurso (semáforo>0) para ejecutar el código de la sección crítica.
Espero que lo disfruten.
Saludos Reinier