bas de page
 

À l'heure du Cloud et des processeurs multi-cœurs :
Alléger le noyau !

Operating System Kernel Debloating

Laurent Bloch
 

Les ordinateurs et les systèmes d'exploitation gaspillent des ressources

   Les ordinateurs contemporains sont dotés de processeurs multi-cœurs (c'est-à-dire en fait de multiprocesseurs) bien trop puissants pour la plupart des usages ordinaires, hormis certains jeux vidéo. La mémoire disponible est également pléthorique. C'est un peu moins vrai pour les serveurs de l'informatique professionnelle, mais là aussi les limites drastiques d'il y a quelques décennies sont oubliées. Alors les auteurs de logiciels, et plus particulièrement les éditeurs de systèmes d'exploitation, ont pris l'habitude de s'étaler. À quoi bon lire un fichier de données ligne par ligne, alors qu'il suffit de le charger d'un bloc en mémoire ? Il en va de même pour les bases de données, etc. Et c'est encore bien pire pour le noyau (kernel) du système d'exploitation, qui, lui, doit rester en permanence en mémoire réelle (par opposition à mémoire virtuelle). Sur la machine que j'utilise pour saisir ce texte, le noyau (vmlinuz-5.15.0-37-generic) fait plus de 11 millions d'octets, auxquels il convient d'ajouter les quelques 62 millions d'octets d'initrd.img qui contient toutes sortes de bricoles nécessaires au démarrage du système, et qui une fois décompressé par unmkinitramf en occupe quand même 195 millions (merci à Papy Tux [1] pour les détails).

   Les utilisateurs avaient pris l'habitude de ne pas se soucier de ces gaspillages : après tout, les systèmes d'exploitation contemporains savent diminuer la fréquence d'horloge d'un processeur s'il n'est pas très chargé, et le mettre en veille s'il ne fait rien. Mais avec l'infonuagique (Cloud Computing) et les préoccupations relatives à l'environnement, cela est en train de changer, tant pour le système d'exploitation que pour les programmes d'application. Étant entendu que depuis déjà au moins deux décennies l'économie d'énergie est au cœur des préoccupations des concepteurs de processeurs, avec des résultats spectaculaires obtenus principalement par la miniaturisation des composants.

Réduire l'encombrement et la consommation du système d'exploitation

   En des temps (non encore révolus) où le gaspillage de ressources n'était pas un souci, les éditeurs de systèmes d'exploitation, par souci de commodité pour l'utilisateur, livraient des configurations dotées de toutes les fonctionnalités imaginables. En effet, une configuration plus ascétique aurait risqué de provoquer l'échec d'une application, ce genre de situation est difficile à résoudre par l'utilisateur de base, et déclenche des appels au service support, ce qui coûte plus cher que l'ajout de quelques méga-octets au système installé pré-configuré sur l'ordinateur livré. Mais les temps d'opulence ont une fin !

   Si l'on utilise un système d'exploitation tel que Windows, on n'a guère de moyens de réduire son empreinte mémoire. Il est plus facile d'agir sur un système ouvert, dont Linux est le spécimen le plus répandu. Le système Linux comporte une partie fixe, le noyau, qui réside en permanence dans la mémoire de la machine (virtuelle ou réelle), des modules chargeables à la demande en fonction des besoins des programmes d'applications, puis des programmes annexes et des bibliothèques de fonctions invoqués en tant que de besoin. Si l'on veut obtenir un système léger, facile à transporter par le réseau pour une exécution en 50 exemplaires dans les nuages à l'autre bout de la planète, le noyau est le premier élément à alléger.

   En principe, Linux vient avec tous les outils désirables pour configurer un noyau adapté à ses applications : on peut choisir les modules dont on a besoin et écarter les autres. Simplement, comme le souligne Daniel Lohmann, de l'université Leibniz de Hannovre, commentateur de l'article, il faut choisir parmi plus de 17 000 modules, autant dire qu'à la main c'est impossible.

   Plusieurs méthodes d'automatisation ont été tentées : elles consistent à faire fonctionner les applications désirées, à enregistrer les traces du comportement du système pendant l'exécution, à analyser ces traces et à construire un noyau réduit aux seules fonctions dont on aura repéré l'activation dans les traces. Ces méthodes reposent sur la fonction ftrace [2], qui comme son nom l'indique permet d'analyser le comportement du système par l'instrumentation au niveau du noyau et d'examiner :

  • les appels systèmes ;
  • les fonctions de traitement d'interruption ;
  • les fonction d'ordonnancement ;
  • les piles réseau.

   Cela semble judicieux, mais malheureusement les résultats ont été décevants. Ils ont rencontré les obstacles suivants :

  • ftrace ne permet pas de savoir quel code est chargé par le noyau pendant la phase de boot du système, de ce fait le noyau construit à partir de ces observations risque de ne pas fonctionner, ou de ne pas permettre le fonctionnement des applications envisagées. Les auteurs ont mesuré que 79 % des dispositifs optionnels du noyau ne pouvaient être détectés que pendant la phase de boot.

  • Ces méthodes demandent beaucoup de temps, ce qui décourage les utilisateurs.

  • ftrace donne des informations au niveau de la fonction, alors qu'il serait utile de descendre à un niveau plus fin.

  • On ne peut jamais être sûr que le jeu d'essai soit complet.

   Entre parenthèses, les questions soulevées ici expliquent que les éditeurs fassent le choix de ratisser large et d'en mettre plutôt trop que pas assez !

Une nouvelle méthode basée sur QEMU

   Un article du numéro de mai 2022 des Communications of the ACM [3] intitulé Set the Configuration for the Heart of the OS : On the Practicality of Operating System Kernel Debloating, écrit par Hsuan-Chi Kuo, Jianyan Chen, Sibin Mohan, et Tianyin Xu, accessible en version préliminaire ici [4], aborde le problème à nouveaux frais, pour contourner les obstacles mentionnés ci-dessus.

   Le principe de leur méthode d'allègement du noyau, implémentée dans le framework COZART, peut se résumer (à grands traits) ainsi :

   – COZART fonctionne au-dessus de QEMU. QEMU [5] est un émulateur qui permet de faire fonctionner des machines virtuelles [6], qu'elles soient d'architecture identique à celle de la machine hôte, ou d'une architecture différente.

Comme COZART tourne dans la machine hôte, il peut observer le boot de la machine virtuelle qui fait tourner le jeu d'essai.

– Comme il tourne au-dessus de QEMU, et que QEMU fournit des outils de trace au niveau de l'instruction machine, COZART permet des observations plus fines que les méthodes antérieures.

– La connaissance des adresses des instructions exécutées permet (au prix sans doute d'un travail minutieux considérable) d'identifier précisément les sections de code noyau utilisées par les applications.

– Des observations obtenues comme indiqué ci-dessus les auteurs déduisent une configuration d'un noyau fonctionnel, stable et capable de faire fonctionner les applications considérées.

   Selon les applications, sous Linux, COZART permet de gagner 14 % sur le temps de boot et 4 mégaoctets sur la taille du noyau. Le temps de calcul pour obtenir ce réultat se compte en dizaines de secondes. Un approfondissement de la démarche permet des gains encore plus grands, cf. l'article.

   Tout cela est bien sûr plus compliqué que ce que suggère le résumé succinct ci-dessus, et l'article expose d'autres détails passionnants, accessibles ici [7]. Les auteurs ont appliqué leur méthode pour construire des noyaux destinés à des machines virtuelles pour les hyperviseurs KVM [8] et Xen [9], ainsi qu'à une machine hôte pour Docker [10], à des machines virtuelles pour AWS [11] et au micro-noyau sécurisé L4Re/Fiasco [12].

   La version d'auteur de l'article est accessible en ligne ici [13].

   Un article précédent de ce site présentait une autre méthode de construction de machines virtuelles allégées, parce qu'adaptées à une application particulière et débarrassées des fonctions que cette application n'utilise pas, MirageOS : machines virtuelles compilées à la demande avec le système d'exploitation et l'application [14].

Langages gloutons, langages sobres

   Certains langages ont été conçus sans aucun souci de sobriété. Il en va ainsi de Python, conçu pour écrire de petits scripts destinés à ordonnancer les programmes qui font les calculs, écrits dans des langages plus efficaces (cf. Biopython [15]), et à cet usage il excelle. Mais Python est utilisé aujourd'hui pour des applications de calcul intensif (apprentissage profond, fouille de données, etc.) pour l'unique raison que leurs auteurs le connaissent, avec une efficacité calamiteuse.

   Java est un langage de conception plus soucieuse d'efficacité, mais une efficacité des années 1990, quand les processeurs multi-cœurs étaient rares, mais la variété des architectures plus grande qu'aujourd'hui : la facilité pour porter un programme d'un type de processeur à un autre était un facteur de productivité plus important que maintenant, d'où l'idée d'une machine virtuelle standard et le slogan write once, run anywhere. Java vient avec un environnement d'exécution (runtime) très lourd, avec glaneur de cellules (GC, garbage collector), compilation just in time (JIT), JVM (Java Virtual Machine), ce qui fait que le démarrage d'une grosse application Java prend pas mal de temps et de ressources, mémoire notamment. Tant que cela se passait sur un serveur de l'entreprise ou sur la machine de l'utilisateur, ce n'était pas trop gênant, l'application était lancée le matin, voir le lundi matin pour toute la semaine, et cette charge n'était endurée qu'une fois, sur une machine déjà payée. Dès lors que cela se passe dans les nuages, sur des machines virtuelles susceptibles de se multiplier au gré des augmentations de la charge, le tout facturé au temps d'utilisation, cela devient un problème. Bastien Vigneron a exposé cette question de façon systématique et proposé une nouvelle approche du développement logiciel [16], avec des langages qui optimisent le temps de lancement et d'exécution plutôt que le temps de portage.

   Ainsi, Bastien Vigneron conseille, pour les développements nouveaux, d'utiliser le langage Rust pour les choses de bas niveau qui doivent interagir étroitement avec le matériel et le système d'exploitation : c'est un langage sans runtime, donc aussi efficace que C, mais avec un modèle de mémoire qui lui donne la sûreté dont C est dépourvu. Et pour les développements plus ordinaires (de plus haut niveau), il recommande Go, plus facile à programmer que Rust, ce qui optimise le temps de développement. Ces deux langages rendent la programmation concurrente assez facile, ce qui permet de tirer le meilleur parti des architectures multi-cœurs.

Laurent Bloch

Paru le 14 juin 2022 sur le site de Laurent Bloch.
https://laurentbloch.net/MySpip3/Alleger-le-noyau#nh1

Cet article est sous licence Creative Commons (selon la juridiction française = Paternité - Pas de Modification). http://creativecommons.org/licenses/by-nd/2.0/fr/

NOTES

[1] https://papy-tux.legtux.org/doc1132/index.html

[2] https://www.linuxembedded.fr/2011/03/introduction-a-ftrace

[3] Communications of the ACM, vol. 65 n° 5, pages 101-109.

[4] https://tianyin.github.io/pub/cozart.pdf

[5] https://fr.wikipedia.org/wiki/QEMU

[6] https://laurentbloch.net/MySpip3/Introduction-aux-machines-virtuelles-255

[7] https://tianyin.github.io/pub/cozart.pdf

[8] https://fr.wikipedia.org/wiki/Kernel-based_Virtual_Machine

[9] https://fr.wikipedia.org/wiki/Xen

[10] https://fr.wikipedia.org/wiki/Docker_( logiciel)

[11] https://fr.wikipedia.org/wiki/Amazon_Web_Services

[12] https://os.inf.tu-dresden.de/fiasco/

[13] https://tianyin.github.io/pub/cozart.pdf

[14] https://laurentbloch.net/MySpip3/MirageOS-machines-virtuelles-compilees-a-la-demande-avec-le-systeme-d-exploitation-et-l-application

[15] https://laurentbloch.net/MySpip3/Python-Scheme-C#outil_sommaire_3

[16] https://bastienvigneron.medium.com/développement-logiciel-quest-ce-qui-a-changé-b8f0518606da

haut de page
Association EPI
Décembre 2022

Accueil Articles