Está en la página 1de 27

SISTEMAS EN TIEMPO REAL

ADA en Tiempo Real


Introducción
Un sistema en Tiempo Real posee muchas características especiales. Claramente, no todos los
sistemas en Tiempo Real exhiben todas esas características. Sin embargo, cualquier lenguaje
de propósito general (o sistema operativo) que es usado para la programación efectiva de
sistemas en Tiempo Real deben tener facilidades que soporten esas características. Se pueden
enumerar las siguientes características:
Largos y Complejos: se dice que la mayoría de los problemas asociados con el
desarrollo de software son aquellos relacionados con el tamaño y la complejidad. La escritura
de programas pequeños no presentan problemas significativos y pueden ser diseñados,
codificados, mantenidos y entendidos por una sola persona.
Desafortunadamente, no todos los software muestran esta característica conveniente de
pequeñez. Intentando caracterizar a los sistemas largos, se rechaza la noción simple, y mas
aun intuitiva, que lo extenso del sistema es simplemente proporcional al numero de
instrucciones, líneas de código o módulos comprendidos en un programa. En lugar de
relacionar la longitud a variedad, y el grado de longitud a la cantidad de variedad. Indicadores
tradicionales como el numero de instrucciones y el esfuerzo de desarrollo, son entonces
síntomas exactos de variedad.
Los sistemas embebidos por esta definición pueden responder a eventos del mundo real. La
variedad asociada con estos eventos deben ser abastecidos por ellos; los programas tienden a
mostrar propiedades indeseables de longitud. Sobre la definición de longitud es inherente la
noción de cambio continuo. El costo de rediseñar y re-escribir software para responder a los
requerimientos de cambio continuo del mundo real es prohibitivo.
Manipulación de números reales: muchos de los sistemas de tiempo real involucran
el control de varias actividades de ingeniería.
Extremadamente confiable y seguro: la falla de un sistema involucrado por una
transferencia automática de fondos entre bancos puede llevar a una perdida irremediable de
millones de dólares; un componente fallado en la generación de electricidad puede ocasionar
una falla de un sistema vital de mantenimiento de vida en una unidad de cuidados intensivos;
una suspensión prematura de una planta química puede causar un daño costoso de
equipamiento o dañar el ambiente. Estos son algunos dramáticos ejemplos que ilustran que el
software y hardware de computadoras deben ser confiables y seguros. En el futuro donde se
requiera la interaccion del operador, se debe tener mucho cuidado en el diseño de la intrfaz en
realción a minimizar la posibilidad del error humano.
Facilidades de Tiempo Real: el tiempo de respuesta es crucial en cualquier sistema
embebido. Desafortunadamente, es muy difícil diseñar e implementar sistemas tales que
garanticen que la salida apropiada puede ser generada en un tiempo apropiado bajo todas las
condiciones posibles. Por esto y para hacer uso de todos los recursos de computación todo el
tiempo, es casi imposible. Por esta razón, los sistemas en tiempo real son construidos
usualmente usando procesadores con un considerable ahorro de capacidad, con lo cual se
asegura que no se produce ninguna inesperada interrupción durante periodos críticos de la
operación del sistema.
Interacción con interfaces de hardware: la naturaleza de los sistemas embebidos
requiere componentes de computación para interactuar con el mundo externo. Ellos necesitan
de sensores de monitor y actores de control para una gran variedad de dispositivos del mundo
real. Estas interfaces de dispositivos para los registros de entrada y salida vía computadora, y
los requerimientos operacionales dependen de la computadora y el dispositivo.

Ada es un lenguaje que esta orientado para muchos aspectos importantes de sistemas
prácticos en el mundo real. Algunas de las características del lenguaje son:

Página 1 de 27
SISTEMAS EN TIEMPO REAL

1. Confiabilidad y Tolerancia a Fallas


Confiabilidad y seguridad son dos de los temas más importantes en las aplicaciones de
Tiempo Real. Si un programa convencional falla, tal vez sea suficiente con abortarlo. En los
STR, tal vez esta acción no sea tolerable. Si un proceso encargado de controlar el
funcionamiento de un horno a gas falla, no es posible “bajar” el sistema drásticamente. En su
lugar, es necesario proveer de una forma de funcionamiento “degradado” del sistema de
manera de evitar dejar totalmente inoperable el horno con el considerable costo que esta tarea
implica. Actualmente, los STR reemplazan una gran parte de las funciones de control que antes
eran realizadas por humanos.

En general hay cuatro fuentes de fallas:


 Especificación inadecuada
 Errores de diseño de las componentes de software
 Fallas de hardware
 Fallas permanente o pasajera del sistema de comunicaciones

Se puede definir a la confiabilidad, como la medida de éxito con la cual el sistema


cumple con su especificación. Esta especificación debería ser: consistente, comprensible y no
ambigua. Es importante notar que el tiempo de respuesta es una parte importante de la
especificación. Cuando el comportamiento del sistema se desvía de dicha especificación se
dice que el programa falla.

Los problemas internos del sistema son los que dan lugar a las fallas, a estos
problemas los llamaremos errores. Un componente fallido del sistema será aquel que en algún
momento de su vista caerá en un error.

Pueden distinguirse tres tipos de fallas


 Pasajeras: aparece en el sistema en un determinado momento, permanece un cierto
período de tiempo y luego desaparece. Ej: interferencia provocada sobre el sistema por
algún campo eléctrica. En general esto ocurre en los sistemas de comunicaciones.
 Permanentes: Aparecen en un determinado período de tiempo y permanecen en el sistema
hasta su reparación. Ej: problema de hardware o error en el diseño de software.
 Intermitentes: Son fallas pasajeras que ocurren de tanto en tanto. Por ej: componente de
hardware sensible al calor

Modos de fallas:
Un sistema provee servicios, de donde es posible clasificar los modelos de falla de un sistema
según el impacto que tiene sobre los servicios que presta.
Puede identificarse dos dominios de fallas:
 Fallas por valor: el valor retornado es un error ya sea porque es incorrecto o porque está
fuera del rango esperado
 Falla por tiempo: el servicio es entregado fuera de tiempo. Puede ser: demasiado temprano,
demasiado tarde o infinitamente tarde (nunca)

Las combinaciones de valor y tiempo son llamadas fallas arbitrarias. En ocasiones, es


entregado un valor no esperado, esto se conoce como impromptu o comisión (error de
encargo). Note que es difícil detectar en un sistema una falla de tiempo donde el valor nunca
llega seguida de otra donde el valor es entregado sin haber sido pedido.

En base a lo anterior puede analizarse como un sistema puede fallar:


 Falla descontrolada: el sistema produce errores arbitrarios
 Falla tardía: el sistema produce valores correctos pero fuera de tiempo

Página 2 de 27
SISTEMAS EN TIEMPO REAL

 Falla silenciosa: los valores dados por el sistema son correctos pero a veces aparece una
falla por omisión
 Falla de detención igual que el anterior en el sentido que entra en “silencio” pero permite
que los demás sistemas lo detecten.
 Nunca falla: un sistema que produce resultados correctos en valor y en tiempo.

Prevención de fallas y tolerancia a fallas


Hay dos enfoques que pueden usarse para manejar fallas.
El primero es conocido como prevención de fallas que trata de evitar la falla antes que se
produzca. El segundo es la tolerancia a fallas donde el sistema continua funcionando aún ante
la presencia de fallas.

1. Prevención de fallas:
Hay dos estados posibles:
a) Evitar la falla: intenta evitar la introducción de fallas potenciales durante la construcción del
sistema.
Desde el punto de vista del hardware esto puede conseguirse mediante
 el uso de componentes más confiables, dentro de las restricciones de costo y
performance dadas.
 Uso de técnica adecuadas para la interconexión de componentes
 Aislar los componentes de la interferencias externas

Desde el punto de vista del software no se produce deterioro físico, de donde la prevención
tiene más que ver con las etapas de diseño. Con este fin deben utilizarse:
 Especificación de requerimientos rigurosa (lo más formal posible)
 Uso de metodologías de diseño probadas
 Uso de lenguajes con facilidades de abstracción y modularización
 Uso de ambientes de ingeniería de software que ayuden a manejar los componentes y
la complejidad del sistema

b) Remoción (remover) de la falla: Pese al uso de herramientas de software que permiten


rever el sistema, aparecen los siguientes problemas:
 Todo test sirve para demostrar la existencia de una falla, no su ausencia
 Por lo general no es posible medir el sistema en condiciones reales , por lo general se
emplean simuladores que no siempre abarcan todas las posibilidades.
 Los errores introducidos en la definición de los requerimientos no será detectado hasta
que el sistema no esté en marcha

Pese a todos los testeos, es factible que el sistema no permita su detención hasta que la falla
sea reparada. Es necesario entonces pensar en otras alternativas.

2. Tolerancia a fallas
Pueden proveerse varios niveles de tolerancia a fallas:
 Tolerancia a fallas completa: el sistema sigue operando en presencia del error sin una
pérdida significativa de su funcionalidad.
 Degradación leve (graciosa): el sistema sigue funcionando degradado hasta su reparación
 Falla segura: el sistema mantiene su integridad y aborta su funcionamiento en forma
temporaria.
El nivel de tolerancia a falla requerido dependerá de la aplicación

3. Redundancia
Las técnicas para lograr tolerancia a fallas se basan en elementos extra introducidos al sistema
para detectar y recuperar errores.

Página 3 de 27
SISTEMAS EN TIEMPO REAL

Son redundantes en el sentido de que no son necesarios para el funcionamiento del sistema.
Esto se denomina redundancia proteccionista. Se busca minimizar la redundancia y maximizar
la confiabilidad sujeto a las restricciones de costo y tamaño del sistema. Es importante ver que
la redundancia incrementa la complejidad del sistema.
En hardware
 Redundancia estática (masking): se replica el mismo componente y se toma por ejemplo el
dato de la mayoría. El que es distinto es enmascarado.

 Redundancia dinámica: La redundancia está dentro del componente el cual indica explícita
o implícitamente que se trata de un error, de donde la recuperación debe ser provista por
otro componente. Ej: chequeo de errores en la comunicación

En software:
N-versión Programming: Se basa en el éxito de la redundancia por hardware. Dado que el
software no se deteriora apunta a la detección de falla de diseño. N-version programming es la
generación de N versiones de programas funcionalmente equivalentes, independientes entre sí,
con N >= 2, para la misma especificación inicial.
Los programas se ejecutan concurrentemente y sus resultados son comparados por un
manejador de procesos. Si hay diferencias en el resultado se toma uno consensuado
(suponiendo que hay mas de 1).

Se supone que los programas podrían fallar independientemente (esto puede no cumplirse si
las versiones están escritas en un mismo lenguaje y la falla está asociada con él). Una manera
de asegurar la independencia de los errores es el uso de diferentes lenguajes y si se usa el
mismo se sugiere el uso de diferentes compiladores (compiladores del mismo lenguaje de
diferentes fabricantes). Las N versiones deberían estar en máquinas separadas comunicadas
mediante líneas tolerantes a fallas.

El manejador de procesos es responsable de


 Invocar a cada versión
 Esperar a que todas se completen
 Comparar y actuar según los resultados

Cada proceso debe poder comunicarse con el manejador durante su ejecución de manera de
poder comunicar problemas. Ej: no va a terminar.

La comunicación entre el proceso y el manejador está dada por el lenguaje usado y su forma
de concurrencia

La interacción consiste de tres componentes:


 Vectores de comparación: estructuras de datos con los valores de salida
 Indicador de estado de comparación: Es la respuesta del manejador a cada proceso. Les
puede decir: continuar, terminar o continuar después de cambiar su resultado por el de la
mayoría
 Puntos de comparación: donde cada proceso debe comunicarse con el manejador.

4. Redundancia de software dinámica:


Con redundancia dinámica, los componentes sólo operan cuando el error ha sido detectado.
Tiene cuatro fases
1) Detección del error
2) Aislamiento del error: es preciso determinar el grado de corrupción del sistema
(diagnóstico del error)
3) Recuperación del error: se debe transformar el estado corrupto en uno que continúe
funcionando normalmente (aunque degradado)

Página 4 de 27
SISTEMAS EN TIEMPO REAL

4) Tratamiento del error: aunque el daño haya sido reparado, el error aún podría existir, de
donde, debe tomarse alguna acción.

1) Hay dos clases para detectar errores:


Detección por el entorno: son detectados en el entorno en el que el programa se ejecuta.
Ej: overflow, violación de protección. También incluye el software de run-time. Ej: una
referencia a un puntero nulo o un valor fuera de rango.

Detección por la aplicación: son detectados por la aplicación en sí misma. Ej: Chequeo
de tiempo: ver que la respuesta es obtenida en un plazo determinado. Es importante ver
que esto no garantiza que la respuesta sea correcta. Otro Ej: códigos de detección de
errores

2) Aislamiento del error


Esto está relacionado con la idea de estructurar el sistema de manera de minimizar el daño
causado por una componente fallido.
Hay dos técnicas que pueden utilizarse en este sentido: descomposición modular y acciones
atómicas.
Mediante la descomposición modular el sistema estará dividido en componentes cada uno
representado por uno o más módulos. La interacción entre los componentes ocurrirá a través
de una interfaz bien definida y los detalles internos será ocultados. Esto evita que un error pase
indiscriminadamente de un componente a otro.

En cuanto a las acciones atómicas, permiten realizar una acción sin interacción con el sistema.
Es decir desde afuera se la ve como una acción indivisible. Esto permite proteger recursos, de
manera que cada vez que un proceso quiera acceder aun recurso debe perdir permiso.

3) Recuperación del error:


Esta es la fase más importante de la recuperación de fallas. La idea es convertir una situación
de error en una situación donde el sistema pueda seguir funcionando.

Hay dos formas de recuperación: forward y backward

Forward: intenta continuar la ejecución haciendo correcciones selectivas sobre el estado


actual. Esto implica que debe ser posible hacer una predicción de las causas y la posición del
error. Ejemplo de esta situación son los punteros redundantes a estructuras de datos o los
códigos de corrección de errores como los de Hamming.

Backware: estas técnicas buscan llevar al sistema a un estado seguro previo. Esto implica la
ejecución de otra parte del código, es similar al N-version programming, con la idea que esta
nueva versión no provoque errores. El punto al cual es recuperado el proceso se llama punto
de recuperación y el acto de establecer esto es llamado punto de chequeo. Para establecer
el punto de recuperación es necesario salvar el estado del sistema en ejecución. Es importante
notar que si bien el sistema retorna aun estado consistente, no es posible cambiar las acciones
que la falla haya podido tener sobre el entorno.

Si los procesos son concurrentes puede ocurrir un efecto dominó donde el roll back de uno
lleve a que el otro también se tenga que recuperar. En la figura, si P2 tiene un error después de
R22 y de que la comunicación IPC4 se realice, llevará a que P1 retorne a R12 y eso comienza
a desandar todas las comunicaciones llevando a ambos procesos al comienzo. Esto se conoce
como efecto dominó.

Obviamente, si no hay interacción de procesos, no habrá efecto dominó. Mientras mas


procesos interactúen mayor posibilidad habrá de que este efecto ocurra. Por esto, los puntos

Página 5 de 27
SISTEMAS EN TIEMPO REAL

de recuperación deben ser seleccionado de forma que no resulte en un roll back de todos los
procesos en lugar de llevar al sistema a un estado consistente.
5. Redundancia dinámica y excepciones
Una excepción se define como la ocurrencia de un error. El traer la excepción para que se
trate es lo que se conoce como raising. La respuesta del invocador es llamada catching. El
manejo de excepciones puede ser considerado una recuperación de error hacia delante, ya que
cuando una excepción se activa, el sistema no vuelve a un estado anterior sino que el control
se pasa al manejador. Sin embargo, puede usarse un manejador de excepciones para proveer
recuperación hacia atrás como veremos más adelante.

La excepciones fueron incorporadas en los lenguajes como forma de manejar condiciones


anormales dadas por el entorno donde el programa se ejecuta. Sin embargo, son usadas para
proveer un mecanismo para tolerancia a fallas.

2. Manejo de excepciones en Ada

Ada define a las excepciones como una forma de solucionar de manera consistente las
condiciones de error. Es decir que las excepciones solo se utilizan para manejar situaciones de
anormalidad. Este lenguaje define cinco excepciones predefinidas que son las que tratan los
errores relacionados con el lenguaje y que son útiles sobre todo en la etapa del debugging:
a. Numeric_error: es la que ocurre al intentar realizar una operación numérica invalida,
como puede ser una división por cero o un overflow. El compilador es el encargado de
levantar la excepciona.
b. Constraint_error: se activa cuando se viola alguna restricción, como por ej. rangos,
índice de un arreglo o condiciones discriminantes.
c. Program_error: atrapa todos los errores semánticos, como por ej. ninguna de las
alternativas del select está habilitada o hay variables no inicializadas.
d. Storage_error: se levanta si no hay espacio suficiente para satisfacer las necesidades
del programa en ejecución.
e. Tasking_error: se levanta si se presenta un problema en la comunicación entre dos
tareas; por ej. una tarea llama a otra que no la va a atender.

El manejador de excepción indica la acción a ser tomada cuando la excepción ocurra. Un


manejador de excepción puede aparecer al final de una de las siguientes unidades de
programa: un bloque begin-end, el cuerpo de un subprograma, un package o un task. Por ej:
With Gnat.IO;
Use Gnat.IO;
Procedure Pr_excel is
Num : integer range 1..20;
Begin
Put (“Comienzo”);
New_line;
Num := 43;
Num := 10;
Exception
When Constraint_error =>
Put (“Error de restriccion”);
When others =>
Put (“Otras excepciones”);
End Pr_excel;

La propagación de una excepción se efectúa en el caso que, si ocurre que la unidad que
levanta la excepción no contiene un manejador de excepciones, dicha excepción se propaga al
modulo que la llamó. Por ej.:

Página 6 de 27
SISTEMAS EN TIEMPO REAL

With Gnat.IO;
Use Gnat.IO;
Procedure Propagacion is
Procedure raise_exc is
Begin
Put (“ raise_exc”);
Raise Constraint_error;
- no hay manejador, se propaga al llamador
end raise_exc;
procedure Llamador is
begin
Put (“Llamador”);
Raise_exc;
Put (“Llamador despues de la excepcion”);
Exception
When Constraint_error =>
Put (“ Constraint_error en llamador”);
End;
Begin
Put (“ Principal llamando a llamador”);
Llamador;
Put (“ Principal retornando de llamador”);
Exception
When Constraint_error =>
Put (“ Constraint_error en el principal”);
End Propagacion;

Además es posible indicar bloques de programa, cada uno con su propio manejador de manera
que se active el que corresponde.

Declaración de excepciones: así como existen identificadores predefinidos y creados por el


usuarios, para las excepciones también hay, como ya se dijo anteriormente, excepciones
predefinidas, pero el usuario también esta habilitado para definir sus propias excepciones. Por
ej:
Tabla_llena : exception
Dato_ilegal : exception

El alcance que tiene la excepción es el mismo que el dado para cualquier identificador. Si
analizamos el siguiente ejemplo:

With Gnat.IO;
Use Gnat.IO;
Procedure demo_alcance is
E: exception;

Procedure p is
Begin
Raise e; { levanta demo_alcance.e }
End;
Procedure q is
E : exception ;
Begin
P;
Exception

Página 7 de 27
SISTEMAS EN TIEMPO REAL

When e => { representa a q.e (local) }


Put (“Manejador para demo_alcance.e”);
New_line;
End;
Begin
Q;
Exception
When e => { referencia la primera declaracion (tercer
linea)
Put (“manejador para demo_alcance.e”);
New_line;
End demo_alcance;

El proceso demo_alcance llama a q y este a p. En p se produce la excepción pero como no


tiene manejador, se la devuelve a q. En q sí hay un manejador, pero el nombre referido como e
desde q, no es el mismo que desde p, entonces se devuelve la excepción al modulo anterior.
Allí encuentra a e que coincide con la llamada de p y es ejecutado imprimiendo “manejador
para demo_alcance.e”.
Esto demuestra la importancia de definir, en lo posible, manejadores con nombres únicos.

Dificultades con el modelo de excepciones de Ada


1. Excepciones y package: no hay forma de testear por compilación que cada proceso
llama a las excepciones que corresponden. Por ejemplo, en el caso de la pila, la
excepción Stack_Full es levantada por Pop y no por Push. Esto no queda claro y es
más, lo único que el programador puede hacer para clarificar el código es utilizar
comentarios.
2. Pasaje de parámetros: el único parámetro factible es un string. Hay ocasiones donde es
necesario pasar objetos. Esto no es posible.
3. Alcance y propagación: es posible propagar excepciones fuera de su alcance que solo
son atrapadas usando when others. El problema está en que podrían volver a estar en
el alcance si son llamadas nuevamente.

3. Programación concurrente en Ada


En los lenguajes de programación secuenciales existen un único hilo de ejecución. El
camino dentro del programa puede diferir según los datos de entrada pero para cualquier
ejecución hay un único camino. Esto no es recomendable en STR.
Un programa concurrente puede verse como una colección de procesos secuenciales
autónomos, ejecutándose en paralelo. Cada proceso en sí mismo tiene su propio hilo de
ejecución.
Los procesos pueden ser: múltiples ejecuciones sobre un único procesador, múltiples
procesos sobre muchos procesadores compartiendo memoria y múltiples procesos en
diferentes procesadores cada uno con su memoria local (sistema distribuido).
Solo en los últimos casos se da el paralelismo real. Concurrencia indica un paralelismo
potencial. Los lenguajes concurrentes habilitan al programador a expresar actividades
paralelas.
Si bien es posible utilizar un lenguaje de programación secuencial y un sistema operativo
multitarea, hay lenguajes que permiten implementar el paralelismo.
Ha existido una larga discusión a cerca de donde debe implementarse la concurrencia, si
en los SO o en los lenguajes.
Los argumentos a favor de los lenguajes de programación son los siguientes:
- Permite obtener programas más confiables y fáciles de mantener.
- Hay muchos SO, por lo tanto si la concurrencia está en el lenguaje, el programa es más
portable.

Página 8 de 27
SISTEMAS EN TIEMPO REAL

- Un sistema embebido puede no contar con ningún SO.


- El compilador podría proveer un chequeo de la interacción entre los procesos.
Los argumentos en contra son:
- Diferentes lenguajes poseen diferentes modelos de concurrencia. Es fácil componer
programas de diferentes lenguajes si todos usan el mismo SO.
- Es difícil que el modelo de concurrencia de un lenguaje pueda ser más eficiente que el
de un SO.
- Comienzan a aparecer standards en SO y esto facilitará la portabilidad de los
programas.
En el momento en que se va a construir una ejecución concurrente, hay diferentes aspectos a
tener en cuenta para determinar el lenguaje a utilizar.
Si bien la noción de concurrencia es común en los lenguajes concurrentes, hay variaciones en
los modelos de concurrencia adoptados. Estas variaciones tienen que ver con:
1. Estructura, que puede se dinámica o estática, dependiendo si el numero de procesos es
conocido en tiempo de ejecución o fijado en la compilación, respectivamente.
2. Nivel de paralelismo soportado que puede ser anidado (un proceso puede contener a
otro proceso) o plano (todos los procesos al mismo nivel).
3. Granularidad, interesa - especialmente para el caso en que el nivel de paralelismo es
anidado -, si la granularidad es fina (muchos procesos, algunos para realizar una única
acción) o gruesa (pocos procesos, cada uno es significativo).
4. Inicialización: hay dos formas de inicializar el proceso, pasándole un parámetro, o
comunicándose explícitamente con él luego de que ha comenzado su ejecución.
5. Terminación: puede ocurrir que, el proceso ha completado su ejecución; el proceso se
auto-termina; aborta por una acción explicita de otro proceso; ocurre un error no
previsto; no termina nunca por haber entrado el proceso en un loop infinito; cuando ya
no es necesario, debido a que todos los procesos que dependían de él, han terminado y
no tienen que esperar a nadie más.
6. Representación: un proceso por lo general necesita distinguir entre su padre y su
guardián (es el encargado de su terminación). El guardián no puede terminar hasta que
todos los procesos que dependen de él lo hayan hecho.

Hay tres mecanismos básicos para representar la ejecución concurrente:


- Co-rutinas: son similares a las subrutinas pero el control no se pasa entre ellas en forma
jerárquica sino que cada una puede hacer un resume de manera de permitir que otra se
siga ejecutando. No es un mecanismo muy adecuado para STR.
- Fork y Join: esta construcción permite que un proceso ejecute una rutina en forma
concurrente.
- Cobegin/Coend: esta construcción permite notar la ejecución concurrente de un
conjunto de procesos.
Como se realiza la ejecución concurrente en Ada, se puede analizar con el ejemplo del brazo
del robot.
Procedure Main is
Type Dimension is (planox,planoy,planoz);
Task type Control (dim:dimension);

C1 : Control (planox);
C2 : Control (planoy);
C3 : Control (planoz);

Task body Control is


Position : Integer - posición absoluta
Mover : Integer;- posición relativa
Begin
Position := 0; - resetea la posición
Loop
New_setting (dim, mover);

Página 9 de 27
SISTEMAS EN TIEMPO REAL

Position := position + mover;


Mover_brazo (dim, position);
End loop
End Control;
Begin
Null;
End Main;

También pueden crearse procesos dinámicamente


Procedure ejemplo is
Task type T;
Type A is access T;
P : A;
Q : A := new T;
Begin
.......
P := new T;
Q := new T;
End ejmplo;

Vemos que hay cuatro hilos de ejecución simultáneos. La ventaja de estas declaraciones
dinámicas es que su guardián no es el bloque donde fueron creadas sino aquel que contiene la
declaración de la tarea.
Declare
Task type T;
Type A is access T;
Begin
.....
declare {bloque interior}
x : T;
y : A := new T;
begin
...... {secuencia de sentencia}
end; {debe esperar que x termine pero no y.all}
{Y podria estar activa aunque su nombre esta
fuera de rango}
......
end; {debe esperar a que y.all termine}

No es legal en Ada el asignar una tarea a otra estructura de datos aunque sean de igual tipo.
Por ejemplo: si Brazo_robot y Nuevo_brazo son dos punteros al mismo tipo de tarea, no es
legal hacer:
Brazo_Robot.all := Nuevo_Brazo.all

Sin embargo
Brazo_Robot := Nuevo_Brazo

Si lo es representa que Brazo_Robot ahora designa a la misma tarea que Nuevo_Brazo. Es


importante tener cuidado de no dejar tareas anónimas (aquella a la que antes apuntaba
Brazo_Robot).
La terminación de una tarea se puede dar porque: completa la ejecución de su cuerpo, ya sea
anormalmente o como resultado de una excepción sin manejador; si ejecuta un terminate de
una sentencia seleccionada; si aborta.

Página 10 de 27
SISTEMAS EN TIEMPO REAL

4. Sincronización y Comunicación
La mayor dificultad de la programación concurrente está en la interacción de los procesos. La
comunicación puede realizarse mediante:
Variables compartidas: en donde la comunicación se lleva a cabo cuando los procesos
referencian estas variables en el momento apropiado.
Pasaje de mensajes: implica un intercambio explícito de datos entre los dos procesos y
dicho cambio se lleva a cabo a través del mensaje.

 Basada en variables compartidas


Estas variables pueden utilizarse si hay memoria compartida entre los procesos. Existen
características principales para la sincronización y comunicación basada en variables
compartidas.
Exclusión mutua y condición de sincronización: La ejecución de dos procesos
simultáneos para resolver la misma instrucción podría dar lugar a una superposición de esas
operaciones. Una secuencia de instrucciones que deben ejecutarse indivisiblemente, se conoce
como región crítica; la sincronización requerida para proteger la región crítica se conoce como
exclusión mutua. La exclusión mutua no es la única

 Sincronización y Comunicación basada en Mensajes


Esta es la alternativa a la comunicación vía variable compartida. Utiliza un único constructor
para sincronización y comunicación. Sin embargo, hay diferentes soluciones propuestas que
varían en:
 El modelo de sincronización
 El método de nombrar los procesos
 La estructura del mensaje

1. Sincronización de Procesos

Hay una cuestión implícita en el mensaje. Un receptor no puede recibir un mensaje que no
haya sido enviado. A diferencia de la variable compartida donde es posible leer el valor de la
variable sin saber si ha sido escrita previamente. En el caso de mensajes, el pretender leer un
mensaje que aun no ha llegado producirá que el proceso que quiere leer quede suspendido
hasta que el mensaje llegue.

La variación en el modelo de sincronización puede clasificarse según la forma en que el


mensaje se envía:
 Asincrónico (no esperar): el mensaje es enviado sin ver si el receptor lo está
esperando o no. Funciona como el correo, cuando el emisor manda la carta sigue con
su vida normal, solo recibiendo otra carta podrá saber sil a que mandó llegó o no.
Desde el punto de vista del que recibe la carta, esta informa a cerca de un evento que
ya pasó, no dice nada acerca del emisor ahora.
 Sincrónico: el emisor continúa sólo si el mensaje está siendo recibido (ej: CSP).
Funciona como el teléfono, el que envía espera a que el receptor atienda. Es lo que se
conoce como cita o rendevouz.
 Invocación remota: el emisor continua cuando recibe el replay del receptor (pide
respuesta), como por ejemplo Ada.

Existe una relación entre estas formas enviar un mensaje. Dos eventos asincrónicos se pueden
comportar como una comunicación sincrónica.

P1 P2

Página 11 de 27
SISTEMAS EN TIEMPO REAL

Send_asincrónico(mensaje) wait(mensaje)
Wait(reconocimiento) send_asincronico(reconocimiento)

Dos comunicaciones sincrónicas pueden ser usadas para construir una invocación remota.

P1 P2
Send_asincrónico(mensaje) wait(mensaje)
Wait(replay) ...
Construye el replay
...
send_asincronico(replay)

De lo anterior puede pensarse que la comunicación asincrónica es la forma más general de


comunicación ya que permite simular a las demás, sin embargo, existen algunos problemas en
su uso:
 Se necesitan buffers infinitos para procesar los mensajes que no han llegado (tal vez el
receptor ya haya terminado)
 Dado que la comunicación es asincrónica (fuera de fecha) los envíos son en general para
esperar un reconocimiento (se busca la comunicación sincrónica).
 La mayoría de las comunicaciones son sincrónicas y esta simulación complica el modelo.
 Es más difícil garantizar la corrección del sistema completo.

2. Nombrado de procesos

El esquema puede ser:


a) Directo: cuando el emisor nombre explícitamente al receptor

Enviar <mensaje> a <nombre del proceso>

b) Indirecto: el emisor utiliza una entidad intermediaria para enviar el mensaje


Enviar <mensaje> a <casilla de correo>

Aún en el caso b) el mensaje puede ser sincrónico, es decir, el emisor espera a que alguien lo
reciba.

El esquema de nombrado es simétrico si ambas partes se mencionan mutuamente (directa o


indirectamente)

Enviar <mensaje> a <nombre de proceso>


Wait <mensaje> de <nombre de proceso>

Senviar <mensaje> a <mailbox>


Wait <mensaje> de < mailbox>

El esquema es asimétrico si el receptor recibe mensajes de cualquiera, de la forma


Wait <mensaje>

3. Estructura del mensaje


Idealmente, cualquier tipo de objeto debería poder transmitirse en un mensaje. Esto no es tan
fácil si se trata, por ejemplo, de punteros. Si bien los lenguajes actuales han ampliado el
espectro (antes sólo se podía transmitir datos predefinidos), por lo general, los datos deben ser
convertidos de bytes para su transmisión.

4. Semántica del pasaje de mensaje de Ada y Occam

Página 12 de 27
SISTEMAS EN TIEMPO REAL

Ada y Occam utilizan el pasaje de mensajes tanto para comunicación como para
sincronización. Occam sólo soporta la comunicación por canal. En cambio Ada permite además
la comunicación vía memoria compartida y objetos protegidos.
 El modelo de Ada
La semántica del modelo remoto tiene mucho que ver con la llamadas a procesos, en el sentido
que el dato es pasado al receptor, este lo recibe, lo ejecuta y lo retorna.

Para poder recibir un mensaje, la tarea debe definir una entrada

Ej:
Task type Salida_pantalla(id:Identif_Pantalla) is
-- define un tipo de task
entry Escribir( valor: character; x_coord, y_Coord:Integer);
end Salida_Pantalla;

Display : Salida_Pantalla(Tty1);

Task horario is --define una única tarea


Entry Leer_Hora (ahora : out Time);
Entry set_Hora (New_Time : in Time);
End Horario;

Un entry puede tener parámetros in, out y in out. No puede tener resultado como una función,
sino que debe ser llamada como un procedimiento. El nombre de la tarea no puede aparecer
en una cláusula use de donde forzosamente debe ser nombrada en la invocación, de la forma
T.E(...) donde T es la tarea y E la entrada.

También es posible definir una familia de entradas:

Type Channel_Number is new Integer range 1..7;


Task Multiplexor is
Entry Channels(channel_Number)(Data:Input_Data);
End Multiplexor;

Lo anterior define siete entradas con la misma especificación para los parámetros. Para llamar
a uno de los canales es necesario poner
Multiplexor.Channels(3)(D)

Ejemplo:
with GNat.Io; Use Gnat.Io;
procedure VerLetras is
task P1 is
entry Mostrar(C : in character);
end P1;
task body P1 is
i : Integer range 1..10;
begin
for i in 1..10 loop
accept Mostrar(c : in character) do
-- lo muestra solo si es letra mayuscula
if C in 'A'..'Z' Then
Put(C);
end if;
end Mostrar;
end loop;

Página 13 de 27
SISTEMAS EN TIEMPO REAL

end P1;

task P2;
task body P2 is
letra : character;
n : Integer range 1..10;
begin
for n in 1..10 loop
Get(Letra);
P1.mostrar(Letra);
end loop;
end P2;
begin
null;
end VerLetras;

Manejo de excepciones y el rendezvous


Cualquier código Ada puede ejecutarse en un rendezvous, de donde si una excepción se
ejecuta:
 Existe un manejador válido dentro del accept que permite que termine normalmente.
 La excepción no tiene manejador dentro del accept y este es levantado inmediatamente.

Para ejemplificar la interacción entre el rendezvous y los modelos de excepción,


consideraremos una tarea que actúa como un servidor de archivo. Una de sus entradas podría
permitir a la tarea cliente abrir el archivo.
task File_Handler is
entry Open(F:File_Type);
...
end File_handler;

task body File_handler is


...
begin
loop
begin
...
accept Open (F:File_Type) do
loop
begin
Device_Open(F);
return;
exception
when Device_off_Line => Boot_Device;
end;
end loop;
end Open;
...
exception
when File_Does_Not_exist => null;
end loop;
end File_Handler;

En el código anterior File_Handler llama al manejador para abrir un archivo. Este requerimiento
puede ser exitoso o terminar con el levantamiento de una de dos excepciones Device_off_Line
o File_Does_Not_Exist. La primera excepción es manejada dentro del accept. Se hace un
intento para bootear el dispositivo y luego el requerimiento de apertura es repetido. Como el

Página 14 de 27
SISTEMAS EN TIEMPO REAL

manejador está dentro del accept, el cliente no advierte esta actividad (si el dispositivo no
bootea, esto quedaría indefinidamente suspendido). La segunda excepción ocurre por una falla
del requerimiento indicado por el usuario, de donde no se maneja dentro de la excepción y se
devuelve al usuario para que actúe. La acción a tomar será del tipo:

begin
File_Handler.Open(New_File);
exception
when File_Does_Not_Exist =>
File_Handler.Create(New_File);
File_Handler.Open(New_File);
end;

Relojes de tiempo real


Debe haber soporte de timing para la programación de entry calls condicionales y
timed usando el rendezvous de ADA, y para expresar eventos timed con la sentencia delay.
Esto asume que cada procesador tiene al menos un timer para medir tiempo relativo. El
principal problema al medir eventos timed entre procesadores distribuidos es la latencia no
determinística asociada con la comunicación inter procesador. Esto implica que debe haber un
reloj global para medir tiempo absoluto para sincronizar eventos inter procesador. La
sincronización de múltiples procesadores con un solo timer global puede no ser confiable para
STR "hard".

Para implementar en forma segura los entry calls condicionales y timed con el modelo
de rendezvous, se necesitan dos relojes: un timer de intervalo y un reloj de time-off-day. El
tiempo asociado con el comienzo del rendezvous puede ser medido en el procesador llamado.
El procesador que llama envía un mensaje al procesador remoto y espera un mensaje de
respuesta observando el estado del rendezvous. En el caso de un entry call timed, el proceso
llamador incluye el delay de tiempo en el mensaje.

Cuando el tiempo se mide en el procesador llamado, la latencia de comunicación no


entra en la determinación de "inmediatamente disponible" en el aso del entry call condicional.
Por supuesto hay overhead adicional por el pasaje de mensajes entre los dos procesadores.

Elaboración, activación y terminación de tareas


La elaboración, activación y terminación de tareas tiene una semántica especificada
en ADA. Las tareas no son activadas y terminadas explícitamente, excepto por el uso de la
sentencia abort. Las tareas son elaboradas en un cierto orden en un programa ADA,
dependiendo de la ubicación de sus declaraciones. Todas las unidades referenciadas por el
programa principal y sus cuerpos, son elaboradas antes de que el programa principal comience
a ejecutarse. Tan pronto como una tarea fue elaborada, es activada por el soporte de run-time y
comienza a ejecutarse. Cada tarea continúa ejecutándose hasta su terminación lógica o hasta
que es abortada por otra tarea (o por si misma). El programa principal no termina hasta que
todas las tareas que dependan de él hayan terminado.

Si nuestra aplicación consiste en múltiples programas ADA comunicantes, la activación


y terminación se da como si cada programa se ejecutara independientemente en un
uniprocesador. Cuando un único programa se distribuye en varios procesadores, el tema es
más complejo. Las imágenes ejecutables se cargan en cada procesador, y deben ser
elaboradas en algún orden. El procedimiento principal reside en uno de los procesadores y
puede ser considerada la tarea "environment".

Página 15 de 27
SISTEMAS EN TIEMPO REAL

El orden de elaboración de un sistema uniprocesador puede usarse en un sistema


multiprocesador. El orden comienza con la tarea environment y es dependiente de las
relaciones with de los NV. Cuando la función de elaboración para la tarea environment detecta
la importación de una referencia a NV remoto, detiene la elaboración y envía un mensaje a la
función de elaboración del procesador remoto, donde se desarrolla hasta completarse o hasta
detectar otra referencia remota. Cuando se completa, se envía un mensaje al procesador
environment. Esto continúa hasta que todos los NV fueron elaborados en el mismo orden que si
estuvieran en un solo procesador.

Espera selectiva
Los ejemplos anteriores son estáticos en cuanto al orden de ejecución de las entradas a las
tareas. Esto en realidad no es así, es necesario contar con la posibilidad de seleccionar de un
conjunto de entradas aquella que se presenta a fin de dar un servicio un poco más general.
Esto funciona como la Chance de CSP

Ejemplo:
with Gnat.Io; use Gnat.Io;
procedure mensaje is
subtype Line is String(1..100);
task Char_To_Line is
entry Agregar(C: in Character);
entry Sacar( L : out Line; N : out Natural );
end;
task body Char_To_Line is
MiLinea : Line;
Longitud : Natural := 0;
begin
loop
select
accept Agregar(C : in Character) do
Longitud := Longitud +1;
MiLinea(Longitud) := C;
end Agregar;
or
accept Sacar(L : out Line; N : out Natural) do
L := MiLinea;
N := Longitud;
end Sacar;
or
terminate;
end select;
end loop;
end Char_To_Line;
dato : Character;
Texto : Line;
cuantos : Natural;
begin
Get(dato);
while dato /= '.' loop
Char_To_line.Agregar(dato);
Get(Dato);
end loop;
Char_To_Line.Sacar(Texto, cuantos);

Put_Line(Texto(1..cuantos));

Página 16 de 27
SISTEMAS EN TIEMPO REAL

Put_line("Esperando que la tarea termine ");


end mensaje;

Note la opción de terminate para evitar que el proceso principal nunca termine. Este evento se
activa cuando el proceso llamador terminó y es un forma de que la tarea hija se entere de esta
situación.

 Procesos competitivos y cooperantes


La interacción fue descripta en término de tres tipos de comportamiento
 Independencia: los procesos no se relacionan. Esto implica que ante una falla la
recuperación puede hacerse en forma aislada.
 Cooperación: si se produce un error, todos los procesos involucrados en la tarea fallida
deben ser notificados. Es lo que veremos a continuación.
 Competencia: los procesos compiten por la obtención de recursos. Se verá con más
detalle más adelante.

5. Acciones atómicas
La programación concurrente permite representar de una forma más natural el funcionamiento
y la interacción de los distintos objetos del mundo real. Desafortunadamente, la concurrencia
trae nuevos problemas que no existían en la programación secuencial.

Para poder trabajar correctamente, un proceso necesita contar con actividades


individuales o acciones atómicas.

Propiedades de una acción atómica:


 Una acción es atómica si los procesos que la están llevando a cabo no advierten la
existencia de otros procesos
 Una acción es atómica si los procesos no se comunican con otros mientras la están
realizando.
 Una acción es atómica si los procesos que la realizan no notan más cambios que aquellos
realizados por sí mismos y si no revelan su estado de cambio hasta que la operación esté
completa.
 Una acción es atómica si puede verse como indivisible, como si fuera un interleaving de
procesos en lugar de procesos concurrentes.

La más fuerte de todas es la segunda ya que la interacción entre la acción atómica y su entorno
sólo se realiza si no interfiere con la operación indivisible.

1.1. Acción atómica de dos fases


Idealmente, la acción atómica debería tomar todos los recursos que necesite antes de
comenzar, de manera de no interactuar con el exterior mientras esté trabajando y debería
devolver todo al final, una vez cumplida la tarea.

Sin embargo, esto sólo se verifica en una situación ideal y se necesita una visión más práctica.
En primer lugar debería permitírsele a la acción atómica comenzar aunque no tenga todos sus
recursos, esto la puede llevar a pedir recursos durante su ejecución, es decir, debería
comunicarse con el administrador de recursos.
El administrador es sólo el encargado de verificar que el recurso sea utilizado adecuadamente
y no hace ninguna gestión sobre él. Sería deseable, además, que en cuanto los recursos ya no
sean necesarios puedan ser liberados en forma prematura por las acciones atómicas a fin de
agilizar el sistema completo.

Página 17 de 27
SISTEMAS EN TIEMPO REAL

Con esta idea y para proteger a la acción atómica, su alocación de recursos se divide en dos
fases, una primera fase de crecimiento donde sólo puede pedir recursos y luego un fase de
achicado donde sólo se le permite liberar recursos.
Es importante ver que la liberación temprana de recursos complica la recuperación de errores
en la acción atómica (el recurso puede haber sido modificado y liberado y luego se presenta el
error).

Asumiremos acciones atómicas de dos fases donde los recursos no se liberan hasta que la
acción no se haya completado.

Transacciones atómicas.
Una transacción atómica es una acción atómica a la que se le ha agregado una propiedad de
éxito o fracaso. Si fracasa se supone que el sistema debe retornar a un estado consistente. Es
decir al momento previo al comienzo de la transacción.
Este tema será analizado más adelante cuando se vean Bases de Datos Distribuidas.

Requerimientos para acciones atómicas


Los requerimientos para poder implementar acciones atómicas son:
Entorno bien definido: Cada acción debe tener un comienzo, un fin y un entorno. El
comienzo es la posición en cada proceso involucrado donde la acción debe comenzar.
El fin es la posición en cada proceso involucrado donde la acción debe terminar. El
entorno separa los procesos involucrados en la acción atómica del resto de los
procesos (los que no intervienen en ella).
Indivisibilidad: los procesos involucrados no pueden comunicarse con ningún otro fuera
del entorno (salvo el administrador de recursos). Si una acción atómica necesita
relacionarse con otra, la ejecución se realizara en secuencia (en algún orden), no en
paralelo. Los procesos involucrados no necesariamente ingresarán juntos pero seguro
sí saldrán juntos (ninguno se va hasta que no se vayan todos).
Anidamiento: En general sólo se permite el anidamiento estricto (una acción atómica
contenida estrictamente dentro de otra).
Concurrencia: debe ser posible ejecutar acciones atómicas en forma concurrente (siempre
que su resultado no sea diferente del que se obtendría de su ejecución secuencial).

Acciones atómicas en lenguajes concurrentes


Se propone un lenguaje marco y se extiende para proveer recuperación de errores (en ambas
direcciones hacia atrás y hacia adelante)

Semáforos:
Un acción atómica realizada por un único proceso puede implementarse por exclusión mutua
mediante un semáforo binario:

Wait(semaforo);
Accion_atómica;
Signal(semaforo);

Esto se complica si más de un proceso se encuentra involucrado.

Supongamos que dos procesos intervienen en la misma acción atómica y ambos utilizan un
recurso que no puede compartirse. Para manejar la acción atómica se utilizan 4 semáforos:
Los semáforos inicio_accion_atomica1,   inicio_accion_atomica2 aseguran que
sólo dos procesos ingresen a la acción. Cualquier otro que lo intente será bloqueado.
Los semáforos termino_accion_atomica1, termino_accion_atomica2 aseguran que
no se pueden ir sin que el otro termine.
Note que se necesita otro semáforo para gestionar la exclusión mutua al recurso.

Página 18 de 27
SISTEMAS EN TIEMPO REAL

Inicio_accion_atomica1, inicio_accion_atomica2 : semaphore := 1;
Termino_accion_atomica1,termino_accion_atomica2: semaphore := 0;

procedure codigo_primer_proceso is
begin
­­ Inicia la accion atomica

wait( inicio_accion_atomica1);

­­ acceder al recurso en modo no­compartido
­­ actualizar el recurso
­­ indicar al segundo proceso que puede acceder al recurso
­­ cualquier procesamiento final de la accion 

wait( termino_accion_atomica2);

­­ devolver el recurso

signal( termino_accion_atomica1);
signal( inicio_accion_atomica1);
end codigo_primer_proceso;

procedure codigo_segundo_proceso is
begin
­­ Inicia la accion atomica

wait( inicio_accion_atomica2);

­­ procesamiento inicial
­­ esperar a aque el primer proceso avise que se puede usar
el recurso
­­ acceder al recurso 

signal( termino_accion_atomica2);

wait( termino_accion_atomica1);

signal( inicio_accion_atomica2);
end codigo_segundo_proceso;

El ejemplo anterior muestra que los semáforos permiten resolver muchos de los problemas de
sincronización, pero presentan los siguientes problemas:
Sólo dos procesos pueden entrar, pero no asegura que son los dos que deberían entrar
La estructura wait-signal ya fue criticada en capítulos anteriores
No es fácil de extender a N procesos.

Monitores:
El encapsular la acción atómica en un monitor asegura que las ejecuciones parciales sean
verificadas.

A continuación se muestra el mismo ejemplo implementado con un monitor:

monitor accion_atomica
export codigo_primer_proceso, codigo_segundo_proceso;

Página 19 de 27
SISTEMAS EN TIEMPO REAL

primer_proceso_activo  : boolean := false;
segundo_proceso_activo  : boolean := false;
primer_proceso_termino  : boolean := false;
segundo_proceso_termino  : boolean := false;
no_primer_proceso, no_segundo_proceso : condition;
fin_accion_atomica1, fin_accion_atomica2 : condition;

procedure codigo_primer_proceso
begin
if primer_proceso_activo then wait( no_primer_proceso);
primer_proceso_activo := true;

­­ acceder al recurso en modo no compartido
­­ actualizar el recurso
­­ indicar al segundo proceso que puede acceder al recurso
(enviar signal)
­­ cualquier proceamiento final
if not segundo_proceso_termino then
wait(fin_accion_atomica2);
primer_proceso_termino := true;

­­ liberar el recurso
signal( fin_accion_atomica1);
primer_proceso_activo := false;
signal(no_primer_proceso);
end;

procedure codigo_segundo_proceso
begin
if segundo_proceso_activo then wait( no_segundo_proceso);
segundo_proceso_activo := true;

­­ procesamiento inicial
­­ esperar que el primer proceso indique que el recurso está
disponible
­­ acceder al recurso
signal( fin_accion_atomica2);
segundo_proceso_termino := true;

if   not   primer_proceso_termino   then


wait(fin_accion_atomica1);

segundo_proceso_activo := false;
signal(no_segundo_proceso);
end;

Muchas de las limitaciones de la solución anterior podrían solucionarse usando el monitor como
un controlador de acciones y realizando las acciones de forma externa al monitor.

Acciones atómicas en Ada:

Página 20 de 27
SISTEMAS EN TIEMPO REAL

En aquellos lenguajes cuyas primitivas de sincronización y comunicación se basan en pasaje


de mensajes aislados, todas las acciones de procesos simples son atómicas siempre que no
haya variables compartidas y los procesos en si mismos no se comuniquen durante la acción.
La acción atómica toma la forma de una sentencia accept y posee sincronización atómica
siempre que:
No actualice ninguna variable que otra tarea pueda acceder
No realice ningún rendezvous con otra tarea.

Una acción atómica que involucre tres tareas podría implementarse en Ada mediante
rendezvous anidados, pero esto impediría el paralelismo dentro de la acción.

Una alternativa es crear un controlador de acción. A continuación se muestra la implementación


utilizando un objeto protegido:

package Action_X is
procedure Codigo_primer_tarea( --params );
procedure Codigo_segunda_tarea( --params );
procedure Codigo_tercer_tarea( --params );
end Action_X;

package body Action_X is


protected Action_Controller is
entry primero;
entry segundo;
entry tercero;
entry fin;
private
Primero_aqui : Boolean := False;
Segundo_aqui : Boolean := False;
Tercero_aqui : Boolean := False;
Liberando : Boolean := False;
end Action_Controller;
protected body Action_Controller is
entry primero when not primero_Aqui is
begin
Primero_aqui := True;
end primero;
entry segundo when not segundo_aqui is
begin
Segundo_aqui := true;
end segundo;
entry tercero when not tercero_aqui is
begin
tercero_aqui := true;
end;
entry fin when liberando or fin'Count = 3 is
begin
if fin'count = 0 Then
liberando := false;
primero_aqui := false;
segundo_aqui := false;
tercero_aqui := false;
else
liberando := true;
end if;
end fin;

Página 21 de 27
SISTEMAS EN TIEMPO REAL

end Action_Controller;

procedure Codigo_primer_tarea( -- params ) is


begin
Action_Controller.Primero;
-- adquirir recursos
-- realizar la acción
-- ejecutra la acción utilizando los recursos
Action_Controller.Fin;
-- liberar los recursos;
end Codigo_primer_tarea;

-- código simiar para la segunda y tercer tarea.


begin
-- cualquier inicialización de los recursos
end Action_X;

En el ejemplo anterior, la acción se sincroniza mediante el objeto protegido. Esto asegura que
sólo tres tareas pueden entrar y que sincronicen su salida.

Un lenguaje marco para acciones atómicas:


Si bien hasta ahora hemos especificado acciones atómicas, aun se depende de la disciplina del
programador para asegurar que no existe interacción con procesos externos. Además, se
asume que ningún proceso dentro de la acción atómica es abortado (la acción podría quedar
en un estado inconsistente).
Se discutirá un lenguaje marco que facilite la recuperación de errores y el mecanismo de
recuperación será discutido en este contexto.

Por simplicidad, sólo se considerarán procesos estáticos. También se asumirá que todos los
procesos que intervienen en la acción atómica son conocidos en compilación.

Cada proceso involucrado en la acción declara una sentencia action que especifica: nombre
de la acción, nombre del otro proceso que interviene en la acción y el nombre del proceso a ser
ejecutado al entrar a la acción.
Ej: el proceso P1 desea ejecutar la acción atómica A junto con los procesos P2 y P3. Se
declara entonces lo siguiente:

action A with (P2,P3) do
­­ adquirir los recurso
­­ comunicación con P2 y P3
­­ liberar los recursos
end A;

Se asume que los alocadores de recursos son conocidos y que la comunicación dentro de la
acción se limita a los tres procesos. Esto se chequea en compilación. Las acciones anidadas
serán permitidas siempre que el anidamiento sea estricto. Note que si los procesos no son
conocidos en compilación cualquier comunicación será permitida siempre que ambos procesos
se encuentren activos en la misma acción atómica.

La sincronización se realiza de la siguiente forma:


 Los procesos que entran a la acción no son bloqueados.
 Un proceso es bloqueado dentro de la acción sólo si éste tiene que esperar por un recurso
o si necesita comunicarse con otro proceso dentro de la acción y ese proceso está activo
en la acción pero no en posición de aceptar la comunicación o ya no está activo.

Página 22 de 27
SISTEMAS EN TIEMPO REAL

Los procesos de la acción sólo pueden salir cuando todos desean hacerlo. Esto implica que
todos los procesos deben entrar a la acción para que los que entraron puedan salir. En algunos
casos es importante que los procesos que hayan entrado puedan salir aunque otros no hayan
llegado.

Acciones atómicas y recuperación de errores hacia atrás:

Las acciones atómicas restringen el flujo de información a un entorno bien definido y por lo
tanto pueden proveer la base tanto para aislar como para recuperar el error.
Las acciones atómicas proveen una línea de recuperación automática evitando de esta forma el
"efecto dominó" visto anteriormente. Si un error se presenta dentro de una acción atómica,
todos los procesos involucrados son llevados al momento en que comenzó la acción y luego
ejecutar un proceso alternativo; la acción atómica asegura que ningún valor erróneo ha sido
transmitido a otros procesos. Las acciones atómicas utilizadas de esta forma se denominan
conversaciones.

3.1. Conversaciones:
En las conversaciones cada acción tiene un bloque de recuperación. Una sintaxis podría ser la
siguiente:

action A with (P2, P3) do


ensure <acceptance test>
by
-- modulo primario
else by
-- modulo alternativo
else by
-- modulo alternativo
else error
end A;

Todos los módulos involucrados en la conversación declaran su parte en la acción de la misma


forma.

Semántica de una conversación:


 El estado del proceso se salva en el momento de entrar en la conversación. El conjunto de
puntos de entrada forma la línea de recuperación.
 Una vez dentro de la conversación el proceso sólo puede comunicarse con otros que se
encuentren activos en la conversación (generalmente manejadores de recursos). Como las
conversaciones se implementan mediante acciones atómicas, esto se hereda.
 Para poder mantener la conversación todos los procesos deben haber pasado su test de
aceptación. En este caso, cuando la conversación termina, todos los puntos de
recuperación son descartados.
 Si alguno de los procesos falla en su test de aceptación, todos los procesos son retornados
al correspondiente estado (salvado antes de iniciar la conversación) y cada uno ejecutará
su módulo alternativo.
 Las conversaciones pueden ser anidadas pero sólo estrictamente.
 Si todas las alternativas de la conversación fallan, la recuperación deberá realizarse a un
nivel más alto.

Según Randell (1975), todos los procesos que intervienen en la conversación deben haber
entrado en ella antes de que alguno pueda salir. Sin embargo, lo aquí definido es un poco

Página 23 de 27
SISTEMAS EN TIEMPO REAL

distinto ya que, si un proceso no entra a la conversación ya sea porque se ha retrasado o


porque ha fallado, mientras que los otros procesos no quieran comunicarse con él, la
conversación podrá terminar exitosamente. Si un proceso intente comunicarse con otro que no
está, o bien se bloquea o bien continua.

Aunque las conversaciones permiten a grupos de procesos coordinar su recuperación, tienen


como desventaja principal que aunque uno solo falle, todos ejecutan su módulo de
recuperación llevando de esta forma a realizar más trabajo del realmente necesario. Según
Gregory and Knight (1985), luego de fallar el proceso puede querer comunicarse con un nuevo
grupo de procesos (módulo secundario). Nótese además, que el test de aceptación del módulo
secundario puede ser diferente.

Acciones atómicas y recuperación de errores hacia adelante:

A veces, es difícil recuperar errores hacia atrás cuando se trata de sistemas embebidos, por lo
cual debe considerarse la recuperación hacia adelante y junto con ella los manejadores de
excepciones. Veremos manejadores de excepciones entre procesos concurrentes involucrados
en acciones atómicas.

En la recuperación hacia atrás, cuando ocurre un error, todos los procesos involucrados
en la acción atómica participan en la recuperación. Lo mismo ocurre con los manejadores de
excepciones y la recuperación hacia adelante. Si una excepción ocurre en uno de los procesos,
también se levanta en todos los procesos activos en la acción. La excepción que se genera
desde otros proceso se llama asincrónica. Una posible sintaxis para Ada podría ser:

action A with (P2, P3) do


-- la acción
exception
when exception_a =>
-- secuencia de sentencias
when exception_b =>
-- secuencia de sentencias
when others =>
raise falla_accion_atomica
end A;

Si se adopta el modelo de terminación, la única forma de terminar normalmente la acción es


cuando ninguna excepción ha sido levantada. Mientras que si se utiliza el resumption model, al
ocurrir una excepción, el proceso la resuelve mediante su manejador y el control retorna al
punto donde la excepción fue levantada.

En cualquiera de los dos casos, si algún proceso falla y no dispone del manejador
correspondiente o si el manejador falla, la acción atómica falla levantando la excepción
falla_accion_atomica. Esta última será levantada en todos los procesos involucrados.

Eventos asincrónicos, señales y transferencia de control asincrónica:

En los STR se combina la recuperación de errores hacia atrás y hacia adelante. La


recuperación hacia atrás permite recuperarse de errores no previstos y la recuperación hacia
adelante es necesaria para deshacer cualquier interacción con el entorno.

Ninguno de los principales lenguajes para tiempo real soporta acciones atómicas ni el
levantamiento asincrónico. Esta es una de las razones por la cual no disponen de un método
adecuado para especificar el dominio de la excepción. Sin embargo, Ada soporta eventos
asincrónicos: es decir una mecanismo para obtener la tensión de otro proceso sin tener
necesidad de esperar. Hay dos modelos básicos: resumption y termination.

Página 24 de 27
SISTEMAS EN TIEMPO REAL

El resumption model de eventos asincrónicos es similar a una interrupción por software. Un


proceso indica cuales son eventos que puede manejar y cuando recibe la señal de uno de
ellos, el proceso es interrumpido y el manejador del evento es ejecutado. El manejador atiende
al proceso asincrónico y luego el proceso continua desde el punto en el cual había sido
interrumpido.

Con el termination model cada proceso especifica un dominio de ejecución durante el cual está
preparado para recibir eventos asincrónicos. Si un evento ocurre fuera de este dominio podría
ser ignorado o encolado. Después que el evento ha sido manejado, el control es retornado al
proceso interrumpido en una posición diferente a la que tenía cuando el evento ocurrió. Ada
soporta el modelo de terminación para el manejo de eventos asincrónicos mediante un
mecanismo de transferencia de control asincrónico.

La inclusión de un mecanismo de eventos asincrónicos en un lenguaje presenta algunas


desventajas ya que complica la semántica del lenguaje e incrementa la complejidad del soporte
del sistema de run-time. Sin embargo, se presentan a continuación algunas justificaciones de
su existencia:

Necesidades por parte del usuario para la existencia de eventos asincrónicos:

El requerimiento principal para la existencia de eventos asincrónicos es habilitar al prceso a


responder rápidamente a una condición detectada por otro proceso. Obviamente cualquier
proceso puede responder a un evento haciendo polling o esperando por ese evento. Pero hay
situaciones donde este tipo de procesamiento (polling) es inadecuado:
 Recuperación del error: Ya vimos que si varios procesos participan de una acción
atómica, en caso que alguno de ellos incurra en un error, todos deben participar de la
recuperación. Suponga que ha ocurrido una falla de hardware que modifica las pre-
condiciones del proceso o una falla de tiempo que no le permitirá terminar a tiempo una
tarea. En ambos casos el proceso debe ser notificado a fin de tomar alguna acción para
recuperarse.
 Cambios de modo: Un STR suele tener varios modos de operación. Ej: un avión puede
tener un modo despegue, otro crucero y otro para aterrizar. El sistema debe estar informado
de cual de los modos es el que se está ejecutando.
 Scheduling utilizando cálculos parciales o imprecisos: La precisión de muchos
algoritmos es proporcional al tiempo de cálculo empleado. Este es el caso de los algoritmos
de cálculo numérico, estimaciones estadísticas o búsquedas heurísticas. En tiempo de
ejecución, puede asignársele al algoritmo un cierto tiempo pasado el cual, debe ser
interrumpido.
 Interrupciones del usuario: puede ocurrir que el usuario haya detectado un error y desee
interrumpir el proceso para comenzarlo nuevamente.

Una forma de manejar eventos asincrónicos es abortar el proceso y permitir al proceso maestro
recuperarse. Todos los sistemas operativos y la mayoría de los lenguajes de programación
concurrente proveen esta facilidad; pero se trata de una solución extrema de donde un
mecanismo de eventos asincrónicos debe ser provista.

Control de Recursos:

Las acciones atómicas tienen como objetivo lograr un proceso de cooperación entre procesos
confiable. También es necesario coordinar cuando se trata de compartir recursos. En este caso
se dice que se trata de procesos competitivos. En este caso los procesos no se comunican
para intercambiar información sino para coordinar el acceso a los recursos compartidos.

Página 25 de 27
SISTEMAS EN TIEMPO REAL

Ya vimos que el manejo de recursos implica un agente de control. Si el agente es pasivo se


dice que el recurso está protegido pero si maneja el control al recurso se dice que es un
servidor.

Es importante proveer de un acceso a los recursos seguro de manera que no ocurra que un
proceso tome algún recurso que nunca libere dejando a otros esperando eternamente.

1. Control de recursos y acciones atómicas

Aunque los procesos necesitan comunicarse y sincronizarse para alocar los recursos, no lo
hacen en la forma de una acción atómica. Esto se debe a que sólo intercambian información
específica. Como resultado, el controlador del recurso, sea protegido o servidor, aceptará
cualquier cambio a su dato local.

Dado que el acceso al recurso se realiza vía el controlador de dicho recurso, la comunicación
entre el proceso y el controlador debería realizarse mediante una acción atómica de manera
que nadie interfiera mientras el recurso se aloca o se libera. Nótese que el administrador del
recurso y el proceso cliente podrían utilizar una recuperación de errores hacia adelante o hacia
atrás que permitan soportar errores previstos o no previstos.

Si el administrador del recurso es un servidor, el cuerpo de Control_de_Recurso estará


contenido en una tarea. Si se trata de un recurso protegido, se utilizará un objeto protegido
dentro del cuerpo de un package.

6. Sistemas Distribuidos en Ada


En Ada, un sistema distribuido es la interconexión de uno o más nodos de procesamiento
(un recurso del sistema que tiene capacidad computacional y de almacenamiento) y cero o más
nodos de almacenamiento (recurso del sistema que sólo tiene capacidad de almacenamiento,
con el almacenamiento direccionable por más de un nodo de procesamiento).
El modelo de Ada para la programación de Sistemas Distribuidos, especifica la partición
como la unidad de distribución. Una partición es un programa o parte de un programa que
puede ser invocado desde afuera de la implementación.
Un programa distribuido comprende una o mas particiones que se ejecutan
independientemente (excepto cuando ellas se comunican) en un sistema distribuido.
El proceso de mapear las particiones de un programa en los nodos de un sistema
distribuido es llamado la configuración de las particiones de un programa.
Las particiones, sin embargo, no son entidades de primera clase, en el sentido que no
pueden ser declaradas como tipos e instancias creadas dentro del programa. Esto implica el
agregado de unidades de biblioteca que pueden ejecutarse en forma distribuida. Quizás, esta
dificultad de definir entidades de partición es la mayor limitación de Ada para trabajar en
Sistemas Distribuidos.
Cada partición se encuentra en el mismo sitio de ejecución donde todas sus unidades de
librería, ocupan el mismo espacio de direcciones lógico. Puede haber más de una partición en
el mismo sitio de ejecución.
Las particiones pueden ser activas o pasivas. Las unidades de librería que forman una
partición activa, residen y se ejecutan sobre el mismo elemento de procesamiento. Por
otro lado, las unidades de librería que forman una partición pasiva, residen en un elemento
de almacenamiento que es directamente accesible por los nodos de diferentes particiones
activas que lo referencia. Este modelo asegura que las particiones activas no pueden acceder
directamente a variables que residen en otras particiones activas. Las variables pueden ser
compartidas por dos particiones activas sólo encapsulándolas en una partición pasiva. La
comunicación entre particiones activas, generalmente está definida como llamadas a
subprogramas remotos.

Página 26 de 27
SISTEMAS EN TIEMPO REAL

 Comunicación remota

Una llamada a un subprograma remoto es una llamada a un subprograma en otra partición.


La partición que origina la llamada es la partición llamadora y la partición que ejecuta el
correspondiente curso del subprograma es la partición llamada.
Hay tres formas de hacer una llamada remota:
. Como una llamada directa a un subprograma remoto explícitamente declarado en una
interfaz de llamada remota.
. Como una llamada indirecta utilizando un acceso remoto al subprograma (access).
. Como una llamada al dispatching de run-time para acceder a un método de un objeto
remoto.
La primera forma de llamar, realiza un binding estático entre el subprograma llamador y el
llamado; mientras que las dos últimas lo hacen en forma dinámica.
Por lo general, las llamadas remotas contienen sólo parámetros “in” o “access” y el llamador
puede desear continuar su ejecución tan rápido como sea posible. Esto es lo que se denomina
una llamada asincrónica. Ada permite que el hecho de ser sincrónico o asincrónico, sea una
propiedad del procedimiento y no de la llamada.
Ada provee un subsistema standard (provisto por la implementación) para manejar todas
las comunicaciones remotas.
El Partition Communication Subsystem (PCS) provee facilidades para soportar
comunicación entre particiones activas de un programa distribuido. El Package System.RPC es
una interfaz del PCS, definida por el lenguaje.

Página 27 de 27

También podría gustarte