- El soporte de preemptibilidad en tiempo real en Linux es un trabajo que lleva casi 20 años esperando entrar al mainline, y Thomas Gleixner indicó en la Linux Plumbers Conference 2023 que el último gran obstáculo es
printk() - El objetivo es que el proceso de mayor prioridad pueda ejecutarse con una latencia corta y predecible, y para ello muchas partes centrales del kernel han sido reescritas durante mucho tiempo
printk()puede llamarse desde cualquier contexto, por lo que es mucho más complicado que una simple salida de logs, y la salida síncrona actual entra en conflicto con los objetivos de latencia en tiempo real- Desde 2018, cerca de 300 parches han entrado upstream o están pendientes en linux-next, y las tareas restantes son el handover de mensajes urgentes y el manejo seguro de los controladores de consola
- Cuando termine la reorganización de
printk()y el resto del código de tiempo real esté listo en linux-next, incluso podría fusionarse en la misma merge window, aunque Gleixner dijo que ya no hará más predicciones sobre la fecha de finalización
Casi 20 años de trabajo en preemptibilidad en tiempo real
- El soporte de tiempo real en Linux apareció por primera vez en LWN en 2004 y durante mucho tiempo pareció estar “a punto de terminar”
- LWN incluso publicó en 2009 un artículo titulado the realtime preemption endgame, pero en la Linux Plumbers Conference 2023 Gleixner considera que ahora sí el final está realmente cerca
- Para Gleixner, en lo personal, ha sido un trabajo de casi 25 años
- Empezó a trabajar en el soporte de tiempo real para Linux en 1999
- El proyecto en sí también ha continuado por casi 20 años
- Dijo que cuando el trabajo termine habrá “a big party”, pero
printk()sigue siendo el último gran obstáculo
La latencia que la preemptibilidad en tiempo real busca reducir
- El objetivo de la preemptibilidad en tiempo real es que el proceso de mayor prioridad siempre pueda ejecutarse con una latencia mínima y predecible
- Para ello, el kernel debe poder ser interrumpido en la mayor cantidad posible de situaciones, y las excepciones deben limitarse a un alcance estrecho y bien definido
- El funcionamiento básico quedó establecido hace mucho tiempo, pero resolver los problemas de detalle ha tomado mucho tiempo
- En este proceso, muchas partes del kernel central fueron reescritas, y esos beneficios se extienden al kernel en general más allá de los casos de uso en tiempo real
Por qué printk() es el último obstáculo
- Cuando el código del kernel necesita enviar mensajes a la consola y al log, llama a
printk()o a funciones construidas sobre él - Aunque parece una simple salida,
printk()debe funcionar en casi cualquier contexto- Puede llamarse incluso desde un manejador de interrupción no enmascarable
- Puede volver a invocarse desde dentro de otra llamada a
printk() - En una caída del sistema, la información que se imprime puede ser crucial, así que es difícil restringir el contexto de llamada
- Debido a estos requisitos, en
printk()se entrelazan de forma compleja problemas de concurrencia, bloqueos y manejo de drivers - El
printk()actual del kernel tiene una estructura completamente síncrona- La llamada no regresa hasta que el mensaje se haya enviado a todos los destinos configurados
- Gleixner describió esta estructura como “stupid”
- Especialmente durante el arranque, gran parte de la salida puede ser simple ruido, pero aun así hay que esperar a que todo se envíe
- Ese tiempo de espera choca de frente con la latencia que el trabajo en tiempo real busca reducir
- Los desarrolladores de tiempo real movieron hace tiempo la salida de
printk()a un hilo separado para volverla asíncrona, pero ese código se parecía más a varios hacks que a una solución de fondo
Revisión de printk() desde 2018
- El problema de
printk()empezó a abordarse en serio desde 2018, y cerca de 300 parches han entrado upstream o están pendientes en linux-next - Actualmente hay en marcha tres últimos conjuntos de parches para cerrar el trabajo
- Uno de los detalles más difíciles es el mecanismo de handover
- Cuando el kernel necesita imprimir un mensaje urgente, como en una caída, puede necesitar tomar el control de una consola que está imprimiendo mensajes de menor prioridad
- No es fácil hacer esto de forma segura en cualquier contexto
- Otro desafío es marcar los controladores de consola que no pueden usarse con seguridad en ciertos contextos
- Por ejemplo, si se quiere imprimir un mensaje durante una interrupción no enmascarable pero se requiere cambiar el modo de video, eso no puede funcionar
- Gleixner respondió que en el último año no ha habido cambios conceptuales de fondo
- Hay 76 controladores de consola en el kernel que necesitan corrección
- El código de handover cambió para permitir actualizar los drivers uno por uno, en lugar de corregirlos todos al mismo tiempo
- Hay más discusión reciente sobre el trabajo en
printk()en este artículo
Salida asíncrona y condiciones para fusionarlo en mainline
- Cuando Masami Hiramatsu preguntó qué mensajes del kernel debían imprimirse de forma síncrona, Gleixner respondió que casi todo debería volverse asíncrono
- La salida asíncrona reduce la latencia causada por las llamadas a
printk()y permite tener un hilo del kernel separado para cada consola- Así, una consola rápida puede operar a su propio ritmo sin esperar a la más lenta
- El código fue cambiado para que los mensajes importantes se copien completamente al búfer de mensajes antes de que se imprima la primera línea
- Esto es una medida para el caso de que un controlador de consola defectuoso termine rompiendo todo el sistema
- Para un orden de salida más seguro, primero se escribe en las consolas que se sabe que son seguras
- Por ejemplo, si hay un almacén en memoria persistente, primero se guarda el mensaje ahí antes de enviarlo al dispositivo físico
- Es una forma de preservar la salida incluso si un driver defectuoso termina matando el sistema
- Gleixner dijo que, aunque el trabajo está cerca,
printk()es difícil de predecir, así que ya no dirá cuándo estará terminado - Aun así, expresó su esperanza de que el resto del código de preemptibilidad en tiempo real entre al mainline antes de su 20.º aniversario a fines de 2024
- Cuando Clark Williams preguntó si el resto del código de tiempo real entraría en la misma merge window después de que los parches de
printk()lleguen upstream, Gleixner respondió “yes” de forma condicional- Si todo el código está staged en linux-next y parece listo, podrían intentarlo
1 comentarios
Opiniones en Hacker News
QNX viene haciendo bien esto desde hace décadas. El microkernel tiene un límite superior para todo lo que hace, y el código tiene apenas decenas de miles de líneas.
El microkernel solo se encarga de la asignación de memoria, el despacho de CPU y el paso de mensajes entre procesos. Todo lo demás, incluidos los drivers y los loggers, está en espacio de usuario y puede ser desalojado por threads de mayor prioridad.
El kernel de QNX no maneja strings. No hay parsing, formatting ni mensajes. Linux se volvió demasiado grande para tiempo real, y su arquitectura en sí no encaja con tiempo real porque habría que hacer que millones de líneas de código del kernel sean desalojables. Por eso tomó 20 años arreglarlo.
Quizás su mayor aporte al diseño de kernels sea el uso generalizado de capabilities, como una forma segura y flexible de delegar el control al espacio de usuario.
La hinchazón del kernel en sí no me preocupa demasiado. En Linux se invierte mucho tiempo de desarrollo, y aunque el desktop no tenga tanta prioridad como los servidores, el trabajo para crear un kernel de buen rendimiento en lugares como dispositivos portátiles también beneficiará a los usuarios de escritorio.
mainbien estructurada en C o en un lenguaje de la familia de C.mainsolo coordina llamadas a otras funciones, y aunque aquí el kernel de QNX hace menos inicialización, el concepto general es parecido.No soy desarrollador de kernels, pero esta forma de mantenerlo simple parece buena.
Hay un ejemplo de cómo el kernel intenta sacar mensajes de log de una u otra forma incluso en un sistema que se está muriendo, y de cómo eso se usa en entornos de producción reales.
https://netflixtechblog.com/kubernetes-and-kernel-panics-ed6...
Me pregunto si, cuando se arregle este problema, podrá reemplazar bastante algunas combinaciones de hardware/software diseñadas para tiempo real. Hoy hay muchas opciones de chips ARM y x86 baratos, de bajo consumo y con frecuencias altas.
Como las frecuencias son tan altas, incluso si se pierde alguna ocasión puede haber tantos ciclos de sobra que la temporalidad perfectamente realista a menudo importe menos. Sé que no es elegante ni eficiente, pero a veces los componentes de propósito general terminan ganándole a la corrección estricta.
Una sola tarea mal hecha puede retener el kernel e impedir que haga trabajo útil. El punto central del tiempo real duro es que “nada puede impedir que se ejecute esta tarea importante”. En automóviles o en el sector aeroespacial, los sistemas de control tienen que funcionar bajo cualquier circunstancia.
Si realmente necesitas tiempo real, realmente lo necesitas; no existe el “suficientemente cerca”. Aunque esa es mi impresión como alguien de afuera.
El sistema operativo, aunque sea un RTOS, estorba demasiado. No sé qué cambiará esto. En todo caso, depende de la aplicación, y hay muchos casos que necesitan algo “casi en tiempo real”, así que para esos usos puede ser útil.
La discusión aquí se centra en la distinción entre aplicaciones de tiempo real “duro” y “blando”. En tiempo real duro, es muy probable que de entrada no quieras usar un sistema operativo de propósito general como Linux; y en tiempo real blando, como videoconferencias o reproducción de audio, no es grave si ocasionalmente hay un corte o se pierden algunos frames.
El argumento es que RT Linux será una solución potente para esos usos de tiempo real blando. Pero los usos blandos propuestos ya son posibles hoy con Linux embebido. No es que la reproducción de video o audio de baja latencia por software haya sido imposible; también era posible hace 20 años.
El problema aparece cuando, en un sistema ocupado, I/O no desalojable interrumpe con frecuencia, pero en entornos embebidos eso es raro. Hay motivos convincentes para hacer que el kernel sea completamente desalojable y dar más control sobre la planificación, pero eso tiene poco que ver con que Linux deba reemplazar sistemas operativos mínimos de tiempo real o código bare-metal.
Es más bien una cuestión de buena higiene, y termina produciendo un sistema operativo que también se comporta mejor bajo carga en aplicaciones que no son de tiempo real.
Es una buena noticia, pero aunque el kernel de Linux se vuelva de tiempo real, es muy probable que el hardware no lo sea por la caché y la compleja magia interna de la CPU.
El hardware grande y complejo no encaja con el tiempo real de verdad. Por eso AbsInt y las herramientas de peor tiempo de ejecución (WCET) suelen tratar sobre arquitecturas de CPU simples. El 8051 de verdad va a sobrevivir para siempre. Como referencia, también está Zephyr RTOS.
Basta con asumir situaciones como cero aciertos de caché y carga máxima. Si se puede poner un límite superior al tiempo que toma, está bien.
Un temporizador puede recibir la entrada de un codificador en cuadratura y solo enviar una interrupción cuando hace wrap, o se puede conectar el sistema GPIO a DMA para hacer streaming de memoria a los pines de salida sin intervención de la CPU. También se puede hacer streaming a un DAC o transferir por DMA desde un ADC a memoria. Estas cosas a menudo evitan la caché para lograr latencias predecibles.
De hecho, muchos sistemas de tiempo real tienen que procesar y agregar una cantidad cada vez mayor de datos de sensores, así que se están volviendo cada vez más potentes.
Cuando era principiante, pasé por una cantidad frustrante de entrevistas en las que los entrevistadores no sabían qué significaba realmente tiempo real. Mucha gente se perdía el concepto de “y latencias predecibles” mencionado en el artículo, y parecía pensar que tiempo real simplemente significa “rápido”.
Si estás controlando el sistema de frenado de un auto, “la latencia promedio es de 50 ms, pero el máximo es de 80 ms” puede ser aceptable, mientras que “la latencia promedio es de 1 ms, pero puede alargarse arbitrariamente y llegar a tardar varios segundos” no lo es.
El logging síncrono volvió a causar problemas. En la empresa tuvimos algo parecido con GLOG (la biblioteca de logging de Google): por ejemplo, si stdout es un archivo, puede bloquearse en la E/S de disco.
Cuando nuestro servicio se detenía por más de 100 ms, en el 90–99% de los casos la causa era GLOG.
“Si algo tiene que quedar registrado sí o sí, pero no se puede registrar, ¿qué van a hacer?” Últimamente simplemente señalo el teorema CAP y digo que el logging es como cualquier otro sistema distribuido. Tal vez porque hay un artículo de Wikipedia con un dibujo de triángulo y la palabra “teorema”, la gente suele aceptarlo.
Después cambiamos el transporte a UDP, porque es mejor perder algunos logs que perder toda la operación.
$MSFT. Imaginen 100 hilos, cada uno con un buffer de logging de 300 MB.Obviamente destrozó la memoria, y el servidor crasheaba incluso en el SKU más caro de Azure App Service.
Esto me trajo muchos recuerdos. Hace unos 17 o 18 años compilé un kernel de Debian con RT_PREEMPT para usarlo en equipo científico que necesitaba temporización más ajustada.
La latencia y el jitter eran muy impresionantes. Desde entonces casi no he vuelto a pensar en ello, pero creo que tendría muchos usos al crear aplicaciones embebidas con Raspberry Pi cuando no quieres pasarte a un microcontrolador con RTOS.
Me pareció especialmente interesante porque antes también había visto propuestas de ejecutar Linux como una tarea dentro de un RTOS. La idea era ejecutar en el RTOS lo que necesitara plazos estrictos de tiempo real, sin que se viera afectado por las latencias que puede provocar el sistema de memoria virtual. No recuerdo si eso era solo una idea o si realmente se implementó, y también solo vi una vez la mención de que RpiOS está sobre un RTOS, así que me da curiosidad.
Me pregunto qué significa esto para los usuarios comunes. Si es una función que se activa solo en situaciones muy específicas, o si también puede traerle al público general un sistema con mejor capacidad de respuesta.
Cada tarea recibe un presupuesto X y no debe excederlo. Si el mejor caso es rápido pero el peor caso es lento, significa que el sistema siempre tiene que asumir el peor caso.
El ejemplo de evitar llamadas síncronas a
printk()encaja perfecto, y debería mejorar la latencia bajo carga incluso sin activar RT. Creo que un kernel RT completamente upstream no se comportará distinto de un kernel normal a menos que realmente se ejecuten procesos RT. La razón por la que tardó tanto en llegar upstream fue que hacían falta concesiones para habilitar RT y, según el artículo, ya no quedan tantas de esas concesiones.Porque permite usar un kernel mainline moderno incluso cuando se necesita planificación en tiempo real.
Me pregunto qué opinan de Xenomai[1]. Lo he usado durante años sin problemas.
En una BeagleBone Black suele dar jitter del orden de cientos de nanosegundos, y lo considero tiempo real “duro”. Puede planificar tareas periódicas en el rango de decenas de microsegundos y nunca falla una.
A diferencia de Real-Time Linux, que intenta hacer que Linux mismo sea apropiable, Xenomai es esencialmente su propio kernel y ejecuta Linux como una tarea encima. Ofrece una ABI para que las tareas creadas por el usuario se ejecuten junto a Linux o con mayor prioridad. Por ejemplo, puede eludir el problema de
printk(): a Xenomai no le importa y está dispuesto a hacer un cambio de contexto desdeprintkpara ejecutar la tarea del usuario.La desventaja es que dentro del contexto de Xenomai no se pueden hacer llamadas de sistema normales. Se puede, pero por supuesto rompe el modelo de tiempo real. Por ejemplo, si llamas a
printf()omalloc()dentro de una tarea de Xenomai, no será apropiable. La ABI de Xenomai replica en la medida de lo posible lo que podrías necesitar en términos de llamadas de sistema, y si estás conforme con manejar directamente la asignación en heap, funciona muy bien.[1]: https://xenomai.org/