Contenido
RTOS vs bare metal: cuándo conviene un planificador
RTOS vs bare metal en firmware: cómo manejan los tiempos un superloop y un planificador, qué garantiza la teoría y la trampa de la inversión de prioridad.
El firmware lee un sensor cada milisegundo, actualiza una pantalla, guarda registros en la flash y habla con un radio, todo en un solo ciclo. Funcionaba bien hasta que la escritura en flash creció a cuatro milisegundos; ahora el sensor se lee tarde cada vez que se guarda un registro. La decisión de RTOS vs bare metal casi siempre aparece justo en ese momento: cuando un trabajo largo empieza a retrasar a uno corto y urgente. En este artículo vemos cómo maneja los tiempos cada enfoque, qué puede y qué no puede garantizar la investigación sobre planificación en tiempo real, y las fallas que llegan junto con un planificador, incluida la inversión de prioridad que reinició una nave en Marte.
Puntos clave
- En un superloop bare metal, la latencia en el peor caso de cualquier trabajo es, más o menos, lo que tarda una vuelta completa del ciclo. Un solo trabajo largo retrasa a todos los demás.
- Un RTOS con desalojo (preemptive) deja que una tarea de alta prioridad interrumpa a una de baja prioridad, así que la latencia de la tarea urgente ya no depende de cuánto duren los trabajos ajenos.
- Liu y Layland (1973) demostraron que con prioridades rate-monotonic se cumplen todos los plazos si la utilización total del CPU se mantiene por debajo de n(2^(1/n) − 1): cerca de 78% con tres tareas y tendiendo a 69% con muchas. El análisis exacto de tiempo de respuesta suele admitir más.
- Lehoczky, Sha y Ding (1989) encontraron que conjuntos de tareas generados al azar normalmente eran planificables hasta alrededor de 88% de utilización, así que esa cota es pesimista, no un techo.
- Los recursos compartidos traen la inversión de prioridad. La Mars Pathfinder se reinició varias veces en 1997 hasta que se activó la herencia de prioridad, que su kernel ya tenía.
El problema: un ciclo es tan rápido como su trabajo más lento
Un programa bare metal suele tener forma de superloop: inicializas el hardware y luego repites para siempre: revisar entradas, correr cada trabajo y, si se puede, dormir. Las interrupciones atrapan los eventos que no pueden esperar y levantan banderas para que el ciclo los procese. No corre nada más. Es la estructura más sencilla que existe y, para muchos productos, es la correcta.
El detalle es que cada trabajo espera a todos los demás. Si el ciclo llama en orden a una rutina de sensor de 0.2 ms, a una actualización de pantalla de 1 ms y a una escritura en flash de 4 ms, la rutina del sensor puede correr como máximo una vez cada 5.2 ms, por urgente que sea. Entonces empiezas a partir los trabajos largos en máquinas de estados que hacen un pedazo por vuelta, a mover lógica a las interrupciones o a meter casos especiales (“si toca leer el sensor, sáltate la pantalla”). Cada parche funciona. Juntos hacen que los tiempos sean difíciles de razonar, que era justo lo que el superloop debía evitar.
Un RTOS (sistema operativo de tiempo real) cambia la estructura. Cada trabajo se vuelve una tarea con prioridad. El kernel corre la tarea lista de mayor prioridad y, cuando una tarea más prioritaria queda lista (porque venció un timer o una interrupción la liberó), desaloja a la que estaba corriendo, guarda su contexto y cambia. La escritura en flash sigue tardando 4 ms, pero la tarea del sensor puede interrumpirla.
Lo que dice la investigación sobre planificación
La cota rate-monotonic
La base de la planificación por prioridades fijas es un artículo de 1973 de C. L. Liu y James Layland en el Journal of the ACM. Consideraron tareas periódicas, cada una con un periodo, un tiempo de ejecución en el peor caso y un plazo igual a su periodo, corriendo en un solo procesador con desalojo. Demostraron que asignar prioridades por frecuencia (periodo más corto, prioridad más alta) es óptimo entre los esquemas de prioridad fija, y que ese conjunto de tareas siempre cumple sus plazos si la utilización total U (la suma de tiempo de ejecución entre periodo de todas las tareas) cumple:
U ≤ n(2^(1/n) − 1)
Con una tarea la cota es 100%, con dos cerca de 82.8%, con tres cerca de 78.0% y, conforme crece n, tiende a ln 2, alrededor de 69.3%. La prueba es suficiente, no necesaria: un conjunto por encima de la cota todavía puede ser planificable, pero la cota no lo demuestra.
Qué tan pesimista es la cota
Lehoczky, Sha y Ding presentaron una caracterización exacta de la planificabilidad rate-monotonic en el Real-Time Systems Symposium de 1989 y estudiaron el comportamiento promedio. Para conjuntos grandes de tareas generadas al azar, la utilización a la que empezaban a perderse plazos andaba normalmente cerca de 88%, muy por arriba del 69% del peor caso. Un firmware real no es un conjunto aleatorio de tareas, así que esto no es una meta de diseño, pero explica por qué muchos sistemas trabajan sin problema por encima de la cota de Liu y Layland.
Análisis de tiempo de respuesta
La herramienta práctica para un diseño concreto es el análisis de tiempo de respuesta, descrito por Audsley, Burns, Richardson, Tindell y Wellings en un artículo de 1993 en el Software Engineering Journal. El tiempo de respuesta en el peor caso R de una tarea es su propio tiempo de ejecución C más la interferencia de cada tarea j de mayor prioridad que se libere mientras corre:
R = C + Σ ⌈R / Tj⌉ · Cj
La ecuación se resuelve iterando: empiezas con R = C más una ejecución de cada tarea más prioritaria y repites hasta que R deja de cambiar. Si R no pasa del plazo de la tarea, es planificable. El mismo marco se extiende al bloqueo por recursos compartidos, al jitter de liberación y a plazos más cortos que el periodo.
Un ejemplo con números
Toma los tres trabajos del inicio como tareas, con números ilustrativos:
- Sensor: periodo 1 ms, ejecución 0.2 ms (U = 0.20)
- Pantalla: periodo 5 ms, ejecución 1 ms (U = 0.20)
- Registro: periodo 20 ms, ejecución 4 ms (U = 0.20)
La utilización total es 60%, por debajo de la cota de 78% para tres tareas, así que con prioridades rate-monotonic está garantizado que funciona. El análisis de tiempo de respuesta da los peores casos reales:
- Sensor: R = 0.2 ms (nada lo desaloja).
- Pantalla: empiezas en 1.2 ms; el sensor se libera dos veces en esa ventana, así que R = 1 + 2 × 0.2 = 1.4 ms, que ya es estable. Plazo de 5 ms: cumple.
- Registro: empiezas en 5.2 ms. Iterando: 4 + 6 × 0.2 + 2 × 1 = 7.2 ms, luego 4 + 8 × 0.2 + 2 × 1 = 7.6 ms y otra vez 7.6 ms. Plazo de 20 ms: cumple.
En el superloop, la latencia del sensor en el peor caso era de 5.2 ms contra un requisito de 1 ms. Con el RTOS es de 0.2 ms más el overhead del kernel. Ese overhead (cambios de contexto, manejo del tick) hay que medirlo en el hardware real y sumarlo a cada tiempo de ejecución; en microcontroladores pequeños suele andar en microsegundos por cambio, pero es un dato que se mide, no que se supone.
La inversión de prioridad y la Mars Pathfinder
El desalojo trae un problema nuevo cuando las tareas comparten recursos. Si una tarea de baja prioridad tiene un mutex que necesita una de alta prioridad, la de alta tiene que esperar. Esa espera es normal y está acotada por la duración de la sección crítica. Pero si una tarea de prioridad media desaloja a la de baja mientras tiene el mutex, la de alta prioridad también espera a la media, todo lo que dure. En la práctica, la tarea de alta prioridad está corriendo con la prioridad más baja.
Sha, Rajkumar y Lehoczky analizaron esto en un artículo de 1990 en IEEE Transactions on Computers y propusieron protocolos de herencia de prioridad: mientras una tarea tiene un recurso que espera otra más prioritaria, corre con la prioridad de la que espera, así que el trabajo de prioridad media no se puede meter en medio. Su protocolo de techo de prioridad va más lejos: limita el bloqueo a una sola sección crítica como máximo y evita el interbloqueo entre esos recursos.
El caso real más conocido ocurrió en julio de 1997. Pocos días después de que la Mars Pathfinder aterrizara, la nave empezó a reiniciarse. Según contó Mike Jones a partir de una charla de David Wilner, de Wind River, una tarea de alta prioridad que administraba el bus compartía un mutex con una tarea meteorológica de baja prioridad; una tarea de comunicaciones de prioridad media podía desalojar a la meteorológica mientras tenía el mutex, la tarea del bus se atrasaba y un watchdog reiniciaba el sistema. Los mutex de VxWorks admitían herencia de prioridad mediante una bandera de inicialización, pero estaba apagada. Los ingenieros subieron un parche que la activó y los reinicios se acabaron.
La lección de diseño tiene menos que ver con Marte que con los valores por defecto: la mayoría de los kernels RTOS ofrecen herencia de prioridad en los mutex, y algunos la hacen opcional o tratan distinto a los semáforos binarios y a los mutex. Usa la primitiva que la tenga para todo lo que se comparta entre prioridades.
RTOS vs bare metal, lado a lado
| Propiedad | Superloop bare metal | RTOS con desalojo |
|---|---|---|
| Latencia en el peor caso de un trabajo urgente | Cerca de una vuelta completa (salvo que lo muevas a una interrupción) | Su propia ejecución más la interferencia y el bloqueo de mayor prioridad |
| Análisis de tiempos | Sencillo mientras todo sea corto; improvisado cuando partes trabajos | Formal: cota de utilización y análisis de tiempo de respuesta |
| Memoria | La mínima; una sola pila | Código del kernel más una pila y un bloque de control por tarea |
| Overhead de CPU | Nada más allá del ciclo | Cambios de contexto y manejo del tick o timer |
| Errores de concurrencia | Sobre todo entre el ciclo principal y las interrupciones | También entre tareas: condiciones de carrera, interbloqueo, inversión de prioridad |
| Manejo de energía | Sleep explícito en el ciclo | Tarea idle o modo tickless, según el kernel |
| Middleware (red, USB, sistemas de archivos) | Muchas veces escrito para polling o callbacks | Muchas veces asume hilos y llamadas bloqueantes |
| Mejor uso | Pocos trabajos, todos cortos; límites estrictos de tamaño o certificación | Trabajo largo y urgente mezclado; varios protocolos o stacks |
Qué hacer en la práctica
Escribe primero la tabla de tareas
Anota cada trabajo con su periodo o evento que lo dispara, su tiempo de ejecución en el peor caso y su plazo. Mide los tiempos en el hardware real con un toggle de GPIO o un contador de ciclos en lugar de estimarlos. Esa tabla te sirve vayas por donde vayas.
Quédate en bare metal si todo es corto
Si el trabajo más largo dura menos que el plazo más apretado, un superloop con interrupciones para los eventos de verdad urgentes muchas veces es el diseño correcto más simple. Muchos nodos de sensores, controladores sencillos y bootloaders caen aquí.
Pásate a un RTOS cuando choquen lo largo y lo urgente
Si te descubres partiendo trabajos largos en pedazos solo para que uno corto llegue a tiempo, o si un stack que necesitas (TCP/IP, USB, sistema de archivos) espera llamadas bloqueantes, un planificador con desalojo suele ser menos complejo que los parches.
Asigna prioridades por plazo y compruébalas
Las prioridades rate-monotonic (o deadline-monotonic, cuando los plazos son más cortos que los periodos) son un buen punto de partida. Corre el análisis de tiempo de respuesta sobre la tabla de tareas, sumando el overhead del kernel que mediste, y conserva el margen.
Usa mutex con herencia de prioridad
Protege los recursos compartidos con el tipo de mutex del kernel que tenga herencia de prioridad, mantén cortas las secciones críticas y evita compartir un recurso entre una tarea de muy alta y otra de muy baja prioridad cuando una cola de mensajes resolvería lo mismo.
Qué no hacer
No uses un RTOS para no pensar en los tiempos; solo mueve el problema a las prioridades y al bloqueo. Y no dimensiones las pilas a ojo: mide la marca de nivel máximo en las peores condiciones, porque un desbordamiento de pila en una tarea corrompe a otra.
Cómo aplicarlo
Si tienes una hora
Arma la tabla de tareas de tu firmware actual: periodo, tiempo de ejecución medido y plazo de cada trabajo. Calcula la utilización total y la latencia en el peor caso de tu trabajo más urgente en el diseño de hoy.
Si tienes un sprint
Si la latencia no cumple el plazo, prueba el trabajo urgente como tarea de alta prioridad en un RTOS, o como máquina de estados manejada por interrupciones dentro del superloop, y compara latencia medida, tamaño de código y RAM.
Si quieres que dure toda la vida del producto
Guarda la tabla de tareas y el análisis de tiempo de respuesta junto con el firmware y vuelve a correrlos cuando cambie un trabajo. Agrega las marcas de nivel máximo de pila y contadores de plazos perdidos a los diagnósticos que puedas leer en campo.
La pregunta que debes hacerte: “Si el trabajo más largo de este firmware durara el doble, ¿qué plazo se perdería primero y me enteraría?”
Preguntas frecuentes
¿Cuál es la diferencia entre un RTOS y un firmware bare metal?
El firmware bare metal corre directo sobre el hardware, normalmente como un solo ciclo más las rutinas de interrupción. Un RTOS agrega un planificador que corre varias tareas con prioridades y puede desalojar a la que está corriendo cuando otra más urgente queda lista, además de primitivas como colas, semáforos y mutex.
¿Cuándo conviene usar un RTOS?
Cuando el trabajo largo y el trabajo corto y urgente tienen que convivir y partir a mano el trabajo largo se vuelve complicado, o cuando los stacks de software de los que dependes asumen hilos y llamadas bloqueantes. Si todos los trabajos son cortos y el sistema es pequeño, bare metal suele ser más simple.
¿Qué es la cota de utilización rate-monotonic?
Es el resultado de Liu y Layland (1973): con prioridades asignadas por periodo, un conjunto de n tareas periódicas siempre cumple sus plazos si la utilización total es como máximo n(2^(1/n) − 1), cerca de 78% con tres tareas y tendiendo a 69% con muchas. Un conjunto por encima de la cota todavía puede ser planificable; el análisis de tiempo de respuesta lo comprueba con exactitud.
¿Qué es la inversión de prioridad?
Es cuando una tarea de alta prioridad espera a una de baja prioridad que tiene un recurso compartido, y una tarea de prioridad media desaloja a la de baja, así que la de alta se retrasa por trabajo que no tiene nada que ver. La herencia de prioridad, en la que quien tiene el recurso toma temporalmente la prioridad de la tarea que espera, evita la parte no acotada del retraso.
¿Un RTOS hace más lento el firmware?
Agrega overhead por los cambios de contexto y el manejo de timers, y memoria para la pila y el bloque de control de cada tarea. A cambio, el trabajo urgente ya no espera a los trabajos largos. Si vale la pena depende de los requisitos de tiempo; el overhead hay que medirlo en el hardware real.
Lecturas relacionadas: polling vs interrupciones vs DMA, cómo medir el consumo de un microcontrolador e inferencia en un Cortex-M.
Fuentes
- Liu, C. L., & Layland, J. W. (1973). “Scheduling Algorithms for Multiprogramming in a Hard-Real-Time Environment.” Journal of the ACM, 20(1), 46–61. https://doi.org/10.1145/321738.321743
- Lehoczky, J., Sha, L., & Ding, Y. (1989). “The Rate Monotonic Scheduling Algorithm: Exact Characterization and Average Case Behavior.” Proceedings of the Real-Time Systems Symposium, 166–171. https://doi.org/10.1109/REAL.1989.63567
- Sha, L., Rajkumar, R., & Lehoczky, J. P. (1990). “Priority Inheritance Protocols: An Approach to Real-Time Synchronization.” IEEE Transactions on Computers, 39(9), 1175–1185. https://doi.org/10.1109/12.57058
- Audsley, N., Burns, A., Richardson, M., Tindell, K., & Wellings, A. J. (1993). “Applying New Scheduling Theory to Static Priority Pre-emptive Scheduling.” Software Engineering Journal, 8(5), 284. https://doi.org/10.1049/sej.1993.0034
- Jones, M. (1997). “What Really Happened on Mars?” Relato de la charla de David Wilner en el IEEE Real-Time Systems Symposium. https://www.cs.cornell.edu/courses/cs614/1999sp/papers/pathfinder.html



