Resumen STR
Resumen STR
Resumen STR
06/06/2022
Cristina Otero Rodríguez
• 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.
• 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.
• 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.
• 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.
• 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:
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
• 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.
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).
• 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.
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:
• 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.
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.
• 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:
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.
• 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.
• 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
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.
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.
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:
Métodos de flujo de datos: para soportar un sistema en tiempo real, deben extenderse de tal forma que suministren:
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
• 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.
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.
Fallos o defectos: causas de los errores. Pueden ser algorítmicas, mecánicas o eléctricas.
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:
• 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
• 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:
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:
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.
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.
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:
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.
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.
Página 12 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez
• 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.
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.
• 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.
Página 13 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez
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.
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.
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.
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:
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.
• 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
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:
Página 16 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez
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.
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:
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:
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.
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.
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.
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.
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.
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.
• 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.
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.
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.
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.
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:
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
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.
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:
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:
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.
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.
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.
Tipos de tareas: en función de la interacción entre tares se distinguen tres tipos de conductas:
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.
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.
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.
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.
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.
Página 26 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez
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:
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
Página 28 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez
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.
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.
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.
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.
• 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.
• 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
• 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.
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.
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.
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.
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.
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:
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
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.
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.
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.
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
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
• 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.
• 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.
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.
• 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.
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.
• 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.
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.
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.
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.
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.
• 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.
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:
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
• 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.
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
Página 45 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez
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:
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.
• 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.
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.
Página 47 de 69
Sistemas en Tiempo Real
06/06/2022
Cristina Otero Rodríguez
• 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
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:
𝑈𝑇𝐶 = 𝑇𝐴𝐼 + 𝐻
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:
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.
• 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.
• 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.
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.
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
• 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.
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 (𝑆).
• 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 dinámico: se toman decisiones relativas a la planificación durante el tiempo de ejecución.
• 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.
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.
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.
• 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.
• 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.
• 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.
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.
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 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á:
𝑅𝑖
𝑅𝑖 = 𝐶𝑖 + ∑ ⌈ ⌉ · 𝐶𝑗 + 𝐵𝑖
𝑇𝑗
𝑗∈ℎ𝑝(𝑖)
𝑅𝑖
𝑤𝑖𝑛+𝑖 = 𝐶𝑖 + ∑ ⌈ ⌉ · 𝐶𝑗 + 𝐵𝑖
𝑇𝑗
𝑗∈ℎ𝑝(𝑖)
• 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.
• 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
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.
• 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.
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 𝑞:
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 (⌈ ⌉ · 𝐶𝑘 )
𝑇𝑗 𝑘∈𝑚𝑝(𝑖) 𝑇𝑓
𝑗∈ℎ𝑝(𝑖)
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
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.
• 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.
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
𝐼𝑁 𝐴𝐶, 𝑃𝑈𝐸𝑅𝑇𝑂
𝐼𝑁 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.
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:
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:
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:
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.
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:
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.
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 𝑇 + 𝑅.
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:
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 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