Un article récent et brillant de Baptiste Mélès organise la philosophie de la pratique informatique entre deux concepts, les processus et les fichiers. Le raisonnement est profond, mais ce sont les fichiers qui me gênent : ils me semblent peu aptes à la conceptualisation, mal définis, inventés pour des raisons circonstancielles, et la mémoire me semblerait plus appropriée. En voici donc une critique.
Ce texte est issu (et adapté) de mon livre Systèmes d’exploitation des ordinateurs : histoire, fonctionnement, enjeux, disponible librement en ligne ici.
La question des fichiers
La notion de fichier pose un problème, qui me fait hésiter à l’ériger en concept, et si je me représente bien pourquoi les concepteurs d’Unix l’ont mise au centre de leur système, je me demande si le nom est bien choisi.
Au début était la mémoire. Comme sa capacité était trop limitée, par héritage de la mécanographie, on a inventé le fichier. Mais ne serait-il pas plus élégant d’en rester à la mémoire, organisée de telle sorte que certaines de ses régions soient persistantes ? Ainsi (c’est Christian Queinnec qui m’en a donné la formule) il y aurait dans la mémoire des objets, dont certains seraient dotés de l’attribut « persistant », ce qui voudrait dire qu’avant l’extinction du processus qui les a créés, ils seraient munis d’un nom unique, et déplacés dans une région persistante de la mémoire (un disque par exemple). Il y a déjà longtemps, c’est ainsi que Multics envisageait les choses. Des systèmes plus récents en ont repris l’idée.
Bien sûr, les auteurs initiaux d’Unix n’étaient pas sans avis polémique sur Multics, trop lourd pour les matériels de l’époque, mais j’ai toujours pensé qu’un malentendu s’était placé là.
Critique des fichiers ; systèmes persistants
Si nous pensons au système de mémoire virtuelle présenté ici et que nous le comparons à la description du système de fichiers donnée là, nous ne pouvons manquer d’être frappés par leur redondance mutuelle. L’un et l’autre systèmes ont pour fonction d’assurer la persistance de données qui étaient dans la mémoire centrale pour y subir un traitement, qui cessent d’y résider pour une raison ou une autre, et que l’on souhaite néanmoins conserver pour un usage ultérieur. La différence entre les deux réside finalement dans les circonstances qui dans l’un et l’autre cas amènent les données à cesser de résider en mémoire, et c’est cette différence qui est à l’origine de réalisations techniques dont la ressemblance ne saute pas aux yeux. Mais au fond, la mémoire virtuelle et le système de fichiers font la même chose, avec des différences d’interface plus que de fonctionnement, et l’on peut dire que si la mémoire virtuelle était venue plus tôt les fichiers n’auraient sans doute pas vu le jour.
Précurseurs
D’ailleurs, des précurseurs ont choisi de s’en passer. Dans Multics, évoqué ici et aussi là, la mémoire virtuelle est découpée en segments de taille variable. C’est une structure de mémoire virtuelle plus complexe que celle que nous avons décrite, mais elle remplit les mêmes fonctions. Eh bien, pour Multics on dit simplement que certains segments sont persistants et résident de façon permanente sur disque. C’est un attribut d’un segment : la persistance ! Et pour savoir quels segments sont présents à tel ou tel emplacement de la mémoire persistante, on utilise une commande de liste des segments, en abrégé ls, que les Unixiens reconnaîtront.
Nous avons déjà mentionné le système Pick et celui de l’IBM AS400, construits autour d’une base de données. Le choix est surtout clair avec Pick : chaque donnée individuelle porte un nom utilisable dans l’ensemble du système, et ce système de nommage est unifié. Par exemple, si le système est utilisé pour une application de paie dans un organisme public, il y a une variable et une seule pour donner la valeur du point d’indice des fonctionnaires. Cette variable a un propriétaire (sans doute le Secrétaire d’État à la Fonction Publique), qui dispose seul du droit d’en modifier la valeur. Tous les programmes qui doivent connaître cette valeur peuvent y accéder. Lors de la parution au Journal Officiel d’une modification de la valeur du point d’indice, une seule opération garantit la justesse de tous les calculs.
Incidemment, cette architecture de données procure aux utilisateurs profanes (et aux autres !) une vision considérablement simplifiée de l’ensemble du processus de traitement et de stockage de l’information. Une grande partie de la complexité de ces choses pour le néophyte tient à la difficulté d’avoir une vision d’ensemble d’un système qui réunit et coordonne des objets qui à l’état actif sont en mémoire et qui au repos sont dispersés dans une foule de fichiers aux statuts variés. Avoir un concept unique de mémoire pour tous les objets, persistants ou non, et une désignation unique pour tout objet quels que soient son état et son activité, ce sont des améliorations intellectuelles considérables. Le corporatisme informatique en a eu raison, provisoirement souhaitons-le.
La recherche sur les systèmes persistants continue, même si elle reste assez confidentielle. L’augmentation des performances des processeurs et des mémoires devrait l’encourager en abolissant les obstacles de cet ordre qui ont eu raison des précurseurs.
Principe de persistance orthogonale
Le principe de persistance orthogonale est apparu à la fin des années 1970, dans le domaine des langages de programmation. Il proclame que toute donnée doit être habilitée à persister pendant un délai aussi long qu’il est utile, et que la méthode d’accès à une donnée doit être indépendante de la nature de sa persistance. Dans les systèmes classiques il en va tout autrement : les données volatiles en mémoire centrale sont invoquées par leur nom, les données persistantes sur mémoire externe sont accueillies en mémoire centrale comme le résultat de l’invocation d’une commande d’entrée-sortie. Un système persistant reléguera ces différences techniques dans les couches basses du système et présentera une interface uniforme d’accès aux données.
Les tentatives pour implanter la persistance orthogonale dans les langages ou les bases de données utilisées dans le contexte de systèmes d’exploitation classique comme Unix n’ont pas donné de très bons résultats, parce que le problème de l’accès aux données est trop fondamental pour être résolu de façon totalement différente par un programme d’application d’une part, par son environnement d’autre part. L’auteur de système persistant est amené à gérer la mémoire de manière parfois subtile et complexe, or dans un système classique tel Unix c’est le noyau qui décide des pages de mémoire virtuelle à garder en mémoire volatile ou à reléguer en mémoire auxiliaire, ce qui aboutit à des contradictions.
Fiasco des bases de données à objets
L’exemple d’un tel échec est fourni par les bases de données à objets qui avaient soulevé un grand intérêt dans les années 1990 avec leur programme très séduisant, qui consistait à stocker les données sous la même forme en mémoire centrale pendant leur vie « active » et en mémoire auxiliaire pendant leur vie « latente ». Le revers de la médaille était que le format des données dans les bases dépendait alors du langage de programmation et du matériel utilisés : une base de données créée par un programme C++ n’était pas accessible à un programme Java, et si elle avait été créée sur une machine Sun à processeur 32 bits elle n’était pas accessible à un programme exécuté par une machine à processeur Alpha 64 bits, sauf à passer par des programmes de conversion de données qui font perdre tout l’avantage attendu. De surcroît la mémoire persistante était réalisée à base de systèmes de fichiers classiques, totalement inadaptés à une telle fonction. Et enfin il résultait de tout ceci une programmation laborieuse et des performances le plus souvent médiocres. Si je puis mentionner mes modestes expériences personnelles de combat avec un Système de Gestion de Données Objet (SGDO) pourtant réputé très industriel (par opposition aux logiciels libres développés par des chercheurs dans les universités), je ne puis me déprendre d’une impression de bricolage : la trace du système révélait que le SGDO passait le plus clair de son temps à balayer en long, en large et en travers des arborescences de répertoire pour y chercher des données qui étaient tout à fait ailleurs, et à recopier un nombre incalculable de fois la même (grande) portion de fichier en mémoire (au moyen de l’appel système mmap pour les connaisseurs [1]). Il n’y avait bien sûr pour un utilisateur naïf aucun moyen simple d’extraire des données de la base : la seule façon était d’écrire du code C++ bare metal, exercice particulièrement punitif ou pervers. Alors que même si l’on peut reprocher aux Systèmes de Gestion de Bases de Données Relationnelles (SGBDR) tels que PostgreSQL, Oracle ou MySQL une certaine rigidité, ils offrent au moins une méthode d’accès aux données relativement normalisée et relativement simple avec le langage SQL.
La leçon à en tirer semble être qu’il vaut mieux implanter les fonctions de persistance au niveau du système d’exploitation, quitte à ce que ce soit une couche d’interface ajoutée à un système classique sous-jacent. L’implantation de la persistance dans le système garantit une uniformité de vision pour tous les programmes d’application, évite la redondance de fonctions qui alourdissait tellement les bases de données à objets, bref elle assure la cohérence de la sémantique d’accès aux données. C’était ce que faisait à sa façon Pick, et que fait encore en 2014 le système de l’AS400, rebaptisé System i5.
Projets de recherche
Les principaux projets de recherche en persistance à l’orée des années 2000 étaient les suivants :
– Le projet MONADS a démarré en 1976 à l’Université Monash (Australie). L’abstraction de base est le segment, à la Multics : persistance à gros grain.
– Clouds vient du Georgia Institute of Technology (1988). Les abstractions sont l’objet et l’activité (thread). Repose sur le micro-noyau Ra.
– Eumel et ses successeurs L3 et L4 ont leur origine en 1977 à l’Université de Bielefeld, puis au GMD (Gesellschaft für Mathematik und Datenverarbeitung, équivalent allemand d’Inria), et sont principalement l’œuvre du regretté Jochen Liedtke. Eumel fut le premier système à persistance orthogonale. Il s’agit aussi d’un système à micro-noyau (voir là).
– Grasshoper est un système à persistance orthogonale développé à l’Université de Saint Andrews, Écosse. Les entités persistantes sont des containers, des loci et des capabilities. Dans les systèmes classiques la notion d’espace-adresse est inextricablement mêlée à celle de processus. Les containers et les loci sont des entités analogues mais mieux distinguées (pardon : orthogonales), qui ont vocation à persister. Le noyau du système lui-même est persistant (il y a quand même toujours une partie non persistante, ne serait-ce que pour le boot).
– Charm est le successeur de Grasshoper. Le noyau de Charm n’exporte aucune abstraction pour le support de la persistance, mais uniquement des domaines de protection capable de communiquer avec le noyau.
Charm appartient à une nouvelle tendance parmi les systèmes d’exploitation : au lieu de cacher le matériel derrière des abstractions, il l’expose afin que les stratégies de gestion des ressources soient implémentées en « mode utilisateur ». Cela suppose des dispositions favorables de l’architecture matérielle.
Le motif est de séparer :
les règles de gestion des ressources de bas niveau ;
des mécanismes qui les implémentent.
L’auteur du système de haut niveau est libre d’implémenter règles et mécanismes par une bibliothèque.
Incidemment, on peut signaler qu’il existait un système d’exploitation universellement répandu et qui possédait beaucoup de caractéristiques « persistantes » : PalmOS, qui anime les ordinateurs de poche PalmPilot et Handspring Visor. Chaque programme reste en mémoire dans l’état où l’utilisateur l’abandonne, et où il peut le retrouver lorsqu’il rallume l’engin. Il n’y a pas de vrai système de fichiers. C’est assez surprenant quand on est habitué aux ordinateurs classiques. Les smartphones contemporains ont une interface moins cohérente de ce point de vue.
Reprise sur point de contrôle
La notion de reprise sur point de contrôle (checkpoint-restart) est bien sûr au cœur de tous ces systèmes. Le problème à résoudre est le suivant : si un programme est interrompu inopinément en cours de traitement, comment reprendre son exécution sans avoir à la recommencer depuis le début ? Idéalement, il faudrait reprendre au point où l’on s’était arrêté, mais il est raisonnablement acceptable de repartir d’un point en amont pas trop éloigné. La difficulté principale réside bien sûr dans la restauration de ce que nous avons appelé le vecteur d’état du programme : valeur des variables, et contenu des fichiers.
Ce problème n’est pas propre aux systèmes persistants, et il a déjà été abordé et résolu. Dès les années 60 l’OS 360 offrait une possibilité de checkpoint-restart pour les programmes utilisateur. Cette possibilité consistait à enregistrer périodiquement sur disque une copie de la mémoire et un relevé de l’état des fichiers ouverts. En cas d’incident le programme repartait automatiquement du dernier point de contrôle. Ce mécanisme entraînait une consommation d’espace disque considérable pour l’époque et surtout une organisation rigoureuse du lancement et de l’exécution des chaînes de programmes, ce qui en restreignait l’usage.
Aujourd’hui une fonction analogue existe pour les ordinateurs portables : une combinaison de touches de commande permet de sauvegarder le contenu de la mémoire sur disque et de mettre l’ordinateur en veille, puis de le réactiver plus tard, ce qui permet par exemple de changer de batterie sans passer par la procédure d’arrêt du système et de redémarrage, elle-même grosse consommatrice de temps et d’électricité.
Les SGBD convenables offrent la possibilité de déclarer des transactions, c’est-à-dire de déclarer un ensemble d’opérations, par exemple une mise à jour complexe de la base, comme une méta-opération atomique. Conformément à l’étymologie une méta-opération atomique est insécable : soit toutes les opérations de la transaction sont accomplies correctement, soit elles sont toutes annulées. Ceci est destiné à éviter de laisser la base dans un état incohérent, voire inconnu. Les transactions sont généralement associées à un dispositif de roll in-roll out qui permet d’entériner une transaction (roll in) ou de ramener la base de données à l’état antérieur à la transaction (roll out).
Depuis les années 1980 sont apparus sur les systèmes en production, issus de la recherche, les systèmes de fichiers journalisés tels Andrew File System de l’Université Carnegie-Mellon, ADVFS dérivé du précédent dans les laboratoires Digital Equipment (DEC), JFS développé par IBM, XFS réalisé par Silicon Graphics (SGI), et plus récemment sous Linux Ext3fs et Reiserfs. Le principe de la journalisation est le suivant : avant toute opération de modification du système de fichiers par une opération d’entrée-sortie, on enregistre dans un fichier spécial (journal, en anglais log) la description de l’opération, et après l’opération on enregistre qu’elle s’est bien passée. Ainsi, après un incident qui aurait corrompu une partie du système de fichiers, il suffit de « rejouer » les opérations enregistrées dans le journal pour restituer un état cohérent, ce qui est infiniment plus sûr et plus rapide que de recourir à un logiciel de contrôle et de restauration de la cohérence interne du système de fichiers tels fsck sous Unix.
Avec les systèmes persistants, le problème est simplifié autant que généralisé. Un système persistant digne de ce nom n’a ni fichiers ni a fortiori système de fichiers. Il est de ce fait indispensable de garantir à tout prix le maintien de la cohérence du contenu de la mémoire virtuelle, seul lieu de conservation des données, et ce quel que soit l’incident, y compris une coupure de l’alimentation électrique. Les systèmes persistants disposent donc d’un système d’enregistrement de points de contrôle, qui permet de sauvegarder périodiquement le contenu de la mémoire, et de reprise sur incident à partir du dernier point de contrôle. Ce mécanisme n’est pas une option comme dans les environnements classiques, mais un fondement du système. Une conséquence amusante de ce type de fonctionnement, qui surprit les auteurs des premiers systèmes persistants, c’est qu’un programme d’application ne peut pas être informé d’un arrêt du système, puisqu’il est reparti automatiquement comme si de rien n’était. On ne sait donc pas qu’il y a eu une coupure de courant dans la nuit, sauf à consulter les journaux.