- 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
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
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
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
mainbien structurée en C ou dans une langue de la famille C.mainne 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 similaireJe ne suis pas développeur noyau, mais cette façon de rester simple me paraît bonne
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
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
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
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
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
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
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
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
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 »
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
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
« 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
Nous sommes ensuite passés à un envoi en UDP. Mieux vaut perdre une partie des logs que perdre toute la production
$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
Ç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
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 ?
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
É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 beaucoupcar cela permet d’utiliser le noyau mainline le plus récent même lorsqu’on a besoin d’un ordonnancement temps réel
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 depuisprintkpour exécuter la tâche de l’utilisateurL’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()oumalloc()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/