- Les gens ne semblent pas suffisamment conscients du caractère incomplet de la documentation de l’API du noyau Linux, ni de la manière dont Rust résout une partie de ce problème
- En écrivant des abstractions Rust pour plusieurs sous-systèmes, il a presque toujours fallu lire le code source C pour comprendre entièrement comment utiliser l’API en toute sécurité
- Les signatures de fonctions et les commentaires de documentation associés, ou même la documentation explicite, ne suffisent pas à déterminer complètement comment utiliser l’API sans danger
- faut-il conserver le verrou
- un argument de compteur de références transfère-t-il une référence ou prend-il sa propre référence
- lors de l’appel d’un callback, le verrou est-il conservé ou faut-il l’acquérir soi-même
- le callback de libération a-t-il quelque chose de particulier
- quel est l’ordre de verrouillage prévu
- existe-t-il des cas particuliers où certaines opérations peuvent parfois utiliser un verrou
- les arguments NULL sont-ils autorisés et, si oui, dans quels cas valides
- que devient le compteur de références en cas d’erreur
- un pointeur à compteur de références renvoyé est-il déjà incrémenté, ou s’agit-il d’un emprunt implicite à partir de la référence de l’argument passé
- la valeur de retour est-elle toujours un pointeur valide, peut-elle être NULL, ou peut-elle aussi être un
ERR_PTR - le pointeur renvoyé via un argument indirect est-il remis à NULL en cas d’erreur ou laissé inchangé
- est-il valide de passer
NULL **quand le pointeur de retour n’est pas nécessaire
- Parfois, les exigences étaient raisonnables mais non documentées
- parfois, elles étaient trop flexibles ou trop complexes, si bien qu’il fallait prendre des décisions subjectives pour les restreindre à un usage sûr lors de l’écriture des abstractions Rust
- parfois, il a fallu introduire un verrouillage supplémentaire à l’intérieur de l’abstraction pour garantir la sûreté
- parfois, il a fallu modifier légèrement le code C pour le rendre plus orthogonal, plus logique et plus simple à utiliser (par exemple, exposer une fonction de déverrouillage à utiliser quand un verrou est détenu)
- parfois, le verrouillage était subtil au point qu’il était possible d’écrire une abstraction Rust sûre, mais qu’il fallait un long commentaire de documentation expliquant qu’il fallait faire attention à l’ordre d’utilisation et de libération pour éviter les interblocages (Rust n’empêche pas intrinsèquement les deadlocks)
- parfois, le problème était insoluble sans rendre le code C lui-même plus raisonnable (cas de
drm_sched)
- Cependant, dans la plupart des cas, les compromis nécessaires pour écrire une abstraction Rust mettent en évidence les problèmes de conception du code C et les pistes d’amélioration
- l’approche générale est « écrire d’abord du code Rust en modifiant le moins possible le code C afin d’éviter les conflits, puis proposer des changements dans le code C à partir des leçons apprises » (la deuxième partie n’a pas encore été entamée)
- Au final, dans la plupart des cas, il suffit de regarder l’API Rust pour savoir comment l’utiliser correctement
- pas besoin de se soucier des compteurs de références, des pointeurs NULL, des vérifications de résultats oubliées ou des libérations de références en cas d’erreur
- pas besoin de se soucier de l’utilisation correcte des verrous, des acquisitions de référence manquantes ou des doubles libérations
- pas besoin de se demander comment sont encodées les valeurs de retour d’erreur
- si l’on se trompe sur ces points, le code ne compile pas
- bien sûr, il reste possible de mal utiliser l’API, mais dans le pire des cas cela entraîne seulement un retour d’erreur ou un interblocage (les deadlocks se déboguent facilement avec
lockdep, et l’intégrationArc<>permet de repérer les erreurs de verrouillage liées à la libération et à la décrémentation de référence)
- Même l’API OpenFirmware/DeviceTree, pourtant relativement bien documentée, est fastidieuse et sujette aux erreurs en C lorsqu’il faut respecter toutes les règles
- en regardant le code OF des pilotes, les fuites de références semblent probables
- la plupart des systèmes ne compilent pas le noyau avec
OF_DYNAMIC, donc le comptage de références y est ignoré et ces problèmes ne sont ni détectés ni corrigés - mais l’abstraction Rust OF écrite ici gère automatiquement le comptage de références, ce qui évite d’avoir à s’en préoccuper
- Les avantages du développement noyau en Rust par rapport au C
- en C, il n’y a essentiellement que deux options
- tenter sa chance en espérant qu’un reviewer détecte le problème, ou souffrir pendant le débogage
- passer des heures à tout comprendre avant même oser utiliser le code, en espérant avoir tout repéré
- cela augmente aussi la charge de travail des reviewers et des mainteneurs
- ils doivent examiner les soumissions pour vérifier que toutes les règles cachées et non documentées sont respectées
- parfois, ils laissent passer des problèmes ; parfois, les problèmes sont si importants qu’il faut refactoriser massivement le code
- Avec Rust, tout cela disparaît. Si le code compile, il est sûr et il n’y a ni dysfonctionnement ni fuite de références (à l’exception du code
unsafe, mais seul celui-ci doit être examiné, avec une règle imposant qu’il soit bien documenté)- bien sûr, il faut toujours des revues de code et l’aide d’experts de sous-systèmes spécifiques ; Rust ne rend pas magiquement le code parfait
- mais il élimine toutes les erreurs et tous les problèmes stupides de bas niveau, ce qui permet de se concentrer sur les questions de plus haut niveau
- Position vis-à-vis des développeurs Linux
- cela ne reproche pas aux développeurs Linux l’insuffisance de la documentation
- le noyau Linux est extrêmement complexe et doit gérer de nombreuses subtilités
- la plupart des API en espace utilisateur ont des règles bien plus simples pour être utilisées sans danger
- le noyau, c’est difficile
- même les développeurs noyau expérimentés se trompent encore régulièrement sur ce genre de choses
- ce n’est pas un problème technique ; il est simplement impossible pour des humains de garder toutes ces règles complexes en tête et de les appliquer parfaitement à chaque fois
- La solution
- nous avons besoin d’outils
- la solution est Rust. Une fois toutes les règles encodées une fois pour toutes dans le code et dans le système de types, il n’y a plus à s’en inquiéter
- c’est comme pour les débats sur le style de code, dont la solution consiste à encoder toutes les règles dans un formateur automatique
- on peut alors cesser de se préoccuper de tous les problèmes de sécurité bas niveau, de propriété et de synchronisation, et se concentrer sur des sujets plus importants comme la conception des pilotes et des sous-systèmes
- Formatage du code dans le projet Rust for Linux
- le projet Rust for Linux applique effectivement
rustfmtaux soumissions - quand on écrit du Rust pour le noyau, il n’y a pas à se soucier du formatage du code ni des plaintes en revue de code à ce sujet
- il suffit de faire
make rustfmt
- le projet Rust for Linux applique effectivement
L’avis de GN⁺
- Cet article met bien en évidence les problèmes de documentation des API et de sûreté dans le développement du noyau Linux. Il montre clairement les limites du langage C et les atouts de Rust
- Cependant, l’idée que « Rust est l’unique solution » semble un peu exagérée. D’autres approches, comme les outils d’analyse statique, pourraient aussi apporter certaines améliorations
- Rust résout de nombreux problèmes, notamment de sûreté mémoire, mais des revues de code rigoureuses et des tests restent nécessaires. Ce n’est pas une solution miracle
- Le passage à Rust peut aussi poser diverses difficultés, notamment la compatibilité avec le code C existant et la courbe d’apprentissage pour les développeurs. Une adoption progressive semble préférable
- Pour faire évoluer les pratiques et la culture historiques du noyau Linux, il faudra sans doute, en plus de Rust, des efforts sur plusieurs fronts : documentation, mentorat, communication, etc.
- Globalement, cet article montre bien le potentiel et les avantages de Rust pour le développement du noyau Linux, tout en mettant en garde contre des attentes excessives ou une confiance aveugle. Son adoption sera sans doute difficile sur les plans technique et culturel, mais elle pourrait, à long terme, contribuer à améliorer la sûreté et la maintenabilité du code du noyau.
2 commentaires
Rust... j’ai essayé de l’étudier à titre personnel, mais nous ne l’avons pas encore adopté dans notre entreprise. Nous avons déjà une montagne de code écrit en C++, et il y a aussi le problème de devoir réapprendre Rust aux équipes existantes... J’ai entendu dire qu’il y a déjà des entreprises en Corée qui utilisent Rust en production ; ce serait bien que des retours d’expérience sur le sujet soient partagés.
Avis Hacker News
Les langages comme Rust et Swift sont très expressifs, et le compilateur peut indiquer la sûreté des types de données ou des méthodes vis-à-vis des threads
De nombreuses bibliothèques Rust souffrent d'une documentation insuffisante
Certains essaient d'utiliser Rust comme du C, puis se heurtent au borrow checker
&selfou&mut self&mut self, il faut utiliser un mutex pour partager l'instance entre les threadsEn regardant une API Rust, on peut dans la plupart des cas comprendre comment l'utiliser correctement
Un exemple concret en Rust est l'utilisation de verrous pour protéger les données
Dans d'autres langages aussi, dupliquer l'API dans l'implémentation peut améliorer la clarté du code et de la documentation
Lorsqu'on utilise du C dans une extension Python, il faut connaître la convention d'appel
Ces personnes sont des héros et font un excellent travail
On se rapproche encore un peu plus d'un code totalement auto-documenté