Resumen STR

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 69

Sistemas en Tiempo Real

06/06/2022
Cristina Otero Rodríguez

1. Tema 1: Introducción a los sistemas en tiempo real


1.1. Definición y clasificación de los sistemas en tiempo real
Sistema en tiempo real: cualquier sistema electrónico que tiene que responder a estímulos de entradas generadas
externamente en un periodo de tiempo finito y especificado. Esto supone que tiene que interaccionar repetidamente
con su entorno físico, y para que el sistema funcione correctamente, tienen que ser correctas las acciones que ejecute
y, además, debe realizarlas dentro del intervalo de tiempo especificado. Se distinguen dos tipos de sistemas en tiempo
real según lo que puede suponer un retraso:

• Sistemas en tiempo real estrictos. Son aquellos en los que es absolutamente imperativo que la respuesta se
produzca dentro del periodo de tiempo especificado.
• Sistemas en tiempo real no estrictos o flexibles. Son aquellos en los que, siendo importantes las
limitaciones temporales, si éstas ocasionalmente no se cumplen, el sistema aún funciona correctamente. Los
sistemas no estrictos se pueden distinguir también de los interactivos, en los que no hay un tiempo límite dado
explícitamente.

Según la arquitectura, se pueden clasificar en las siguientes categorías:

• Sistemas centralizados. Todos los procesadores se encuentran en un único nodo, con lo que se consigue
hacer despreciable el tiempo de comunicación entre procesadores frente al tiempo de proceso.
• Sistemas distribuidos. Los procesadores se encuentran conectados a una red. El tiempo de comunicación
entre procesadores no es despreciable frente al tiempo de proceso.

En función de la especificación para la creación del software, se distinguen:

• Sistemas propietarios. El software se crea para una plataforma determinada dependiendo, por lo general, del
sistema operativo y de su arquitectura.
• Sistemas abiertos. El software se crea a partir de estándares que permiten independizar el sistema de la
plataforma donde se ejecuta.

En función de los flujos de ejecución de los que está compuesto, se tienen:

• Sistemas monotarea. Están compuestos por un único flujo de ejecución. El sistema se compone de un bucle
infinito en el que se muestran los dispositivos de entrada a una determinada frecuencia y se generan las salidas
correspondientes. Su principal ventaja es la sencillez. Sin embargo, son poco flexibles, lo que impide añadir
nuevas funcionalidades debido a la alta interdependencia entre las tareas a realizar.
• Sistemas multitarea. Están compuestos por un conjunto de tareas que se ejecutan de forma concurrente para
el control del proceso global. Si es necesario el control de nuevos procesos, sólo es preciso añadir nuevas
tareas. El principal problema es la planificación de las tareas concurrentes de tal forma que se cumplan los
requisitos temporales. En este tipo de sistemas es necesario controlar los recursos y la comunicación entre
tareas.

1.2. Características de los sistemas en tiempo real y de los lenguajes para su implementación
Consideraciones de los sistemas en tiempo real:

• Los sistemas en tiempo real interactúan con un entorno externo por lo que es necesario utilizar sensores que
permitan realizar la toma de datos del entorno y un conjunto de actuadores que permitan modificar el estado
del sistema controlado.
• Los sistemas en tiempo real tienen que procesar información en un plazo de tiempo finito.
• No todos los componentes de un sistema en tiempo real tienen restricciones de tiempo.
• Los sistemas en tiempo real suelen tomar acciones sobre el entorno controlado que dependen de una
secuencia de datos de entrada-salida y no sólo de las entradas en un determinado instante (sistemas
reactivos).

Página 1 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

• El tiempo de ejecución del código debe estar por debajo de un valor especificado, por lo que el sistema
controlado debe ser predecible.

Características de los sistemas en tiempo real (requerimientos de los lenguajes de programación):

• Tamaño y complejidad. Los sistemas en tiempo real se caracterizan por responder a sucesos del mundo real.
Es por ello por lo que el software de tiempo real se encuentra sometido a continuos cambios, lo que implica un
constante mantenimiento. Por lo general, una de las características de este tipo de software es su complejidad.
• Manipulación de números reales. Los lenguajes de programación deben permitir operar con números reales
y de coma flotante. Así, en control de procesos es preciso poder calcular el valor del error a partir de la señal
de referencia y del valor de la media de la variable a controlar, y poder realizar los cálculos que requiera el
algoritmo de control.
• Confianza y seguridad. Los lenguajes de programación en tiempo real tienen que permitir introducir
instrucciones que impidan que se produzcan errores inesperados.
• Control concurrente de distintos componentes del sistema. El mayor problema asociado a la producción
de software para sistemas en los que se produce concurrencia es cómo expresar esa concurrencia en la
estructura del programa.
• Facilidades de tiempo real. El tiempo de respuesta es crucial en un sistema en tiempo real. Para facilitar la
construcción de los sistemas en tiempo real, se utilizan procesadores que tienen la capacidad de no producir
retardos en períodos críticos de operación del sistema.
• Interacción con la interfase hardware. El computador debe ser capaz de comunicarse con sensores,
actuadores y otros dispositivos. Estos dispositivos pueden generar interrupciones con el fin de que la operación
a realizar se produzca de forma adecuada.
• Implementación eficiente. Al ser el tiempo crítico, la eficiencia de la implementación es más importantes que
en otros tipos de sistemas. Los lenguajes de programación deben permitir una implementación eficiente. Así,
si la respuesta a una determinada entrada se requiere en microsegundos, se debe utilizar un lenguaje que
permita realizar tareas en microsegundos. De ahí que los lenguajes compilados (no interpretados) sean los
más habituales en la implementación de los sistemas en tiempo real.

Interrupción: evento externo al que debe responder el sistema en un tiempo especificado. Con frecuencia, se presentan
estímulos múltiples (interrupciones), por lo que se deben establecer prioridades e interrupciones prioritarias de tal forma
que la tarea con mayor prioridad sea ejecutada dentro de las ligaduras de tiempo especificadas e independientemente
de otros sucesos. El manejo de interrupciones implica almacenar información de tal forma que el computador pueda
restablecer correctamente la tarea interrumpida, así como evitar interbloqueos y bucles sin fin.

Manejo de una interrupción: cuando se produce una interrupción, el flujo normal de procesamiento es modificado por
un suceso que necesita un servicio inmediato. Dicho suceso puede ser generado por software o hardware. Los pasos
a seguir en el manejo de una interrupción son:

• Guardar el estado del programa interrumpido.


• Determinar la naturaleza de la interrupción.
• Servir a la interrupción.
• Restaurar el estado del programa interrumpido.
• Volver al programa interrumpido.

En ocasiones, el servicio de interrupción de un suceso puede verse a su vez interrumpido por otro suceso de mayor
prioridad. En tal caso, pueden establecerse niveles de prioridad.

Para manejar las interrupciones y cumplir las ligaduras de tiempo del sistema, muchos sistemas operativos de tiempo
real hacen cálculos dinámicos con el fin de determinar si pueden cumplirse los objetivos del sistema. Estos cálculos
dinámicos se basan en la frecuencia media de ocurrencia de sucesos, la cantidad de tiempo que lleva servirlos (si
pueden ser servidos) y las rutinas que pueden interrumpirlos y temporalmente evitar su servicio.

Si los cálculos dinámicos muestran que es imposible manejar los sucesos que pueden ocurrir en el sistema y también
cumplir las ligaduras de tiempo, se debe decidir sobre un esquema de acción. Un posible esquema consiste en
almacenar en un buffer los datos, de forma que puedan ser procesados cuando el sistema esté preparado.

Página 2 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

1.3. Ejemplo de un sistema de control por computador


Estructura convencional de un sistema de control por computador:

• Sensor: mide la salida continua del proceso 𝑌(𝑡), y genera una señal continua 𝑌𝑠 (𝑡) de la misma.
• Conversor analógico digital A/D: muestrea, con un periodo de muestro 𝑇, la señal 𝑌𝑠 (𝑡), y genera una señal
discreta 𝑌𝑠∗ , que es digitalizada.
• Comparador: calcula el valor de la señal de error, 𝑒 ∗ , como la diferencia entre el último valor obtenido de 𝑌𝑠∗ y
el valor indicado en el computador para la señal de referencia 𝑌𝑟∗ .
• Controlador: entre periodos de muestreo, a partir de valor de la señal 𝑒 ∗ y mediante un determinado algoritmo,
genera como salida el valor de la señal de control 𝑢 ∗ que se debe aplicar al actuador.
• Conversor digital analógico D/A: manteniendo constante durante un tiempo 𝑇 el último valor calculado de 𝑢∗
produce la señal de control continua 𝑢(𝑡) que se aplica al actuador.
• Actuador: admite como entrada 𝑢(𝑡) y genera como salida la señal de control continua del proceso 𝑢𝑎(𝑡).
• Proceso: recibe la señal 𝑢𝑎(𝑡) y produce la señal de salida continua 𝑌(𝑡), que es la variable a controlar.

Figura 1.1 Sistema de control por computador

La entrada y salida del computador se actualizan en los mismos instantes de tiempo (instantes de muestreo) que se
producen a intervalos de tiempo 𝑇 (periodo de muestreo).

Tipos de señales: en los sistemas de control se utilizan tres tipos de señales:

• Señales continuas. Son todas las que intervienen en la planta, desde la señal de control 𝑢(𝑡) hasta la señal
de medida 𝑌𝑠 (𝑡).
• Señales discretas. Corresponden a la señal que resulta del muestro de la señal de medida 𝑌𝑠 (𝑡) y a la señal
que toma los valores de la señal de control 𝑢(𝑡) en cada periodo de muestreo.
• Señales digitales. Son aquellas con las que opera el computador. En cada periodo de muestreo se digitaliza
la señal discreta de medida y se pasa de digital al valor discreto de la señal de control.

1.4. Programación de sistemas en tiempo real


Programa en tiempo real: programa en el cual las correctas operaciones dependen de los resultados lógicos
computacionales y del tiempo que se tarda en producir estos resultados. Para que un lenguaje sea apropiado para el
tiempo real debe tener todas las características mencionadas en la Sección 1.2. Entre ellas, destacan la capacidad de
multitarea, construcciones para implementación directa de funciones de tiempo real y características modernas de
programación que ayuden a asegurar la corrección del programa.

Es importante que el lenguaje de programación soporte directamente la multitarea ya que los sistemas en tiempo real
deben responder a sucesos asíncronos que ocurren simultáneamente. Aunque muchos sistemas operativos de tiempo
real dan capacidades multitarea, frecuentemente existe software de tiempo real empotrado sin un sistema operativo.
En vez de ello, las aplicaciones empotradas se escriben en un lenguaje que da un soporte de tiempo real suficiente
Página 3 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

para la ejecución del programa en tiempo real. El soporte de tiempo de ejecución requiere menos memoria que un
sistema operativo y puede ser adaptado a una aplicación incrementando así el rendimiento.

También son necesarias facilidades que permitan una programación fiable, debido a que los programas en tiempo real
son frecuentemente grandes y complejos. Estas características incluyen la programación modular, un fuerte
reforzamiento de la tipificación de los datos y una multitud de otras construcciones de control y definición de datos.

Primitivas de sincronización: son órdenes que permiten la sincronización entre tareas. Un lenguaje de programación
que soporte directamente estas primitivas simplifica mucho la traslación del diseño al código. Entre estas órdenes se
encuentran:

• 𝑆𝐶𝐻𝐸𝐷𝑈𝐿𝐸: planifica un proceso basándose en el tiempo o en un suceso.


• 𝑆𝐼𝐺𝑁𝐴𝐿 y 𝑊𝐴𝐼𝑇: manipulan un indicador especial, llamado semáforo, que facilita la sincronización de tareas
concurrentes.

Criterios para el diseño de un programa en tiempo real:

• Seguridad: probabilidad de que las condiciones que pueden llevar a mal funcionamiento y contratiempos no
ocurran, por ejemplo, los errores de programación. El análisis de seguridad se debe ir realizando a lo largo de
cada una de las etapas del ciclo de vida software.
• Flexibilidad: facilidad para que el programador pueda expresar todas las operaciones que se requieran de
una forma sencilla y coherente.
• Legibilidad: facilidad para poder entender el funcionamiento de un programa leyendo el código del mismo en
dicho lenguaje de programación. La legibilidad permite una mayor seguridad, mayor facilidad de mantenimiento
y requiere menos documentación.
• Simplicidad: facilidad para escribir un programa, con un número suficiente y no contradictorio de componentes
y criterios de combinación para las estructuras de control y manejo y definición de los datos.
• Eficiencia: garantía de que los tiempos de respuesta cumplen las especificaciones, de forma que el programa
sea eficiente y predecible.
• Portabilidad: independencia del hardware en el que se ejecute. Esto es difícil para los sistemas en tiempo real
pero, en la medida de lo posible, debe aislar la parte del programa que depende de la máquina, de forma que
se facilite su portabilidad.

Elementos de los sistemas en tiempo real: se clasifican en función de si tienen que acontecer en tiempo real o no.
Dentro de las que operan en tiempo real, se pueden establecer dos tipos:

• Tipo 1 o general: el tiempo de ejecución medio debe ser inferior a un tiempo máximo especificado.
• Tipo 2 o específico: el resultado de cada cálculo individual debe ser completado dentro de un tiempo máximo
especificado.

Clasificación de la programación: se establecen distintas categorías en función de la forma en que se deben realizar
las distintas acciones:

• Programación secuencial: es el tipo de programación clásico. En ella las acciones a realizar se ordena de
forma consecutiva. El comportamiento del programa depende sólo de los efectos de las acciones individuales
y de su orden, no teniendo ninguna importancia el tiempo que lleve realizar una determinada acción. La validad
de estos programas requiere:
- Que cada sentencia particular defina una acción establecida.
- Que las distintas estructuras del programa produzcan una secuencia de sucesos establecidos.
• Programación multitarea o concurrente: las acciones que se deben realizar no son necesariamente
disjuntas en el tiempo; puede ser necesario que algunas tareas se realicen en paralelo. No obstante, las
relaciones secuenciales entre acciones pueden seguir siendo de interés, esto es, tales programas pueden estar
formados por varias partes, que se denominan tareas o procesos, que pueden ser parcialmente secuenciales,
pero que se ejecutan de forma concurrente y se comunican entre ellas utilizando variables que comparten y
señales de sincronización. Si las tareas no poseen variables comunes, la validación del programa se realiza
validando cada tarea por separado. Si hay variables compartidas, la concurrencia hace que no se pueda
ejecutar el programa a menos que haya una regla que gobierne la secuencia de las distintas acciones de las
tareas. El uso de procedimientos de sincronización significa que el tiempo para que se realice cada acción no
es relevante para la validación.
Página 4 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

• Programación en tiempo real: sus acciones no necesitan ser disjuntas en el tiempo. La secuencia de algunas
acciones no se determina por el diseñador del programa, si no por los sucesos que ocurran en el entorno que
influye sobre el programa. Un programa de tiempo real también puede ser dividido en número de tareas, pero
la comunicación entre las tareas no tiene porque esperar una señal de sincronización. En los sistemas en
tiempo real, el tiempo que se tarda una determinada acción es esencial para el proceso de validación. Esto
hace a estos sistemas más difíciles de verificar. Una característica común a todos los programas en tiempo
real es que tienen que ejecutarse continuamente, por lo que una estructura natural es un bucle infinito como el
indicado en el Código 1.1.

Código 1.1 Bucle infinito propio de un sistema en tiempo real

Polling (sondeo): método para la sincronización que consiste en ver si ha ocurrido un suceso externo. Se puede realizar
de dos formas:

• Si el suceso no ha ocurrido el programa continúa con otras acciones antes de volver a comprobar otra vez si
se ha producido éste.
• El programa de forma continua repite la comprobación hasta que ocurre el suceso.

El polling representa como ventaja la consideración de que el programa realiza una única tarea, simplificando así su
diseño, y como inconveniente, que se debe asegurar que todas las tareas se pueden realizar dentro de un tiempo
especificado. Este método se suele utilizar solo en sistemas pequeños.

Otros métodos de sincronización:

• Si se requiere que una acción esté sincronizada a unos instantes determinados de tiempo, se puede utilizar
una medida de reloj de tiempo real que marque los instantes de ejecución de una tarea. Un programa que
contempla esto para 𝐶𝑜𝑛𝑡𝑟𝑜𝑙𝑇𝑎𝑠𝑘 se presenta en el Código 1.2:

Código 1.2 Sincronización temporal de acción con medida de reloj

Página 5 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

• También se puede realizar la sincronización mediante interrupciones. En este caso no se necesita hacer la
comprobación de una señal, ya que el computador puede estar realizando unos cálculos que suspende cuando
se produce la interrupción para ejecutar la acción demandada. Una manera es con un reloj que produce una
interrupción periódica que es atendida por una rutina que se encarga de ir actualizando el tiempo actual, tal y
como se muestra en el Código 1.3. Este método proporciona una excelente solución a problemas sencillos de
control de tiempo real por lo que se suele utilizar con bastante frecuencia.

Código 1.3 Tarea única con señal de reloj

1.5. Diseño de sistemas en tiempo real


Métodos para el diseño de sistemas en tiempo real: incluyen dos fases:

• Fase de planificación: se interpretan los requerimientos de los usuarios para producir una especificación
detallada del sistema y se planifican los recursos, personal, tiempo, equipamientos y costes requeridos.
También se toman las decisiones sobre la división de funciones entre el hardware y el software de forma
completa, detallada y sin ambigüedades. El esquema de la fase de planificación se muestra en la Figura 1.2.

Figura 1.2 Fase de planificación

• Fase de desarrollo: los estados preliminares de diseño corresponden a la descomposición del sistema en un
conjunto de subtareas específicas. Las entradas a este estado son las especificaciones. Durante el mismo, es
necesario ligar los diseños del hardware y del software. El diseño detallado se suele hacer en dos estados: la
descomposición en módulos y el diseño interno de cada módulo. La Figura 1.3 muestra un esquema de la fase
de desarrollo.

Página 6 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

Figura 1.3 Fase de desarrollo

El proceso de desarrollo mostrado es del tipo top-down, en el que se van descomponiendo las tareas en otro conjunto
de subtareas que pueden considerarse separadamente. Como resultado del diseño preliminar, se obtienen las
estructuras globales de datos y la arquitectura software de alto nivel.

Fase de diseño preliminar: se realizan las siguientes tareas:

• Se definen las distintas funciones del sistema.


• Se especifican las necesidades de comportamiento temporal.
• Se especifica el comportamiento en caso de fallo de algún componente.
• Se definen las pruebas de aceptación que se deben pasar al software y al hardware.
• Se construye un modelo del entorno de la aplicación.

Descomposición: ruptura del sistema en partes cada vez más pequeñas hasta que sus componentes puedan ser
comprendidos por un grupo de trabajo o por un individuo. Cada nivel de descomposición debe tener una descripción
adecuada y un método de documentación de dicha descripción.

Abstracción: permite que los detalles, en especial los relativos a la implementación, se pospongan.

Subprograma: los procesos de descomposición y de abstracción llevan al desarrollo de subcomponentes del


programa. Estos subprogramas deben tener unos papeles bien definidos y unas interconexiones y relaciones entre sí
claras y sin ambigüedades. Los lenguajes de programación de tiempo real ADA y MODULA-2 utilizan la estructura
conocida como módulo para soportar dichos procesos.

Descomposición en módulos: una buena descomposición es aquella que tiene fuerte cohesión y un acoplamiento
débil.

Acoplamiento: se dice que es fuerte si se produce intercambio de información de control entre los módulos. El
acoplamiento es débil si sólo se produce intercambio de datos entre los módulos.

Cohesión: las siguientes medidas dan una indicación de la cohesión desde muy débil a muy fuerte:

• Casual: los elementos del módulo están ligados de una forma muy superficial; por ejemplo, escritos en el
mismo mes.
• Lógico: los elementos del módulo están relacionados en términos del sistema global, pero no en términos del
software utilizado. Un ejemplo son los drivers de los dispositivos de salida.
• Temporal: los elementos del módulo se ejecutan en tiempos similares; por ejemplo, rutinas de arranque.
• Procedural: los elementos del módulo se usan juntos en la misma sección del programa. Un ejemplo son los
componentes de la interfaz gráfica.
• Comunicacional: los elementos trabajan sobre la misma estructura de datos, por ejemplo, los algoritmos
usados para analizar la señal de entrada.
Página 7 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

• Funcional: los elementos del módulo trabajan juntos para contribuir a la realización de una única función del
sistema. Un ejemplo es procesamiento de datos de un sistema de ficheros distribuidos.

Si los módulos con fuertes limitaciones temporales pueden separarse y manejarse independientemente de los módulos
sin ninguna restricción temporal, o con restricciones temporales mucho más débiles, se obtienen ventajes, tales como:
menor interacción entre módulos y limitaciones sobre el tiempo de ejecución menos ajustadas.

Foreground: en él se ejecutan los módulos o tareas con fuertes restricciones temporales que están ligados a los
procesos externos. A estos módulos se les dota de mayor prioridad y de un mecanismo que les permite interrumpir las
tareas que se ejecutan en el background.

Background: en él se ejecutan los módulos con menores restricciones temporales, o sin ninguna restricciones.

Método foreground-background: en su forma más simple, sólo se permiten dos tareas: una en el foreground y otra en
el background. Aunque este método separa la estructura de control de los módulos foreground y background, estos
módulos continúan ligados por la estructura de datos. La ligadura entre las tareas ocurre porque comparten variables
de datos. Puesto que una tarea de foreground y otra de background pueden estar activas al mismo tiempo y, cada
tarea no sabe que la otra está activa, si tienen variables en común, la modificación de alguna de estas variables por
una tarea puede resultar fatal para la otra.

Sistema multitarea: permite la ejecución de tareas en paralelo. Simplifica el diseño y la programación en sistemas en
tiempo real grandes mediante la extensión de la partición foreground-background a particiones múltiples que permitan
el concepto de muchas tareas activas. Esto permite que en la fase de diseño preliminar se consideren como tareas
separadas cada una de las actividades que se deben realizar. La implementación de un sistema multitarea requiere la
habilidad necesaria para:

• Crear tareas separadas.


• Controlar las tares que se están ejecutando normalmente con un sistema de prioridades.
• Compartir datos entre tareas.
• Sincronizar las tareas entre sí y con sucesos externos.
• Impedir que las tareas se afecten unas a otras.
• Controlar el arranque y parada de las tareas.

Diseño de un sistema multitarea: requiere la creación de dos módulos:

• Módulo abstracto (o modelo esencial o lógico): no contiene ningún detalle de la implementación, y se


desarrolla en primer lugar. El desarrollo de un modelo abstracto requiere:
- Disponer de una notación que contenga un conjunto de ideas conceptuales lo suficientemente rica
como para expresar todas las necesidades del diseño.
- Una metodología que permita que el modelo abstracto sea trasladable a un modelo de implementación.
• Módulo de implementación (o módulo de producción): contiene todos los detalles para la construcción del
sistema y se suele desarrollar a continuación del modelo abstracto o en paralelo con él.

Métodos de flujo de datos: para soportar un sistema en tiempo real, deben extenderse de tal forma que suministren:

• Un mecanismo para la representación de la comunicación y sincronización entre tareas.


• Una notación para representar la dependencia de estados.
• Un procedimiento que conecte los métodos de flujo de datos con el mundo del tiempo real.

Sincronización de tareas: se realiza mediante la exclusión mutua o la estimulación cruzada. La exclusión mutua se
aplica cuando dos tareas pueden acceder a un área de datos compartida al mismo tiempo. Los semáforos se utilizan
para excluir una tarea del acceso a los datos mientras que la otra los utiliza. La estimulación cruzada se implementa
cuando una tarea señala a otra, que se encuentra en espera, que ha terminado alguna actividad y la tarea señalada
puede proseguir.

Comunicación de tareas: ocurre cuando una tarea debe transmitir información a otra. La comunicación por mensajes
es un método frecuente de comunicación. Cuando una tarea productora envía un mensaje a una tarea consumidora y
luego espera una respuesta de ésta, la comunicación está fuertemente acoplada, mientras que si las tareas productoras
y consumidoras continúan el procesamiento a sus propios ratios y utilizan una cola para almacenar los mensajes, la
comunicación está débilmente acoplada.
Página 8 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

2. Tema 2: Fiabilidad y tolerancia a fallos


2.1. Introducción
Origen de los fallos: hay cuatro tipos de orígenes que pueden provocar la aparición de fallos en sistemas embebidos:

• Una especificación errónea. Esta es la fuente más común de fallos producidos en el software de sistemas
embebidos.
• Fallos introducidos debido a errores en la creación del software.
• Fallos introducidos debido al mal funcionamiento (transitorio o permanente) de componentes hardware del
sistema.
• Fallos provocados por interferencias transitorias o permanentes en el subsistema de comunicaciones.

2.2. Fiabilidad, fallos y defectos


Fiabilidad: se define como una medida del éxito con el que el sistema se ajusta a la especificación dada para su
comportamiento.

Avería: se produce cuando el comportamiento del sistema no se ajusta a la especificación dada. Puede tener distintas
causas y, es habitual que cuando ésta aparece, en primera instancia, se desconozcan las motivos. En ese sentido,
como primera aproximación, la aparición de una avería puede verse como un modelo de caja negra.

Errores: manifestación externa de problemas del sistema.

Fallos o defectos: causas de los errores. Pueden ser algorítmicas, mecánicas o eléctricas.

Figura 2.1 Cadena de activación de los “estados” defecto, error y avería

Componente defectuoso: es un fallo o defecto del sistema del cual forma parte, puede ser una pieza mecánica o una
línea de código, que provocará, durante el tiempo de vida de operación del mismo, un error.

Tipos de fallos:

• Fallos transitorios: comienzan en un instante de tiempo concreto, permanecen en el sistema durante un


intervalo y finalmente desaparecen. Este tipo de fallos es típico en componentes físicos que pueden llegar a
comportarse de manera incorrecta cuando están sometidos a algún tipo de interferencia externa. Cuando esa
perturbación externa desaparece, o se atenúa lo suficiente, lo hace también el fallo. Es importante tener en
cuenta, sin embargo, que la desaparición del fallo no significa necesariamente que el error provocado por dicho
fallo desaparezca a su vez.
• Fallos permanentes: comienzan en un instante de tiempo concreto, pero permanecen en el sistema de
manera indefinida, hasta que son reparados.
• Fallos intermitentes: ocurren de manera intermitente.

Bugs: fallos o defectos de software. Originalmente se establecieron dos clases de bugs:

• Bohrbugs: este tipo de bug es aquel que es reproducible e identificable, lo cual hace que sea, por norma
general, fácil de depurar y eliminar.
• Heisenbugs: son aquellos que sólo se activan bajo unas ciertas circunstancias extrañas que, por tanto, hacen
muy difícil su detección y depuración.

Página 9 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

2.3. Modos de fallo


Tipos de fallo: puesto que un sistema embebido de tiempo real tiene por objetivo proporcionar algún tipo de servicio,
los fallos se pueden clasificar en función del impacto que tendrán en los servicios ofrecidos, distinguiendo:

• Fallos de tiempo: defectos que provocan que el servicio del sistema se complete a destiempo. Cuando se
produce este tipo de fallos, caben cuatro posibilidades:
- Que el servicio se entregue demasiado pronto.
- Que el servicio se entregue demasiado tarde. Estos fallos se conocen como error de prestaciones.
- Que el servicio no se entregue nunca. Estos fallos se denominan error por omisión.
- Que el servicio se entregue sin que sea esperado. Son los llamados fallos de encargo o
improvisación.
• Fallos de valor: el valor asociado con el servicio es incorrecto. Las posibilidades en este caso son: que el valor
erróneo esté dentro de un rango asumible como correcto o válido, o que dicho valor quede fuera de ese rango.
En este último caso, el fallo se conoce como error de límites.
• Fallo arbitrario: provoca ambos tipos de problemas. Son difíciles de distinguir de una secuencia de dos errores
seguidos de tipo error por omisión primero y de encargo o improvisación, después.

Modos de fallo:

• Fallo descontrolado: dicho en aquel sistema en el que se producen errores arbitrarios.


• Fallo de retraso: sistema que da servicios correctos en el dominio de valor, pero que sufre errores de
prestaciones, es decir, que entrega los servicios tarde.
• Fallo silencioso: producidos en los sistemas que dan servicios correctos tanto en el dominio del valor como
en el del tiempo, hasta que fallan. En estos casos, el único fallo posible es un error por omisión y, cuando éste
ocurre, todos los servicios siguientes sufren también errores por omisión
• Fallo de parada: un sistema que presenta las mismas características de un fallo silencioso pero que, además,
de algún modo permite que otros sistemas sean capaces de detectar que ha entrado en el estado de fallo
silencioso.
• Fallo controlado: se producen en sistemas que fallan de un modo controlado.
• Sin fallos: cuando un sistema produce los servicios correctos de la manera adecuada, tanto en el dominio del
tiempo como en el del valor.

2.4. Prevención de fallos y tolerancia a fallos


2.4.1. Prevención de fallos

Prevención de fallos: medida de seguridad pasiva, donde la idean es evitar que exista ningún fallo en el sistema antes
de que éste empiece a estar operativo. Existen dos fases en la prevención de fallos:

• Evitación: trata de reducir al máximo la introducción de componentes potencialmente defectuosos durante la


fase de construcción del sistema. En el caso del software, se pueden tomar algunas medidas para mejorar la
calidad:
- Realizar una especificación rigurosa y, en el caso de ser posible, incluso formal, de los requisitos del
sistema.
- Usar metodologías de diseño bien conocidas y probadas.
- Utilizar herramientas de análisis para verificar el cumplimiento de propiedades claves de los programas.
- Hacer uso de lenguajes de programación que ofrecen características de modularidad y abstracción de
datos.
En el caso del hardware, algunas medidas son:
- Aislar correctamente los dispositivos físicos sensibles a formas externas de interferencia.
- Montar los componentes más fiables existentes dentro de las limitaciones de presupuesto y
prestaciones.
- Usar técnicas exhaustivamente refinadas para la interconexión de componentes y el ensamblado de
subsistemas.

Eliminación de fallos: esta fase consiste en aplicar métodos para encontrar y eliminar fallos. Si bien para esto se usan
técnicas como la revisión de los diseños y del código, se pone una especial atención en realizar un testeo exhaustivo

Página 10 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

del sistema. Por desgracia, las pruebas que pueden aplicarse a un sistema nunca son completos cuando el sistema es
moderadamente complejo, en el sentido de que es imposible cubrir toda la casuística y, por tanto, eliminar todos los
defectos potenciales. En concreto, se dan los siguientes problemas:

• Una prueba sólo puede revelar la existencia de fallos, pero no puede mostrar la ausencia de éstos.
• No es infrecuente que resulte imposible testear un sistema en condiciones realistas.
• Los errores que se introduzcan durante la fase de definición de requisitos del sistema pueden no manifestarse
hasta que el sistema comienza a estar operativo.

2.4.2. Tolerancia a fallos

Tolerancia a fallos: medida de seguridad activa, donde se intenta que, una vez se produce un fallo, el efecto de éste
sea lo menos pernicioso posible para el sistema de modo que éste pueda seguir funcionando. En general, cuando
ocurre un fallo es necesario que se produzca una reconfiguración del sistema, que, habitualmente, consiste en poner
en funcionamiento algún tipo de componente redundante que pueda sustituir al componente causante del fallo. En
función del modo en el que un sistema reacciona a la activación de un fallo, se dice que éste presenta:

• Tolerancia total frente a fallos: cuando el sistema continúa en funcionamiento en presencia de fallos, aun si
es sólo por un periodo de tiempo limitado, sin pérdidas significativas de prestaciones ni funcionalidades.
• Fallo seguro: ocurre en el caso de que el sistema anteponga su integridad a seguir en operación durante el
fallo, realizando una parada temporal de su funcionamiento.
• Degradación controlada (o caída suave): si, al contrario que en el caso anterior, el sistema continúa en
operación en presencia de errores y permite una degradación parcial de las prestaciones y/o funcionalidades
durante la recuperación.

El nivel de tolerancia a fallos requerido por un sistema depende del ámbito de aplicación de este. Si bien los sistemas
más críticos respecto a la seguridad deben exigir una tolerancia total frente a fallos en teoría, en la práctica muchos se
conforman con ofrecer una degradación controlada. Concretamente, los sistemas que pueden sufrir daños físicos
pueden provocar varios grados de degradación controlada. Además, aquellas aplicaciones que requieren de un servicio
continuado no pueden detenerse en un fallo seguro y, por tanto, la degradación controlada es una necesidad. A pesar
de todo, en ciertas circunstancias, puede resultar necesario apagar el sistema en un estado seguro. Estos sistemas,
seguros frente a fallos, intentan limitar el alcance del daño causado por un defecto.

Diseño de sistemas tolerantes a fallos: se realizaban en base a tres suposiciones:

• Los algoritmos del sistema han sido diseñados correctamente.


• Se conocen todos los posibles modos de fallo de todos los componentes que forman parte del sistema.
• Se han considerado todas las posibles interacciones entre el sistema y su entorno.

Sin embargo, la creciente complejidad del software y la introducción de componentes hardware han provocado que
dichas suposiciones dejen de ser válidas. Por lo tanto, es importante que un sistema presente tolerancia a fallos tanto
cuando éstos son esperados como cuando son inesperados.

Redundancia protectora: es una de las técnicas utilizadas para lograr una tolerancia a fallos, que se basa en añadir
componentes extra al sistema de tal forma que éste pueda detectar los fallos y recuperarse de los mismos. Se dice que
estos componentes son redundantes, debido a que no son necesarios para el funcionamiento normal del sistema, sino
que están ahí como medida de protección.

Objetivo de la tolerancia a fallos: minimizar la redundancia a la par que se maximiza la fiabilidad proporcionada y
teniendo en cuenta las restricciones de coste y tamaño del sistema. Es importar tener cuidado al estructura los sistemas
tolerantes a fallos, puesto que la introducción de componentes distintos y redundantes incrementa siempre la
complejidad de todo el sistema y esto, por sí mismo, puede conducir a sistemas menos fiables.

Clasificación de la redundancia:

• Tolerancia a fallos de hardware: la redundancia consiste en incorporar hardware adicional:


- Redundancia estática: los componentes físicos redundantes son utilizados dentro de un sistema para
ocultar los efectos de los fallos. Un ejemplo es la redundancia triple modular (TMR), que consiste en
tres subcomponentes idénticos emplazados en circuitos de votación por mayoría. Los circuitos
comparan la salida de todos los componentes, y si alguna difiere de las otras dos, ésta es bloqueada
Página 11 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

o ignorada. Para que un sistema presente tolerancia frente a fallos en más de un componente se
requiere de una mayor redundancia (NMR).
- Redundancia dinámica: se aporta dentro de un componente que hace que dicho componente indique,
ya sea explícita o implícitamente, que la salida es errónea. De esta forma, se proporciona una
capacidad de detección de errores, aunque la recuperación frente al mismo debe proporcionarla otro
componente distinto.
• Tolerancia a fallos de software: hay dos estrategias distintas:
- Programación de N-versiones.
- Redundancia dinámica de software: está basada en la detección de errores y la recuperación frente
a los mismos. Pone en marcha los procedimientos de recuperación sólo después de que se haya
detectado un error. Una técnica que entra dentro de esta categoría es la de los bloques de
recuperación.

2.5. Programación de N-versiones


Programación de N-versiones: consiste en generar, de manera independiente, N (𝑁 ≥ 2) programas funcionalmente
equivalentes, partiendo en todos los casos de la misma especificación inicial. Cuando se dice que las N versiones son
generadas de manera independiente, se está refiriendo a que ha habido N personas o grupos de personas que ha
producido una de las N versiones, sin que haya existido interacción entre dichas personas o grupos. Una vez las N
versiones están diseñadas y escritas, cada una de estas versiones del programa se ejecuta de forma concurrente, una
y otra vez, con las mismas entradas, y un programa director compara sus resultados. En teoría, los resultados
obtenidos deberían ser idénticos, pero en la práctica existirán algunas diferencias. Por tanto, es necesario decidir, de
alguna forma, cuál de esas salidas es la correcta, lo que suele hacerse por consenso. La idea de que la programación
de N-versiones es una técnica útil que realmente mejora la tolerancia a fallos de un sistema, se asienta sobre dos
suposiciones:

• Es posible especificar un programa de forma completa, consistente y sin ambigüedad.


• Los programas que son desarrollados de manera independiente fallan de forma independiente. Esta suposición
puede no ser correcta en ciertas condiciones, por ejemplo, si se utiliza el mismo lenguaje de programación
puede existir errores asociados con la implementación del lenguaje que sean comunes. Por tanto, una
recomendación importante en la programación de las diferentes N-versiones es utilizar lenguajes y entornos
de programación diferentes para la creación de cada una de dichas versiones. Si finalmente se utiliza el mismo
lenguaje, deberían, al menos, utilizarse compiladores y entornos de distintos fabricantes. Por otro lado, para
proteger el sistema de posibles fallos físicos, las N versiones se deben distribuir en diferentes máquinas que
tengan líneas de comunicación tolerantes a fallos.

Proceso director: controla el programa con N-versiones, encargándose de:

• Invocar a cada una de las versiones.


• Esperar a que las versiones completen su tarea.
• Comparar los resultados y actuar en base a éstos.

El director y las N versiones deben comunicarse durante el transcurso de sus ejecuciones y no, simplemente, cuando
éstas finalizan. El lenguaje de programación utilizado para cada versión influirá en la forma en la que éstas se
comunican y se sincronizan con el proceso director, así como también lo hará sus modelos de concurrencia.

Figura 2.2 Proceso director en la programación de 4 versiones

Página 12 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

Interacción entre las N-versiones y el proceso director: consta de tres elementos:

• Vectores de comparación de votos: son estructuras de datos que representan las salidas de los votos
producidos por cada una de las N versiones y pueden contener también atributos asociados con sus cálculos.
Las salidas o votos son comparadas por el proceso director.
• Indicadores de estatus de comparación: son comunicados por el proceso director a las versiones, e indican
las acciones que éstas deben ejecutar como resultado de la comparación de resultados realizada por el
director, donde intervienen factores tales como si los votos coinciden, o si han sido entregados a tiempo. Los
posibles resultados son:
- Continuación del proceso de la versión.
- Terminación de alguna versión.
- Continuación de las versiones tras modificar uno o más votos respecto al valor de la mayoría.
• Puntos de comparación: son puntos dentro de las versiones donde deben comunicar sus votos al proceso
director. Marcan la frecuencia con la cual se realizan las comparaciones, lo cual es una decisión de diseño muy
relevante pues otorga la granularidad de la tolerancia a fallos. Cuando hay una granularidad grande
(comparaciones poco frecuentes) se minimiza la penalización de las prestaciones inherentes a las estrategias
de comparación, y se obtiene una gran independencia en el diseño de las versiones. Sin embargo, produce
habitualmente una gran divergencia en los resultados obtenidos, debido al gran número de pasos que se
ejecutan entre cada comparación. Obtener una granularidad pequeña requiere que exista una semejanza
importante en los detalles de la estructura de los programas, y por lo tanto, el grado de independencia entre
las versiones no puede ser tan alto. Además, incrementa la sobrecarga asociada con esta técnica.

2.5.1. Comparación de votos

Algoritmos de votación inexacta: se utilizan cuando un proceso director tiene que comparar resultados que no tienen
una naturaleza exacta o una única respuesta correcta. Como ejemplo sencillo, un algoritmo de votación inexacta es,
simplemente, comprobar si los valores arrojados por las versiones están dentro de un rango dado, utilizando para ello
una estimación previa, o bien un valor medio de los N resultados recibidos.

Comparación consistente: es un problema asociado a la aritmética de número reales mediante precisión finita. Se
produce cuando se tiene que realizar una comparación basada en un valor finito dado en la especificación del sistema
y el resultado de dicha comparación determina el curso de la acción.

2.5.2. Aspectos principales de la programación de N-versiones

Aplicación del método de programación de N-versiones: depende de varios factores:

• Especificación inicial: una gran cantidad de los fallos existentes en el software provienen de una
especificación inadecuada.
• Independencia en el diseño: hay estudios que indican que en un sistema de tres versiones es entre cinco y
nueve veces más fiable que un sistema de una única versión.
• Presupuesto adecuado: en la mayoría de los sistemas embebidos, el principal coste de desarrollo del sistema
se debe al software. Un sistema de tres versiones casi triplicará el presupuesto en comparación al sistema de
una versión, y, lo que es más, aumentará mucho la complejidad de este, provocando problemas de
mantenimiento.

También se ha demostrado que en algunos casos es difícil encontrar algoritmos válidos de votación inexacta, y que, si
no se presta especial cuidado al problema de la comparación consistente, los votos pueden llegar a diferir incluso en
situaciones donde no existe fallo alguno.

La programación de las N-versiones, aunque desempeña un papel muy importante en la producción de software fiable,
debe ser utilizada con cuidado y, siempre que sea posible, junto con otras técnicas distintas.

2.6. La estrategia de bloques de recuperación en la tolerancia a fallos software


Bloques de recuperación: son un modo habitual de recuperación que consisten en encapsular un algoritmo con una
funcionalidad específica para tareas de recuperación. Estos bloques tienen como entrada un punto de recuperación
automático y como salida un test de aceptación.

Página 13 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

Figura 2.3 Proceso de ejecución de los bloques de recuperación

Test de aceptación: se usa para comprobar si el sistema se encuentra en un estado aceptable tras la ejecución del
bloque (o módulo primario). Un correcto diseño del test de aceptación es crucial para la eficiencia del esquema de los
bloques de recuperación. Existe una relación entre disponer de un test de aceptación completo y mantener la
sobrecarga que esto implica en niveles bajos, de tal forma que la ejecución normal (libre de errores) no se vea
demasiado afectada. Es importante resaltar que en el caso de estos test se utiliza siempre el término “aceptación”, y
no “corrección”, lo que indica que dichos test pueden aceptar como válidos resultados de un componente que
proporciona un servicio degradado.

Proceso de ejecución de los bloques de recuperación: en caso de que el test de aceptación falle, el programa es
restaurado al punto de recuperación que tiene el bloque como entrada y se ejecuta un módulo alternativo. El proceso
se repite en este módulo alternativo, comprobando de nuevo el test de aceptación y, si éste también falla, el programa
es restaurado una vez más al punto de recuperación y, de nuevo, se ejecuta otro módulo alternativo distinto. Esto se
repite tantas veces como módulos alternativos haya desarrollados en la estrategia de bloques de recuperación
implementada. Si todos los módulos fallan en el test de aceptación, entonces el bloque falla también y la recuperación
debe producirse en un nivel más alto.

En base a las cuatro fases de la tolerancia software a fallo, se tiene que:

1. La detección de errores viene dada por el test de aceptación.


2. No se precisa de la evaluación de daños, ya que se considera que la recuperación hacia atrás limpia los estados
erróneos.
3. El tratamiento del fallo se hace utilizando un recambio que hasta entonces permanecía a la espera.

Los bloques de recuperación se pueden anidar por lo que si un bloque anidado falla su test de aceptación y todas sus
alternativas también fallan, se debe restaurar el punto de recuperación del nivel exterior y ejecutar a continuación el
módulo primario de otro bloque anidado distinto al anterior.

2.7. Comparación entre la programación de N-versiones y los bloques de recuperación


• Redundancia y diversidad en el diseño: ambas técnicas explotan la redundancia para aumentar la diversidad
en el diseño y así conseguir una cierta tolerancia frente a errores no previstos. Así pues, tanto la una como la
otra son susceptibles de contener errores originados en la especificación de requisitos.
• Sobrecargas asociadas: ambas comparten el problema de que suponen un coste extra en el desarrollo del
sistema. Además, en la programación de N-versiones se debe diseñar el proceso director encargado de la
comparación de votos, y en los bloques de recuperación el test de aceptación encargado de decidir si un
proceso está dando un servicio aceptable o no. En cuanto a los costes de procesamiento en tiempo de
ejecución, la programación de N-versiones necesita N veces los recursos de un sistema que cuenta con una
sola versión. Por su parte, aunque los bloques de recuperación necesitan tan sólo un conjunto de recursos en
un instante de tiempo concreto, los procesos de establecer los puntos de recuperación y de restaurar el estado
son costosos.
• Redundancia estática frente a dinámica: en la programación de N-versiones todas las versiones corren en
paralelo, independientemente de si se producen fallos o no. Por el contrario, en los bloques de recuperación,
los módulos alternativos sólo se ejecutan cuando se detecta algún error.

Página 14 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

• Detección de errores: la forma en la que se detectan los errores en ambas técnicas es completamente distinta.
Mientras que en la programación de N-versiones se usa la comparación de votos para detectarlos, en los
bloques de recuperación se usa un test de aceptación.
• Atomicidad: la recuperación de errores hacia atrás utilizada en los bloques de recuperación presenta el
problema de que no se pueden deshacer los errores que se hayan podido producir en el entorno. La
programación de N-versiones no presenta esta limitación debido a que las versiones son atómicas, es decir,
no interfieren entre ellas. Esto significa que cada versión se comunica con el proceso director, en lugar de
hacerlo directamente con el entorno. Sin embargo, es perfectamente viable crear un programa con una
estructura tal que las operaciones no recuperables no se produzcan nunca en los bloques de recuperación.

Es mejor considerar la programación de N-versiones y los bloques de recuperación como técnicas complementarias.
Por tanto, es perfectamente válido y aceptable que se diseñe un sistema de N-versiones que utilice bloques de
recuperación dentro de cada una de dichas versiones.

2.8. Redundancia dinámica y excepciones


Excepción: un programa da una excepción cuando ocurre un error durante su ejecución. Los términos habituales para
referirse a la visualización de la condición de excepción que ha provocado dicho error son generar, arrojar y lanzar.
Los términos más comunes para la respuesta al invocador de dicha excepción son gestión, captura o manejo.

Gestión de excepciones: puede considerarse un mecanismo de recuperación hacia adelante, ya que cuando un
programa arroja una excepción, se pasa el control de la ejecución del código al gestor de dicha excepción, de tal forma
que se puedan iniciar los procedimientos de recuperación.

Uso de las excepciones: originalmente se incluyó el tratamiento de excepciones en los lenguajes de programación
porque era necesario disponer de algún método para gestionar las condiciones anormales que aparecían a veces en
los entornos en los que se ejecutaban los programas. Hoy en día se utilizan para:

• Ofrecer un mecanismo para que un programa pueda reaccionar frente a condiciones anormales que surgen en
el entorno, motivo por el cual apareció el tratamiento de excepciones originalmente.
• Aumentar la tolerancia de un programa a los defectos que pueda tener el mismo.
• Proporcionar capacidades, de propósito general, para la detección de errores y la recuperación frente a éstos.

Componentes ideales: acepta peticiones de servicio y, de ser necesario, invocan los servicios de otros componentes
antes de otorgar una respuesta que puede ser normal o bien una excepción. El fallo puede ser de dos tipos:

• Debido a una petición de servicio ilegal (excepción de interfaz).


• Debido a un mal funcionamiento del componente necesario para servir la petición original.

Cabe la posibilidad de que un componente no pueda tolerar estos tipos de fallos mediante una recuperación de errores
hacia adelante ni hacia atrás. En estos casos, se lanza una excepción de fallo que recibe el componente invocador.
Además, antes de arrojar la excepción, el componente invocado debe volver, siempre que sea posible, a un estado
consistente, de tal forma que en el futuro pueda seguir sirviendo peticiones correctamente.

2.9. Medida y predicción de la fiabilidad del software


Fiabilidad del software: probabilidad de que un programa concreto funcione de manera correcta, durante un periodo de
tiempo determinado, en un entorno dado. Los modelos de estimación de la fiabilidad del software pueden ser
catalogados en dos tipos:

• De crecimiento: son acumulativos y se asientan en la idea de predecir la fiabilidad del software basándose en
su historial de errores, es decir, cuando en un programa se detecta y, posteriormente repara, un error.
• Estadísticos: afrontan el problema de la estimación de la fiabilidad del software mediante la determinación de
los niveles de éxito y fracaso de la respuesta del programa frente a casos de prueba aleatorios, pero no corrigen
los errores encontrados mediante este proceso.

Para que un software se considere fiable, se suele exigir que el grado de fiabilidad del mismo sea de 10 -9, esto es, que
el programa no arroje más de 10-9 fallos por cada hora de operación.

Página 15 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

2.10. Seguridad, fiabilidad y confiabilidad


Seguridad: puede definirse como la ausencia de aquellas causas que ocasionan algún tipo de daño o lesión, ya sea
permanente (irreversible) o temporal (reversible). En el mundo del software, la seguridad se considera habitualmente
en términos de los percances ocurridos. Se entiende por percance cualquier evento, o secuencia de eventos, no
planeado que puede producir daño o lesión. Por lo tanto, la seguridad es la improbabilidad de que se produzca un
percance, independientemente de si el sistema realiza o no la función para la que éste fue diseñado.

Fiabilidad: medida del éxito con el cual un sistema se ajusta a la especificación de su comportamiento, lo que se expresa
habitualmente en términos de probabilidad.

Confiabilidad: propiedad de un sistema que permite confiar en el servicio que dicho sistema ofrece. Se puede definir
en términos de tres componentes:

• Riesgos: son las circunstancias que causan escenarios de no confiabilidad.


• Medios: son los métodos, herramientas y soluciones necesarias para ofrecer un servicio con el nivel de
confianza requerida.
• Atributos: son las medidas mediante las cuales se puede valorar la calidad de un servicio confiable.

Página 16 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

3. Tema 3: Excepciones y manejo de excepciones


3.1. Introducción
Excepción: mecanismo de recuperación de errores.

Errores de sintaxis: son errores que aparecen debido a escribir el código violando las reglas sintácticas del lenguaje de
programación. En los lenguajes compilados, el compilador detecta estos errores en la fase de compilación e informa al
programador. En el caso de los lenguajes interpretados, el intérprete detectará el error al intentar ejecutar la sentencia
inválida.

Errores de ejecución o en tiempo de ejecución: son aquellos que normalmente no es posible detectar al compilar. Al
ejecutar el programa, éste intenta realizar una operación que, a pesar de ser sintácticamente correcta, no se puede
llevar a cabo. Debido a la naturaleza del error, en general, no es posible detectarlo en tiempo de compilación.

Errores lógicos: son aquellos en los que el código puede compilarse y ejecutarse correctamente, pero en los que existe
una discrepancia entre el resultado esperado y el obtenido. Es decir, el programa es correcto desde el punto de vista
sintáctico y de ejecución, pero no hace lo esperado pro el programador.

3.2. Las excepciones


Excepciones: eventos que ocurren durante la ejecución de un programa, haciendo que éste salga de su flujo normal
de instrucciones. Una excepción, por tanto, es un error en tiempo de ejecución, y es la indicación de un problema
asociado con una condición de error que es preciso manejar para que el sistema pueda continuar su ejecución.

Gestión de errores por valor devuelto por la función: alternativa al manejo de excepciones que consiste en el uso de
códigos de error como valor devuelto por la función. De esta manera, es responsabilidad del programador verificar
explícitamente, mediante el uso de sentencias condicionales, el valor devuelto por la función, para comprobar si el
resultado de la ejecución no ha sido satisfactorio o ha ocurrido alguna situación de error. Su uso acarrea algunos
efectos indeseados que pueden degradar la calidad del código. Uno de los problemas de este método es el uso con
funciones que pueden devolver cualquiera de los posibles valores. Otro problema es que a veces puede llevar a un
código enmarañado, ya que es necesario comprobar cada posible valor de error devuelto. Es más, si ocurren sucesivas
invocaciones a la función, es preciso repetir la comprobación para cada una de ellas. Por otra parte, habitualmente se
considera que las sentencias condicionales son más propensas a contener errores que cualquier otro tipo de sentencia,
por lo que, en las mismas circunstancias, el incremento de condicionales introduce una mayor probabilidad de error en
el código. Además, debido a la mayor cantidad de bifurcaciones en el código, evaluar todos los posibles caminos de
ejecución requiere un incremento de los casos de prueba, que se traduce en un mayor tiempo de verificación del código.

Mecanismos de gestión de errores y excepciones: permiten separar el tratamiento de los errores del código normal del
programa, proporcionando un método elegante para gestionar las circunstancias excepcionales, y que fomenta una
mayor legibilidad del código. Esto se traduce, en la práctica, en que la ocurrencia de un error durante la ejecución
normal del código de programa provoca la ejecución de un código de tratamiento y recuperación del error. Cuando
ocurre una excepción, se dice que se ha lanzado o señalizado y a realizar las acciones adecuadas para resolver la
situación se conoce como manejar o atrapar. Un mecanismo de manejo de excepciones debe cumplir los siguientes
requerimientos:

• Debe ser simple y fácil de entender y usar.


• No debe oscurecer el flujo normal del programa en ausencia de error.
• No debe haber sobrecarga en las condiciones normales de operación.
• El tratamiento de las excepciones debe ser uniforme con independencia de si son detectados por el entorno o
por el programa.
• Debe permitir la programación de acciones de recuperación.

3.2.1. Detección de excepciones

Clasificación de excepciones: según dónde se detecta la ocurrencia de una excepción se pueden categorizar en dos
tipos: las detectadas por el entorno de ejecución, y las detectadas por el programa. Además, dependiendo del momento
en que se detecta el error, se distingue entre excepciones síncronas, que se producen inmediatamente como resultado

Página 17 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

de un código que intenta una operación apropiada, o bien asíncronas que pueden aparecer algún tiempo después de
la operación que provoca el error. Combinando ambas clasificaciones, se obtiene:

• Detectadas por el entorno y lanzadas de forma síncrona.


• Detectadas por el entorno y lanzadas de forma asíncrona.
• Detectadas por el programa y lanzadas de forma síncrona.
• Detectadas por el programa y lanzadas de forma asíncrona.

3.2.2. Declaración de excepciones

Declaración: hay varios modelos posibles. En Ada, se interpretan como una nueva constante que debe ser declarada
explícitamente:

Nuevo_Tipo: exception;

De esta manera, es posible representar errores que pueden aparecer durante la ejecución del programa. El paquete
Standard de Ada proporciona una serie de excepciones predefinidas:

• Contraint_Error: representa la condición de error que ocurre al violar una restricción impuesta en la
declaración.
• Program_Error: representa el intento de violar una estructura de control.
• Storage_Error: ocurre cuando el uso requerido del almacenamiento es superior al disponible.
• Tasking_Error: representa un error en la comunicación o en la gestión de las tareas.

Otra opción es verlos como un objeto de un tipo determinado, que puede o no haber sido declarado explícitamente.

3.2.3. Dominio de una excepción

Dominio del manejador: especifica la zona del código en la que el manejador será activado si se lanza la excepción.
Es posible que dentro de un programa existan diferentes manejadores para una excepción en particular, cada uno de
los cuales lleva asociado un domino. Será, por tanto, la definición del domino la que determine cuál es el manejador
encargado de tratar una ocurrencia de una excepción, según la parte del código en la que ésta haya sido generada.
Mientras mayor sea la precisión con la que se puede definir un dominio, mayor será también la precisión con la que se
puede detectar la fuente del error.

3.2.4. Propagación de excepciones

Propagación de excepciones: es el mecanismos que provee el lenguaje para elevar las excepciones entre bloques del
programa. Tras la ocurrencia de una excepción, es necesario determinar qué manejador es el encargado de tratarla,
utilizando el concepto de dominio. Sin embargo, puede darse la circunstancia de que no exista un manejador definido
para tratar la excepción actual. En este caso, es preciso determinar qué ocurre con la excepción, es decir, que parte
del código es responsable de lidiar con el problema. Existen dos posibles enfoques para gestionar esta situación:

• Dinámicamente: en caso de que no exista un manejador local definido para tratar la excepción, ésta se
propaga hacia atrás en la cadena de invocación. Esta búsqueda se realiza en tiempo de ejecución, de ahí la
consideración de dinámico. Un problema que surge con este enfoque es que existe la posibilidad que una
excepción se propague fuera de su ámbito de visibilidad. Para evitar este problema, se pueden utilizar
manejadores por defecto, que atrapan cualquier tipo de excepción. En caso de que no se encuentre un
manejador adecuado, la excepción no atrapada provocará la terminación de un programa secuencial. Si el
programa contiene más de un proceso, normalmente lo que se hace es abortar el proceso en el que aparece
la excepción no manejada.
• Estáticamente: los manejadores deben estar definidos en el contexto en que se lanza la excepción. Si no
existe un manejador adecuado, se genera un error de compilación, ya que se considera que el programador
debería haberlo declarado. Sin embargo, el problema de este enfoque es que a veces una excepción solo
puede ser manejada en el contexto en que el procedimiento fue invocado.

3.2.5. Modelos de tratamiento de excepciones

Modelo de reanudación: se basa en asumir que el manejador de excepciones ha sido capaz de reparar el problema
que causó la excepción. Una vez restaurado el estado válido, el invocador puede continuar la ejecución. Para entender
Página 18 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

mejor el modelo de reanudación, éste se puede ver como una llamada a una función implícita, cuando ocurre una
excepción, que intenta recuperar la situación para poder continuar con la ejecución. Las mayores críticas a este modelo
apuntan a que la dificultad de implementación no compensa las ventajas de su uso, y que la reanudación hace el flujo
de control de un programa difícil de seguir, y por tanto más propenso a errores.

Figura 3.1 Modelo de reanudación

Modelo de terminación: el invocador no retoma el control de flujo de programa, finalizando así la ejecución del código
del cual se produjo la excepción.

Figura 3.2 Modelo de terminación

Modelo híbrido o mixto: este modelo comparte características de los dos anteriores, siendo el propio manejador el
responsable de decidir si continúa la operación que causó la excepción o por el contrario se termina.

3.2.6. Cuando usar excepciones

• En general, deben usarse para tratar con condiciones anormales, no para realizar el control del flujo normal de
ejecución de una aplicación. Qué es una condición anormal y qué no lo es puede depender del contexto y del
criterio del programador.
• El uso de throw debería limitarse a señalizar un error, es decir, la incapacidad del código para cumplir con el
resultado esperado. No se debe utilizar para depurar errores de programación, o al descubrir un incumplimiento
de una invariante de un componente.
• Un manejador de excepción sólo debería atrapar una excepción en caso de que esté capacitado para recuperar
la situación, por ejemplo, corrigiendo la causa del error o relanzando como un nuevo tipo de excepción.

3.2.7. Ventajas y desventajas de usar excepciones

Ventajas:

• En el caso de los lenguajes de programación tradicional, las excepciones proporcionan un método más limpio
y simple para separar el flujo normal de ejecución del código de tratamiento de situaciones anormales o
erróneas, mejorando por tanto la legibilidad y facilidad de mantenimiento de código. Además, no es necesario
maquinaria extra para propagar tanto los casos de éxito como de error al invocador.
• En el caso de los lenguajes orientados a objetos, el uso de excepciones conlleva un agrupamiento jerárquico
de los tipos de error, que permite una mejor representación del dominio del problema, y un tratamiento de los
errores en diferentes granularidades.

Inconvenientes: para poder implementar el mecanismo de gestión de excepciones, el compilador introduce código
extra. Este código puede tener que ser también verificado durante las pruebas de la aplicación, lo que incrementa el
Página 19 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

esfuerzo y coste del desarrollo. Además, puede afectar a la compresión del código por parte del programador, lo que
a su vez dificulta la escritura de código robusto.

3.3. Las excepciones en Java


Manejo de excepciones: se basa en el modelo de terminación, es decir, cuando se produce una excepción se finaliza
el flujo de ejecución actual y se devuelve el control al punto de invocación. Al ser un mecanismo integrado en la
programación orientada a objetos, todos los tipos de excepciones se definen como subclases de una clase base,
Throwable. Un objeto de la clase java.lang.Throwable, o una de sus clases derivadas, representa una excepción o
error que puede ser lanzado tanto por la máquina virtual de Java, como por una sentencia throw de código Java.

3.3.1. Tipos de excepciones

Tipos de excepciones: todos los tipos de excepciones derivan de la clase Throwable, que a su vez se especializa en
Exception y Error:

• Exception y todas sus subclases se utilizan para todas aquellas excepciones de las que el programa podría
querer recuperarse. Un tipo particular de Exception son las excepciones en tiempo de ejecución, representadas
por la clase RuntimException y sus derivadas. Son condiciones de error internas a la aplicación, que ocurren
durante la evaluación de expresiones, tales como un puntero nulo o un índice fuera de rango, cuya recuperación
podría ser posible.
• Error y sus subclases representan condiciones excepcionales que son externas a la aplicación, y que por tanto
ésta normalmente no puede anticipar o recuperarse del error.

Excepciones: se dividen en dos tipos según si el compilador comprueba la existencia de manejador asociado o no:

• Excepciones comprobadas. Son de la clase Throwable y todas sus subclases que no son RuntimeException
o Error, ni sus subclases. Son condiciones excepcionales que están sujetas a la comprobación por parte del
compilador, y por tanto deben cumplir con el requisito atrapar o especificar, es decir, si un método contiene
código que pueda lanzar un tipo de excepción, entonces debe gestionar la excepción internamente o bien
especificarlo en su definición mediante la cláusula throws.
• Excepciones no comprobadas. Son las clases RuntimeException y sus subclases, y las clases Error y su
subclases. Son condiciones excepcionales o de error que el compilador no verifica, y por tanto es decisión del
programador el atraparlas o no.

3.3.2. Funcionamiento del mecanismo de excepciones

Funcionamiento: un programa válido escrito en Java debe respetar lo que se conoce como requisito atrapar o
especificar (catch o specify requirement). Este requisito consiste en que, si un código pudiera generar una excepción
comprobada, entonces es preciso que, o bien se enmarque dentro de un bloque try, que debe definir un manejador
para la excepción, o bien el método que contiene el código que puede generar la excepción lo declare mediante la
cláusula throws. Es decir, cada método que debe definir una lista de excepciones Throwable, que indica cuales son las
excepciones que pueden ser generadas dentro de su código, peor no son atrapadas. De esta manera, el compilador
puede detectar si un método intenta arrojar una excepción que no se ha declarado y generar un error de compilación.
A diferencia de las excepciones comprobadas, tanto los errores como las excepciones en tiempo de ejecución no están
sujetas a esta restricción.

Lanzamiento de una excepción: una excepción se genera utilizando la sentencia throw, seguida del objeto throwable
que contiene la información asociada a la excepción.

Manejador de excepciones: en Java se define mediante los bloques que generan la estructura try { } … catch { }:

• Sentencia try. Se usa para definir el inicio de un bloque de código dentro del cual se podría generar una
excepción. Si el manejador no puede recuperar el error y lo propaga al invocador, puede ser necesario que
realice determinadas acciones, por ejemplo, para liberar recursos o para asegurase que el sistema se mantiene
en un estado consistente. La cláusula finally se puede incluir como parte de una sentencia try, para ejecutar
el código de últimas voluntades. El código que se define dentro de finally se ejecuta, en cualquier caso, con
independencia de si han aparecido o no excepciones.

Página 20 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

• Sentencia catch. Se puede ver como una declaración de función, en la que el parámetro identifica el tipo de
excepción que debe ser atrapada.

Dentro del código del manejador, el nombre del objeto que identifica la excepción se comporta como una variable local.
Aunque las clases Errory RuntimeException pueden ser utilizadas como argumento en una cláusula catch, en general
suele ser más indicado no hacerlo. En el caso de los de tipo Error, habitualmente son debidos a una causa externa y
la aplicación probablemente no puede hacer nada para recuperarlos. En el caso de RuntimeException, generalmente
son debidos a errores de programación, por lo que tiene más sentido depurar el código y eliminar el error.

3.3.3. Información contenida en la excepción

Información contenida en la excepción: al arrojar una excepción se asociada un objeto instancia de Throwable, que
contiene la siguiente información:

• Tipo de excepción (clase).


• Lugar donde ha ocurrido la excepción (traza de la pila de ejecución).
• Información de contexto (mensaje de error y otros tipos de información adicional que ayudan a determinar el
estado del sistema en el momento en que ocurrió la excepción).

La clase Throwable proporciona algunos métodos para acceder a dicha información como getMessage( ) o
getStackTrace( ).

Encadenamiento (exception chaining): técnica comúnmente utilizada en la gestión de las excepciones. Bajo ciertas
condiciones, un manejador de excepciones responde a la ocurrencia de una excepción generando otra excepción. Este
método se utiliza para encapsular una excepción, aportando información adicional y añadiendo capas de abstracción.
En lugar de propagar directamente a los niveles superiores el error ocurrido en la capa de bajo nivel, se realiza una
traducción que permite adaptar el significado a las abstracciones de la capa superior.

Página 21 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

4. Tema 4: Programación concurrente


4.1. Introducción
Programación concurrente: técnica de programación y sus notaciones que permiten expresar el paralelismo potencial
y la solución de los problemas de sincronización y comunicación resultantes. Hay tres motivaciones principales para
escribir programas concurrentes:

• Modelizar el paralelismo existente en el mundo real.


• Utilizar plenamente el procesador.
• Permitir el uso de más de un procesador para resolver un problema.

Programación secuencial: en esta técnica, el programador tiene que construir el sistema de forma que se haga una
ejecución cíclica de un programa secuencial para que permita manejar las diversas actividades concurrentes. Esto
complica aún más la tarea del programador, y supone el tener que considerar estructuras que son irrelevantes para el
control de las actividades que se tienen que hacer. Los programas resultantes son más oscuros y poco elegantes,
haciendo más compleja la descomposición del problema.

4.2. Noción de proceso, tarea e hilo


Proceso: se puede considerar que es un programa en ejecución. Cada vez que se ejecuta un programa, el sistema
operativo crea un proceso asociado con dicho programa. Un programa secuencial contiene un solo camino para una
ejecución mientras que un programa concurrente consistirá en una colección de procesos secuenciales autónomos
en paralelo, con diferentes caminos de ejecución.

Programa concurrente: puede verse como un conjunto de procesos secuenciales o autónomos, que son ejecutados
en paralelo. Todos los lenguajes de programación concurrente incorporan de forma implícita o explícita el concepto de
proceso, donde cada proceso tiene su propio hilo de control, que se ejecuta en su propia máquina virtual.

Ejecución de tareas: la implementación real (ejecución) de un conjunto de tareas tiene lugar en una de las tres
siguientes formas, y posibles combinaciones de estas:

• Se multiplexan en un único procesador (multiprogramación).


• Sistema multiprocesador con memoria compartida (multiprocesamiento).
• Se ejecutan en varios procesos sin memoria compartida (sistemas distribuidos).

Solo el segundo y tercer caso suponen una ejecución en paralelo real. Cuando se usa el término concurrencia, lo que
se indica es paralelismo potencial. Los lenguajes de programación concurrente permiten al programador expresar
actividades lógicamente paralelas sin tener en cuenta su implementación.

Ejecución de un programa concurrente: no es tan directa como la ejecución de un programa secuencial. Los procesos
deben ser creados y finalizados, así como distribuidos entre los procesadores disponibles. Estas actividades son
efectuadas por el sistema de soporte de tiempo real (Run-time Support System, RTSS) o núcleo de ejecución.

Sistema de soporte de tiempo real: posee muchas de las características del planificador de un sistema operativo, y
está ubicado, entre el hardware/sistema operativo y el software de aplicación. Puede tomar una de las siguientes
formas:

• Una estructura de programa que es parte de la aplicación (librerías C y C++).


• Un sistema software estándar generado con el código objeto del programa (Ada o Java).
• Una estructura hardware microcodificada en el procesador (aJile System aJ100).

El RTSS o sistema operativo tiene que disponer de algoritmos de planificación que decidan las tareas que se tienen
que ir ejecutando, aunque en un programa que esté bien construido, su ejecución lógica no depende de la planificación.

En la actualidad se considera que todos los sistemas operativos proporcionan el concepto de proceso, que se ejecuta
en su propia máquina virtual para evitar interferencias con otros procesos.

Página 22 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

Hilo: tareas creadas en la misma máquina virtual mediante los mecanismos proporcionados por el sistema operativo.
Estos hilos, en muchas ocasiones, son transparentes al propio sistema operativo. Los hilos tienen acceso sin
restricciones a su máquina virtual, por lo que el programador y el lenguaje deben proporcionar la protección de posibles
interferencias.

Programación orientada a objetos: se pueden considerar dos clases de objetos:

• Activos: ejecutan acciones espontáneas, en un procesador, permitiendo que se realice la computación.


• Reactivos: realizan acciones cuando son invocados por un objeto activo. Los recursos serían reactivos,
aunque puedan realizar el control de sus estados internos, y el control de cualquier recurso real.

La implementación de las entidades de los recursos necesita algún agente de control. Cuando este agente de control
es en sí mismo pasivo, como ocurre con un semáforo, se dice que el recursos es protegido o sincronizado. Si se
requiere a nivel de control entones el recurso sería en cierto sentido activo, denominando a este tipo de entidades como
servidor, y recursos protegido a los pasivos.

Por tanto, se pueden considerar cuatro tipos de entidades:

• Pasiva: reactivo sin restricciones de sincronización. Necesita un hilo de control externo.


• Recurso protegido: reactivo con restricciones de sincronización.
• Activo: con una hebra interna explícita o implícita.
• Servidor: activo con restricciones de sincronización.

Entidades en lenguajes de programación concurrente:

• Las entidades activas se representan mediante hilos.


• Las entidades pasivas pueden representarse directamente como variables de datos, o encapsulados en
construcciones del tipo módulo, paquete o clase con una interfaz procedural.
• Los recursos protegidos también se pueden encapsular en módulos, disponiendo de un servicio de
sincronización de bajo nivel.
• Los servidores requieren de una tarea, ya que es necesario programar el agente de control.

Los recursos suelen implementarse eficientemente ya que normalmente emplean un agente de control de bajo nivel.
Pero, para algunas clases de tareas, esto puede ser poco flexible, y llevar a estructuras de programas deficientes. Los
servidores son esencialmente flexibles, ya que el agente de control se programa mediante un proceso. El inconveniente
de esta aproximación es que puede desembocar en un aumento de tareas y en un elevado número de cambios de
contexto durante la ejecución. Esto es particularmente problemático si el lenguaje no permite recursos protegidos y hay
que utilizar un servidor para cada cantidad.

4.3. Ejecución concurrente


Implementación de la programación concurrente: varía de un sistema operativo a otro y de un lenguaje a otro, pero se
tienen que considerar tres mecanismos básicos que deben proporcionar los sistemas operativos y lenguajes:

• La expresión de actividades concurrentes a través de tareas e hilos.


• Sincronización entre actividades concurrentes.
• Primitivas de soporte a comunicación entre actividades concurrentes.

Tipos de tareas: en función de la interacción entre tares se distinguen tres tipos de conductas:

• Tareas independientes: no se sincronizan ni comunican con las otras.


• Tareas competitivas: son tareas independientes que se tienen que comunicar y sincronizar para utilizar
recursos que son limitados.
• Tares cooperativas: son tareas que se comunican y sincronizan con las otras tareas.

Estructura de un proceso: se puede considerar estática o dinámica.

• Estructura estática: el número de procesos es fijo y conocido en tiempo de compilación.

Página 23 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

• Estructura dinámica: los procesos son creados en cualquier momento. En este caso, el número de procesos
existentes solo se determina en tiempo de ejecución.

Nivel de paralelismo: se puede considerar que las tareas se definen en cualquier nivel del texto del programa,
pudiéndose definir tareas dentro de otras tareas (nivel de paralelismo anidado), o bien las tareas se definen
únicamente en el nivel más externo del texto del programa (nivel de paralelismo plano).

Granularidad: dentro de los lenguajes que soportan construcciones anidades hace referencia la paralelismo de grano
fino o grano grueso. Un programa concurrente de grano grueso contiene pocas tareas, cada una de larga duración.
Los programas con paralelismo de grano fino tienen un mayor número de tareas sencillas, muchas de ellas de poca
duración.

Inicialización de una tarea: se da información relacionada con su ejecución, hay que inicializarla, para lo cual o bien se
pasa la información en forma de parámetros a la tarea, o se comunica con la misma una vez que ha empezado su
ejecución.

Terminación de una tarea: se puede realizar de diferentes formas:

• Se ha completado la ejecución del cuerpo de la tarea.


• Se ha ejecutado una sentencia del tipo auto-terminación.
• Se ha abortado la tarea por una acción explícita de otra tarea.
• Ha ocurrido una condición de error no tratado.
• Termina cuando ya no es necesaria la tarea.
• Nunca finaliza ejecutándose en un bucle sin terminación.

Jerarquías de las tareas: cuando se tiene una anidación de niveles se pueden crear jerarquías entre tareas. Las tareas
tienen que distinguir entre aquellas tareas que son responsables de su creación (relación padre/hijo) y aquellas que
son afectadas por su terminación (relación guardián/dependiente). En este segundo caso, un guardián no puede
terminar hasta que no hayan terminado todos sus dependientes, lo que supone que un programa no puede terminar
hasta que todas las tareas creadas en él hayan terminado. En una jerarquía de tareas, el aborto de un guardián supone
el aborto de todas las tareas que dependen de él. En el caso de estructuras estáticas, el padre y el guardián son el
mismo, si son estructuras dinámicas, pueden ser diferentes.

Figura 4.1 Diagrama de estado de una tarea o proceso

Mecanismos de representación de la ejecución concurrente: hay tres mecanismos básicos:

• Mediante fork y join.


• Cobegin.
• Declaración explicita de las tareas.

Página 24 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

Fork y join: se basa en dos instrucciones. La instrucción fork (bifurca) especifica que una rutina deberá ejecutarse
concurrentemente con el invocador. La instrucción join (reúne) hace que el invocador espere a la finalización de la
rutina invocada. Estas instrucciones permiten crear tareas dinámicamente, y proporcionan un mecanismo para pasar
información a la tarea hijo a través de parámetros. Normalmente, al terminar el hijo, devuelve un solo valor. Aunque
flexibles, fork y join no proporcionan una aproximación estructurada de la creación de procesos y su utilización es
propensa a errores.

Código 4.1 Ejemplo de uso de fork y join

Cobegin: es un método estructurado para denotar la ejecución concurrente de un conjunto de instrucciones. La


instrucción cobegin termina cuando han terminado todas las instrucciones concurrentes. Cada instrucción debe ser una
sentencia válida del lenguaje. Si se invoca algún procedimiento, se podrán pasar datos a los procesos invocados
mediante los parámetros de la llamada. La instrucción cobegin podría incluir, a su vez, una secuencia de instrucciones
donde aparecería cobegin, y así construir una jerarquía de procesos. Cobegin tampoco es muy usado actualmente.

Código 4.2 Ejecución concurrente con cobegin

Declaración explícita de procesos: permite que las propias rutinas establezcan su ejecución concurrente, permitiendo
que la estructura de un programa concurrente sea más clara y fácil de entender. Otros lenguajes que soportan la
declaración explícita de procesos, como Ada, permiten la creación implícita de tareas. Todos los procesos declarados
dentro de un bloque comienzan a ejecutarse concurrentemente al final de la parte declarativa de dicho bloque.

Código 4.3 Declaración explícita de procesos

4.4. Multiprocesador y sistemas distribuidos


Multiprocesador: las tareas multiplexan sus ejecuciones sobre un sistema multiprocesador, donde se accede a una
memoria compartida. Se suele suponer que todas las tareas se pueden ejecutar en cualquiera de los procesadores del
sistema. Las tareas son planificadas para cualquiera de los procesadores, es decir, se hace una planificación global.
Las tareas pueden en su tiempo de vida migrar de un procesador a otro.

Página 25 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

Afinidad del procesador: consiste en fijar tareas a los procesadores, sin permitir que migren, de forma que se consiguen
tiempos de respuesta más previsibles.

Sistemas distribuidos: las tareas multiplexan sus ejecuciones en varios procesadores que no comparte memoria. Las
tareas se pueden asignar a diferentes procesadores, aunque se suele dar alguna otra forma de encapsulación que
representa la unidad de distribución; por ejemplo, procesos, objetos, agentes, particiones, etc., que sirvan como interfaz
para encapsular los recursos locales y dar un acceso remoto bien definido. Producir una aplicación que se ejecute en
un sistema distribuido supone varias etapas que no son necesarias cuando se hace para una plataforma con uno o
múltiples procesadores:

• Particionado: proceso de dividir el sistema en partes (unidades de distribución) adecuadas para ser situadas
sobre elementos de proceso del sistema en cuestión.
• Configuración: tendrá lugar cuando las partes en las que está particionado el programa se encuentren ya
asociadas con elementos de proceso concretos del sistema en cuestión.
• Asignación: cubre el proceso real de convertir el sistema configurado en un conjunto de módulos ejecutables,
y descargar éstos sobre los elementos de procesamiento del sistema en cuestión.
• Ejecución transparente: es la ejecución del software distribuido de modo que sea posible acceder a los
recursos remotos independientemente de su ubicación.
• Reconfiguración: cambio dinámico de ubicación de un componente o recurso software.

Mientras que las tareas se pueden asignar a diferentes procesadore en un sistema distribuido, es más habitual para
proporcionar algún otro método de encapsulación para representar a la unidad de distribución.

Los lenguajes que explícitamente se conciben para abordar la programación distribuida proporcionan soporte
lingüístico, al menos en la etapa de particionado y en el desarrollo del sistema, proponiéndose como unidades de
distribución de procesos, objetos, particiones, agentes y vigilantes. Todas estas construcciones sirven de interfaz que
les permiten encapsular los recursos locales y proporcionan acceso remoto bien definidos.

La asignación y reconfiguración requieren el apoyo del entorno de programación y del sistema operativo.

Para la ejecución transparente se precisan mecanismos que permitan que los procesos no tengan que tratar con la
forma de bajo nivel de los mensajes. También se tiene que presuponer que todos los mensajes recibidos por los
procesos usuario se encuentran intactos y en buenas condiciones, que los mensajes que reciba cada proceso sean de
la clase que éste espera, y que no haya restricciones para la comunicación entre procesos con relación a los tipos
predefinidos en el sistema.

Estándares para la comunicación de programas distribuidos:

• Mediante una interfaz de programación de aplicaciones (Application Programming Interface, API).


• Mediante el programa de llamada a un procedimiento remoto (Remote Procedure Call, RPC). Tiene como
objetivo hacer que la comunicación distribuida sea tan simple como sea posible. Generalmente se utiliza para
comunicar programas escritos en el mismo lenguaje. Entre los procedimientos, uno de ellos (servidor) es
llamado remotamente. A partir de la especificación del servidor, se generan automáticamente dos
procedimientos más un resguardo de cliente (client stub) y un resguardo de servidor (server stub). El
resguardo de cliente queda en el servidor donde se origina la llamada remota. El resguardo de servidor se
ubica en el mismo lugar que el procedimiento de servicio. La función de estos procedimientos es disponer de
un enlace transparente entre el cliente y el servidor. El resguardo del cliente identifica la dirección del
procedimiento del servidor, realiza el empaquetado de parámetros (convierte los parámetros de la llamada en
un bloque de bytes para su transmisión), realiza la petición de ejecución al servidor, espera la respuesta del
servidor y devuelve el control al cliente junto con los parámetros devueltos. El resguardo del servidor recibe
peticiones del procedimientos cliente, desempaqueta los parámetros, invoca al servidor, atrapa las excepciones
generadas por el servidor, empaqueta los parámetros devueltos y envía la respuesta al cliente.
• Mediante el paradigma de objetos distribuidos. Permite la creación dinámica de un objeto sobre una
máquina remota, la identificación de un objeto por determinar y alojado en cualquier máquina, la invocación
transparente de un método remoto sobre un objeto como si fuera un método local, sin importar el lenguaje en
el que está especificado el objeto, y el dispacher de ejecución de la llamada a un método a través de la red.

Página 26 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

4.5. Concurrencia en Java


Creación de un hilo: hay dos formas, declarar una clase como subclase de Thread y reescribir el método run, o declarar
una clase que implemente la interface Runnable. En Java no se tiene el concepto de maestro o guardián: un hilo puede
esperar a que termine otro hilo invocando el método join, y con el método isAlive verificar si el hilo objetivo ha terminado.

Identificación de hilos: se pueden identificar de dos formas:

• Si el código del hilo es una subclase de Thread, se define el identificador de la forma:

Thread IDhilo;

• Si el hilo se ha implementado como una clase que implementa Runnable, el identificador es de la forma:

Runnable IDcodigohilo;

La identificación del hilo que se está ejecutando se puede hacer con el método currentThread.

Terminación de un hilo:

• Al finalizar la ejecución del método run, que se puede producir con normalidad o como resultado de una
excepción no tratada.
• Mediante el método stop, que libera los bloques sobre los objetos y ejecuta cláusulas finally. Esta forma es
inherentemente insegura, se indica ya como obsoleto, por lo que no debería ser usado.
• Mediante el método destroy, que termina directamente.

Clases de hilos:

• Hilos usuarios (user).


• Hilos demonios (daemon): proporcionan servicios y normalmente no finalizan hasta que no quedan hilos
usuario. El método setDaemon se usa para identificar los hilos daemon, y debe ser invocado antes de iniciar
el hilo.

Tipos de excepciones: hay dos tipos de excepciones asociadas a los hilos:

• IllegalThreadStateException. Se produce cuando el método start se ha invocado después de iniciar el hilo, o el


método setDaemon se ha invocado después de iniciar el hilo.
• InterruptException. Se produce si un hilo que ha invocado un método join es despertado por el hilo que está
siendo interrumpido en vez de por el hilo dependiente.

Estados de un hilo en Java: el hilo se crea cuando un objeto de la clase Thread es creado, en ese momento el hilo aún
no se ejecuta. Una vez que el método start se invoca, el hilo es elegible por el planificador. Si el hilo llamada al método
wait en un objeto, o el método join es invocado desde otro objeto hilo, el hilo pasa a bloqueado y no será elegible para
ejecución. Pasará a ejecutable como resultado de la notificación asociada a un método que es llamado por otro hilo, o
si el hilo con el que tiene una solicitud de join ha muerto. Un hilo entra en el estado muerto como resultado de la salida
del método run o porque su método destroy ha sido invocado.

Página 27 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

Figura 4.2 Estados de un hilo en Java

Página 28 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

5. Tema 5: Sincronización y comunicación basadas en variables compartidas


5.1. Introducción
Proceso: proporciona un contexto de ejecución completo, con sus propios recursos y espacios de memoria para permitir
la ejecución de un programa. Los procesos pueden contener varios hilos de ejecución, que comparten entre sí los
recursos del proceso, como la memoria o los ficheros abiertos. Aunque ambos proporcionan un entorno de ejecución,
habitualmente la creación de un nuevo hilo requiere muchos menos recursos que la de un proceso.

5.2. Variables compartidas


Variable compartida: son objetos accesibles por varias tareas, que pueden leerlas y escribirlas a conveniencia, sin que
haya un intercambio explícito de información. Cuando varios procesos acceden a información compartida mediante
variables, se requiere sincronización para asegurar una correcta interacción. Es importante asegurar que el acceso y
modificación de las variables compartidas se realiza de una forma atómica o indivisible, es decir, la operación sobre
una variable se lleva a cabo de forma que ningún otro proceso puede interrumpir o interactuar con ella hasta que haya
terminado. Si el acceso a las variables no es atómico, pueden existir secuencias de ejecución que generen un resultado
incorrecto.

Condición de carrera: dependencia del resultado con el orden de ejecución de los procesos.

Sección crítica: secuencias de instrucciones de código en las que es preciso garantizar la atomicidad.

Exclusión mutua: no puede haber al mismo tiempo dos tareas ejecutándose dentro de una sección crítica.

5.3. Espera activa u ocupada


Espera activa: mecanismo básico para la implementación de los métodos de sincronización que consiste en utilizar una
variable que actúa como bandera para controlar el acceso. El proceso debe comprobar continuamente el valor de la
variable, para poder detectar posibles cambios en su estado. Conlleva un uso ineficiente de la CPU, ya que un proceso
en espera malgastará todos sus ciclos de ejecución en la comprobación del estado, cuando podría asignarse ese
tiempo a otros procesos.

Sincronización de condición: es relativamente fácil de implementar mediante la espera activa. Se utiliza una variable
para representar la condición que debe satisfacerse para que el proceso pueda continuar su ejecución. Mientras tanto,
éste utiliza su tiempo de ejecución para comprobar el valor de la variable. El proceso que señaliza la condición
simplemente modifica el valor de la bandera.

Código 5.1 Condición de sincronización con espera activa

Exclusión mutua: no es fácil implementarla con espera activa. Una solución es el algoritmo de Peterson, que se basa
en el uso de dos variables compartidas, que indican la intención del proceso de entrar en la sección crítica, y una
variable de turno, que regula el acceso en caso de conflicto. Cuando un proceso desea obtener la exclusividad, lo
primero que hace es activar su bandera correspondiente y ceder el turno al otro proceso. A continuación, espera
Página 29 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

mientras no sea su turno y el otro proceso quiera entrar en la sección crítica. El algoritmo de Peterson se puede
generalizan fácilmente para 𝑁 procesos.

Código 5.2 Algoritmo de Peterson

5.4. Suspender y reanudar


Suspensión y reanudación: cuando una tarea encuentra una situación en la que debe esperar una condición externa
para continuar su ejecución, pasa al estado de suspensión, es decir, se elimina de la lista de tareas ejecutables y pasa
a la lista de tareas suspendidas. Cuando la condición se cumple, la tarea es reanudada. Uno de los principales
problemas del mecanismo es que puede presentar deadlock, llevando a una situación en la que se impide la evolución
de los procesos de ejecución.

5.5. Semáforos
Semáforos: su funcionamiento se basa en una variable que puede tomar valores enteros, sobre la que se definen tres
operaciones atómicas, llamadas habitualmente inicialización (init), espera (wait) y señalización (signal). Las
operaciones se definen así:

• Inicialización: asigna un valor inicial al semáforo. Una vez inicializado, el semáforo solo puede ser modificado
a través de las operaciones de espera y señalización. Tampoco es posible leer su valor actual.
• Señalización: incrementa en uno el valor del semáforo.
• Espera: disminuye en uno el valor del semáforo. Si el resultado es negativo, se bloquea la tarea hasta que el
valor del semáforo sea positivo.

Bloquear una tarea: significa que ésta se elimina temporalmente del planificador de ejecución, por lo que no se le asigna
tiempo de procesador hasta que se vuelven a satisfacer las condiciones adecuadas para que las tareas vuelvan a
reanudarse. De esta manera la espera es más eficiente, ya que no se malgasta el tiempo de la CPU en comprobar el
estado, como ocurre con la espera activa.

Utilización de semáforos: algunos de los problemas habituales de sincronización que son fáciles de resolver con
semáforos son los siguientes:

Página 30 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

• Notificación: a veces un proceso o hilo necesita esperar que ocurra un evento, o se ejecute una sección de
código en otro proceso, antes de proseguir su ejecución. En este caso, la solución con semáforos es trivial.

Código 5.3 Notificación mediante semáforos

• Encuentro o rendezvous: es una generalización del problema anterior, en el que ambas tareas esperan a la
otra, de ahí el nombre. De esta manera, se tiene una solución simétrica, en la que cada proceso, después de
ejecutar la sección de código previa al encuentro, notifica al otro proceso que está listo para continuar y pasa
a esperarle. El primero de los dos que llegue al punto de encuentro, esperará al otro para que ambos puedan
proseguir su ejecución.

Código 5.4 Encuentro mediante semáforo

• Exclusión mutua: se inicializa el semáforo a uno, luego, cada proceso que quiera entrar en la sección crítica
debe hacer una espera antes, y señalizar al salir. Así, el primer proceso que ejecute la espera en el semáforo
encontrará un valor positivo y podrá proseguir la ejecución del código, entrando en la sección crítica. Mientras
tanto, cualquier otro proceso encontrará un valor de cero, por lo que pasará a bloquearse hasta que el primero
señalice el semáforo. El problema puede generalizarse fácilmente para permitir 𝑛 accesos simultáneos. Basta
con inicializar el semáforo al valor 𝑛.

Página 31 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

Código 5.5 Exclusión mutua

• Barrera: es una generalización del en encuentro. Se trata de varios procesos que deben esperar a reunirse en
un punto de la ejecución, antes de proseguir. Para resolver este problema, se utilizan dos semáforos: mutex,
para la exclusión mutua, y barrera, para reunir a los procesos. Inicialmente, barrera vale cero, por lo que
cualquier llamada a wait provocará la suspensión del proceso invocador. El semáforo mutex se utiliza para
garantizar el acceso exclusivo a la variable procesos, que lleva la cuenta de los procesos que han llegado al
punto de encuentro. A medida que los procesos van llegando a la barrera, pasarán a la espera. Finalmente,
cuando el último proceso verifica que la cuenta es 𝑛, señaliza la barrera, permitiendo proseguir la ejecución a
uno de los procesos en espera. Esto provoca una reacción en cadena, donde cada proceso que despierta
señalizará al siguiente, hasta que todos los procesos puedan continuar de nuevo la ejecución.

Código 5.6 Barrera mediante semáforos

5.6. Monitores
Sección crítica: bloque de código que debe ejecutarse en exclusión mutua, pero que podría no hacerlo, por ejemplo,
debido a un error de programación.

Región crítica: mecanismo proporcionado por el lenguaje de programación, por lo que automáticamente garantiza la
exclusión mutua en la ejecución.

Región crítica condicional: bloque de código del que se garantiza la ejecución en exclusión mutua, en relación con una
variable de condición. Antes de entrar a la región, se comprueba la condición de entrada. Si ésta evalúa a cierto, se
permite la entrada, en caso contrario la tarea es bloqueada.
Página 32 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

Monitor: se define como un tipo abstracto de datos, de tal manera que en cualquier momento solo permite que se esté
ejecutando uno de sus métodos. Además, el monitor contiene los datos y procedimientos necesarios para la asignación
y control de uno o varios recursos compartidos por diferentes procesos. Por definición, los monitores proporcionan
exclusión mutua, ya que no es posible que más de un proceso se encuentre ejecutando uno de sus métodos. Por tanto,
no es preciso codificar explícitamente la sincronización. No obstante, dado que puede requerirse otro tipo de
sincronización, se utilizan variables de condición, que se comportan de forma parecida a los semáforos, definiendo dos
tipos de operación: wait, para hacer que un proceso espere a que se cumpla una determinada condición, y signal, para
despertar a un proceso que estaba esperando la condición.

Problema productor/consumidor: en este problema, se tiene uno o varios procesos, los productores, que generan
datos y los van almacenando en una estructura de datos. Por otra parte, uno o más procesos, los consumidores, van
procesando los datos a media que estos están disponibles. Como la velocidad con la que se generan los datos no tiene
por qué coincidir con la de proceso de estos, es necesario asegurar la sincronización, en concreto:

• Si el buffer de intercambio está vacío, los consumidores deben esperar a que algún productor deposite nuevos
datos.
• Si el buffer está lleno, los productores deben esperar a que algún consumidor extraiga datos para liberar
espacio.

Código 5.7 Productor/consumidor mediante monitores

5.7. Programación concurrente en Java


Enfoque multihilo: todas aplicaciones en Java contienen al menos un hilo de ejecución, llamado el hilo principal. Java
permite la creación y control de ejecución de nuevos hilos mediante el uso de la clase Thread. Todos los hilos en
ejecución se encuentran asociados con una instancia de Thread, y la gestión de la ejecución se puede realizar mediante
dos enfoques: control directo o mediante el uso de ejecutores, que permiten abstraer el control de la ejecución del
resto de la aplicación.

5.7.1. Sincronización en Java

Sincronización: Java proporciona la palabra clave synchronized, que puede ser utilizado como modificador de un
método o para definir un bloque sincronizado. Un método o bloque de este tipo define un bloqueo asociado al objeto
en el que ha sido definido, y que debe ser obtenido de forma exclusiva antes de poder ejecutar el código, de manera
que se consigue la exclusión mutua.

La forma más sencilla de utilizar la sincronización es añadiendo la palabra clave synchronized a la definición del
método. Internamente, la sincronización se implementa mediante una entidad conocida como bloque intrínseco o
bloqueo de monitor. Cada objeto tiene asociado un bloque intrínseco, de manera que cuando un hilo invoca un método
sincronizado, debe obtener en exclusiva el bloqueo del objeto antes de iniciar la ejecución, y liberarlo al terminar.

Página 33 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

También se pueden utilizar sentencias sincronizadas que, a diferencia de los método sincronizados, para definir un
bloque de código sincronizado es preciso indicar explícitamente el objeto del que se deberá obtener el bloqueo. La
ventaja de este método es que es posible un control más fino de la sincronización, principalmente para optimizar el
rendimiento eliminando bloqueos innecesarios. Sin embargo, al incrementar la complejidad también hay una mayor
probabilidad de introducir errores de concurrencia, por lo que debe analizarse cuidadosamente su uso.

5.7.2. Monitores en Java

Monitores: para implementarlos, hay que definir una clase cuyos métodos públicos estén etiquetados con la palabra
clave synchronized. De esta manera, el bloqueo proporcionado por el objeto permite controlar el acceso exclusivo. La
sincronización de los accesos se completa con el uso de los métodos wait, notify y notifyAll:

• wait: una invocación a este método provoca la suspensión del hilo actual, que libera el bloqueo sobre el objeto.
La espera se realiza hasta que otro hilo haga una llamada a notify o notifyAll sobre el objeto. Entonces, el hilo
despertado espera hasta que pueda obtener de nuevo el bloqueo sobre el objeto, y prosigue su ejecución.
• notify: despierta un solo hilo de los que se encuentran esperando en el bloqueo del objeto. El hilo que invoque
este método debe haber obtenido previamente el bloqueo sobre el objeto.
• notifyAll: despierta a todos los hilos esperando en el objeto.

El bloque sobre una instancia de objeto se puede obtener de tres maneras:

• Ejecutando un método sincronizado del objeto.


• Ejecutando un bloque de código que sincroniza con el objeto.
• Para objetos de tipo Class, ejecutando un método sincronizado estático de la clase.

5.7.3. Problema del productor/consumidor

Problema del productor/consumidor: la solución se basa en la clase Buffer, que encapsula la estructura de intercambio
de datos, implementada como un monitor que regula el acceso de los productores y consumidores. Proporciona los
métodos públicos depositar, para depositar un nuevo elemento, y extraer, que permite extraer datos para procesar.
Además, se definen dos hilos: Productor y Consumidor, encargados de generar y procesar datos, respectivamente. El
uso de los métodos synchronized garantiza la exclusión mutua.

• Depositar un elemento en el buffer. Al producir un nuevo dato, el proceso productor invoca al método
depositar(valor). Lo primero que debe hacer el hilo actual de ejecución es comprobar si el buffer está lleno. En
caso afirmativo, se invoca al método wait para esperar a que se libere espacio. Si no, el hilo continúa su
ejecución. Puesto que está garantizado el acceso único, simplemente se añade el nuevo dato al buffer y a
continuación se invoca a notify para desbloquear a un consumidor.
• Extraer un elemento del buffer. Inicialmente, se comprueba si hay algún dato para procesar, y en caso
contrario el hilo pasa a desbloquearse. Una vez conseguido el acceso, se extrae el dato y se notifica la
disponibilidad de espacio.

La comprobación de la condición para ir a la espera se hace dentro de un bucle while, en lugar de con una sentencia
condicional if. Esto es así porque en Java no es posible asociar el bloqueo de un hilo a una condición de espera, y por
tanto al despertar no se puede garantizar que ésta se cumpla. Es decir, si se utiliza un if es posible que el
consumidor/productor despierte y prosiga su ejecución con el buffer vacío/lleno. Por otro lado, para notificar se utiliza
el método notifyAll, en lugar de notify. Por el mismo motivo de antes, si un hilo despierta y comprueba que su condición
no se verifica, volverá a bloquearse. Esto provocaría la pérdida de la notificación y daría lugar a una situación de
interbloqueo. Utilizando notifyAll, se asegura que al menos un hilo pueda continuar la ejecución, aunque a costa de
tener una solución potencialmente más ineficiente.

Para hacer uso de la estructura de datos compartida con el acceso controlado por monitor, se crean dos hilos de
ejecución: un productor y un consumidor:

• Productor. Al inicializar la instancia, el constructor recibe una referencia al buffer que deberá utilizar para
depositar los datos. En la implementación del método run, simplemente se ejecuta periódicamente una llamada
al método sleep para dormir el proceso durante un tiempo aleatorio, y se deposita un dato en el buffer. De esta
manera se intenta simular un proceso productor, que generaría e iría introduciendo datos a una velocidad
variable.

Página 34 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

• Consumidor. El proceso tiene una estructura similar a la del productor, espera intervalos de tiempo aleatorios,
y va extrayendo datos para procesar, según la disponibilidad.

5.7.4. Problema de los lectores/escritores

Problema de los lectores/escritores: hay una estructura de datos, sistema de archivos, o algún elemento que deba ser
leído y modificado concurrentemente por varios hilos de ejecución. Para garantizar la correcta ejecución, se debe
asegurar que ningún escritor tenga acceso a la sección crítica mientras haya otros hilos dentro, escritores o lectores.
Se asumen los siguientes supuestos:

• Varios lectores pueden realizar lecturas simultáneas sin riesgo de interferencia.


• La escritura debe tener acceso exclusivo, bloqueando el acceso a cualquier otro hilo, ya sea lector o escritor.
• Si un hilo desea escribir, se debe impedir la entrada de nuevos lectores, hasta que se realice la escritura. Esta
asunción evita la inanición de los escritores, es decir, que un escritor quede postergado indefinidamente a la
espera de obtener el acceso exclusivo.

Los dos primeros supuestos previenen la posible corrupción de memoria debida al acceso concurrente por varios hilos.

El monitor proporciona dos conjuntos de métodos públicos, uno para los lectores: empezarLectura y terminarLectura,
y otro para los escritores: empezarEscritura y terminarEscritura.

• Lector: cuando un lector desea empezar a realizar una lectura, debe invocar al método empezarLectura. Un
lector sólo podrá obtener acceso si no hay ningún escritor dentro, ni tampoco hay escritores intentando obtener
el acceso. Al finalizar la lectura, se debe invocar al método terminarLectura para notificar al resto de procesos
de la salida del lector.
• Escritor: el método empezarEscritura anuncia el deseo del escritor de obtener acceso exclusivo, para a
continuación esperar a que no haya ningún otro hilo leyendo o escribiendo. Una vez realizada la escritura, el
escritor deberá invocar al método terminarEscritura para liberar el acceso y dejar paso a futuros lectores y
escritores.

La clase LectorEscritor proporciona el monitor con los métodos de comienzo y terminación de escritura y lectura. De
esta manera, siempre que cada lector se aseguro de encerrar el código que realiza la lectura entre las llamadas a los
métodos empezarLectura y terminarLectura, y que los escritores utilicen empezarEscritura y terminarEscritura, el
monitor garantizará que los accesos se realizan de forma sincronizada.

Página 35 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

6. Tema 6: Comunicación y sincronización basada en mensajes


6.1. Introducción
Sistema basado en mensajes: es un sistema más genérico que permite la comunicación y sincronización entre unidades
de ejecución que, incluso, pueden estar en distintas máquinas, como ocurre en el caso de los sistemas distribuidos o
en una sola máquina, pero sin que los hilos que toman parte en la comunicación tengan acceso a una memoria
compartida.

6.2. Modelos de sincronización de procesos


Uso de mensajes: un proceso receptor A no puede obtener información del proceso emisor B (sean, o no, procesos
que estén en la misma máquina) hasta que el proceso B no haya enviado un mensaje.

Tipos de comunicación:

• Comunicación asíncrona: en esta situación, el emisor del mensaje continúa su labor nada más realizar el
envío, sin reparar en si dicho mensaje ha sido recibido ya o no. Esta comunicación sirve sólo para informar de
sucesos que ya han tenido lugar (con un mayor o menor desfase) y nunca dicen nada sobre la situación o el
estado presente del emisor.
• Comunicación síncrona: el proceso emisor sólo continúa su actividad cuando tiene confirmación de que el
mensaje emitido ha sido recibido. Se pude construir una relación de comunicación síncrona mediante el uso
de dos eventos asíncronos. Para ello, el proceso emisor A enviaría un mensaje asíncrono con la diferencia de
que inmediatamente quedaría a la espera de respuesta. Por su parte, el proceso receptor B debería estar a la
espera de recibir el mensaje de A y, una vez lo recibe, debería mandar un mensaje asíncrono de confirmación.
• Comunicación por invocación remota: el emisor del mensaje continúa sólo cuando recibe una respuesta a
su petición por parte del receptor.

Uso del modelo del asíncrono: se podría pensar que, en aras de la atomicidad, toda comunicación debería realizarse
partiendo del modelo asíncrono, utilizado éste tal cual, o como base para construir la comunicación síncrona,
dependiendo de las necesidades. Sin embargo, esto presenta las siguientes desventajas:

• El principal inconveniente de dicha aproximación es que se podrían llegar a necesitar infinitos búferes para
almacenar los mensajes que el proceso receptor no hubiese leído todavía.
• Actualmente, la mayoría de las comunicaciones se realizan de manera síncrona, lo cual implicaría un esfuerzo
de implementación mayor si se partiese siempre de mensajes asíncronos.
• Además, construir una comunicación síncrona a partir del modelo asíncrono requiere de más comunicaciones,
por lo que los programas así construidos son más complejos.
• Finalmente, cabe destacar que probar si el programa completo funciona correctamente sería más difícil.

6.3. Nombrado de los socios de comunicación y estructura de los mensajes


Nombrado de los socios de comunicación: en un esquema de nombrado de socios se distinguen dos características:
si es directo o indirecto y si es simétrico o asimétrico.

• Esquemas de nombrado directos: el emisor llama directamente al receptor.


• Esquemas de nombrado indirectos: el emisor llama a un intermediario que es el que finalmente se comunica
con el receptor.
• Esquema de nombrado simétrico: el emisor y el receptor se pueden nombrar entre sí (directa o
indirectamente).
• Esquema de nombrado asimétrico: el receptor no nombra un emisor específico, pero acepta mensajes de
cualquier fuente. Los esquemas asimétricos son siempre aptos para su aplicación en el paradigma cliente-
servidor, donde es necesario que los procesos servidor ofrezcan algún tipo de servicio de respuesta a los
mensajes entrantes de cualquier número de procesos cliente. En estos casos es imprescindible que exista una
cola de procesos esperando por el servidor.

Página 36 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

Tipos de nombrado:

• Un hilo o proceso directamente: los socios son procesos que corren en la misma máquina. Esta opción de
asignación de nombres es propia de los paquetes de programación concurrente PVM y MPI y utiliza un id que
está unívocamente asignado a cada proceso. De esta forma, cada orden de enviar o recibir incorpora dicho
parámetro para identificar el socio de comunicación adecuado. Este es un método directo y puede ser tanto
simétrico como asimétrico, dependiendo de la implementación.
• Una entrada o puerto de un módulo o proceso: se direccionan los mensajes a puertos. Se utiliza
habitualmente en Ada. Los emisores deben incluir el nombre del puerto de entrada del receptor, que
habitualmente es un módulo o proceso con uno o varios hilos dentro. Una llamada entrante de la forma t.foo
(args) envía un mensaje a la entrada con el nombre foo en la tarea (hilo) t. Realmente, foo puede ser un nombre
de tarea o el nombre de una variable cuyo valor es un puntero a una tarea. En Ada, una tarea se asemeja a un
módulo y sus entradas se asemejan a cabeceras de subrutinas anidadas directamente dentro de la tarea. Así,
una tarea en Ada puede recibir un mensaje que ha sido enviado a una de sus entradas mediante la ejecución
de una declaración de aceptación. Cada entrada pertenece entonces exactamente a una tarea y todos los
mensajes enviados a la misma entrada deben ser recibidos por esa única tarea. Este método es de tipo directo
y asimétrico (el receptor acepta mensajes de cualquier emisor, siempre y cuando vengan a través del puerto
especificado).
• Algún tipo de toma o abstracción de canal: se direccionan los mensajes a canales. Esta opción aparece en
el lenguaje de programación concurrente Occam. El proceso consiste en, primero, que emisor y receptor
declaren un canal con un nombre dado y, luego, utilizar dicho nombre para realizar el intercambio de los
mensajes. Este método es de tipo indirecto y asimétrico.

Estructura de los mensajes: idealmente el tipo de mensajes que puede enviarse no debería presentar restricción alguna.
En el caso de que los emisores y/o receptores presenten representaciones distintas para los objetos de datos que se
pasan en los mensajes, alcanzar el objetivo anterior puede ser realmente complicado. Si la presentación de dichos
objetos se hace, además, mediante el uso de punteros, la cosa se complica aún más. Actualmente hay algunos
lenguajes que restringen el contenido a objetos fijos no estructurados de tipo definido por el sistema. Además, los
sistemas operativos mantienen la restricción de que los datos sean convertidos a bytes antes de realizar la
comunicación.

6.4. Espera selectiva


Espera activa: el proceso debe comprobar continuamente el valor de una variable, malgastando así sus ciclos de
ejecución.

Operación con guarda: operación que se ejecuta únicamente en el caso de que su guarda se evalúe a verdadero. Este
tipo de construcciones puede realizarse con tantos números de componentes como se necesite. En caso de que más
de una guarda se evalúe a verdadero, la elección es arbitraria. En caso de que ninguna lo haga, se considera un error
y la sentencia dentro del proceso que la ejecuta queda abortada.

Espera selectiva: es aquella estructura en la que las operaciones que se están guardando son operadores de mensajes.
Estos operadores de mensaje son normalmente de recibir. Cuando dos o más guardas se evalúan a verdadero, se
realiza una selección arbitraria, minimizando la inanición de procesos.

6.5. Sistemas distribuidos


Sistema distribuido: sistema que consta de más de un elemento de procesamiento capaz de operar de manera
autónoma y en el que dichos elementos de procesamiento pueden cooperar con el fin de llevar a cabo alguna tarea
común.

Sistemas distribuidos en Java: la librería estándar java.net proporciona dos formas de paso de mensaje útiles para una
arquitectura de sistemas distribuidos donde sus elementos de procesamiento están conectados a través de internet:

• TCP: utiliza el nombrado basado en puertos, pero sólo para establecer las conexiones. Una vez establecida
dicha conexión, ésta permanece invariable para todas las comunicaciones posteriores, siendo esta la situación
del direccionado de mensajes a canales. Bajo este protocolo, las conexiones entregan mensajes de manera
confiable y en orden. Para las comunicaciones sobre TCP, un servidor normalmente está a la escucha en un
puerto por el cual se esperan las conexiones de los clientes cuando éstas tratan de iniciar una comunicación.
Página 37 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

La operación de aceptación del establecimiento de comunicaciones queda entonces bloqueada y a la espera


hasta que el servidor recibe una petición de un cliente que trata de conectar al puerto especificado.
Normalmente, es ese momento los servidores lanzan un nuevo hilo para comunicarse con el cliente, mientras
que el hilo padre vuelve atrás para quedar nuevamente a la espera de otras peticiones de conexión. Una vez
se ha creado la conexión, es frecuente que tanto el cliente como el servidor en Java llame a funciones de la
clase Socket para crear flujos de datos de entrada y salida, que soportan ya todos los mecanismos estándar
de Java para entradas y salidas de texto.
• UDP: protocolo más simple. Cada mensaje se envía a su destino de forma independiente. El software de red
intentará entregar el mensaje, pero no garantiza que la entrega se produzca. Además, dos mensajes enviados
al mismo destino pueden llegar en cualquier orden, sin respetar el hecho de que uno se enviase antes que el
otro. Los mensajes UDP usan nombres basados en puertos: cada mensaje es enviado a una dirección IP
específica y a un puerto concreto. Para enviar o recibir mensajes UDP, hilo Java debe crear un socket de tipo
datagram. El puerto al que se desea conectar se puede indicar como un parámetro del constructor
DatagramSocket.

Otra opción para crear programas para sistemas distribuidos es utilizar la librería java.rmi. Esta posibilidad, sin
embargo, requiere que todos los sistemas en los que se ejecutan los programas que intervengan en la comunicación
estén implementados en Java, mientras que las soluciones con java.net son más generales y permiten que emisor y
receptor estén implementados en lenguajes distintos.

Página 38 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

7. Tema 7: Acciones atómicas, tareas concurrentes y fiabilidad


7.1. Introducción
Tareas concurrentes: tareas que son capaces de cooperar, ejecutándose en paralelo, de forma fiable en presencia de
errores.

Interacciones entre tareas: hay tres tipos de comportamiento:

• Independiente: las tareas no se comunican ni se sincronizan entre ellas y la recuperación de errores se realiza
de forma aislada.
• Cooperante: las tareas tienen un propósito común y se comunican de forma regular. Consecuentemente, la
recuperación de un error que se produzca en alguna de las tareas debe llevarse a cabo de forma conjunta.
• Competitivo: las tareas, esencialmente independientes, no tienen una finalidad común, pero deben
sincronizarse y comunicarse puesto que acceden a unos recursos compartidos y limitados. En este caso, la
recuperación de errores queda ligada al uso de los recursos, ya que la funcionalidad de las tareas es
independiente.

7.2. Acción atómica


Acción atómica: actividad conjunta que desarrolla el grupo de tareas. Una acción a la que pertenecen varias tareas es
atómica si dichas tareas no son conscientes de otras tareas fuera de la acción a la que pertenecen y, además, ninguna
otra tarea que no forme parte de la acción es consciente de las actividades efectuadas dentro de la acción atómica
mientras ésta se desarrolla. Por tanto, las tareas que se intercomunican dentro de una misma acción atómica no pueden
detectar ni tienen constancia de cambios de estado externos a la acción. De la misma manera, los cambios de estado
internos no se comunican al exterior hasta que finaliza la acción. Por estos motivos, las acciones atómicas son vistas
desde fuera como indivisibles e instantáneas. Hay dos formas de considerar la acción atómica:

• Estricta: las tareas que conforman una acción atómica no pueden comunicarse con otras tareas mientras se
lleva a cabo la acción. Los recursos necesarios para realizar la acción atómica no pueden asignarse mediante
instrucciones en el programa.
• No estricta: las tareas que conforman una acción atómica sí pueden comunicarse con otras taras externas
con las restricciones mencionadas. Los recursos pueden ser distribuidos por el programa, ya que las tareas
internas a la acción atómica pueden comunicarse con el gestor de recursos.

Acciones atómicas anidadas: subconjunto de la acción atómica de nivel exterior. En la Figura 7.1 se muestra un ejemplo
de acciones anidadas. Las acciones B y C se encuentran anidadas dentro de la acción A. Además, la acción C tiene
anidadas en su interior las acciones D y E. A su vez, los procesos P1 y P4 forman parte únicamente de la acción A,
mientras que los proceso P2 y P3 quedan dentro de todas las acciones atómicas.

Figura 7.1 Acciones atómicas anidadas

7.2.1. Acciones atómicas de dos fases

Comunicación con el exterior: se realiza en dos fases eludiendo posibles inconsistencias o problemas de acceso a los
datos.

Página 39 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

• Fase creciente: se efectúan las peticiones de los recursos que se necesitan para hacer la acción atómica.
• Fase decreciente: se realiza la liberación de recursos. En ningún caso, una vez que la fase decreciente está
activa, podrá haber nuevas peticiones de recursos.

Este esquema asegura la integridad de la acción atómica. Sin embargo, debe tenerse en cuenta que la liberación
prematura de recursos se hace más difícil la recuperación de errores en el futuro, ya que puede que en el momento del
error no se dispongan de suficientes recursos como para realizar la recuperación.

7.2.2. Requisitos de las acciones atómicas

Requisitos de las acciones atómicas:

• Límites bien definidos: la acción atómica debe tener un inicio, un final y una demarcación. El comienzo es el
lugar de cada tarea involucrada en la acción en el que comienza dicha acción. Análogamente se describe el
final. La demarcación separa las tareas involucradas en la acción atómica del resto de procesos.
• Indivisibilidad: expresa que no hay intercambio de información entre las tareas internas de la acción atómica
y las tareas externas a ésta.
• Anidamiento: las acciones atómicas pueden estar anidadas siempre y cuando no se solapen con otras
acciones atómicas, i.e., debe existir un anidamiento estricto.
• Concurrencia: distintas acciones pueden ejecutarse de forma concurrente. Sin embargo, dado que deben ser
independientes, el resultado debe ser el mismo si las acciones se ejecutan de forma secuencial.

7.2.3. Transiciones atómicas

Transición atómica: acción atómica que puede tener éxito o fallar, entendiendo por fallar si, durante el desarrollo de la
acción, ha ocurrido un error del que ésta no ha podido recuperarse. Debe entonces realizarse una recuperación de
errores hacia atrás para evitar estados inconsistentes. De esta manera, los componentes de la acción atómica son
devueltos a su estado original. Las transiciones atómicas pueden caracterizarse por las siguientes propiedades:

• Atomicidad: el paso desde el estado inicial hasta el resultado se finaliza sin que ningún estado intermedio
pueda ser observado desde el exterior.
• Consistencia: una transacción produce únicamente resultados consistentes. De otra manera, la transacción
debe abortarse.
• Aislamiento: su ejecución no interfiere en la ejecución de otras transacciones.
• Durabilidad: los resultados satisfactorios de una transacción atómica no pueden ser olvidados por el sistema.
Es decir, si el sistema tiene conocimiento de la ejecución de una transacción, entonces debe ser capaz de
recuperar su resultado ante cualquier tipo de fallo, ya sea causado por el usuario, el entorno o el hardware del
sistema.

Figura 7.2 Estructura de una transacción atómica

Diferencias entre transacciones y acciones atómicas:

• Atomicidad de fallo: la transacción debe completarse satisfactoriamente o no tener efecto.


• Atomicidad de sincronización (o aislamiento): la transacción es indivisible, es decir, ninguna otra
transacción que se ejecute simultáneamente puede observar su ejecución parcial.

No todas las acciones pueden ser catalogadas como transacción atómicas.


Página 40 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

Aspectos de las transiciones atómicas:

• No es completamente adecuada en el marco de los sistemas tolerantes a fallos dado que implica que algún
tipo de mecanismo de recuperación de errores sea suministrado por el sistema.
• El programador no tiene control sobre el funcionamiento del mecanismo de recuperación puesto que es fijado
por el sistema.
• La transacción atómica proporciona una forma de recuperación de errores hacia atrás pero no permite que se
realicen procedimientos de recuperación.
• Las transacciones atómicas tienen una importante utilidad en la protección de la integridad de la base de datos
de los sistemas en tiempo real.

7.3. Acciones atómicas recuperables


7.3.1. Recuperación de errores hacia atrás

Recuperación de errores hacia atrás: permite que las tareas involucradas sean retrotraídas al comienzo de la acción.
De esta forma, se permite la ejecución de algoritmos alternativos para cada tarea. Dado que la atomicidad de la acción
asegura que las tareas no han comunicado valores erróneos a otros procesos exteriores hasta que no finalice la acción,
no será necesario observar proceso externos a la acción. Cada una de las tareas de la acción atómica debe por tanto
definir módulos alternativos al módulo primario para que sea posible la recuperación de errores hacia atrás.

Conversaciones: mecanismo de gestión de errores en el que si ocurre un error en alguno de los módulos primarios de
cualquiera de las tareas, todas ellas deben volver al principio y ejecutar el siguiente módulo alternativo. Cada proceso
que se involucra en una conversación debe en primera instancia guardar su estado inicial, de manera que pueda volver
a él en caso de error. Dentro de la conversación, el proceso solo podrá comunicarse con otros procesos activos de la
misma y con los gestores de recursos. Por lo tanto, para abandonar la conversación todos los procesos deben pasar
un test de aceptación. Si alguno de ellos falla, todos los proceso vuelven hacia atrás y recuperan el estado guardado
de la conversación con el fin de iniciar un método alternativo.

Si un proceso falla o no entra en la conversación antes de que otro quiera salir, el éxito de la conversación dependerá
de que el resto de los procesos no quiera comunicarse con el proceso fallido. Si alguno de los procesos intenta
comunicarse con el que no está activo existen dos opciones:

• Bloqueo y espera.
• Continuar sin la comunicación. Esta aproximación tiene dos grandes ventajas: las conversaciones pueden
diseñarse de manera que la participación no sea obligatoria y se permite que los procesos con plazos de tiempo
puedan abandonar la conversación y, si fuera necesario, realizar acciones alternativas.

Desventajas de las conversaciones: si falla la conversación, todos los procesos vuelven al inicio y realizan módulos
alternativos. Esto fuerza comunicaciones entre el mismo grupo de procesos por lo que podría volver a fallar la
comunicación. Además, no es posible en esta situación modificar de manera dinámica el número de procesos que
están interactuando.

Métodos basados en diálogos y coloquios: los diálogos son una manera de encapsular un conjunto de procesos en la
acción atómica. El diálogo per se no proporciona ningún método de recuperación ni define como actuar debido al fallo.
Por el contrarios, restaura los puntos de verificación y señala el fallo en el coloquio que lo resuelve. El coloquio, formado
por un conjunto de diálogos, controla la ejecución de éstos y decide qué acción de recuperación debe tomarse si el
código ha fallado. Así pues, el coloquio proporciona un medio para construir alternativas usando un conjunto de
procesos potencialmente diferentes, lo que deriva en un diseño más diverso que en el caso de las conversaciones.

7.3.2. Recuperación de errores hacia delante

Recuperación de errores hacia delante: si ocurre una excepción en alguno de los procesos involucrados en la acción
atómica, esta excepción se genera para todos los procesos activos. Cada uno de los procesos puede entonces incluir
gestores o manejadores para la excepción producida. Se pueden aplicar el modelo de terminación y el modelo de
reanudación. En el primer caso, si todos los procesos activos tienen un manejador y todos son capaces de gestionar
la excepción sin dar lugar a ninguna otra, entonces la acción atómica se completa normalmente. En el segundo modelo,
el proceso retoma su actividad en el punto en el que la excepción fue generada después de llevar a cabo el manejo de
la misma. En cualquiera de los dos modelos es posible que se genere la excepción estándar atomic_action_failure.

Página 41 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

Esta excepción se produce cuando no existe ningún manejador en ninguno de los procesos activos o cuando falla
alguno de ellos, provocando un fallo en la acción atómica. La excepción se reproducirá para todos los procesos
involucrados en la acción.

7.3.3. Excepciones concurrentes

Excepciones concurrentes: al ejecutar tareas o procesos concurrentes, es posible que varias tareas generen
excepciones al mismo tiempo que deben tratarse con la dificultad añadida de que, si hay varios manejadores distintos
en cada proceso, es necesario elegir el manejador que se va a utilizar para la excepción. Una estrategia muy utilizada
para tratar excepciones concurrentes es hacer uso de un árbol de excepciones, donde se elige como identificador del
manejador, aquella excepción que ocupa la raíz del subárbol más pequeño que contenga todas las excepciones.

7.3.4. Excepciones en acciones atómicas anidadas

Excepciones en acciones atómicas anidadas: en la Figura 7.3 se observa que se ha generado una excepción en la
tarea T4, el cual no forma parte de las acciones atómicas B-E. Sin embargo, sí debe informarse a éstas de la excepción
para que no se realicen, puesto que ha aparecido una excepción en la acción externa. Todas las tareas deben participar
en la recuperación del error, incluyendo las que no son atravesadas por la tarea (B-E). Para ello, una solución es retener
la generación de la excepción hasta que finalice la acción interna o, al menos, evitar transmitir la información de la
excepción hasta que se haya realizado. Evidentemente, esta solución puede presentar problemas si existe un límite de
tiempo (deadline), ya que no en todos los casos será posible esperar a que la acción atómica interna finalice. En
algunos casos puede implementarse gestores de excepciones que permitan abortar un acción interna en caso de
excepción. En última instancia, si no es posible abortar la acción interna se generará la excepción atomic_action_failure.

Figura 7.3 Excepción en un conjunto de acciones atómicas anidadas

7.4. Notificación asíncrona


Notificación asíncrona: se basa en la idea de permitir que una tarea llame a otras sin esperas. Pueden diferenciarse
dos modelos distintos relacionados con los modelos de terminación y reanudación:

• Modelo de reanudación o manejo de eventos: se comporta como una interrupción software. De esta forma,
cada proceso indica que interrupciones está dispuesto a manejar y cuando recibe cada evento. Entonces, el
manejado responde al evento asíncrono y, tras haber manejado el evento, el proceso se reanuda. Es decir, el
flujo de control del proceso ha cambiado temporalmente, pero después de tratar el evento, el proceso continua
su ejecución.
• Modelo de determinación o transferencia asíncrona: implica que cada proceso especifica un domino de
ejecución donde podrá recibir notificaciones asíncronas que lo finalicen. De esta forma, si una petición de
transferencia asíncrona de control ocurre dentro de un proceso, pero no dentro de su domino de ejecución, el
proceso no recibirá la notificación y ésta será ignorada o encolada. El concepto de notificación asíncrona
permite que un proceso responda rápidamente a unas condiciones detectadas por otro, reduciendo las esperas
en los casos en los que la notificación síncrona no es adecuada. En un sistema en tiempo real, un proceso
puede querer avisar a otro porque ha ocurrido algún cambio. También puede utilizarse para atajar problemas
de planificación.

Página 42 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

La notificación asíncrona es útil también en entornos interactivos donde el usuario puede querer finalizar el
procesamiento porque ha detectado un error. Así pues, es necesario disponer de mecanismos de notificación asíncrona
que resulten adecuados allí donde la notificación síncrona no es aceptable.

7.5. Acciones atómicas en Java


Acciones atómicas en Java: la estructura de programación de una acción atómica sigue el patrón de la Figura 7.4. Un
controlador es el encargado de realizar la comunicación con las diferentes tareas involucradas en la acción. La
implementación en Java está basada en el uso de interfaces.

Figura 7.4 Estructura de programación de acciones atómicas en Java

En una clase, 𝑐𝑜𝑛𝑡𝑟𝑜𝑙_𝑎𝑐𝑐𝑖𝑜𝑛_𝑎𝑡𝑜𝑚𝑖𝑐𝑎, se implementa tanto el controlador como el código de las 𝑁 taras. En esta
estructura se implementa con las siguientes características:

• La clase 𝑐𝑜𝑛𝑡𝑟𝑜𝑙_𝑎𝑐𝑐𝑖𝑜𝑛_𝑎𝑡𝑜𝑚𝑖𝑐𝑎 implementa los métodos que aparecen en la interfaz


𝑎𝑐𝑐𝑖𝑜𝑛_𝑎𝑡𝑜𝑚𝑖𝑎𝑐𝑎_𝑁𝑡𝑎𝑟𝑒𝑎𝑠 y además incluye una clase que implementa el controlador.
• El constructor de la clase 𝑐𝑜𝑛𝑡𝑟𝑜𝑙_𝑎𝑐𝑐𝑖𝑜𝑛_𝑎𝑡𝑜𝑚𝑖𝑐𝑎 inicializa el controlador.
• La clase interna dispone de 𝑁 atributos de tipo lógico y tres atributos de tipo entero. Los atributos de tipo lógico
corresponden a los 𝑁 proceso. Los atributos 𝑡𝑎𝑟𝑒𝑎𝑠_𝑟𝑒𝑎𝑙𝑖𝑧𝑎𝑑𝑎𝑠, 𝑡𝑎𝑟𝑒𝑎𝑠_𝑠𝑎𝑙𝑖𝑒𝑛𝑡𝑒𝑠 y 𝑛𝑢𝑚𝑒𝑟𝑜_𝑡𝑎𝑟𝑒𝑎𝑠 son
atributos de tipo entero que permiten al controlador gestionar la acción. Los atributos lógicos se inicializan a
𝑓𝑎𝑙𝑠𝑒 en el constructor, 𝑡𝑎𝑟𝑒𝑎𝑠_𝑟𝑒𝑎𝑙𝑖𝑧𝑎𝑑𝑎𝑠 a cero, 𝑛𝑢𝑚𝑒𝑟𝑜_𝑡𝑎𝑟𝑒𝑎𝑠 y 𝑡𝑎𝑟𝑒𝑎𝑠_𝑠𝑎𝑙𝑖𝑒𝑛𝑡𝑒𝑠 a 𝑁.
• Dentro del controlador existe un método para cada una de las tareas que se encarga de cambiar a 𝑡𝑟𝑢𝑒 el valor
de la variable correspondiente. Además, un método 𝑡𝑒𝑟𝑚𝑖𝑛𝑎𝑟_𝑡𝑎𝑟𝑒𝑎 que se encarga de gestionar la tarea
correspondiente.
• Cada vez que una tarea invoca a 𝑡𝑒𝑟𝑚𝑖𝑛𝑎𝑟_𝑡𝑎𝑟𝑒𝑎, se incrementa el contador de 𝑡𝑎𝑟𝑒𝑎𝑠_𝑡𝑒𝑟𝑚𝑖𝑛𝑎𝑑𝑎𝑠. En el caso
de que 𝑡𝑎𝑟𝑒𝑎𝑠_𝑡𝑒𝑟𝑚𝑖𝑛𝑎𝑑𝑎𝑠 coincida con 𝑛𝑢𝑚𝑒𝑟𝑜_𝑡𝑎𝑟𝑒𝑎𝑠, se notifica a todas las tareas que la última ha
terminado. Las tareas salen del sistema a la par que se disminuye el contador 𝑡𝑎𝑟𝑒𝑎𝑠_𝑠𝑎𝑙𝑖𝑒𝑛𝑡𝑒𝑠. Cuando la
última tarea ha abandonado la acción, se reinicia el controlador y se notifica por si hubiera otras tareas que
quisieran entrar en la acción.
• Dentro de la clase 𝑐𝑜𝑛𝑡𝑟𝑜𝑙_𝑎𝑐𝑐𝑖𝑜𝑛_𝑎𝑡𝑜𝑚𝑖𝑐𝑎 deben implementarse los métodos correspondientes a cada una de
las tareas. En este método, cada tarea notifica al controlador que va a comenzar con sus instrucciones
relacionadas con la acción atómica. Una vez avisado el controlador, realiza las computaciones necesarias,
cambia el valor de la variable 𝑐𝑎𝑡𝑐ℎ a 𝑓𝑎𝑙𝑠𝑒 y notifica al controlador que ha terminado su labor y que puede
abandonar la acción atómica.

El modelo de Java basado en el uso de interfaces permite de manera sencilla introducir nuevas tareas en una acción
atómica.

Página 43 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

7.6. Notificación asíncrona en Java


Notificación asíncrona en Java: Java real time soporta el modelo de reanudación mediante el manejo de eventos
asíncronos y el modelo de determinación mediante la transferencia asíncrona de control. Estas especificaciones forman
parte de Java Real Time Specificactionsy no de la especificación estándar de Java. En Java Real Time un manejador
puede ser asociado a uno o más eventos asíncronos y viceversa, un evento asíncrono puede tener asociado uno o
más manejadores. Cada manejador tiene un contador del número de disparos pendientes, de forma que, al disparar
un evento, el contador del manejador se incrementa. Las clases principales asociadas a los evento asíncronos son:

• AsyncEvent. Destaca el método addHandler, que permite añadir un manejador a un evento asíncrono y el
método fire que es el método que se llamará para que se ejecuten los manejadores asociados a este evento.
• AsyncEventHandler: es el manejador de eventos asíncronos. Se incluyen los métodos para gestionar los
eventos y para obtener y modificar el contador asociado al número de eventos.
• BoundAsyncEventHandler: implementa los manejadores dedicados.

En la clase Thread se incluyen los métodos para trabajar con transferencia asíncrona de control (interrupt, isInterrupted
y interrupted). Cuando un hilo interrumpe a otro, si el hilo interrumpido está bloqueado lanza la excepción
interruptedException. Si el hilo que se desea interrumpir está en ejecución, esto no tiene un efecto inmediato, ya que
el propio hilo es el encargado de comprobar si se le ha interrumpido con el método isInterrupted. Si se utiliza la
especificación de Java Real Time en vez de la especificación estándar, se permite la interrupción inmediata y la
transferencia asíncrona de control queda integrada en el mecanismo de manejo de excepciones, añadiendo una
extensión de interrupción de hilos. En este contexto, cada método es el que debe indicar si está preparado para permitir
una transferencia asíncrona de control.

Uso de transferencia asíncrona: hacen falta tres actividades:

• Declaración de una excepción asíncrona AsynchronouslyInterruptedException (AIE).


• Identificación de los métodos que pueden ser interrumpidos.
• Señalización de la excepción AIE al hilo.

Si se considera un método que puede ser interrumpido y que lanza la excepción AIE. Por otra parte, se crea un objeto
de este método asociado a un cierto hilo 𝑡. Si el hilo 𝑡 se está ejecutando en un método que no tiene definida la
excepción, se marca la excepción como pendiente y se tendrá en cuenta cuando el hilo 𝑡 esté en un método que sí
permita tratar la excepción. Por tanto, si 𝑡 se está ejecutando en el bloque 𝑡𝑟𝑦, el control pasa al bloque catch y la
excepción se capturaría si se programa explícitamente dentro del bloque catchy se propagaría si se está ejecutando
sin ningún bloque trye. Por otro lado, si el hilo 𝑡 está bloqueado mediante los métodos sleep, wait o join, debe
replanificarse y la excepción se propaga o se marca como pendiente dependiendo de cuál sea el método que produjo
el bloqueo del hilo 𝑡.

En todos los casos, una vez que la excepción sea recibida por el manejador es necesario comprobar si la transferencia
síncrona de control era la esperada. Para ello se utiliza el método clear de la clase AIE. Si no era la esperada, seguirá
pendiente y se propaga a un nivel superior. Para facilitar el uso estructurado de la transferencia asíncrona de control
en Java se proporciona la interfaz interruptible. Esta interfaz será implementada por aquellos objetos que quieran
proporcionar un método interruptible en el cual el método room es interruptible y, si este se interrumpe, el sistema
llamará al método interrupt_action. En este contexto, una vez implementada la interfaz interruptible, se pasa a un objeto
del tipo do_interruptible de la clase AIE. De esta manera, el método podrá ser interrumpido invocando al método fire
de la clase AIE. Si se desea tener más control de la excepción, se pueden utilizar los métodos disable, enable y
isEnable.

Página 44 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

Figura 7.5 Notificación asíncrona en acciones atómicas en Java

Página 45 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

8. Tema 8: Control de recursos


8.1. Mecanismos de sincronización
Mecanismos de sincronización: cumplen dos funciones básicas:

• Aseguran la exclusividad en el acceso a un recurso.


• Se encargan de la planificación de su uso para garantizar un reparto adecuado, teniendo en cuenta
restricciones de prioridad.

Restricciones de sincronización: se pueden expresar mediante un esquema si condición entonces excluir proceso A,
o si condición encones proceso A es prioritario sobre B.

Condiciones a evaluar: deben tener en cuenta información sobre las restricciones, el recurso o la solicitud de acceso
entre otros. Esta información se puede clasificar en diferentes categorías:

• La operación de acceso solicitada.


• Temporización.
• Parámetros de la petición.
• Estado de sincronización del recurso.
• Estado local del recurso.
• Historial de uso.
• Prioridad de proceso.

Primitivas de sincronización: son la base para implementar la solución de problemas de acceso a los recursos. Estas
primitivas incluyen los monitores/métodos sincronizados, los servidores (paso de mensajes) y los recursos
protegidos (objetos protegidos).

Sincronización de condición: se aceptan todas las peticiones que puedan llegar, pero los proceso que no pueden ser
atendidos se suspenden en una cola de espera asociada a una variable de condición hasta que el recurso esté
disponible.

Sincronización de evitación: las peticiones solo se aceptan en el caso de que puedan ser atendidas.

8.2. Propiedades de los mecanismos de sincronización


Propiedades de los mecanismos de sincronización: los requisitos que debe satisfacer un mecanismo de sincronización
se definen de acuerdo a tres categorías:

• Modularidad: una implementación es modular si está estructurada de tal manera que es sencillo modificar una
parte sin afectar al resto del sistema. Esta propiedad es importante para que el sistema sea más fácil de
entender y mantener.
• Expresividad: un mecanismo es expresivo si permite fácilmente la definición de los requisitos de
sincronización. En contraposición, un mecanismo cuenta con poca expresividad cuando no es posible usar
directamente la información que se dispone sobre el problema de sincronización.
• Facilidad de uso: un sistema debe permitir la descomposición en restricciones de sincronización individuales
que puedan ser implementadas de forma aislada. En caso contrario, a medida que las restricciones aumentan,
se incrementa rápidamente la complejidad de la implementación.

Tipo de operación: algunos recursos admiten diferentes tipos de acceso, tales como un acceso compartido o un acceso
exclusivo. Esta información se puede utilizar para dar prioridad a un tipo de acceso frente a otro.

Temporización: el orden de llegada de las solicitudes de acceso se utiliza habitualmente para gestionar el reparto de
los recurso. De esta manera, considerando únicamente este factor, un proceso que solicite un recurso antes que otro,
conseguirá el acceso en primer lugar.

Parámetros: a veces puede ser conveniente utilizar información contenida en los parámetros asociados a la petición.
Con frecuencia, esa información se refiere al tamaño o número de elementos solicitados, en el caso de recursos

Página 46 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

cuantificables como la memoria, o bien a la identidad del recurso. De esta manera, el servidor puede realizar un control
más fino del recurso.

Estado del recurso: dentro del estado del recurso se pueden considerar diferente tipos de información: estado de
sincronización, estado local, historial de uso, etc.

• Estado de sincronización: incluye toda la información de estado del recurso necesaria únicamente para
propósitos de sincronización, que no sería necesaria si el recurso no se accede concurrentemente. Dentro de
esta categoría se incluye información sobre los procesos que tiene acceso al recurso y las operaciones que
están ejecutando.
• Estado local: contiene información sobre el recurso que es necesaria independientemente de si el recurso se
accede de forma concurrente o secuencial.

Historial de uso: contiene información sobre los eventos ocurridos previamente en relación a un recurso. A diferencia
del estado de sincronización, la información histórica se refiere a operaciones que ya se han completado, por lo que no
afectan directamente a la sincronización del recurso.

Prioridad del proceso: la prioridad puede utilizarse para determinar el orden de acceso a los recursos.

8.3. Evaluación de los mecanismos de sincronización


Monitores: en cuanto al tipo de operación los monitores permiten expresar diferentes tipos de solicitud implementando
diferentes métodos. Sin embargo, estos métodos se planifican normalmente por orden FIFO, con lo que no es posible.
Por esta misma razón, los monitores permiten expresar fácilmente algunas restricciones temporales, ya que su
implementación FIFO automáticamente garantiza el orden con el que se atenderán las peticiones. Sin embargo, no es
posible controlar el orden con llamadas de diferentes tipos. Con respecto al estado del recurso, los monitores
permiten, mediante el uso de variables de condición, representar adecuadamente las restricciones de estado, así como
los parámetros de la petición, pueden utilizarse de forma natural. Finalmente, las restricciones de prioridad no son
fáciles de tratar con las implementaciones basadas en FIFO. Sin embargo, es posible definir monitores basados en
prioridades.

Servidores y objetos protegidos: permiten una mejor gestión del tipo de operación. Las restricciones de estado se
pueden representar con condiciones de guarda. Por otra parte, los parámetros de la solicitud solo pueden ser tenidos
en cuenta después de la aceptación de la llamada, por lo que es necesario construir la petición como una interacción
en dos pasos. Por último, puesto que la espera en las guardas se hace en general de modo arbitrario o FIFO, no es
sencillo realizar una gestión de las prioridades.

Primitivas de sincronización: aunque las primitivas basadas en evitación permiten una mejor gestión del tipo de
operación, en general no pueden tratar con parámetros. Si esta información no es accesible hasta que llamada ha sido
aceptada, puede ser necesario construir la interacción con el gestor de recursos como una interacción de dos pasos.

8.4. Funcionalidad de reencolado


Reencolado: consiste en mover explícitamente una tarea, que se encuentra en una barrera o guarda, a otra cola de
espera en la que deberá conseguir de nuevo el acceso. A diferencia de lo que ocurre en una llamada a otro
procedimiento, en el caso de la ejecución de un reencolado el invocador no retoma el control. Cuando un punto de
entrada se reencola en otro, el flujo de control del primero termina. Cuando el segundo finaliza, el control se devuelve
al objeto que realizó la llamada original. Como consecuencia, cuando se ejecuta un reencolado de un objeto protegido
a otro, la exclusión mutua se libera.

Página 47 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

Figura 8.1 Funcionalidad de reencolado

8.5. Nombrado asimétrico y seguridad


Nombrado asimétrico: el servidor no conoce la identidad del cliente. Esto tiene la desventaja de que se pueden escribir
servidores de propósito general, pero esto puede dar lugar a pobre seguridad en el uso de recursos. En particular, una
tarea servidor puede querer conocer la identidad del cliente:

• Una solicitud puede ser rechaza para evitar posibilidad de interbloqueo.


• Puede garantizarse que los recursos solo se liberen por la tarea que los ha obtenido previamente.

8.6. Uso de recursos


Uso de recursos: cuando tareas competitivas o cooperativas requieren recursos, el modo normal de operación es
solicitar el recurso, esperando si es necesario, usar el recurso y por último liberarlo. Un recurso es requerido para uso
en modo compartido o exclusivo:

• Modo compartido: el recurso puede ser utilizado al mismo tiempo por más de una tarea.
• Modo exclusivo: solo una tarea puede acceder al mismo en un instante determinado.

Algunos recursos pueden ser utilizados de las dos maneras. Así, si un proceso solicita acceso en modo compartido
mientras está siendo utilizado en modo exclusivo, el proceso debe esperar. Si el recurso estaba siendo utilizado en
modo compartido, podrá continuar. De la misma manera, si se solicita acceso exclusivo a un recurso, la tarea solicitante
debe esperar a que los procesos accediendo en modo compartido terminen.

Puesto que las taras pueden bloquearse cuando solicitan recursos, es necesario que no lo hagan hasta que no los
necesiten. Es más, una vez solicitados deben ser liberados lo antes posible. Si esto no se cumple, el rendimiento del
sistema puede verse afectado. Si las tareas liberan los recursos demasiado pronto y luego fallan, pueden haber pasado
información errónea al recurso. De esta manera, el uso de recurso debe modificarse para que estos no sean liberados
hasta que la acción sea completada. Cualquier procedimiento de recuperación debe o bien liberar los recursos, si la
acción es completada con éxito, o bien deshacer los efectos en el recurso. Con recuperadores hacia adelante, estas
operaciones pueden ser realizadas por los manejadores de excepción. Con recuperación hacia atrás, no es posible
realizar estas operaciones si no es con soporte adicional del lenguaje de programación.

8.7. Interbloqueo
Condiciones de carrera: cuando dos o más tareas acceden a más de un recurso compartido, existe la posibilidad de
que se genera una situación indeseable en la que los procesos involucrados quedan incapacitados para continuar su
normal evolución. Dependiendo de las características del sistema, pueden aparecer distintas condiciones, entre las
que se incluyen:

• Punto muerto (deadlock): en el que los procesos quedan suspendidos a la espera de que se libere el recurso
que necesitan.
• Bloque activo (livelock): donde los procesos continúan en ejecución, pero no pueden evolucionar.
• Inanición (starvation): cuando algunas tareas no obtienen recursos ya que siempre hay otras de mayor
prioridad.

Página 48 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

Interbloqueos: para que existan, es necesario que se den al mismo tiempo cuatro condiciones:

• Exclusión mutua: se garantiza que dos procesos no pueden tener acceso a un recurso al mismo tiempo.
• Retención y espera: los procesos pueden retener recursos mientras esperan a otros.
• No apropiación: un proceso no puede ser expropiado de un recurso por parte del sistema operativo, sino que
debe cederlo voluntariamente.
• Espera circular: existe una lista circular de procesos tal que cada uno de ellos retiene un recurso solicitado
por el siguiente.

Inanición: se produce cuando un proceso no es capaz de obtener acceso a un recursos. Se produce cuando la política
de reservas no es juta y existen otros proceso que intentan acceder continuamente al mismo recurso. De esta manera,
la petición de acceso a un recurso por parte de un proceso no será atendida, postergándose indefinidamente su
ejecución en favor de otros procesos. A diferencia de un interbloqueo, la inanición no impide la evolución del sistema,
pero provoca un reparto injusto de los recursos, ya que algunos procesos podrán hacer uso de ellos y otro no. En
particular, para un sistema basado en el uso de prioridades, puede ocurrir que un proceso de alta prioridad quede a la
espera de un resultado de otro proceso con menor prioridad que no llegará a ejecutarse nunca, dando lugar a lo que
se conoce como inversión de prioridades.

Soluciones al interbloqueo:

• Prevención: consiste en tomar las medidas necesarias para eliminar alguna de las cuatro condiciones que
dan lugar al interbloqueo.
• Evitación: se concede acceso a los recursos únicamente si el sistema se mantiene en estado seguro. Un
estado inseguro es aquel que puede llevar a un interbloqueo. El algoritmo del banquero es un ejemplo que
utiliza el método de la evitación.
• Detección y recuperación: consiste en actuar después de que aparezca un estado de interbloqueo. Para ello
es necesario detectar la ocurrencia del problema e identificar los procesos y recursos implicados, y a
continuación tomar las medidas necesarias para restablecer un estado consistente. El algoritmo de detección,
que se ejecuta periódicamente, por lo general se basa en técnicas centradas en la búsqueda de esperas
circulares. Para eliminar el interbloqueo, una vez detectado, se puede optar por terminar los proceso implicados
o retroceder a un estado anterior, o bien por expropiar recursos para llegar a un estado consistente. En
cualquier caso, se debe evitar provocar inanición en los procesos.

Página 49 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

9. Tema 9: Capacidades de tiempo real


9.1. El concepto de tiempo. Sistemas de referencia de tiempo
Tiempo: lo que interesa en sistemas de tiempo real es disponer de un concepto de tiempo que permita definir la
simultaneidad de sucesos, internos y externos, e introducir un orden en la ocurrencia de los mismos, permitiendo
coordinar la ejecución de las tareas internas con el tiempo marcado por el entorno. En este sentido, el tiempo real se
utiliza para distinguirlo del tiempo del computador, ya que es un tiempo externo.

Tiempo universal (UT): se definió en 1884 como el tiempo solar medio en el meridiano cero (Greenwich), siendo 1 𝑠 =
1/86400 de un día solar medio. Esta definición fue oficial hasta 1955. Si bien, era bastante imprecisa, ya que la duración
del día no es constante, y se ve afectada por distintos fenómenos, como es la pérdida de energía a causa de las
mareas.

Tiempo atómico universal (TAI): se definió en 1970 como un estándar de atómico de alta precisión para medir el tiempo
propio de un cuerpo geoide con reloj atómico. Et TAI Está mantenido por una red coordinada por el BIMP (Bureau
International de Mésures et Poids), y se sincronizó con el UT en 1958-01-01:00:00:00. El TAI va lentamente
diferenciándose del UT, ya que el día solar medio va aumentando.

Universal Time Coordinated (UTC): se obtiene a partir del TAI pero añadiendo o quitando un segundo cuando es
necesario, de forma que:

𝑈𝑇𝐶 = 𝑇𝐴𝐼 + 𝐻

Donde 𝐻 se elige de forma que |𝑈𝑇 − 𝑈𝑇𝐶| ≤ 0.9 𝑠.

Esta modificación la hace el IERS (International Earth Rotation Service) el 30 de junio o 31 de diciembre a las 24:00.
La hora oficial de cada país se basa en el UTC.

El UTC es adecuado para la comunicación con personas, aunque presenta saltos (segundos intercalares) que lo hacen
inadecuado para medidas de tiempo precisas, y puede dar problemas en muchos sistemas informáticos.

Tiempo en sistemas de tiempo real: se necesita una referencia de tiempo que sea:

• Estable, sin variaciones grandes a lo largo del tiempo.


• Exacta, diferencia con el TAI acotada, excepto una constante, aunque puede tener una época (origen)
cualquiera.
• Precisa, la diferencia entre dos lecturas sucesivas está acotada.
• Monótona no decreciente, sin saltos hacia atrás.

El TAI es una referencia más adecuada para controlar la ejecución de tareas de tiempo real.

9.2. Relojes
Medición del tiempo: si un programa ha de cumplir con determinados requisitos temporales, debe tener alguna forma
de medir el paso del tiempo, lo que puede hacer accediendo al marco temporal del entorno o mediante un reloj hardware
interno. La primera opción se puede hacer por diferentes métodos, utilizando una señal de tiempo internacional (UTC)
como la que proporciona el GPS, o servicios de internet. Con esta señal se proporciona una interrupción regular que
se sincroniza con el reloj interno.

Relojes internos: son dispositivos hardware y software que proporcionan el valor del tiempo real cuando se leen. Están
compuestos por una electrónica que genera eventos periódicos, un contador que acumula la cuenta de estos eventos,
y un software que convierte el valor del contador en unidades de tiempo. Típicamente estos eventos periódicos
corresponden a los pulsos de un cristal de cuarzo. La contabilización de estos pulsos se registra en un contador al que
puede acceder el software del sistema.

Las características más importantes de un reloj son:

• Resolución: corresponde a la unidad mínima representable por el reloj, es decir, las unidades que se
representan en el contador de pulsos.
Página 50 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

• Intervalo de valores: es el límite inferior y superior de tiempo medible, que corresponde a la capacidad máxima
del contador.
• Precisión o exactitud: diferencia con respecto a un reloj de referencia. Se pueden tener errores o derivas con
respecto a la referencia de tiempo externo, causados por variaciones de temperaturas y otros efectos.
• Granularidad: período del oscilador, que corresponde a la distancia en tiempo real entre dos tiempos de reloj
consecutivos.
• Estabilidad: derivas en la frecuencia de progreso del reloj respecto a una referencia externa, es decir, la
variación con el tiempo:

𝜏(𝑡 ′ ) − 𝜏(𝑡)
1−𝜌 ≤ ≤1+𝜌
𝑡′ − 𝑡

Donde 𝜌 es la desviación permitida en la frecuencia de funcionamiento del reloj, 𝜏 es la medida de un reloj real,
𝑡′ y 𝑡 son los tiempos medidos en un reloj ideal.

Contador: como es finito, se puede desbordar y, en ese caso, se tendría que reinicializar. En la Figura 9.1 se muestra
el comportamiento de reinicio. Mientras el contador no se desborda, el tiempo medido es monótono creciente, pero al
desbordarse el valor del reloj tiene saltos hacia atrás. La monotonía del tiempo depende del tiempo de vida de la
aplicación y del tamaño del contador en relación con el tamaño del tick. Si el tiempo es no monótono, no se dispone
del tiempo absoluto y no se pueden utilizar intervalos temporales por encima del tiempo de desbordamiento del contador
del reloj. El tiempo de los relojes debe ser monótono creciente, por lo que hay que diseñar el contador con la suficiente
capacidad.

Figura 9.1 Tiempo de reloj frente tiempo real

9.3. Relojes en Java


Sistemas de tiempo real: requieren los siguientes relojes:

• Reloj monótono: debe progresar con un ritmo constante y no estar sujeto a la introducción de ticks extras
para reflejar los segundos intercalares. El ritmo constante resulta necesario para los algoritmos de control que
deben ser ejecutados con una base regular. Muchos relojes monótonos son relativos al arranque del sistema
y, por tanto, solo pueden usarse para medir el paso del tiempo, no el tiempo del calendario. Pero, pueden ser
usados para proporcionar tiempos límites en los que no se ha producido un evento, como pueda ser una
interrupción.
• Reloj de cuenta atrás: debe poder pausar, continuar y reiniciarse.
• Reloj del tiempo de ejecución de la CPU: mide la cantidad de tiempo de procesador que ha consumido un
cierto hilo u objeto.

Estos relojes requieren de una resolución que sería menor a los milisegundos, pudiendo estar basados en un mismo
reloj físico o en diferentes relojes. Cuando se proporciona más de un reloj, es importante tener en cuenta la relación
entre ellos, especialmente si puede haber derivas entre ellos.

Relojes en Java: las especificaciones de tiempo real para Java (RTSJ, Real-Time Specifications for Java) se considera
un reloj adicional y se dispone de unas clases de tiempo con lo que se aumentan las capacidades del Java estándar.
En RTSJ se definen una jerarquía de clases de tiempo anidadas en la clase abstracta HighResolutionTime,
proporcionando un reloj con una granularidad de nanosegundos. La clase HighResolutionTime tiene tres subclases:

Página 51 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

• AbsoluteTime: representa el tiempo absoluto, medido con respecto al inicio de una determinada era o época,
que depende del reloj que ese tenga asociado, como se tiene para el tiempo UTC.
• RelativeTime: representa el tiempo relativo, y corresponde a una duración temporal medido por un determinado
reloj.
• RationalTime: representa el tiempo racional, que es un tiempo de tipo relativo, que tiene asociado una
frecuencia, expresada por un numerador long (número de ocurrencias), y un denominador RelativeTime
(intervalo de las ocurrencias). Se usa para medir la frecuencia con la que ciertos eventos ocurren.

La clase Clock de RTSJ define la clase abstracta a partir de la cual se derivan todos los relojes.

9.4. Retardos, tiempos límites y temporizadores


Retardo relativo: posibilita que un proceso sea encolado hasta cierto evento futuro en lugar de efectuar una espera
ocupada basada en llamadas al reloj.

Retardo absoluto: consiste en suspender la ejecución hasta algún instante futuro. En el caso de no disponer facilidades
para los retardos absolutos, estos se pueden conseguir mediante un retardo relativo, si se dispone de la hora actual:
𝑟𝑒𝑡𝑎𝑟𝑑𝑜 𝑟𝑒𝑙𝑎𝑡𝑖𝑣𝑜 = ℎ𝑜𝑟𝑎 𝑎𝑏𝑠𝑜𝑙𝑢𝑡𝑎 − ℎ𝑜𝑟𝑎 𝑎𝑐𝑡𝑢𝑎𝑙. No obstante, así se corre el riesgo de que se produzca una
interrupción cuando se está calculando la diferencia y se va a hacer la llamada.

Factores que afectan al retardo: en un sistema multiproceso hay varios factores que pueden modificar el tiempo de
reanudación de un proceso, haciendo que el tiempo de retardo sufra un desplazamiento. Entre estos factores se
encuentran:

• Cuando se inhiben las interrupciones en el sistema, si hay una interrupción para despertar el proceso, esto se
retrasa hasta volver a habilitar las interrupciones.
• La diferente granularidad entre la expresión del tiempo y la granularidad el reloj. Esto se produce, por ejemplo,
si se implementa el reloj mediante una interrupción periódica con una granularidad mayor que la del reloj.
• Durante el paso de listo a ejecutable puede que procesos de mayor prioridad estén también listos, con lo que
el proceso se retrasaría hasta terminar los de mayor prioridad.

Deriva local: tiempo adicional que dura el retardo, tanto relativo como absoluto. No se puede eliminar.

Deriva acumulada: se corresponde con el desplazamiento que se obtiene en las sucesivas invocaciones causado por
la suma de las derivas locales.

Restricciones temporales: se utilizan para limitar el tiempo en el que se espera que ocurra un suceso. Se introducen
en la ocurrencia de sucesos de una determinada tarea, o al tiempo que un proceso esperaría para una comunicación.
Estas restricciones corresponden al o que se denomina límites temporales o timeouts, y se necesita un mecanismo de
recuperación de errores.

Tiempos límites: se utilizan con los métodos de sincronización como semáforos, regiones críticas, variables de
condición en monitores o entradas en objetos protegidos. Se pueden considerar como una notificación, y se pueden
utilizar con notificaciones asíncronas. Con los tiempos límites se pueden aumentar tanto los modelos de reanudación
como los modelos de terminación. Generalmente, los tiempos límites se asocian con condiciones de error, de forma
que si no se produce una comunicación en 𝑁 milisegundos es porque se ha producido algo inadecuado y se debe
proceder a una acción correctiva.

Temporización: es una restricción sobre el tiempo que una tarea permanece a la espera de un evento. Las
temporizaciones son a menudo incluidas en las primitivas de sincronización y comunicación. Un temporizador es un
mecanismo que permite avisar de que ha trascurrido cierto tiempo. Se tienen de dos tipos: de un solo disparo o
periódicos. Tienen asociados tres eventos.

• Activar: se fija valor, tipo, etc.


• Cancelar: el evento esperado llega a tiempo.
• Expirar: normalmente es una señal

El mecanismo por el cual se avisa depende del sistema operativo y del lenguaje de programación.

Página 52 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

9.5. Requisitos temporales


Requisitos temporales: se pueden enfocar mediante métodos formales o analíticos:

• Métodos formales: se tienen que especificar las propiedades temporales con un modelo formal, validar la
especificación y comprobar que la implementación satisface las propiedades temporales.
• Métodos analíticos: se tienen que analizar las propiedades temporales desde el punto de vista de la
planificación de las tareas.

Verificación de un sistema en tiempo real: es un proceso de dos etapas:

1. Verificación de los requisitos y el diseño, viendo si son coherentes y consistentes los requerimientos
temporales, es decir, si se pueden satisfacer.
2. Verificación de la implementación, viendo si un determinado conjunto de recursos hardware, no siempre fiables,
se pueden satisfacer los requerimientos.

Marcos temporales: conjunto de sentencias asociadas a una restricción temporal. Se definen mediante atributos
temporales asociados a una secuencia de instrucciones. Entre estos atributos estarían:

• Latencia mínima, 𝑱𝒎𝒊𝒏 : tiempo mínimo que debe transcurrir desde que se produce el evento de activación
hasta que se ejecuta el marco temporal.
• Latencia máxima, 𝑱𝒎𝒂𝒙 : tiempo máximo que debe transcurrir desde que se produce el evento de activación
hasta que se ejecuta el marco temporal.
• Plazo de respuesta, 𝑫: máximo tiempo en que la ejecución del marco temporal debe haber finalizado, medido
desde el evento de activación del mismo.
• Tiempo de respuesta, 𝑹: tiempo transcurrido desde que se produce el evento de activación hasta que acaba
la ejecución del marco temporal 𝑅 ≤ 𝐷.
• Tiempo de cómputo, 𝑪: tiempo de utilización del procesador del marco temporal.
• Tiempo límite, 𝑳: máximo tiempo en que la ejecución del marco temporal debe haber finalizado, medido desde
el comienzo de su ejecución.

Marcos temporales periódicos: se repiten periódicamente, y pueden corresponder al muestreo de una señal o a la
ejecución de un lazo de control, que deben tener un plazo de respuesta asignado. Se caracterizan por el periodo de
ejecución 𝑇.

Marcos aperiódicos o esporádicos: corresponden generalmente a eventos asíncronos que ocurren fuera del
computador. En estos casos, para permitir que se realicen los cálculos en el peor de los casos, a menudo se define un
periodo mínimo entre dos eventos aperiódicos (𝑆).

Fluctuación o jitter: desviación del cumplimiento de un atributo temporal.

9.6. Tolerancia a fallos


Tolerancia a fallos: para que un sistema sea considerado tolerante a fallos de temporización, el sistema tiene que poder
detectar:

• El desbordamiento de un tiempo límite.


• El desbordamiento del tiempo de ejecución en el peor caso.
• Eventos esporádicos que ocurran más a menudo de lo previsto.
• Tiempos límites de espera en comunicaciones.

Interrupción de procesos: a menudo, las consecuencias de un error de temporización en un proceso/hilo responsable


de un tiempo límite, son debidos a que otros procesos deben alterar sus límites temporales, o incluso terminar lo que
están haciendo, o hay que arrancar nuevos procesos. Los procesos tienen que ser interrumpidos, normalmente para
responder a:

• Se debe devolver inmediatamente el mejor de los resultados obtenidos.


• Cambiar a un algoritmo más rápido.
• Pasar a estar disponible para recibir nuevas instrucciones.
Página 53 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

10. Tema 10: Planificación


10.1. Introducción
Planificación: es el mecanismo que permite limitar el no determinismo que se da en la ejecución de sistemas
concurrentes. En un esquema de planificación se pueden distinguir dos elementos:

• El algoritmo que se usa para ordenar o gestionar el uso de los recursos de procesamiento del sistema (tiempo
de ejecución en los procesadores).
• El modelo que se utiliza para predecir o prever el comportamiento del sistema en el peor caso posible de
ejecución al aplicar el algoritmo de planificación. Estas predicciones son fundamentales para confirmar si se
pueden satisfacer los requisitos temporales del sistema de tiempo real.

Esquema de planificación estático: realiza sus predicciones antes de realizar la ejecución.

Esquema de planificación dinámico: se toman decisiones relativas a la planificación durante el tiempo de ejecución.

10.2. Modelo de proceso simple


Modelo de proceso simple: modelo sencillo que se utiliza para predecir el comportamiento en el peor caso de un sistema
utilizando esquemas de planificación estándar. En este modelo deben cumplirse los siguientes supuestos:

• El programa debe estar compuesto por un número fijo de procesos.


• Los procesos deben ser independientes unos de otros. Este supuesto significa que es posible que en un
instante de tiempo dado todos los procesos estén siendo ejecutados simultáneamente. Este instante de tiempo
es conocido como instante crítico y supone la carga máxima que podrá exigírsele al procesador.
• Todos los procesos del programa han de ser periódicos y el periodo de los mismos, 𝑇, ha de ser conocido.
• Todos los procesos del programa han de acabar antes de que puedan ser ejecutados nuevamente.
• En el peor d ellos casos, todos los procesos tienen un tiempo de ejecución constante y conocido.
• No existen perturbaciones que puedan afectar al reparto de tiempo de uso de los procesadores de los distintos
procesos.

10.3. El enfoque de ejecución cíclico


Enfoque de ejecución cíclico: puede resumirse como una tabla de llamadas a procedimientos, cada uno de los cuales
representa una parte del código de un proceso. Esta tabla es conocida como ciclo principal y usualmente consta de
varios ciclos secundarios. Los ciclos secundarios tienen una duración fija. Algunas características importantes del
enfoque de ejecución cíclico son estas:

• Los procesos realmente no existen en tiempo de ejecución; cada ciclo secundario es una secuencia de
llamadas a procedimientos.
• Dichos procedimientos comparten un espacio de direcciones, de tal forma que se pueden pasar datos entre
ellos. Éstos no necesitan ser protegidos, porque es imposible que se dé una situación de acceso concurrente.

Inconvenientes:

• Todos los periodos de los procesos deben ser múltiplos del tiempo del ciclo secundario.
• Es complicado incluir procesos esporádicos.
• También es complicado incluir procesos con periodos grandes. El tiempo del ciclo principal determina el
máximo periodo acomodable sin tener que recurrir a planificación secundaria.
• Construir la ejecución cíclica no es una tarea sencilla.
• Es habitual que los procesos con un tiempo de cálculo notable lleguen a ser divididos en un número fijo de
procedimientos de tamaño dado y menor. Esto, por tanto, podría llegar a romper la estructura del código según
la visión de la ingeniería del software.

A pesar de estos inconvenientes, si puede plantearse un enfoque de ejecución cíclico, entonces no es necesario diseñar
ni implementar ningún test de planificabilidad más. Sin embargo, en sistemas de alta utilización, la construcción de este

Página 54 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

enfoque es realmente problemática. En esos casos, conviene utilizar esquemas heurísticos que, si bien no son óptimos,
pueden ofrecer un buen comportamiento si están bien desarrollados.

10.4. Planificación basada en tareas


Planificación basada en tareas: es un esquema de planificación que soporta la ejecución de procesos de manera
directa. Se determina qué proceso es el que debe ejecutarse en cada instante de tiempo. Esto se hace utilizando varios
atributos de planificación definidos a tal efeto. De este modo, un proceso dado estará necesariamente en uno de los
posibles estados:

• Ejecutable: cuando el proceso se encuentra listo para ser ejecutado.


• Suspendido: cuando el proceso está a la espera de que ocurra un evento. Caben aquí dos posibilidades:
- En espera de un evento temporizado: apropiado para procesos de tipo periódico.
- En espera de un evento no temporizado: apropiado para procesos esporádicos.

10.5. Planificación de prioridad fija


Planificación de prioridad fija (FPS): es el tipo de planificación basada en tareas que está más extendido. En este
esquema de planificación, cada proceso tiene una cierta prioridad fija, determinada y preasignada antes de realizar
su ejecución. Una vez establecida esta prioridad, los procesos que se encuentran en estado ejecutable se ejecutan en
el orden que determina su nivel de prioridad. En un sistema de tiempo real, estos niveles de prioridad se obtienen en
base a los requisitos de temporización de los procesos y no en base a la importancia que tienen los mismos para el
correcto funcionamiento o para la integridad del sistema.

En un momento dado, puede ocurrir que un proceso de alta prioridad deba ser ejecutado en el mismo instante que otro
proceso de menor prioridad que está siendo ejecutado. Caben entonces dos posibilidades:

• En un esquema apropiativo el planificador retirará los recursos de ejecución al proceso de menor prioridad y
se los asignará al de mayor prioridad.
• En un esquema no apropiativo el proceso de menor prioridad completaría su ejecución antes de que se
ejecute el proceso de prioridad más alta.

10.6. Planificación de primero el tiempo límite más temprano


Planificación de primero el tiempo límite más temprano (EDF): se basa en dar prioridad de ejecución a aquellos
procesos cuyo tiempo límite, 𝐷, está más cercano. Es en base a los tiempos límites absolutos, y no a los relativos, que
se otorga prioridad de ejecución a un proceso u otro. Aunque los tiempos límite relativos de cada proceso suelen
conocerse de antemano, los tiempos límite absolutos se calculan en tiempo de ejecución. Es por esto por lo que se
dice que este esquema es dinámico.

10.7. Test de planificabilidad basada en la utilización


Test de planificabilidad para planificación de prioridad fija: este test es aplicable al esquema de planificación de prioridad
fija o FPS sólo en su versión apropiativa. Este test dice que si se cumple la siguiente condición, los 𝑁 procesos
involucrados completarán su ejecución dentro de sus tiempos límite:
𝑁
𝐶𝑖 1
∑ ≤ 𝑁 · (2𝑁 − 1)
𝑇𝑖
𝑖=1

Donde 𝐶𝑖 y 𝑇𝑖 son el tiempo de ejecución en el peor caso y el periodo del proceso 𝑖, respectivamente. El miembro
derecho de la expresión anterior representa el uso del procesador que atenderá a la ejecución de los procesos y va
desde un valor máximo de uno, para 𝑁 = 1, hasta un valor mínimo de 0.693 para 𝑁 infinito.

Test de planificabilidad para planificación de primero el tiempo límite más temprano: se formula de la siguiente manera:
𝑁
𝐶𝑖
∑ ≤1
𝑇𝑖
𝑖=1

Página 55 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

Si el conjunto de proceso requiere de una utilización del procesador menor o igual a la unidad, entonces los tiempos
límite, 𝐷, de dichos procesos se cumplirán. De este modo, es fácil entender que el esquema de planificación EDF es
superior al de FPS, pues es más fácil cumplir el requisito del tes de planificabilidad y, por tanto, es más probable que
los proceso de un conjunto dado puedan cumplir todos sus tiempos límite.

Otra forma de test de planificabilidad para FPS: consiste en calcular el tiempo de respuesta en el pero caso de cada
proceso y comparar luego este valor con los tiempos límites de dichos procesos. Si los tiempos de respuesta de todos
los procesos son inferiores a sus respectivos tiempos límite, entonces el conjunto de procesos es planificable. Para un
proceso 𝑖 dado, que tiene un tiempo máximo de interferencia 𝐼, su tiempo de respuesta es:

𝑅𝑖 = 𝐶𝑖 + 𝐼𝑖

La máxima interferencia se da en la eventualidad de que todos los procesos de prioridad alta se activen al mismo
tiempo que el proceso 𝑖: esto es, en un instante crítico. Si se supone que todos los procesos se activan en el instante
0 y se considera un proceso distinto, 𝑗, que tiene una prioridad mayor que el anterior. En el intervalo [0, 𝑅𝑖 ), el proceso
𝑗 será activado un número de veces dado por:

𝑅𝑖
𝑁𝑖𝑛𝑡𝑒𝑟𝑓𝑒𝑟𝑒𝑛𝑐𝑖𝑎𝑠 = ⌈ ⌉
𝑇𝑗

Cada activación del proceso 𝑗 producirá una interferencia en la ejecución del proceso 𝑖 de tiempo 𝐶𝑗 , de modo que el
tiempo de interferencia total sufrido por el proceso será:

𝑅𝑖
𝑀𝑖𝑛𝑡𝑒𝑟𝑓𝑒𝑟𝑒𝑛𝑐𝑖𝑎𝑠 = ⌈ ⌉ · 𝐶𝑗
𝑇𝑗

Para obtener el tiempo de interferencia sufrido por el proceso 𝑖 queda solamente considerar todos los procesos de
prioridad mayor que provocan interferencias sobre el mismo y sumar los tiempos de interferencia que producen cada
uno de ellos:

𝑅𝑖
𝐼𝑖 = ∑ ⌈ ⌉ · 𝐶𝑗
𝑇𝑗
𝑗∈ℎ𝑝(𝑖)

Donde ℎ𝑝(𝑖) representa el conjunto de procesos que tienen una prioridad más alta que el proceso considerado, 𝑖.
Finalmente:

𝑅𝑖
𝑅𝑖 = 𝐶𝑖 + ∑ ⌈ ⌉ · 𝐶𝑗
𝑇𝑗
𝑗∈ℎ𝑝(𝑖)

𝑅𝑖 es un valor desconocido y, por tanto, la cantidad exacta de interferencias que sufre el proceso tampoco se sabe.
Esto dificulta la resolución de la ecuación anterior. Una forma de hacerlo es utilizando una relación de recurrencia:

𝑅𝑖
𝑤𝑖𝑛+𝑖 = 𝐶𝑖 + ∑ ⌈ ⌉ · 𝐶𝑗
𝑇𝑗
𝑗∈ℎ𝑝(𝑖)

Cuando 𝑤𝑖𝑛+𝑖 sea igual a 𝑤𝑖𝑛 , entonces se habrá dado con la solución de la ecuación.

10.8. Comparación de planificadores FPS y EDF


Ventajas FPS: FPS presenta algunas ventajas con respecto a EDF:

• FPS resulta más sencillo de implementar. Esto es debido a que, en este esquema de planificación, la prioridad
de los procesos es estática.
• Resulta también más simple introducir procesos sin tiempos límites y tan sólo asignarles una prioridad. Asignar
a los procesos un tiempo límite arbitrario es una solución más artificial que, para garantizar un buen
comportamiento, requiere, además, de un conocimiento preciso del problema.

Página 56 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

• Durante situaciones de sobrecarga, donde típicamente pueden darse condiciones de fallo, el comportamiento
de la planificación FPS resulta más predecible: los procesos con una prioridad más baja son aquellos que
tienen más probabilidad de incumplir sus tiempos límite.
• Los tests basados en la utilización para FPS y EDF son sólo suficientes para FPS y necesario y suficiente para
EDF. Por tanto, es habitual que con FPS puedan conseguirse mayores utilizaciones de las que el test informa,
reduciendo en parte esa ventaja que EDF ofrece en comparación a FPS.
• EDF no proporciona el peor caso del tiempo de interferencia, ni por tanto el de respuesta, de un proceso de la
forma sencilla que sí lo hace FPS.

10.9. Tiempo de ejecución en el peor caso


Tiempo de ejecución en el peor caso: para poder determinar el tiempo de respuesta de un proceso se puede hacer
mediante análisis o bien de manera experimental. El problema de la medición radica en que es difícil asegurar que el
tiempo que se ha medido es el máximo posible o el del peor caso. Por otra parte, el análisis implica que se debe tener
un modelo preciso del procesador para que la determinación sea acertada.

Técnicas de análisis: se basan en el uso y aplicación de dos actividades bien diferenciadas:

• Descomponer el código de cada proceso en un grafo dirigido de bloques básicos. Cada uno de estos bloques
representa el código básico.
• Utilizar el modelo disponible del procesador que se vaya a utilizar para realizar la ejecución de los procesos
para estimar el tiempo de ejecución en el peor caso de cada bloque básico de código. De este modo, se obtiene
un tiempo para cada bloque. Una vez obtenido, se colapsa el grafo dirigido. Las técnicas de reducción de grafos
para realizar el colapso varían, dependiendo de la información semántica de la que se disponga.

Otras técnicas más avanzadas pueden afinar y reducir aún más el cálculo del coste de ejecución. El proceso, sin
embargo, no es sencillo, ni está libre de errores. Además, modelar en detalle el comportamiento temporal de un
procesador no es trivial.

Dado que los sistemas de tiempo real son igualmente sometidos a muchas pruebas, se suele considerar apropiado
aplicar las técnicas de medición y las de análisis de forma combinada.

10.10. Procesos esporádicos y aperiódicos


Modelo de proceso simple modificado: para considerar procesos que presentan un comportamiento temporal aperiódico
y esporádico, deben introducirse las siguientes características en el modelo de proceso simple:

• Se considera que el valor 𝑇, antes entendido como el periodo de un proceso, ahora es el valor mínimo del
intervalo entre ejecuciones.
• Mientras que en el modelo simple se suponía que el tiempo límite de un proceso y su periodo eran iguales, el
nuevo modelo para procesos esporádicos debe permitir que el tiempo límite sea menor que el valor de 𝑇 (𝐷 <
𝑇). Esto es así porque los procesos aperiódicos a menudo se utilizan para encapsular una rutina de gestión de
error. En estos casos, el modelo de fallo del sistema podría establecer que dicha rutina de error sólo sea
invocada muy de vez en cuando, de tal forma que cuando, efectivamente, es invocada, debe significar que es
algo crítico y urgente y, por lo tanto, debe tener asociado un tiempo límite pequeño.

Requisitos de planificabilidad: hay dos reglas que deben aplicarse para cumplir los requisitos mínimos de
planificabilidad en situaciones de uso de procesos esporádicos y aperiódicos cuando éstos son de tiempo real estrictos
y flexibles:

1. Todos los procesos deben ser planificables utilizando los tiempos medios y las tasas medias de llegada.
2. Todos los procesos de tiempo real estrictos deben ser planificables utilizando los peores casos del tiempo de
ejecución y de la tasa de llegada de todos los procesos, incluidos aquellos que son flexibles. Esta regla implica
que ningún proceso de tiempo real puede incumplir su tiempo límite. Esto, sin embargo, puede llegar a producir
una utilización demasiado baja en la ejecución de los otros procesos. Si esto ocurre, deberían aplicarse
medidas que ayudasen a reducir los tiempos de ejecución (o las tasas de llegada) en el peor de los casos.

Página 57 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

Servidor: protege los recursos de procesamiento necesarios para, en cierta forma, reservarlos para los procesos
estrictos. No obstante, el servidor, además, debe permitir que los procesos flexibles se ejecuten tan pronto como sea
posible.

Procesos aperiódicos y servidores FPS: cuando un proceso que utiliza una capacidad 𝐶 del procesador llega en el
instante de tiempo 𝑡, un servidor tendrá respuesta a dicha capacidad pasadas 𝑇𝑠 unidades de tiempo. Es posible utilizar
los servidores para asegurar que los procesos esporádicos no se ejecuten con más frecuencia de lo esperado. Si un
proceso esporádico con 𝑇𝑃 y un tiempo de ejecución en el peor caso de 𝐶𝑃 se implementa a través de un servidor en
lugar de hacerlo directamente como un proceso, entonces se tendría 𝑇𝑆 = 𝑇𝑃 y 𝐶𝑆 = 𝐶𝑃 . De esta forma, el impacto del
proceso sobre los procesos de prioridad baja será limitado, incluso si el proceso esporádico llega antes de lo esperado.
Los servidores siempre intentan que los recursos de procesamiento estén, siempre que exista capacidad, disponibles
para los procesos aperiódicos, y que exista la mayor capacidad de procesamiento disponible ante la no existencia de
procesos aperiódicos, para permitir así la ejecución de procesos estrictos.

Procesos aperiódicos y servidores EDF: en este caso, el algoritmo de tiempo de ejecución asigna al servidor el tiempo
límite actual más pequeño si, y sólo si, existe un proceso aperiódico pendiente de ser servido y se dispone de capacidad
suficiente de procesamiento. Una vez que la capacidad de procesamiento ha sido agotada, el servidor queda en
suspenso hasta que dicha capacidad sea repuesta.

10.11. Sistemas para procesos con 𝑫 < 𝑻


Ordenación de prioridades monotónica de tiempo límite (DMPO): implica que la prioridad fija asignada a un proceso es
inversamente proporcional a su tiempo límite. De esta forma, si un proceso 𝐴 tiene un tiempo límite menor que un
proceso 𝐵 (𝐷𝐴 < 𝐷𝐵 ), entonces su prioridad será más alta.

Planificación de procesos con 𝐷 < 𝑇: si bien la planificación FPS puede tratar también de forma adecuada el conjunto
de requisitos de planificación donde los procesos pueden tener 𝐷 < 𝑇, esto no es aplicable a la planificación EDF.
Siempre que un conjunto de procesos cumpla un test de planificabilidad de FPS, será posible cumplir también con sus
requisitos de temporización si se ejecuta con EDF. Dicho de otro modo, un test de planificabilidad que implique
necesidad y suficiencia para FPS es también un test de suficiencia para EDF.

10.12. Interacciones y bloqueos entre procesos


Proceso bloqueado: se dice que un proceso está bloqueado cuando éste está esperando a otro proceso de menor
prioridad.

Herencia de prioridad: consiste en que un proceso herede la prioridad del proceso al que está bloqueando. Con esta
regla de herencia, la prioridad efectiva de un proceso será el valor máximo entre su prioridad por defecto y las
prioridades de los procesos que en cada instante de tiempo dependan de él.

La implementación del modelo de concurrencia de procesos afectará a la aplicación de la herencia de prioridad. Si se


usan semáforos y variables de condición, no existe una relación directa entre el acto de suspender un proceso y la
identidad del proceso al que afecta esa situación. Esto implica que la herencia de prioridades puede ser muy difícil de
implementar. Por otro lado, si se utilizan mensajes síncronos, las referencias indirectas también pueden complicar la
identificación del proceso por el que se está esperando. Por lo tanto, para facilitar el uso de la herencia de prioridades
y maximizar su eficiencia, lo mejor es usar el nombrado directo y simétrico.

La herencia de prioridad puede no estar restringida a un único paso y encadenarse (bloqueos transitivos). La prioridad
de procesos cambiará a menudo y en esta situación es posible que la mejor estrategia de planificación no sea gestionar
una cola ordenada por prioridad, sino elegir bien qué proceso se debe ejecutar en cada momento.

Puede darse el caso de que un proceso sea bloqueado más de una vez. Esto puede ocurrir cuando el proceso tiene 𝑚
secciones críticas que pueden provocar su bloqueo. En un caso así, el proceso puede llegar a ser bloqueado hasta 𝑚
veces.

Tiempo de bloqueo: si 𝐵𝑖 es el tiempo máximo de bloqueo que puede sufrir un proceso dado y 𝑆 el número de secciones
críticas que usa dicho proceso, la fórmula que determina el límite superior para 𝐵𝑖 es:

Página 58 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez
𝑆

𝐵𝑖 = ∑ 𝑢𝑡𝑖𝑙𝑖𝑧𝑎𝑐𝑖ó𝑛(𝑗) · 𝐶(𝑗)
𝑗=1

𝑢𝑡𝑖𝑙𝑖𝑧𝑎𝑐𝑖𝑜𝑛 es una función que vale 0 ó 1. Su valor será 1 cuando el recurso 𝑗 es utilizado por, al menos otro proceso
que tiene una prioridad menor que la del proceso 𝑃 considerado. En cualquier otro caso, el valor de la función será 0.
Por otro lado, 𝐶(𝑗) es el tiempo de ejecución de la sección crítica 𝑗 en el peor caso.

Tiempo de respuesta: dado un proceso con un tiempo de bloqueo máximo 𝐵, el tiempo de respuesta del proceso será:

𝑅𝑖
𝑅𝑖 = 𝐶𝑖 + ∑ ⌈ ⌉ · 𝐶𝑗 + 𝐵𝑖
𝑇𝑗
𝑗∈ℎ𝑝(𝑖)

Nuevamente, se puede construir una relación de recurrencia para resolverla:

𝑅𝑖
𝑤𝑖𝑛+𝑖 = 𝐶𝑖 + ∑ ⌈ ⌉ · 𝐶𝑗 + 𝐵𝑖
𝑇𝑗
𝑗∈ℎ𝑝(𝑖)

10.13. Protocolos de acotación de la prioridad


Protocolos de acotación de la prioridad: permiten al gestor de recursos minimizar el bloqueo y eliminar, en la medida
de lo posible, las condiciones de fallo. Hay varios tipos de protocolos para FPS:

• Protocolo original de acotación de la prioridad (OCPP).


• Protocolo inmediato de acotación de la prioridad (ICPP).

Cuando se utiliza uno de estos protocolos en un sistema monoprocesador:

• Un proceso con prioridad alta puede ser bloqueado por procesos de prioridad baja sólo en una única ocasión
durante su ejecución.
• Se previenen los bloqueos mutuos o interbloqueos.
• Se previenen los bloqueos transitivos.
• Se aseguran los accesos mutuamente excluyentes a los recursos.

Los protocolos de acotación de la prioridad aseguran que, si un recurso está bloqueado por un proceso 𝐵, y esto
conduce a que se bloquee un proceso de mayor prioridad 𝐴, entonces no se permite que ningún otro recurso que pueda
bloquear a 𝐵 sea bloqueado más que por 𝐴. De este modo, un proceso puede ser retardado no sólo mientras está
intentando bloquear un recurso previamente bloqueado, sino cuando también ese bloqueo pudiera producir un bloqueo
múltiple de procesos de mayor prioridad.

Los protocolos están libres de interbloqueos en sistemas de un solo procesador.

Protocolo original de acotación de la prioridad (OCPP): presenta las siguientes características:

• Todos los procesos tienen asignados una prioridad estática por defecto.
• Todos los recursos tienen definido un valor cota estático, que es igual a la prioridad máxima de los procesos
que lo están utilizando.
• Los procesos tienen una prioridad dinámica que es el máximo de su propia prioridad estática y de cualquier
prioridad que hereden debido a que bloquea procesos de mayor prioridad.
• Un proceso dado sólo puede bloquear un recurso si su prioridad dinámica es mayor que la cota máxima de
cualquier recurso actualmente bloqueado, excluyendo cualquiera que ese mismo proceso pueda tener ya
bloqueado.
• Se permite el bloqueo del primer recurso del sistema.

El efecto de este protocolo es que asegura que el segundo recurso del sistema sólo pueda ser bloqueado si no existe
un proceso de mayor prioridad que utilice ambos recursos. De esta forma, la cantidad máxima de tiempo que un proceso
puede ser bloqueado es igual al tiempo de ejecución de la sección crítica más larga en cualquiera de los procesos de
menor prioridad que son accedidos por procesos de alta prioridad. La ecuación para 𝐵𝑖 es:

Página 59 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

𝐵𝑖 = max (𝑢𝑡𝑖𝑙𝑖𝑧𝑎𝑐𝑖𝑜𝑛(𝑗) · 𝐶(𝑗))


1≤𝑗≤𝑆

La principal ventaja de los protocolos de acotación de la prioridad es que, por cada activación de un proceso de prioridad
alta, éste sólo puede ser bloqueado una vez por procesos que tienen una prioridad baja. La contrapartida de esta
ventaja es que habrá más procesos que experimenten bloqueos.

Protocolo inmediato de acotación de la prioridad (ICPP): presenta un enfoque más simple. La prioridad de un proceso
queda fijada en el preciso instante en el que éste bloquea un recurso. Este protocolo cuenta con las siguientes
características:

• Todos los procesos tienen asignados una prioridad estática por defecto.
• Todos los recursos tienen definidos un valor cota estático, que es igual a la prioridad máxima de los procesos
que lo están utilizando.
• Los procesos tienen una prioridad dinámica que es el máximo de su propia prioridad estática y de los valores
cota de cualquier recurso que tenga bloqueado. La consecuencia de esta característica es que un proceso sólo
podría ser bloqueado al principio de su ejecución. Cuando el proceso comienza a ejecutarse, todos los recursos
necesarios para atender a ese proceso deben estar libres. Si no lo estuvieran, sería que algún otro proceso
tiene una prioridad igual o mayor, pero en este caso la ejecución del proceso debe ser aplazada y no llega a
producirse.

Comparación entre OCPP e ICPP: el comportamiento en el peor caso de los dos protocolos es idéntico. No obstante
existe algunas diferencias:

• ICPP provoca un número de cambios de contexto menor que OCPP, ya que el bloqueo de un proceso es previo
a la ejecución del mismo.
• ICPP supone más cambios de prioridad que OCPP debido a que, con ICPP éstos se producen con cada nueva
utilización de recurso, mientras que con OCPP la prioridad sólo se modifica si ocurren bloqueos.
• ICPP es más sencillo de implementar que OCPP puesto que no es necesario monitorizar las relaciones de
bloqueo.

Pila de recursos (SRP): es un esquema popular para EDF, que funciona de un modo muy similar al ICPP para FPS.
En SRP, se le asigna un nivel de apropiación a cada proceso. Estos niveles de apropiación se asignan en base a los
tiempos límite relativos de los procesos, de tal forma que cuanto más cortos sean dichos tiempos límite, mayor será el
nivel de apropiación del proceso. También se establece para cada recurso, en tiempo de ejecución, una cota basada
en el máximo nivel de apropiación de los procesos que utilizan dicho recurso. Se impone entonces una regla por la
cual, cuando un proceso se activa, sólo puede desalojar al proceso actualmente en ejecución si su tiempo límite
absoluto es más corto y su nivel de apropiación es mayor que la cota más alta de los recursos actualmente bloqueados.
El efecto de este protocolo es el mismo que el provocado por el ICPP: los procesos sufren únicamente un bloqueo, que
ocurre al ser activados. De esta forma, se previenen los bloqueos mutuos, lo que facilita dar una fórmula sencilla para
el tiempo de bloqueo.

10.14. Un modelo de proceso extensible


Modelo de proceso extensible: parte del modelo de proceso simple para quitarle restricciones y añadirle así
características más realistas en la ejecución de sistemas en tiempo real: se incorporan los procesos esporádicos o
aperiódicos, se suma la posibilidad de que los tiempos límite fueran inferiores al periodo (𝐷 < 𝑇), y se añade la
posibilidad de que interaccionen entre sí. Además, se pueden añadir otras cinco generalizaciones:

• Planificación cooperativa.
• Fluctuaciones en la activación.
• Tiempos límite arbitrarios.
• Tolerancia a fallos.
• Desplazamiento.

Planificación cooperativa: este esquema utiliza la apropiación diferida. La planificación cooperativa aprovecha la
propiedad no acumulativa de los bloqueos aumentando el número de situaciones en las cuales se pueden producir
bloqueos. El código de la aplicación se divide en bloques no apropiables, cuyos tiempos de ejecución están limitados
por el tiempo de bloqueo máximo, 𝐵𝑚𝑎𝑥 . Al final de cada uno de estos bloques, el código de la aplicación realiza una

Página 60 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

llamada al núcleo del sistema para desplanificar. La desplanificación consiste en que, si hay al menos un proceso de
prioridad alta en estado ejecutable, el núcleo ordenará un cambio de contexto, pero, si no es así, el proceso que está
en ejecución en ese momento continúa en el siguiente bloque no apropiable. De esta forma, la ejecución normal del
código de la aplicación es cooperativa: un proceso continúa su ejecución hasta que él mismo informa que es
desplanificable. Así, la exclusión mutua está asegurada, siempre que las secciones críticas estén completamente
contenidas entre dos invocaciones de desplanificación. Por lo tanto, es de vital importancia establecer cuidadosamente
cuándo se realizan las llamadas de desplanificación.

Ventajas de la apropiación diferida:

• Puede conducir a valores menores de 𝐶.


• Aumenta la planificabilidad del sistema.
• Ofrece una mayor precisión para predecir los tiempos de ejecución de los bloques no apropiables de los
procesos, pues se puede utilizar el conocimiento de la no expropiación para predecir mejor la velocidad del
código.

Fluctuación o jitter: es el valor máximo de la variación en la activación de un proceso y suele representarse por la letra
𝐽. La ecuación para el tiempo de respuesta se actualiza para considerar la fluctuación:

𝑅𝑖 + 𝐽𝑗
𝑅𝑖 = 𝐶𝑖 + ∑ ⌈ ⌉ · 𝐶𝑗 + 𝐵𝑖
𝑇𝑗
𝑗∈ℎ𝑝(𝑖)

En situaciones donde un proceso también sufra fluctuaciones en su activación (algo que solo ocurre cuando una
implementación particular impone restricciones en la granularidad del temporizador del sistema), se tiene que medir el
𝑝𝑒𝑟𝑖𝑜𝑑𝑖𝑐𝑜
tiempo de respuesta relativo al tiempo de respuesta real. Para esto, se designa por 𝑅𝑖 a este tiempo de respuesta
relativo. Entonces, basta con sumar 𝐽𝑖 a 𝑅𝑖 :
𝑝𝑒𝑟𝑖𝑜𝑑𝑖𝑐𝑜
𝑅𝑖 = 𝑅𝑖 + 𝐽𝑖

Tiempos límites arbitrarios: si el valor del tiempo límite (𝐷𝑖 ) es mayor que 𝑇𝑖 , hay que considerar que la activación de
un proceso es retrasada hasta que finalice cualquier ejecución que se hubiese producido del mismo proceso con
anterioridad. De esta forma, si un proceso dado se ejecuta en dos periodos consecutivos, las dos activaciones deben
ser analizadas para ver cuál de ellas requiere un tiempo de respuesta más alto. Esta comprobación, además, debe
realizarse con futuras llamadas: si la segunda activación no se ha completado en el momento en el que se produce
una tercera, se debe considerar esta nueva activación también. Así, para cada activación donde existe posibilidad de
solapamiento con actividades anteriores, se define una ventana separada 𝑤(𝑞). Aplicando esto sobre la ecuación para
el tiempo de respuesta e ignorando las fluctuaciones se obtiene:

𝑤𝑖𝑛 (𝑞)
𝑤𝑖𝑛+𝑖 (𝑞) = (𝑞 + 1) · 𝐶𝑖 + ∑ ⌈ ⌉ · 𝐶𝑗 + 𝐵𝑖
𝑇𝑗
𝑗∈ℎ𝑝(𝑖)

𝑅𝑖 (𝑞) = 𝑤𝑖𝑛 (𝑞) − 𝑞 · 𝑇𝑖

El número de activaciones que habrá que considerar viene dado por el valor más pequeño de 𝑞 para el cual se cumple
que 𝑅𝑖 (𝑞) ≤ 𝑇𝑖 . Para 𝐷 ≤ 𝑇 esta relación se cumple cuando 𝑞 = 0. Por otro lado, cuando 𝑅 > 𝐷, el proceso no es
planificable.

El peor caso del tiempo de respuesta ocurre cuando se tiene el valor máximo del tiempo de respuesta para cada 𝑞:

𝑅𝑖 = max (𝑅𝑖 (𝑞))


𝑞=0,1,2,...

Si se quiere considerar las fluctuaciones en la activación, se deben considerar las fluctuaciones en la activación de los
procesos de mayor prioridad y las que afectan al proceso 𝑖. En primer lugar, se debe incrementar el factor de
interferencia si cualquier proceso de prioridad mayor sufre una fluctuación en la activación, de modo que:

𝑤𝑖𝑛 (𝑞) + 𝐽𝑖
𝑤𝑖𝑛+𝑖 (𝑞) = (𝑞 + 1) · 𝐶𝑖 + ∑ ⌈ ⌉ · 𝐶𝑗 + 𝐵𝑖
𝑇𝑗
𝑗∈ℎ𝑝(𝑖)

Página 61 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

En segundo lugar, si el proceso 𝑖 puede sufrir también fluctuaciones, entonces podría ser que dos ventanas
consecutivas se solapasen. Esto ocurriría en el caso de que el tiempo de respuesta más la fluctuación sea mayor que
el periodo del proceso:

𝑅𝑖 (𝑞) = 𝑤𝑖𝑛 (𝑞) − 𝑞 · 𝑇𝑖 + 𝐽𝑖

Tolerancia a fallos: la tolerancia a fallos mediante recuperación de errores implica siempre una cierta cantidad de
𝑓
cálculos. Si se designa mediante 𝐶𝑖 al tiempo de cálculo necesario para recuperarse de un fallo que ha aparecido en
el proceso 𝑖. Si se considera que el número de fallos permitidos es 𝐹, la ecuación del tiempo de respuesta de un proceso
quedaría como:

𝑅𝑖 𝑓
𝑅𝑖 = 𝐶𝑖 + ∑ ⌈ ⌉ · 𝐶𝑗 + 𝐵𝑖 + max (𝐹 · 𝐶𝑘 )
𝑇𝑗 𝑘∈𝑚𝑝(𝑖)
𝑗∈ℎ𝑝(𝑖)

donde 𝑚𝑝(𝑖) representa el conjunto de procesos que tienen una prioridad mayor o igual que la del proceso 𝑖.

Si el modelo de fallos considerado indica un intervalo mínimo de llegada para los fallos, la ecuación anterior se puede
escribir como:

𝑅𝑖 𝑅𝑖 𝑓
𝑅𝑖 = 𝐶𝑖 + ∑ ⌈ ⌉ · 𝐶𝑗 + 𝐵𝑖 + max (⌈ ⌉ · 𝐶𝑘 )
𝑇𝑗 𝑘∈𝑚𝑝(𝑖) 𝑇𝑓
𝑗∈ℎ𝑝(𝑖)

donde 𝑇𝑓 es el tiempo mínimo entre llegadas para fallos.

Desplazamiento: se produce un desplazamiento temporal entre la activación de unos procesos y otros cuando se
considera que los tiempos de activación no ocurren en el mismo instante. El problema es que los conjuntos de procesos
que presentan desplazamientos en su activaciones no son fáciles de analizar. De hecho, seleccionar estos
desplazamientos de tal forma que el conjunto de procesos sea óptimamente planificable es un problema NP-completo.
No obstante, en los sistemas donde los periodos de los procesos presentan algún tipo de relación entre ellos, es posible
realizar este análisis, ya que puede resultar sencillo dar a un proceso un desplazamiento dado de 𝑇/2 y analizar el
sistema resultante usando un mecanismo de transformación que elimine el desplazamiento.

Se tienen tres procesos y dos de ellos comparten el mismo periodo (𝑇𝑖𝑖 = 𝑇𝑖𝑖𝑖 ). Al proceso 𝑖𝑖𝑖 se le asigna el
𝑇 𝑇
desplazamiento 𝑇𝑖𝑖𝑖 /2. Se puede definir un proceso artificial nuevo que tendrá un periodo de 𝑇𝑛 = 𝑖𝑖 = 𝑖𝑖𝑖. El tiempo de
2 2
ejecución, 𝐶, de este nuevo proceso artificial será el máximo de los tiempos de los procesos 𝑖𝑖 y 𝑖𝑖𝑖 a los que reemplaza,
mientras que el tiempo límite, 𝐷, será el mínimo de ambos tiempos límite. Este proceso no tendrá desplazamiento y
presenta dos propiedades importantes:

• Si es planificable, los dos procesos reales reemplazados cumplirán sus tiempos límite cuando a uno de ellos
se le asigne la mitad del periodo como desplazamiento.
• Si todos los procesos de menor prioridad que el proceso artificial son planificables cuando sufren la interferencia
de éste, seguirán siendo planificables cuando el proceso artificial sea reemplazado nuevamente por los dos
procesos reales a los que sustituía.

Asignación de prioridades: se define un algoritmo que se basada en la aplicación de un teorema que establece que, si
un proceso 𝑖 tiene asignada la menor de las prioridades y es realizable, entonces, si existe una ordenación de
prioridades realizable para el conjunto completo de procesos, ese orden asignará al proceso 𝑖 la menor de las
prioridades. En el Algoritmo 10.1, 𝑐𝑜𝑛𝑗𝑢𝑛𝑡𝑜 representa un conjunto de procesos ordenado en un array por orden de
prioridad, de modo que 𝑐𝑜𝑛𝑗𝑢𝑛𝑡𝑜(𝑛) es el de prioridad más alta y 𝑐𝑜𝑛𝑗𝑢𝑛𝑡𝑜(0) el de prioridad más baja. El método
𝑇𝑒𝑠𝑡𝑃𝑟𝑜𝑐𝑒𝑠𝑜(𝑐𝑜𝑛𝑗𝑢𝑛𝑡𝑜, 𝑖) prueba si un proceso 𝑖 es realizable en ese lugar del array 𝑐𝑜𝑛𝑗𝑢𝑛𝑡𝑜. En el doble bucle,
primero se intercambian los procesos en la última posición hasta que se encuentra un resultado factible. Al hacerlo,
este proceso se fija en esa posición. Luego, se analiza la siguiente posición de prioridad. Si el bucle falla al buscar un
proceso realizable, se abandona todo el procedimiento.

Página 62 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

Algoritmo 10.1 Asignación de prioridades

Si el test de factibilidad es necesario y suficiente, entonces el orden de prioridad es óptimo. Así, para tiempos límites
arbitrarios, se encuentra un orden óptimo.

10.15. Sistemas dinámicos y análisis en línea


EDF y análisis en línea: el principal problema de EDF es que presenta un mal comportamiento en situaciones de
sobrecarga transitoria, ya que en estos casos puede producirse un efecto en cascada en el que muchos procesos
incumplan su tiempo límite. Para aliviar este problema, la mayoría de análisis en línea incluyen los dos siguientes
mecanismos:

• Un algoritmo de admisión, encargado de limitar el número de procesos que pueden competir por los recursos
de ejecución (procesadores).
• Una rutina de distribución EDF para los procesos que se admiten en dicha competición de recursos.

Algoritmo de admisión: su función es evitar que se produzca la sobrecarga de los procesadores, asegurando de este
modo que EDF funcione sin problemas. Para decidir qué procesos admitir y cuáles rechazar, se debe conocer la
importancia relativa de cada proceso, lo que suele hacerse asignando un valor añadido a los mismos. Estos valores
pueden clasificarse de alguna de estas tres formas:

• Estático: los procesos mantienen los valores, independientemente de cuándo se activen éstos.
• Dinámico: los valores cambian para los procesos en el momento en el que se activan, pero no mientras se
ejecutan.
• Adaptativo: los valores de los procesos cambian durante la propia ejecución del proceso.

Problemas del análisis en línea: hay que llegar a un compromiso entre la calidad de la planificación obtenida y los
recursos y el tiempo que se necesita para obtener dicha planificación. Un caso extremo se produce cuando cada vez
que llega un proceso nuevo, el conjunto completo de procesos debe se somete a un test exacto fuera de línea. Si el
resultado de este test indica que el conjunto de procesos resulta no ser planificable, se eliminaría el proceso de menor
valor y se volvería a ejecutar el test, repitiendo hasta que se obtuviese un conjunto planificable. Este enfoque, conocido
como el mejor posible, es óptimo para la asignación de valores estáticos o dinámicos, de no ser por las sobrecargas
que producen los test. Si se consideran estas sobrecargas, la efectividad de este enfoque queda en entredicho. Por lo
tanto, se hace recomendable usar heurísticas en la planificación en línea, aunque esto presenta el problema de que
es improbable que cada heurística funcione para todas las aplicaciones. Así pues, se necesitan mecanismos generales
a partir de los cuales las aplicaciones puedan programar sus propios esquemas para cumplir con sus propios requisitos
concretos.

Sistemas híbridos: presentan una combinación de componentes estrictos y dinámicos. Para proteger a los procesos
estrictos de las sobrecargas producidas por los procesos no estrictos, se puede usar una planificación de prioridades
estática para los procesos estrictos y servidores para el resto del trabajo. Estos servidores pueden utilizar la política de
admisión más deseable y servir el trabajo dinámico entrante mediante EDF.

10.16. Programación en Java de sistemas basados en prioridad

Página 63 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

Programación en Java de sistemas basados en prioridad: en Java para tiempo real, se pueden definir objetos
planificables. Para ello, dicho objeto debe soportar la interfaz Schedulable. Las clases AsyncEventHandler,
BoundAsyncEventHandler, NoHeapRealTimeThread y RealTimeThread, incluyen el soporte de esta interfaz. Un objeto
de este tipo ofrecerá unos ciertos parámetros de planificación. Por ejemplo, la prioridad, que, cuanto mayor sea su
valor, mayor será la prioridad de un objeto. En relación con este parámetro, se exige que las implementaciones Java
para tiempo real soporte al menos 28 niveles de prioridad de tiempo real. Pueden existir hilos que no sean de tiempo
real y que convivan con los que sí lo son. Estos hilos reciben una prioridad inferior a la prioridad de tiempo real mínima.
Los parámetros de planificación de un objeto quedan ligados a sus hilos en el mismo momento en que estos se crean.
Así, si se alteran los parámetros de planificación de un objeto, se afecta al instante a los hilos asociados.

Política de despacho: política utilizada por Java para tiempo real, es la apropiativa basada en prioridades. Una
particularidad del funcionamiento del mecanismo es que los hilos expropiados no son colocados al comienzo de la cola
de ejecución asociada con sus niveles de prioridad. Por otro lado, y además, también se incorpora un planificador de
alto nivel que se encarga de:

• Fijar la prioridad de los objetos planificables en correspondencia con el algoritmo de asignación de prioridades
asociado al algoritmo de realizabilidad.
• Elegir cuándo se admiten nuevos objetos planificables en base al algoritmo de factibilidad y a los recursos que
haya disponibles.

Soporte de herencia de prioridad: Java en tiempo real permite usar algoritmos de herencia de prioridad cuando se
accede a clases sincronizadas. Para este propósito existen tres clases:

• MonitorControl: permite establecer y leer las políticas por defecto y para un objeto dado.
• PriorityCeilingEmulation: se utiliza para crear un objeto con un techo de prioridad dado y para obtener el valor
de techo de prioridad de dicho objeto.
• PriorityInheritance: especifica el protocolo de herencia de prioridad.

Hilos aperiódicos: se pueden implementar hilos aperiódicos bajo la forma de grupos de procesos. Es posible asociar a
un grupo de hilos aperiódicos u asignarle a este grupo algunas características que puedan ayudar a realizar el análisis
de factibilidad. Se puede establecer, por ejemplo, que los hilos del grupo no consuman nunca más de una cierta
cantidad de tiempo del procesador en un periodo dado.

Página 64 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

11. Tema 11: Programación de bajo nivel


11.1. Mecanismos hardware de entrada/salida
Acceso a E/S: un sistema suele acceder a los dispositivos conectados al mismo por medio de buses lógicos destinados
a este efecto. Para acceder a los registros de dichos dispositivos, el sistema hace uso de instrucciones de ensamblado
que suelen seguir el formato:

𝐼𝑁 𝐴𝐶, 𝑃𝑈𝐸𝑅𝑇𝑂

𝑂𝑈𝑇 𝐴𝐶, 𝑃𝑈𝐸𝑅𝑇𝑂

𝐼𝑁 es una instrucción utilizada para leer el registro de un dispositivo. El dispositivo sobre cuyo registro desea operarse
viene dado por el 𝑃𝑈𝐸𝑅𝑇𝑂, que a su vez se define sobre el actuador 𝐴𝐶. 𝑂𝑈𝑇 se utiliza para escribir sobre el registro
del dispositivo.

E/S sobre memoria: cuando hay más de un dispositivo en el mismo bus lógico, algunas direcciones pueden indicar
posición de memoria mientras que otras sirven para acceder a uno de los dispositivos.

11.1.1. Dirigido por estatus

Dirigido por estatus: cada programa realiza una operación de test para conocer el estatus de cada dispositivo con el
que tenga que comunicarse. Una vez se conoce su estado o estatus, el programa será capaz de realizar las acciones
adecuadas y soportadas. Las siguientes instrucciones hardware soportan este mecanismo:

• Operaciones de test: son las que permiten determinar el estatus de un dispositivo.


• Operaciones de control: sirven para dar instrucciones al dispositivo sobre tareas u operaciones que debe
realizar que no están relacionadas con la transferencia de datos.
• Operaciones de E/S: son las encargadas de transferir datos entre el dispositivo y el procesador del sistema.

11.1.2. Dirigido por interrupción

Interrupciones: permiten que la E/S se efectúe de manera asíncrona, lo que evita la llamada espera ocupada, que
significaría tener que realizar una comprobación constante del estatus del dispositivo.

Cambio de contexto: cuando se produce una interrupción, se debe preservar el estado del proceso en el momento en
el que ocurre la interrupción para, a continuación, permitir la ejecución de la rutina de servicio adecuada. Cuando se
ha terminado de atender a la interrupción, hay que recuperar el proceso que se interrumpió y permitir que se reanude
la ejecución de este. También es posible que el planificador tome la decisión de dar el recurso del procesador a un
segundo proceso distinto, como consecuencia de los efectos que pueda provocar la interrupción.

Mecanismo de cambio de contexto: elemento presente en todos los mecanismos dirigidos por interrupción. Cuando
ocurre una interrupción se encarga de preservar:

• Posición de memoria de la instrucción actual, o la siguiente, en la secuencia de ejecución.


• Información de estatus del programa.
• Contenidos de los registros programables.

Se definen tres niveles de cambios de contexto, en base a las posibilidades que ofrece el hardware para realizar la
preservación y posterior restauración del estado del proceso:

• Básico: únicamente se guarda y se actualiza el contador de programa.


• Parcial: se guardan tanto el contador del programa como los registros de estatus del programa para alojar
nuevos valores.
• Completo: se guarda el contexto completo del proceso y se aloja uno nuevo.

Hay casos en los que las acciones no son suficientes para preservar el estado del proceso, por lo que sería necesario
un soporte adicional de software para esta tarea.

Página 65 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

Identificación del dispositivo de la interrupción: elemento común a los mecanismos dirigidos por interrupción. Este
elemento es necesario porque distintos dispositivos de E/S requieren de diferentes rutinas de manejo de interrupciones.
Por tanto, se debe identificar primero qué dispositivo ha lanzado la interrupción para poder utilizar el manejador
adecuado.

Identificación de la interrupción: este paso, común a todos los mecanismos dirigidos por interrupción, consiste en
determinar la causa por la cual se produce la interrupción. En ocasiones, la causa se determina en base a la información
de estatus del dispositivo E/S. En otras, la propia interrupción indica esta causa, si es que el dispositivo cuenta con
diferentes interrupciones o con varios canales.

Control de interrupciones: permite ignorar o no las interrupciones de un dispositivo E/S dependiendo de si éstas son
permitidas en el sistema. La habilitación e inhabilitación de dichas interrupciones se puede garantizar de distintas
maneras:

• Por control por estatus de interrupción: usa indicadores para habilitar e inhabilitar las interrupciones. Dichos
indicadores se dan a través de una tabla de estado o por medio del dispositivo y palabras de estatus de
programa.
• Por control por máscara de interrupción: asocian cada interrupción de un dispositivo de E/S con un bit
concreto de palabra. Cuando el bit está a uno, se habilita la interrupción; cuando está a cero, se inhabilita.
• Por control por nivel de interrupción: asocian un nivel a cada dispositivo y el nivel actual del procesador
determina qué dispositivos podrán interrumpirle.

Control de prioridad: los dispositivos de E/S pueden presentar una mayor o menor urgencia a la hora de ser atendidos.
Existen mecanismos de prioridad que gestionan las interrupciones producidas por estos dispositivos. Dichos
mecanismos pueden ser estáticos o dinámicos y suelen estar relacionados con los mecanismos de control de
interrupción de los dispositivos y con los niveles de prioridad del procesador.

Dirigido por interrupción y controlado por programa: funciona de forma que el procesador, cuando se reconoce la
petición de interrupción de un dispositivo, suspende el proceso en ejecución para, en su lugar, ejecutar un proceso
específico encargado de manejar la interrupción. Este proceso efectúa entonces las acciones necesarias y, cuando
finaliza, se recupera el estado del procesador que había antes de que se produjese la interrupción, devolviendo así el
uso del procesador al proceso que se suspendió previamente.

Dirigido por interrupción e iniciado por programa: se conoce como acceso a memoria (DMA). Cuando se emplaza un
dispositivo DMA entre el dispositivo de E/S y la memoria principal, éste asume las funciones de procesador en lo que
respecta a las transferencias de datos que se producen entre el dispositivo de E/S y la memoria del sistema. En esta
situación, el programa inicia la comunicación E/S, pero es el dispositivo DMA el que realmente controla la transferencia
de datos. Desde el DMA se efectúa una petición de un ciclo de memoria por cada fragmento de datos que se va a
transferir y una transferencia se realiza y hace efectiva cuando se accede a la petición. Una vez se ha completado la
transferencia entera, se produce una interrupción de transferencia completa desde el dispositivo DMA, lo que se
gestiona ya mediante el mecanismo dirigido por interrupción y controlado por programa.

Los dispositivos DMA provocan un impacto en el acceso a la memoria del sistema, conocido como robo de ciclo. Este
efecto puede producir que el sistema presente un comportamiento no determinista, lo que hace muy difícil calcular los
tiempos de ejecución de los programas en el peor caso.

Dirigido por interrupción y controlado por programa de canal: elimina la carga del procesador en la gestión de los
dispositivos de E/S. Se distinguen tres componentes principales:

• Canal hardware y los dispositivos conectados: el canal hardware se encarga de hacer las mismas gestiones
que los dispositivos DMA. También dirige las operaciones de control del dispositivo siguiendo las indicaciones
de los programas de canal.
• Programa de canal: se ejecuta desde la aplicación.
• Instrucciones de E/S.

Cuando se indica al canal que ejecute un programa de canal dado, el canal seleccionado y el dispositivo comienzan a
actuar por cuenta propia y continúan así hasta que el programa canal finaliza.

Página 66 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

Los tiempos de acceso a memoria pueden verse afectados de manera imprevisible ya que, a fin de cuentas, un canal
hardware se puede entender, en muchos aspectos, como un procesador independiente que comparte memoria con el
procesador del sistema.

11.2. Requisitos del lenguaje


Mecanismos de encapsulamiento y modularidad: las interfaces de bajo nivel de los dispositivos son dependientes de
la máquina, por lo que no suelen ser portables. Es importante separar las secciones de código de un software que son
portables de las que no lo son. Por lo tanto, es recomendable encapsular siempre que sea posible, todo el código que
depende de la máquina en unidades fácilmente identificables.

Modelos abstractos de manejo de dispositivos: la comunicación con los dispositivos de E/S se puede considerar como
procesos que se ejecutan de manera paralela a los procesos habituales que se ejecutan en el procesador del sistema.
Estos supuestos procesos pueden comunicarse y sincronizarse con aquellos que se ejecutan en el procesador del
sistema si proporcionan:

• Mecanismos para la representación, direccionamiento y manipulación de los registros del dispositivo:


cada registro del dispositivo de E/S puede representarse bien como una variable del programa, bien como un
objeto o bien como un canal de comunicación.
• Una representación de las interrupciones: esta representación debe ser adecuada y puede tener distintas
formas:
- Procedimental: la interrupción se interpreta como si se tratase de una llamada a un procedimiento,
invocado en este caso por el proceso del dispositivo de E/S. La comunicación y sincronización debe
ser programada en el procedimiento de manejo de interrupción. Este procedimiento no es anidado y,
por tanto, sólo podría acceder al estado global o al estado local del manejador. Es la representación
más popular entre los lenguajes de programación y sistemas operativos de tiempo real.
- Proceso esporádico: la interrupción es una solicitud de ejecución de un proceso. Implica un cambio
de contexto. El manejador sería un proceso esporádico que puede acceder tanto a los datos
persistentes locales como a los globales.
- Notificación asíncrona: la interrupción es una notificación producida de manera asíncrona y dirigida
a un proceso específico. Implica un cambio de contexto. El manejador puede acceder tanto al estado
local como al global del proceso.
- Sincronización por variable de condición compartida: la interrupción es un proceso de
sincronización de condición por medio de variables compartidas. Implica un cambio de contexto. El
manejador puede acceder a los estados locales y globales del proceso.
- Sincronización basada en mensajes: la interrupción es un mensaje que no tiene contenido y que es
enviado por un canal de comunicación. El proceso receptor podría acceder sólo al estado local del
proceso.

11.3. Planificación de controladores de dispositivos


Sincronización por interrupción: cuando una interrupción libera una tarea esporádica para la ejecución, se tiene que
considerar el coste del manejador de interrupciones en sí, ya que puede darse que la prioridad del controlador sea
mayor que la de la tarea, con lo que tareas con una prioridad mayor, pero menor que el manejador, se verían
interferidas. Es decir, puede producirse una inversión de la prioridad. En la mayoría de los sistemas, las
interrupciones tienen prioridades superiores a las de los programas. Cuando se usa un objeto protegido para sincronizar
el manejador y la tarea, también se debe considerar el efecto sobre los bloqueos.

Sincronización por consulta de estado: el protocolo a seguir es:

1. Petición de lectura.
2. Espera hasta que el dispositivo hardware efectúa la lectura.
3. Acceso al registro para llevar la lectura al programa.

El problema reside en cómo gestionar el retraso mientras se realiza la lectura. En función de la demora que se pueda
considerar, hay tres enfoques:

Página 67 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

• Espera ocupada: solo es aceptable si la operación es muy breve. El procedimiento consiste en acotar la
duración de la espera en el bucle. En este caso, el tiempo de cómputo será la suma de los dos segmentos más
la duración máxima de la espera.
• Replanificación del proceso para un instante posterior: se suspende la tarea y se consideran los dos
segmentos de la misma como dos tareas por separado. El tiempo de respuesta es igual a la suma de los
tiempos de respuesta de cada segmento más el retardo. Este caso tiene tres implicaciones significativas:
- Los tiempos de respuesta no son tan fáciles de calcular, cada segmento de la tarea debe analizarse
por separado.
- El tiempo extra de cálculo involucrado en retrasar y reprogramar debe agregarse al peor tiempo de
ejecución de la tarea.
- Hay un impacto en el bloqueo, se puede bloquear al principio de cada uno de los segmentos, aunque
se use el protocolo del techo de prioridad inmediato.
• Para procesos periódicos, separación de la acción entre periodos, lo que se denomina desplazamiento del
periodo. Para ello se puede considerar la operación mostrada en el Código 11.1 al final del ciclo.

Código 11.1 Desplazamiento del periodo

Dado que la lectura es del periodo anterior, puede que no siempre sea válida, ya que los datos pueden ser
viejos cuando se usan. Para asegurar que da tiempo, tiene que cumplirse que 𝐷 ≤ 𝑇 − 𝑆, donde 𝑆 es el tiempo
necesario para la operación, y la máxima antigüedad de los datos es 𝑇 + 𝑅.

11.4. Gestión de memoria


Estructuras de datos: en muchos lenguajes para la implementación de sistemas de tiempo real se proporcionan dos
estructuras de datos que ayudan a la gestión de memoria: la pila (stack) y el montón (heap). En Java, la pila se usa
generalmente para almacenar variables locales de tipos de datos básicos. La memoria montón es la región de memoria
disponible para las solicitudes de memoria dinámica del sistema operativo.

Gestión de memoria: se necesita un sistema de gestión de reservas y liberaciones de memoria. Para asignar memoria
se puede disponer de un operador, que se suele denominar new, que devolverá un puntero a la memoria montón donde
se dispondrá de suficiente espacio para almacenar la estructura de datos. Las liberaciones de memoria se pueden
hacer de varias formas:

• Exigir que se haga expresamente por parte del programador.


• Requerir que el sistema de tiempo real monitorice la memoria para determinar de forma lógica cuando no se
va a seguir accediendo a ella.
• Usar un recolector de basura que se encarga de liberar los fragmentos que ya no se utilizan. Este enforque
es el más general, puesto que permite que la memoria se libera aun cuando su tipo de acceso asociado está
disponible.

Recolección de basura: se puede realizar cuando la memoria montón está llena o incrementalmente. La ejecución de
la recolección de basura tiene un impacto significativo en el tiempo de respuesta de un hilo de tiempo crítico. Por lo
que también puede tener un impacto importante sobre la predictibilidad en la ejecución de los procesos.

Recolección de basura en Java: en las especificaciones de tiempo real para Java se reconoce que es necesario permitir
la administración de la memoria que no se ve afectada por los problemas de la recolección de basura. Para ello,
introduce las denominadas áreas de memoria inmortales y delimitadas (o de alcance), que son áreas de memoria

Página 68 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez

que están fuera de la memoria montón y, por lo tanto, no están sujetas a la recolección de basura. Si un objeto
programable está activo en un área de memoria, todas las llamadas a new crean el objeto en esa área de memoria.

La memoria asociada con los objetos asignados en la memoria inmortal se comparte por todos los hilos de la aplicación
y no está sujeta a la recolección de basura, nunca se liberan durante la vida útil de la aplicación. Los objetos asignados
en la memoria delimitada tienen un tiempo de vida bien definido. Los objetos programables pueden entrar y salir de un
área de memoria delimitada. Mientras se ejecutan dentro de esa área, todas las asignaciones de memoria se realizan
desde la memoria delimitada. Cuando no hay objetos programables activos dentro de un área de memoria delimitada,
la memoria asignada se recupera. Para ello, s asigna un contador a la memoria delimitada que indica cuantos procesos
tienen acceso a ella, liberándose cuando el contador es cero.

Memoria delimitada: hay dos tipos:

• Memoria de tiempo lineal: requiere que el tiempo de asignación sea directamente proporcional al tamaño del
objeto asignado.
• Memoria de tiempo variable: la asignación puede ocurrir en un tiempo variable.

Tamaño de la pila: hay que disponer de un mecanismo para el control de la pila, aunque el cálculo del tamaño es algo
más complejo, ya que según se ejecutan los procedimientos, las pilas correspondientes van aumentando. Para estimar
con precisión la extensión máxima de este crecimiento, es necesario conocer el comportamiento de ejecución de cada
tarea. Este conocimiento es similar al requerido para llevar a cabo el análisis del peor tiempo de ejecución.
Consecuentemente, es útil disponer de herramientas para el análisis del flujo de control a partir del código de la tarea.

Página 69 de 69

También podría gustarte