Un sistema operativo en tiempo real ( RTOS ) es un sistema operativo (SO) para aplicaciones informáticas en tiempo real que procesa datos y eventos que tienen limitaciones de tiempo críticamente definidas. Un RTOS se diferencia de un sistema operativo de tiempo compartido , como Unix, que gestiona el uso compartido de recursos del sistema con un programador, búferes de datos o priorización de tareas fija en entornos multitarea o multiprogramación. Los requisitos de tiempo de procesamiento deben entenderse plenamente y limitarse, en lugar de limitarse a limitarse a un mínimo. Todo el procesamiento debe ocurrir dentro de las restricciones definidas. Los sistemas operativos en tiempo real están controlados por eventos y son preventivos , lo que significa que el sistema operativo puede monitorear la prioridad relevante de las tareas en competencia y realizar cambios en la prioridad de la tarea. Los sistemas controlados por eventos cambian entre tareas según sus prioridades, mientras que los sistemas de tiempo compartido cambian la tarea según las interrupciones del reloj . [1]
Una característica clave de un RTOS es el nivel de coherencia con respecto a la cantidad de tiempo que lleva aceptar y completar la tarea de una aplicación ; la variabilidad es " jitter ". [2] Un sistema operativo "duro" en tiempo real (RTOS duro) tiene menos fluctuación que un sistema operativo "suave" en tiempo real (RTOS suave); una respuesta tardía es una respuesta incorrecta en un RTOS duro, mientras que una respuesta tardía es aceptable en un RTOS suave. El objetivo principal del diseño no es un alto rendimiento , sino más bien una garantía de una categoría de rendimiento suave o duro . Un RTOS que habitual o generalmente puede cumplir con una fecha límite es un sistema operativo suave en tiempo real, pero si puede cumplir una fecha límite de manera determinista , es un sistema operativo difícil en tiempo real. [3]
Un RTOS tiene un algoritmo avanzado para la programación . La flexibilidad del programador permite una orquestación más amplia de las prioridades de los procesos en el sistema informático, pero un sistema operativo en tiempo real se dedica con mayor frecuencia a un conjunto limitado de aplicaciones. Los factores clave en un sistema operativo en tiempo real son una latencia mínima de interrupción y una latencia mínima de conmutación de subprocesos ; Un sistema operativo en tiempo real se valora más por la rapidez o la previsibilidad de su respuesta que por la cantidad de trabajo que puede realizar en un período de tiempo determinado. [4]
Un RTOS es un sistema operativo en el que el tiempo necesario para procesar un estímulo de entrada es menor que el tiempo transcurrido hasta el siguiente estímulo de entrada del mismo tipo.
Los diseños más comunes son:
Los diseños de tiempo compartido cambian de tareas con más frecuencia de lo estrictamente necesario, pero ofrecen una multitarea más fluida , dando la ilusión de que un proceso o usuario tiene uso exclusivo de una máquina.
Los primeros diseños de CPU necesitaban muchos ciclos para cambiar de tarea durante los cuales la CPU no podía hacer nada más útil. Debido a que el cambio tomaba tanto tiempo, los primeros sistemas operativos intentaron minimizar la pérdida de tiempo de CPU evitando cambios innecesarios de tareas.
En diseños típicos, una tarea tiene tres estados:
La mayoría de las tareas están bloqueadas o listas la mayor parte del tiempo porque generalmente sólo se puede ejecutar una tarea a la vez por núcleo de CPU . La cantidad de elementos en la cola listos puede variar mucho, según la cantidad de tareas que el sistema necesita realizar y el tipo de programador que utiliza. En sistemas más simples, no preventivos pero aún así multitarea, una tarea tiene que ceder su tiempo en la CPU a otras tareas, lo que puede causar que la cola lista tenga una mayor cantidad de tareas generales en estado lista para ser ejecutada ( falta de recursos ). .
Por lo general, la estructura de datos de la lista lista en el programador está diseñada para minimizar el tiempo transcurrido en el peor de los casos en la sección crítica del programador, durante el cual se inhibe la preferencia y, en algunos casos, todas las interrupciones están deshabilitadas, pero la elección La estructura de datos depende también del número máximo de tareas que pueden estar en la lista lista.
Si nunca hay más de unas pocas tareas en la lista lista, entonces probablemente sea óptima una lista doblemente vinculada de tareas listas. Si la lista lista generalmente contiene solo unas pocas tareas pero ocasionalmente contiene más, entonces la lista debe ordenarse por prioridad, de modo que encontrar la tarea de mayor prioridad para ejecutar no requiera recorrer la lista. En cambio, insertar una tarea requiere recorrer la lista.
Durante esta búsqueda, no se debe inhibir la preferencia. Las secciones críticas largas deben dividirse en partes más pequeñas. Si se produce una interrupción que prepara una tarea de alta prioridad durante la inserción de una tarea de baja prioridad, esa tarea de alta prioridad se puede insertar y ejecutar inmediatamente antes de que se inserte la tarea de baja prioridad.
El tiempo de respuesta crítico, a veces llamado tiempo de retorno, es el tiempo que lleva poner en cola una nueva tarea lista y restaurar el estado de ejecución de la tarea de mayor prioridad. En un RTOS bien diseñado, preparar una nueva tarea requerirá de 3 a 20 instrucciones por cada entrada de la cola lista, y la restauración de la tarea lista de mayor prioridad requerirá de 5 a 30 instrucciones.
En los sistemas avanzados, las tareas en tiempo real comparten recursos informáticos con muchas tareas que no son en tiempo real, y la lista lista puede ser arbitrariamente larga. En tales sistemas, una lista lista para el programador implementada como una lista enlazada sería inadecuada.
Algunos algoritmos de programación RTOS comúnmente utilizados son: [5]
Un sistema operativo multitarea como Unix es pobre en tareas en tiempo real. El programador otorga la máxima prioridad a los trabajos con menor demanda en la computadora, por lo que no hay forma de garantizar que un trabajo en el que el tiempo es crítico tendrá acceso a suficientes recursos. Los sistemas multitarea deben gestionar el intercambio de datos y recursos de hardware entre múltiples tareas. Por lo general, no es seguro que dos tareas accedan a los mismos datos específicos o recursos de hardware simultáneamente. [6] Hay tres enfoques comunes para resolver este problema:
Los sistemas operativos de propósito general generalmente no permiten que los programas de usuario enmascaren (deshabiliten) las interrupciones , porque el programa de usuario podría controlar la CPU durante el tiempo que esté diseñado para hacerlo. Algunas CPU modernas no permiten que el código en modo de usuario desactive las interrupciones, ya que dicho control se considera un recurso clave del sistema operativo. Sin embargo, muchos sistemas integrados y RTOS permiten que la aplicación se ejecute en modo kernel para una mayor eficiencia de las llamadas al sistema y también para permitir que la aplicación tenga un mayor control del entorno operativo sin requerir la intervención del sistema operativo.
En sistemas de un solo procesador, una aplicación que se ejecuta en modo kernel y enmascara las interrupciones es el método de menor sobrecarga para evitar el acceso simultáneo a un recurso compartido. Si bien las interrupciones están enmascaradas y la tarea actual no realiza una llamada de bloqueo al sistema operativo, la tarea actual tiene uso exclusivo de la CPU ya que ninguna otra tarea o interrupción puede tomar el control, por lo que la sección crítica está protegida. Cuando la tarea sale de su sección crítica, debe desenmascarar las interrupciones; Las interrupciones pendientes, si las hay, se ejecutarán. El enmascaramiento temporal de interrupciones sólo debe realizarse cuando el camino más largo a través de la sección crítica es más corto que la latencia máxima de interrupción deseada . Normalmente, este método de protección se utiliza sólo cuando la sección crítica consta de solo unas pocas instrucciones y no contiene bucles. Este método es ideal para proteger registros de mapa de bits de hardware cuando los bits están controlados por diferentes tareas.
Cuando el recurso compartido debe reservarse sin bloquear todas las demás tareas (como esperar a que se escriba la memoria Flash), es mejor utilizar mecanismos también disponibles en sistemas operativos de propósito general, como un mutex y mensajería entre procesos supervisada por el sistema operativo. Dichos mecanismos implican llamadas al sistema y generalmente invocan el código del despachador del sistema operativo al salir, por lo que normalmente requieren cientos de instrucciones de la CPU para ejecutarse, mientras que enmascarar interrupciones puede requerir tan solo una instrucción en algunos procesadores.
Un mutex (no recursivo) está bloqueado o desbloqueado. Cuando una tarea ha bloqueado el mutex, todas las demás tareas deben esperar a que su propietario desbloquee el mutex : el hilo original. Una tarea puede establecer un tiempo de espera en su espera por un mutex. Existen varios problemas bien conocidos con los diseños basados en mutex, como la inversión de prioridad y los interbloqueos .
En la inversión de prioridad, una tarea de alta prioridad espera porque una tarea de baja prioridad tiene un mutex, pero a la tarea de menor prioridad no se le da tiempo de CPU para finalizar su trabajo. Una solución típica es hacer que la tarea que posee un mutex "herede" la prioridad de la tarea de mayor espera. Pero este enfoque simple se vuelve más complejo cuando hay múltiples niveles de espera: la tarea A espera un mutex bloqueado por la tarea B , que espera un mutex bloqueado por la tarea C. El manejo de múltiples niveles de herencia hace que otro código se ejecute en un contexto de alta prioridad y, por lo tanto, puede provocar la inanición de subprocesos de prioridad media.
En un punto muerto , dos o más tareas bloquean el mutex sin tiempos de espera y luego esperan eternamente el mutex de la otra tarea, creando una dependencia cíclica. El escenario de interbloqueo más simple ocurre cuando dos tareas bloquean alternativamente dos mutex, pero en el orden opuesto. El punto muerto se evita mediante un diseño cuidadoso.
El otro enfoque para compartir recursos es que las tareas envíen mensajes en un esquema organizado de paso de mensajes . En este paradigma, el recurso es gestionado directamente por una sola tarea. Cuando otra tarea quiere interrogar o manipular el recurso, envía un mensaje a la tarea administradora. Aunque su comportamiento en tiempo real es menos nítido que el de los sistemas de semáforos , los sistemas simples basados en mensajes evitan la mayoría de los riesgos de bloqueo de protocolos y, en general, se comportan mejor que los sistemas de semáforos. Sin embargo, son posibles problemas como los de los semáforos. La inversión de prioridad puede ocurrir cuando una tarea está trabajando en un mensaje de baja prioridad e ignora un mensaje de mayor prioridad (o un mensaje que se origina indirectamente en una tarea de alta prioridad) en su cola de mensajes entrantes. Los puntos muertos de protocolo pueden ocurrir cuando dos o más tareas esperan entre sí para enviar mensajes de respuesta.
Dado que un controlador de interrupciones bloquea la ejecución de la tarea de mayor prioridad y dado que los sistemas operativos en tiempo real están diseñados para mantener la latencia de los subprocesos al mínimo, los controladores de interrupciones generalmente se mantienen lo más cortos posible. El manejador de interrupciones pospone toda interacción con el hardware si es posible; normalmente, todo lo que se necesita es reconocer o deshabilitar la interrupción (para que no vuelva a ocurrir cuando regrese el controlador de interrupciones) y notificar una tarea que debe realizarse. Esto se puede hacer desbloqueando una tarea del controlador mediante la liberación de un semáforo, estableciendo una bandera o enviando un mensaje. Un programador a menudo brinda la capacidad de desbloquear una tarea del contexto del controlador de interrupciones.
Un sistema operativo mantiene catálogos de objetos que administra, como subprocesos, mutex, memoria, etc. Las actualizaciones de este catálogo deben controlarse estrictamente. Por esta razón, puede ser problemático cuando un controlador de interrupciones llama a una función del sistema operativo mientras la aplicación también lo hace. La función del sistema operativo llamada desde un controlador de interrupciones podría encontrar que la base de datos de objetos está en un estado inconsistente debido a la actualización de la aplicación. Hay dos enfoques principales para abordar este problema: la arquitectura unificada y la arquitectura segmentada. Los RTOS que implementan la arquitectura unificada resuelven el problema simplemente deshabilitando las interrupciones mientras se actualiza el catálogo interno. La desventaja de esto es que la latencia de las interrupciones aumenta, lo que puede provocar la pérdida de interrupciones. La arquitectura segmentada no realiza llamadas directas al sistema operativo, sino que delega el trabajo relacionado con el sistema operativo a un controlador independiente. Este controlador se ejecuta con una prioridad más alta que cualquier subproceso pero menor que los controladores de interrupciones. La ventaja de esta arquitectura es que agrega muy pocos ciclos para interrumpir la latencia. Como resultado, los sistemas operativos que implementan la arquitectura segmentada son más predecibles y pueden manejar tasas de interrupción más altas en comparación con la arquitectura unificada. [ cita necesaria ]
De manera similar, el modo de administración del sistema en hardware compatible con x86 puede tardar mucho tiempo antes de devolver el control al sistema operativo.
La asignación de memoria es más crítica en un sistema operativo en tiempo real que en otros sistemas operativos.
Primero, por motivos de estabilidad no puede haber pérdidas de memoria (memoria que se asigna pero no se libera después de su uso). El dispositivo debería funcionar indefinidamente, sin necesidad de reiniciarlo. Por este motivo, la asignación dinámica de memoria está mal vista. [ cita necesaria ] Siempre que sea posible, toda la asignación de memoria requerida se especifica estáticamente en el momento de la compilación.
Otra razón para evitar la asignación de memoria dinámica es la fragmentación de la memoria. Con la asignación y liberación frecuente de pequeños fragmentos de memoria, puede ocurrir una situación en la que la memoria disponible se divide en varias secciones y el RTOS no puede asignar un bloque de memoria continuo lo suficientemente grande, aunque hay suficiente memoria libre. En segundo lugar, la velocidad de asignación es importante. Un esquema de asignación de memoria estándar escanea una lista vinculada de longitud indeterminada para encontrar un bloque de memoria libre adecuado, [7] lo cual es inaceptable en un RTOS ya que la asignación de memoria debe ocurrir dentro de un cierto período de tiempo.
Debido a que los discos mecánicos tienen tiempos de respuesta mucho más largos e impredecibles, el intercambio a archivos de disco no se utiliza por las mismas razones que la asignación de RAM discutida anteriormente.
El algoritmo simple de bloques de tamaño fijo funciona bastante bien para sistemas integrados simples debido a su baja sobrecarga.