2 puntos por GN⁺ 2023-11-17 | 1 comentarios | Compartir por WhatsApp
  • 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

 
GN⁺ 2023-11-17
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.

    • Un ejemplo moderno es seL4. Según entiendo, no hace asignación dinámica de memoria y además fue verificado formalmente respecto de varias propiedades.
      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.
    • ¿QNX no se usa en sistemas de infoentretenimiento de autos? Me da curiosidad en qué otros lugares se usa.
      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.
    • El kernel de SDP 8 actual tiene 15,331 líneas, incluyendo comentarios y Makefiles.
    • Se ve como una función main bien estructurada en C o en un lenguaje de la familia de C. main solo 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.
    • Aproximadamente 90% de esos “millones de líneas de kernel” son drivers de dispositivos. Si quieres correr en hardware arbitrario, al final también los necesitas en un microkernel.
  • 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.

    • Las tareas que requieren tiempo real duro no se satisfacen con “si se pierde algo, hay muchos ciclos de sobra”. Tampoco es solo un problema de ciclos de CPU.
      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.
    • Las aplicaciones con requisitos reales de tiempo real suelen tener exigencias tan fuertes que no pueden tolerar ni una posibilidad de falla muy pequeña. Piensa en aviónica, dispositivos médicos, automóviles y aplicaciones militares.
      Si realmente necesitas tiempo real, realmente lo necesitas; no existe el “suficientemente cerca”. Aunque esa es mi impresión como alguien de afuera.
    • Cuando se crean aplicaciones de tiempo real con chips ARM de bajo consumo y alta frecuencia, directamente no se usa un sistema operativo. Para esos usos, x86 ni siquiera se considera.
      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.
    • Es cierto, pero esto no va a eliminar mágicamente la necesidad de cores dedicados. Probablemente termine siendo algo como indicarle al scheduler que ponga las tareas de tiempo real no desalojables solo en un core LITTLE.
  • 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.

    • Tengo entendido que las funciones de las CPU modernas no impiden los usos de tiempo real. Si algo tiene un límite superior y se puede razonar sobre él, puede usarse para construir un sistema de tiempo real.
      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.
    • En placas de microcontrolador “grandes” como Raspberry Pi, me parece bastante útil. Ahí existe cierta cultura de tiempo real y, aunque no hagas bit banging directamente con la CPU, desde afuera todo ocurre a tiempo.
      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.
    • SpaceX usa procesadores x86 en sus cohetes. El pequeño helicóptero dron que la NASA envió a Marte también usa un núcleo ARM “bastante grande”, al punto de poder correr un Android antiguo.
    • No es necesariamente cierto que el hardware grande y complejo no sirva para tiempo real de verdad. Hay núcleos avanzados de tiempo real como Arm Cortex-R82.
      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.
    • El 68000 sí que es el verdadero rey del tiempo real.
  • 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”.

    • Incluso quitaría por completo la parte de “mínimo”. Lo central del tiempo real es que las tareas tienen un límite superior predecible. Eso significa que, en promedio, puede ser más lento que un sistema que no es de tiempo real.
      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.
    • Como dice el viejo dicho, “real time” no es “real fast”. La distinción entre tiempo real duro y blando lo vuelve un poco difuso, pero creo que muchos desarrolladores de software tampoco entienden bien qué es realmente el tiempo real.
  • 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.

    • Suelo tener este tipo de conversación con colegas sobre el logging: “Tenemos una API de mejor esfuerzo y una API de entrega garantizada”. “¡Queremos entrega garantizada!” “Si la interfaz de logging con entrega garantizada está offline o lenta, el servicio se va a caer; ¿está bien?” “No, ¡no puede caerse!”
      “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.
    • Una vez se nos detuvo todo el entorno de producción cuando se cayó el servidor syslog. Estábamos enviando los logs por TCP, y ese bloqueo se propagó por todo el entorno de producción.
      Después cambiamos el transporte a UDP, porque es mejor perder algunos logs que perder toda la operación.
    • También hubo problemas del tipo “la biblioteca de logging lo rompe todo” en la biblioteca de logging de $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.
    • Si la disponibilidad de un producto depende de ±100 ms, está profundamente mal diseñado, y no creo que eso sea culpa de la biblioteca de logging. Al usuario no le va a importar si, después de presionar un botón, la operación tarda 100 ms más en completarse.
  • 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.

    • Es interesante que menciones Raspberry Pi. Hace uno o dos días vi un texto que decía que RpiOS arranca y se ejecuta sobre un 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.

    • Según entiendo, el tiempo real vuelve más lento al sistema. Para que sea de tiempo real, hay que asignar tiempos a todo.
      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.
    • RT no necesariamente mejora la latencia; les da a algunas tareas un límite superior fijo. Pero el trabajo necesario para hacer posible RT sí puede mejorar claramente la latencia en los casos normales.
      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.
    • Si por usuarios “comunes” se refieren a usuarios de escritorio, no habrá grandes cambios. Pero para dispositivos embebidos como control industrial y equipos de telecomunicaciones, es algo importante.
      Porque permite usar un kernel mainline moderno incluso cuando se necesita planificación en tiempo real.
    • Según lo entiendo, Linux se convierte en una opción para situaciones donde se necesita un RTOS. Es para sistemas críticos como aviación o dispositivos médicos, y no afecta mucho a los usuarios comunes.
    • Los usuarios finales de escritorio más comunes que pueden beneficiarse son quienes hacen trabajo de audio. Ahí la latencia, en particular el jitter, puede ser bastante problemática.
  • 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 desde printk para 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() o malloc() 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/