Site WWW de Laurent Bloch
Slogan du site

ISSN 2271-3905
Cliquez ici si vous voulez visiter mon autre site, orienté vers des sujets moins techniques.

Pour recevoir (au plus une fois par semaine) les nouveautés de ce site, indiquez ici votre adresse électronique :

La conversion à Unix
Un exemple de prophétisme informatique ?
Article mis en ligne le 21 octobre 2017
dernière modification le 7 juillet 2023

par Laurent Bloch

Le colloque « Unix en France et aux États-Unis : innovation, diffusion et appropriation », organisé par le laboratoire Histoire des technosciences en société, s’est tenu au Cnam le 19 octobre. À l’invitation des organisateurs, et après en avoir discuté avec eux, le thème de ma communication fut : « La conversion à Unix. Un exemple de prophétisme informatique ? ». Comme explicité par Benjamin Thierry, historien des techniques et de l’innovation à la Sorbonne, qui me donnait la réplique, les pratiquants de disciplines en principe entièrement sous l’empire de la raison, telle l’informatique, ont fréquemment recours au vocabulaire religieux pour évoquer les aspects non rationnels de leur pratique, qui n’ont en principe pas droit de cité. Vous pourrez lire ci-dessous le texte de ma communication.

*Introduction

Unix survient une vingtaine d’années après l’invention de l’ordinateur, et une dizaine d’années après que quelques pionniers eurent compris qu’avec l’informatique une nouvelle science naissait, qu’ils eurent tenté de la faire reconnaître comme telle, et qu’ils eurent échoué dans cette tentative. Certains traits d’Unix et certains facteurs de son succès procèdent de cet échec, et c’est de cette histoire qu’il va être question ici, selon la perception que j’en ai eue de ma position de praticien. Cette perception me venait de façon rétrospective, aussi l’ordre chronologique n’est-il pas toujours respecté dans cet exposé. Ce qui suit est le récit de l’élaboration d’une vision personnelle, qui assume sa part de subjectivité.

L’histoire des sciences et des techniques ne nous laisse pas ignorer la présence en ces domaines de phénomènes de croyance qui peuvent aller jusqu’à des formes quasi-religieuses, et ainsi engendrer des manifestations typiques de la vie religieuse, telles que le prophétisme et la conversion. Le développement d’Unix m’a semblé doté d’une dimension prophétique, et pour développer ce point de vue j’emprunterai quelques idées au livre fondateur d’André Neher L’essence du prophétisme [1]. Pour rester fidèle (si j’ose dire) à l’esprit unixien, j’invite le lecteur à prendre cette dernière thèse cum grano salis.

*Avant l’informatique

Entre 1936 et 1938 à Princeton Alan Turing avait bien conscience de faire de la science, mais ne soupçonnait pas que ses travaux de logique seraient un jour considérés comme la fondation théorique d’une science qui n’existait pas encore, l’informatique.

Dans les couloirs de l’IAS il croisait John von Neumann, parfois ils parlaient travail, mais pas du tout de questions de logique, domaine que von Neumann avait délibérément abandonné après la publication des travaux de Gödel [2]. En fait, ils étaient tous les deux mathématiciens, et ils parlaient des zéros de la fonction ζ(s) et de l’hypothèse de Riemann (RH). Les efforts d’Alonzo Church pour éveiller l’intérêt de ses collègues pour les travaux sur les fondements de son disciple Turing (“On computable numbers, with an application to the Entscheidungsproblem”) rencontraient peu de succès, la question apparaissait clairement démodée.

Ce n’est qu’à la rencontre d’Herman Goldstine, et, par son entremise, des concepteurs de l’ENIAC Eckert et Mauchly, à l’été 1944, que von Neumann s’intéressa aux calculateurs, et sans le moins du monde établir un lien entre cet intérêt et les travaux de Turing dont il avait connaissance. Néanmoins, si Turing avait jeté les bases de l’informatique, von Neumann allait inventer l’ordinateur.

Samuel Goyet [3] a eu, lors d’une séance du séminaire Codes sources, une formule frappante et qui me semble exacte : avant von Neumann, programmer c’était tourner des boutons et brancher des fiches dans des tableaux de connexion, depuis von Neumann c’est écrire un texte ; cette révolution ouvrait la voie à la science informatique.

Lors d’une séance précédente du même séminaire, Liesbeth De Mol [4] avait analysé les textes de von Neumann et d’Adele et Herman Goldstine, en montrant que tout en écrivant des programmes, ils n’avaient qu’une conscience encore imprécise du type d’activité à laquelle ils s’adonnaient.

C’est donc une douzaine d’années après le First Draft of a Report on the EDVAC [5] de von Neumann (1945) que s’est éveillée la conscience de l’arrivée d’une science nouvelle, et j’en retiendrai comme manifestations les plus explicites la naissance du langage de programmation Algol, puis la naissance et la diffusion du système d’exploitation Multics. Il faudra encore une bonne quinzaine d’années pour que la bonne nouvelle se répande quelque peu parmi les praticiens de l’informatique, dont l’auteur de ces lignes, d’abord sous les espèces de la Programmation structurée [6] [7], qui semait l’espoir d’une sortie du bricolage. Inutile de préciser que l’esprit de bricolage est encore présent parmi les praticiens.

*Algol et Multics

Algol et Multics sont les cadavres dans le placard d’Unix, même si les meurtriers ne sont pas clairement identifiés.

**Algol

La composition des comités de rédaction des rapports Algol successifs [8] et la teneur de leurs travaux [9] me semblent marquer un point de non retour dans la constitution de l’informatique comme science.

Jusqu’alors la programmation des ordinateurs était considérée un peu comme un bricolage empirique, qui empruntait sa démarche à d’autres domaines de connaissance.

Fortran, le premier langage dit évolué, était conçu comme la transposition la plus conforme possible du formalisme mathématique, du moins c’était son ambition, déçue comme il se doit [10], et le caractère effectuant du texte du programme, qui le distingue radicalement d’une formule mathématique, plutôt que d’être signalé, était soigneusement dissimulé, notamment par l’emploi du signe = pour désigner l’opération d’affectation d’une valeur à une variable, variable au sens informatique du terme, lui aussi distinct radicalement de son acception mathématique. La conception même du langage obscurcissait la signification de ses énoncés, ce que l’on peut pardonner à John Backus parce qu’il s’aventurait dans un domaine jamais exploré avant lui : « Fortran montrait qu’un langage de programmation pouvait introduire de nouvelles abstractions qui seraient codées par un compilateur, plutôt que directement implémentées par le matériel » [11]. Cette affaire du signe = n’est pas si anecdotique qu’il y paraît, nous y reviendrons.

De même, Cobol se voulait le plus conforme possible au langage des comptables, et RPG cherchait à reproduire les habitudes professionnelles des mécanographes avec leurs tableaux de connexions.

Algol rompt brutalement avec ces compromis, il condense les idées de la révolution de la programmation annoncée par Alan Perlis [12] en tirant toutes les conséquences (telles que perçues à l’époque) de la mission assignée au texte d’un programme : effectuer un calcul conforme à l’idée formulée par un algorithme. La syntaxe du langage ne doit viser qu’à exprimer cette idée avec précision et clarté, la lisibilité du texte en est une qualité essentielle, pour ce faire il est composé de mots qui composent des phrases. L’opération d’affectation := est clairement distinguée du prédicat d’égalité = [13].

La composition du comité Algol 58 est significative, la plupart des membres sont des universitaires, l’influence des industriels est modérée, Américains et Européens sont à parité (j’ai ajouté au tableau quelques personnalités influentes qui n’appartenaient pas formellement au comité International Algebraic Language (IAL) initial, ou qui ont rejoint ultérieurement le comité Algol 60, ou qui simplement ont joué un rôle dans cette histoire) :

Friedrich L. Bauer 1924 Professeur Munich
Hermann Bottenbruch 1928 PhD Darmstadt Ingénieur
Heinz Rutishauser 1918 PhD ETH Zürich Professeur Zürich
Klaus Samelson 1918 PhD Munich Professeur Munich
John Backus 1924 MS Columbia Ingénieur IBM
Alan Perlis 1922 PhD MIT Professeur Yale
Joseph Henry Wegstein 1922 MS U. Illinois NIST
Adriaan van Wijngaarden 1916 U. Amsterdam
Peter Naur 1928 PhD Copenhague Prof. Copenhague
Mike Woodger 1923 U. College London NPL
Bernard Vauquois 1929 Doctorat Paris Prof. U. Grenoble
Charles Katz 1927 MS U. Penn Ingénieur Univac
Edsger W. Dijkstra 1930 PhD Amsterdam U. of Texas, Austin
C. A. R. Hoare 1934 Prof. Oxford
Niklaus Wirth 1934 PhD Berkeley Professeur Zürich
John McCarthy 1927 PhD Princeton Professeur Stanford
Marcel-Paul Schützenberger 1920 PhD Math. Professeur Poitiers
Jacques André 1938 PhD Math. DR Inria
Jacques Arsac 1929 ENS

Les années de naissance sont significatives : il s’agit de la génération 1925 (plus ou moins).

S’ils ne figurent pas au sein des comités, Edsger W. Dijkstra et Jacques Arsac ont contribué (avec Dahl, Hoare et beaucoup d’autres) à la systématisation de leurs idées sous la forme d’une doctrine, la programmation structurée [14] [15], qui a contribué à extraire ma génération de l’ignorance dans laquelle elle croupissait. Elle est souvent et abusivement réduite à une idée, le renoncement aux instructions de branchement explicite (GOTO), promulgué par un article célèbre de Dijkstra [16], Go to Statement Considered Harmful. Il s’agit plus généralement d’appliquer à la programmation les préceptes du Discours de la Méthode, de découper les gros programmes compliqués en petits programmes plus simples [17], et ainsi d’éviter la programmation en « plat de spaghettis », illisible et donc impossible à maintenir.

Rien n’exprime mieux l’aspiration de cette époque à la création d’une science nouvelle que le livre de Dijkstra A Discipline of Programming [18] : l’effort pour exprimer de façon rigoureuse les idées les plus ardues de la programmation va de pair avec la recherche d’un formalisme qui ne doive rien aux notations mathématiques.

Pierre-Éric Mounier-Kuhn, dans un article de 2014 [19], narre le succès initial rencontré dans les années 1960 en France par Algol 60, puis son déclin au cours des années 1970, parallèle à celui connu en d’autres pays.

Algol 68 était un beau monument intellectuel, peu maniable pour la faible puissance des ordinateurs de l’époque, peu apte à séduire constructeurs d’ordinateurs et utilisateurs en entreprise avec des problèmes concrets à résoudre en temps fini.

**Multics, un système intelligent

Multics est à la science des systèmes d’exploitation ce qu’est Algol à celle des langages de programmation : un échec public, mais la source d’idées révolutionnaires et toujours d’actualité qui sont à l’origine d’une science des systèmes d’exploitation. Idées dont certaines ont d’ailleurs été largement empruntées par les créateurs d’Unix.

Multics est né en 1964 au MIT (Massachusetts Institute of Technology) dans le cadre d’un projet de recherche nommé MAC, sous la direction de Fernando Corbató.

Nous ne reviendrons pas sur les circonstances bien connues dans lesquelles Ken Thompson et Dennis M. Ritchie ont créé Unix parce que le système Multics qu’ils utilisaient aux Bell Labs allait disparaître (cf. mon article [20] ou mon livre [21]). De Multics ils voulaient conserver les caractères suivants :

 Le système est écrit non pas en assembleur, mais dans un langage de haut niveau (PL/1 pour Multics, C pour Unix). On a pu dire que le langage C était un assembleur portable.
 Le système de commandes qui permet de piloter le système est le même interpréteur qui permet à l’utilisateur d’exécuter des programmes et des commandes, et il donne accès à un langage de programmation. C’est le shell, inventé par Louis Pouzin pour CTSS, ancêtre de Multics.
 Le système de fichiers d’Unix doit beaucoup à Multics, d’où vient aussi l’idée d’exécuter chaque commande comme un processus distinct.
 Mais surtout, comme Dennis Ritchie l’a expliqué dans son article de 1979 [22], ce que lui et ses collègues des Bell Laboratories voulaient retrouver de Multics en créant Unix, c’était un système qui engendrait pour ainsi dire spontanément la communication et l’échange d’expériences entre ses adeptes.

Cette qualité, partagée par Multics et Unix, d’être propice à la création d’une communauté ouverte et communicative, mérite que l’on s’y arrête. Lorsque Multics a été introduit dans l’Institut statistique qui employait l’auteur de ces lignes à la fin des années 1970, il y a immédiatement cristallisé la formation d’une petite communauté intellectuelle, que la Direction n’a d’ailleurs eu de cesse de résorber parce qu’elle n’en comprenait pas la fécondité et qu’elle percevait son activité comme un gaspillage. D’innombrables expériences similaires ont eu lieu autour de Multics et d’Unix, sans qu’une cause unique puisse leur être attribuée. Le fait que ces systèmes aient été créés par des chercheurs, habitués à l’idée que la connaissance soit objet de partage gratuit et de communication désintéressée mais assortie de plaisir, est un élément. L’existence de logiciels commodes pour la création de textes, le courrier électronique et les forums en ligne a aussi joué, mais cette existence était-elle une cause ou une conséquence ? La nature programmable du shell, l’accès possible pour tous aux paramètres du système, inscrits dans des fichiers de texte ordinaires, encourageaient un usage intelligent du système, et l’intelligence va de pair avec l’échange.

De Multics les créateurs d’Unix rejetaient la lourdeur, due en majeure partie au fait que les ordinateurs de l’époque n’étaient pas assez puissants pour le faire fonctionner confortablement : ils étaient trop chers, trop lents, trop encombrants.

**Avenir de Multics

Il n’est paradoxal qu’en apparence de prédire l’avenir de ce système disparu. Certaines des idées les plus brillantes de Multics sont restées inabouties du fait des limites des ordinateurs de l’époque. Celle qui me tient le plus à cœur consiste à évacuer la notion inutile, voire nuisible, de fichier, pour ne garder qu’un seul dispositif d’enregistrement des données, la mémoire (mieux nommée en anglais storage), dont certains segments seraient pourvus de l’attribut de persistance [23].

Le fichier est un objet rétif à toute définition conceptuelle consistante, hérité de la mécanographie par les cartes perforées et la puissance d’IBM, mais sans aucune utilité pour un système d’exploitation d’ordinateur moderne. Il n’est que de constater la difficulté qu’il y a à expliquer à un utilisateur ordinaire pourquoi il doit sauvegarder ses documents en cours de création, et pourquoi ce qui est dans la mémoire est d’une nature différente de ce qui est sur le disque dur, terminologie qui révèle une solution de continuité injustifiée.

Les systèmes d’exploitation de demain, je l’espère, n’auront plus de fichiers, c’est déjà le cas de systèmes à micro-noyaux comme L4.

*Unix

**Le crépuscule de Multics

Dans son article de 1979 [24] Dennis Ritchie a décrit la période où les Bell Labs se retiraient du projet Multics. Le groupe de D. Ritchie, K. Thompson, M. D. McIlroy et Joseph F. Ossanna souhaitait conserver l’environnement de travail luxueux que Multics leur procurait à un coût d’autant plus exorbitant qu’ils en étaient les derniers utilisateurs. Pour ce faire ils allaient développer leur propre système sur un petit ordinateur bon marché et un peu inutilisé récupéré dans un couloir, un PDP 7 de Digital Equipment. Unix était sinon né, du moins conçu.

**Unix, un système en marge

Le livre de Peter H. Salus A Quarter Century of UNIX [25] met en scène les principaux acteurs de la naissance d’Unix ; il est patent que c’est du travail « en perruque ». On est frappé en lisant ces aventures de découvrir que cette création, qui a eu des répercussions considérables dans les domaines scientifique et technique autant qu’industriel et économique, n’a vraiment été décidée ni par un groupe industriel, ni par un gouvernement, ni par aucun organisme doté de pouvoir et de moyens financiers importants.

À la lecture du livre de Salus, quiconque a un peu fréquenté les milieux scientifiques d’une part, les milieux industriels de l’autre, ne peut manquer d’être frappé par le caractère décalé, pour ne pas dire franchement marginal, de la plupart des acteurs de la genèse unixienne.

Du côté de l’industrie informatique, la domination d’IBM était écrasante (70% du marché mondial). Les constructeurs d’ordinateurs de gestion comme leur clients ne se souciaient guère des aspects scientifiques de l’informatique, et là où l’informatique était appliquée à la science (essentiellement la physique) sa nature scientifique n’était pas mieux reconnue.

Dans le monde universitaire l’informatique échouait à se faire reconnaître comme véritable objet intellectuel, et le système d’exploitation encore moins que les langages ou les algorithmes [26].

**Les auteurs d’Unix

Or que nous apprend Salus, auquel j’emprunte le générique avec lequel j’ai construit le tableau ci-desous ? Thompson et Ritchie étaient chercheurs dans une entreprise industrielle. Au fur et à mesure de leur apparition, les noms de ceux qui ont fait Unix, parmi eux Kirk McKusick, Bill Joy, Eric Allman, Keith Bostic, sont toujours accompagnés d’un commentaire de la même veine : ils étaient étudiants undergraduates ou en cours de PhD, et soudain ils ont découvert qu’Unix était bien plus passionnant que leurs études. Bref, les auteurs d’Unix n’ont jamais emprunté ni la voie qui mène les ingénieurs perspicaces vers les fauteuils de Directeurs Généraux, ni celle que prennent les bons étudiants vers la tenure track, les chaires prestigieuses, voire le Nobel.

Si l’on compare ce tableau avec celui des concepteurs d’Algol, on constate une différence de génération (1945 contre 1925), une plus faible propension à soutenir une thèse de doctorat, le choix de carrières d’ingénieur plutôt que d’universitaire, même si ces carrières se déroulent souvent dans un environnement de type universitaire. On notera que Joseph Ossanna avait aussi fait partie de l’équipe qui a réalisé Multics. On notera aussi la disparition des Européens, présents presque à parité dans le groupe Algol.

Ken Thompson 1932 Berkeley, MS 1966 Ingénieur Bell Labs
Dennis Ritchie 1941 Harvard, PhD 1968) Ingénieur Bell Labs, Lucent
Brian Kernighan 1942 Princeton (PhD) Ingénieur Bell Labs
Stephen Bourne 1944 PhD. Cambridge Ingénieur Bell Labs etc.
Keith Bostic 1959 Ingénieur UC Berkeley
Joseph Ossanna 1928 BS Wayne State U. Ingénieur Bell Labs
Douglas McIlroy 1932 Cornell, MIT PhD Ingénieur Bell Labs
Kirk McKusick 1954 PhD Berkeley Ingénieur, UC Berkeley
Eric Allman 1955 MS UC Berkeley Ingénieur, UC Berkeley
Bill Joy 1954 MS UC Berkeley Ingénieur Sun Microsystems
Özalp Babaoğlu 1955 PhD Berkeley Professeur, U. Bologne
John Lions 1937 Doctorat Cambridge Ing. Burroughs, U. Sydney
Robert Morris 1932 MS Harvard Ingénieur Bell Labs, NSA
Mike Lesk PhD. Harvard Ingénieur Bell Labs
Mike Karels BS U. Notre Dame Ing. Berkeley

**Prophétisme informatique

Les circonstances évoquées ci-dessus et le caractère de leurs acteurs suggèrent un rapprochement avec les attitudes associées à un phénomène religieux, le prophétisme, tel qu’analysé par André Neher [27]. Il distingue (p. 47) quatre types de prophétisme :

 magique,
 social-revendicatif,
 mystique,
 eschatologique (c’est-à-dire apocalyptique, ou messianique).

Le prophétisme unixien relève assez clairement du type social-revendicatif, même si certains aspects de l’informatique peuvent plaider en faveur du type magique, et si certaines prophéties délirantes relatives à l’« intelligence artificielle » versent dans le style apocalyptique. Ken Thompson et Dennis Ritchie voulaient et annonçaient un style informatique approprié à leur style social et professionnel, et ils ont fait en sorte de le créer.

Un autre trait caractéristique du prophétisme est le scandale : un prophète qui ne créerait pas de scandale serait simplement ridicule. Neher explique (p. 255) que pour le prophète les temps ne sont pas accomplis, ils restent à construire, alors que les gens ordinaires préféreraient se reposer dans la léthargie du présent.

Les apôtres d’Unix n’ont pas manqué à leur mission sur le terrain du scandale. Ils ont bravé tant les informaticiens universitaires que le monde industriel, avec un succès considérable. Les puissances d’établissement (industriels, managers, autorités académiques du temps) ont nourri une véritable haine d’Unix, dont une des manifestations les plus ridicules fut la constitution d’un club des associations d’utilisateurs de systèmes d’exploitation mono-constructeurs, à l’intitulé spécialement formulé de sorte à écarter l’Association française des utilisateurs d’Unix et des systèmes ouverts (AFUU), fondée en 1982, dont j’étais à l’époque membre du Conseil d’Administration.

**Unix occupe le terrain

Ces épisodes ont influencé tant la pratique informatique que le milieu social auxquels je participais, en général avec au moins une décennie de décalage. Cette expérience, renforcée par quelques décennies de discussions et de controverses avec quantité de collègues, m’a suggéré une hypothèse : au cours des années 1970, la génération d’Algol et de Multics a finalement perdu ses batailles, ses idées n’ont guère convaincu ni le monde industriel, dominé à l’époque par IBM et l’ascension des constructeurs japonais et de Digital Equipment, ni le monde universitaire. Et la génération Unix a occupé le terrain laissé vacant, par une stratégie de guérilla (du faible au fort), avec de nouvelles préoccupations et de nouveaux objectifs. Pendant ce temps les géants de l’époque ne voyaient pas venir la vague micro-informatique qui allait profondément les remettre en cause.

La mission d’un universitaire consiste, entre autres, à faire avancer la connaissance en élucidant des problèmes compliqués par l’élaboration de théories et de concepts nouveaux. Les comités Algol et le groupe Multics ont parfaitement rempli cette mission en produisant des abstractions de nature à généraliser ce qui n’était auparavant que des collections d’innombrables recettes empiriques, redondantes et contradictoires. L’élégance d’Algol resplendit surtout dans sa clarté et sa simplicité.

La mission d’un ingénieur consiste en général à procurer des solutions efficaces à des problèmes opérationnels concrets. Nul ne peut contester que ce souci d’efficacité ait été au cœur des préoccupations des auteurs d’Unix, parfois un peu trop, pas tant d’ailleurs pour le système proprement dit que pour son langage d’implémentation, C.

**Inélégances du langage C

Certains traits du langage C me sont restés inexplicables jusqu’à ce que je suive un cours d’assembleur VAX, descendant de leur ancêtre commun, l’assembleur PDP. J’ai compris alors d’où venaient ces modes d’adressage biscornus et ces syntaxes à coucher dehors, justifiées certes par la capacité exiguë des mémoires disponibles à l’époque, mais de nature à décourager l’apprenti. L’obscurité peut être un moyen de défense de techniciens soucieux de se mettre à l’abri des critiques.

Sans trop vouloir entrer dans la sempiternelle querelle des langages de programmation, je retiendrai deux défauts du langage C, dus clairement à un souci d’efficacité mal compris : l’emploi du signe = pour signifier l’opération d’affectation, et l’usage du caractère NUL pour marquer la fin d’une chaîne de caractères.

À l’époque où nous étions tous débutants, la distinction entre l’égalité [13] et l’affectation fut une affaire importante. En venant de Fortran, les idées sur la question étaient pour le moins confuses, et il en résultait des erreurs cocasses ; après avoir fait de l’assistance aux utilisateurs pour leurs programmes Fortran je parle d’expérience. L’arrivée d’une distinction syntaxique claire, avec Algol, Pascal, LSE ou Ada, sans parler de Lisp, permettait de remettre les choses en place, ce fut une avancée intellectuelle dans la voie d’une vraie réflexion sur les programmes.

Les auteurs de C en ont jugé autrement : ils notent l’affectation = et l’égalité ==, avec comme argument le fait qu’en C on écrit plus souvent des affectations que des égalités, et que cela économise des frappes. Ça c’est de l’ingénierie de haut niveau ! Dans un article du bulletin 1024 de la SIF [28], une jeune doctorante explique comment, lors d’une présentation de l’informatique à des collégiens à l’occasion de la fête de la Science, une collégienne lui a fait observer que l’expression i = i+1 qu’elle remarquait dans le texte d’un programme était fausse. Cette collégienne avait raison, et l’explication forcément controuvée qu’elle aura reçue l’aura peut-être écartée de l’informatique, parce qu’elle aura eu l’impression d’une escroquerie intellectuelle. Sa question montrait qu’elle écoutait et comprenait ce que lui disaient les professeurs, et là des idées durement acquises étaient balayées sans raison valable.

Pour la critique de l’usage du caractère NUL pour marquer la fin d’une chaîne de caractères je peux m’appuyer sur un renfort solide, l’article de Poul-Henning Kamp The Most Expensive One-byte Mistake [29]. Rappelons que ce choix malencontreux (au lieu de représenter une chaîne comme un doublet {longueur, adresse}) contribue aux erreurs de débordement de zone mémoire, encore aujourd’hui la faille favorite des pirates informatiques. Poul-Henning Kamp énumère dans son article les coûts induits par ce choix : coût des piratages réussis, coût des mesures de sécurité pour s’en prémunir, coût de développement de compilateurs, coût des pertes de performance imputables aux mesures de sécurité supplémentaires…

Un autre article de ce site expose de façon plus développée mes réticences à l’égard du langage C.

**Élégance d’Unix

Si le langage C ne manque pas de défauts, il est néanmoins possible d’écrire des programmes C élégants.

La première édition en français du manuel de système d’Andrew Tanenbaum [30] comportait le code source intégral de Minix, une version allégée d’Unix à usage pédagogique, qui devait servir d’inspiration initiale à Linus Torvalds pour Linux. Pour le commentaire du code source de Linux on se reportera avec profit au livre exhaustif et d’une grande clarté de Patrick Cegielski [31] (la première édition reposait sur la version 0.01 du noyau, beaucoup plus sobre et facile d’accès que la version ultérieure commentée pour la seconde édition à la demande de l’éditeur).

On trouvera à la fin de cet article un exemple de code source extrait du scheduler (éventuellement traduit par ordonnanceur) de Linux 0.01. Le scheduler est l’élément principal du système d’exploitation, il distribue le temps de processeur aux différents processus en concurrence pour pouvoir s’exécuter.

Les codes de ces systèmes sont aujourd’hui facilement disponibles en ligne, ici [32] et là [33] par exemple. Par souci d’équité (d’œcuménisme ?) entre les obédiences on n’aura garde d’omettre BSD [34], ici la version historique 4BSD. Quelques extraits figurent à la fin du présent texte.

L’élégance d’Unix réside dans la sobriété et la simplicité des solutions retenues, qui n’ont pas toujours très bien résisté à la nécessité de les adapter à des architectures matérielles et logicielles de plus en plus complexes. Ainsi, la totalité des 500 super-ordinateurs les plus puissants du monde fonctionnent sous Linux, ce qui implique la capacité de coordonner plusieurs milliers de processeurs, 40 460 pour le chinois Sunway TaihuLight qui tenait la corde en 2016, dont chacun héberge plusieurs centaines de processus : le scheduler ultra-concis du noyau Linux v0.01 dont on trouvera le texte ci-dessous en serait bien incapable.

Autre élégance des versions libres d’Unix (Linux, FreeBSD, NetBSD, OpenBSD...) : le texte en est disponible, et les paramètres variables du système sont écrits dans des fichiers de texte lisible, ce qui permet à tout un chacun de les lire et d’essayer de les comprendre.

*Conclusion

Finalement les universitaires orphelins d’Algol et de Multics se sont convertis en masse à Unix, ce qui assurait son hégémonie sans partage. La distribution de licences à un coût symbolique pour les institutions universitaires par les Bell Labs, ainsi que la disponibilité de compilateurs C gratuits, furent pour beaucoup dans ce ralliement, à une époque (1982) où le compilateur Ada que j’avais acheté à Digital Equipment, avec les 80 % de réduction pour organisme de recherche, coûtait 500 000 francs.

Unix a permis pendant un temps la constitution d’une vraie communauté informatique entre universitaires et ingénieurs, ce qui fut positif. Mon avis est moins positif en ce qui concerne la diffusion du langage C : pour écrire du C en comprenant ce que l’on fait, il faut savoir pas mal de choses sur le système d’exploitation et l’architecture des ordinateurs, savoirs qui ne peuvent s’acquérir que par l’expérience de la programmation. C n’est donc pas un langage pour débutants.

Si le langage C est relativement bien adapté à l’écriture de logiciel de bas niveau, typiquement le système d’exploitation, je reste convaincu que son usage est une torture très contre-productive pour les biologistes (ce sont ceux que je connais le mieux) et autres profanes obligés de se battre avec malloc, sizeof, typedef, struct et autres pointeurs dont ils sont hors d’état de comprendre la nature et la signification. La conversion à C n’a pas vraiment amélioré la pratique du calcul scientifique, la mode récente de Python leur donne au moins l’impression (fallacieuse) de comprendre le sens des programmes qu’ils écrivent.

*Annexe : code source

Scheduler du noyau Linux v0.01 (extrait du fichier kernel/sched.c) :

  1. void schedule(void)
  2. {
  3.         int i,next,c;
  4.         struct task_struct ** p;
  5. /* check alarm, wake up any interruptible tasks that have got a signal */
  6.         for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
  7.                 if (*p) {
  8.                         if ((*p)->alarm && (*p)->alarm < jiffies) {
  9.                                         (*p)->signal |= (1<<(SIGALRM-1));
  10.                                         (*p)->alarm = 0;
  11.                                 }
  12.                         if ((*p)->signal && (*p)->state==TASK_INTERRUPTIBLE)
  13.                                 (*p)->state=TASK_RUNNING;
  14.                 }
  15. /* this is the scheduler proper: */
  16.         while (1) {
  17.                 c = -1;
  18.                 next = 0;
  19.                 i = NR_TASKS;
  20.                 p = &task[NR_TASKS];
  21.                 while (--i) {
  22.                         if (!*--p)
  23.                                 continue;
  24.                         if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
  25.                                 c = (*p)->counter, next = i;
  26.                 }
  27.                 if (c) break;
  28.                 for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
  29.                         if (*p)
  30.                                 (*p)->counter = ((*p)->counter >> 1) + (*p)->priority;
  31.         }
  32.         switch_to(next);
  33. }

Télécharger

Avec l’aide de Patrick Cegielski [35] (p. 196) on observe que la variable c représente la priorité dynamique de la tâche [36] considérée. Le scheduler parcourt la table des tâches, et parmi celles qui sont dans l’état TASK_RUNNING, c’est-à-dire disponibles pour s’exécuter, il sélectionne celle qui a la priorité dynamique la plus élevée et lui donne la main : switch_to(next);

Bien que pour un texte C ce snippet (fragment de code) soit relativement propre, il illustre la raison de mes réticences à l’égard de ce langage. Et lorsque Peter Salus [37] (p. 77) écrit que C est un langage différent des autres langages de programmation, plus proche d’un langage humain comme l’anglais, on peut se demander s’il a un jour écrit une ligne de programme, en C ou autre chose.

En cadeau-prime voici le fichier d’en-tête include/linux/sched.h, avec même un peu d’assembleur :

  1. #ifndef _SCHED_H
  2. #define _SCHED_H
  3.  
  4. #define NR_TASKS 64
  5. #define HZ 100
  6.  
  7. #define FIRST_TASK task[0]
  8. #define LAST_TASK task[NR_TASKS-1]
  9.  
  10. #include <linux/head.h>
  11. #include <linux/fs.h>
  12. #include <linux/mm.h>
  13.  
  14. #if (NR_OPEN > 32)
  15. #error "Currently the close-on-exec-flags are in one word, max 32 files/proc"
  16. #endif
  17.  
  18. #define TASK_RUNNING            0
  19. #define TASK_INTERRUPTIBLE      1
  20. #define TASK_UNINTERRUPTIBLE    2
  21. #define TASK_ZOMBIE             3
  22. #define TASK_STOPPED            4
  23.  
  24. #ifndef NULL
  25. #define NULL ((void *) 0)
  26. #endif
  27.  
  28. extern int copy_page_tables(unsigned long from, unsigned long to, long size);
  29. extern int free_page_tables(unsigned long from, long size);
  30.  
  31. extern void sched_init(void);
  32. extern void schedule(void);
  33. extern void trap_init(void);
  34. extern void panic(const char * str);
  35. extern int tty_write(unsigned minor,char * buf,int count);
  36.  
  37. typedef int (*fn_ptr)();
  38.  
  39. struct i387_struct {
  40.         long    cwd;
  41.         long    swd;
  42.         long    twd;
  43.         long    fip;
  44.         long    fcs;
  45.         long    foo;
  46.         long    fos;
  47.         long    st_space[20];   /* 8*10 bytes for each FP-reg = 80 bytes */
  48. };
  49.  
  50. struct tss_struct {
  51.         long    back_link;      /* 16 high bits zero */
  52.         long    esp0;
  53.         long    ss0;            /* 16 high bits zero */
  54.         long    esp1;
  55.         long    ss1;            /* 16 high bits zero */
  56.         long    esp2;
  57.         long    ss2;            /* 16 high bits zero */
  58.         long    cr3;
  59.         long    eip;
  60.         long    eflags;
  61.         long    eax,ecx,edx,ebx;
  62.         long    esp;
  63.         long    ebp;
  64.         long    esi;
  65.         long    edi;
  66.         long    es;             /* 16 high bits zero */
  67.         long    cs;             /* 16 high bits zero */
  68.         long    ss;             /* 16 high bits zero */
  69.         long    ds;             /* 16 high bits zero */
  70.         long    fs;             /* 16 high bits zero */
  71.         long    gs;             /* 16 high bits zero */
  72.         long    ldt;            /* 16 high bits zero */
  73.         long    trace_bitmap;   /* bits: trace 0, bitmap 16-31 */
  74.         struct i387_struct i387;
  75. };
  76.  
  77. struct task_struct {
  78. /* these are hardcoded - don't touch */
  79.         long state;     /* -1 unrunnable, 0 runnable, >0 stopped */
  80.         long counter;
  81.         long priority;
  82.         long signal;
  83.         fn_ptr sig_restorer;
  84.         fn_ptr sig_fn[32];
  85. /* various fields */
  86.         int exit_code;
  87.         unsigned long end_code,end_data,brk,start_stack;
  88.         long pid,father,pgrp,session,leader;
  89.         unsigned short uid,euid,suid;
  90.         unsigned short gid,egid,sgid;
  91.         long alarm;
  92.         long utime,stime,cutime,cstime,start_time;
  93.         unsigned short used_math;
  94. /* file system info */
  95.         int tty;                /* -1 if no tty, so it must be signed */
  96.         unsigned short umask;
  97.         struct m_inode * pwd;
  98.         struct m_inode * root;
  99.         unsigned long close_on_exec;
  100.         struct file * filp[NR_OPEN];
  101. /* ldt for this task 0 - zero 1 - cs 2 - ds&ss */
  102.         struct desc_struct ldt[3];
  103. /* tss for this task */
  104.         struct tss_struct tss;
  105. };
  106.  
  107. /*
  108.  *  INIT_TASK is used to set up the first task table, touch at
  109.  * your own risk!. Base=0, limit=0x9ffff (=640kB)
  110.  */
  111. #define INIT_TASK \
  112. /* state etc */ { 0,15,15, \
  113. /* signals */   0,NULL,{(fn_ptr) 0,}, \
  114. /* ec,brk... */ 0,0,0,0,0, \
  115. /* pid etc.. */ 0,-1,0,0,0, \
  116. /* uid etc */   0,0,0,0,0,0, \
  117. /* alarm */     0,0,0,0,0,0, \
  118. /* math */      0, \
  119. /* fs info */   -1,0133,NULL,NULL,0, \
  120. /* filp */      {NULL,}, \
  121.         { \
  122.                 {0,0}, \
  123. /* ldt */       {0x9f,0xc0fa00}, \
  124.                 {0x9f,0xc0f200}, \
  125.         }, \
  126. /*tss*/ {0,PAGE_SIZE+(long)&init_task,0x10,0,0,0,0,(long)&pg_dir,\
  127.          0,0,0,0,0,0,0,0, \
  128.          0,0,0x17,0x17,0x17,0x17,0x17,0x17, \
  129.          _LDT(0),0x80000000, \
  130.                 {} \
  131.         }, \
  132. }
  133.  
  134. extern struct task_struct *task[NR_TASKS];
  135. extern struct task_struct *last_task_used_math;
  136. extern struct task_struct *current;
  137. extern long volatile jiffies;
  138. extern long startup_time;
  139.  
  140. #define CURRENT_TIME (startup_time+jiffies/HZ)
  141.  
  142. extern void sleep_on(struct task_struct ** p);
  143. extern void interruptible_sleep_on(struct task_struct ** p);
  144. extern void wake_up(struct task_struct ** p);
  145.  
  146. /*
  147.  * Entry into gdt where to find first TSS. 0-nul, 1-cs, 2-ds, 3-syscall
  148.  * 4-TSS0, 5-LDT0, 6-TSS1 etc ...
  149.  */
  150. #define FIRST_TSS_ENTRY 4
  151. #define FIRST_LDT_ENTRY (FIRST_TSS_ENTRY+1)
  152. #define _TSS(n) ((((unsigned long) n)<<4)+(FIRST_TSS_ENTRY<<3))
  153. #define _LDT(n) ((((unsigned long) n)<<4)+(FIRST_LDT_ENTRY<<3))
  154. #define ltr(n) __asm__("ltr %%ax"::"a" (_TSS(n)))
  155. #define lldt(n) __asm__("lldt %%ax"::"a" (_LDT(n)))
  156. #define str(n) \
  157. __asm__("str %%ax\n\t" \
  158.         "subl %2,%%eax\n\t" \
  159.         "shrl $4,%%eax" \
  160.         :"=a" (n) \
  161.         :"a" (0),"i" (FIRST_TSS_ENTRY<<3))
  162. /*
  163.  *      switch_to(n) should switch tasks to task nr n, first
  164.  * checking that n isn't the current task, in which case it does nothing.
  165.  * This also clears the TS-flag if the task we switched to has used
  166.  * tha math co-processor latest.
  167. */
  168.  
  169. #define switch_to(n) {\
  170. struct {long a,b;} __tmp; \
  171. __asm__("cmpl %%ecx,_current\n\t" \
  172.         "je 1f\n\t" \
  173.         "xchgl %%ecx,_current\n\t" \
  174.         "movw %%dx,%1\n\t" \
  175.         "ljmp %0\n\t" \
  176.         "cmpl %%ecx,%2\n\t" \
  177.         "jne 1f\n\t" \
  178.         "clts\n" \
  179.         "1:" \
  180.         ::"m" (*&__tmp.a),"m" (*&__tmp.b), \
  181.         "m" (last_task_used_math),"d" _TSS(n),"c" ((long) task[n])); \
  182. }
  183.  
  184. #define PAGE_ALIGN(n) (((n)+0xfff)&0xfffff000)
  185.  
  186. #define _set_base(addr,base) \
  187. __asm__("movw %%dx,%0\n\t" \
  188.         "rorl $16,%%edx\n\t" \
  189.         "movb %%dl,%1\n\t" \
  190.         "movb %%dh,%2" \
  191.         ::"m" (*((addr)+2)), \
  192.           "m" (*((addr)+4)), \
  193.           "m" (*((addr)+7)), \
  194.           "d" (base) \
  195.         :"dx")
  196.  
  197. #define _set_limit(addr,limit) \
  198. __asm__("movw %%dx,%0\n\t" \
  199.         "rorl $16,%%edx\n\t" \
  200.         "movb %1,%%dh\n\t" \
  201.         "andb $0xf0,%%dh\n\t" \
  202.         "orb %%dh,%%dl\n\t" \
  203.         "movb %%dl,%1" \
  204.         ::"m" (*(addr)), \
  205.           "m" (*((addr)+6)), \
  206.           "d" (limit) \
  207.         :"dx")
  208.  
  209. #define set_base(ldt,base) _set_base( ((char *)&(ldt)) , base )
  210. #define set_limit(ldt,limit) _set_limit( ((char *)&(ldt)) , (limit-1)>>12 )
  211.  
  212. #define _get_base(addr) ({\
  213. unsigned long __base; \
  214. __asm__("movb %3,%%dh\n\t" \
  215.         "movb %2,%%dl\n\t" \
  216.         "shll $16,%%edx\n\t" \
  217.         "movw %1,%%dx" \
  218.         :"=d" (__base) \
  219.         :"m" (*((addr)+2)), \
  220.          "m" (*((addr)+4)), \
  221.          "m" (*((addr)+7))); \
  222. __base;})
  223.  
  224. #define get_base(ldt) _get_base( ((char *)&(ldt)) )
  225.  
  226. #define get_limit(segment) ({ \
  227. unsigned long __limit; \
  228. __asm__("lsll %1,%0\n\tincl %0":"=r" (__limit):"r" (segment)); \
  229. __limit;})
  230.  
  231. #endif

Télécharger