2 points par GN⁺ 2023-11-17 | 1 commentaires | Partager sur WhatsApp
  • La prise en charge de la préemption temps réel dans Linux attend son intégration dans le mainline depuis près de 20 ans, et Thomas Gleixner a indiqué lors de la Linux Plumbers Conference 2023 que le dernier gros obstacle était printk()
  • L’objectif est de permettre au processus de plus haute priorité de s’exécuter avec une latence courte et prévisible, ce qui a nécessité la réécriture de nombreuses parties centrales du noyau sur une longue période
  • printk() peut être appelé dans n’importe quel contexte, ce qui le rend bien plus complexe qu’un simple affichage de logs, et la sortie synchrone actuelle entre en conflit avec les objectifs de latence du temps réel
  • Depuis 2018, environ 300 patchs ont été intégrés en upstream ou sont en attente dans linux-next ; les tâches restantes concernent le handover des messages urgents et la gestion de la sûreté des pilotes de console
  • Une fois le chantier printk() terminé et le reste du code temps réel prêt dans linux-next, une fusion dans la même merge window pourrait être possible, mais Gleixner dit qu’il ne fera plus de prévision sur la date d’achèvement

Près de 20 ans de travail sur la préemption temps réel

  • Le support temps réel de Linux est apparu pour la première fois sur LWN en 2004, et a longtemps semblé être un projet « presque terminé »
  • LWN utilisait déjà en 2009 le titre the realtime preemption endgame, mais lors de la Linux Plumbers Conference 2023, Gleixner a estimé que la fin était cette fois réellement proche
  • Pour Gleixner lui-même, il s’agit d’un chantier de presque 25 ans
    • Il a commencé à travailler sur le support temps réel de Linux en 1999
    • Le projet lui-même dure depuis près de 20 ans
  • Il a déclaré qu’il y aurait « a big party » une fois le travail terminé, mais printk() reste le dernier grand obstacle

La latence que la préemption temps réel cherche à réduire

  • L’objectif de la préemption temps réel est de permettre au processus de plus haute priorité de s’exécuter avec une latence minimale et prévisible
  • Pour cela, le noyau doit pouvoir être préempté dans le plus grand nombre de situations possible, et les exceptions doivent être limitées à un périmètre étroit et clairement défini
  • Le mode de fonctionnement de base est établi depuis longtemps, mais il a fallu beaucoup de temps pour résoudre les problèmes de détail
  • Au cours de ce processus, de nombreuses parties du noyau central ont été réécrites, et les bénéfices dépassent les seuls usages temps réel pour s’étendre à l’ensemble du noyau

Pourquoi printk() est le dernier obstacle

  • Quand le code du noyau doit envoyer des messages à la console et aux logs, il appelle printk() ou des fonctions construites au-dessus
  • Cela semble être une simple opération d’affichage, mais printk() doit fonctionner dans presque tous les contextes
    • Il peut être appelé depuis un gestionnaire d’interruption non masquable
    • Il peut être rappelé depuis l’intérieur d’un autre appel à printk()
    • En cas de crash système, les informations affichées peuvent être cruciales, ce qui rend difficile la restriction des contextes d’appel
  • À cause de ces exigences, printk() concentre des problèmes complexes de concurrence, de verrouillage et de gestion des pilotes
  • Dans le noyau actuel, printk() repose sur une architecture entièrement synchrone
    • L’appel ne retourne pas tant que le message n’a pas été envoyé à toutes les destinations configurées
    • Gleixner a qualifié cette structure de « stupid »
    • En particulier pendant le boot, la majorité des sorties peuvent n’être que du bruit, mais il faut malgré tout attendre qu’elles soient toutes transmises
  • Ce temps d’attente entre en collision frontale avec l’objectif de réduction de latence du travail sur le temps réel
  • Les développeurs temps réel ont depuis longtemps déplacé la sortie de printk() vers un thread séparé pour la rendre asynchrone, mais ce code relevait davantage d’un ensemble de hacks que d’une solution de fond

Refonte de printk() depuis 2018

  • Le problème de printk() est traité sérieusement depuis 2018, et environ 300 patchs ont été intégrés en upstream ou sont en attente dans linux-next
  • Les trois derniers ensembles de patchs nécessaires pour terminer le travail sont actuellement en cours
  • L’un des points de détail les plus délicats est le mécanisme de handover
    • Quand le noyau doit afficher un message d’urgence, par exemple lors d’un crash, il peut être nécessaire de reprendre le contrôle d’une console occupée à afficher des messages de plus faible priorité
    • Le faire de manière sûre dans n’importe quel contexte n’est pas simple
  • Un autre défi consiste à marquer les pilotes de console qui ne peuvent pas être utilisés en toute sécurité dans certains contextes
    • Par exemple, si un message doit être affiché pendant une interruption non masquable mais nécessite un changement de mode vidéo, cela ne peut pas fonctionner
  • Gleixner a indiqué qu’il n’y avait pas eu de changement conceptuel fondamental au cours de l’année écoulée
    • Le noyau contient 76 pilotes de console qui doivent être corrigés
    • Le code de handover a été modifié pour permettre de mettre à jour les pilotes un par un, plutôt que de tous les corriger d’un coup
    • D’autres discussions récentes sur le travail autour de printk() sont disponibles dans cet article

Sortie asynchrone et conditions de fusion dans le mainline

  • Quand Masami Hiramatsu a demandé quels messages du noyau devaient être affichés de manière synchrone, Gleixner a répondu que presque tout devait devenir asynchrone
  • La sortie asynchrone réduit la latence provoquée par les appels à printk() et permet d’avoir un thread noyau distinct pour chaque console
    • Une console rapide peut ainsi fonctionner à son propre rythme sans attendre la plus lente
  • Le code a été modifié pour que les messages importants soient entièrement copiés dans le buffer de messages avant que la première ligne ne soit affichée
    • Cette mesure vise à se prémunir contre le cas où un pilote de console défectueux compromettrait l’ensemble du système
  • Pour un ordre d’affichage plus sûr, l’écriture se fait d’abord sur les consoles connues comme sûres
    • Par exemple, s’il existe un persistent-memory store, le message y est d’abord enregistré avant d’être envoyé au périphérique physique
    • L’objectif est de préserver le contenu affiché même si un pilote défectueux fait tomber le système
  • Gleixner a déclaré que, même si la fin approche, printk() reste imprévisible et qu’il ne donnerait plus de date de fin
  • Il espère néanmoins que le reste du code de préemption temps réel entrera dans le mainline avant le 20e anniversaire à la fin de 2024
  • Quand Clark Williams a demandé si le reste du code temps réel pourrait entrer dans la même merge window après l’intégration upstream des patchs printk(), Gleixner a répondu « yes », sous conditions
    • Si tout le code est staged dans linux-next et semble prêt, cela pourrait être tenté

1 commentaires

 
GN⁺ 2023-11-17
Avis sur Hacker News
  • QNX faisait déjà bien cela il y a des décennies. Le microkernel a une borne supérieure sur tout ce qu’il fait, et son code ne compte que quelques dizaines de milliers de lignes
    Le microkernel ne fait que l’allocation mémoire, l’ordonnancement CPU et le passage de messages entre processus. Tout le reste, y compris les pilotes et le logger, est en espace utilisateur et peut être préempté par des threads de priorité plus élevée
    Le noyau QNX ne manipule pas de chaînes de caractères. Pas de parsing, pas de formatting, pas de messages. Linux est devenu trop volumineux pour du temps réel, et comme il faut rendre préemptables des millions de lignes de code noyau, son architecture n’est tout simplement pas adaptée au temps réel. C’est pour cela que cela a pris 20 ans à corriger

    • Comme exemple moderne, il y a seL4. Si je me souviens bien, il ne fait pas d’allocation mémoire dynamique et il est aussi formellement vérifié sur plusieurs propriétés
      La plus grande contribution à la conception du noyau est probablement l’usage généralisé des capabilities, une manière d’exposer le contrôle à l’espace utilisateur de façon sûre et flexible
    • QNX n’est-il pas utilisé dans les systèmes d’infodivertissement automobiles ? Je me demande où il est employé ailleurs
      Le gonflement du noyau en soi ne m’inquiète pas beaucoup. Linux bénéficie d’énormément de temps de développement et, même si le desktop n’a pas la même priorité que les serveurs, le travail fait pour obtenir un noyau performant sur des appareils comme les terminaux portables profite aussi aux utilisateurs desktop
    • Le noyau SDP 8 actuel fait 15 331 lignes, commentaires et Makefile compris
    • Cela ressemble à une fonction main bien structurée en C ou dans une langue de la famille C. main ne fait qu’orchestrer les appels aux autres fonctions, et ici le noyau QNX fait moins d’initialisation, mais l’idée générale est assez similaire
      Je ne suis pas développeur noyau, mais cette façon de rester simple me paraît bonne
    • Sur ces « millions de lignes de noyau », environ 90 % sont des pilotes de périphériques. Si l’on veut tourner sur du matériel arbitraire, il en faut de toute façon aussi avec un microkernel
  • Même sur un système en train de mourir, le noyau essaie malgré tout d’émettre des messages de log, et il existe un exemple montrant comment cela est utilisé en production réelle
    https://netflixtechblog.com/kubernetes-and-kernel-panics-ed6...

  • Je me demande si, une fois ce problème corrigé, cela pourrait remplacer une part non négligeable des couples matériel/logiciel conçus pour le temps réel. Il existe maintenant beaucoup d’options de puces ARM et x86 bon marché, peu gourmandes en énergie et avec des fréquences élevées
    Les fréquences sont si hautes que, même s’il y a des ratés, il peut rester suffisamment de cycles pour que le temps réel parfait devienne moins crucial dans bien des cas. Je sais que ce n’est ni élégant ni efficace, mais parfois le matériel générique l’emporte sur la précision

    • Les tâches qui exigent du temps réel dur ne peuvent pas se satisfaire de « on peut rater, il reste assez de cycles ». Ce n’est pas qu’une question de cycles CPU
      Une seule tâche mal conçue peut monopoliser le noyau et l’empêcher de faire quoi que ce soit d’utile. L’essence du temps réel dur, c’est que « rien ne peut empêcher l’exécution de cette tâche critique ». Dans l’automobile ou l’aérospatial, les systèmes de contrôle doivent fonctionner en toute circonstance
    • Les applications qui ont de vraies exigences temps réel ont généralement des contraintes si strictes qu’elles ne peuvent tolérer la moindre petite probabilité d’échec. Il suffit de penser à l’avionique, aux dispositifs médicaux, à l’automobile ou aux applications militaires
      Quand il faut vraiment du temps réel, il le faut vraiment, et il n’existe pas de « suffisamment proche ». Ce n’est toutefois que mon impression d’observateur extérieur
    • Quand on construit des applications temps réel sur des puces ARM à basse consommation et haute fréquence, on n’utilise pas de système d’exploitation du tout. On ne considérerait pas non plus x86 pour ce type d’usage
      Un système d’exploitation, même un RTOS, gêne déjà trop. Je ne sais pas ce que ce changement modifiera. Cela dit, cela dépend des applications, et il existe beaucoup de cas où l’on a seulement besoin de « presque temps réel », donc cela pourrait être utile pour ces usages
    • Oui, mais cela ne fera pas disparaître comme par magie le besoin de cœurs dédiés. Il est fort probable qu’on dise simplement au scheduler de placer les tâches temps réel non préemptables uniquement sur un cœur LITTLE
  • La discussion ici se concentre sur la distinction entre applications temps réel « dur » et applications temps réel « souple ». Pour le temps réel dur, on n’a probablement pas envie d’utiliser dès le départ un OS généraliste comme Linux, tandis que pour le temps réel souple, comme la visioconférence ou la lecture audio, quelques saccades ou quelques frames perdues ne sont pas catastrophiques
    L’argument est que RT Linux serait une solution puissante pour ce type d’usages temps réel souples. Mais les usages souples proposés sont déjà possibles aujourd’hui avec Linux embarqué. Les vidéos logicielles à faible latence ou la lecture audio n’ont jamais été impossibles, c’était déjà faisable il y a 20 ans
    Le problème apparaît quand, sur un système chargé, des E/S non préemptables s’intercalent fréquemment, mais cela reste rare en environnement embarqué. Il y a de bonnes raisons de rendre le noyau entièrement préemptable et de donner davantage de contrôle sur l’ordonnancement, mais cela a peu à voir avec l’idée que Linux devrait remplacer les RTOS minimalistes ou le code bare metal
    C’est surtout une question de bonne hygiène, et cela permet aussi de construire un OS qui se comporte mieux sous charge, même pour des applications non temps réel

  • C’est une bonne nouvelle, mais même si le noyau Linux devient temps réel, le matériel, lui, ne l’est probablement pas à cause des caches et de la magie complexe à l’intérieur du CPU
    Un matériel gros et complexe n’est pas adapté au vrai temps réel. C’est pourquoi AbsInt et les outils d’analyse du pire temps d’exécution (WCET) traitent surtout des architectures CPU simples. Le 8051 survivra vraiment pour toujours. À noter qu’il existe aussi Zephyr RTOS

    • À ma connaissance, les fonctionnalités des CPU modernes n’empêchent pas leur usage en temps réel. Tant qu’il existe une borne supérieure et qu’on peut raisonner dessus, on peut s’en servir pour construire un système temps réel
      Il suffit de supposer une absence totale de cache hit, une charge maximale, etc. Si l’on peut fixer une borne supérieure au temps nécessaire, alors c’est acceptable
    • Je pense que c’est assez utile sur des cartes de type microcontrôleur « grandes » comme le Raspberry Pi. Il y a là une certaine culture du temps réel, et même sans faire directement du bit banging avec le CPU, vu de l’extérieur tout arrive à temps
      Les timers peuvent recevoir des entrées d’encodeurs quadrature et n’envoyer une interruption qu’au moment du wraparound, ou bien on peut connecter le système GPIO au DMA pour streamer la mémoire vers les broches de sortie sans intervention du CPU. On peut aussi streamer vers un DAC ou faire des transferts DMA d’un ADC vers la mémoire. Souvent, ces mécanismes contournent le cache afin d’obtenir une latence prévisible
    • SpaceX utilise des processeurs x86 dans ses fusées. Le petit drone hélicoptère que la NASA a envoyé sur Mars utilise lui aussi des cœurs ARM « assez gros », capables de faire tourner un ancien Android
    • Dire qu’un matériel gros et complexe n’est pas adapté au vrai temps réel n’est pas forcément exact. Il existe des cœurs temps réel avancés comme l’Arm Cortex-R82
      En pratique, beaucoup de systèmes temps réel doivent traiter et agréger des flux de capteurs toujours plus nombreux, et deviennent donc de plus en plus puissants
    • Le 68000 est le véritable roi du temps réel
  • En début de carrière, j’ai passé un nombre frustrant d’entretiens avec des recruteurs qui ne savaient pas ce que signifie réellement temps réel. Beaucoup passaient à côté de l’idée de « et une latence prévisible » mentionnée dans l’article, et semblaient considérer que temps réel voulait simplement dire « rapide »

    • J’enlèverais même complètement la partie « minimale ». L’essentiel du temps réel, c’est qu’une tâche a une borne supérieure prévisible. Cela signifie qu’en moyenne, cela peut même être plus lent qu’un système non temps réel
      Si vous contrôlez le système de freinage d’une voiture, « latence moyenne de 50 ms mais maximum de 80 ms » peut être acceptable, alors que « latence moyenne de 1 ms mais pouvant s’allonger arbitrairement et parfois prendre plusieurs secondes » ne l’est pas
    • Comme le dit l’ancienne formule, le « real time » n’est pas du « real fast ». La distinction entre hard real-time et soft real-time brouille un peu les choses, mais à mon avis même beaucoup de développeurs logiciels ne comprennent pas vraiment ce qu’est le temps réel
  • La journalisation synchrone a encore causé des problèmes. Dans mon entreprise, nous avons eu quelque chose de similaire avec GLOG (la bibliothèque de journalisation de Google) : si stdout pointe par exemple vers un fichier, on peut se retrouver bloqué sur l’I/O disque
    Quand notre service se figeait pendant plus de 100 ms, GLOG en était la cause dans 90 à 99 % des cas

    • J’ai souvent ce genre de conversation avec des collègues au sujet des logs. « Il y a une API best effort et une API à livraison garantie. » « Nous voulons une livraison garantie ! » « Si l’interface de logs à livraison garantie est hors ligne ou lente, cela va interrompre le service ; ça vous va ? » « Non, il ne faut surtout pas d’interruption ! »
      « Que faites-vous si quelque chose doit absolument être journalisé mais qu’il est impossible de l’écrire ? » Ces jours-ci, je me contente de montrer le théorème CAP et d’expliquer que la journalisation n’est pas différente des autres systèmes distribués. Comme il existe un article Wikipédia avec un triangle et le mot « théorème », les gens ont tendance à l’accepter
    • Il nous est arrivé qu’un serveur syslog se bloque et paralyse tout l’environnement de production. Nous poussions les logs en TCP, et ce blocage s’est propagé à toute la prod
      Nous sommes ensuite passés à un envoi en UDP. Mieux vaut perdre une partie des logs que perdre toute la production
    • Il y avait aussi, dans une bibliothèque de logs de $MSFT, ce type de problème où « la bibliothèque de logs casse tout ». Imaginez 100 threads avec chacun un tampon de logs de 300 Mo
      Évidemment, cela a pulvérisé la mémoire et le serveur a crashé même sur le SKU le plus cher d’Azure App Service
    • Si la disponibilité de votre produit dépend de ±100 ms, alors c’est qu’il est profondément mal conçu, et ce n’est pas la faute de la bibliothèque de logs. Les utilisateurs ne se soucieront pas de savoir si, après avoir cliqué sur un bouton, l’opération a pris 100 ms de plus pour se terminer
  • Ça me rappelle de vieux souvenirs. Il y a 17 ou 18 ans, j’avais compilé un noyau Debian avec RT_PREEMPT pour l’utiliser sur un instrument scientifique demandant un timing plus serré
    La latence et le jitter étaient vraiment impressionnants. Je n’y ai presque plus repensé depuis, mais pour des applications embarquées sur Raspberry Pi où l’on ne veut pas passer à un microcontrôleur avec RTOS, je pense qu’il y a encore beaucoup de cas d’usage

    • La mention du Raspberry Pi est intéressante. J’ai vu il y a un ou deux jours un article affirmant que RpiOS démarre et s’exécute au-dessus d’un RTOS
      J’avais déjà vu autrefois des propositions pour exécuter Linux comme une tâche d’un RTOS, ce qui m’avait particulièrement intéressé. L’idée est de faire tourner dans le RTOS ce qui exige de vraies échéances temps réel strictes, pour que cela ne subisse pas les latences que peut introduire un système à mémoire virtuelle. Je ne me souviens plus si cela est resté une simple idée ou si cela a réellement été implémenté, et comme je n’ai vu qu’une seule mention de RpiOS au-dessus d’un RTOS, cela m’intrigue
  • Je me demande ce que cela signifie pour l’utilisateur ordinaire. Est-ce une fonctionnalité qu’on n’active que dans des situations très particulières, ou est-ce que cela peut aussi apporter au grand public un système plus réactif ?

    • Si je comprends bien, le temps réel rend le système plus lent. Pour faire du temps réel, il faut allouer du temps à tout
      chaque tâche reçoit un budget X et ne doit pas le dépasser. Le meilleur cas peut être rapide, mais si le pire cas est lent, cela signifie que le système doit toujours partir du pire cas
    • Le RT n’améliore pas forcément la latence, il donne surtout à certaines tâches une borne supérieure fixe. En revanche, le travail nécessaire pour rendre le RT possible peut clairement améliorer la latence dans le cas général
      Éviter les appels synchrones à printk() en est un bon exemple, et même sans activer le RT, il faut améliorer la latence en situation de charge. Un noyau RT complètement intégré en upstream ne devrait en pratique pas se comporter différemment d’un noyau normal tant qu’il n’exécute pas de processus RT. Si l’intégration upstream a pris si longtemps, c’est parce qu’il fallait des compromis pour rendre le RT possible, et d’après l’article il n’en reste désormais plus beaucoup
    • Si par utilisateur « ordinaire » on entend utilisateur de bureau, il n’y a pas de grand changement. Mais pour des appareils embarqués comme le contrôle industriel et les équipements de télécommunication, c’est un gros enjeu
      car cela permet d’utiliser le noyau mainline le plus récent même lorsqu’on a besoin d’un ordonnancement temps réel
    • Si j’ai bien compris, cela fait de Linux une option dans les situations où un RTOS est nécessaire. C’est destiné aux systèmes critiques comme l’aéronautique ou les dispositifs médicaux, et cela n’a pratiquement aucun effet pour l’utilisateur ordinaire
    • Les utilisateurs finaux de bureau les plus susceptibles d’en profiter sont ceux qui font de l’audio. Dans ce domaine, la latence, et surtout le jitter, peut être assez pénible
  • Je me demande ce que vous pensez de Xenomai[1]. Je l’utilise depuis des années sans aucun problème
    Sur un BeagleBone Black, on obtient généralement un jitter de l’ordre de quelques centaines de nanosecondes, ce que je considère comme du temps réel « dur ». On peut planifier des tâches périodiques à l’échelle de quelques dizaines de microsecondes sans jamais en manquer une seule
    Contrairement à Real-Time Linux, qui cherche à rendre Linux lui-même préemptible, Xenomai est essentiellement son propre noyau et exécute Linux comme une tâche au-dessus. Il fournit une ABI qui permet d’exécuter des tâches écrites par l’utilisateur à côté de Linux, ou avec une priorité plus élevée. Par exemple, cela permet de contourner le problème de printk() : Xenomai s’en moque et effectue volontiers un changement de contexte depuis printk pour exécuter la tâche de l’utilisateur
    L’inconvénient, c’est qu’on ne peut pas faire d’appels système ordinaires dans le contexte Xenomai. On peut, mais cela casse évidemment le modèle temps réel. Par exemple, si on appelle printf() ou malloc() dans une tâche Xenomai, elle ne sera pas préemptible. L’ABI de Xenomai reproduit autant que possible ce dont on peut avoir besoin du point de vue des appels système, et si l’on accepte de gérer soi-même l’allocation sur le tas, cela fonctionne très bien
    [1]: https://xenomai.org/