Un sistema operativo en tiempo real ( RTOS ) es un sistema operativo (SO) para aplicaciones de computación en tiempo real que procesa datos y eventos con restricciones de tiempo definidas de manera crítica. Un RTOS se dirige principalmente a dispositivos con recursos limitados, como los microcontroladores . Se diferencia de un sistema operativo de tiempo compartido , como Unix , que gestiona el uso compartido de los recursos del sistema mediante un planificador, búferes de datos o priorización fija de tareas en entornos multitarea o multiprogramación. Todas las operaciones deben completarse de forma verificable dentro de las restricciones de tiempo y recursos dadas; de lo contrario, el RTOS fallará de forma segura . Los sistemas operativos en tiempo real son orientados a eventos y preventivos , lo que significa que el SO puede supervisar la prioridad relevante de las tareas en competencia y modificarla.
Características
Una característica clave de un RTOS es su nivel de consistencia con respecto al tiempo que tarda en aceptar y completar la tarea de una aplicación ; la variabilidad se denomina " jitter ". [ 1 ] El principal objetivo de diseño no es un alto rendimiento , sino garantizar una categoría de rendimiento flexible o estricta . Un RTOS que suele cumplir con un plazo es un sistema operativo de tiempo real flexible, pero si puede cumplirlo de forma determinista, es un sistema operativo de tiempo real estricto. [ 2 ]
Un RTOS cuenta con un algoritmo avanzado para la planificación . La flexibilidad del planificador permite una orquestación más amplia de las prioridades de los procesos en todo el sistema informático, pero un sistema operativo en tiempo real se dedica con mayor frecuencia a un conjunto reducido de aplicaciones. Los factores clave en un sistema operativo en tiempo real son la mínima latencia de interrupción y la mínima latencia de cambio 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. [ 3 ]
Filosofías de diseño
Los diseños más comunes son:
- Orientado a eventos: cambia de tarea solo cuando un evento de mayor prioridad necesita atención; se denomina prioridad preventiva o planificación por prioridad.
- El tiempo compartido alterna las tareas en función de una interrupción programada y de eventos; también se denomina round-robin .
Los sistemas de tiempo compartido permiten cambiar de tarea con más frecuencia de la estrictamente necesaria, pero facilitan la multitarea , dando la ilusión de que un proceso o usuario tiene el uso exclusivo de una máquina.
Los primeros diseños de CPU requerían muchos ciclos para cambiar de tarea, durante los cuales la CPU no podía realizar ninguna otra actividad útil. Debido a que el cambio de tarea era tan lento, los primeros sistemas operativos intentaron minimizar el desperdicio de tiempo de CPU evitando cambios de tarea innecesarios.
Programación
En los diseños típicos, una tarea tiene tres estados:
- En ejecución (ejecutándose en la CPU);
- Listo (listo para ser ejecutado);
- Bloqueado (esperando un evento, por ejemplo, una operación de entrada/salida).
La mayoría de las tareas están bloqueadas o listas la mayor parte del tiempo porque, por lo general, solo una tarea puede ejecutarse a la vez por núcleo de CPU . El número de elementos en la cola de tareas listas puede variar considerablemente, dependiendo del número de tareas que el sistema deba realizar y del tipo de planificador que utilice. En sistemas más simples, no preemptivos pero que aún admiten multitarea, una tarea tiene que ceder su tiempo en la CPU a otras tareas, lo que puede provocar que la cola de tareas listas tenga un mayor número de tareas en estado de espera para su ejecución ( agotamiento de recursos ).
Por lo general, la estructura de datos de la lista de tareas listas en el planificador está diseñada para minimizar el tiempo máximo que se pasa en la sección crítica del planificador, durante la cual se inhibe la interrupción y, en algunos casos, se desactivan todas las interrupciones; sin embargo, la elección de la estructura de datos también depende del número máximo de tareas que pueden estar en la lista de tareas listas.
Si la lista de tareas listas nunca tiene más de unas pocas, entonces una lista doblemente enlazada de tareas listas probablemente sea la opción óptima. Si la lista de tareas listas 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 preempción. 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, esta última puede insertarse y ejecutarse inmediatamente antes de que se inserte la tarea de baja prioridad.
El tiempo de respuesta crítico, también conocido como tiempo de recuperación, es el tiempo que se tarda en poner en cola una nueva tarea lista y restablecer el estado de la tarea de mayor prioridad. En un sistema operativo en tiempo real bien diseñado, la preparación de una nueva tarea requiere de 3 a 20 instrucciones por cada entrada en la cola de tareas listas, y la restauración de la tarea lista de mayor prioridad requiere de 5 a 30 instrucciones.
En sistemas avanzados, las tareas en tiempo real comparten recursos informáticos con muchas tareas que no lo son, y la lista de tareas listas puede ser arbitrariamente larga. En tales sistemas, una lista de tareas listas del planificador implementada como una lista enlazada sería inadecuada.
Algoritmos
Algunos algoritmos de planificación RTOS de uso común son: [ 4 ]
- Programación cooperativa
- Planificación preventiva
- Programación de tasa monótona
- Programación por turnos rotativos
- Planificación preventiva de prioridad fija , una implementación de la división de tiempo preventiva.
- Planificación de prioridad fija con preempción diferida
- Planificación no preferente de prioridad fija
- Planificación preventiva de secciones críticas
- Planificación de tiempo estático
- Enfoque prioritario según la fecha límite más temprana
- Digrafos estocásticos con recorrido de grafos multihilo
Comunicación entre tareas y compartición de recursos
Un sistema operativo multitarea como Unix es deficiente en tareas en tiempo real. El planificador da la máxima prioridad a los trabajos con menor demanda de recursos del ordenador, por lo que no hay forma de garantizar que un trabajo crítico en tiempo real tenga acceso a suficientes recursos. Los sistemas multitarea deben gestionar el uso compartido de datos y recursos de hardware entre múltiples tareas. Generalmente, no es seguro que dos tareas accedan simultáneamente a los mismos datos o recursos de hardware específicos. [ 5 ] Existen tres enfoques comunes para resolver este problema:
Enmascaramiento/deshabilitación temporal de interrupciones
Los sistemas operativos de propósito general generalmente no permiten que los programas de usuario enmascaren (desactiven) las interrupciones , ya que podrían controlar la CPU mientras se les concediera. Algunas CPU modernas no permiten que el código en modo usuario desactive las interrupciones, puesto que dicho control se considera un recurso clave del sistema operativo. Sin embargo, muchos sistemas embebidos y sistemas operativos en tiempo real (RTOS) permiten que la propia aplicación se ejecute en modo kernel para una mayor eficiencia en las llamadas al sistema y para que la aplicación tenga un mayor control del entorno operativo sin necesidad de 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 con menor sobrecarga para evitar el acceso simultáneo a un recurso compartido. Mientras las interrupciones están enmascaradas y la tarea actual no realiza una llamada bloqueante al sistema operativo, esta tarea tiene el 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 las interrupciones solo debe realizarse cuando la ruta más larga a través de la sección crítica sea menor que la latencia máxima de interrupción deseada . Normalmente, este método de protección se utiliza solo cuando la sección crítica consta de unas pocas instrucciones y no contiene bucles. Este método es ideal para proteger los registros de hardware mapeados por bits cuando los bits son controlados por diferentes tareas.
Mutex
Cuando es necesario reservar un recurso compartido sin bloquear otras tareas (como esperar a que se escriba en la memoria Flash), es mejor utilizar mecanismos también disponibles en sistemas operativos de propósito general, como un mutex y la mensajería entre procesos supervisada por el sistema operativo. Estos mecanismos implican llamadas al sistema y, por lo general, invocan el código del despachador del sistema operativo al finalizar, por lo que suelen requerir cientos de instrucciones de CPU para ejecutarse, mientras que en algunos procesadores, el enmascaramiento de interrupciones puede requerir tan solo una instrucción.
Un mutex (no recursivo) puede estar bloqueado o desbloqueado. Cuando una tarea bloquea el mutex, todas las demás tareas deben esperar a que su propietario (el hilo original) lo desbloquee. Una tarea puede establecer un tiempo de espera para el mutex. Existen varios problemas conocidos en 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 esta última no se le asigna tiempo de CPU para finalizar su trabajo. Una solución típica consiste en que la tarea propietaria del mutex «herede» la prioridad de la tarea con mayor prioridad en espera. Sin embargo, este enfoque sencillo se complica cuando existen múltiples niveles de espera: la tarea A espera un mutex bloqueado por la tarea B , que a su vez espera un mutex bloqueado por la tarea C. Gestionar múltiples niveles de herencia provoca que otro código se ejecute en un contexto de alta prioridad, lo que puede causar inanición en los hilos de prioridad media.
En un interbloqueo , dos o más tareas bloquean un mutex sin tiempos de espera y luego esperan indefinidamente a que la otra tarea lo haga, creando una dependencia cíclica. El escenario de interbloqueo más simple ocurre cuando dos tareas bloquean alternativamente dos mutex, pero en orden inverso. Un diseño cuidadoso previene el interbloqueo.
paso de mensajes
Otro enfoque para compartir recursos consiste en que las tareas envíen mensajes mediante un esquema organizado de paso de mensajes . En este paradigma, el recurso es gestionado directamente por una sola tarea. Cuando otra tarea desea consultar o manipular el recurso, envía un mensaje a la tarea gestora. Si bien su comportamiento en tiempo real es menos preciso que el de los sistemas de semáforos , los sistemas simples basados en mensajes evitan la mayoría de los riesgos de interbloqueo de protocolo y, en general, se comportan mejor que los sistemas de semáforos. Sin embargo, pueden surgir problemas similares a los de los semáforos. La inversión de prioridad puede ocurrir cuando una tarea está procesando un mensaje de baja prioridad e ignora un mensaje de mayor prioridad (o un mensaje que se origina indirectamente de una tarea de alta prioridad) en su cola de mensajes entrantes. Los interbloqueos de protocolo pueden ocurrir cuando dos o más tareas esperan a que la otra envíe mensajes de respuesta.
Manejadores de interrupciones y el planificador
Dado que un controlador de interrupciones impide la ejecución de la tarea de mayor prioridad, y que los sistemas operativos en tiempo real están diseñados para minimizar la latencia de los subprocesos, los controladores de interrupciones suelen ser lo más breves posible. El controlador de interrupciones pospone toda interacción con el hardware, si es posible; normalmente, basta con reconocer o deshabilitar la interrupción (para que no se produzca de nuevo cuando el controlador de interrupciones finalice) y notificar a una tarea que debe realizar una tarea. Esto se puede lograr desbloqueando una tarea del controlador mediante la liberación de un semáforo, el establecimiento de una bandera o el envío de un mensaje. Un planificador suele proporcionar la capacidad de desbloquear una tarea desde el contexto del controlador de interrupciones.
Un sistema operativo (SO) mantiene catálogos de los objetos que administra, como subprocesos, mutexes, memoria, etc. Las actualizaciones de este catálogo deben controlarse estrictamente. Por esta razón, puede resultar problemático que un controlador de interrupciones llame a una función del SO mientras la aplicación también lo hace. La función del SO llamada desde un controlador de interrupciones podría encontrar la base de datos de objetos en un estado inconsistente debido a la actualización de la aplicación. Existen dos enfoques principales para abordar este problema: la arquitectura unificada y la arquitectura segmentada. Los sistemas operativos en tiempo real (RTOS) que implementan la arquitectura unificada resuelven el problema simplemente deshabilitando las interrupciones mientras se actualiza el catálogo interno. La desventaja es que aumenta la latencia de las interrupciones, pudiendo incluso perderse algunas. La arquitectura segmentada no realiza llamadas directas al SO, sino que delega el trabajo relacionado con el SO a un controlador independiente. Este controlador se ejecuta con mayor prioridad que cualquier subproceso, pero menor que los controladores de interrupciones. La ventaja de esta arquitectura es que añade muy pocos ciclos a la latencia de las interrupciones. 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.
Del mismo modo, el modo de administración del sistema en hardware compatible con x86 puede tardar mucho tiempo antes de que devuelva el control al sistema operativo.
Asignación de memoria
La asignación de memoria es más crítica en un sistema operativo en tiempo real que en otros sistemas operativos.
En primer lugar, para garantizar la estabilidad, no debe haber fugas de memoria (memoria asignada que no se libera tras su uso). El dispositivo debe funcionar indefinidamente, sin necesidad de reiniciarse. Por este motivo, no se recomienda la asignación dinámica de memoria . Siempre que sea posible, toda la memoria necesaria se especifica de forma estática durante la compilación.
Otra razón para evitar la asignación dinámica de memoria es la fragmentación de la memoria. Con la asignación y liberación frecuentes de pequeños bloques de memoria, puede darse una situación en la que la memoria disponible se divida en varias secciones y el RTOS no pueda asignar un bloque de memoria continuo lo suficientemente grande, aunque haya suficiente memoria libre. En segundo lugar, la velocidad de asignación es importante. Un esquema estándar de asignación de memoria examina una lista enlazada de longitud indeterminada para encontrar un bloque de memoria libre adecuado, [ 6 ] lo cual es inaceptable en un RTOS ya que la asignación de memoria debe ocurrir dentro de un cierto lapso 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 que se comentaron anteriormente.
El sencillo algoritmo de bloques de tamaño fijo funciona bastante bien para sistemas embebidos simples debido a su baja sobrecarga.
abstracción de hardware
Si bien todos los sistemas operativos en tiempo real (RTOS) pueden ejecutarse en microcontroladores para los que tienen puertos, algunos, como Zephyr, son capaces de abstraer aún más varios controladores de dispositivos, como memorias flash externas, dispositivos I2C o periféricos internos como comandos UART y modos de suspensión. Esto elimina la necesidad de que el desarrollador cree controladores, ya que se proporciona un conjunto de API para portar los controladores no compatibles, mientras que los compatibles están listos para usarse en el código de la aplicación.
Véase también
- Planificador de particiones adaptativo
- Comparación de sistemas operativos en tiempo real (lista RTOS)
- DO-178B
- La fecha límite más temprana se programará primero.
- Sistema operativo integrado
- Firmware
- Sistema operativo interrumpible
- INtime
- Programación con el menor tiempo de holgura posible
- Programación de tasa monótona
- Lenguaje de programación síncrono
- Sistema activado por tiempo
- Función de utilidad del tiempo
- Lista de sistemas operativos
Referencias
- ↑ "Tiempo de respuesta y fluctuación" . Archivado del original el 23/07/2011 . Consultado el 04/12/2010 .
- ↑ Tanenbaum, Andrew (2008). Sistemas operativos modernos . Upper Saddle River, NJ: Pearson/Prentice Hall. pág. 160. ISBN 978-0-13-600663-3.
- ↑ "Conceptos de RTOS" . Archivado del original el 23/07/2011 . Consultado el 04/12/2010 .
- ↑ Samek, Miro (23 de mayo de 2023). "Programación de sistemas embebidos: RTOS – ¿qué es el tiempo real?" . Embedded.com . Consultado el 13 de septiembre de 2023 .
- ↑ Phraner, Ralph A. (Otoño de 1984). "El futuro de Unix en el IBM PC" . Byte . págs. 59–64 .
- ↑ "CS 241, Universidad de Illinois" (PDF) .
- Sistemas operativos en tiempo real
- Sistemas operativos
- Computación en tiempo real