Vous êtes sur la page 1sur 560

Cours IN201

Anne 2010-2011

Les Systmes dExploitation

Responsable : Bertrand Collin. Auteurs : Bertrand Collin, Marc Baudoin, Manuel Bouyer, Jrme Gueydan, Thomas Degris, Frdric Loyer, Damien Mercier.

cole nationale suprieure de techniques avances

Copyright c Bertrand Collin 20092011 Ce document est mis disposition selon les termes du contrat Creative Commons Paternit - Pas dutilisation commerciale - Partage des conditions initiales lidentique 2.0 France :
http://creativecommons.org/licenses/by-nc-sa/2.0/fr/

Vous tes libre : de reproduire, distribuer et communiquer cette cration au public ; de modier cette cration. Selon les conditions suivantes : Paternit. Vous devez citer le nom de lauteur original de la manire indique par lauteur de luvre ou le titulaire des droits qui vous confre cette autorisation (mais pas dune manire qui suggrerait quils vous soutiennent ou approuvent votre utilisation de luvre). Pas dutilisation commerciale. Vous navez pas le droit dutiliser cette cration des ns commerciales. Partage des conditions initiales lidentique. Si vous modiez, transformez ou adaptez cette cration, vous navez le droit de distribuer la cration qui en rsulte que sous un contrat identique celui-ci. chaque rutilisation ou distribution de cette cration, vous devez faire apparatre clairement au public les conditions contractuelles de sa mise disposition. La meilleure manire de les indiquer est un lien vers cette page Web. Chacune de ces conditions peut tre leve si vous obtenez lautorisation du titulaire des droits sur cette uvre. Rien dans ce contrat ne diminue ou ne restreint le droit moral de lauteur ou des auteurs. Ce qui prcde naffecte en rien vos droits en tant quutilisateur (exceptions au droit dauteur : copies rserves lusage priv du copiste, courtes citations, parodie...).

Avertissement

Les auteurs du document


Ce document est luvre itrative des nombreux enseignants ayant particip au cours Systmes dexploitation de lE NSTA ParisTech depuis sa cration : mme si chacun des chapitres a t initialement crit par un seul enseignant, ceux-ci ont t ensuite relus, corrigs, complts, remanis chaque anne et il est difcile aujourdhui dattribuer avec justesse la paternit exacte des diffrentes contributions. Les auteurs des documents originaux sont cits ci-dessous, de mme que les enseignants qui ont particip au cours au l des ans, en esprant que personne nait t oubli. Les chapitres allant de lintroduction 5 ont t crits par Jrme Gueydan et mis jour par Bertrand Collin. Le chapitre 6 sinspire trs fortement de textes crits par Frdric Loyer et Pierre Fiorini et a t remis jour par Bertrand Collin. Le chapitre 7 a t crit par Manuel Bouyer et Frdric Loyer et trs lgrement mis jour par Bertrand Collin puis corrig pour la partie concernant lUEFI. Les chapitres 8 et 9 ont t rdigs par Bertrand Collin et relus par Marc Baudoin. Les chapitres 10, 11, 12 et 13 sont luvre de Manuel Bouyer, Jrme Gueydan et Damien Mercier. Les chapitres 14, 15 et 16 ont t crits par Marc Baudoin et Damien Mercier. Le chapitre 17 a t crit par Bertrand Collin et patiemment relu et corrig par Marc Baudoin et Manuel Bouyer. Les chapitres 18 et 19 ont t rdigs par Thomas Degris. Les exercices et corrigs proposs tout au long de ce document ont t conjointement crits par Marc Baudoin, Manuel Bouyer, Bertrand Collin, Thomas Degris, Jrme Gueydan et Damien Mercier. Les projets dcrits dans les chapitres 20, 21, 22, 23, 24 et 25 ont t respectivement proposs par Nicolas Isral, Olivier Perret, Damien Mercier et Bertrand Collin. Laide-mmoire du langage C propos dans le chapitre 26 a t rdig par Damien Mercier. 5

Les personnes suivantes ont par ailleurs particip la constitution de ce document loccasion de leur participation au cours Systmes dexploitation : Robert NGuyen, Rgis Cridlig, Antoine de Maricourt, Matthieu Vallet.

Anglicisme et autres barbarismes


Il est trs difcile dexpliquer le fonctionnement de linformatique, des systmes dexploitation et des ordinateurs sans employer de mots anglais ou, pire, de mots franciss partir dune racine anglaise (comme rebooter, par exemple, souvent employ en lieu et place de rinitialiser). Nous avons essay tout au long de ce document de cours dutiliser systmatiquement des mots franais en indiquant en italique le mot anglais habituellement employ. Nanmoins, certains mots anglais nont pas de traduction satisfaisante (comme bug, par exemple) et nous avons parfois prfr utiliser le mot anglais original plutt que lune des traductions ofcielles (bogue, en loccurrence). Cependant, an de ne pas tomber dans le travers fustig au paragraphe prcdent, cette utilisation des angliscismes a t strictement limite au terme initial et des traductions franaises ont t employes pour les termes drivs (par exemple, dbogueur et dbogage). Enn, certaines expressions anglaises sont parfois employes sans faire rfrence leur sens originel (comme fork, par exemple). Dans ce cas, la traduction franaise donne une expression correcte, mais plus personne ne fait le lien entre cette expression et le sujet voqu. Lessentiel tant que nous nous comprenions tous, nous avons dans ce cas-l utilis le mot anglais.

Mises jour
Les dernires mises jour concernant les sujets prsents en cours, les noncs et les exercices des travaux pratiques ou lnonc des projets gurent sur la page www consacre ce cours 1 . Tous les programmes prsents en exemple dans ce document sont disponibles via cette page www ainsi que la majorit des corrections des exercices de travaux pratiques.

Remerciements
Les auteurs remercient tous les (re)lecteurs bnvoles qui ont eu le courage dindiquer les erreurs, les imprcisions et les fautes dorthographe, ainsi que toutes les personnes qui, par le biais de discussions fructueuses, de prts douvrages ou dexplications acharnes, ont permis la ralisation de ce manuscrit. En particulier, les auteurs remercient :
1. Cette page est accessible partir de lurl http://www.ensta.fr/~bcollin/

Thierry Bernard et Jacques Le Coupanec pour les nombreuses explications sur larchitecture des ordinateurs et la programmation de la MMU, ainsi que pour la relecture attentive du chapitre 1 ; Eric Benso, Christophe Debaert et Cindy Blondeel pour la relecture minutieuse des chapitres allant de lintroduction 7 ; Rgis Cridlig pour les nombreuses prcisions apportes aux chapitres allant de lintroduction 5 ; Batrice Renaud pour la lecture attentive des chapitres allant de lintroduction 7 ainsi que pour la correction des nombreuses fautes dorthographe ; Lamine Fall et Denis Vervisch pour leur relecture patiente de lensemble du document.

Glossaire

De nombreux acronymes ou abrviations sont utiliss dans ce manuscrit. Ils sont gnralement explicits dans le texte, mais un rsum de ces termes employs parat toutefois utile.
ADSL ALU APIC ATA ATAPI BSB FSB LAPIC CISC CPU DDR DRAM DRM DVD SRAM GPT IPI IMR IRQ IRR ISR MMU MPI PCIe PIC POSIX PTE

Asymmetric Digital Subscriber Line Arithmetic and Logic Unit Advanced Programmable Interrupt Controller Advanced Technology Attachment ATA with Packet Interface Back Side Bus Front Side Bus Local Advanced Programmable Interrupt Controller Complex Instruction Set Computer Central Process Unit Double Data Rate Dynamic Random Access Memory Digital Right Management Digital Versatil Disk Static Random Access Memory Globally Unique Identier Partition Table Inter Processor Interrupts Interrupt Mask Register Interruption ReQuest Interrupt Request Register In-Service Register Memory Management Unit Message Passing Interface Peripheral Component Interconnect Express Programmable Interrupt Controler Portable Operating System Interface [for Unix] Page Table Entry 9

PVM RISC SCSI SDRAM SSII TLB UEFI USB

Parallel Virtual Machine Reduced Instruction Set Computer Small Computer System Interface Synchronous Dynamic Random Access Memory Socit de Services en Ingnierie et Informatique ou Socit de Services et dIngnierie en Informatique Translocation Lookaside Buffer Unied Extensible Firmware Interface Universal Serial Bus

10

Introduction

Pourquoi un systme dexploitation ?


Le systme dexploitation est llment essentiel qui relie la machine, compose dlments physiques comme le microprocesseur, le disque dur ou les barrettes mmoire, et lutilisateur qui souhaite effectuer des calculs. Sans systme dexploitation, chaque utilisateur serait oblig de connatre le fonctionnement exact de la machine sur laquelle il travaille et il serait, par exemple, amen programmer directement un contrleur de priphrique USB pour pouvoir enregistrer ses donnes sur une cl USB. Sans le contrle du systme dexploitation, les utilisateurs pourraient aussi dtruire irrmdiablement certaines donnes stockes sur un ordinateur, voire dtruire certains priphriques comme le disque dur en programmant des oprations illicites. Les systmes dexploitation jouent donc un rle majeur dans lutilisation dun ordinateur et si loutil informatique sest rpandu dans le monde entier, cest certes grce labaissement des prix dachat et grce laugmentation de la puissance des ordinateurs, mais cest surtout grce aux progrs raliss lors des cinquante dernires annes dans la programmation des systmes dexploitation : avec une machine de puissance quivalente, la moindre opration sur un ordinateur qui aujourdhui nous parat triviale tait alors proprement impossible raliser ! 11

Ce document prsente les systmes dexploitation ainsi que leurs composants principaux et leurs structures. Toutes les notions essentielles la comprhension du fonctionnement dun ordinateur avec son systme dexploitation sont ici abordes, mais un lecteur dsireux dapprofondir un sujet particulier doit se reporter un ouvrage spcialis, par exemple un des ouvrages cits dans la bibliographie. Les notions prsentes ici ne font en gnral rfrence aucun systme dexploitation en particulier. Nanmoins, nous avons choisi dillustrer les fonctionnalits avances des systmes dexploitation par des exemples provenant essentiellement des systmes dexploitation de type Unix. Nous justierons plus loin ce choix et un lecteur critique pourra considrer que ce choix est a priori aussi valable quun autre. Nous abordons aussi dans ce document certains sujets qui ne relvent pas directement des systmes dexploitation, comme larchitecture des ordinateurs ou la compilation de programmes, mais dont ltude permet dexpliquer plus facilement le rle dun systme dexploitation et de montrer ltendue des services quil rend.

Pourquoi tudier les systmes dexploitation ?


Avant de se lancer corps perdu dans ltude des systmes dexploitation, il est raisonnable de se demander ce que cette tude peut nous apporter. Prcisons tout dabord que ce document est lorigine le support crit dun cours propos en deuxime anne du cycle dingnieur de lE NSTA ParisTech 2 . Les raisons voques ci-dessous sadressent donc tous les tudiants non spcialiss en informatique qui exerceront rapidement des mtiers dingnieur responsabilit. Tous les ingnieurs issus de grandes coles gnralistes auront utiliser loutil informatique dans leur mtier. Bien entendu, suivant le mtier exerc ou suivant lvolution de la carrire de chacun, cette utilisation sera plus ou moins frquente et certains ne feront que tapoter de temps en temps sur leur clavier alors que dautres passeront des heures se battre avec (contre ?) la machine. Quel que soit le prol choisi, tous les ingnieurs de lE NSTA ParisTech se retrouveront dans une des trois 3 catgories suivantes. Lutilisateur : comme son nom lindique, la seule proccupation de lutilisateur est dutiliser sa machine. Son dsir le plus vif est que celle-ci se mette fonctionner normalement quand il lallume et que ses logiciels favoris lui permettent de travailler correctement. Le dcideur : il prend les dcisions vitales concernant les choix stratgiques et commerciaux de linformatique dentreprise. Cest lui qui dcidera par exemple sil vaut mieux acheter un gros ordinateur reli 50 terminaux ou sil est prfrable dacheter 50 micro-ordinateurs
2. Voir http://www.ensta.fr/~bcollin. 3. Nous pourrions ajouter dautres catgories an de distinguer certaines professions comme par exemple le conseil en informatique.

12

connects en rseau. Souvent le dcideur se fonde sur les besoins exprims par les utilisateurs pour prendre sa dcision. Le programmeur : il cherche tirer le meilleur parti de la machine quil programme tout en perdant le moins de temps possible en dveloppements. Le programmeur cherche aussi prserver ses programmes du temps qui passe et tente de les rutiliser de machine en machine an de ne pas tout recommencer chaque fois. Beaucoup dlves ingnieurs estiment spontanment que seule la 3e catgorie doit sintresser aux cours dinformatique et, notamment, aux systmes dexploitation. En fait, ceci est faux et cest mme une erreur grave.
Lutilisateur et les systmes dexploitation

Lutilisateur doit connatre le fonctionnement et les capacits des systmes dexploitation car cest pour lui lunique faon de savoir ce quil est en droit dattendre dun ordinateur. En pratique, lorsquun utilisateur exprime ses besoins, il fait souvent rfrence des logiciels (ou des machines) quil utilise dj ou dont il a entendu parler. Il est trs rare quun utilisateur exprime ses besoins en dehors de tout contexte commercial, cest--dire sans citer dexemples de logiciel ou de marque. Ce phnomne a un effet pervers : il nest pas rare quun utilisateur passe sous silence un besoin informatique soit parce quil pense que ce nest pas possible, soit parce quil ne connat pas dexemples de logiciel rpondant ce besoin. Par exemple, bon nombre dutilisateurs de PC dans les entreprises ne savent pas quun ordinateur peut fonctionner pendant de longs mois sans quil y ait besoin de le ramorcer (de le rebooter ) et, leur environnement professionnel ou commercial ne proposant aucun systme stable, ce besoin est rarement exprim. Inversement, il est fort probable que si un systme stable leur tait propos, il aurait beaucoup de succs 4 . Connatre le fonctionnement des systmes dexploitation et des ordinateurs permet donc lutilisateur dexprimer ses besoins informatiques rels et non un sous-ensemble rduit de ces besoins. Par ailleurs, connatre le fonctionnement de la machine permet gnralement dadopter la bonne attitude quand celle-ci ne fonctionne pas exactement comme on le dsire et cela vite souvent dentreprendre des actions quasi-mystiques, voire totalement sotriques, comme il arrive den voir dans certains cas... Bien entendu, pour lutilisateur, seuls les principes de fonctionnement comptent et il na pas besoin de connatre les dtails concernant tel ou tel systme dexploitation. Il est nanmoins capital dtudier un systme dexploitation complexe et efcace qui offre le plus de services possibles (comme Unix par exemple) et lutilisateur ne peut donc se contenter dtudier le systme dexploitation quil utilise habituellement 5 .
4. La propagation rapide de Windows 2000, nettement plus stable que les prcdentes moutures, et le succs mitig de Windows XP, qui napporte rien de mieux dans ce domaine, tend conrmer cette analyse. 5. Sauf si cest Unix, bien entendu !

13

Le dcideur et les systmes dexploitation

Le dcideur doit senqurir des besoins des utilisateurs et prendre la dcision dachat informatique (par exemple) adquate. La connaissance des systmes dexploitation est ici capitale pour au moins deux raisons : la rponse aux besoins des utilisateurs et la prennit des choix informatiques. Pour rpondre aux besoins des utilisateurs, le dcideur doit connatre les possibilits des machines et lensemble des logiciels disponibles (systmes dexploitation compris). Il doit aussi connatre les diffrentes possibilits matrielles, les rseaux, etc. Par ailleurs, il peut (doit ?) aider les utilisateurs mieux dnir leurs besoins en faisant abstraction des rfrences commerciales habituelles. Cette tape souvent nglige est capitale, car si les utilisateurs nexpriment pas correctement leurs besoins informatiques, le dcideur ne pourra pas prendre une dcision efcace. En supposant que notre dcideur ait russi dnir les besoins des utilisateurs dont il est responsable, il doit maintenant prendre la bonne dcision. Le matriel informatique cote beaucoup moins cher quauparavant et il est donc possible den changer souvent. Cette possibilit est trompeuse car elle laisse supposer que les choix stratgiques se font dsormais court terme, ce qui est totalement faux. Ainsi, si une entreprise dcide de changer de type dordinateur pour passer par exemple de PC Mac, il est probable que la plupart des dveloppements quelle a effectus et la plupart des donnes quelle a stockes seront perdues dans lopration. Dans le meilleur cas, cela ncessitera une phase de traduction dun format un autre, phase qui revient trs cher. Outre les cots mentionns ci-dessus, il faut aussi prendre en compte les cots de formation des utilisateurs : rares sont les utilisateurs qui sont capables de sautoformer partir des documentations des systmes dexploitation ou des logiciels nouvellement installs et il est donc ncessaire dorganiser des sessions de formation, le plus souvent sous-traites des socits de formation et gnralement trs coteuses (dun plusieurs milliers deuros par jour et par personne). Ces sessions de formation peuvent aussi engendrer une interruption du service puisque les personnels de lentreprise se retrouvent indisponibles pendant plusieurs jours (voire plusieurs semaines en temps cumul). En consquence, mme si le matriel est frquemment renouvel, la politique informatique est maintenue long terme. Le dcideur doit donc sassurer que les choix quil fait ne mettent pas son entreprise en danger 6 et quils lui permettront de sadapter aux diffrents changements dans les annes venir. Par ailleurs, il ne faut pas que les choix du dcideur lient le sort de son entreprise un fournisseur particulier, comme, par exemple, un fabricant dordinateurs : les fournisseurs savent pertinemment quil est difcile pour une entreprise de changer de type de machine et ils agissent alors en situation de quasi-monopole. Ils jouent sur le fait que cela revient moins cher lentreprise de payer trs cher ce matriel plutt que de changer de fournisseur.
6. La scurit des solutions mises en place devrait ce titre tre une proccupation majeure.

14

Une bonne solution pour assurer cette indpendance et pour rpondre aux besoins de prennit est dutiliser des systmes dexploitation toujours plus ouverts, toujours plus efcaces, qui voluent rapidement et qui sont ports sur de nombreuses architectures diffrentes. Ce mme raisonnement doit tre tenu pour les applications sur serveurs (messagerie, gestionnaire de bases de donnes, serveurs web, etc.), pour les applications dites mtier (outil de gestion nancire, outil de gestion des ressources humaines, logiciel de publication assiste par ordinateur, etc.) et pour les applications de bureautique (traitement de textes, tableur, outil de prsentation, etc.).
Le programmeur et les systmes dexploitation

La premire proccupation du programmeur est de ne pas refaire ce qui a dj t fait, cest--dire de ne pas perdre de temps (r)crire du code qui existe dj. La connaissance des systmes dexploitation est bien entendu extrmement importante dans ce cas-l et ceux-ci se prsentent alors comme des bibliothques de services disponibles. Cette vision des systmes dexploitation, mme si elle a longtemps perdur, est cependant primitive et tout bon programmeur doit prendre en compte deux autres problmes : le portage de ses programmes et le choix du systme dexploitation utiliser. Le portage dun programme est lensemble des actions permettant dadapter un programme dvelopp sur une machine donne avec un systme dexploitation donn une autre machine dote ventuellement dun autre systme dexploitation. Si le portage seffectue immdiatement (ou au moins simplement), le programme est dit portable (les personnes peu soucieuses de la langue franaise parlent parfois de portabilit ). Le portage dapplications est un des principaux problmes des entreprises actuelles, notamment des SSII, et les cots de portage dune application nayant pas t correctement conue ds le dpart sont gnralement normes. Un des intrts des systmes dexploitation est justement de rendre les programmes plus facilement portables, en sintercalant entre la ralit physique des machines et les programmes des utilisateurs. Par exemple, en crivant des programmes en langage C, le programmeur na pas besoin de se soucier de la machine sur laquelle il travaille et il pourra probablement porter son programme sur dautres machines. Notons que ce nest pas une garantie car beaucoup de constructeurs de machines proposent des bibliothques de fonctions ou des facilits propres leurs machines (an de dliser les programmeurs, voir discussion sur le dcideur). Outre les spcicits des constructeurs, le programmeur est confront aux diffrences des systmes dexploitation qui ne proposent pas tous la mme interface. Un programme en C fonctionnant sous Unix, par exemple, a peu de chance de fonctionner sous Windows (et rciproquement). Un effort de standardisation a t ralis pour faciliter les portages et une norme a t tablie (Posix). En attendant que tous les systmes 15

dexploitation proposent la mme interface 7 , le choix dun systme dexploitation est donc crucial et, en particulier, il est prfrable de choisir le systme qui assure le portage le plus facile sur le maximum de machines et de systmes dexploitation. Notons que le choix dun systme dexploitation ne doit pas tre confondu avec le choix de la machine 8 sur laquelle ce systme fonctionne et, mme si la plupart des constructeurs de machine tentent dimposer leur propre systme en prtendant quaucun autre systme ne fonctionne sur les machines en question, le systme dexploitation est un programme comme un autre : ce titre, il se doit dtre portable et ce serait une aberration de choisir un systme dexploitation non portable pour dvelopper des applications portables...

Plan du document
Ce document se compose de 26 chapitres rpartis en 4 parties : une partie de cours gnral (chapitre 2 9) ; une partie traitant particulirement de la programmation systme en C sous Unix (chapitre 10 19) ; une partie proposant des projets informatiques (chapitre 20 25) ; une partie consistant en une aide-mmoire du langage C (chapitre 26).
Premire partie : les systmes dexploitation

Cette partie est gnrale tous les systmes dexploitation et, mme si certains exemples particuliers sont choisis pour illustrer le propos, les principes sappliquent lessentiel des systmes dexploitation. De nombreux rappels sur larchitecture des ordinateurs sont effectus dans le chapitre Rappels sur larchitecture des ordinateurs et laccent est particulirement mis sur tous les composants des ordinateurs sans lesquels il serait trs difcile de dvelopper un systme dexploitation efcace. Une section de ce chapitre aborde un sujet un peu diffrent qui, cependant, a trait la fois aux systmes dexploitation et larchitecture des ordinateurs : la mesure des performances des ordinateurs. Le chapitre Quest-ce quun systme dexploitation ? est consacr aux principales dnitions des systmes dexploitation et leur structure. Nous y annonons en particulier quelles sont les tches quun systme dexploitation doit assurer et nous esquissons les choix faire pour assurer un fonctionnement efcace dun ordinateur. Lhistorique des systmes dexploitation est dress au chapitre volution des systmes dexploitation , ainsi que celui des ordinateurs : ordinateurs et systmes dexploitation se sont dvelopps ensemble et il nest pas possible de prsenter lun sans lautre. Cet historique permet de constater que les principes des systmes dexploitation
7. En supposant que cela arrive un jour... 8. Ce choix est aussi trs important.

16

nont pas t tablis en une fois et que ce domaine a beaucoup volu et volue encore. la n de ce chapitre, nous passons en revue les systmes dexploitation actuels. Le chapitre Compilation et dition de liens prsente rapidement les principes de la compilation de programmes. Mme si ce sujet ne fait pas partie de ltude des systmes dexploitation, il est prfrable de laborder an de faciliter lexplication de la gestion des processus et de la gestion de la mmoire. Ces explications seront respectivement abordes aux chapitres La gestion des processus et La gestion de la mmoire . Le chapitre Le systme de chiers sera consacr au systme de chiers qui, avec la gestion des processus et la gestion de la mmoire, est un lment essentiel des systmes dexploitation modernes. Comme les lecteurs sont gnralement trs familiers avec cet lment qui reprsente nalement labstraction ultime dun ensemble de donnes numriques, nous avons repouss son explication vers la n de ce document. Les chapitres Les architectures multi-processeurs et les threads et La virtualisation des systmes dexploitation ont t introduits rcemment. Avec larrive massive des processeurs multi-curs dans les stations de travail et les serveurs, il tait indispensable de dcliner les diffrents concepts vus prcdemment dans le cadre dune architecture parallle. Toutefois il faut garder lesprit que le chapitre Les architectures multi-processeurs et les threads ne dispense en aucun cas de suivre assidument le cours IN203 Paralllisme car il ne donne quune partie des notions indispensables la comprhension complte des threads. Le chapitre La virtualisation des systmes dexploitation se veut avant tout une introduction la notion de virtualisation dun systme dexploitation. Il permettra aux lecteurs de dcouvrir les fondements mais aussi les diffrentes mthodes employes pour virtualiser un serveur ainsi que les avantages et inconvnients de chacune de ces techniques. Enn il se veut aussi un moyen de dcouvrir la terminologie employe an de saisir rapidement, lorsquune dcision simpose, les offres proposes sur le march actuel.

Deuxime partie : la programmation systme en C sous Unix

La deuxime partie concerne uniquement le systme dexploitation Unix. La plupart des concepts abords dans la premire partie sont ici mis en uvre par le biais de travaux pratiques de programmation systme en langage C. Les exercices proposs sont en gnral fournis avec leurs corrections et ils permettent de passer en revue lessentiel des appels systme Posix fournis par le systme dexploitation Unix. Cette partie est compose des chapitres suivants : gestion des processus, cration de processus, recouvrement de processus, entres / sorties et systme de chiers, utilisation des signaux, communication entre processus par tuyau, gestion des entres / sorties synchrones, communication entre machines par socket et, enn, utilisation dun dbogueur. 17

Troisime partie : sujets des projets

La programmation de plusieurs projets est propose dans cette partie. Les sujets sont assez ouverts et ils permettent chacun de dvelopper sa propre stratgie pour rpondre au problme. Quelle que soit la voie choisie, la rsolution de ce problme sera fonde sur les principes dtaills dans les deux parties prcdentes et, en particulier, le dveloppeur des projets devra affronter, sous une forme lgrement diffrente, certains problmes qui sont habituellement rsolus par les systmes dexploitation.
Quatrime partie : aide-mmoire du langage C

Cet aide-mmoire nest ni un manuel pour apprendre le langage C, ni un manuel de programmation. Il contient simplement de faon synthtique mais prcise toutes les informations permettant de faciliter la rsolution des exercices proposs en travaux pratiques. En particulier, cet aide-mmoire dcrit une partie des fonctions de la bibliothque standard dentres / sorties du langage C. Signalons au lecteur que sattaquer la programmation systme en C sous Unix sans matriser le langage C ou sans savoir ce dont il est capable relve du d ! Dans tous les cas, une (re)lecture de cet aide-mmoire ne peut faire de mal personne.

Les sujets qui ne sont pas traits dans ce document


Il nest pas possible daborder tous les sujets concernant les systmes dexploitation dans un document synthtique et encore moins dans un cours de 2e anne du cycle dingnieur. Les sujets suivants, bien quimportants, ne seront donc pas traits dans ce document : les systmes dexploitation temps rel ; les systmes embarqus ; les problmes lis au paralllisme et aux systmes distribus (ils sont abords de manire supercielle au sein du chapitre Les architectures multi-processeurs et les threads ; lutilisation de rseaux locaux 9 ou internationaux ; la scurit des systmes dinformation (ce sujet sera abord durant le dernier cours dun point de vue macroscopique).

9. Une introduction ce problme est nanmoins propose dans le chapitre 16.

18

Table des matires

Avertissement Glossaire Introduction Pourquoi un systme dexploitation ? . . . . . . . . Pourquoi tudier les systmes dexploitation ? . . . Plan du document . . . . . . . . . . . . . . . . . . Les sujets qui ne sont pas traits dans ce document Table des matires

5 9 11 11 12 16 18 19

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

I Les systmes dexploitation


1 Rappels sur larchitecture des ordinateurs 1.1 Reprsentation symbolique et minimaliste dun ordinateur 1.2 Reprsentation fonctionnelle dun ordinateur . . . . . . . 1.3 Mode noyau versus mode utilisateur . . . . . . . . . . . . 1.4 Le jeu dinstructions . . . . . . . . . . . . . . . . . . . . 1.5 Rle de lunit de gestion de mmoire . . . . . . . . . . . 1.6 Performances des ordinateurs . . . . . . . . . . . . . . . . 1.7 Conclusion : que retenir de ce chapitre ? . . . . . . . . . . Quest-ce quun systme dexploitation ? 2.1 Dnitions et consquences . . . . . . . . . . . 2.2 Les appels systme . . . . . . . . . . . . . . . 2.3 Structure dun systme dexploitation . . . . . 2.4 Les diffrents types de systmes dexploitation 2.5 Les services des systmes dexploitation . . . . 2.6 Conclusion : que retenir de ce chapitre ? . . . . 19 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

25
27 27 28 40 41 45 47 53 55 56 58 60 64 66 67

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

volution des systmes dexploitation 3.1 Les origines et les mythes (16xx1940) . . . . 3.2 La prhistoire (19401955) . . . . . . . . . . . 3.3 Les ordinateurs transistor (19551965) . . . . 3.4 Les circuits intgrs (19651980) . . . . . . . 3.5 Linformatique moderne (19801995) . . . . . 3.6 Les systmes dexploitation daujourdhui . . . 3.7 Conclusion : que faut-il retenir de ce chapitre ? Compilation et dition de liens 4.1 Vocabulaire et dnitions . . . . . . . . . . . 4.2 Les phases de compilation . . . . . . . . . . 4.3 Ldition de liens . . . . . . . . . . . . . . . 4.4 Structure des chiers produits par compilation 4.5 Les problmes dadresses . . . . . . . . . . . 4.6 Les appels systme . . . . . . . . . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

69 70 70 74 79 85 86 96 97 100 104 111 115 117 119 121 122 126 132 139 149 151 152 157 162 165 168 169 170 176 182 185

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

La gestion des processus 5.1 Quest-ce quun processus ? . . . . . . . . . . . 5.2 La hirarchie des processus . . . . . . . . . . . . 5.3 Structures utilises pour la gestion des processus 5.4 Lordonnancement des processus (scheduling) . . 5.5 Conclusion . . . . . . . . . . . . . . . . . . . . La gestion de la mmoire 6.1 Les adresses virtuelles et les adresses physiques 6.2 Les services du gestionnaire de mmoire . . . . 6.3 La gestion de la mmoire sans pagination . . . 6.4 La pagination . . . . . . . . . . . . . . . . . . 6.5 Conclusion . . . . . . . . . . . . . . . . . . . Le systme de chiers 7.1 Les services dun systme de chier . 7.2 Lorganisation des systmes de chiers 7.3 La gestion des volumes . . . . . . . . 7.4 Amliorations des systmes de chiers

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

Les architectures multi-processeurs et les threads 191 8.1 De la loi de Moore au multi-curs . . . . . . . . . . . . . . . . . . . 191 8.2 Les threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199 8.3 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210 La virtualisation des systmes dexploitation 20 211

9.1 9.2 9.3

Les intrts et les enjeux de la virtualisation . . . . . . . . . . . . . . 212 Les diffrentes solutions . . . . . . . . . . . . . . . . . . . . . . . . 216 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228

II Programmation systme en C sous Unix


10 Les processus sous Unix 10.1 Introduction . . . . . . . . . . . . . . . . . 10.2 Les entres / sorties en ligne . . . . . . . . 10.3 Les fonctions didentication des processus 10.4 Exercices . . . . . . . . . . . . . . . . . . 10.5 Corrigs . . . . . . . . . . . . . . . . . . . 10.6 Corrections dtailles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

229
231 231 231 235 236 238 239 245 246 246 253 258 259 261 267 267 268 269 270 273

11 Les entres / sorties sous Unix 11.1 Les descripteurs de chiers . . . . . . . . . . . . . . . 11.2 Les appels systme associs aux descripteurs de chier 11.3 La bibliothque standard dentres / sorties . . . . . . 11.4 Exercices . . . . . . . . . . . . . . . . . . . . . . . . 11.5 Corrigs . . . . . . . . . . . . . . . . . . . . . . . . . 11.6 Corrections dtailles . . . . . . . . . . . . . . . . . . 12 Cration de processus sous Unix 12.1 La cration de processus . . 12.2 Lappel systme wait() . . 12.3 Exercices . . . . . . . . . . 12.4 Corrigs . . . . . . . . . . . 12.5 Corrections dtailles . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

13 Recouvrement de processus sous Unix 289 13.1 Les appels systme de recouvrement de processus . . . . . . . . . . . 289 13.2 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 291 13.3 Corrigs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293 14 Manipulation des signaux sous Unix 14.1 Liste et signication des diffrents signaux . 14.2 Envoi dun signal . . . . . . . . . . . . . . 14.3 Interface de programmation . . . . . . . . . 14.4 Conclusion . . . . . . . . . . . . . . . . . 14.5 Exercices . . . . . . . . . . . . . . . . . . 14.6 Corrigs . . . . . . . . . . . . . . . . . . . 15 Les tuyaux sous Unix 21 297 297 298 299 302 303 304 307

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

15.1 15.2 15.3 15.4

Manipulation des tuyaux Exercices . . . . . . . . Corrigs . . . . . . . . . Corrections dtailles . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

308 318 320 329 347 347 347 347 348 351 363 363 370 371 379 381 381 382 383 385

16 Les sockets sous Unix 16.1 Introduction . . . . . . . . . . . . . . . 16.2 Les RFC request for comments . . . . . 16.3 Technologies de communication rseau 16.4 Le protocole IP . . . . . . . . . . . . . 16.5 Interface de programmation . . . . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

17 Les threads POSIX sous Unix 17.1 Prsentation . . . . . . . . . . . . . . . . 17.2 Exercices . . . . . . . . . . . . . . . . . 17.3 Corrigs . . . . . . . . . . . . . . . . . . 17.4 Architectures et programmation parallle 18 Surveillances des entres / sorties sous Unix 18.1 Introduction . . . . . . . . . . . . . . . 18.2 Lappel systme select() . . . . . . . 18.3 Exercices . . . . . . . . . . . . . . . . 18.4 Corrigs . . . . . . . . . . . . . . . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

19 Utilisation dun dbogueur 393 19.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 393 19.2 gdb, xxgdb, ddd et les autres . . . . . . . . . . . . . . . . . . . . . . 394 19.3 Utilisation de gdb . . . . . . . . . . . . . . . . . . . . . . . . . . . . 394

III Projets
20 Projet de carte bleue 20.1 Introduction . . . . . . . . . . . . . . . . . 20.2 Cahier des charges . . . . . . . . . . . . . 20.3 Conduite du projet . . . . . . . . . . . . . 20.4 volutions complmentaires et optionnelles 20.5 Annexes . . . . . . . . . . . . . . . . . . . 21 Le Scaphandre et le Papillon 21.1 nonc . . . . . . . . . . 21.2 Contraintes gnrales . . . 21.3 Contraintes techniques . . 21.4 Services mettre en uvre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

407
409 409 411 416 420 421 427 427 427 428 429

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

22

21.5 Prcisions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 431 21.6 Droulement du projet . . . . . . . . . . . . . . . . . . . . . . . . . 431 22 Projet de Jukebox 22.1 nonc . . . . . . . . . . . . 22.2 Outils utiliss . . . . . . . . . 22.3 Commandes de mpg123 . . . . 22.4 Les ID3-tag . . . . . . . . . . 22.5 Suggestion dun plan daction 433 433 433 434 437 437 439 439 440 441 444 444 445 447 448 455 456 461 465 469

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

23 Administration distante 23.1 Position du problme . . . . . . 23.2 Le processus Machine . . . . . 23.3 Le processus Administrateur . 23.4 Linterface . . . . . . . . . . . . 23.5 Les dialogues . . . . . . . . . . 23.6 Consignes et cahier des charges 23.7 Amliorations possibles . . . . . 23.8 Aide mmoire . . . . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

24 Parcours multi-agents dans un labyrinthe 24.1 Position du problme . . . . . . . . . 24.2 Lorganisation gnrale . . . . . . . . 24.3 Mise en uvre du projet . . . . . . . 24.4 Conseils en vrac . . . . . . . . . . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

25 Course de voitures multi-threads 471 25.1 Prsentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 471 25.2 Cahier des charges . . . . . . . . . . . . . . . . . . . . . . . . . . . 476 25.3 Annexes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 479

IV Laide-mmoire du langage C
26 Aide-mmoire de langage C 26.1 lments de syntaxe . . . . . . . . . . . . . . . . . . . . . . . . . . 26.2 La compilation sous Unix . . . . . . . . . . . . . . . . . . . . . . . . 26.3 Types, oprateurs, expressions . . . . . . . . . . . . . . . . . . . . . 26.4 Tests et branchements . . . . . . . . . . . . . . . . . . . . . . . . . . 26.5 Tableaux et pointeurs . . . . . . . . . . . . . . . . . . . . . . . . . . 26.6 Le prprocesseur . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26.7 Fonctions dentre/sortie . . . . . . . . . . . . . . . . . . . . . . . . 26.8 Complments : la bibliothque standard et quelques fonctions annexes 23

487
489 491 493 500 514 518 527 531 534

Index Gnral Index Programmation Bibliographie

553 557 559

24

Premire partie Les systmes dexploitation

1
Rappels sur larchitecture des ordinateurs

Nous nous limitons dans ce chapitre fournir les lments permettant de comprendre le fonctionnement dun ordinateur et le rle dun systme dexploitation. Certains aspects de larchitecture des ordinateurs sont volontairement simplis et il est prfrable pour toute prcision sur le fonctionnement dun ordinateur de se reporter un cours spcialis. Mots cls de ce chapitre : microprocesseur, processeur, mmoire, entres/sorties, bus, registres, unit arithmtique et logique (ALU, Arithmetic and Logic Unit), unit de gestion de mmoire (MMU, Memory Management Unit), cache, coprocesseur, horloge, priphriques, contrleur de priphriques, interruptions, jeu dinstructions, adresse physique, adresse virtuelle, page, segment.

1.1

Reprsentation symbolique et minimaliste dun ordinateur

Un ordinateur peut tre reprsent symboliquement de la faon suivante (voir gure 1.1) : il se compose dun microprocesseur (souvent abrg en processeur) qui effectue les calculs, dune unit de mmoire volatile qui permet de stocker les donnes et dun certain nombre de priphriques qui permettent deffectuer des entres / sorties sur des 27

Chapitre 1. Rappels sur larchitecture des ordinateurs supports non volatiles comme un disque dur, une bande magntique ou un CD-ROM. Ces composants dialoguent entre eux par lintermdiaire dun bus de communication.

Processeur Mmoire Entres Sorties

Bus

F IGURE 1.1 Reprsentation symbolique et minimaliste dun ordinateur.

En ralit, la structure dun ordinateur est bien entendu plus complexe, mais le fonctionnement peut se rsumer cette reprsentation symbolique.

1.2

Reprsentation fonctionnelle dun ordinateur

An de bien comprendre les consquences de larchitecture des ordinateurs sur le dveloppement des systmes dexploitation, nous allons adopter une reprsentation lgrement plus complique : larchitecture lmentaire dun ordinateur reprsente sur la gure 1.2. Elle se compose dun microprocesseur, dune horloge, de contrleurs de priphriques, dune unit de mmoire principale, dune unit de mmoire cache et de plusieurs bus de communication.
Le microprocesseur

Le microprocesseur est lui-mme compos de nombreux lments. Nous citerons ici les principaux. - Les registres : ce sont des lments de mmorisation temporaire. Ils sont utiliss directement par les instructions (voir section 1.4) et par les compilateurs pour le calcul des valeurs des expressions et pour la manipulation des variables. - Lunit arithmtique et logique : gnralement nomme ALU (Arithmetic and Logic Unit), elle effectue toutes les oprations lmentaires de calcul. - Lunit de gestion de la mmoire : gnralement nomme MMU (Memory Management Unit), elle contrle les oprations sur la mmoire et permet dutiliser des adresses virtuelles pour accder une donne en mmoire (voir section 1.5). - Le cache primaire : cette unit de mmorisation temporaire fonctionne sur le mme principe que le cache secondaire (voir section 1.2). 28

1.2. Reprsentation fonctionnelle dun ordinateur

F IGURE 1.2 Reprsentation fonctionnelle dun ordinateur.

- Le coprocesseur : le microprocesseur peut contenir plusieurs coprocesseurs comme, par exemple, le coprocesseur ottant qui effectue tous les calculs en nombre non entiers (i.e. de type oat). Autrefois optionnel sur les ordinateurs, il est aujourdhui systmatiquement prsent. - Le bus interne : il permet aux diffrents lments du processeur de communiquer entre eux. Une partie importante des processeurs dont nous navons pas parl ici est celle concernant le contrle des oprations sur les lments le constituant et le dcodage des instructions.
Lhorloge

Le processeur est un composant synchrone, cest--dire quil a besoin dune horloge pour contrler la cadence des oprations quil excute. Lhorloge contraint donc la vitesse de fonctionnement du processeur et il est frquent que le processeur fonctionne moins vite quil ne le pourrait. En se fondant sur cette remarque, certains fabricants peu scrupuleux augmentent leurs bnces en associant des processeurs prvus pour fonctionner (par exemple) 750 MHz avec des horloges 1 GHz. Cette manipulation (appele overclocking) rduit souvent de beaucoup la dure de vie du processeur car 29

Chapitre 1. Rappels sur larchitecture des ordinateurs la dissipation de chaleur qui en rsulte est bien plus importante. Dautre part, aprs chaque coup dhorloge, les transitions qui affectent chaque ligne dans le processeur doivent se stabiliser. Changer la frquence dhorloge peut mettre en pril la stabilit du processeur et conduire des dysfonctionnements. Sur certains PC anciens, il ntait pas rare de trouver un bouton turbo permettant daugmenter la cadence de lhorloge. Nanmoins, ces PC fonctionnaient sur le principe inverse : la vitesse de lhorloge tait en fait rduite pour des raisons de compatibilit avec certains priphriques obsoltes et laugmentation ramenait lhorloge la vitesse normale. Ce mme principe est aujourdhui appliqu de faon automatique sur les processeurs des ordinateurs portables de dernire gnration qui, en cas dinactivit, abaissent leur vitesse de fonctionnement an dconomiser de lnergie et dissiper moins de chaleur. La vitesse de transfert des bus de communication et de la plupart des priphriques est aussi rgie par une horloge, mais il est frquent que deux horloges diffrentes soient utilises (ce qui permet aux vendeurs peu scrupuleux de tromper le client naf voir section 1.2). Il est enn important de comparer ce qui est comparable ! La vitesse de lhorloge ne doit servir comparer que des processeurs de la mme famille. Lannonce faite par Apple de commercialiser le Power Mac 8100, lordinateur possdant la plus haute frquence dhorloge (110 MHz compars aux 100 MHz des processeurs Intel), tait assez douteuse puisque le PowerPC 601 et le Pentium taient structurs diffremment et nutilisaient pas les mmes jeux dinstructions. De nos jours, les processeurs multiplient gnralement la vitesse de lhorloge par un facteur de 10 16 pour fonctionner plus rapidement en interne. Cest--dire que, par exemple, pour une horloge 150 MHz, le processeur fonctionnera en interne 1500 ou 2400 MHz. Ceci nest efcace que si le processeur na pas besoin de communiquer avec le cache secondaire, la mmoire principale ou les entres / sorties qui, eux, sont cadencs des vitesses moindres (voir aussi section 1.2).
La mmoire principale

La mmoire principale (autrefois appele mmoire vive ) est gnralement constitue de barrettes de type DRAM (Dynamic Random Access Memory). Ces mmoires doivent tre rafrachies rgulirement cest--dire quil faut sans cesse lire puis rcrire les donnes quelles contiennent 1 . Par ailleurs, il est indispensable de rcrire systmatiquement la donne que lon vient de lire. Ce travail de rafrachissement est pris en charge de manire transparente par le contrleur de mmoire.
1. Ce phnomne est li lutilisation de pico-condensateurs dont les courants de fuite provoquent la perte de charge.

30

1.2. Reprsentation fonctionnelle dun ordinateur Il existe des mmoires de type SRAM (Static Random Access Memory) qui ne ncessitent pas de rafrachissement. Malheureusement, elles sont beaucoup plus chres... Les nouvelles technologies chassant les anciennes, on trouve maintenant de la SDRAM (Synchronous Dynamic Random Access Memory). Les mmoires de type DRAM ncessitaient un temps dattente pour tre certain que les donnes retaient bien le changement introduit. Ce nest plus le cas avec les SDRAM qui attendent les fronts dhorloge pour prendre en compte les signaux dentre. Citons galement les DDR SDRAM (Double Data Rate) qui fonctionnent de manire synchrone sur le front montant et le front descendant de lhorloge. Les DDR actuelles (DDR-600) peuvent assurer un dbit de 4,8 Gio/s. La mmoire est divise en zones lmentaires qui reprsentent la plus petite quantit qui peut tre stocke. Gnralement, cette quantit est de 1 octet et la mmoire est alors divise en un grand nombre doctets contigus 2 que lon numrote dans un ordre croissant. Pour accder une donne, on parle alors de ladresse de cette donne en mmoire, cest--dire du numro de loctet dans lequel est stocke la donne ou le dbut de la donne si celle-ci occupe plusieurs octets. Une donne stocke en mmoire peut donc tre lue condition de connatre son adresse et sa taille, cest--dire le nombre doctets lire. Notons ce propos que, suivant les modles de processeur, les donnes dont la taille dpasse loctet ne sont pas toujours ranges de la mme faon en mmoire (pour une mme adresse) 3 .
La mmoire cache

La mmoire cache 4 (parfois appel simplement le cache ) est une zone de mmoire de petite taille, plus rapide que la mmoire principale et situe plus prs du processeur. Cette mmoire cache est utilise comme intermdiaire pour tous les accs la mmoire principale : chaque fois que le processeur dsire accder une donne stocke en mmoire principale, il lit en fait celle-ci dans la mmoire cache, ventuellement aprs que cette donne a t recopie de la mmoire principale vers le cache sil savre quelle ntait pas prsente dans le cache au moment de la lecture. La mmoire cache tant de taille rduite, il nest pas possible de conserver ainsi toutes les donnes auxquelles accde le processeur et, gnralement, les donnes les plus anciennes sont alors crases par les donnes qui doivent tre recopies. Cette stratgie est efcace si le processeur lit plusieurs fois la mme donne dans un laps de temps trs court ou sil utilise peu de donnes :
2. La mmoire tant compose de plusieurs barrettes, ces octets ne sont pas vritablement contigus. 3. La norme IEEE 754 (http://floating-point-gui.de pour aller plus loin) donne la reprsentation suivante pour un nombre rel virgule ottante. Un nombre rel est enregistr dans un mot de 32 bits (soit 4 octets). Le bit de poids fort donne le signe s du rel. Les 8 bits qui suivent donnent lexposant x et les 23 bits de poids faible donnent la partie signicative m (parfois appele mantisse). On obtient la valeur du rel par r = (1)s (1 + m 223 2x127 ) 4. Le terme exact est mmoire tampon , mais le terme cache est si couramment utilis que certains informaticiens ne comprennent plus le terme de mmoire tampon .

31

Chapitre 1. Rappels sur larchitecture des ordinateurs lors du premier accs la donne, celle-ci ne se trouve pas dans la mmoire cache et elle est alors copie de la mmoire principale vers la mmoire cache ; lors des accs suivants, la donne est dj prsente dans la mmoire cache et il nest pas ncessaire daccder la mmoire principale (qui est beaucoup plus lente). Notons que cette stratgie peut se rvler trs efcace, notamment lorsque lordinateur est utilis pour des calculs scientiques : la multiplication de deux matrices est un exemple typique de calcul o il est ncessaire daccder plusieurs fois aux mmes donnes. Inversement, cette stratgie est parfois trs mauvaise, en particulier lors daccs squentiels des donnes, comme par exemple le parcours de tableaux. Mme si aujourdhui les compilateurs ont fait de nombreux progrs quant lutilisation du cache, tout bon programmeur doit faire attention lors de la conception de programmes ne pas utiliser lcriture la plus dfavorable la politique de cache (par exemple, en cas dutilisation de tableaux plusieurs dimensions)... En pratique, le processeur ne peut pas savoir lavance si la donne quil cherche se trouve ou ne se trouve pas dans la mmoire cache et la copie de la donne partir de la mmoire principale ne peut donc pas tre effectue de faon prventive. Une stratgie de tentative / chec est alors employe et nous verrons que cette stratgie intervient de nombreux endroits dans la conception des ordinateurs et des systmes dexploitation : le processeur lit la donne dans le cache ; si la donne nest pas prsente, un dfaut 5 de cache a lieu (cache fault 6 en anglais) ; ce dfaut de cache dclenche alors une action prdtermine, en loccurrence la copie de la donne de la mmoire principale vers la mmoire cache ; la donne peut alors tre lue par le processeur. Lchange de donnes entre la mmoire cache et la mmoire principale est similaire lchange de donnes entre la mmoire principale et la mmoire secondaire (gnralement le disque dur) qui sera abord dans le chapitre sur la gestion de la mmoire. Le mme principe est aussi employ entre le cache primaire, log directement au sein du processeur, et le cache secondaire, log lextrieur. La plupart des processeurs possdent mme plusieurs zones de mmoire cache interne, parfois appeles cache de niveau 1, cache de niveau 2, etc. La plupart des priphriques et des contrleurs de priphriques possdent aussi de la mmoire cache pour acclrer les transferts et ce principe est donc trs gnrique. Pourquoi utiliser cinq zones diffrentes de mmoire (registres, cache primaire, cache secondaire, mmoire principale et mmoire secondaire) ? On pourrait en effet imaginer que le processeur qui a besoin dune donne aille directement la chercher dans
5. Croire quun dfaut de cache traduit un mauvais fonctionnement de lordinateur ou du systme dexploitation serait donc une grossire erreur. Nous verrons plus loin dautres dfauts qui apparaissent frquemment, comme les dfauts de page. 6. Le terme fault est souvent traduit par faute . Le terme dfaut est nanmoins plus appropri.

32

1.2. Reprsentation fonctionnelle dun ordinateur la mmoire principale sans passer par le cache. Il y a en fait deux raisons intimement lies qui justient ce choix : le temps daccs une donne et le prix de la mmoire. Il est trs difcile de construire des mmoires qui soient la fois rapides et de grande taille. Par ailleurs, le prix de loctet de mmoire dpend trs fortement et de faon non linaire de sa rapidit. Se limiter une seule zone de mmoire revient donc faire un choix dnitif : des accs rapides une tout petite quantit de mmoire ou des accs lents une grande quantit de mmoire. Lide consiste donc utiliser des zones mmoires trs rapides pour stocker quelques donnes utilises trs souvent et des zones mmoires moins rapides pour les autres. On parle alors de hirarchie des mmoires. Le tableau 1.1 rsume les quantits de mmoire gnralement utilises et les temps daccs aux donnes stockes dans ces mmoires. Outre le temps daccs nominal ces zones mmoire, il faut aussi prendre en compte les temps daccs aux diffrents intermdiaires. Par exemple, le processeur accde directement au cache primaire, mais doit passer par le bus externe et le contrleur de cache pour accder au cache secondaire. Nom Taille Temps daccs Dbit registres 1 Ko 0.25 ns 32 64 Go/s L1 64 Ko 1 ns 32 Go/s L2 1 Mo 2 ns 16 Go/s RAM 2 Go 5 70 ns 12 Go/s Swap 4 Go 20 ms 300 Mo/s

TABLE 1.1 Quantits et temps daccs typiques des mmoires utilises sur un ordinateur. La mmoire cache L1 est situ lintrieur du microprocesseur et utilise la mme horloge que le processeur, ce qui en fait une mmoire laccs trs rapide. La mmoire cache L2 peut tre situe lintrieur ou partage entre les deux curs (dans le cas dun double cur). Elle est lgrement moins rapide. En fait ces deux accs dpendent fortement de lemplacement et du mode daccs ; si le processeur utilise un bus systme pour accder au cache L2, naturellement les performances sont revoir la baisse.

Les contrleurs de priphriques

En ralit, le processeur ne communique pas directement avec les priphriques : ceux-ci sont relis des contrleurs de priphriques et cest avec eux que le processeur dialogue. Un contrleur peut grer plusieurs priphriques et le processeur na pas besoin de connatre prcisment ces priphriques : sil souhaite, par exemple, crire une donne sur le disque dur, il le demande au contrleur de disque dur et cest ce dernier qui se dbrouille pour effectivement satisfaire la demande du processeur. Le processeur transmet alors la donne crire au contrleur, le contrleur la stocke dans une mmoire tampon quil possde et la transmettra au disque dur. 33

Chapitre 1. Rappels sur larchitecture des ordinateurs Ce relais de linformation par des contrleurs permet dj de sabstraire des spcicits des priphriques : une partie de ces spcicits nest connue que du contrleur. Cela permet par exemple de dvelopper des priphriques trs diffrents les uns des autres sans quil y ait de problme majeur pour les insrer dans un ordinateur. Cela permet aussi de renouveler les priphriques dun ordinateur sans avoir changer quoi que ce soit (si ce nest le priphrique en question, bien entendu !). Parmi les contrleurs utiliss dans les ordinateurs, nous citerons le contrleur SCSI (Small Computer System Interface) sur lequel il est possible de connecter indiffremment des disques durs, des lecteurs de CDROM, des lecteurs de bandes magntiques et bien dautres priphriques encore. Pour le processeur, et donc pour tous les programmes dun ordinateur, laccs un disque dur SCSI est parfaitement identique laccs un lecteur de CDROM SCSI : seule la spcicit du contrleur SCSI importe. Les priphriques SCSI sont cependant un peu plus chers que les priphriques IDE (appels aussi priphriques ATA ou ATAPI ou encore SATA) et ces derniers, bien que moins universels et souvent moins performants sont donc plus rpandus. La gnralisation des priphriques USB ou Firewire va peut-tre permettre de trouver un juste compromis. Quels sont les priphriques dun ordinateur ? En fait, il ny a pas de limite stricte et on peut considrer que tous les composants dun ordinateur qui ne sont pas regroups dans le processeur sont des priphriques. ce titre, la mmoire principale est un priphrique comme les autres et elle est gre par un contrleur. Pour claircir un peu les ides, voici la liste des priphriques quun ordinateur moderne 7 doit possder : - Un clavier : difcile de faire quoi que ce soit sans clavier ; - Une souris : la souris est apparue assez rcemment et parat aujourdhui absolument indispensable. Elle a peu volu et se prsente parfois sous forme de boule, de levier (joystick) ou de surface sensitive (touchpad) ; - Un cran : comme pour la souris, il est difcile aujourdhui dimaginer un ordinateur sans cran, mais il faut savoir que lapparition des crans est aussi assez tardive. Ils voluent lentement en proposant de plus grandes tailles (17, 19 ou 24 pouces), de meilleures rsolutions (16001200 voire 19201080) et toujours plus de couleurs (16 millions). La prochaine volution devrait consister en la disparition compltes des tubes cathodiques au prot des crans plats ainsi qu la fusion entre les crans de tlvision et les crans dordinateur. De nombreux crans quipant soit des portables soit des ordinateurs xes font maintenant tat de leur capacit afcher des DVD Blu-Ray dans une rsolution HD ; - De la mmoire : le prix des barrettes de mmoire a soudainement chut en 1996, passant de 150 20 e les 4 Mo ! En octobre 1999, une
7. Tous les chiffres prsents ici doivent tre revus la baisse pour les ordinateurs portables qui sont confronts des problmes dencombrement et de consommation lectrique.

34

1.2. Reprsentation fonctionnelle dun ordinateur barrette de 32 Mo cotait environ 40 e et, aujourdhui, le prix est presque de 0,05 e pour 1 Mo (sauf pour les mmoires de dernire gnration, toujours plus chres). La mmoire est donc devenue une denre beaucoup moins rare et il est dsormais frquent de voir des ordinateurs personnels munis de 4 Go de mmoire et certaines stations de travail spcialises (notamment celles qui accueillent des serveurs virtuels) vont jusqu 64 Go ! - Un disque dur : un ordinateur possde souvent un, voire plusieurs, disques durs. La tendance actuelle est de lordre de 350 Go pour un ordinateur portable et dau moins 500 Go pour une station de travail personnelle. Leurs capacits croissent danne en anne et, aujourdhui, il est impensable 8 dacheter un disque dur de moins de 150 Go et quasiment impossible de trouver des disques durs neufs de moins de 20 Go. - Un lecteur de disquette : les disquettes (1,44 Mo) sont en train de disparatre au prot de nouveaux supports comme les cls USB (de 256 Mo 32 Go). Le principe reste cependant le mme. - Un lecteur de CD-ROM ou de DVD : les CD-ROM vont court terme tre supplants par les DVD qui peuvent contenir beaucoup plus de donnes (jusqu 17 Go pour un DVD double couche et double face contre 650 Mo pour un CD-ROM). Lexistence de graveurs de DVD bas prix (de lordre de 70 e suivant le type et la vitesse de gravure) et lexistence de DVD devraient rapidement sonner le glas du CD-ROM. - Un lecteur de bandes magntiques : essentiellement utilises pour effectuer des sauvegardes, les bandes magntiques souffrent dun dfaut majeur, savoir laccs squentiel et lent aux donnes. Elles restent cependant un support trs able, notamment dans le temps. - Une interface rseau : pour pouvoir se connecter un rseau local (lui-mme ventuellement reli un rseau international tel que lInternet). - Un modem : pour effectuer des transferts de donnes numriques via une ligne tlphonique et, par exemple, se connecter un rseau distant. Lavnement de lADSL a rendu ce priphrique moins frquent et, mme si les modems existent toujours, ils sont maintenant le plus souvent externes aux ordinateurs. - Une carte son : indispensable pour transformer en signal analogique (audible via des haut-parleurs) les sons stocks sous forme numrique.
8. Hormis pour des raisons de compatibilit avec des ordinateurs anciens ou avec des systmes dexploitation qui nont pas t prvus pour fonctionner avec de telles quantits de disque dur.

35

Chapitre 1. Rappels sur larchitecture des ordinateurs


Les bus de communication

Un ordinateur possde gnralement de nombreux bus de communication spcialiss que lon symbolise souvent comme tant un seul bus. Il est traditionnel den citer au moins trois : le bus de donnes sur lequel sont transfres les donnes traiter, le bus dadresses qui sert uniquement transfrer les adresses des donnes en mmoire et le bus de commandes qui permet de transfrer des commandes entre le processeur et les priphriques. Par exemple, lorsque le processeur dsire lire une information dans la mmoire principale, il envoie ladresse de cette information sur le bus dadresses ; le bus de commande indique au contrleur de la mmoire principale quil doit donner linformation au processeur ; le contrleur renvoie alors linformation demande sur le bus de donnes. La taille des bus de communication est un facteur important dans la rapidit des ordinateurs. Elle se mesure en bits et reprsente la taille de la plus grande donne qui peut tre transmise en une seule fois par ce bus. Supposons que nous disposons dun ordinateur muni dun bus de 8 bits ; si nous voulons transmettre un entier compos de 32 bits (cest souvent le cas en C, par exemple), il faudra le dcouper en 4 portions de 8 bits et le transmettre en 4 fois, do une perte de temps. Cette perte de temps nest bien entendu pas trs grande en elle-mme, mais multiplie par un grand nombre de donnes transmettre, cela peut devenir trs pnalisant. Ainsi, si nous disposons dun bus de 8 bits capable deffectuer 33 millions doprations par seconde (on dit souvent : un bus de 8 bits 33 MHz) et si nous ne traitons que des donnes de 32 bits, le bus pourra transmettre au mieux 9 8,25 millions de donnes par seconde. Notons que les constructeurs restent souvent discrets sur les performances des bus des ordinateurs quils produisent car cela leur permet de vendre plus facilement leur dernier modle : un processeur 500 MHz peut effectivement effectuer 500 millions doprations par seconde condition quil nait pas besoin de communiquer avec les lments situs lextrieur du processeur, cest--dire en particulier avec les diffrents lments de stockage (cache secondaire, mmoire principale, mmoire secondaire). Rciproquement, un processeur 500 MHz qui aurait besoin de lire ou dcrire en permanence des donnes en mmoire sera limit par la vitesse du bus, par exemple 100 MHz, et passera donc les quatre cinquimes de son temps attendre que les donnes soient disponibles. On peut alors se demander sil est vraiment intressant dacheter le tout dernier processeur qui fonctionne non pas 2 GHz mais 2,5 GHz, ce qui lui permettra de perdre non pas les neuf diximes de son temps mais les quatorze quinzimes... Notons quen pratique il est trs rare quun processeur ait besoin de lire ou dcrire des donnes en permanence et lutilisation dun processeur plus rapide conduit donc gnralement un gain de temps (voir discussion de la section 1.6 sur ce sujet et sur la faon dont on peut apprcier un gain de temps). Nanmoins, ce gain nest pas
9. Le dcoupage de la donne en 4 lots prend un certain temps...

36

1.2. Reprsentation fonctionnelle dun ordinateur proportionnel au gain ralis en vitesse de processeur et, formul diffremment, un ordinateur muni dun processeur fonctionnant 1 GHz ne sera pas deux fois plus rapide quun ordinateur muni dun processeur fonctionnant 500 MHz.
BSB

CPU

L2, L3 cache RAM

FSB

PCIe NorthBridge

Bus mmoire

Bus interne du chipset IDE SATA USB Ethernet ...

SouthBridge

PCI

Clavier Souris ...

F IGURE 1.3 Les composants dun ordinateur ne sont pas tous relis entre eux par le mme bus. An de rduire les latences, diffrents bus sont utiliss et deux microcomposants (le NorthBridge et le SouthBridge) contrlent la circulation des donnes dun composant un autre.

Le bus systme, ou bus processeur, ou encore bus mmoire, souvent connu sous le nom de FSB (Front Side Bus) permet de connecter le processeur la mmoire de type RAM ainsi qu dautres composants (voir g. 1.3). Les communications sur ce bus systme sont gres par un composant particulier de la carte mre : le NorthBridge. Cest ce composant qui conditionne souvent le chipset de la carte mre. Les communications avec des priphriques plus lents tels que les disques durs par exemple sont gres par un autre composant : le SouthBridge. Le bus de cache ou BSB (Back Side Bus) ralise la connexion entre le processeur et la mmoire cache secondaire (cache L2 et cache L3) quand celle-ci nest pas directement intgre au processeur. Le bus PCIe (Peripheral Component Interconnect Express) permet la connexion de priphriques varis comme une carte video,. . . Cette nouvelle technologie, compatible avec lancien bus PCI, adopte un modle de transmission par commutation de paquets (trs proche du modle rseau TCP/IP) sur une ligne srie la diffrence de lancien protocole parallle dans lequel un composant monopolisait le bus pendant sa transaction. 37

Chapitre 1. Rappels sur larchitecture des ordinateurs Cette ligne srie peut dailleurs tre dcompose en plusieurs voies an de parallliser les transferts. Le processeur et les bus de communication tant cadencs par lhorloge, la vitesse des processeurs est gnralement un multiple de la vitesse du bus. Actuellement, les progrs raliss pour les bus de communication sont beaucoup plus lents que ceux raliss pour les processeurs et les bus de communication fonctionnent gnralement 600 ou 1060 MHz. Dans un pass proche, certains modles de PC faisaient directement rfrence ce phnomne en indiquant le facteur multiplicatif. Par exemple, on parlait de DX2 66 (bus 33 MHz et processeur 66 MHz). Notons que cette habitude a rapidement dgnr car les DX4 100 , par exemple, taient en fait des DX3 100 . . . Il est gnralement intelligent de choisir un ordinateur dont le FSB et le CPU sont dans une juste proportion. . .
Les interruptions

Les diffrents composants dun ordinateur communiquent via des interruptions qui, comme leur nom lindique, peuvent provoquer linterruption de la tche queffectue le processeur. Ce dernier peut alors dcider de donner suite linterruption en dclenchant une routine dinterruption approprie ou il peut dcider dignorer linterruption. Gnralement, les routines ainsi excutes font partie du systme dexploitation et les interruptions facilitent donc lintervention de celui-ci.
Interruption (horloge) Noyau du systme d'exploitation

Processeur

F IGURE 1.4 Le systme dexploitation reprend rgulirement la main grce aux interruptions : chaque tic , lhorloge dclenche une interruption qui sera traite par le processeur via lexcution dune routine particulire, situe dans une zone mmoire particulire. Il suft donc de placer le systme dexploitation dans cette zone mmoire pour garantir lintervention rgulire de celui-ci.

Le terme dinterruption est rapprocher du mot interrupteur, car cest par le biais dune liaison lectrique que la communication stablit : lorsquun priphrique, par exemple, veut dclencher une interruption du processeur, il applique une tension sur une ligne lectrique les reliant (voir gure 1.5). Les interruptions sont utilises pour informer de larrive dvnements importants, comme par exemple lors daccs mmoire impossibles ou lorsquun priphrique a termin le travail qui lui tait demand. Gnralement, on distingue les interruptions matrielles, effectivement produites par le matriel, et les interruptions logicielles 38

1.2. Reprsentation fonctionnelle dun ordinateur

Reu Processeur Envoy Erreur Contrleur

F IGURE 1.5 Les composants des ordinateurs communiquent en appliquant des tensions sur des lignes lectriques

que le processeur sapplique lui-mme. Ces interruptions logicielles jouent un rle important dans la conception des systmes dexploitation. Les interruptions matrielles ne sont habituellement pas transmises directement au processeur et elles transitent par divers contrleurs. Il se peut alors que lun de ces contrleurs dcide de ne pas transmettre certaines demandes dinterruptions (souvent appeles IRQ pour Interruption ReQuest) et, donc, quune demande dinterruption dclenche par un priphrique ne provoque pas linterruption de la tche en cours. Le terme dinterruption est donc assez mal choisi, ce qui explique lapparition dautres termes : exceptions, trappes (trap en anglais). Certains contrleurs dinterruption autorisent les utilisateurs masquer des interruptions, ce qui reprsente une sorte de programmation bas-niveau du matriel. Prcisons, enn, que rien nassure que la transmission dune interruption est synchrone...
Interruption

Processeur

Ignore

Traite

F IGURE 1.6 Une interruption peut tre ignore par le processeur (ou par un des contrleurs transmettant celle-ci) et ninterrompt donc pas toujours un traitement en cours.

La gestion des interruptions est assez complexe et ncessite en particulier de prendre quelques prcautions : si une interruption interrompt une tche et que le processeur dcide de dclencher une routine dinterruption, il est absolument ncessaire de sauvegarder ltat de la tche interrompue, avec en particulier le contenu des 39

Chapitre 1. Rappels sur larchitecture des ordinateurs registres, an quelle puisse reprendre ds la n de la routine dinterruption. Un autre problme se pose pour les interruptions : que se passe-t-il si une interruption intervient alors que le processeur est dj en train dexcuter une routine dinterruption ? Gnralement, des niveaux de priorit diffrents ont t affects aux interruptions et le choix dans de telles situations dpend de la priorit des interruptions traites. Cette tche est en gnral du ressort du PIC 10 ( Programmable Interrupt Controler ) qui contrle le niveau de priorit des interruptions ( Interrupt Priority Level ). Les PICs fonctionnent sur la base dun ensemble de registres : lIRR ( Interrupt Request Register ) qui spcie quelles interruptions sont en attente dacquittement, lISR ( In-Service Register ) qui contient les interruptions acquittes mais en attente dun signal de Fin dInterruption (EOI soit End Of Interrupt ) et enn lIMR ( Interrupt Mask Register ) qui tient la liste des interruptions ignores et donc non acquittes. On distingue mme sur les architectures plusieurs processeurs deux gestionnaires de politique de priorit 11 : le traditionnel APIC et le LAPIC (pour Local APIC) dont le rle est de traiter les interruptions entre un processeur et un autre ( Inter-Processor Interrupts ) et naturellement de soccuper des interruptions externes signales au processeur auquel il est attach.

1.3

Mode noyau versus mode utilisateur

Les processeurs rcents ont au moins deux modes de fonctionnement : un mode noyau (ou systme ou superviseur) et un mode utilisateur 12 . Ces deux modes sont utiliss pour permettre au systme dexploitation de contrler les accs aux ressources de la machine. En effet, lorsque le processeur est en mode noyau, il peut excuter toutes les instructions disponibles (voir section suivante) et donc modier ce quil veut. En revanche, lorsque le processeur est en mode utilisateur, certaines instructions lui sont interdites. Le systme dexploitation (au moins une partie) dun ordinateur est gnralement excut en mode noyau alors que les autres programmes fonctionnant sur la machine sont excuts en mode utilisateur. Ce principe contraint ces programmes faire appel au systme dexploitation pour certaines oprations (en particulier pour toutes les oprations de modication des donnes) et reprsente donc une protection fondamentale, situe au cur du systme, puisque tous les accs aux donnes sont ncessairement contrls par le systme dexploitation 13 . Nous verrons dans la suite du document
10. On le dnomme aussi Advanced Programmable Interrupt Controler pour APIC. 11. Il est important de noter que ces gestionnaires faisant partie de la carte mre ne sont pas exempts de bugs. Cest pourquoi on retrouve parfois, pour contourner les bugs matriels les fameuses options de dmarrage noapic,nolapic sous Linux. 12. La presse informatique dsigne parfois ces modes par ring 0 et ring 3 . 13. Voir section 2.1 ce sujet.

40

1.4. Le jeu dinstructions comment ce principe est utilis et comment il parat difcile desprer dvelopper un systme dexploitation efcace contre les intrusions sans adopter une telle dmarche 14 . Lutilisation de ces modes est un bel exemple du dveloppement complmentaire des systmes dexploitation et des machines. En effet, la distinction noyau versus utilisateur provient de considrations lies aux systmes dexploitation et le fait quelle soit assure par les couches matrielles de la machine simplie la tche du systme. Rciproquement, une fois cette distinction implante, elle a permis de dvelopper des couches matrielles plus labores et plus efcaces.

1.4

Le jeu dinstructions

Le jeu dinstructions est lultime frontire entre le logiciel et le matriel. Cest la partie de la machine vue par le programmeur et cest le langage dans lequel les compilateurs doivent transformer les codes sources de plus haut niveau. !"#$%&'(')$#*"#+*,-.')$
/%89:)& ;*<$)#6)5#'05,."(,'%05 =05,."(,'%0#>>?
4"5#6)# (%++*06)5

22233322

2 2 2 3 3 3 2 2

7 7 7

/%0,.1$)".

7 7

F IGURE 1.7 Le jeu dinstruction est la couche ultime entre le logiciel et le matriel. La traduction de chaque instruction en action lectrique seffectue par une simple table de correspondance et une instruction nest quune reprsentation symbolique de lapplication de tensions sur des contacteurs.

Les jeux dinstructions dpendent du processeur et varient normment dun type dordinateur un autre. En particulier, le nombre dinstructions disponibles et le travail effectu par ces instructions sont difcilement comparables. Les instructions sont gnralement classes en 5 catgories : arithmtique et logique (addition, soustraction, et, ou) ; ottant (oprations ottantes). transfert de donnes (de la mmoire principale vers les registres et rciproquement) ; contrle (appel de procdure, branchement, saut) ;
14. Prcisons quil existe des systmes dexploitation qui nutilisent que le mode noyau du processeur.

41

Chapitre 1. Rappels sur larchitecture des ordinateurs systme (trappe, appel au systme dexploitation) ; Les instructions systme sont typiquement les instructions interdites en mode utilisateur. En particulier, citons linstruction permettant le changement de contexte (context switching, voir chapitre 5 sur les processus et sur lordonnancement), linstruction permettant de manipuler la pile (voir chapitre 4 sur la compilation et les processus) et linstruction permettant de passer du mode utilisateur au mode noyau (et rciproquement). On peut alors se demander comment un ordinateur peut passer du mode utilisateur au mode noyau, puisquil doit dj tre en mode noyau pour excuter linstruction permettant ce passage... En fait, il existe une instruction spciale qui dclenche une interruption logicielle et, donc, lexcution de la routine dinterruption correspondante. Cette routine fait gnralement partie du systme dexploitation, cest--dire quelle sexcute en mode noyau, et elle permet donc de contrler que rien dillgal nest demand. Lorsque la routine est termine, le processeur repasse en mode utilisateur.
Excution d'une instruction autorise en mode utilisateur Excution d'une instruction interdite en mode utilisateur

Processeur

Noyau du systme d'exploitation

Processeur

Noyau du systme d'exploitation Refus de passer le processeur en mode noyau

F IGURE 1.8 Le passage du mode utilisateur au mode noyau fait ncessairement appel au systme dexploitation : les appels systme provoquent une interruption logicielle qui dclenche une routine dinterruption correspondant au systme dexploitation. Le principe est similaire celui mis en uvre avec les interruptions de lhorloge.

Notons que rien noblige utiliser une routine relevant du systme dexploitation et que, dailleurs, certains systmes laissent lutilisateur excuter de cette faon ses propres routines : cest ce quon appelle le droutement de linterruption. Notons aussi que lutilisation de cette instruction spciale simplie grandement la tche des systmes dexploitation et que, comme nous le verrons tout au long de ce cours, il est difcile de concevoir un systme dexploitation sans utiliser les couches matrielles des ordinateurs. Une instruction se droule en plusieurs tapes, effectues par des composants diffrents. Elle comporte au moins cinq tapes : la recherche de linstruction en mmoire (fetch) ; le dcodage de linstruction (decode) ; la lecture des oprandes (load) ; lexcution proprement dite (execute) ; le stockage du rsultat (store). 42

1.4. Le jeu dinstructions Une instruction peut tre dcompose en plusieurs oprandes. Le nombre de ces oprandes dpend du travail effectu par cette instruction, cest--dire essentiellement de la catgorie laquelle appartient linstruction (voir ci-dessus). Par exemple, une instruction daddition a besoin dau moins trois oprandes : les deux nombres additionner et lemplacement o le rsultat sera stock 15 . En pratique, linstruction nutilise pas directement les deux nombres additionner mais les registres ou les adresses de la zone de mmoire principale o sont stocks ces deux nombres. On distingue dailleurs plusieurs stratgies pour les jeux dinstructions selon quils effectuent les calculs directement partir de donnes stockes en mmoire principale, partir de donnes stockes dans les registres ou les deux la fois. Lexemple suivant montre comment est effectue lopration consistant lire les valeurs des donnes B et C stockes en mmoire principale, additionner ces deux valeurs et stocker le rsultat dans A. Cest ce que symboliquement nous cririons : A=B+C Jeu dinstructions de type mmoire-mmoire : 1. Addition et stockage direct (Add B C A) ; Jeu dinstructions de type registre-registre : 1. Chargement de B dans le registre R1 (Load R1 B) ; 2. Chargement de C dans le registre R2 (Load R2 C) ; 3. Addition et stockage du rsultat dans le registre R3 (Add R1 R2 R3) ; 4. Stockage du rsultat dans A (Store R3 A). Le principe qui consiste dcomposer une instruction complexe, comme laddition du modle mmoire-mmoire, en une srie dinstructions lmentaires, comme laddition du modle registre-registre (aussi appel chargement-rangement), a donn naissance des jeux dinstructions appels RISC (Reduced Instruction Set Computer). Les autres jeux dinstructions sont alors nomms CISC (Complex Instruction Set Computer). RISC est souvent traduit en franais par jeu rduit dinstructions . Cette traduction rete effectivement les consquences historiques de lapparition des RISC, mais ne correspond plus la ralit : il y a aujourdhui plus dinstructions dans un jeu dinstructions RISC que dans un CISC. En revanche, les instructions RISC sont toujours beaucoup simples que les instructions CISC et comportent moins de modes dadressage. La bonne traduction est donc jeu dinstructions rduites pour RISC et jeu dinstructions complexes pour CISC. Quel est lintrt des RISC ? Tout dabord, lutilisation dinstructions rduites permet de faire en sorte que toutes les instructions aient la mme taille, ce qui facilite
15. Certains processeurs stockent le rsultat au mme endroit que lun des deux nombres ajouter et nont donc besoin que de deux oprandes.

43

Chapitre 1. Rappels sur larchitecture des ordinateurs grandement lopration de dcodage. Ensuite, cela favorise la mise en place des pipeline, cest--dire des recouvrements des tapes dexcution dune intruction. Ces cinq tapes (fetch, decode, load, execute et store) peuvent tre assures par des composants diffrents et il est alors possible, lorsque deux instructions se suivent et lorsquelles ont la mme taille, dexcuter ltape de fetch de la seconde instruction alors que la premire en est ltape de decode 16 . Cela conduit au schma de la gure 1.9.
temps

Instruction 1 Instruction 2 Instruction 3 Instruction 4 Instruction 5

Fetch

Decode Fetch

Load Decode Fetch

Execute Load Decode Fetch

Store Execute
Load

Store

Execute
Load

... ... ...

Decode Fetch

Decode

F IGURE 1.9 Le principe du pipeline

En fait, une tape est excute chaque cycle dhorloge et, donc, pour n instructions et un pipeline cinq tages, il faudra n + 4 cycles dhorloge pour toutes les excuter, soit en moyenne 1 + 4 cycle par instruction. Ainsi, pour une machine RISC, une n instruction est en moyenne excute chaque cycle dhorloge. Une autre faon de voir les choses est reprsente sur la gure 1.10.
temps

Unit 1 Unit 2 Unit 3 Unit 4 Unit 5

Fetch

Fetch Decode

Fetch

Fetch Decode
Load Execute

Fetch

Fetch Decode
Load ... ... ...

Decode
Load

Decode
Load

Execute
Store

Execute
Store

Inst

ruct

Inst r ion 1 uction 2

F IGURE 1.10 Le principe du pipeline vu de faon diffrente.

16. Il existe des machines CISC avec pipeline mais leur mise en uvre est plus complexe car il est difcile de prvoir la n dune instruction.

44

1.5. Rle de lunit de gestion de mmoire

1.5

Rle de lunit de gestion de mmoire

La protection de la mmoire est une des tches principales des systmes dexploitation modernes. Cette protection est trs difcile raliser sans lintervention des couches matrielles de la machine et certains composants, comme la MMU (Memory Management Unit), jouent un rle capital dans cette protection. Inversement, il est difcile de parler de la protection de la mmoire sans faire rfrence des services particuliers du systme dexploitation. Ce chapitre tant consacr uniquement au matriel, nous resterons le plus vague possible en ce qui concerne le rle rel de la MMU et un lecteur curieux pourra trouver des explications plus concrtes dans le chapitre 6 consacr la gestion de la mmoire.
Adresses physiques

Nous avons dni la section 1.2 le terme adresse comme une indexation des octets disponibles dans la mmoire principale de lordinateur. Nous appellerons dsormais ce type dadresse des adresses physiques dans la mesure o elles font directement rfrence la ralit physique de la machine. Une adresse physique est donc un nombre quil suft de passer au contrleur de mmoire pour dsigner une donne stocke en mmoire. Le nombre dadresses physiques diffrentes dpend de la taille des adresses : une adresse de 8 bits permet de coder 28 = 256 zones dun octet, soit 256 octets ; une adresse de 32 bits permet de coder 232 = 22 210 210 210 = 4 Go. Pendant longtemps, la taille des bus dadresses ne permettait pas de passer une adresse en une seule fois et diverses mthodes taient utilises pour recomposer ladresse partir de plusieurs octets. Aujourdhui les bus dadresses ont une taille de 32 ou 64 bits, ce qui permet de passer au moins une adresse chaque fois.
Protection de la mmoire

Il est trs difcile de protger des zones de la mmoire en travaillant directement sur les adresses physiques des donnes contenues dans ces zones 17 . Supposons en effet que nous souhaitons crire un systme dexploitation qui protge les donnes utilises par diffrentes tches. Tout dabord, il est clair quil est plus facile de protger ces donnes si elles sont rassembles en lots cohrents (toutes les donnes de la tche A, puis toutes les donnes de la tche B, etc.) que si elles sont entrelaces (une donne de la tche A, une donne de la tche B, une donne de la tche A, etc.). Ensuite, si les donnes sont entrelaces, le systme devra pour chaque donne marquer le propritaire de la donne et les droits daccs cette donne (voir gure 1.11). Par exemple, il faudra prciser que certaines donnes du systme dexploitation sont inaccessibles pour les autres tches. Imaginons quau moins 2 bits soient ncessaires
17. Voir aussi le chapitre sur la gestion de la mmoire.

45

Chapitre 1. Rappels sur larchitecture des ordinateurs pour marquer chaque donne. Cela signie donc que pour 8 bits allous en mmoire, 2 bits supplmentaires devront tre utiliss. Par ailleurs, il faut aussi marquer ces 2 bits supplmentaires... Si on suppose que ces marquages sont regroups quatre par quatre, cela veut dire que pour 3 octets allous en mmoire, le systme dexploitation a besoin de 4 octets.
1 2 3 4 5 6 7 8 9 ...
Processus 1 Processus 2 Processus 3

F IGURE 1.11 La protection de donnes entrelaces nest gure efcace

Cette politique de protection nest donc pas raliste, puisquelle entrane un gchis considrable de la place mmoire. Comme nous le disions plus haut, la mmoire a longtemps t une denre rare et tous les dveloppeurs de systmes dexploitation ont rapidement cherch une solution.

Adresses virtuelles

En pratique, les ordinateurs nont jamais 4 Go de mmoire principale (adresses sur 32 bits) et une grande partie des adresses ne sert rien. Une ide intressante est dutiliser tout lespace dadressage pour effectuer la gestion de la mmoire, puis de transformer au dernier moment ladresse utilise en une adresse physique. Par exemple, utilisons les 2 premiers bits dadresse pour rserver des zones de mmoire au systme. Ces 2 bits dsignent 4 zones de 1 Go et nous allons rserver la zone 3 (11 en binaire) pour le systme. Ainsi, toutes les donnes concernant le systme dexploitation auront une adresse de 32 bits dbutant par 11 et il sufra donc de tester ces 2 premiers bits pour savoir si une donne appartient au systme ou pas. Lavantage de ce systme est vident ! Mais la correspondance entre les adresses ainsi composes et les adresses physiques nest plus assure : les adresses de 32 bits commenant par 11 correspondent des donnes stockes aprs les 3 premiers Go de mmoire... De telles adresses nayant pas de correspondance physique sappellent des adresses virtuelles. Une adresse virtuelle est donc une reprsentation particulire de la zone occupe par un octet qui ncessite une transformation adapte pour correspondre une adresse physique. Cette transformation sappelle la traduction dadresses ou, par abus de langage, translation dadresses. 46

1.6. Performances des ordinateurs


Traduction dadresses et TLB

La conversion des adresses virtuelles en adresses physiques connues par les couches matrielles a lieu constamment pendant lexcution des diffrents programmes utiliss sur lordinateur. Cette conversion doit donc tre trs rapide car elle conditionne la vitesse dexcution des programmes. Cest pourquoi cette traduction est directement assure par un composant lectronique log dans le processeur : la MMU 18 . Le systme dexploitation nintervient donc pas au moment de la traduction dadresses. Nanmoins, la MMU est programme par le systme dexploitation et cest ce dernier, notamment, qui lui indique comment sont composes les adresses et qui met jour la table de traduction. Ce procd est encore un exemple montrant quel point la conception des systmes dexploitation et la conception des architectures des ordinateurs sont lies. Il est noter que, outre la MMU, dautres composants matriels interviennent dans la traduction dadresse, comme par exemple des mmoires tampon de traduction anticip (Translocation Lookaside Buffer ou TLB) et un lecteur dsireux den savoir plus pourra se reporter des ouvrages spcialiss sur larchitecture des ordinateurs (voir bibliographie de ce document, par exemple).

1.6

Performances des ordinateurs

Cette section sur les performances nest pas directement lie ltude des architectures des machines, ni ltude des systmes dexploitation. Nanmoins, les systmes dexploitation interviennent aussi dans la mesure des performances des ordinateurs et nous (r)tablirons ici quelques vrits importantes pour le choix dun ordinateur.
Le fond du problme

Les constructeurs dordinateurs saffrontent gnralement sur la place publique en annonant des performances toujours plus leves. Outre le fait que la notion de performance masque les problmes et les innovations des architectures des ordinateurs, elle est trs ambigu et chaque constructeur joue sur cette ambigut pour proposer des tests de rapidit (benchmarks en anglais, parfois abrgs en benchs) qui avantagent leurs choix darchitecture ou leurs spcicits matrielles. Nous allons dans cette section essayer de faire le point sur les mesures proposes par les constructeurs et, comme dhabitude, essayer de sparer le bon grain de livraie. Comme pour les sections prcdentes, un lecteur dsireux dapprofondir ces questions de mesure de performances doit se reporter des ouvrages spcialiss. Tout dabord, il est ncessaire de bien dnir ce que nous voulons mesurer. Deux approches sont possibles : celle de lutilisateur ou celle du centre de calcul.
18. La MMU na pas toujours t loge directement dans le processeur, mais cest dsormais le cas.

47

Chapitre 1. Rappels sur larchitecture des ordinateurs Lutilisateur dun ordinateur est intress par le temps que mettra un programme pour sexcuter sur son ordinateur. Pour lui, ce temps na cependant pas de sens a priori car il est difcile destimer la dure quune excution devrait avoir et il est dailleurs trs rare de voir un utilisateur mesurer prcisment le temps dexcution dun programme. En revanche, les diffrences entre ordinateurs sont parfaitement perceptibles pour un utilisateur car il possde une notion trs prcise du temps de rponse usuel de son ordinateur (on parle parfois de ractivit) : sil utilise un tout nouvel ordinateur et si son programme sexcute plus rapidement sur cet ordinateur, lutilisateur se rend compte immdiatement de lacclration ! Cest gnralement cette perception de lacclration de lexcution dun programme qui permet de dire quun ordinateur est plus puissant quun autre. Le centre de calcul nest en revanche pas intress par les capacits isoles des ordinateurs quil possde. Il a une vision globale des performances : sur lensemble des ordinateurs du centre et pour un temps donn, combien de programmes ont pu tre excuts ? Cette vision des choses comporte (au moins !) deux mrites : dune part, seule une moyenne des performances des ordinateurs est prise en compte, dautre part, lenchanement des excutions est aussi mesur. La moyenne des performances est probablement une mesure plus juste car il est facile dimaginer quun ordinateur puisse tre trs rapide pour certaines tches et trs lent pour dautres. Mesurer ses performances pour une seule tche reviendrait donc avantager un type de programme. Lenchanement des excutions est aussi trs important mesurer : on pourrait imaginer un ordinateur qui utiliserait de faon trs efcace les couches matrielles dont il dispose, mais qui, en contrepartie, rendrait les phases de chargement des programmes ou de lecture des donnes trs longues et trs pnibles. Les systmes dexploitation (chargs de ces deux services) interviennent donc aussi dans cette partie de linformatique et il parat douteux de vouloir mesurer les performances dun ordinateur sans prciser le systme dexploitation utilis pour les tests de rapidit. Remarquons quun utilisateur peut tre considr comme un centre de calcul ne disposant que dun ordinateur : il est trs rare de nos jours quun utilisateur nexcute quun programme la fois, mme sil existe encore des systme dexploitation primitifs noffrant que cette possibilit ! Nous ne ferons donc plus dans le reste du texte de diffrence entre lutilisateur et le centre de calcul. Remarquons aussi que les principes noncs ci-dessus sappliquent uniquement des programmes qui ont une dure dexcution limite, comme par exemple des programmes effectuant des calculs scientiques. Ainsi, par exemple, il parat difcile de mesurer le temps dexcution dun diteur de texte ou dun programme de jeu ! Nous pouvons donc au regard de ces quelques remarques distinguer plusieurs mesures possibles. La mesure des performances dun processeur : cette mesure intresse a priori le constructeur dordinateurs car elle lui permet de choisir un processeur adapt aux besoins de ses clients. Notez que cest malheureusement souvent cette mesure qui sert dargument publicitaire 48

1.6. Performances des ordinateurs pour la vente dordinateurs... La mesure des performances dune architecture dordinateur : cette mesure intresse assez peu de personnes et elle reprsente en fait la capacit du constructeur dordinateurs intgrer efcacement les composants dun ordinateur autour du processeur. Cette mesure devrait tenir compte des performances du bus de communication, des performances des zones de mmoire, du disque dur, etc. Elle donne en fait la mesure de la performance maximale dun ordinateur, quels que soient les moyens utiliss pour la mesurer. La mesure des performances dun ordinateur : cette mesure na aucun sens ! La mesure des performances dun couple (ordinateur, OS) : cette mesure intresse en premier lieu lutilisateur car elle reprsente directement la performance quil peut attendre de sa machine et car elle correspond la sensation de rapidit (ou de lenteur) qua lutilisateur devant son ordinateur. Insistons sur le fait que le systme dexploitation intervient dans cette mesure et que, donc, de mauvais rsultats concernant cette mesure ne signient pas ncessairement que lordinateur ou les programmes utiliss sont mauvais. Il nest pas rare de voir des constructeurs proposer des ordinateurs ayant dexcellentes mesures des performances de leur architecture, mais incapables de fournir le systme dexploitation et le compilateur qui sauront tirer partie de cette machine. Cela revient quiper une voiture dun moteur de formule 1 en oubliant de fournir la bote de vitesses : certes le moteur tournera trs vite et sera trs performant, mais mme fond en premire, il est probable que a naille pas trs vite !
Temps utilisateur versus temps systme

Si nous chronomtrons lexcution dun programme, nous mesurons en fait plusieurs choses la fois : le temps que lordinateur a effectivement mis pour effectuer les calculs demands, le temps utilis par le systme dexploitation pour rendre les services ncessaires lexcution du programme et le temps perdu attendre des entres / sorties comme par exemple des lectures sur le disque dur ou des afchages lcran. Comme voqu plus haut, ce qui compte pour lutilisateur, cest la somme des temps pris par ces trois entits, soit effectivement le temps global. Les choses se compliquent cependant lorsque plusieurs programmes sexcutent en concurrence sur une mme machine : comment mesurer le temps utilis par un seul des programmes ? Outre ce problme, il faut aussi pouvoir distinguer dans le temps utilis par le systme dexploitation le temps effectivement employ pour rpondre des besoins du programme dont on cherche mesurer lexcution. Ces considrations ont entrin la distinction entre le temps utilisateur, cest--dire le temps effectivement 49

Chapitre 1. Rappels sur larchitecture des ordinateurs utilis pour les calculs, et le temps systme, cest--dire le temps employ par le systme dexploitation pour servir le programme test. Malheureusement, cette distinction sous-entend que le temps utilisateur ne dpend pas du systme dexploitation, ce qui est en gnral compltement faux : le programme qui sexcute est obtenu par compilation dun code source crit dans un langage de haut niveau (comme le C) et lefcacit de ce programme dpend non seulement de la faon dont il a t compil, mais aussi de la faon dont les appels systme ont t programms (par le systme dexploitation). Disposer du temps utilisateur dun programme permet nanmoins aux dveloppeurs damliorer le code source de ce programme. Cependant, cela ne veut pas dire quil nest pas possible de diminuer le temps systme en modiant les sources... Par ailleurs, et nous insistons sur ce fait, ce qui intresse lutilisateur, cest la dure dexcution dun programme comprenant donc le temps utilisateur, le temps systme et le temps utiliss pour effectuer des entres / sorties. Les constructeurs dordinateur et les vendeurs de logiciel ont cependant vu dans cette distinction le moyen de saffranchir des performances des systmes dexploitation et ont donc propos des mesures sappuyant uniquement sur le temps utilisateur, ce qui na pas de sens. Pour reprendre lanalogie avec les voitures, supposons que nous voulons mesurer le temps que met une voiture pour atteindre la vitesse de 100 km/h partir dun dpart arrt. Mesurer le temps utilisateur revient mesurer le temps total de laction auquel nous soustrairons le temps de raction du conducteur (entre le moment o le dpart a t donn et le moment o le conducteur a acclr), le temps des changements de vitesse et le temps pendant lequel les pneus ont patin. Notons que cette mesure favorise trangement les voitures qui ont de mauvais conducteurs, de mauvais pneus et de mauvaises botes de vitesses...
Les units de mesure

Nous allons ici dcrire quelques units utilises par les constructeurs pour mesurer les performances des ordinateurs. Comme nous lavons dj mis en avant, ces mesure ne retent pas la puissance dun ordinateur et de son systme dexploitation car elles se fondent gnralement sur le temps utilisateur. Lunit de mesure la plus courante a longtemps t le MIPS 19 (millions dinstructions par seconde). Cette mesure varie gnralement de pair avec la puissance des ordinateurs, en tout cas avec la puissance ressentie par les utilisateur : plus une machine est puissante, plus elle afche de MIPS. Ceci explique probablement pourquoi cette mesure est si populaire... Nanmoins, elle na aucun sens et il est facile de trouver des contre-exemples agrants. Sans entrer dans les dtails, remarquons deux faits majeurs : 1. pour une mme opration, le nombre dinstructions ncessaires varie dune machine lautre ;
19. ne pas confondre avec le fabricant de microprocesseurs MIPS, rachet par la socit Silicon Graphics Incorporated (SGI).

50

1.6. Performances des ordinateurs 2. certaines instructions complexes demandent beaucoup plus de temps que dautres instructions simples. Par exemple, les instructions faisant appel au coprocesseur sont gnralement trs coteuses en temps. Pour effectuer un calcul ottant, il est cependant bien clair quil vaut mieux faire un appel au coprocesseur plutt que dexcuter des dizaines dinstructions entires. Donc lunit MIPS est totalement inadapte, en particulier pour les calculs utilisant les ottants. On trouve souvent des traduction de MIPS qui retent ce phnomne : Meaningless Indication of Processor Speed ou Meaningless Indoctrination by Pushy Salespersons . An de prendre en compte les calculs utilisant des ottants, le FLOPS (Flotting point Operation Per Second) a vu le jour. On parlait au dbut de MgaFLOPS, puis de GigaFLOPS et certaines machines afchent dsormais des performances en TraFLOPS. Cette unit est plus astucieuse que le MIPS : dune part, elle est spcialise dans un type dapplications (les calculs ottants) et, dautre part, elle ne compte plus le nombre dinstructions, mais le nombre doprations par seconde ce qui lui permet de sabstraire un tant soit peu des couches matrielles. Les critiques faites sur les MIPS restent nanmoins valables pour les FLOPS et, en particulier, certaines oprations ottantes, comme les additions, sont beaucoup plus rapides que dautres, comme les divisions. Deux programmes particuliers sont beaucoup utiliss par les fabricants de PC pour mesurer les performances de leurs machines : le Dhrystone et le Whetstone. Le programme Dhrystone nutilise pas doprations ottantes et cette mesure est donc peu utile. Le programme Whetstone est crit en FORTRAN et est compil sans optimisation. Il est alors raisonnable de se poser la question suivante : les valeurs seraient-elles du mme ordre avec des programmes crits en C ou en un autre langage de haut niveau ? Il est probable que non. Les rsultats du Dhrystone et du Whetstone se mesurent en MIPS, mais ces deux noms sont souvent directement utiliss comme une unit. Notons que ces deux units furent surtout utilises par les fabricants de PC parce que le systme dexploitation utilis (MS-DOS) tait si rudimentaire (mono-tche et mono-utilisateur) quil supprimait ipso facto les problmes de rpartition entre temps utilisateur et temps systme. Nanmoins, ces mesure tiennent compte des entres / sorties, ce qui est dj un bel effort. Notons aussi que lutilisation du Dhrystone est un bon exemple de dmarche commerciale ingnieuse : pendant longtemps les processeurs Intel obtenaient de mauvais rsultats pour les calculs ottants alors quils excellaient dans les calculs entiers. Les constructeurs de processeurs pour station de travail (comme SUN, DEC, MIPS, Motorola) afchaient eux des performances opposes : excellentes pour les calculs ottants, mais mdiocres pour les calculs entiers. Promouvoir lunit Dhrystone revenait donc radiquer la concurrence... Le tableau 1.2 indique les rsultats obtenus par des processeurs assez anciens mais il permet dillustrer simplement cette section. On peut ainsi remarquer que le 51

Chapitre 1. Rappels sur larchitecture des ordinateurs Modle Sun Sparc 10 Sun Sparc 20 MIPS R5000 Pentium 120 Pentium 120 Sun Ultra 170 Sun Ultra 170 Cadence (MHz) 100 75 200 120 120 167 167 Mmoire (Mo) 48 32 64 32 24 128 128 OS Solaris Solaris IRIX 5.3 Solaris NT Solaris Solaris D (MIPS) 156 172 214 120 186 284 332 W (MIPS) 122 137 110 60 70 152 188 M (S) 11,0 11,0 8,6 10,2 10,0 5,4 2,9

TABLE 1.2 Quelques performances de stations Unix et NT. Les trois dernires colonnes rfrencent les rsultats des tests Dhrystone, Whetstone et Maxwell.

Pentium 120 obtient dexcellentes notes sous Windows NT au test Dhrystone, mais des notes dans la moyenne pour le test Maxwell. De mme, on peut voir que le MIPS R5000 obtient suivant les test de trs bonnes notes (Dhrystone et Maxwell) ou de trs mauvaises (Whetstone). Enn, les deux dernires lignes du tableau ont t ralises grce deux compilateurs diffrents et on peut constater que le rsultat sen ressent. De nombreuse units sur le mme principe apparaissent rgulirement. Voici par exemple trois units utilises par un magazine sur les PC : les Winstone, les Winmark ou les CPUmark. A priori, leur seul intrt est de perdre le client en proposant systmatiquement une unit bien adapte la machine vendre. La mode actuelle est de promouvoir les units de calcul fondes sur le rendu dimages ou de squences 3D utilisant un ou plusieurs curs. Il est toutefois vident pour le lecteur que le meilleur processeur affubl dune quantit faible de mmoire ou dun bus systme la vitesse rduite se comportera comme une voiture de course sur laquelle auraient t greff un train de pneus de mobylette ainsi que les freins dun vlo. Comment ds lors accepter de participer ces concours que lon voit eurir sur la toile 20 .
La voie de la sagesse ?

Une unit plus intressante a vu rcemment le jour : le SPEC (System Performance Evaluation Cooperative). Cette unit est soutenue par un certain nombre de constructeurs et prend en compte lintervention du systme dexploitation et du compilateur dans la mesure des performances. Sa philosophie est la suivante : La mthodologie basique du SPEC est de fournir aux personnes dsireuses de raliser des tests de performances une suite standardise de codes
20. Les noms, et souvent lorthographe, sont exemplaires de cours de maternelle : Venez faire chauffer votre CPU multicore : Cinebench R10 Comme dans tout bench, le but est datteindre le plus gros score possible, toutefois, sachez que je ne prendrai en compte que le score cpu. Bien entendu, les processeurs seront class par nombre de core, un dual core ne pouvant videment pas rivaliser contre un quad core pour le temps de rendu (sauf lui coller un frquence de brute).

52

1.7. Conclusion : que retenir de ce chapitre ? sources fonds sur des applications existantes et portes sur une large varit de plateformes. La personne en charge des mesures prend ce code source, le compile pour son systme et peut optimiser le systme test pour obtenir les meilleurs rsultats. Cette utilisation de code connu et ouvert rduit grandement le problme des comparaisons entre oranges et pommes 21 . Les codes sources mentionns sont par exemple : perl, gcc, bzip2, h264ref,. . . Il apparat souvent sous deux formes, les SPECint et les SPECop, faisant ainsi rfrence des programmes utilisant ou pas les calculs ottants. Cette unit ayant volu au l des ans, il est aussi habituel de spcier la version utilise en accolant lanne de cration : par exemple, des SPECint95. Un grand pas a t franchi avec cette unit car, dune part, le rle du systme dexploitation est reconnu et, dautre part, les programmes utiliss sont proches des proccupations des utilisateurs. Cette unit nest nanmoins pas parfaite et, en particulier, elle saccommodait trs mal des ordinateur multi-processeurs. Ce nest plus le cas dans la mesure o elle diffrencie mme les rsultats selon les catgories curs par processeurs et nombre de processeurs.
Conclusion sur les performances

Nous tirerons deux conclusions de cette courte tude des mesures des performances des ordinateurs. Tout dabord, ces mesures ne riment rien et il est trs clair que certains constructeurs nhsitent pas modier larchitecture de leurs machines pour que celles-ci obtiennent de meilleurs rsultats. En quelque sorte, ils optimisent larchitecture de la machine en tentant de maximiser la mesure obtenue. Cette course la mesure des performances va lencontre du progrs informatique car les programmes de test ne retent gnralement pas les proccupations des utilisateurs et les constructeurs optimisent donc leurs machines pour des tches qui nintressent personne. Ensuite, si un ingnieur a besoin dexcuter un programme donn en un temps donn et sil dcide dacheter un ordinateur adapt ce besoin, il est illusoire de se fonder sur les performances afches par les constructeurs pour choisir ce nouvel ordinateur. La bonne dmarche consiste tester la machine avec le programme en question ! Contrairement ce que lon pourrait croire, les constructeurs prtent volontiers leurs machines des industriels ou des laboratoires de recherche pour que ces derniers les testent et il serait stupide de ne pas proter de telles occasions.

1.7

Conclusion : que retenir de ce chapitre ?

Ce chapitre assez long a permis de faire le point sur larchitecture des ordinateurs et sur ses relations troites avec les systmes dexploitation. Il est important de retenir
21. apples-to-oranges comparizons NDLT.

53

Chapitre 1. Rappels sur larchitecture des ordinateurs les points suivants. Sur larchitecture des ordinateurs : les composants des ordinateurs communiquent par des interruptions qui reprsentent une forme de programmation bas-niveau ; les processeurs ont deux modes de fonctionnement (mode noyau et mode utilisateur) qui permettent aux systmes dexploitation de protger fondamentalement les ressources de la machine (notamment les donnes des utilisateurs) ; plusieurs zones de mmoire (mmoire secondaire, mmoire principale, cache externe, cache interne, registres) de caractristiques trs diffrentes sont utilises par les ordinateurs pour effectuer leur travail et ces zones sont organises sous la forme dune hirarchie de mmoire. Sur la facilit de portage des dveloppements : le jeu dinstructions, spcique chaque processeur, est la couche logicielle ultime ; tout programme crit grce au jeu dinstruction est spcique de lordinateur pour lequel il a t crit (processeur et priphrique) et ne peut donc pas tre rutilis sur dautres ordinateurs. Sur les relations entre larchitecture des ordinateurs et les systmes dexploitation : les systmes dexploitation et les ordinateurs ont volu ensemble (rle de la MMU, par exemple), chacun permettant lautre de progresser ; la protection des ressources na de sens que si elle est effectue au plus profond de lordinateur, cest--dire si elle est effectue par le matriel.

54

2
Quest-ce quun systme dexploitation ?

Nous avons vu dans le chapitre prcdent que la programmation dun ordinateur peut tre trs complexe si elle seffectue directement partir des couches matrielles. Lutilisation dun langage de type assembleur permet de saffranchir du jeu dinstructions, mais la programmation reste nanmoins trs dlicate. Par ailleurs, tous les dveloppements faits en assembleur ne sont valables que pour une seule machine. Un autre inconvnient de la programmation de bas niveau est quelle permet de tout faire sur un ordinateur, y compris des oprations illicites pour les priphriques qui peuvent entraner leur dgradation ou tout simplement la perte irrmdiable de donnes importantes. Le systme dexploitation dun ordinateur est un programme qui assure toutes les tches relevant des ces deux aspects, cest--dire que, dune part, il facilite laccs aux ressources de la machine tout en assurant une certaine portabilit des dveloppements et, dautre part, il contrle que tous ces accs sont licites en protgeant ses propres donnes, celles des utilisateurs et celles de toutes les tches sexcutant sur la machine. Mots cls de ce chapitre : machine virtuelle, ressources, gestionnaire de ressources, CPU, appel systme, systme monolithique, systme en couches, noyau, micro-noyau, services. 55

Chapitre 2. Quest-ce quun systme dexploitation ?

2.1

Dnitions et consquences

Les diffrents ouvrages traitant du sujet ne sont pas daccord sur la dnition donner dun systme dexploitation. Nous ne donnerons donc ici des dnitions formelles que pour claircir les ides des lecteurs, mais ces dnitions importent peu en elles-mmes. En revanche, il est important de bien saisir les deux approches diffrentes des systmes dexploitation, quelle que soit la dnition quon en donne : dune part, faciliter laccs la machine et, dautre part, contrler cet accs.
Le systme dexploitation est une machine virtuelle

Le systme dexploitation est une machine virtuelle plus simple programmer que la machine relle. Il offre une interface de programmation lutilisateur qui na donc pas besoin de connatre le fonctionnement rel de la machine : lutilisateur demande au systme deffectuer certaines tches et le systme se charge ensuite de dialoguer avec la machine pour raliser les tches qui lui sont demandes. Dans cette optique, la machine relle est en fait cache sous le systme dexploitation et ce dernier permet donc un dialogue abstrait entre la machine et lutilisateur. Par exemple, lutilisateur se contente de dire je veux crire mes donnes dans un chier sur le disque dur sans se proccuper de lendroit exact o se trouvent ces donnes, ni de le faon dont il faut sy prendre pour les copier sur le disque dur.
Abstration

Application

Systme d'exploitation

Ralit physique

Matriel

F IGURE 2.1 Le systme dexploitation permet un dialogue abstrait entre la machine et lutilisateur.

Notons qu cet effet le systme dexploitation se doit de proposer une interface permettant ce dialogue abstrait et cest pour cela que des concepts dnus de toute ralit physique sont gnralement utiliss lorsquon parle des systmes dexploitation. Citons par exemple les variables qui permettent de faire rfrence des valeurs stockes quelque part en mmoire, les chiers qui reprsentent labstraction du stockage de donnes ou les processus qui sont labstraction dun programme en cours dexcution. Une partie de ce document est dailleurs ddie lexplication de ces concepts. 56

2.1. Dnitions et consquences Pour reprendre lanalogie automobile que nous avons dj utilise dans le chapitre prcdent, nous dirions que le systme dexploitation en tant que machine virtuelle est ce qui relie les lments mcaniques de la voiture au conducteur. Il propose une interface plus ou moins standard (le volant, les pdales dacclrateur, de frein et dembrayage, etc.) qui permet au conducteur de dialoguer avec le moteur et dobtenir un travail de ce dernier, sans pour autant se soucier du fonctionnement rel de ce moteur.
Le systme dexploitation est un gestionnaire de ressources

Les ressources dune machine sont tous les composants qui sont utiliss pour effectuer un travail et quun utilisateur de la machine pourrait sapproprier. ce titre, tous les priphriques comme la mmoire ou les disques durs sont des ressources. Les registres du processeur ou le temps pass par le processeur faire des calculs 1 sont aussi des ressources. Le systme dexploitation est un gestionnaire de ressources, cest--dire quil contrle laccs toutes les ressources de la machine, lattribution de ces ressources aux diffrents utilisateurs de la machine et la libration de ces ressources quand elles ne sont plus utilises. Ce contrle est capital lorsque le systme permet lexcution de plusieurs programmes en mme temps 2 ou lutilisation de la machine par plusieurs utilisateurs la fois. En particulier, il doit veiller ce quun utilisateur ne puisse pas effacer les chiers dun autre utilisateur ou ce quun programme en cours dexcution ne dtruise pas les donnes dun autre programme stockes en mmoire. Un autre aspect capital du contrle des ressources est la gestion des conits qui peuvent se produire quand plusieurs programmes souhaitent accder en mme temps aux mme donnes. Supposons par exemple quun utilisateur excute deux programmes, chacun deux crivant dans le mme chier. Il y a de grandes chances que, si aucune prcaution nest prise, le rsultat contenu dans le chier ne soit pas celui escompt. Si nous nous rfrons notre analogie automobile, le systme dexploitation en tant que gestionnaire de ressources est reprsent par le limiteur de vitesse qui oblige le conducteur conduire sagement ou par le systme darrt automatique en cas dendormissement qui empche le conducteur endormi dentrer en collision avec un mur.
Commodit versus efcacit

Les deux dnitions ci-dessus adoptent deux points de vue diamtralement oppos et le grand dilemme dans le dveloppement des systmes dexploitation a t le choix entre la commodit et lefcacit.
1. On parle gnralement de temps CPU (Central Processing Unit). 2. Nous dtaillerons plus loin ce quil faut comprendre exactement par ces termes.

57

Chapitre 2. Quest-ce quun systme dexploitation ? Certes il est agrable de disposer dun ordinateur commode, mais il ne faut pas oublier que les ordinateurs sont de plus en plus frquemment utiliss pour effectuer des tches critiques. Ainsi, autant il peut tre acceptable quun ordinateur individuel plante de temps en temps, autant il est rassurant de savoir que les ordinateurs contrlant les avions ou les oprations de chirurgie assiste sont dune stabilit toute preuve, quitte ce que cela soit au dtriment dun commodit dutilisation. Unix et Windows sont deux archtypes de systme ayant choisi chacun un des aspect au dtriment de lautre (au moins lorigine) : sous Unix, tout est interdit par dfaut et il faut explicitement autoriser les diffrentes actions (se connecter, avoir accs un chier, etc.) ; ceci permet aux systmes Unix dtre trs stable mais rend ces systmes dun abord difcile ; sous Windows, tout est autoris par dfaut et il faut explicitement interdire ce qui doit ltre ; ceci permet un accs facile aux systmes Windows mais rend trs difcile leur scurisation et leur stabilisation. Il convient donc, dune part, de garder ce dilemme lesprit pour effectuer des choix raisonnables en fonction des circonstances, dautre part, duvrer pour le dveloppement de systmes dexploitation qui soient la fois commodes et efcaces.
Mode utilisateur versus mode noyau

Nous avons aussi vu dans le chapitre prcdent que les processeurs fonctionnent dans deux modes diffrents : le mode noyau o toutes les instructions sont autorises et le mode utilisateur o certaines instructions sont interdites. Le systme dexploitation peut utiliser cette proprit pour faciliter le contrle quil exerce : il sexcute en mode noyau alors que tous les autres programmes sont excuts en mode utilisateur. Ces programmes utilisateur ont ainsi par essence des pouvoirs limits et certaines oprations leurs sont interdites. Par exemple, en nautorisant laccs aux diffrentes ressources de la machine quaux programmes sexcutant en mode noyau, le systme dexploitation protge ces ressources et contraint les programmes utilisateur faire appel lui (pas ncessairement de faon explicite) pour accder aux ressources de la machine.

2.2

Les appels systme

Sur les systmes dexploitation utilisant le mode noyau, tout programme utilisateur doit faire explicitement appel aux services du systme dexploitation pour accder certaines ressources. ces ns, le systme dexploitation propose une interface de programmation, cest--dire quil permet daccder un certain nombre de fonctionnalits quil excutera pour lutilisateur. Les appels systme sont linterface propose par le systme dexploitation pour accder aux diffrentes ressources de la machine. Par exemple, il est ncessaire de faire appel au systme dexploitation pour crer un chier sur le disque dur et cela via un appel systme comme ceux dtaills dans le 58

2.2. Les appels systme chapitre 11. Si nous demandons que ce chier soit cr dans un rpertoire qui nous est interdit daccs, par exemple un rpertoire appartenant un autre utilisateur, lappel systme va refuser de crer le chier.
Des appels systme trs nombreux

Le nombre dappels systme proposs varie suivant les systmes dexploitation et, pour un mme type de systme dexploitation (Unix par exemple), ce nombre diffre suivant les versions (4.4BSD, Linux, Irix, etc.). Ces appels retent les services que peut rendre le systme dexploitation et il sont gnralement classs en quatre catgories : gestion des processus ; gestion des chiers ; communication et stockage dinformations ; gestion des priphriques. Notons au passage que les trois premires catgories correspondent trois concepts sans ralit physique...
Le cas dUnix

Sous Unix, les priphriques sont grs comme de simples chiers ce qui reprsente labstraction absolue et il ny a donc que trois catgories dappels systme. Le manuel en ligne man 3 permet de connatre la liste des appels systme et le but de chaque appel : tous les appels systme se trouvent dans la section 2 du manuel. Les appels systme peuvent tre directement utiliss dans un programme C et il faut savoir que la plupart des fonctions de la bibliothque standard utilisent aussi ces appels systme. La deuxime partie de ce document donnera de nombreux exemples dutilisation des appels systme.
Excution dun appel systme

Un appel systme provoque en fait une interruption logicielle et il suft alors de programmer la machine pour que la routine correspondant linterruption fasse partie du systme dexploitation. En pratique, il est frquent que toutes les interruptions aient la mme routine associe : le systme dexploitation. Suivant le type dinterruption (matrielle ou logicielle) et suivant la nature de linterruption (erreur, travail termin, etc.), le systme dcide alors quelle action il doit entreprendre. Nous avons vu dans le chapitre prcdent que la gestion des interruptions est assez complexe et, notamment, que la coexistence des interruptions logicielles avec les
3. Un bon moyen dapprendre utiliser la commande est de faire man man. On dcouvrira alors que les pages de manuel sont organises en sections et le lecteur assidu apprendra certainement lutilisation de la commande man -k.

59

Chapitre 2. Quest-ce quun systme dexploitation ? interruptions matrielles pose de nombreux problmes de conit. En particulier, on peut se demander ce qui se passe lorsquune demande dinterruption matrielle a lieu au milieu dune interruption logicielle. La rponse cette question dpend non seulement de chaque systme dexploitation, mais aussi de limplantation dun mme systme sur plusieurs architectures diffrentes. Nous aborderons une partie de ce problme lors de ltude des signaux dans le chapitre 14. Nous avons vu aussi que les interruptions peuvent tre synchrones ou asynchrones. Cela veut donc dire quun appel systme peut donner lieu une requte dinterruption qui sera traite de faon asynchrone et que, par exemple, il se peut que le systme ait dautres choses plus importantes faire. Nanmoins, du point de vue du programme qui excute lappel systme, celui-ci se droule de faon synchrone, cest--dire que lappel systme interrompt lexcution du programme et celui-ci ne peut pas reprendre son excution tant que lappel systme nest pas termin. Toutefois, et an de ne pas trop pnaliser les programmes effectuant des appels systme, le systme dexploitation utilise gnralement des intermdiaires rapides et il rend la main au programme avant davoir effectivement accompli laction demande. Par exemple, si un programme demande lcriture de donnes dans un chier sur disque dur, le systme dexploitation va copier ces donnes dans une mmoire tampon qui lui appartient et va ensuite rendre la main au programme. Le systme dexploitation crira ces donnes sur le disque dur un autre moment. La seconde partie du document devrait clairer le lecteur sur lutilisation et le rle des appels systme. Pour linstant, il est capital de retenir les faits suivants : un appel systme permet de demander au systme dexploitation deffectuer certaines actions ; un appel systme interrompt le programme qui lexcute ; un appel systme est excut en mode noyau mme si le programme ayant demand son excution est excut en mode utilisateur. Lutilisation des appels systme illustre aussi un autre phnomne qui permet de dvelopper des systmes dexploitation efcaces et cohrents : le meilleur moyen pour faire intervenir le systme dexploitation est de dclencher une interruption !

2.3

Structure dun systme dexploitation

Noyau des systmes dexploitation

Le systme dexploitation dune machine nest en pratique pas constitu dun seul programme. Il se compose dau moins deux parties que lon nomme souvent le noyau (kernel en anglais), qui est le cur du systme (core en anglais), et les programmes systme. Le noyau est excut en mode noyau et il se peut quil corresponde plusieurs processus diffrents (2 ou 3 sous les systmes Unix, par exemple). Mme si le noyau effectue lessentiel des tches du systme dexploitation, ce dernier ne peut se rduire son noyau : la plupart des services quun utilisateur utilise sur une machine sont en fait des sur-couches du noyau. Les programmes systme qui 60

2.3. Structure dun systme dexploitation

Applications portables

Application
Environnement

Convivialit du travail Outils Interface Noyau


Applications spciques

Automatisation de lutilisation Demande de ressources Accs aux ressources matrielles

Matriel

Mode noyau

F IGURE 2.2 Reprsentation en couches des systmes dexploitation.

assurent ces services sont nanmoins excuts en mode utilisateur et, donc, ils doivent faire appel au noyau comme nimporte quel utilisateur pour accder aux ressources.
Le modle couches

La relation entre la machine, le systme dexploitation et les programmes des utilisateurs est toujours reprsente sous forme dempilement dun certain nombre de couches aux frontires bien dnies : le systme dexploitation reliant la machine et lutilisateur, il sintercale naturellement entre les couches reprsentant ces derniers. La gure 2.2 montre un exemple dune telle reprsentation. En pratique, il est toujours difcile de distinguer o nit le systme dexploitation et o commence lenvironnement utilisateur. Formul diffremment, il est toujours difcile de dire si un programme sexcutant en mode utilisateur fait partie du systme dexploitation ou pas. En revanche, la limite entre le noyau et les programmes systme est claire : le noyau est toujours excut en mode noyau ! Linterface propose par le noyau est lensemble des appels systme et, comme tout programme sexcutant en mode utilisateur, les programmes systme utilisent ces appels. Il est donc traditionnel dintercaler, dans notre modle couches, les appels systme entre le noyau et les programmes systme. De mme et pour les mmes raisons, il est traditionnel dintercaler le jeu dinstructions entre le matriel et le noyau. Lobjectif du modle couches est sloigner de plus en plus des spcicits de lordinateur et de son systme dexploitation pour sapprocher de plus en plus de labstraction fonctionnelle dont a rellement besoin lutilisateur. Sur les systmes bien conus, chaque couche sappuie ainsi sur la couche prcdente pour proposer de nouveaux services et il devient possible, par exemple, de transporter le mme environnement de travail dun systme dexploitation un autre : il suft pour cela que le dessus de la couche situe en dessous soit le mme. Il est donc tout fait imaginable de retrouver les mmes outils, le mme environnement et les mmes applications fonctionnant sur deux systmes dexploitation diffrents. Cela suppose juste que ces systmes soient bien faits. Rien sur le principe 61

Chapitre 2. Quest-ce quun systme dexploitation ?

Applications portables

Application
Environnement

Outils Interface Noyau


Applications spciques

Appels systme Jeu dinstructions

Matriel

F IGURE 2.3 Reprsentation dtaille en couches des systmes dexploitation.

ne permet donc dexpliquer pourquoi par exemple MS Word ne fonctionnerait pas sous Linux ! De la mme manire, il ny a aucune raison pour quun systme dexploitation impose un environnement de travail particulier et, sauf si ce systme est mal fait, il devrait par exemple tre possible de laisser chaque utilisateur le choix de son environnement graphique, le choix de son compilateur, etc.
Le systme dexploitation nintervient pas souvent

Lutilisation du modle couches pour reprsenter la structure des systmes dexploitation pourrait laisser croire quun systme dexploitation intervient systmatique, chaque accs aux ressources de la machine, et quil a donc besoin de beaucoup de temps CPU pour accomplir sa mission. Par exemple, an de contrler que laccs toutes les ressources de la machine seffectue dans de bonnes conditions, que ce soit pour lallocation de mmoire ou de temps CPU, lcriture de donnes, la lecture de donnes ou la libration dune ressource, nous pourrions envisager de faire appel au systme dexploitation pour chaque requte daccs. Ainsi un programme qui souhaiterait crire en mmoire pour affecter la valeur 3.0 la variable x devrait faire appel au systme. Si ce programme effectue une boucle de 10 000 affectations, il faudrait alors faire 10 000 fois appel au systme pour contrler laccs la mmoire. Ce nest bien entend pas le cas et ce ne serait dailleurs par raisonnable de procder ainsi : mme si aujourdhui ces machines ne cotent plus trs cher, il nest pas envisageable de les employer essentiellement pour faire fonctionner des systmes dexploitation. En dautres termes, il ne faut pas que le systme provoque une perte de puissance de la machine en voulant faciliter la vie des utilisateurs ou en cherchant contrler les accs aux ressources et il est indispensable de garantir la rapidit et lefcacit du travail des systmes dexploitation. 62

2.3. Structure dun systme dexploitation

utilisateur systme dexploitation matriel

F IGURE 2.4 Le systme dexploitation contrle les accs mais na pas besoin dintervenir tout le temps.

On pourrait alors songer travailler trs prs des couches physiques de la machine, mais nous avons vu que ce nest pas non plus une solution : certes cela permet de tirer pleinement partie de la puissance de lordinateur, mais tous les dveloppements ainsi conus ne sont valables que pour une machine donne. Une des cls pour un systme dexploitation russi rside donc dans sa capacit contrler tous les accs aux ressources en intervenant le moins souvent possible de faon dynamique. Ceci est possible grce des composants qui contrlent directement (i.e. sans intervention du systme dexploitation) que les accs sont licite en se fondant sur des autorisations daccs gres par le systme dexploitation. Le principe est similaire celui utilis dans le mtro pour la carte Orange : le guichetier de la station de Mtro (qui reprsente le systme dexploitation) nintervient quune seule fois par mois an de valider les droits daccs de lusager, aprs avoir vri que ledit usager a bien vers la somme dargent prvue ; chaque accs de lusager au mtro est contrl par des couches matrielles (les tourniquets dentre et de sortie des stations de mtro), sans que lusager nait besoin de solliciter le guichetier et dattendre que celui-ci soit disponible. La MMU dont nous avons parl dans le chapitre prcdent est un de ces composants : elle contrle laccs aux diffrentes zones de mmoire partir de tables de traduction dadresses et le systme a juste besoin dcrire de temps en temps dans ces tables pour assurer en permanence la protection de la mmoire entre les diffrentes tches. Cet exemple sera dvelopp dans le chapitre sur la gestion de la mmoire. Pour le moment, il faut simplement garder lesprit quil existe des moyens pour que le systme dexploitation contrle les accs aux ressources sans pour autant pnaliser le fonctionnement de la machine et que la reprsentation en couches permet de mettre en vidence les relations entre les diffrents composants des systmes dexploitation sans correspondre ncessairement la ralit. 63

Chapitre 2. Quest-ce quun systme dexploitation ?

2.4

Les diffrents types de systmes dexploitation

Dans cette section, nous prsentons les diffrentes structures des systmes dexploitation. Nous en protons pour essayer de dterminer o se situe exactement un systme dexploitation dans les diffrentes couches qui composent lenvironnement dun ordinateur.
Les systmes monolithiques

Le chapitre suivant retrace lhistorique des systmes dexploitation et montre que ceux-ci ont t dvelopps petit petit. Bien souvent, les dveloppeurs de tels systmes reprenaient les travaux mens par les dveloppeurs du systme prcdent et les compltaient. De gnration en gnration, le noyau des systmes dexploitation se mit donc grossir et cela mena vite des systmes difcilement exploitables. Ainsi, nimporte quelle procdure du noyau peut en appeler nimporte quelle autre et limplantation de nouveaux services est trs dlicate : une erreur un endroit du noyau peut entraner un dysfonctionnement un autre endroit qui, a priori, na rien voir. Ces inconvnients entranrent le dveloppement de systmes dexploitation fonds sur dautres modles.
Les systmes couches

Les systmes couches appliquent au noyau le principe des couches tel que nous lavons expliqu ci-dessus. Lide consiste charger chaque couche du noyau dune tche bien prcise et proposer une interface pour les couches au-dessus. Chaque couche masque ainsi les couches situes en dessous delle et oblige la couche situe au-dessus utiliser linterface quelle propose. Lavantage est vident : les couches sont totalement indpendantes les unes des autres et seule linterface entre chaque couche compte. Cela permet de dvelopper, de dbuger et tester chaque couche en se fondant sur les couches infrieures qui sont sres. Mme si ce principe est sduisant, il est trs complexe mettre en uvre et les systmes ainsi structurs sont souvent trs lourds et peu performants.
Les micro-noyaux

Nous avons vu que les noyaux des systmes monolithiques ont tendance tre volumineux. Par ailleurs, si un service particulier est trs rarement utilis, il doit nanmoins tre prsent dans le programme du noyau et il utilisera donc de la mmoire de faon inutile. Pour viter ces problmes, une solution consiste rduire le noyau quelques procdures essentielles et reporter tous les autres services dans des programmes systme. Le noyau ainsi rduit sappelle souvent micro-noyau. 64

2.4. Les diffrents types de systmes dexploitation Ce travail est nanmoins considrable car il suppose une refonte totale des systmes dexploitation habituels : ceux-ci ayant t dvelopps au l des ans, il se peut que des services trs importants, mais apparus tardivement, soient implants la priphrie du noyau et que, donc, lisolement de ces services demande la rcriture complte du noyau. Un autre inconvnient des micro-noyaux rside dans la communication entre les diffrents services. Cette partie fondamentale permet le dialogue entre le noyau et les services (tels que le rseau, laccs aux disques. Chaque service tant cloisonn dans son espace dadresses, il est impratif dtablir ce mcanisme de communication entre le micro noyau et les services. Cette couche de dialogue, qui nexiste pas dans un noyau monolithique, ralentit les oprations et conduit une baisse de performance. Cela permet nanmoins de clarier les choses et de concevoir des systmes trs gnraux se fondant uniquement sur des services essentiels, comme le partage du temps. La plupart des systmes micro-noyau en protent aussi pour inclure des services pour les machines multi-processeurs ou pour les rseaux de processeurs.

Les systmes mixtes

Certains systmes monolithiques tentent dallger leurs noyaux en adoptant quelques principes des systmes couches ou des micro-noyaux. Par exemple, le systme dexploitation Linux permet de charger de faon dynamique des modules particuliers qui sont agglomrs au noyau lorsque celui-ci en a besoin 4 . Ce procd est assez pratique car, dune part, il permet de ne pas rcrire le systme dexploitation et donc de proter de lexprience des systmes Unix et, dautre part, il minimise la mmoire utilise par le noyau : les modules ne sont chargs que lorsque cest ncessaire et ils sont dchargs quand le systme nen a plus besoin. Le systme dexploitation Windows NT 4.0 est aussi un systme mixte : son noyau (appel Executif par Microsoft) contient un gestionnaire dobjet, un moniteur de rfrences de scurit, un gestionnaire de processus, une unit de gestion des appels de procdures locales, un gestionnaire de mmoire, un gestionnaire dentres / sorties et une autre partie que Microsoft nomme le noyau, probablement pour semer la confusion. Dans les versions suivantes, les noyaux de Windows XP et de Windows Vista sont des noyaux hybrides. Celui de Vista, en raison du retard de dveloppement, a volu 5 vers un noyau module. Une partie importante des pilotes matriels qui sexcutaient auparavant en mode noyau sont maintenant relgus en mode utilisateur. Subsiste nanmoins peut-tre un regret, le fait que linterface graphique reste un des services du noyau.
4. Les systmes NetBSD et FreeBSD offrent aussi cette possibilit. 5. Il sagit de lpisode surnomm Longhorn Reset .

65

Chapitre 2. Quest-ce quun systme dexploitation ?

2.5

Les services des systmes dexploitation

Pour terminer ce chapitre qui prsente les systmes dexploitation, nous esquissons rapidement les diffrents travaux que doit effectuer un systme dexploitation moderne. Ces travaux sont gnralement nomms services et ils seront dtaills dans les chapitres suivants. La plupart de ces travaux sont pris en charge par le noyau du systme dexploitation. La gestion des processus La gestion des processus na de sens que sur les machines fonctionnant en temps partag. Elle comprend la cration et la destruction dynamique de processus. Le chapitre 5 est consacr la gestion des processus et les chapitres 10 et 14 montrent comment il est possible de crer et de dtruire des processus sous Unix. La gestion de la mmoire An de simplier la gestion des processus, les systmes dexploitation modernes travaillent dans un espace mmoire virtuel, cest--dire avec des adresses virtuelles qui doivent tre traduites pour correspondre des adresses physiques. Cela permet dallouer chaque processus (y compris au noyau) son propre espace mmoire de telle sorte quil a lillusion davoir la machine pour lui tout seul. La faon dont les processus utilisent la mmoire est dcrite dans le chapitre 5 sur la gestion des processus. Une autre partie de la gestion de la mmoire, trs diffrente de celle prsente ci-dessus, concerne lutilisation de pages de mmoire et la possibilit daccder la mmoire secondaire pour optimiser la mmoire utilise par tous les processus. Cette gestion est dtaille dans le chapitre 6. La gestion des entres / sorties Les entres / sorties permettent de faire transiter des donnes par lordinateur et dutiliser ces donnes pour faire des calculs. Ces donnes peuvent provenir de priphriques, de processus prsents sur la machine ou de processus prsents sur dautres machines (via un rseau). Nous discuterons brivement des priphriques dans le chapitre 7 consacr au systme de chiers. Les chapitres 15 et 16 consacrs respectivement aux tuyaux et aux sockets sous Unix montreront un exemple dutilisation des entres / sorties. Le systme de chiers Le systme de chiers est un lment essentiel des systmes dexploitation moderne : il permet daccder aux diffrents priphriques et il propose une interface abstraite pour manipuler des donnes. Mme si le systme de chiers fait souvent rfrence au disque dur, la notion de chier est beaucoup plus gnrale et nous la dtaillerons au chapitre 7. La gestion des communications entre machines Il est aujourdhui impensable de disposer dordinateurs usage professionnel sans que ceux-ci soient relis entre eux par un rseau local. Par ailleurs, lutilisation de rseaux internationaux 66

2.6. Conclusion : que retenir de ce chapitre ? comme lInternet se rpand et le systme dexploitation doit donc prendre en charge la gestion des communications par rseaux. Comme nous lavons annonc en introduction, nous ne traiterons pas des rseaux dans ce document (hormis une initiation au chapitre 16).

2.6

Conclusion : que retenir de ce chapitre ?

Ce chapitre a dtaill le rle des systmes dexploitation, les consquences qui en dcoulent pour lutilisation des ordinateurs et la liste des services que doivent rendre les systmes dexploitation. Il est important de retenir les points suivants : un systme dexploitation est un programme ; un systme dexploitation est une machine virtuelle plus facile programmer quune machine relle ; ce titre, il facilite laccs aux ressources de la machine et il permet de dvelopper des applications portables, qui ne sont pas spciques dun ordinateur ou dun systme donn ; un systme dexploitation est un gestionnaire de ressources qui attribue les ressources aux diffrents utilisateurs et qui empche laccs illicite cellesci ; ce titre, un systme dexploitation est garant du bon fonctionnement de la machine (abilit et stabilit) et de la conservation des donnes (pas de ramorages intempestifs, pas de perdre de donnes) ; les appels systme sont linterface que le systme dexploitation met la disposition des utilisateurs pour quils puissent lui demander des services ; sur les systmes dexploitation assurant leur rle de gestionnaire de ressources, les utilisateurs ne peuvent pas accder directement aux ressources de la machine et doivent ncessairement faire appel au systme dexploitation ; un systme dexploitation ne doit pas monopoliser le temps CPU de lordinateur quil gre et un bon systme dexploitation est capable deffectuer son travail (notamment la gestion de laccs aux ressources) sans avoir disposer du processeur (utilisation des couches matriels comme la MMU) ;

67

3
volution des ordinateurs et des systmes dexploitation

Les ordinateurs et les systmes dexploitation sont aujourdhui des objets complexes et il est parfois difcile de comprendre les motivations des personnes qui les ont dvelopps. Ainsi certains archasmes subsistent encore et, les raisons ayant motiv leurs dveloppements disparaissant, il nest pas rare de se demander pourquoi telle ou telle stratgie a t mise en place. Ce chapitre retrace lvolution des ordinateurs et des systmes dexploitation tout au long des quarante dernires annes et il devrait permettre de comprendre quels ont t les choix stratgiques importants. Outre cet aspect culturel de lvolution des ordinateurs, il est capital de bien comprendre que les systmes dexploitation ont tent de satisfaire les exigences des utilisateurs dordinateurs et que, donc, leur dveloppement nest pas luvre dune intelligence suprieure qui aurait tout plani, mais bien le rsultat des efforts parfois contradictoires de nombreuses personnes dans le monde entier. ce titre, ce serait une grossire erreur de croire que les systmes dexploitation nvoluent plus ou quil est difcile de mieux faire... Il est aussi capital de comprendre que les systmes dexploitation et larchitecture des ordinateurs ont volu ensemble et quil nest pas possible de concevoir lun sans lautre : les demandes des dveloppeurs de systmes dexploitation ont amen les 69

Chapitre 3. volution des systmes dexploitation constructeurs dordinateurs modier les architectures de ces derniers et les nouveauts technologiques des architectures des ordinateurs ont permis aux dveloppeurs de systmes dexploitation de progresser. Mots cls de ce chapitre : gestionnaire de priphriques (device driver), travail, tche, traitement par lots (batch), moniteur rsident, interprte de commande (shell), spool, tampon (buffer), ordonnancement, multiprogrammation, partage du temps, excution concurrente, multi-tches, multi-tches premptif, multi-utilisateurs.

3.1

Les origines et les mythes (16xx1940)

Les ouvrages sur linformatique situent souvent lorigine des ordinateurs au XVIIe ou au XVIIIe sicle en faisant rfrence Blaise Pascal ou Charles Babbage.

F IGURE 3.1 La pascaline et une de ses volutions, calculatrice invente par Pascal.

La pascaline (g. 3.1) permettait de raliser des additions et des soustractions. Mais comme lpoque ne connaissait pas encore le systme de mesures internationales et quil sagissait avant tout de simplier les calculs de la vie courantes, diffrentes versions taient proposes. Selon le nombre de dents de chaque roue on pouvait ainsi calculer en toises, en sols (avec 20 sols par livre monnaie), en livres (sachant que 12 onces font une livre poids), etc. La tabulatrice dHollerith (voir g. 3.2) lisait des cartes perfores et en traitait le contenu laide dun tableau de connexions. Ces connexions tant xes (dans les premiers temps), les cartes peuvent tre considres comme le jeux de donnes manipuler par un programme inscrit de manire permanente au travers de ces connexions. partir de 1920 le tableau de connexions sera amovible ce qui permettra de. . . changer de programme. Un parallle amusant serait de devoir changer le CPU selon le programme que vous souhaitez excuter !

3.2

La prhistoire (19401955)

Mme si les apports antrieurs aux annes 1940 sont importants, toutes les machines cres jusqualors taient des calculettes ou des calculateurs dots de composants 70

3.2. La prhistoire (19401955)

F IGURE 3.2 The tabulating machine, utilise pour le recensement des USA la n du XIXe sicle. Son inventeur (Hermann Hollerith) fonda pour loccasion lentreprise Tabulating Machine Corporation , devenue en 1911 Computing Tabulating Recording Corporation , puis en 1924 International Business Machines Corp (plus connue sous le nom dIBM).

F IGURE 3.3 Alan Turing (1912-1954) pose les bases fondamentales de linformatique thorique et sinterroge sur la calculabilit des algorithmes. En particulier, il cherche dterminer si tout ce qui est calculable humainement peut tre calcul par ordinateur et rciproquement. Lapplication directe de ses travaux lui permettront de casser le code de chiffrement utilis par les allemands pendant la Seconde Guerre mondiale (la machine Enigma).

mcaniques ou lectromcaniques. Nous considrerons ici que le premier ordinateur est la premire machine entirement lectronique et rellement programmable. Perscut en 1952 en raison de son homosexualit, Alan Turing dcdera dans lignorance de la communaut scientique anglaise qui lavait pourtant largement applaudit. En 1966 la cration du prix Turing rcompensant des travaux originaux dans le domaine informatique commencera rtablir sa place au sein de la communaut scientique. Cette rhabilitation se poursuivra en 2009 avec les excuses du Premier Ministre britannique Gordon Brown au nom de la justice anglaise pour les traitements inigs Alan Turing et ayant entran son dcs prmatur . 71

Chapitre 3. volution des systmes dexploitation

F IGURE 3.4 John von Neumann (1903-1957), le pre de linformatique moderne. Les concepts dicts par von Neumann dans les annes 40 continuent aujourdhui de rgir la conception des ordinateurs.

Le premier ordinateur fut construit en Pennsylvanie en 1946 et permettait deffectuer des calculs de tirs dartillerie. Il utilisait 18000 tubes vide et tait absolument gigantesque : 30 m de long, 2,80 m de haut et 3 m de large ! Dot dune mmoire de 20 mots de 10 chiffres, il tait capable deffectuer 5000 additions ou 350 multiplications par seconde.

F IGURE 3.5 ENIAC (Electronic Numerical Integrator And Computer), le premier ordinateur entirement lectronique (sans partie mcanique), oprationnel en fvrier 1946. Un doute subsiste encore aujourdhui sur les relles capacits dENIAC et certains pensent quil ne sagissait que dun gros calculateur.

La dure de vie des tubes vide tait trs faible et les ingnieurs avaient lpoque effectu le calcul de la probabilit de panne dun tel ordinateur. Le rsultat du calcul tant absolument effrayant 1 , ils dcidrent de grouper les tubes par cinq sur des rteliers
1. Les diffrents ouvrages traitant de ce sujet ne sont pas daccord sur le rsultat de ce calcul. Toutefois,

72

3.2. La prhistoire (19401955) et de changer tout le rtelier ds quun tube tombait en panne.

F IGURE 3.6 ENIAC consommait 150 kW, pesait 30 tonnes, contenait 18 000 tubes vide, 70 000 rsistances, 10 000 condensateurs et 6 000 commutateurs. . .

La programmation seffectuait directement en manipulant des interrupteurs et des contacteurs. Il ny avait donc pas de support pour mmoriser les programmes et la programmation devait donc tre refaite chaque changement de programme. Lavantage (le seul !) de ce procd est que, en cas derreur, lordinateur restait dans ltat qui avait conduit lerreur et il tait donc possible de faire du dbogage vif !

F IGURE 3.7 ENIAC tait programmable, mme si la programmation prenait lpoque une autre tournure.

Ces ordinateurs taient en fait entirement grs par un petit groupe de personnes et il ny avait pas de diffrence entre les concepteurs, les constructeurs, les programmeurs, les utilisateurs et les chargs de la maintenance. La notion de service tait donc totaletous saccordent pour dire que la dure moyenne sans panne de cet ordinateur tait de 1 2 minutes.

73

Chapitre 3. volution des systmes dexploitation ment absente et, comme par ailleurs les programmes devaient tre reprogramms chaque fois, la notion de systme dexploitation aussi.

3.3

Les ordinateurs transistor (19551965)

Dautres ordinateurs ont vu le jour sur le mme principe jusquen 1959, mais ils ont rapidement intgr des fonctionnalits indispensables comme les mmoires, les langages de programmation de type assembleur ou lutilisation de cartes perfores pour stocker les programmes.

F IGURE 3.8 EDVAC (Electronic Discrete VAriable Computer), successeur dENIAC, est indniablement un ordinateur (et non un calculateur).

F IGURE 3.9 UNIVAC (UNIVersal Automatic Computer) construit en 1951. UNIVAC tait sufsamment able et sufsamment abordable pour tre commercialis.

Les cartes perfores (voir gure 3.11) permettaient de rutiliser des programmes dj crits et, en particulier, de moins se soucier des entres / sorties : pour chaque priphrique, un programmeur crivait un petit programme permettant de lire et dcrire sur ce support et ce petit programme tait ensuite inclus dans des programmes plus gros. Ces petits programmes sappellent des gestionnaires de priphriques (device 74

3.3. Les ordinateurs transistor (19551965) driver) et reprsentent la premire notion de service, donc la premire tape vers un systme dexploitation. Lapparition des transistors (voir gure 3.10) a permis de construire des ordinateurs plus puissants et surtout plus petits. Par ailleurs, les transitors taient beaucoup plus ables que les tubes vide et il devenait envisageable de vendre des ordinateurs ainsi construits. Cette poque voit donc la sparation entre, dune part, les concepteurs et les constructeurs et, dautre part, les programmeurs et les utilisateurs.

F IGURE 3.10 Les premiers transistors.

Lutilisation de cartes perfores stant gnralise, la notion de travail ou de tche effectuer est apparue. Lorsquun programmeur voulait excuter une tche, il apportait la carte perfore correspondante (ou les cartes perfores correspondantes) la personne charge de lexcution. Cette personne collectait ainsi les diffrentes cartes de programme du centre de calcul, les chargeait dans lordinateur, dclenchait manuellement leur lecture, puis leur excution. Lorsque le programme tait crit dans un langage de haut niveau (pour lpoque) comme le FORTRAN ou le COBOL, loprateur devait en plus se munir de la carte perfore du compilateur et assurer quelle serait charge avant la carte perfore du programme.

F IGURE 3.11 Une carte perfore utilise lE NSTA ParisTech.

partir de cette poque, les programmeurs et les utilisateurs ntaient plus les mmes personnes, mme sil est aujourdhui difcile de considrer loprateur charg 75

Chapitre 3. volution des systmes dexploitation du fonctionnement de lordinateur comme un utilisateur. Par ailleurs, il ntait plus possible de dboguer les programmes en temps rel et il devenait donc ncessaire dimprimer toutes les informations utiles pour un dbogage a posteriori. Ces manipulations faisant intervenir un oprateur humain taient trs lentes et lordinateur passait lessentiel du temps attendre quon le nourrisse. Comme chaque ordinateur cotait trs cher (de lordre du million de dollars), il tait capital que son utilisation soit optimise an de rentabiliser linvestissement. Cest ce quon appelle gnralement le retour sur investissement . Notons que la programmation de gestionnaires de priphriques rutilisables permet dj damliorer lutilisation de lordinateur : cela vite, dune part, que chaque programmeur perde du temps reprogrammer ces fonctionnalits et cela assure, dautre part, que le gestionnaire utilis est optimal (et non programm la va-vite...).

Le traitement par lots (batch)

An de limiter les manipulations de cartes perfores, la premire tape fut de regrouper les travaux par lots. Plutt que de charger les cartes dans nimporte quel ordre et dtre oblig de recharger plusieurs fois les cartes perfores des compilateurs, loprateur regroupait tous les travaux du centre de calculs et les sparait en diffrents lots correspondant aux diffrents langages utiliss : un pour le FORTRAN, un pour le COBOL, etc. La carte perfore du compilateur FORTRAN (par exemple) ntait alors charge quune seule fois dans la journe.

Compilateur FORTRAN Programme FORTRAN Compilateur COBOL Programme COBOL Compilateur FORTRAN Programme FORTRAN

Compilateur FORTRAN Programme FORTRAN Programme FORTRAN Compilateur COBOL Programme COBOL

F IGURE 3.12 Principe du traitement par lots : regrouper les travaux ayant besoin des mmes cartes perfores.

Le tri des cartes perfores tait nanmoins toujours assur par loprateur et ce dernier continuait de dclencher manuellement la lecture de ces cartes et lexcution des travaux. 76

3.3. Les ordinateurs transistor (19551965)


Le moniteur rsident

An de rduire encore lintervention de loprateur, la lecture des cartes perfores et lenchanement des travaux ont t automatiss grce un petit programme appel moniteur rsident . Son nom provient du fait quil rsidait en permanence dans la mmoire (voir gure 3.13), ce qui lpoque tait une premire : jusque l, la mmoire tait entirement utilise par le travail en cours et tait vide la n de celui-ci.
Chargeur Moniteur rsident Enchanement de travaux Interprte de cartes de contrle

Zone disponible pour l'utilisateur

Mmoire de la machine

F IGURE 3.13 Occupation de la mmoire par le moniteur rsident.

Il fallait nanmoins indiquer au moniteur rsident le travail effectuer et des cartes perfores particulires des cartes de contrle taient utilises cet effet : elles indiquaient le dbut et la n dun travail, le nom du compilateur appeler, le chargement des donnes et le dbut de lexcution proprement dite. Ces cartes taient intercales entre les cartes perfores du programme ou des donnes (voir gure 3.14) et reprsentent le premier interprte de commande.
$END Donnes $RUN $LOAD Programme $FNT $JOB

F IGURE 3.14 Utilisation de cartes perfores de contrle pour spcier le travail accomplir.

Le travail de loprateur se rduisait alors trier les cartes perfores pour effectuer le traitement par lots et ajouter les cartes de contrle. Le moniteur rsident assurait 77

Chapitre 3. volution des systmes dexploitation trois tches distinctes (le chargement des cartes, lenchanement des travaux et linterprtation des commandes) et reprsente le premier systme dexploitation de lhistoire de linformatique.
Le traitement hors-ligne (off-line)

Mme en utilisant un moniteur rsident, lordinateur perdait beaucoup de temps en lisant les cartes perfores et en imprimant les rsultats. Peu peu les bandes magntiques, beaucoup plus rapides, remplacrent les cartes perfores et les imprimantes, ce qui amliora nettement les temps dentres / sorties. Toutefois, il fallait dabord recopier les cartes perfores des programmes sur une bande magntique, puis, une fois tous les travaux effectus, relire la bande pour imprimer les rsultats.
Bandes magntiques

Cartes perfores

IBM 1401

Imprimante

Bandes magntiques

Bandes magntiques moniteur rsident

IBM 7094

Bandes magntiques sortie

F IGURE 3.15 Le principe du traitement hors-ligne.

Comme il ntait pas envisageable dutiliser des ordinateurs de calcul pour effectuer ces traitements hors-ligne, de petits ordinateurs, trs mauvais pour les calculs, taient utiliss pour ces transferts des cartes vers les bandes et des bandes vers limprimante. Cette pratique imposa petit petit lide de priphriques indpendants et autonomes qui prennent en charge lessentiel du travail dentres / sorties. Pourquoi ne pas crire les programmes directement sur les bandes magntiques ? Les bandes magntiques sont des systmes accs squentiel, cest--dire que pour lire une donne situe au milieu de la bande, il faut dabord la rembobiner, puis la lire de bout en bout jusqu ce quon trouve la donne en question. Lutilisation de cartes perfores permet donc plusieurs programmeurs dcrire en mme temps leurs programmes (chacun sur sa carte et chacun sur son ordinateur), ce qui serait totalement impossible avec des bandes magntiques. 78

3.4. Les circuits intgrs (19651980)


Le traitement diffr des entres / sorties (spool)

Le caractre squentiel des bandes magntiques tait trs pnalisant et entrana le dveloppement dun priphrique accs alatoire, cest--dire pour lequel laccs nimporte quelle donne est direct. Ce priphrique sappelle le disque dur et est devenu depuis un des priphriques les plus courants.

F IGURE 3.16 Le premier disque dur.

Les disques durs se sont rapidement rpandus et ont tout naturellement relgu au rang de stockage secondaire les bandes magntiques (elles sont toujours utilises pour raliser des sauvegardes). Le traitement hors-ligne disparut en mme temps au prot du spool : Simultaneous Peripheral Operation On Line. Les cartes perfores sont alors directement copies sur le disque dur et lordinateur y accde quand il en a besoin. De mme, lorsque lordinateur effectue un travail, il crit le rsultat sur le disque dur et il ne lenvoie limprimante que lorsquil a termin ce travail. Aujourdhui, le terme de spool perdure pour les travaux envoys limprimante mais a disparu pour la plupart des entres / sorties : on parle dsormais de buffering, cest--dire de lutilisation de mmoires tampon (buffer) pour accumuler les entres / sorties avant de les envoyer aux priphriques concerns. La diffrence entre le spool et lutilisation de buffer tient dans la petite taille des buffer. Par exemple, lorsquun programme demande limpression lcran dune ligne de texte, cette ligne est stocke dans une mmoire tampon et nest imprime que lorsque ce tampon est plein ou lorsque le programmeur en fait la demande explicite 2 .

3.4

Les circuits intgrs (19651980)

Le traitement hors-ligne des entres / sorties entrana le dveloppement de deux gammes dordinateurs diffrentes : dune part, des gros ordinateurs trs coteux spcialiss dans les calculs et, dautre part, des petits ordinateurs beaucoup moins chers
2. Parfois sans le savoir...

79

Chapitre 3. volution des systmes dexploitation spcialiss dans les entres / sorties. La premire gamme tait essentiellement utilise par larme, les universits ou la mtorologie nationale. La seconde gamme tait utilise par les banques, les assureurs et les grandes administrations. Ces deux gammes taient malheureusement incompatibles et une banque dsireuse dacheter du matriel plus perfectionn ne pouvait pas changer de gamme sans au pralable re-dvelopper tous les programmes quelle utilisait. Les constructeurs dordinateur dcidrent alors de produire des ordinateurs compatibles, ce qui fut notamment possible grce lutilisation de circuits intgrs. La coexistence de ces deux gammes reprsente bien les deux approches des systmes dexploitation : les gros ordinateurs devaient tre utiliss au mieux pour faire des calculs et tous les efforts tendaient vers lefcacit des traitements ; rciproquement, les petit ordinateurs devaient tre pratiques dutilisation et tous les efforts tendaient vers la commodit. Le dilemme efcacit versus commodit persiste encore de nos jours et, paradoxalement, les systmes dexploitation se sont essentiellement dvelopps sur les petits ordinateurs.
Ordonnancement dynamique

Lordonnancement des travaux est lordre dans lequel ces travaux vont tre excuts par lordinateur. Cet ordonnancement tait jusqualors dtermin par loprateur lorsque celui-ci triait les cartes perfores. Puis, les cartes tant recopies sur des bandes magntiques, cet ordonnancement tait x une fois pour toutes et lordinateur tait contraint dexcuter les travaux dans lordre dans lequel ils arrivaient. Avec lutilisation des disques durs, lordinateur est capable de choisir lui-mme lordre dans lequel les tches vont tre excutes. Si, par exemple, les tches A, B et C doivent tre excutes et si la tache B a besoin des rsultats de C, lordinateur pourra dcider dexcuter C avant B. Lordre dans lequel les programmes sont crits sur le disque dur et lordre dans lequel ils sont excuts nont donc plus rien voir. Une des tches du systme dexploitation est donc de dterminer cet ordre.
Multiprogrammation

partir du moment o le systme dexploitation peut choisir lordre dans lequel les tches sexcutent et dans la mesure o deux tches peuvent coexister en mmoire (le systme dexploitation plus la tche courante), il devenait possible de charger plusieurs tches en mmoire et de passer de lune lautre. Lide, comme toujours, tait de ne pas laisser lordinateur inoccup. Or les temps dentres / sorties taient assez longs (quelques milli-secondes) par rapport aux temps de raction des ordinateurs et une faon de rentabiliser la machine consistait excuter une autre tche pendant que la tche en cours attendait des donnes stockes (ou stocker) sur des priphriques. Par exemple, si une tche doit afcher lcran des lignes de texte, lordinateur a le temps entre deux afchages successifs dun caractre deffectuer quelques calculs. Ce principe sappelle la multiprogrammation et il ne faut pas le confondre avec le 80

3.4. Les circuits intgrs (19651980) temps partag que nous dcrirons juste aprs : ds que la tche a termin ses entres / sorties, elle reprend la main pour effectuer les calculs sans tre interrompue jusquaux prochaines entres / sorties.

Processus C

Processus B

Processus A entre / sortie Temps

F IGURE 3.17 Principe de la multi-programmation : un processus attendant une entre / sortie nutilise plus le processeur et celui-ci est affect un autre processus.

La commutation dune tche lautre suppose que le systme dexploitation est capable de sauvegarder toutes les informations et toutes les donnes ncessaires lexcution des deux tches. Cela suppose aussi que le systme est capable de protger les tches entre elles, cest--dire quil est capable dempcher chaque tche daccder aux zones mmoires utilises par les autres, y compris celles quil utilise lui-mme ! Les systmes dexploitation multiprogrammation ont surtout t utiliss pour les petits ordinateurs destins aux banques, aux assureurs et aux administrations. En effet, les gros ordinateurs taient essentiellement utiliss pour des calculs scientiques et les temps dentres / sorties taient trs faibles par rapport aux temps de calculs. Pour des entreprises grant des comptes bancaires ou des dossiers de clients, ctait bien entendu linverse !
Le partage du temps

La multiprogrammation permettait de ne pas perdre trop de temps en attendant les entres / sorties, mais le fonctionnement des ordinateurs tait encore et toujours fond sur le traitement par lots. Avec la disparition des bandes magntiques et des cartes perfores, il devenait difcile de justier ce procd et de nombreux dfauts le rendaient trs pnible au quotidien. En particulier, un programmeur qui voulait tester son programme devait attendre que celui-ci soit slectionn par le systme dexploitation, ce qui pouvait prendre des heures si les travaux en cours duraient trs longtemps. En cas derreur, non seulement le programmeur ne pouvait pas dboguer le programme directement, mais en plus il devait remettre son programme (aprs 81

Chapitre 3. volution des systmes dexploitation correction) dans la le dattente. Tester et dboguer un programme prenait donc des jours et des jours, car la moindre erreur supposait une attente de quelques heures. Il parat difcile aujourdhui de se reprsenter le temps ainsi perdu, mais imaginons que lors de lcriture dun gros programme, nous perdions ne serait-ce quune demiheure pour tout point-virgule oubli ou pour toute accolade mal place...
criture du programme Programme dans la machine Attente Programme trait Bug ! Modication du programme 1 heure ? 2 heures ?

F IGURE 3.18 La mise au point fastidieuse des programmes sur des ordinateurs traitement par lots...

Ces contraintes ont donc fait place la notion dinteractivit. Lide consistait largir le principe de la multiprogrammation an de donner lillusion aux utilisateurs de disposer de toute la machine pour excuter leur tche. Pour cela, chaque tche dispose dun petit laps de temps pour sexcuter, puis est contrainte de passer la main la tche suivante, et ainsi de suite jusqu ce que la main revienne la premire tche. Ainsi, si les laps de temps sont assez petits, lutilisateur a la sensation que, pour une dure donne, plusieurs tches sexcutent de faon concurrente.

Processus C

Processus B

Processus A

Temps

F IGURE 3.19 Principe de lexcution en temps partag.

Il ne faut pas confondre lexcution concurrente de tches avec lexcution parallle : lors dune excution concurrente, tout instant, une seule et unique tche 82

3.4. Les circuits intgrs (19651980) sexcute sur lunique processeur de lordinateur et ce nest que lorsquon observe la situation une chelle de temps plus grande (de lordre de la seconde, par exemple) quon a lillusion que plusieurs tches sexcutent en mme temps. En revanche, si on utilise un ordinateur dot de plusieurs processeurs, il est possible dexcuter plusieurs tches en parallle, cest--dire qu un instant donn chaque processeur nexcutera quune seule tche, mais lensemble des processeurs de lordinateur excuteront bel et bien plusieurs tches en mme temps. Notons que rien ninterdit dexcuter des tches de faon concurrente et parallle sur un ordinateur multi-processeurs.

Processus C

Processus B

Processus A

Temps

F IGURE 3.20 Lexcution en temps partag inclut la multi-programmation et un processus peut trs bien perdre la main avant la n du quantum de temps qui lui avait t affect : il suft pour cela quil ait besoin dune entre / sortie.

Par abus de langage, les systmes dexploitation permettant dexcuter les tches en temps partag sont appels multi-tches, mais ce terme est assez ambigu et il est prfrable de ne pas lemployer. Nous verrons dailleurs dans la section sur les systmes dexploitation actuels que certains vendeurs de systmes jouent sur cette expression pour tromper le client. Avec un systme temps partag, la commutation dune tche lautre doit tre trs rapide car elle est effectue plusieurs fois par seconde. Il devient donc capital dacclrer le procd de sauvegarde qui permet de restaurer ltat dans lequel tait une tche avant dtre interrompue pour cder la place une autre tche. En pratique, cette opration, appele changement de contexte, est essentiellement prise en charge par les couches matrielles de la machine. La protection des tches entre elles devenait aussi de plus en plus importante et les systmes temps partag ont rapidement impos lutilisation de la mmoire virtuelle et des modes noyau ou utilisateur des processeurs. Ceci a dailleurs frein leur propagation car beaucoup dordinateurs de lpoque taient dots de processeurs qui ne disposaient pas de ce double mode dexcution et il tait bien sr impensable de racheter des ordinateurs uniquement pour offrir un peu dinteractivit aux program83

Chapitre 3. volution des systmes dexploitation meurs (noublions pas que le temps ordinateur a longtemps cot plus cher que le temps programmeur).

F IGURE 3.21 Le principe dinteractivit permet dutiliser des priphriques pour contrler le fonctionnement de lordinateur. Ici la premire souris.

Unix
Le MIT, les laboratoires Bell et la compagnie General Electrics dcidrent de construire une machine sur laquelle pourraient se connecter des centaines dutilisateurs la fois. Le systme dexploitation de cette machine sappelait MULTICS (MULTiplexed Information and Computing Service). Il comportait des innovations intressantes et intgrait lessentiel des services assurs jusqualors par lensemble des systmes dexploitation. Malheureusement, le projet MULTICS tait trop ambitieux et fut abandonn. Entre temps, les mini-ordinateurs avaient vu le jour : le premier de ces ordinateurs tait le DEC PDP-1, n en 1961, qui pour 120 000 dollars offrait une capacit de 4096 mots de 8 bits. Cet ordinateur remporta un franc succs et DEC produisit dautres modles, tous incompatibles entre eux, ce qui permit de faire les meilleurs choix technologiques pour chaque gnration. Parmi ces ordinateurs, le PDP-7 et le PDP-11 sont les plus connus. Ken Thompson travaillait chez Bell et avait particip au projet MULTICS. Il dcida den crire une version simplie, en particulier mono-utilisateur, pour son PDP-7. Cette version fut baptise ironiquement UNICS (UNiplexed...) par Brian Kernighan, puis son nom se transforma en Unix. Le rsultat tant trs satisfaisant, les laboratoires Bell achetrent un PDP-11 et Thompson y implanta son systme. Dennis Ritchie 3 , qui conut et ralisa le langage C, travaillait aussi chez Bell et il rcrivit avec Thomson le systme Unix en C. Comme le C est un langage de haut niveau facile porter de machine en machine, Unix fut rapidement adapt de nombreuses architectures diffrentes et devint le systme dexploitation le plus port de tous les temps.
3. Dennis Ritchie est dcd dbut octobre 2011. Si sa mort ne t pas la couverture des journaux comme celle de Steve Jobs, son inuence, aux dires des historiens, fut largement comparable.

84

3.5. Linformatique moderne (19801995) Unix est une marque dpose des laboratoires Bell, mais ceux-ci distriburent (quasi-)librement les sources dUnix pendant un certain temps. Puis, aprs avoir pris conscience de la valeur marchande dun tel systme, ils dcidrent de le commercialiser. Le nom dUnix tait dj connu, mais ntait plus utilisable (sauf par Bell). Cest pourquoi de nombreux systmes dexploitation de type Unix ont vu le jour sous des noms trs diffrents, mais gnralement de consonance proche : Minix, Ultrix, Irix, Linux...

F IGURE 3.22 Les mini-ordinateurs Dec PDP-1 et PDP-7.

3.5

Linformatique moderne (19801995)

Linformatique moderne se traduit essentiellement par la gnralisation de lutilisation des ordinateurs. Paradoxalement, cette gnralisation sest faite au mpris des systmes dexploitation, car comme il devenait possible de donner un ordinateur chaque utilisateur, le rle du systme dexploitation a vite t sous-estim. Ainsi, de nombreuses socits ont prfr acheter de grandes quantits de petites machines dotes de systmes dexploitation rudimentaires plutt que de croire en lutilisation intelligente de loutil informatique. Ces choix, datant des annes 1980, continuent et continueront de pnaliser les socits ainsi dotes et de favoriser leur inertie face aux innovations. Comme ces socits ont fond leur stratgie sur des principes, sur des langages de programmation, sur des outils ou sur des systmes dexploitation qui, depuis, sont compltement obsoltes, elles sont confrontes deux choix pour le futur : ou bien elles continuent malgr tout utiliser ces outils du pass sachant que a leur revient trs cher (aujourdhui le temps programmeur est beaucoup beaucoup plus cher que le temps ordinateur), ou bien elles dcident de se mettre jour et cela suppose que tous les dveloppements thsauriss pendant ces 20 dernires annes seront mis la poubelle. Cela revient repartir zro ! 85

Chapitre 3. volution des systmes dexploitation


Les ordinateurs personnels

Les ordinateurs personnels datent du dbut des annes 1980. On parle parfois de micro-ordinateurs, mais cette considration est aujourdhui dpasse : la taille dun ordinateur nest pas proportionnelle sa puissance. Il est dailleurs difcile de distinguer de nos jours un ordinateur personnel dun ordinateur professionnel. Les ordinateurs personnels sont naturellement groups en deux catgories : les PC et les Macintosh. Lorigine de cette distinction nest pas dtermine et, une fois de plus, il devient aujourdhui trs difcile de distinguer un PC dun Mac. Dailleurs, bon nombre de personnes pensent quun PC est un ordinateur qui excute les programmes Windows ou Windows XP, ce qui na rien voir !

MS-DOS et Unix
Les deux systmes dexploitation qui se sont propags pendant cette priode sont MS-DOS et Unix. Une fois de plus, ces deux systmes reprsentent les deux extrmes des systmes dexploitation : la commodit contre lefcacit. MS-DOS est un systme rudimentaire trs facile utiliser. Unix est un systme complexe et redoutablement efcace, mais de prime abord parfois rude. Peu peu, MS-DOS sest rapproch dUnix et Unix a fait des efforts pour devenir plus accessible. Nous aborderons lavenir de ces deux systmes dans la section sur les systmes actuels.
Les rseaux dordinateurs

La multiplication des ordinateurs a permis de les connecter et de crer des rseaux dordinateurs. La gestion des communications entre ordinateurs est alors une des nouvelles tches des systmes dexploitation.
Les machines (massivement) parallles

Des ordinateurs contenant plusieurs dizaines, voire plusieurs milliers, de processeurs on vu le jour. Sur le principe, ils se rapprochaient en fait des vieux ordinateurs de calcul, car ils taient trs coteux et il fallait tout prix les rentabiliser. Ainsi, le traitement par lots a eu un sursaut de vie lorsque ces machines taient la mode. Aujourdhui cette mode est rvolue et les machines de demain devraient tre dotes de quelques processeurs seulement (2, 4, 8 ou 16). En revanche, il est exclu dutiliser ces machines sans un systme dexploitation multi-utilisateurs temps partag.

3.6

Les systmes dexploitation daujourdhui

Dans cette section, nous prsentons rapidement les systmes dexploitation que lon trouve couramment aujourdhui sur les ordinateurs. Les caractristiques prsentes 86

3.6. Les systmes dexploitation daujourdhui

F IGURE 3.23 La Thinking Machine contenait 65536 processeurs rpartis dans un hypercube, savoir les processeurs taient la mme distance les uns des autres, par le biais dun rseau de routage inter-processeurs. Elle pouvait toutefois tre utilise en mode asynchrone pour simuler des cas rels tels que les grands rseaux lectriques.

ici sont susceptibles de ne pas tre totalement dactualit car les systmes voluent assez rapidement de version en version. Avant de dcrire les systmes, nous faisons un rapide point sur le vocabulaire la mode.

Vocabulaire et dnition

Il nest pas possible de dsosser tous les systmes dexploitation que lon rencontre, ne serait-ce que parce que la plupart dentre eux (tous les systmes commerciaux) ne sont pas fournis avec leurs sources. Pour parler des capacits des systmes, il faut donc se mettre daccord sur un vocabulaire commun et faire conance aux opinions des dveloppeurs ou des utilisateurs des systmes. Comme justement beaucoup de constructeurs ou de vendeurs de logiciels jouent sur lambigut des mots pour imposer leurs productions, nous allons ici essayer de dnir clairement les notions que nous avons nonces dans les sections ci-dessus.

Le moniteur rsident

Le moniteur rsident reprsente le strict minimum du travail dun systme dexploitation. Les seuls services assurs sont linterprtation des commandes, lenchanement des tches et laccs simpli aux priphriques. Il parat impensable aujourdhui dutiliser encore des moniteurs rsidents pour contrler des ordinateurs employs professionnellement. 87

Chapitre 3. volution des systmes dexploitation


Les systmes multi-tches

Comme nous le disions plus haut, le terme multi-tches est mal choisi et il faut en fait lui prfrer lexpression temps partag . Malheureusement, le terme multi-tches sest impos et il est difcile de revenir en arrire. Un systme temps partag est un systme qui peut excuter plusieurs tches pendant une dure donne. Chaque tche dispose du processeur pendant un bref laps de temps, puis est interrompue pour laisser le processeur une autre tche (et ainsi de suite).
Les systmes multi-tches premptifs

La notion de multi-tches premptif ne devrait pas exister et nous pouvons considrer quun systme qui ne serait pas multi-tche premptif ne mrite pas le nom de multi-tches. Pourquoi alors cette distinction ? En fait, certaines socits commerciales qui ntaient pas capables de produire un systme dexploitation multi-tches ont dvelopp des systmes qui donnaient lillusion du multi-tches 4 . Ces systmes, plutt que dtre appels faux multi-tches , ont conserv le nom de multi-tches et il a donc fallu trouver un autre nom pour les vrais systmes multi-tches. Un systme multi-tches non premptif ne peut pas interrompre lexcution dune tche en cours et il doit attendre que celle-ci rende spontanment la main pour attribuer le processeur une autre tche. Donc si une tche dcide de garder le processeur pour elle pendant des heures, rien ne len empche ! Mais il est toujours possible de choisir deux ou trois programmes rendant spontanment et rgulirement la main pour faire de jolies dmonstrations... Inversement, un systme multi-tches premptif dcide quand il interrompt une tche pour attribuer le processeur une autre tche. Choisir la prochaine tche excuter nest pas simple et les diffrentes politiques dordonnancement sont trs intressantes tudier. Cest ce quon appelle lordonnancement dynamique des processus (scheduling en anglais). Il est bien entendu beaucoup plus facile de demander gentiment une tche de rendre la main plutt que de linterrompre dautorit !
Les systmes multi-utilisateurs

Il ne faut pas confondre les systmes multi-tches et les systmes multi-utilisateurs, mme si souvent les deux vont de pair. Un systme multi-utilisateurs est un systme sur lequel plusieurs utilisateurs peuvent travailler, mais pas ncessairement de faon interactive. Par exemple, il existe des systmes multi-utilisateurs traitement par lots. Un systme multi-utilisateurs doit protger les donnes de chaque utilisateur sur les priphriques support non volatile, comme par exemple, les chiers sur disque dur.
4. Sachant que, par dnition, le multi-tches donne dj lillusion du paralllisme, ces systmes donnent donc lillusion de lillusion du paralllisme...

88

3.6. Les systmes dexploitation daujourdhui Cependant, plusieurs tches du mme utilisateur peuvent accder aux mmes donnes sur disque dur. Cette protection est donc trs diffrente de celle de la mmoire dcrite ci-dessous.
La mmoire virtuelle

Les systmes mmoire virtuelle permettent, dune part, dutiliser plus de mmoire que lordinateur na de mmoire principale et, dautre part, de ne pas utiliser explicitement des adresses physiques pour accder aux donnes.
La protection de la mmoire

Les systmes temps partag doivent protger les tches les unes des autres et, en particulier, ils doivent se prserver eux-mmes des intrusions des autres tches. Lorsquun systme protge la mmoire utilise par les diffrentes tches, il lui est toujours possible de garder la main, mme lorsquune tche commet une erreur et sarrte de fonctionner normalement. Ainsi, il est facile de reconnatre les systmes nutilisant pas de protection de mmoire : si un programme plante, il est gnralement ncessaire de rinitialiser la machine (reboot en anglais). La protection de la mmoire va gnralement de pair avec les systmes multi-tches et lutilisation de la mmoire virtuelle.
Les systmes de type Unix

Il est paradoxalement trs difcile de dnir ce quest un systme dexploitation de type Unix. Pendant longtemps, les systmes Unix taient les seuls tre multi-tches (premptif, bien-sr !), multi-utilisateurs, avec mmoire virtuelle et protection de la mmoire. Avec lapparition rcente de systmes offrant les mmes caractristiques, la question de ce quest un systme Unix se pose. Est-il dni par linterface quil offre aux utilisateurs et aux programmeurs ? Est-il dni par la faon dont il est programm ? Est-il dni par la philosophie de gestion des priphriques ? Cette question reste ici sans rponse et nous nous contenterons de citer quelques systmes dexploitation de type Unix : 4.4BSD : systme dvelopp par luniversit de Berkeley et dont une grande partie est gratuite ; Minix : systme gratuit dvelopp partir de la version 7 de lUnix des laboratoires Bell ; Linux : ce systme est gratuit et peut tre install sur de nombreuses machines, comme les PC, les MacIntosh, les DEC alpha ou les stations de travail SUN ; NetBSD : tout comme Linux, ce systme fond sur 4.4BSD est gratuit et il peut tre install sur un grand nombre de machines ; 89

Chapitre 3. volution des systmes dexploitation OpenBSD : issu de NetBSD, il en conserve les caractristiques mais se focalise sur les architectures de types PC et SPARC. Ses objectifs sont avant tout la scurit et la cryptographie des donnes ; FreeBSD : fond sur 4.4BSD, ce systme est aussi gratuit et le plus rpandu de la famille BSD ; Irix : systme utilis sur les stations de travail SGI ; Solaris : systme utilis sur les stations de travail SUN ou plutt Oracle ; HP-UX : systme utilis sur les stations de travail HP ; AIX : systme dvelopp par IBM ; Ultrix : systme utilis sur les stations Digital VAX et Decstation a base de processeur MIPS ; True64 Unix : autrefois appel OSF/1 puis Digital Unix, ce systme est utilis sur les DEC Alpha.
Les systmes dexploitation de Microsoft

La socit Microsoft a dvelopp plusieurs systmes dexploitation qui ont eu beaucoup de succs : MS-DOS, Windows, Windows 95, Windows 98, Windows NT, Windows XP, Windows Server 2000, Windows Vista, Windows Seven. . . MS-DOS Le principal systme dexploitation fonctionnant sur des PC est MS-DOS (Disc Operating System). Initialement, en 1980, le systme dexploitation des PC tait le CP/M 80, dvelopp par Digital Research. Ce systme tait conu pour des processeurs 8 bits et ne convenait donc pas au tout nouveau processeur 8086. Digital Research annona alors le dveloppement du CP/M 86 adapt au processeur 8086, mais la sortie de ce systme tarda, et un programmeur, Jim Paterson, dveloppa en 1981 un nouveau systme dexploitation, de 6 Ko seulement, quil nomma 86-DOS. Peu aprs Microsoft produisit MS-DOS. Prcisons que Microsoft na pas dvelopp la premire version de MS-DOS mais la achete. Les premires versions de MS-DOS restaient compatibles avec le CP/M 80, ce qui ne facilitait pas leurs dveloppements. En 1983 et 1984 naquirent respectivement les versions 2.0 et 3.0 de MS-DOS qui sloignaient au fur et mesure du CP/M 80 pour tenter de se rapprocher des facilits dUnix. Voici les grandes tapes de MS-DOS : MS-DOS 1.0 en 1981, MS-DOS 2.0 en 1983 qui adopte une structure hirarchique pour le systme de chiers ; MS-DOS 3.0 en 1984 qui autorise des lecteurs de 1,2 Megaoctets (jusqualors les disquettes avaient une capacit de 360 Ko !) et les disques durs ; 90

3.6. Les systmes dexploitation daujourdhui MS-DOS 3.2 en 1986 qui permet lutilisation de disquette 3 pouces 1/2 (le format qui tait plus rpandu lpoque) et qui est aussi la premire version de DOS incompatible avec les prcdentes ; MS-DOS 6.22 la dernire version autonome de MS-DOS ; MS-DOS 7 sortie en 1995, le DOS de Windows 95, MS-DOS 7.1 enn une version capable de supporter la FAT32 ( !), MS-DOS 8 il sagit de la dernire version si lon exclut MS-DOS 2000 qui rajoute simplement quelques fonctionnalits. MS-DOS nest ni plus ni moins quun moniteur rsident. Il est donc mono-tche, mono-utilisateur, sans mmoire virtuelle ni protection de la mmoire. Comme tous les moniteurs rsidents, il assure uniquement linterprtation des commandes et lenchanement de celles-ci. MS-DOS est construit sur une sous-couche appele BIOS (Basic Input Output System) qui assure lessentiel des fonctions dentres / sorties.

Windows Initialement, Windows ntait quune interface graphique de MS-DOS. Puis, peu peu, Windows sest dvelopp indpendamment et la version Windows 3.11 utilisait par exemple la mmoire virtuelle. Nanmoins, Windows 3.11 est un systme mono-tche, mono-utilisateur et sans protection de mmoire. Les raisons expliquant le succs de Windows sont probablement les mmes que celles expliquant le succs de MS-DOS : simplicit et possibilit daccder directement aux couches matrielles les plus basses de la machine. Il y a cependant une autre raison expliquant le succs de Windows : les traitements de texte, les tableurs et les logiciels de dessins dvelopps par Microsoft avaient de relles qualits et ne pouvaient fonctionner sur PC quavec le systme dexploitation Windows. En liant les deux, Microsoft assurait la propagation de son systme dexploitation. Cette stratgie commerciale est trs classique, mais on peut lui reprocher de sopposer aux progrs informatiques. Windows et Unix se sont longtemps opposs en proposant des services totalement diffrents : dun cot, Windows tait simple daccs mais peu efcace, dun autre cot, Unix tait trs efcace mais peu convivial. Cette guerre a eu au moins le mrite de montrer quaucune de ces deux attitudes ntait la bonne et il est probable que sans lintervention de Windows, les systmes Unix daujourdhui seraient peut-tre moins avancs en termes de convivialit. Notons toutefois quen matire dinterfaces graphiques, NeXT, socit fonde en 1985 par Steve Jobs, offrait un systme dexploitation bas sur Unix avec une interface graphique trs soigne. Cest dailleurs le rachat, en 1996, par Apple de NeXT qui conduira la mise en place de Mac OS X tel que nous le connaissons aujourdhui. 91

Chapitre 3. volution des systmes dexploitation Windows 95 Windows 95 a t annonc comme une rvolution par Microsoft. Il devait tre multitches, avec mmoire virtuelle et protection de la mmoire. En pratique, Microsoft a eu beaucoup de mal tenir ses promesses et le dveloppement du systme prenant de plus en plus de retard, il fut nalement vendu alors quil ntait pas tout fait prt. Par ailleurs et pour des raisons de compatibilit, les programmes prvus pour Windows 3.11 peuvent aussi tre excuts sous Windows 95. Or ces programmes ont lhabitude dutiliser la machine comme bon leur semble et il est trs difcile les faire fonctionner en multi-tches (voir section 5.1). Windows 95 nest donc pas multi-tches, quoi que peut en dire Microsoft, et il apparat lusage que la protection de la mmoire ne fonctionne pas toujours. Windows 98 Windows 98 est la nouvelle mouture du systme dexploitation Windows 95. Il reprend les mmes caractristiques que Windows 95 et, donc, les mmes dfauts. Lapparition de Windows 98 a t justie par un remaniement de certaines fonctionnalits et par la correction de certains bugs, mais il savre que ctait surtout une bonne occasion pour Microsoft de tenter dimposer ses logiciels de navigation sur lInternet (Internet Explorer) et de gestion des courriers lectroniques (Outlook). Notons aussi que cela permet, dune part, doccuper le march et, dautre part, de se faire un peu dargent en vendant des mises jour pour Windows 95. Contrairement aux systmes dexploitation volus, MS-DOS, Windows 3.11, Windows 95 et Windows 98 laissaient lutilisateur accder directement aux divers priphriques, avec ou sans lintermdiaire du BIOS. Ctait probablement cette facult allie la simplicit extrme de ces environnements qui expliquait leur succs. Ils sont aujourdhui totalement dpasss et peuvent peine servir pour des machines de jeux. Windows NT Windows NT est le premier systme multi-tches avec mmoire virtuelle et protection de la mmoire produit par Microsoft. En comparaison des prcdentes versions de Windows, Windows NT tait donc plus stable, mais aussi moins accessible. Microsoft visait essentiellement avec Windows NT le march des systmes dexploitation pour serveurs et tait prsent comme le concurrent direct dUnix. Windows NT nest pas multi-utilisateurs 5 et apparat comme un systme peu mature 6 . Ses utilisateurs lui reprochent en particulier sa lourdeur (il sagit dun systme dexploitation mixte comme cela est prsent dans le paragraphe 2.4) et sa lente volution. Pourtant, malgr ses dfauts, Windows NT a atteint son but et Microsoft a ainsi pu pntrer dans le march trs ferm des serveurs.
5. Il existe cependant des surcouches permettant de grer plusieurs utilisateurs. 6. Malgr des numros de version levs, cest un systme trs rcent (1993).

92

3.6. Les systmes dexploitation daujourdhui Windows NT fonctionne sur des PC, mais aussi sur des stations de travail SGI 7 ou DEC Alpha. Windows Server 2000 Windows Server 2000 est la premire version du systme dexploitation de Microsoft prenant en compte la notion dutilisateurs et le fait que plusieurs utilisateurs peuvent utiliser le mme ordinateur. Lobjectif tait de crer un systme tenant la fois de Windows 98 et de Windows NT, capable de remplacer les deux. Cest aussi la version qui indique les difcults de Microsoft continuer dans la voie du systme dexploitation bon tout faire : il existe en fait plusieurs versions de Windows 2000 et, en particulier, une version ddie aux serveurs. En pratique, Windows Server 2000 a des qualits indniables et sa stabilit est incomparable avec celle des versions prcdentes. Il ny a cependant pas de miracle : pour construire un systme stable, Microsoft a d employer les mmes mthodes que celles des autres systmes robustes (notamment les mmes mthodes quUnix) et a introduit de nombreuses limitations dans la lgendaire facilit dutilisation de Windows. En particulier, la compatibilit ascendante na pas pu tre maintenue (les logiciels fonctionnant sous Windows 95 ou Windows 98 ne fonctionnent pas ncessairement sous Windows Server 2000) et le paramtrage dun serveur sous Windows Server 2000 est nettement plus complexe que le paramtrage dune station sous Windows 95 ! Ceci a eu deux effets de bord inattendus : Beaucoup dentreprises ont attendu trs longtemps avant de passer sous Windows Server 2000 car cette migration entranait le redveloppement de la plupart des logiciels, car elle impliquait un changement important dans les mthodes de gestion et dadministration des postes de travail et car elle entranait une bascule violente dun systme lautre, avec des risques dinterruption prolonge de service 8 . Beaucoup dentreprises qui ont fait les efforts et les investissements ncessaires pour passer Windows Server 2000 ont dcid damortir sur la dure cette bascule et ont ensuite refus de passer aux versions suivantes de Windows. Windows Server 2000 est ainsi aujourdhui le systme Windows le plus rpandu dans les entreprises (plus que ses prdcesseurs et plus que ses successeurs). Mme sils sont assez proches, Windows Server 2000 et Unix reposent lternel problme des systmes dexploitation : commodit versus efcacit. La querelle qui oppose Windows Server 2000 et Unix est la mme que celle qui opposait MS-DOS et Unix, puis Windows et Unix. Il est probable que, comme prcdemment, les systmes
7. Aprs une brve tentative visant commercialiser des stations graphiques sous Windows NT, SGI a nalement dcid de revenir Unix et, en particulier, de dvelopper des stations graphiques fonctionnant sous Linux... 8. Ces risques ne sont pas propres Windows et se retrouvent chaque fois que lon change de systme dinformation dans une entreprise, ds lors quil nest pas possible de faire une bascule douce de lancien au nouveau (poste par poste, par exemple).

93

Chapitre 3. volution des systmes dexploitation dexploitation de Microsoft sinspirent largement dUnix pour amliorer les services quils proposent et quUnix sinspire de la facilit daccs de ces systmes pour amliorer sa convivialit. En fait, tant que ces deux systmes subsistent, lutilisateur est gagnant car non seulement il peut choisir lun ou lautre suivant ses besoins, mais en plus, il se voit proposer des systmes de plus en plus performants. Windows XP et les suivants Windows XP est une volution de Windows Server 2000 qui porte essentiellement sur des corrections dergonomie et sur un allgement des protections mises en place sous Windows Server 2000 : ce retour arrire est vraisemblablement une tentative pour contrer les deux effets de bord dcrits ci-dessus. Windows Server 2003 est une version consacre aux serveurs, drive de Windows Server 2000, corrigeant certains dysfonctionnements. Windows Vista est une version intermdiaire entre Windows Server 2000 et Windows Seven. Les principales nouveauts sont lies la gestion des systmes 64 bits, une interface graphique utilisant de manire massive les donnes vectorielles (sappuyant sur Windows Presentation Foundation) qui aurait pour effet indirect de faire baisser lautonomie des ordinateurs portables. Des efforts ont aussi t consentis dans le domaine de la scurit. . . , notamment le fait davoir des programmes lancs dans un contexte dexcution plus faible que le niveau administrateur et surtout lincorporation de la couche TCPA Palladium. Cette couche de DRM 9 inclut deux choses. Tout dabord TCPA (Trusted Computing Plateform Alliance) qui permet dassurer que seuls des logiciels signs (. . . par Microsoft ?) peuvent sexcuter. Ensuite larchitecture Palladium qui instaure un chiffrement des donnes circulant sur les bus de donnes. Cela permet par exemple dviter toute interception dun ux vido provenant dun lecteur de DVD et arrivant sur la carte graphique. Ce chiffrement devrait tre assur par des composants matriels intgrs la carte mre et aux cartes graphiques. Le prix de cet ajout permettant de garantir limpossibilit des copies serait support par. . . les utilisateurs !
Les autres systmes
OS/2

OS/2 est un systme multi-tches, multi-utilisateurs, avec mmoire virtuelle et protection de la mmoire. Il est essentiellement soutenu par IBM et aurait d avoir du succs. Conu la base en 1987 par Microsoft et IBM, il a t conu comme un descendant commun de MS-DOS et dUnix. Il est difcile de dire pourquoi OS/2 na pas eu beaucoup de succs. Initialement, la raison invoque tait sa lourdeur, mais il sest avr que Windows 95 et Windows NT souffraient du mme dfaut. Il est probable quOS/2 ait en fait souffert dune guerre
9. Digital Rights Management.

94

3.6. Les systmes dexploitation daujourdhui qui oppose lalliance Apple/IBM lalliance Intel/Microsoft et qui visait dvelopper un processeur (le PowerPC) en dehors du contrle dIntel.

Mach

Le systme dexploitation Mach est en fait un micro-noyau (voir section 2.4) sur lequel doivent tre greffs dautres services. Il fut conu lUniversit de Carnagie Mellon en 1985 dans le but de raliser des travaux de recherche sur les systmes dexploitation. Mach a un rapport trs net avec Unix car il propose une interface entirement compatible avec BSD. Cependant, les concepteurs de Mach annoncent que ce nest pas un systme Unix. La question de la dnition dun systme Unix se repose donc par ce biais. Mach est multi-utilisateurs, multi-tches, avec mmoire virtuelle et protection de la mmoire. Il a t port sur de nombreuses architectures et il devrait lavenir prendre de limportance car il peut tre la sous-couche de nimporte quel systme. Comme il assure lessentiel des services dun systme dexploitation moderne, la couche suprieure peut tre entirement ddie linterface pour lutilisateur. Lutilisation dun micro-noyau comme Mach permet donc dentrevoir une solution au dilemme efcacit versus commodit : deux couches totalement disjointes soccupant lune de lefcacit et lautre de la commodit. Mach est utilis par les systme dexploitation NeXT et MacOS. Il devait aussi tre utilis pour 4.4BSD, mais il sest avr que la structure monolithique de 4.4BSD tait beaucoup plus efcace et moins lourde.

Hurd

Hurd est le systme dexploitation du projet GNU de la FSF. Hurd est annonc comme un systme non Unix, mais il a cependant un rapport trs net avec Unix : Hurd est fond sur un micro-noyau Mach. De nombreux espoirs sont fonds sur Hurd et il se pourrait que ce soit le premier systme qui soit la fois commode et efcace. Il est malheureusement trop tt pour linstant pour statuer sur son sort.

Mac OS X Le systme Mac OS X (pour Macintosh Operating System version 10) quipant les derniers ordinateurs Apple est fond sur un systme dexploitation Unix de type BSD. Il est donc multi-tches, multi-utilisateurs, avec mmoire virtuelle et protection de la mmoire (ce qui ntait pas le cas des versions prcdentes de Mac OS) Trs rput pour son savoir-faire en ergonomie des ordinateurs, Apple a tenu ses promesses avec lenvironnement de travail propos sous Mac OS X : celui-ci est 95

Chapitre 3. volution des systmes dexploitation gourmand en ressources (il faut des ordinateurs puissants et bien dots en mmoire), mais se rvle un excellent 10 compromis entre commodit et efcacit. Apple a annonc quil abandonnait les processeurs IBM (gamme PowerPC) au prot des processeurs Intel, ce qui lui a permis de proposer des ordinateurs moins chers, plus proches des prix dentre de gamme des PC. Si les ventes restent toutefois modestes, la combinaison dune interface graphique trs agrable et dun noyau trs scuris comme Unix pour surprendre tout le monde et on pourrait voir Apple gagner la bataille Windows versus Unix . Ce nest cependant pas la premire fois quApple se retrouve en position favorable sur le march de linformatique, avec des machines innovantes, bien conues et de bonne qualit mais, malheureusement, cela ne lui a pas suft pour simposer. Prdire lavenir de linformatique est donc un exercice trs difcile et lexprience montre que les vnements dcisifs se situent souvent en dehors du champs de bataille : il ne serait pas tonnant, par exemple, que la bataille du poste de travail pour lentreprise se joue sur des considrations ayant trait aux jeux vido ou aux capacits multimdia. . . Il faut en effet savoir que la plupart des processeurs sufsent plus quamplement pour des travaux bureautiques, mais cest principalement lindustrie du jeu lectronique qui tire vers plus de puissance et qui dope lconomie numrique.

3.7

Conclusion : que faut-il retenir de ce chapitre ?

Ce chapitre nous a permis de parcourir lhistoire des ordinateurs et des systmes dexploitation an de mieux expliquer comment linformatique en est arriv au stade actuel. Les points suivants sont importants : le principal moteur du progrs informatique a longtemps t la ncessit (et la volont) daugmenter le retour sur investissement ; cette motivation est aujourdhui moins prsente dans les entreprises, probablement parce que les cots informatiques sont dsormais rpartis sur de nombreux postes, mais elle nen reste pas moins capitale ; linformatique actuelle est fonde sur lutilisation de composants et de priphriques apparus trs tardivement (disque dur, cran, souris, etc.) ; il est donc important de ne pas croire que linformatique nvoluera plus et que ltat actuel reprsente loptimum que lon ne pourra pas dpasser ; les concepts fondamentaux de linformatique actuels (multi-tches, multi-utilisateurs, protection de mmoire et mmoire virtuelle) ont t mis en uvre de faon oprationnelle depuis 30 ans et certains systmes comme le systme Unix proposent les 4 services la fois ; il est donc important dvaluer sa juste valeur la prouesse technique dun dveloppeur de systmes qui nobtiendrait ce rsultat qu partir de lan 2000...

10. Ceci est bien sr lavis personnel de lauteur qui travaille quotidiennement sous Mac OS, Windows Server 2000 et Linux, aussi bien dans son environnement professionnel que pour ses loisirs.

96

4
Compilation et dition de liens

Depuis trs longtemps dj, plus personne ne programme directement en langage machine : ce type de programmation requiert beaucoup de temps, notamment pour le dbogage, et les programmes ainsi crits dpendent trs fortement de lordinateur pour lequel ils ont t crits. Pour sabstraire de la machine, des langages de haut niveau ont t dvelopps et ils permettent dcrire des programmes qui, dune part, peuvent tre rutiliss de machine en machine et, dautre part, ont une structure plus proche de la faon dont un tre humain conoit les choses. Ces programmes crits dans des langages de haut niveau doivent ensuite tre traduits en langage machine : cest le but de la compilation. Il existe de nombreux langages qui peuvent tre compils et il est difcile de prvoir quels seront les langages du futur. Parmi les langages qui ont eu du succs, citons le FORTRAN (FORmula TRANslator, 1954), le COBOL (COmmon Business Oriented Language, 1959) et le LISP (LISt Processing language 1 , 1958). Les langages actuellement les plus utiliss sont le C (1972), le C++ et JAVA. Enn, nous citerons le PASCAL (1970) qui est un langage intemporel : cest probablement le meilleur langage pour apprendre programmer, mais il est difcilement utilisable dans des conditions oprationnelles.
1. Parfois appel List of Inutil and Stupid Parentheses par ses dtracteurs

97

Chapitre 4. Compilation et dition de liens

Fichier source Compilation Fichier excutable Excution Processus

F IGURE 4.1 La compilation permet de passer dun chier source un chier excutable, lexcution permet de passer dun chier excutable une xcutable en cours dxcution (cest--dire un processus)

Il ne faut pas confondre les langages compils avec les langages interprts, comme le BASIC, le SHELL ou P ERL : ces langages sont directement interprts, gnralement ligne ligne, par un autre programme (qui porte parfois le mme nom). Les programmes crits avec de tels langages sont donc moins rapides, mais ils peuvent tre dbugs plus facilement : linterprte est souvent coupl avec un diteur de texte et il signale gnralement les zones du programme quil ne comprend pas au moment o celles-ci sont crites. Cette facilit a donn naissance des langages qui peuvent tre la fois interprts et compils, comme le PASCAL ou le LISP. Si les programmes compils restent les plus rapide excuter, certains langages interprts intgrent maintenant des compilateurs qui vont transformer un ensemble dinstructions lies au langage en un jeu de codes opratoires. Si lon prend lexemple de linterprte Tcl, le court extrait suivant :
while (my_condition} { do something change my_condition }

sera transform de la faon suivante lors de la premire interprtation :


goto y x: do something change my_condition y: evaluate my_condition branche-if-true x

ce qui permet une seconde lecture beaucoup plus efcace, du point de la machine. Cest aussi sur ce principe quest fond le langage JAVA. Il sagit dun langage objet (proche du C++). Le code est tout dabord traduite en bytecode. Cest ce jeu dinstructions qui est envoy la machine virtuelle JAVA qui en assurera lexcution sur le systme dexploitation cible. Cest naturellement la qualit de la machine virtuelle 98

qui dpend totalement du systme dexploitation sur lequel elle a t conue et de sa capacit intgrer les diverses versions du langage que repose la bonne excution du bytecode. Il est naturellement possible de demander cette machine virtuelle de fournir un chier binaire qui pourra tre excut (sous rserve de pouvoir trouver toutes les librairies dont il dpendra obligatoirement). Toutefois on perd totalement dans ce cas la philosophie de JAVA qui est la portabilit 2 . Les machines virtuelles JAVA actuelles les plus sophistiques fonctionnent selon un mcanisme diffrent du bytecode, jug trop lent par rapport aux langages compils tels que C et C++. Le bytecode est compil en code natif par la machine virtuelle au moment de son excution (JIT pour Just In Time compiler). On trouve aussi maintenant des recompilations dynamique du bytecode permettant de tirer partie dune phase danalyse du programme et dune compilation slective de certains morceaux du programme 3 . Suivant les ouvrages traitant du sujet, les compilateurs font ou ne font pas partie du systme dexploitation 4 : rien nempche un utilisateur dcrire son propre compilateur mais il est difcile de faire quoi que ce soit sur une machine sans au moins un compilateur (il peut y en avoir plusieurs). Quoi quil en soit, le systme dexploitation intervient doublement dans la phase de compilation. Tout dabord, le travail du compilateur dpend trs fortement des choix faits pour le systme dexploitation et, par exemple, la construction dun excutable destin un systme utilisant la mmoire virtuelle nest pas la mme que la construction dun excutable destin un systme ne travaillant quavec des adresses physiques. Ensuite, le systme dexploitation fournit une interface de programmation aux programmeurs : les appels systme. La faon dont ces appels systme sont effectivement programms et excuts dpend de chaque systme dexploitation et un mme programme compil avec le mme compilateur sur une mme machine peut avoir des performances trs diffrentes suivant le systme dexploitation utilis. Nous allons donc dans cette section expliquer rapidement les principes de compilation en dtaillant les notions importantes que nous rutiliserons dans le chapitre sur la gestion de la mmoire. Mots cls de ce chapitre : compilation, prprocesseur, assemblage, dition de liens, bibliothque statique, bibliothque dynamique, bibliothque partage, excutable, segment de code, segment de programme, segment de donnes, table des symboles, rfrences externes, rfrences internes, adresse relative.

2. Notons ce propos que cette portabilit nest pas toujours simple obtenir et que le slogan de Sun concernant JAVA Write once, run anywhere t dtourn pour donner Write once, debug anywhere ! 3. Linterprtation du bytecode, mme si elle est toujours beaucoup moins lourde que linterprtation du langage source, introduit ipso facto un surcot. Pouvoir transformer le bytecode en code natif conduit une relle amlioration des performances en termes de temps. 4. JAVAOs en est un bon exemple puisquil sagit dun systme dexploitation ne reconnaissant quun seul langage de programmation. . . JAVA et faisant donc ofce de compilateur !

99

Chapitre 4. Compilation et dition de liens

4.1

Vocabulaire et dnitions

Les termes employs en informatique manquent gnralement de prcision et, en particulier, ceux utiliss pour tout ce qui concerne la programmation et la compilation. Cette brve section vise simplement mettre les choses au clair.

Les chiers

Un chier est une zone physique de taille limite contenant des donnes. Cette zone peut se trouver sur un des priphriques de la machine, comme le disque dur, le lecteur de CD-ROM ou la mmoire, ou sur un des priphriques dun ordinateur distant, connect notre machine via un rseau local. La notion de chier sera discute dans le chapitre sur les systmes de chiers, mais, pour linstant, considrons simplement que cest une abstraction dun ensemble de donnes. Du point de vue de lordinateur, un chier est un ensemble de 0 et de 1 stocks de faon contige.
Sens de lecture
nn e 1 Do nn e 2 Do nn e 3 Do nn e 4

Do

0011010101001110011101101101101011100101101001011101010101110101010101

F IGURE 4.2 Un chier nest quun ensemble de 0 et de 1, stocks selon un ordre donn, sur un support indni. Cette suite de 0 et de 1 peut ensuite tre interprte comme des objets informatiques de taille et de type diffrents.

Les chiers texte et les chiers binaires

Les chiers sont arbitrairement rangs en deux catgories : les chiers texte et les chiers binaires. Un chier texte est un chier contenant des caractres de lalphabet tendu (comme #, e ou 1) cods sur un octet ou plusieurs octets. Chaque donne est en fait un entier (variant entre 0 et 232 pour lUTF-8 par exemple) et une table de correspondances permet de faire le lien entre chaque entier et le caractre quil reprsente. Pendant de nombreuses annes, la table de correspondances utilise tait la table ASCII (American Standard Code for Information Interchange). Cette table nest nanmoins pas adapte aux langues accentues (comme le franais et la plupart des langues 100

4.1. Vocabulaire et dnitions


010000100110111101101110011010100110111101110101011100100010000000100001

66

117

110

106

111

111

114

32

33

F IGURE 4.3 Dans un chier texte utilisant une reprszentation simple telle que le codage ASCII, la suite de 0 et de 1 sinterprte par paquets de 8, chaque paquet reprsentant en binaire un nombre entre 0 et 255.

europens) et dautres tables ont vu le jour aprs avoir t normalises. Ainsi, la table ISO 8859-1 contient lessentiel des caractres utiliss par les langues europennes 5 .

F IGURE 4.4 Table ASCII.

La table ASCII, bien que toujours trs utilise, se voit peu peu supplanter par le systme de codage UTF-8. Il serait en effet impossible, en utilisant une table possdant 256 entres, de grer des langues telles que le chinois ou larabe. Il devint assez incontournable que pour tre utilise de manire quasi universelle, une table de correspondance se devait de pouvoir intgrer la totalit des symboles prsents dans les diffrents langages. Cela suppose donc un codage de chaque caractre sur un nombre doctets plus grand que 1. Il tait pourtant impratif de conserver une compatibilit
5. La table ISO 8859-1 ne contient malheureusement pas les caractres et : la lgende raconte que ces caractres ont t vincs au dernier moment par les reprsentants des pays europens en protant de labsence du reprsentant franais lors de la dernire runion qui devait enterrinner la norme...

101

Chapitre 4. Compilation et dition de liens avec la table ASCII. LUTF-8 apporte une solution dans le ce quil permet, en fonction du bit de poids fort de chaque octet servant au codage dun caractre, de savoir sil est ncessaire de lire loctet suivant pour obtenir la totalit du code du symbole. On conserve ainsi les 128 premiers caractres de la table ASCCI auquel on peut rajouter dautres symboles, le codage pouvant tre tendu trois octets. Insistons sur le fait que les chiers texte ne contiennent que des chiffres cods sur un octet. Cela signie quil suft de changer la table de correspondances pour quun mme chier texte change compltement dapparence. Par exemple, il sufrait de lire le texte composant ce polycopi avec la table de correspondances utilise en Chine pour avoir limpression de lire du chinois...
010000100110111101101110011010100110111101110101011100100010000000100001

66

111

110

106

111

117

114

32

33

010000100110111101101110011010100110111101110101011100100010000000100001

66

111

110

106

111

117

114

32

33

F IGURE 4.5 Les nombres compris en 0 et 255 dun chier texte peuvent tre traduits sous forme de caractres grce des tables de correspondance. Un mme chier peut donner des rsultats trs diffrents en fonction de la table de correspondance utilise. En haut, le rsultat obtenu en utilisant la table ASCII. En bas, le rsultat obtenu en utilisant une autre table.

La seule chose qui distingue un chier texte dun chier qui nest pas un chier texte, cest que nous connaissons la faon dont les informations sont codes : il suft de lire les 0 et les 1 par paquets de 8 et de traduire le nombre ainsi obtenu en caractre grce la table utilise (ce qui suppose que le nom de cette table est stock quelque part dans le systme de chiers). Les chiers binaires contiennent des donnes qui sont stockes dans un format variable et non dtermin a priori : pour lire un chier binaire, il faut connatre le format des donnes quil contient. Par ailleurs, rien nassure que toutes les donnes contenues dans ce chier sont codes de la mme manire. Par exemple, nous pouvons crire dans un chier 10 entiers, qui seront chacun cods sur 4 octets, puis 20 caractres, qui seront chacun cods sur 1 octet. Nous obtiendrons ainsi un chier de 60 octets et il est impossible de deviner comment nous devons le dcoder : nous pourrions, 102

4.1. Vocabulaire et dnitions par exemple, considrer que cest un chier texte et le lire octet par octet ce qui donnerait probablement un texte sans aucun sens... Ainsi, un chier ne contient pas des informations qui ont un sens absolu, mais simplement des informations qui nont que le sens que nous voulons bien leur donner.
010000100110111101101110011010100110111101110101011100100010000000100001

???
010000100110111101101110011010100110111101110101011100100010000000100001

???
010000100110111101101110011010100110111101110101011100100010000000100001

???

F IGURE 4.6 Impossible dinterprter un chier binaire sans savoir lavance ce quil contient.

Les chiers texte sont trs utiliss car, non seulement les informations sont codes dans un format prdtermin, mais en plus ils permettent de coder lessentiel des informations dont nous avons besoin. Ainsi, il est frquent de coder des entiers non pas directement, mais par une suite de caractres : par exemple, la chane de caractres 1048575 code sur 7 octets (un octet par caractre) reprsente lentier 1048575 cod sur quatre octets (00000000 00000111 11111111 11111111 en binaire).
Les chiers excutables

Les chiers excutables sont des chiers binaires particuliers qui sont comprhensibles directement par la machine. Plus exactement, ils sont directement comprhensibles par le systme dexploitation qui saura comment les traduire en actions concrtes de la machine. La composition de ces chiers est complexe et nous en verrons les grands principes un peu plus loin. Pour linstant, il faut bien comprendre que ce sont des chiers qui peuvent tre excuts. Mais ce ne sont que des chiers, cest--dire des objets passifs contenant des informations et des donnes.
Les processus

La notion de processus sera clairement explique dans le chapitre suivant sur la gestion des processus. Disons pour le moment quun processus reprsente lexcution dun chier excutable, cest--dire un programme qui est en train dtre excut. Un processus est donc un objet actif, contrairement aux chiers qui sont des objets passifs. Ainsi, au moment o un ordinateur est teint, tous les processus encore prsents disparaissent, alors que tous les chiers sur support non volatile demeurent. 103

Chapitre 4. Compilation et dition de liens


Les programmes

Le terme de programme regroupe toutes les notions nonces ci-dessus. Il est donc trs mal choisi car pas assez restrictif. Parfois, un programme reprsente les chiers ASCII de code. Par exemple, on dit : jcris un programme . Parfois, le terme de programme fait rfrence au chier excutable : jexcute un programme . Enn, le terme de programme est parfois employ pour dsigner le processus obtenu par lexcution du chier excutable : mon programme sest arrt . Il nest donc pas trs prudent dutiliser le mot programme , mais malheureusement son emploi est rpandu et il est probable que nous lemploierons dans ce texte.
La compilation

Un compilateur est un programme qui lit un chier contenant du code crit dans un langage donn, appel programme source, et qui produit un chier contenant du code dans un autre langage, appel programme cible. Lors de cette traduction, le compilateur indique les erreurs prsentes dans le programme source. Le principe est reprsent sur la gure 4.7.
Programme source dans un langage donn

Compilateur

Programme cible dans un langage donn

Programme source en langage C

Compilateur

Excutable pour Pentium IV sous Linux

Erreurs

Erreurs

F IGURE 4.7 Principe de la compilation et la compilation telle quon lemploie usuellement.

Dans notre cas, nous nous intresserons des programmes source crits dans un langage de haut niveau, comme le C et des programmes destination crits dans un langage de bas niveau, directement comprhensible par un ordinateur (gure 4.7). Le compilateur effectue donc la transformation dun chier source en un chier excutable. La faon dont un processus peut tre obtenu partir dun chier excutable ne fait pas partie du procd de compilation et sera dcrite ultrieurement, dans le chapitre sur la gestion des processus.

4.2

Les phases de compilation

Les principes dcrits dans cette section sont gnraux, mais nous supposerons, pour simplier les explications, que nous souhaitons compiler des chiers source crits en C an dobtenir un excutable. Certaines prcisions sinspirent fortement de la faon 104

4.2. Les phases de compilation


crosscompilateur (excutable Pentium IV sous Linux) toto.c compilateur natif (excutable Pentium IV sous Linux) a.out (excutable Pentium IV sous Linux) a.out (excutable PowerPC sous MacOS X)

F IGURE 4.8 La cross-compilation permet dobtenir des chiers excutables destination dune architecture donne partir dun chier source et dun compilateur fonctionnant sur une autre architecture. Cest par ce biais quil est possible de crer de nouvelles architecture sans rinventer la roue : les diffrents programmes ncessaires au bon fonctionnement de cette nouvelle architecture sont obtenus par crosscompilation partir dun architecture existante.

dont cela fonctionne sous Unix, mais lensemble du raisonnement est valable pour tous les compilateurs. Considrons les chiers principal.c et mafonc.c suivant, crits en langage C :
principal.c
#include <stdio.h> #include <math.h> #define NBR_MAX 20 #define VAL 2.0 int main(int argc, char *argv[]) { int j; float i; printf("Ceci est le debut\n"); i = VAL; j = 0; while(j < NBR_MAX) { i = myfonc(2*sqrt(i)); j++; } printf("i = %f -- j = %d\n",i,j); exit(1); } if(i < SEUIL) { j = i + i/ECHELLE; } else { j = i - i/ECHELLE; } return j; }

myfonc.c

#define SEUIL 0.5 #define ECHELLE 2.0

float myfonc(float i) { float j;

Les chiers sont tout dabord traits sparment et leur compilation seffectue en quatre tapes : laction du prprocesseur, la compilation, lassemblage et ldition de liens.
Laction du prprocesseur

Cette tape, parfois appele prcompilation ou preprocessing, traduit le chier


principal.c en un chier principal.i ne contenant plus de directives pour le

105

Chapitre 4. Compilation et dition de liens


toto.c Prprocesseur toto.i Compilation toto.s Assemblage a.out traduit le programme assembleur en code binaire. traduit le programme source en assembleur. dnitions de constantes, macros, inclusion de chiers.

F IGURE 4.9 Les phases de compilation.

prprocesseur. Ces directives sont toutes les lignes commenant par le caractre # et sont essentiellement de deux types : les inclusions de chiers et les macros. Les commentaires du chier principal.c sont aussi supprims lors de cette tape. Le prprocesseur remplace aussi les occurences de __FILE__, __LINE__ et autres par ce qui correspond. Les inclusions de chiers servent gnralement insrer dans le corps du programme des chiers den-tte (headers en anglais) contenant les dclarations des fonctions des bibliothques utilises (voir chapitre 26). Ces inclusions sont soit sous la forme #include <xxx> pour inclure un chier se trouvant dans des rpertoires prdnis (gnralement /usr/include), soit sous la forme #include "xxx" pour inclure des chiers dont on spcie le chemin relatif. Traditionnellement, les chiers contenant des dclarations ont le sufxe .h (pour header). Ainsi, le chier /usr/include/stdio.h est inclus dans le chier principal.c, ce qui permet notamment au compilateur de connatre le prototype de la fonction printf() et, donc, de vrier que les arguments passs cette fonction sont du bon type (pour plus de dtail, voir laide-mmoire du chapitre 26). Les macros utilisent la directive #define et permettent de remplacer une chane de caractres par une autre. Par exemple, dans principal.c, le prprocesseur remplacera toutes les occurrences de la chane de caractres VAL par la chane de caractres 2.0 . Les macros sont parfois utilises pour excuter des macro-commandes et, dans ce cas, le remplacement effectu par le prprocesseur est plus subtil (voir laide-mmoire du chapitre 26). Il est possible de modier la valeur dune macro directement lors de la compilation, cest--dire sans diter le programme : cet effet, les compilateurs sont gnralement dots de loption -D . Ainsi, nous pourrions par exemple donner la valeur 3.0 VAL via une commande du type :
gcc -c -DVAL=3.0 principal.c

106

4.2. Les phases de compilation


La compilation

Une des phases de la compilation sappelle aussi la compilation, ce qui naide pas clarier ce concept. En fait, toutes les tapes de compilation dcrites ici satisfont la dnition dune compilation : elles transforment un chier crit dans un langage en un chier crit dans un autre langage. Seul lusage permet en fait de leur donner des noms plus signicatifs. La phase de compilation proprement dite transforme le chier principal.i en un chier principal.s, crit en assembleur. Elle se dcompose traditionnellement en quatre tapes : lanalyse lexicale, qui reconnat les mots cls du langage, lanalyse syntaxique, qui dnit la structure du programme, lanalyse smantique qui permet la rsolution des noms, la vrication des types et laffectation puis enn la phase dcriture du code en assembleur.

Suite de caractres

Analyse Lexicale
Token

Analyse syntaxique
Arbre syntaxique

Vrication contextuelle (types) Optimisation


Arbre syntaxique amlior

F IGURE 4.10 La phase de compilation se dcompose elle-mme en 4 phases distinctes. Les trois premires tapes, analyse lexicale, analyse syntaxique et analyse smantique (vrication contextuelle et optimisation) senchanent avant la production du code excutable.

Aprs les tapes lexicale et syntaxique, le programme a une forme indpendante du langage utilis et indpendante de la machine. Cette reprsentation des programmes est dailleurs commune beaucoup de compilateurs et elle permet deffectuer certaines optimisations pour amliorer les performances du programme. Ces optimisations ne tiennent pas compte des caractristiques de la machine et sont ralises avant que le programme ne soit traduit en assembleur. Dautres optimisations peuvent avoir lieu pendant la phase dassemblage : elles dpendent alors fortement de larchitecture de la machine. 107

Chapitre 4. Compilation et dition de liens


gcc2_compiled.: ___gnu_compiled_c: .text .align 8 LC0: .ascii "Ceci est le debut\12\0" .align 8 LC2: .ascii "i = %f -- j = %d\12\0" .align 4 LC1: .word 0x40000000 .align 4 .global _main .proc 020 _main: !#PROLOGUE# 0 save %sp,-120,%sp !#PROLOGUE# 1 st %i0,[%fp+68] st %i1,[%fp+72] call ___main,0 nop sethi %hi(LC0),%o1 or %o1,%lo(LC0),%o0 call _printf,0 nop sethi %hi(LC1),%o0 ld [%o0+%lo(LC1)],%o1 st %o1,[%fp-16] st %g0,[%fp-12] L2: ld [%fp-12],%o0 cmp %o0,19 ble L4 nop b L3 nop L4: ld [%fp-16],%f2 fstod %f2,%f4 std %f4,[%fp-8] ldd [%fp-8],%o2 mov %o2,%o0 mov %o3,%o1 call _sqrt,0 nop std %f0,[%fp-24] ldd [%fp-24],%f4 ldd [%fp-24],%f6 faddd %f4,%f6,%f4 std %f4,[%fp-8] ldd [%fp-8],%o2 mov %o2,%o0 mov %o3,%o1 call _myfonc,0 nop

st %o0,[%fp-8] ld [%fp-8],%f7 fitos %f7,%f2 st %f2,[%fp-16] ld [%fp-12],%o1 add %o1,1,%o0 mov %o0,%o1 st %o1,[%fp-12] b L2 nop L3: ld [%fp-16],%f2 fstod %f2,%f4 std %f4,[%fp-8] ldd [%fp-8],%o2 sethi %hi(LC2),%o1 or %o1,%lo(LC2),%o0 mov %o2,%o1 mov %o3,%o2 ld [%fp-12],%o3 call _printf,0 nop L1: ret restore

Ci-dessus se trouve le rsultat de cette phase de compilation, ralise avec le compilateur gcc 2.7.2 sur un PC fonctionnant sous Linux 2.0.18. Le sens exact des instructions de ce chier importe peu, mais il est important de remarquer quelques faits sur lesquels nous reviendrons plus tard : tout dabord, les chanes de caractres sont regroupes en tte de ce chier et, ensuite, lappel aux fonctions qui ne sont pas dnies dans principal.c se fait via linstruction call .
Lassemblage

Lassembleur est un langage si proche de la machine quil est dailleurs spcique chaque processeur. Il doit cependant tre lui aussi transform en un chier crit dans un langage de plus bas niveau : le langage machine. Cette transformation sappelle lassemblage et elle produit un chier binaire, principal.o, que nous ne pouvons donc pas reprsenter ici. Ce chier nest pas encore le chier excutable mme sil en est trs proche et sappelle un chier objet ou relogeable (relocatable en anglais).
Ldition de liens

Ldition de liens est la dernire phase de la compilation. Elle concerne tous les chiers objet du programme, plus les bibliothques de fonctions. Les bibliothques de fonctions sont gnralement places dans le rpertoire /usr/lib et sont en fait des chiers objet regroups sous un format particulier. Elles ont aussi t cres par compilation et contiennent le code dun certain nombre de fonctions que le systme dexploitation met la disposition des utilisateurs. Par exemple, la fonction printf() fait partie de la bibliothque standard des entres / sorties. 108

4.2. Les phases de compilation


toto.c
Prprocesseur

main.c

tutu.c

toto.i
Compilation

toto.s
Assemblage

toto.o

main.o

tutu.o

bibliothques (stdio, stdlib, math, etc. ) a.out

crt0.o crtbegin.o ... crtend.o

F IGURE 4.11 Ldition de liens permet de transformer des chiers objets en chier excutable.

Pour pouvoir utiliser une fonction de ces bibliothques, il faut 6 avoir au pralable inclus le ou les chiers de dclarations correspondant dans le chier C appelant la fonction en question. Par exemple, pour utiliser la fonction printf(), il faut inclure le chier /usr/include/stdio.h via une directive du prprocesseur. Dans notre exemple, les chiers concerns par ldition de liens sont principal.o et mafonc.o et les bibliothques concernes sont la bibliothque standard dentres / sorties et la bibliothque mathmatique (pour sqrt()). ces chiers sajoute un ou plusieurs chiers trs importants qui se nomment souvent crt0.o, crt1.o, etc. Ces chiers sont totalement dpendants de la machine et du systme dexploitation : cest en fait le code contenu dans ces chiers qui appelle la fameuse fonction main() des programmes en C et ce sont ces chiers qui contiennent certaines informations ncessaires pour crer un excutable comprhensible par la machine, comme par exemple les en-ttes des chiers excutables 7 (voir plus loin). Ldition de liens permet donc, partir des chiers objet et des bibliothques de fonctions, de crer (enn !) lexcutable dsir. Le fonctionnement de lditeur de liens est assez complexe et nous dtaillerons quelques-unes de ses fonctions dans la section suivante.
Remarques pratiques sur la compilation

Lorsquun compilateur compile des chiers C, il ne cre pas 8 les diffrents chiers intermdiaires (.i et .s). Il faut le lui demander explicitement par diverses options. De mme, lorsque le programme ne se compose que dun seul chier C, le compilateur ne cre pas de chier objet, mais directement lexcutable.
6. En pratique, ces inclusions permettent au compilateur deffectuer des vrications. Elles ne sont donc pas obligatoires, mais trs fortement recommandes. En cas dabsence, le compilateur afche gnralement de nombreux messages davertissement pour signaler les vrications quil ne peut pas faire. 7. ne pas confondre avec les en-ttes des chiers sources. 8. Des chiers temporaires situs gnralement dans /tmp sont utiliss, puis effacs.

109

Chapitre 4. Compilation et dition de liens Voici comment obtenir les diffrents chiers intermdiaires avec gcc :
gcc gcc gcc gcc -E principal.c -S principal.c -c principal.c principal.o -o -o principal.i -o principal.s -o principal.o principal

Par ailleurs, le compilateur nexcute pas lui-mme toutes les phases de la compilation : il fait appel dautres outils, comme par exemple le prprocesseur cpp, le compilateur cc1, lassembleur as ou lditeur de liens ld. Comme pour les chiers intermdiaires, une option du compilateur permet de dtailler exactement les actions quil entreprend. Voici les diffrentes phases de compilation, dtailles par le compilateur et effectues sur un PC sous Linux 2.0.18 avec gcc 2.7.2 :
linux> gcc -v -c principal.c Reading specs from /usr/lib/gcc-lib/i486-linux/2.7.2/specs gcc version 2.7.2 /usr/lib/gcc-lib/i486-linux/2.7.2/cpp -lang-c -v -undef -D__GNUC__=2 -D__GNUC_MINOR__=7 -D__ELF__ -Dunix -Di386 -Dlinux -D__ELF__ -D__unix__ -D__i386__ -D__linux__ -D__unix -D__i386 -D__linux -Asystem(unix) -Asystem(posix) -Acpu(i386) -Amachine(i386) -D__i486__ principal.c /tmp/cca00581.i GNU CPP version 2.7.2 (i386 Linux/ELF) #include "..." search starts here: #include <...> search starts here: /usr/local/include /usr/i486-linux/include /usr/lib/gcc-lib/i486-linux/2.7.2/include /usr/include End of search list. /usr/lib/gcc-lib/i486-linux/2.7.2/cc1 /tmp/cca00581.i -quiet -dumpbase principal.c -version -o /tmp/cca00581.s GNU C version 2.7.2 (i386 Linux/ELF) compiled by GNU C version 2.7.2. as -V -Qy -o principal.o /tmp/cca00581.s GNU assembler version 2.7 (i586-unknown-linux), using BFD version 2.7.0.2 linux> gcc -v -c mafonc.c Reading specs from /usr/lib/gcc-lib/i486-linux/2.7.2/specs gcc version 2.7.2 /usr/lib/gcc-lib/i486-linux/2.7.2/cpp -lang-c -v -undef -D__GNUC__=2 -D__GNUC_MINOR__=7 -D__ELF__ -Dunix -Di386 -Dlinux -D__ELF__ -D__unix__ -D__i386__ -D__linux__ -D__unix -D__i386 -D__linux -Asystem(unix) -Asystem(posix) -Acpu(i386) -Amachine(i386) -D__i486__ myfonc.c /tmp/cca00587.i GNU CPP version 2.7.2 (i386 Linux/ELF) #include "..." search starts here: #include <...> search starts here:

110

4.3. Ldition de liens


/usr/local/include /usr/i486-linux/include /usr/lib/gcc-lib/i486-linux/2.7.2/include /usr/include End of search list. /usr/lib/gcc-lib/i486-linux/2.7.2/cc1 /tmp/cca00587.i -quiet -dumpbase myfonc.c -version -o /tmp/cca00587.s GNU C version 2.7.2 (i386 Linux/ELF) compiled by GNU C version 2.7.2. as -V -Qy -o myfonc.o /tmp/cca00587.s GNU assembler version 2.7 (i586-unknown-linux), using BFD version 2.7.0.2 linux> gcc -v -o principal principal.o mafonc.o -lm Reading specs from /usr/lib/gcc-lib/i486-linux/2.7.2/specs gcc version 2.7.2 ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.1 -o principal /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/crtbegin.o -L/usr/lib/gcc-lib/i486-linux/2.7.2 principal.o myfonc.o -lm -lgcc -lc -lgcc /usr/lib/crtend.o /usr/lib/crtn.o

Notons dans cette dition de liens la prsence des fameux chiers crt : /usr/lib/crt1.o ; /usr/lib/crti.o ; /usr/lib/crtbegin.o ; /usr/lib/crtend.o ; /usr/lib/crtn.o.

4.3

Ldition de liens

Le travail de lditeur de liens concerne essentiellement la rsolution des rfrences internes et la rsolution des rfrences externes.
Rsolution des rfrences internes

Il arrive que des programmes soient obligs de faire des sauts en avant ou des sauts en arrire. Par exemple, lorsquune instruction du type goto ou while est excute, le programme doit ensuite excuter une instruction qui nest pas linstruction situe juste aprs, mais qui est situe avant ou aprs dans le texte du programme. Les sauts en arrire ne posent pas de problme car le compilateur connat dj lendroit o se situe la prochaine instruction excuter : comme il traite le chier source du dbut vers la n (ce qui va de soi), il a dj trait cette instruction et il connat son adresse (voir section 4.5). En revanche, lorsque le compilateur se trouve face un saut en avant, il ne connat pas encore la prochaine instruction excuter. Lorsquil arrive lendroit du chier o 111

Chapitre 4. Compilation et dition de liens


0x000F1601 0x000F1600 instruction N instruction N-1 fin 0x00000003

0x00000F00

instruction n

0x000F1600

0x00000003 0x00000002 0x00000001 0x00000000

instruction 4 instruction 3 instruction 2 instruction 1

0x00000005 0x00000F00 0x00000002 0x00000001

F IGURE 4.12 Chaque instruction donne ladresse laquelle se rendre une fois son excution acheve. Un programme peut contenir des sauts avant et des sauts arrire.

se trouve cette instruction, il ne peut plus indiquer son adresse linstruction effectuant le saut en avant, moins de revenir en arrire ou deffectuer une seconde passe. Il se trouve que la plupart des compilateurs ne reviennent pas en arrire et neffectuent quune seule passe, laissant la rsolution de ce genre de rfrence lditeur de liens.
Rsolution des rfrences externes

Les rfrences externes sont les rfrences faites des variables externes ou des fonctions qui ne sont pas dnies dans le chier C trait. Par exemple, dans principal.c, myfonc() et printf() sont des rfrences externes. Mme si apparemment, nous nutilisons pas de variables externes dans principal.c, il est fort probable que des dclarations de variables externes soient faites dans les chiers den-tte inclus. Pour que lditeur de liens puisse rsoudre ces rfrences, le compilateur inclut dans chaque chier objet lensemble des rfrences utilises par le chier source correspondant. Ces rfrences sont classes en deux catgories : les rfrences connues, cest--dire dnies dans le chier objet et qui peuvent tre appeles dans dautres chiers objet, et les rfrences inconnues qui doivent tre dnies ailleurs, cest--dire soit dans un des autres chiers objet, soit dans une bibliothque. Ces informations sont crites dans une table que lon nomme table des symboles et il est possible, par exemple grce la commande nm sous Unix, de lire la table des symboles dun chier objet ou dune bibliothque. Voici par exemple la table des symboles de principal.o (T pour text signie que le symbole est connu, U quil est inconnu) :
linux> nm principal.o 00000000 t gcc2_compiled. 00000000 T main

112

4.3. Ldition de liens


U myfonc U printf U sqrt

Les chiers des bibliothques comportent aussi une table des symboles et on peut donc aussi la lire. Voici un bref extrait de la table des symboles de la bibliothque mathmatique :
linux> nm /usr/lib/libm.so ... 0000555c T sin 00005584 T sinh 000055e4 T sinhl 00005644 T sinl 0000566c T sqrt 000056ac T sqrtl 000056ec T tan 00005710 T tanh 0000576c T tanhl 000057c8 T tanl ...

Lorsque lditeur de liens rencontre une fonction qui est dnie dans un des autres chiers objet, il doit juste faire le lien (do son nom) entre lappel de la fonction et le code de la fonction. En revanche, lorsquil ne trouve pas le code de la fonction, il doit chercher ce dernier dans les bibliothques qui lui ont t spcies sur la ligne de commande. Si jamais il ne trouve pas le symbole quil cherche dans les tables des symboles de ces bibliothques, il ne peut pas continuer son travail et il retourne gnralement le message undened symbol . En revanche, lorsquil trouve le symbole adquat dans une des bibliothques, lditeur de liens a trois solutions pour crer lexcutable : 1. Il inclut toute la bibliothque ; 2. Il ninclut que la partie indispensable de la bibliothque ; 3. Il ninclut rien, mais indique o trouver le code indispensable. Ce choix ne dpend pas de lditeur de liens, mais du format dans lequel les bibliothques ont t crites et de la stratgie adopte par le systme dexploitation. Les excutables produits avec des bibliothques du premier type sont normes et cette mthode a vite t abandonne. Par exemple, si nous utilisions de telles bibliothques pour notre programme, il contiendrait le code de toutes les fonctions trigonomtriques, exponentielles, etc. Dans le second cas, les bibliothques sont des bibliothques statiques. Ce type de bibliothques permet de produire des excutables beaucoup plus petits, mais tout de mme assez volumineux. Par ailleurs, cette mthode comporte un petit dfaut : lorsque 113

Chapitre 4. Compilation et dition de liens deux programmes font appel aux mmes fonctions, par exemple la fonction sqrt(), le code de cette fonction sera crit dans chacun des deux excutables et, lorsque ces deux programmes seront excuts, sera donc charg en double dans la mmoire de lordinateur . Le troisime cas vite justement cet inconvnient, mais requiert des bibliothques particulires : les bibliothques partages ou bibliothques dynamiques. Lorsquun programme est excut, le systme dexploitation vrie si le code de la fonction excuter nest pas dj en mmoire. Si cest le cas, il se contente de lutiliser, sinon il charge cette fonction en mmoire directement partir de la bibliothque. On parle alors de rsolution dynamique de symboles et la plupart des systmes dexploitation modernes utilisent ce type de bibliothques. Sous Unix, il existe un utilitaire, ldd, qui indique pour un excutable donn les bibliothques dont ce dernier aura besoin au moment de lexcution.
linux> ldd /usr/local/bin/emacs libXaw3d.so.6 => /usr/X11R6/lib/libXaw3d.so.6 libXmu.so.6 => /usr/X11R6/lib/libXmu.so.6 libXt.so.6 => /usr/X11R6/lib/libXt.so.6 libSM.so.6 => /usr/X11R6/lib/libSM.so.6 libICE.so.6 => /usr/X11R6/lib/libICE.so.6 libXext.so.6 => /usr/X11R6/lib/libXext.so.6 libX11.so.6 => /usr/X11R6/lib/libX11.so.6 libncurses.so.3.0 => /usr/lib/libncurses.so.3.0 libm.so.5 => /lib/libm.so.5.0.6 libc.so.5 => /lib/libc.so.5.3.12 libXmu.so.6 => /usr/X11R6/lib/libXmu.so.6.0 libXt.so.6 => /usr/X11R6/lib/libXt.so.6.0 libSM.so.6 => /usr/X11R6/lib/libSM.so.6.0 libICE.so.6 => /usr/X11R6/lib/libICE.so.6.0 libXext.so.6 => /usr/X11R6/lib/libXext.so.6.0 libX11.so.6 => /usr/X11R6/lib/libX11.so.6.0

La cration de bibliothques partages est dlicate et a pouss bon nombre de dveloppeurs de systme dexploitation adopter de nouveaux formats comme le format ELF (Executable and Linking Format) pour le stockage des bibliothques. Sans entrer dans les dtails, les bibliothques dans ces nouveaux formats sont plus proches dun excutable que ne ltaient les bibliothques dans les formats anciens (comme a.out ou COFF). La cration de bibliothques partages nest valable que pour des programmes rentrants. Un programme est dit rentrant sil ne se modie pas lui-mme en cours dexcution, cest--dire sil ne modie ni son code, ni ses donnes (variables locales). Cette proprit permet deux programmes dexcuter la mme copie de code sans se proccuper des modications ventuelles du code ou des donnes de lun par lautre. 114

4.4. Structure des chiers produits par compilation Sous les systmes Unix, chaque processus utilise son propre espace dadressage et une copie des donnes est effectue pour chaque processus. La proprit de rentrance se rsume donc pour un programme ne pas modier son code en cours de route.

4.4

Structure des chiers produits par compilation

Nous allons dans cette section dtailler la structure des chiers objet, des chiers de bibliothques et des excutables. Nous nous fonderons pour cela sur des systmes dexploitation utilisant des formats rcents, comme ELF. Ces formats offrent un avantage majeur par rapport aux formats plus anciens : les trois types de chiers qui nous intressent ont la mme forme et, structurellement, il ny a pas de diffrence entre un chier objet, un chier de bibliothque et un chier excutable. Les chiers objet et les chiers de bibliothques au format ELF sont donc trs proches dun excutable et il ne reste pas grand chose faire pour les transformer en excutable. Cela facilite notamment les rsolutions dynamiques de symbole telles quelles sont pratiques avec les bibliothques partages.
Structure commune

Les chiers au format ELF sont constitus dun en-tte, de plusieurs sections de structure identique et dune section contenant des informations optionnelles (voir gure 4.13).
Fichier objet Entte Bloc 1 Informations dbugage optionnelles gcc g magic number taille du code taille des donnes

F IGURE 4.13 Structure dun chier objet.

La section optionnelle contient les informations utiles pour le dbogage des programmes et, sauf demande explicite au compilateur 9 , elle nest gnralement pas incluse car elle nest pas ncessaire pour le fonctionnement du programme. Len-tte contient de nombreuses informations permettant de retrouver les donnes stockes dans le chier. Elle dbute gnralement par un nombre (magic number) qui indique le type du chier considr, puis elle continue en spciant le nombre de sections prsentes dans le chier. Enn, elle prcise un certain nombre dinformations
9. La fameuse option -g...

115

Chapitre 4. Compilation et dition de liens qui seront utiles au moment de lexcution, comme la taille du programme contenu, la taille des donnes initialises et les tailles des donnes non initialises. Ces informations permettront au systme dexploitation dallouer la mmoire ncessaire au bon droulement de lexcution. Certains utilitaires, comme size sous Unix, permettent de lire les informations contenues dans len-tte. Voici par exemple le rsultat de lexcution de la commande size principal :
linux> size principal text data bss 568 953 4 dec 1525 hex 5f5 filename principal

La structure interne des sections suivantes est un peu plus complexe et varie dun format lautre. Gnralement, elle se compose de plusieurs tables et de plusieurs segments (voir gure 4.14) : la table des symboles connus ; la table des symboles inconnus ; la table des chanes de caractres ; le segment de texte (parfois appel segment de code ou segment de programme) ; le segment des donnes initialises.
Table des symboles connus Table des symboles inconnus Segment de donnes Segment de code (text) Rfrences aux donnes Rfrences aux fonctions Variables globales Chane de caractres

F IGURE 4.14 Structure des blocs des chiers objet.

Il ny a pas de segment des donnes non initialises car ces dernires sont cres dynamiquement au moment de lexcution.
Les chiers objet

Les chiers objet ne contiennent gnralement quune seule section et prennent donc la forme suivante : un en-tte ; la table des symboles connus ; la table des symboles inconnus ; la table des chanes de caractres ; le segment de texte ; le segment des donnes initialises ; la section optionnelle des informations de dbogage. 116

4.5. Les problmes dadresses


Les chiers excutables

Les chiers excutables contiennent plusieurs sections et, en fait, chaque section correspond au corps dun chier objet : ldition de liens se contente donc de concatner les diffrentes sections des chiers objet et de rsoudre les rfrences internes et externes.
Fichier excutable
a.out

Entte Fichier objet


fichier1.o

Fichier objet
fichierX.o

Bloc 1 ...... Bloc X Informations dbugage

Entte Bloc 1 Informations dbugage

Entte

+ ... +

Bloc X Informations dbugage

F IGURE 4.15 Structure dun chier excutable.

Cette opration est nanmoins complexe et demande en particulier dindiquer pour chaque symbole inconnu dun chier objet o se trouve le code correspondant. Il faut donc remplacer les occurrences des symboles inconnus par les adresses du programme correspondant. Nous verrons dailleurs dans une des sections suivantes que la dtermination des adresses du programme pose quelques problmes.
Les chiers de bibliothques

Les chiers de bibliothques ressemblent beaucoup aux chiers excutables, mais ils ne possdent pas de fonction main(). Il se composent de plusieurs sections correspondant aux diffrents chiers objet utiliss pour crer la bibliothque.

4.5

Les problmes dadresses

Comme nous lavons dj annonc et comme nous le verrons dans le chapitre sur les processus, chaque processus possde son propre espace mmoire et a ainsi lillusion davoir la machine pour lui tout seul. Les processus ne peuvent donc pas savoir quelle partie de la mmoire physique ils utilisent. Une consquence de ce principe est quil nest pas possible pour un chier excutable de prdire lendroit o le code et les donnes du programme quil contient seront copis en mmoire. 117

Chapitre 4. Compilation et dition de liens


fichier1.o magic number taille du code taille des donnes Table des symboles connus Table des symboles inconnus fichier2.o magic number taille du code taille des donnes

fonction1 fonction2

0x123 0x356

fonction3

0x104

fonction3 ????????

Segment de donnes Segment de code (text)

Segment de donnes Segment de code (text)

F IGURE 4.16 Rsolution des symboles inconnus par ldition de liens.

Mme si cela tait possible, il ne serait pas trs intressant que cet excutable contienne des adresses physiques absolues pour les diffrents lments quil faudra copier en mmoire au moment de lexcution. Supposons, en effet, quun programme pense utiliser une zone mmoire dtermine. Dune part, rien ne lui assure que cette zone sera libre au moment de lexcution et, dautre part, rien ne lui assure que cette zone existe physiquement dans la machine. En utilisant cette mthode, il serait par exemple trs dangereux de compiler un programme sur une machine et de le copier sur une autre. En pratique, donc, les excutables ne contiennent pas les adresses physiques du code et des donnes quil faudra copier en mmoire au moment de lexcution. En revanche, il est ncessaire que chaque excutable puisse reprer les diffrentes instructions du code (par exemple) : sans cette facult, il nest pas possible de faire des sauts en avant ou en arrire ou de demander lexcution dune fonction. Formul diffremment, chaque fois que la prochaine instruction excuter ne se trouve pas sur la ligne suivante, il faut disposer dun moyen pour la retrouver. Lditeur de liens utilise pour cela des adresses relatives : il suppose queffectivement le processus correspondant dispose de tout lespace mmoire et que donc il peut commencer copier le code ladresse 0. Ainsi, nous avons vu au moment de lexcution de la commande nm principal.o (section 4.3) que la fonction main() tait situe ladresse 0. Au moment de lexcution, il est donc ncessaire deffectuer une translation dadresse, cest--dire quil faut ajouter chaque adresse de lexcutable ladresse (physique ou virtuelle) correspondant au dbut de la zone mmoire effectivement utilise. Cette opration peut tre trs longue, car elle est effectue dynamiquement lors de la demande dexcution et car lexcutable peut contenir dinnombrables instructions. Cependant, les systmes dexploitation utilisant des pages (ou des segments) de mmoire disposent dun moyen lgant pour rsoudre ce problme : il suft que les adresses prsentes dans lexcutable correspondent au dcalage (offset) dadresse dans 118

4.6. Les appels systme une page et que la zone o le code sera copi corresponde une page : ainsi, il suft de spcier le numro de page pour que toutes les adresses soient automatiquement traduites par la MMU !

4.6

Les appels systme

c = getc(stdin);

table des appels systmes getc()

Pilotes logiques read(stdin,buf,1)

Pilotes physiques

stdin -> tty majeur(tty) kbd(...)

Tampon

vide

Attente clavier chgt_ctx

Non vide Gestion interruption intkdb()

ter caractre Renvoyer caractre

F IGURE 4.17 Un appel getc

Pour terminer ce chapitre sur la compilation, nous allons nous intresser ce qui se passe lorsquun programme utilise un appel systme. Considrons le chier source principal2.c suivant :
#include <sys/utsname.h> int main(int argc, char *argv[]) { struct utsname buf; if(uname(&buf)==0) { printf("sysname : %s\n", buf.sysname); printf("nodename : %s\n", buf.nodename); printf("release : %s\n", buf.release); printf("version : %s\n", buf.version); printf("machine : %s\n", buf.machine); } }

Ce chier utilise lappel systme uname() qui permet dobtenir des informations sur le systme dexploitation et la machine utilise. Voici, par exemple, le rsultat dune excution de ce programme sous un PC 486 avec Linux 2.0.18 :
linux> ./principal2 sysname : Linux nodename : menthe22

119

Chapitre 4. Compilation et dition de liens


release : 2.2.14-15mdksecure version : #1 SMP Tue Jan 4 21:15:44 CET 2000 machine : i586

Aprs avoir compil ce programme avec des bibliothques dynamiques, observons maintenant la table des symboles indnis de lexcutable correspondant :
linux> nm -u principal2 __libc_init __setfpucw atexit exit printf uname

Lappel systme uname() est prsent dans cette table des symboles indnis. Ainsi, lutilisation dappels systme est strictement identique lutilisation de fonctions des bibliothques standard, comme par exemple printf() : la rsolution de ces rfrences seffectue dynamiquement lexcution. Comme les appels systme font partie du code du noyau, il nest pas ncessaire de les charger en mmoire (puisque le noyau est dj charg en mmoire) et la rsolution consiste simplement indiquer ladresse du code correspondant en mmoire. Ceci tant, il est parfois difcile de se rendre compte du nombre de fonctions mises en uvre pour raliser certains appels (voir g. 4.17). Nanmoins, il y a au moins deux grandes diffrences entre un appel systme et une fonction de bibliothque : dune part, lappel systme sera excut en mode noyau et, dautre part, le code correspondant lappel systme est dj charg en mmoire (au moins pour les systmes dexploitation monolithiques) car il correspond une partie du code du noyau.

120

5
La gestion des processus

Un processus est labstraction dun programme en cours dexcution. Une abstraction est toujours difcile reprsenter et cest ce qui explique que bon nombre douvrages restent trs discrets sur la gestion des processus. Toutefois, le concept de processus est capital pour la ralisation dun systme dexploitation efcace et nous tenterons dans ce chapitre dclaircir cette notion. La notion de processus na cependant de sens que pour les systmes dexploitation fonctionnant en temps partag ou en multiprogrammation. Si un systme dexploitation ne peut pas commuter dun processus A un processus B avant que le processus A ne se termine, il est inutile de concevoir une gestion complexe des processus : il suft de les excuter dans lordre. Cette remarque insiste sur une caractristique mise en avant dans ce document : un systme dexploitation digne de ce nom doit fonctionner en temps partag ! Les systmes qui sont de plus multi-utilisateurs doivent offrir une gestion de processus plus labore, qui comprend notamment la protection des processus dun utilisateur contre les actions dun autre utilisateur. Parmi les diffrents systmes multi-tches et multi-utilisateurs disponibles actuellement, nous avons choisi Unix pour illustrer nos propos. Dune part ce systme propose de nombreux utilitaires qui seront facilement mis en uvre par un lecteur intress, dautre part, il dispose de nombreux appels systme qui nous permettront dcrire 121

Chapitre 5. La gestion des processus pendant les travaux pratiques des programmes ralisant des tches trs proches de celles du systme. Mots cls de ce chapitre : processus, contexte, changement de contexte (context switching), ordonnancement (scheduling), segment de texte, segment de donnes, pile, tas.

5.1

Quest-ce quun processus ?

Un processus est labstraction dun programme en train dtre excut. An de bien saisir la notion de processus, la plupart des ouvrages traitant des systmes dexploitation font appel une analogie clbre et nous la citerons ici essentiellement parce que cette analogie fait dsormais partie de la culture gnrale en informatique.
Une petite analogie

Considrons un pre attentionn qui souhaite prparer un gteau danniversaire pour son ls. Comme la cuisine nest pas son domaine de prdilection, il se munit dune recette de gteau et il rassemble sur son plan de travail les diffrents ingrdients : farine, sucre, etc. Il suit alors les instructions de la recette pas pas et commence prparer le gteau. Dans cette analogie, la recette reprsente un programme, cest--dire une suite dinstructions que lon doit suivre pour raliser le travail voulu. Les ingrdients sont les donnes du programme et le pre attentionn tient le rle de lordinateur. ce moment l, le ls de notre pre attentionn arrive en pleurant car il sest gratign le coude. Le pre dcide alors de lui porter secours, mais il annote auparavant sur sa recette la ligne quil tait en train dexcuter. Comme la mdecine nest pas non plus son fort, notre pre attentionn se procure un livre sur les premiers soins et soigne son ls en suivant les instructions du livre. Ce que vient de faire le pre correspond ce qui se passe pour les systmes dexploitation temps partag ou multiprogrammation : le pre a interrompu le processus de cuisine alors que celui-ci ntait pas termin pour dbuter le processus de soins. Comme il a marqu lendroit de la recette o il sest arrt et comme il a conserv tous les ingrdients, il pourra reprendre le processus de cuisine quand il voudra, par exemple ds quil aura termin de soigner son ls. Lannotation faite par le pre sur la recette pour marquer lendroit o il sest arrt reprsente le contexte dexcution du processus. Un processus est ainsi caractris par un programme (la recette), des donnes (les ingrdients) et un contexte courant. En pratique, lannotation qui permet de dterminer la prochaine instruction excuter dun programme sappelle le compteur ordinal ou compteur dinstructions. Le contexte dun processus ne se rsume pas simplement ce compteur et il comprend dautres paramtres que nous dcrivons dans ce chapitre. Lessentiel, pour linstant, est de bien comprendre la notion de processus. 122

5.1. Quest-ce quun processus ?


Le partage du temps

Maintenant que le concept de processus est clair, reprenons lexplication du partage du temps ou de lexcution concurrente de plusieurs processus. Notons ce sujet que lon parle souvent par abus de langage de lexcution concurrente de processus , ce qui est structurellement incorrect : un processus est dj lexcution dun programme. La priphrase adquate serait lexistence concurrente de processus . Supposons que trois programmes doivent tre excuts sur notre ordinateur. un instant donn t, seul un des processus reprsentant ces programmes disposera du processeur pour excuter les instructions de son programme (les instructions du segment de texte). Pour faciliter lexplication, nous nommerons A, B et C les trois processus reprsentant lexcution des trois programmes. Supposons alors que nous dcoupions le temps en petites units de lordre de la milliseconde (souvent appeles quanta) et que nous attribuions une unit de temps chaque processus. Pendant le laps de temps qui lui est accord, A pourra excuter un certain nombre dinstructions. Une fois ce laps de temps coul, A sera interrompu et il sera ncessaire de sauvegarder toutes les informations dont A a besoin pour reprendre son excution (son contexte), en particulier le compteur dinstructions de A. Un autre processus peut alors utiliser le processeur pour excuter les instructions de son programme. Quel processus choisir ? A, B ou C ? Ce choix est assur par le systme dexploitation 1 et la faon dont les processus sont choisis sappelle lordonnancement ou le scheduling. On parle aussi parfois de scheduler pour dsigner la partie du systme dexploitation qui prend en charge lordonnancement. Le processus A vient dtre interrompu. Pourquoi alors se demander si A ne pourrait pas tre le prochain processus choisir ? Parce quil est possible que B et C soient dans des tats particuliers qui ne requirent pas lutilisation du processeur. Par exemple, nous pouvons imaginer que B est un diteur de texte et que C est un lecteur de courrier (e-mail) dont les utilisateurs sont partis discuter dans le couloir. B et C nont donc rien faire, si ce nest dattendre le retour de leurs utilisateurs et il nest donc pas ncessaire de leur attribuer le processeur. Pourquoi alors avoir interrompu A pour lui rendre la main ? Nous pourrions poser la question diffremment : est-il possible de savoir si dautres processus ont besoin du processeur avant dinterrompre le processus courant ? Si on accepte lide que seul le systme dexploitation a accs aux ressources permettant de faire fonctionner la machine, alors la rponse cette question est non ! En effet, le systme dexploitation est un programme et il faut lui donner la main pour quil puisse choisir le prochain processus qui se verra attribuer le processeur. Donc, il est ncessaire dinterrompre A pour laisser le systme dexploitation prendre cette dcision. Cette interruption nest ncessaire que dans le cadre des microprocesseurs mono-cur. En pratique, cette politique ne peut tre efcace que si le systme dexploitation utilise beaucoup moins dun quantum pour slectionner le prochain processus...
1. Lordonnancement des processus est gnralement directement effectue par le noyau du systme dexploitation.

123

Chapitre 5. La gestion des processus Reprenons notre ordonnancement : A vient dtre interrompu. Supposons que B soit choisi. Tout dabord, il faut rtablir le contexte de B, cest--dire remettre le systme dans ltat o il tait au moment o B a t interrompu. Lopration consistant sauvegarder le contexte dun processus, puis rtablir le contexte du processus suivant sappelle le changement de contexte (context switching) et est essentiellement assure par les couches matrielles de lordinateur. Aprs rtablissement de son contexte, le processus B va disposer du processeur pour un quantum et pourra excuter un certain nombre dinstructions de son programme. Ensuite, B sera interrompu et un autre processus (ventuellement le mme) se verra attribuer le processeur. Ce principe est rsum sur la gure 5.1

Processus C

Processus B

Processus A

Temps

F IGURE 5.1 Principe de lordonnancement des processus : chaque processus dispose du processeur pendant un certain temps, mais, chaque instant, un seul processus peut sexcuter.

Ainsi, tout instant, un seul processus utilise le processeur et un seul programme sexcute. Mais si nous observons le phnomne lchelle humaine, cest--dire pour des dures de lordre de la seconde, nous avons lillusion que les trois programmes correspondant aux trois processus A, B et C sexcutent en mme temps. Cette excution est en fait concurrente, car les units de temps attribues un processus ne servent pas aux autres. Il reste cependant un problme dans cette belle mcanique : comment le processus correspondant au systme dexploitation est-il choisi ? En effet, nous avons dit que lorsquun processus est interrompu, le systme dexploitation slectionne le prochain processus qui bnciera du processeur. Mais comment le processus du systme dexploitation a-t-il eu la main ? Qui a slectionn ce processus ? La plupart des systmes utilisent en fait des interruptions matrielles, provoques par une horloge, qui ont lieu rgulirement (tous les 1/50e de seconde, par exemple). Il suft alors de choisir le noyau du systme dexploitation comme routine associe cette interruption... 124

5.1. Quest-ce quun processus ?


Prcision sur les problmes de compatibilit

Dans le chapitre consacr aux systmes dexploitation actuels, nous avons voqu le fait que certains systmes dexploitation ne pouvaient pas raliser de partage du temps pour des raisons de compatibilit avec de vieux programmes. Cette remarque laisse supposer quun programme doit tre prvu pour fonctionner sur un systme dexploitation temps partag, ce qui est contradictoire avec la notion de multi-tches premptif. Nous allons dans cette courte section dtailler le problme. Supposons quun programme, ignorant les capacits des systmes dexploitation modernes, dcide dcrire directement sur un lecteur de disquettes. Les lecteurs de disquettes mettent le support magntique de la disquette en rotation sur lui-mme pour lire et crire dessus. Cependant, et an de ne pas user prmaturment les disquettes, le lecteur stoppe cette rotation quand on ne fait pas appel lui. Pour crire sur une disquette, il faut donc dabord remettre le support en rotation, attendre que la vitesse se stabilise et ensuite envoyer les donnes crire. Notre programme demande donc dabord au lecteur de remettre le support en rotation, puis il effectue une boucle vide pour attendre que la vitesse se stabilise et, enn, il envoie les donnes crire. Supposons maintenant que nous dcidions dutiliser ce programme sur un ordinateur dot dun systme dexploitation moderne, donc multi-utilisateurs, multi-tches, avec mmoire virtuelle et protection de la mmoire. Tout dabord, il faudra trouver un moyen pour permettre au programme daccder directement la disquette, ce qui est contraire la philosophie du systme dexploitation et ce qui suppose donc quon laisse une faille dans la protection des ressources. Ensuite, comme les excutions sont concurrentes et que le systme est temps partag, rien nassure que notre programme aura la main aprs avoir effectu sa boucle dattente : il se peut que dautres processus se voient attribuer le processeur. Ainsi, il se peut que le lecteur de disquettes se remette en pause avant que notre programme nait eu le temps denvoyer ses donnes. Au moment o il les enverra, le lecteur ne sera pas prt et les donnes seront probablement perdues. Comme les actions dun programme sont imprvisibles par le systme dexploitation, les systmes cherchant maintenir la compatibilit avec de vieux programmes sont obligs de laisser de nombreuses failles dans la protection des ressources, en particulier de la mmoire, et sont contraints de ne pas interrompre ces programmes. Nous esprons qu ce stade de la lecture du document, tous les lecteurs connaissent la dmarche suivre pour effectuer le travail de notre programme exemple. Nous allons cependant la prciser... Tout dabord, il ne faut pas essayer daccder directement aux ressources de la machine, comme le lecteur de disquettes, mais il faut faire appel au systme dexploitation via linterface quil nous propose. Non seulement, cest beaucoup plus simple car le systme prend en compte lui-mme toutes les considrations spciques, comme la stabilisation de la vitesse de rotation ou les temps de mise en pause, mais en plus, 125

Chapitre 5. La gestion des processus cela ne dpend pas du support sur lequel on crit. Il y a donc de grandes chances que notre programme soit trs gnral et quil survive aux changements de matriels ou de systme dexploitation : seule linterface compte. Ensuite, il ne faut pas effectuer sa propre gestion du temps, par lintermdiaire de boucles vides ou dautres moyens suspects. Il est possible de demander au systme dexploitation de signaler une dure coule ou un dlai dpass. Non seulement cest beaucoup plus simple que de le faire soi-mme, mais en plus, cest le seul moyen dobtenir des temps prcis (temps la microseconde prs !). En outre, le systme dexploitation sait alors si le programme doit attendre sans rien faire et il vitera en consquence de lui attribuer le processeur pendant ce temps-l.

5.2

La hirarchie des processus

Les systmes dexploitation modernes permettent la cration et la suppression dynamique des processus (cest la moindre des choses !). Plusieurs stratgies sont employes pour cela : par exemple, sous Unix, le nouveau processus est cr par duplication ou clonage (appel systme fork()) dun autre processus, puis par crasement du programme et des donnes du processus clone par le programme et les donnes du processus que lon souhaite nalement crer (appel systme exec()). Certains systmes prfrent crer le nouveau processus de toutes pices sans faire appel au procd de clonage.
Une arborescence de processus

Toutes les stratgies employes sont fondes sur le mme principe : tout processus est cr par un autre processus et il stablit donc une liation de processus en processus. Gnralement, on appelle processus pre le processus qui en cre un autre et processus ls le processus ainsi cr. Comme chaque processus ls na quun seul pre, cette hirarchie des processus peut se traduire sous forme darbre. En revanche, comme un processus pre peut avoir plusieurs ls, le nombre de branches en chaque nud de larbre nest pas dtermin.
Processus A

Processus B

Processus C

Processus D

Processus E

Processus G

Processus F

F IGURE 5.2 Arborescence des processus.

126

5.2. La hirarchie des processus Notons que la notion de pre et de ls est relative : un processus pre ayant plusieurs ls a lui-mme un pre et, pour ce processus pre, cest un processus ls.
Lexemple dUnix

Sous Unix, un numro est attribu tous les processus ainsi crs et ce numro rete lordre dans lequel ils ont t crs : le premier processus cr porte le numro 1, le suivant le numro 2, etc. Ce numro est donc unique et il permet didentier les diffrents processus. Il sappelle gnralement le Process IDentier ou pid. Le pid est nombre dont la valeur maximale est en gnral fonction du systme dexploitation. Sous certains Unix (notamment Linux), cette valeur maximale est consigne dans un chier spcial 2 : /proc/sys/kernel/pid_max. Les numros de pid tant affects de manire squentielle, une fois ce nombre atteint, laffectation repart de 0 en vitant tous les numros de processus encore actif. La commande ps permet de visualiser les diffrents processus dun ordinateur. Voici, par exemple, le rsultat de cette commande :
linux> ps PID TTY 169 p2 170 p1 189 p1 1513 p1 STAT TIME COMMAND S 0:00 -csh S 0:00 -csh S 11:11 emacs Compilation/compil.tex R 0:00 ps

Par dfaut, cette commande ne donne quune partie des processus qui ont t crs par lutilisateur qui demande la visualisation. Diffrentes options permettent de visualiser les autres processus prsents. Par exemple, voici le rsultat de la commande 3 ps -ax :
linux> ps PID TTY 1 ? 2 ? 3 ? 4 ? 5 ? 6 ? 7 ? 21 ? 77 ? 86 ? 97 ? 109 ? 125 ? 138 1 139 2 140 3 141 4 142 5 143 6 144 ? 146 ? -ax STAT S SW SW< SW SW SW SW S S S S S S S S S S S S S S TIME COMMAND 0:01 init 0:00 (kflushd) 0:00 (kswapd) 0:00 (nfsiod) 0:00 (nfsiod) 0:00 (nfsiod) 0:00 (nfsiod) 0:00 /sbin/kerneld 0:00 syslogd 0:00 klogd 0:00 crond 0:00 lpd 0:00 gpm -t MouseMan 0:00 /sbin/mingetty tty1 0:00 /sbin/mingetty tty2 0:00 /sbin/mingetty tty3 0:00 /sbin/mingetty tty4 0:00 /sbin/mingetty tty5 0:00 /sbin/mingetty tty6 0:00 /usr/bin/X11/xdm -nodaemon 0:00 update (bdflush)

2. Ce chier na pas dexistence sur le disque dur ou sur un priphrique de stockage. Il correspond aux diffrentes valeurs utilises par le systme dexploitation et permet, dans certains cas, de raliser des changements en cours de fonctionnement. Larborescence /proc est ce que lon appelle un systme de chiers virtuels. 3. Suivant les versions dUnix, les options des commandes peuvent varier. Sur les Unix System V, la commande correspondante est : ps -ef. Dans le doute : man ps.

127

Chapitre 5. La gestion des processus

148 149 163 164 157 162 166 167 168 169 170 189 1510

? ? ? ? ? ? ? ? ? p2 p1 p1 p1

R S S S S S S S S S S S R

0:53 0:00 0:00 0:02 0:00 0:00 0:01 0:00 0:00 0:00 0:00 11:10 0:00

/usr/X11R6/bin/X -auth /usr/X11R6/lib/X11/xdm/A:0-a00144 -:0 xterm -sb -sl 1200 -j -cu -name Commande -geometry 80x7+120 xterm -sb -sl 1200 -j -cu -name Terminal -geometry 80x23+12 sh /usr/X11R6/lib/X11/xdm/Xsession xclock -geometry 100x100+10+10 -display :0 fvwm /usr/lib/X11/fvwm/GoodStuff 9 4 /home/gueydan/.fvwmrc 0 8 /usr/lib/X11/fvwm/FvwmPager 11 4 /home/gueydan/.fvwmrc 0 8 0 -csh -csh emacs Compilation/compil.tex ps -ax

Lorsquun utilisateur ouvre une session 4 , un interprte de commandes (un shell) est excut et cet interprte de commande peut son tour crer dautres processus, comme ceux correspondant aux diffrentes fentres apparaissant sur lcran juste aprs la procdure douverture de session. Si lon se connecte sur une machine de lensta au travers dune connexion scurise 5 , on obtient un ensemble de processus relativement restreint :
ensta:22 ->ps ux USER PID TIME COMMAND bcollin 14112 0:00 sshd: bcollin@pts/1 bcollin 14113 0:00 -tcsh

Si lon utilise maintenant une connexion directement sur lcran daccueil de la station, dans une des salles informatiques, on obtient un nombre de processus plus important. Dans lexemple donn ci-dessous (obtenu grce la commande ps -ux), le processus de pid 688 est probablement lanctre commun de tous les processus appartenant lutilisateur gueydan. Notons que rien ne permet de lafrmer, car cet utilisateur a trs bien pu se loguer deux fois et il y aurait ce moment-l deux ensembles de processus appartenant cet utilisateur et issus de deux pres diffrents.
USER gueydan gueydan gueydan gueydan gueydan gueydan gueydan gueydan gueydan gueydan gueydan gueydan gueydan gueydan gueydan gueydan gueydan gueydan PID 688 739 740 741 742 746 747 750 751 32109 32119 14716 15559 15565 16470 20875 20882 25664 TIME 0:00 0:00 0:00 0:00 0:58 0:09 0:01 0:00 0:02 12:21 0:00 0:41 0:01 0:00 3:48 0:08 0:05 0:00 COMMAND [.xsession] xclock -geometry 100x100+10+10 -display :0 xbiff -geometry 100x80+9+114 -display :0 xterm -sb -sl 1200 -name Commande -geometry 80x9+120+10 -ls -C xterm -sb -sl 1200 -name Terminal -geometry 80x31+120-20 -ls fvwm2 /usr/lib/X11/fvwm2/FvwmPager 7 4 .fvwm2rc 0 8 0 0 [tcsh] [tcsh] /usr/lib/netscape/netscape-communicator (dns helper) xemacs xterm -csh xemacs poly.tex xdvi.bin -name xdvi poly gs -sDEVICE=x11 -dNOPAUSE -q -dDEVICEWIDTH=623 -dDEVICEHEIGHT=879 ps -ux --cols 200

Pour pouvoir retrouver le pre dun processus, celui-ci conserve parmi ses diffrents attributs le numro didentication de son pre : le ppid (Parent Process IDentier). Ainsi la commande ps -jax permet de visualiser tous les processus de la machine ainsi que lidenticateur de leur pre :
4. On dit parfois quun utilisateur se logue en rfrence la procdure douverture de session des systmes multi-utilisateurs, procdure qui demande lidenticateur de lutilisateur ou login en anglais. 5. Par exemple en utilisant ssh.

128

5.2. La hirarchie des processus

PPID 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 144 144 149 157 157 157 157 166 166 163 164 170 170

PID 1 2 3 4 5 6 7 21 77 86 97 109 125 138 139 140 141 142 143 144 146 148 149 157 162 163 164 166 167 168 169 170 189 1512

PGID 0 1 1 1 1 1 1 21 77 86 97 109 125 138 139 140 141 142 143 144 146 148 149 157 157 157 157 157 157 157 169 170 189 1512

SID TTY TPGID 0 ? -1 1 ? -1 1 ? -1 1 ? -1 1 ? -1 1 ? -1 1 ? -1 21 ? -1 77 ? -1 86 ? -1 97 ? -1 109 ? -1 125 ? -1 138 1 138 139 2 139 140 3 140 141 4 141 142 5 142 143 6 143 144 ? -1 146 ? -1 144 ? -1 144 ? -1 144 ? -1 144 ? -1 144 ? -1 144 ? -1 144 ? -1 144 ? -1 144 ? -1 169 p2 169 170 p1 1512 170 p1 1512 170 p1 1512

STAT S SW SW< SW SW SW SW S S S S S S S S S S S S S S S S S S S S S S S S S S R

UID TIME COMMAND 0 0:01 init 0 0:00 (kflushd) 0 0:00 (kswapd) 0 0:00 (nfsiod) 0 0:00 (nfsiod) 0 0:00 (nfsiod) 0 0:00 (nfsiod) 0 0:00 /sbin/kerneld 0 0:00 syslogd 0 0:00 klogd 0 0:00 crond 0 0:00 lpd 0 0:00 gpm -t MouseMan 0 0:00 /sbin/mingetty tty1 0 0:00 /sbin/mingetty tty2 0 0:00 /sbin/mingetty tty3 0 0:00 /sbin/mingetty tty4 0 0:00 /sbin/mingetty tty5 0 0:00 /sbin/mingetty tty6 0 0:00 /usr/bin/X11/xdm -nodaemon 0 0:00 update (bdflush) 0 0:53 /usr/X11R6/bin/X -auth /usr 0 0:00 -:0 500 0:00 sh /usr/X11R6/lib/X11/xdm/X 500 0:00 xclock -geometry 100x100+10 0 0:00 xterm -sb -sl 1200 -j -cu 0 0:02 xterm -sb -sl 1200 -j -cu 500 0:01 fvwm 500 0:00 /usr/lib/X11/fvwm/GoodStuff 500 0:00 /usr/lib/X11/fvwm/FvwmPager 500 0:00 -csh 500 0:00 -csh 500 11:10 emacs Compilation/compil.te 500 0:00 ps -jax

Unix est un systme multi-utilisateurs et chaque utilisateur est identi par un numro (uid, User IDentier). Lorsquun programme est excut par un utilisateur, le processus correspondant appartient cet utilisateur et possde donc comme attribut son uid. Seul le propritaire dun processus et le super-utilisateur de la machine peuvent effectuer des oprations sur ce processus, comme par exemple le dtruire. Pour des raisons pratiques, on identie gnralement aussi un utilisateur par un nom et une correspondance est faite entre ce nom et le numro didentication 6 . La commande ps aux permet par exemple de visualiser tous les processus prsents sur la machine ainsi que leurs propritaires :
ensta:22 ->ps aux UID PID PPID TIME CMD root 1 0 00:00:05 init [5] root 2 0 00:00:00 [kthreadd] ... carrel 1830 1 911-01:31:43 /home/2012/carrel/IN104/traiterimage ... root 2767 1 00:00:59 automount nobody 2791 1 00:00:00 oidentd -q -u nobody -g nobody ... ntp 2851 1 00:00:01 ntpd -u ntp:ntp -p /var/run/ntpd.pid -g ... smmsp 2902 1 00:00:00 sendmail: Queue runner@01:00:00 for /var/spool/clientmqueue ... canna 2932 1 00:00:01 /usr/sbin/cannaserver -syslog -u canna ... omni 3024 3022 00:00:00 /usr/bin/omniNames -errlog /var/omniNames/error.log -logdir /var/omniNames

6. La commande id bcollin permet de connatre luid ainsi que les groupes auxquels appartient lutilisateur dont on connat le patronyme :uid=4045(bcollin) gid=401(prof) groupes=401(prof).

129

Chapitre 5. La gestion des processus

... haldaemon 3098 1 ... carrel 3658 1 arnoult 4125 1 ... haas 9457 1 ... bcollin 14345 14343 ... dupoiron 27053 1 denie 28172 1

00:00:04 hald 00:01:57 /usr/bin/artsd -F 10 -S 4096 -s 60 -m artsmessage -l 3 -f 00:00:00 /usr/libexec/bonobo-activation-server --ac-activate --ior-output-fd=17 00:00:00 /usr/libexec/bonobo-activation-server --ac-activate --ior-output-fd=17 00:00:00 sshd: bcollin@pts/1 00:00:00 /usr/libexec/bonobo-activation-server --ac-activate --ior-output-fd=17 00:00:00 /usr/bin/gnome-keyring-daemon -d

Le premier processus

Quel est le processus situ au sommet de la hirarchie des processus ? En dautres termes, quel est le processus anctre de tous les autres processus ? La cration de processus par liation ne peut en effet pas sappliquer au premier processus cr : cest lhistoire de luf et de la poule ! En fait, le premier processus est cr au moment o lordinateur dmarre et il fait gnralement partie des processus reprsentant le noyau du systme dexploitation. Ce premier processus est excut en mode noyau et effectue un certain nombre de tches vitales. Par exemple, il sempresse de programmer la MMU et dinterdire laccs aux diffrentes structures quil met en place. Puis il cre un certain nombre de processus, excuts en mode noyau, qui assurent les services fondamentaux du noyau. Ces processus peuvent alors leur tour crer des processus systme, excuts en mode utilisateur, qui assureront dautres services du systme dexploitation ou qui permettront aux utilisateurs dutiliser la machine. Le nombre de processus fonctionnant en mode noyau varie dun systme dexploitation lautre. Sur des systmes monolithiques, il ne devrait en thorie ny en avoir quun, mais il y en a gnralement quelques-uns. Par exemple sur le systme 4.4BSD, seulement deux processus sont en mode noyau : le processus pagedaemon qui permet dchanger une page situe en mmoire principale avec une page en mmoire secondaire (voir chapitre sur la gestion de la mmoire) et le processus swapper qui dcide quand procder un tel change. Les numros didentication attribus ces processus sont, pour des raisons historiques, 0 pour le swapper et 2 pour le pagedaemon. Le premier processus cr en mode utilisateur est le processus init qui se voit attribuer le numro didentication 1. Les numros didentication de ces trois premiers processus ne retent donc pas lordre de leur cration, mais cest gnralement le seul contre-exemple 7 .
7. Il existe des projets de rustines du noyau Linux pour faire en sorte que les pid soient attribus de manire alatoire. Ces projets ont pour but la scurit, mais sont souvent rejets car, dixit Alan Cox : its just providing security through obscurity . Les dfendeurs de tels projets clament que tant que les logiciels seront dvelopps par des tres humains faillibles, il y aura des bugs et quun moyen comme un autre de prvenir des exploits serait de rendre alatoire lattribution des pid. Le dbat reste ouvert !

130

5.2. La hirarchie des processus


Le dmarrage de la machine

La faon dont le premier processus est cr dpend beaucoup des systmes dexploitation et des machines. Gnralement, les ordinateurs possdent une mmoire programmable non volatile appele PROM (Programmable Read Only Memory). Dans cette PROM est stock un programme qui est directement charg en mmoire, puis excut au moment o lordinateur est allum. Lopration consistant dmarrer lexcution de ce programme sappelle le bootstrapping 8 (amorage en franais). Les PROM ont gnralement des petites capacits et il est ncessaire de faire appel au constructeur de lordinateur pour les reprogrammer. Le systme dexploitation ne peut donc pas tre stock dans la PROM de la machine et il se trouve gnralement sur le disque dur. Lorsque la machine a dmarr, le processeur sait comment accder au disque dur, mais il ne sait pas comment est structur le systme de chiers et il nest donc pas capable de lire le chier excutable correspondant au systme dexploitation 9 . Sur de nombreuses machines Unix, cet excutable sappelle vmunix et se trouve trs prs du sommet de larborescence du systme de chiers. Le programme contenu dans la PROM se contente donc gnralement de lancer lexcution dun autre programme, stock au tout dbut du disque dur sous une forme ne dpendant pas du systme de chiers. Ce programme, souvent appel le programme de boot (amorce en franais), peut en revanche contenir ladresse physique absolue de lendroit o est stock le chier vmunix : comme le programme de boot est aussi stock sur disque dur, il est possible de le modier chaque fois que le chier vmunix est modi (que ce soit sa place sur le disque dur ou son contenu). Dans la mesure o le systme dexploitation nest pas encore excut, les programmes de la PROM et de boot nutilisent pas les facilits habituellement proposes par les systmes dexploitation, comme la mmoire virtuelle, et ils doivent donc assurer la gestion de la mmoire. En particulier, il est ncessaire de prendre de grandes prcautions lorsque le programme de boot charge en mmoire le systme dexploitation : si jamais il ne le trouve pas ou si jamais a se passe mal, la machine reste dans un tat o rien ne fonctionne ! Pour viter ce genre de problmes, le programme de boot et le systme dexploitation peuvent se trouver sur une disquette ou sur un CDROM et ils sont dailleurs gnralement dabord cherchs sur ces supports avant dtre cherchs sur le disque dur. Cela permet par exemple dutiliser un systme dexploitation donn mme si le disque dur tombe en panne ou sil est ncessaire de le reformater. Malheureusement, cette mthode offre souvent la possibilit de contourner les protections des ordinateurs en plaant tout simplement un systme dexploitation trs
8. Soit littralement la languette de botte , sorte de petite pice de tissu ou de cuir place en haut larrire des bottes et permettant de les enler. 9. Le systme dexploitation est un programme comme un autre et il existe donc un excutable correspondant au systme dexploitation. Cest cependant un excutable particulier.

131

Chapitre 5. La gestion des processus permissif sur une disquette. Ce systme sera alors lu au dmarrage de la machine et rien nempchera alors daccder toutes les donnes places sur le disque dur. Pour viter ce type de piraterie, il est frquent que lordinateur propose une protection matrielle via un mot de passe : ce dernier est directement stock dans la PROM et ne peut donc tre modi aussi facilement. Notons cependant que si un pirate peut accder une machine pour lui faire lire une disquette, rien ne lempche douvrir la machine pour tout simplement enlever le disque dur et le lire ensuite tranquillement chez lui... Certains systmes ont des programmes de boot qui permettent de choisir un systme dexploitation parmi plusieurs. Par exemple, il est possible dinstaller Linux sur une machine ainsi que dautres systmes dexploitation comme DOS ou Windows NT. Le programme de boot de Linux charge par dfaut Linux, mais donne la possibilit lutilisateur de lancer lexcution des autres systmes dexploitation. Il est donc tout fait possible dutiliser une machine avec plusieurs systmes dexploitation slectionns de faon dynamique au moment du dmarrage de la machine.

5.3

Structures utilises pour la gestion des processus

Un processus est obtenu partir de lexcution dun chier excutable. Les informations prsentes dans ce chier sont alors utilises pour effectuer les diffrentes tapes ncessaires lexcution : lallocation de la mmoire pour stocker le code et les donnes, lallocation et linitialisation de donnes utilises par le systme dexploitation pour grer les processus. Lorsquun programme est excut, le processus correspondant a lillusion de disposer de toute la mmoire pour lui et la correspondance entre les adresses mmoire quil utilise et les adresses physiques est assure par la MMU. Nous discuterons de ce point dans le chapitre suivant. Ce principe permet en fait dattribuer des zones mmoire chaque processus et on dit parfois, par abus de langage, que chaque processus travaille dans son propre espace mmoire. Une consquence de ce principe est quil est assez facile dautoriser ou dinterdire laccs au programme ou aux donnes dun processus. Gnralement, le programme dun processus peut tre partag avec dautres processus, mais les donnes ne sont en revanche accessibles que par le processus propritaire. Les processus doivent alors utiliser des communications particulires, gres par le systme dexploitation, pour changer des donnes. Notons cependant que rcemment est apparu un type de processus trs particulier, appel thread, qui permet justement le partage du mme espace de donnes entre plusieurs processus de ce type. Les threads vont jouer un rle capital dans le dveloppement des futurs systmes dexploitation, mais nous nen parlerons pas ici, le chapitre 8 leur sera consacr. 132

5.3. Structures utilises pour la gestion des processus


Structure de lespace mmoire dun processus

Lespace mmoire utilis par un processus est divis en plusieurs parties reprsentes sur la gure 5.6. On trouve en particulier le segment de code (souvent appel segment de text), le segment de donnes, la pile (stack en anglais) et le tas (heap en anglais).
Le segment de code

Le segment de code est obtenu en copiant directement en mmoire le segment de code du chier excutable. Au cours de lexcution du programme, la prochaine instruction excuter est repre par un pointeur dinstruction. Si lexcution dune instruction du code modie lagencement de lespace mmoire du processus et, par exemple, provoque un dplacement du segment de code, le pointeur dinstruction ne sera plus valable et le programme se droulera de faon incorrecte. An dviter ce problme, le segment de code est toujours plac dans des zones xes de la mmoire (virtuelle), cest--dire au dbut de la zone disponible. Comme le systme utilise la mmoire virtuelle, cette zone dbute gnralement ladresse 0 et les adresses obtenues seront ensuite traduites en adresses physiques. Notons ce sujet que, comme les adresses du segment de texte de lexcutable dbutent aussi 0, placer le segment de texte ladresse 0 de la mmoire du processus vite davoir modier toutes les adresses du code. An dviter les problmes de rentrance, le segment de code nest gnralement accessible quen lecture. Il est frquent quil soit partag par tous les processus excutant le mme programme.
Le segment de donnes

Au dessus du segment de code (voir gure 5.6) se trouve le segment de donnes. Ce segment est traditionnellement compos dun segment de donnes initialises (data), qui est directement copi partir de lexcutable, et dun segment de donnes non initialises (bss pour block storage segment) qui est cr dynamiquement. Les donnes initialises correspondent toutes les variables globales et statiques initialises des programmes C. Les donnes non initialises correspondent aux variables globales et statiques non initialises. Le segment de donnes est agrandi ou rduit au cours de lexcution pour permettre dy placer des donnes. Nanmoins, il est habituel de considrer que ce segment est xe et correspond celui obtenu avant lexcution de la premire instruction : le segment bss se rsume alors aux variables locales de la fonction main().
La pile (stack )

Pour stocker les donnes obtenues en cours dexcution, le systme utilise alors un segment nomm la pile (stack en anglais). Lavantage de cette pile est quelle 133

Chapitre 5. La gestion des processus peut justement tre gre comme une pile, cest--dire en empilant et en dpilant les donnes.

Pile

Donnes Code
0x00000000

F IGURE 5.3 La pile permet de stocker les donnes des processus.

Prenons un exemple : la pile (que nous reprsenterons de gauche droite dans ce texte) contient les donnes ABCD et linstruction en cours fait appel une fonction. Le systme peut alors placer sur la pile les paramtres passer la fonction (pile ABCDQR), puis placer sur la pile les diffrentes variables locales de la fonction (pile ABCDQRXYZ). La fonction sexcute normalement et le systme na plus alors qu dpiler toutes les donnes quil a places pour lappel de la fonction (pile ABCD). Le systme peut alors accder directement aux donnes quil avait stockes avant lappel de la fonction et peut excuter linstruction suivante.
Pile
Pile

Fonction 2 Fonction 1 Donnes Code

Fonction 2

Trou
Donnes Code

F IGURE 5.4 Les variables locales, les paramtres passs des fonctions, les retour dappels de fonctions, etc. sont stocks en mmoire par empilement et sont supprims par dpilement. Cest de cette faon que les variables locales une fonction sont dtruite ds la n de la fonction. Il nest donc pas possible de crer de trou au sein de la pile, par dnition.

Lavantage de cette pile est donc vident : les donnes sont toujours stockes de 134

5.3. Structures utilises pour la gestion des processus faon contigu et lespace mmoire du processus forme un ensemble compact quil sera facile de traduire en adresses physiques.
Le tas (heap)

Malheureusement, ceci nest plus vrai en cas dallocation dynamique : lespace mmoire allou par une fonction comme malloc() (par exemple) ne pourra pas tre dpil et les donnes ne seront plus stockes de faon contigu. Reprenons notre exemple de la section prcdente et supposons que la fonction appele alloue 4 octets de mmoire (les octets libres sont reprsents par un point et les octets allous dynamiquement par +) : avant lappel de la fonction la pile est ABCD, juste avant la n de la fonction la pile est ABCDQRXYZ+++ et aprs lappel de la fonction la pile est ABCD.....+++. Pour viter de conserver en mmoire lemplacement des zones libres mais non contigus, les systmes dexploitation utilisent gnralement un autre segment pour les allocations dynamiques : le tas (heap).
Informations systme Arguments en ligne Pile Tas Donnes Code
0x00000000 0xFF000000

F IGURE 5.5 Le tas permet de stocker des donnes de faon non contigu et est situ lautre extrmit de lespace mmoire de chaque processus.

Toutefois, le problme nest rsolu que si lemplacement du tas ne varie pas en fonction des donnes stockes dans la pile. Cest pour cette raison que le tas est gnralement plac la n de lespace mmoire du processus et quil est agrandi du haut vers le bas, contrairement la pile. Notons que, comme le systme dexploitation utilise la mmoire virtuelle, ladresse virtuelle du dbut du tas importe peu car les zones de mmoire libre entre le tas et la pile ne seront pas traduites en zones physiques. En pratique, les tailles de la pile et du tas sont bornes et ces bornes font partie des limitations (modiables) imposes par le systme dexploitation. Voici, par exemple, le rsultat de lexcution de la commande limit sous Unix qui indique entre autres la taille maximale de la pile.
linux> limit cputime filesize unlimited unlimited

135

Chapitre 5. La gestion des processus

datasize stacksize coredumpsize memoryuse descriptors memorylocked maxproc

unlimited 8192 kbytes 1000000 kbytes unlimited 256 unlimited 256

Les autres informations

Dautres informations sont places dans lespace mmoire du processus, comme les paramtres passs sur la ligne de commande lors de lexcution du programme. Suivant les systmes dexploitation, le systme peut aussi stocker cet endroit diffrents renseignements, comme par exemple le mode dans lequel doit sexcuter le processus. Ces renseignements se trouvent dans une structure qui se nomme souvent Process Control Bloc (PCB) ou Task Control Bloc (TCB). Toutes ces informations sont utilises par le systme et doivent tre faciles retrouver : il ne faut pas qu chaque changement de la pile ou du tas, le systme ait remettre jour ses structure internes. Pour cette raison, ces informations sont gnralement stockes la n de lespace mmoire du processus, aprs le tas. Comme leur taille est xe, cela ne gne en rien le fonctionnement du tas.
Conclusion

Lespace mmoire dun processus est donc divis en deux morceaux variables situs chacun une extrmit de lespace. Ces morceaux sont eux-mme diviss en plusieurs segments, certains de taille xe situs aux extrmits, dautres de taille variable.
Informations systme Arguments en ligne

0xFF000000

Tas Pile

Donnes Code

0x00000000

F IGURE 5.6 Organisation de lespace mmoire dun processus.

Suivant les systmes dexploitation, le tas et la pile peuvent se trouver lun en haut et lautre en bas, ou linverse. 136

5.3. Structures utilises pour la gestion des processus


tat des processus

Dans les systmes temps partag, les processus se trouvent dans diffrents tats suivant quils ont la main ou quils attendent davoir la main. Le nombre de ces tats varie dun systme lautre, mais nous pouvons en distinguer au moins six : Nouveau : le processus est en cours de cration ; En excution : le processus dispose du processeur et les instructions de son programme sont en train dtre excutes ; Prt : le processus est prt et attend que le systme dexploitation lui attribue le processeur ; En attente : le processus a besoin dun vnement particulier pour pouvoir continuer, par exemple lachvement dune entre / sortie ; Stopp : le processus na besoin de rien pour continuer et il pourrait demander laccs au processeur, mais il ne le fait pas ; Termin : toutes les instructions du programme du processus ont t excutes et le processus attend que le systme dexploitation libre les ressources correspondantes.

F IGURE 5.7 Graphe des tats des processus.

Pour slectionner le prochain processus, le systme dexploitation parcours la liste des processus ltat prt et choisit celui qui doit effectuer le travail le plus prioritaire. Le processus choisi passe alors ltat en excution . Si ce processus a besoin dune entre / sortie, il passe alors ltat en attente et le systme dexploitation choisit un autre processus. Une fois que le quantum attribu un processus est coul, le systme dexploitation le remet dans ltat prt et refait une slection. Sous Unix, les processus peuvent prendre cinq tats : SIDL : au moment de la cration (pour idle) ; SRUN : prt tre excut ; SSLEEP : en attente dun vnement ; SSTOP : arrt par le signal SIGSTOP, parfois sur la demande de lutilisateur ; 137

Chapitre 5. La gestion des processus SZOMB : en attente de terminaison. Notons quil ny a pas dtat pour spcier si le processus est en cours dexcution : au moment o ces tats sont pris en compte, seul le noyau du systme dexploitation est en train de sexcuter...
La table des processus

La plupart des systmes dexploitation gardent en mmoire une table contenant toutes les informations indispensables au systme dexploitation pour assurer une gestion cohrente des processus. Cette table sappelle la table des processus et elle est stocke dans lespace mmoire du noyau du systme dexploitation, ce qui signie que les processus ne peuvent pas y accder. Cette table se dcompose en blocs lmentaires correspondants aux diffrents processus.
PID
PPID tat

PID
PPID tat

PID
PPID tat

PID
PPID tat

....

....

....

Informations syst"me Arguments en ligne

Tas Pile

Table des chiers ouverts (entre et sortie standard)

Donn!es Code

F IGURE 5.8 La table des processus.

Les informations prsentes dans cette table ne doivent pas tre confondues avec les informations stockes dans lespace mmoire du processus. Le nombre et le type des informations contenues dans chaque bloc varient dun systme dexploitation lautre. Nous pouvons nanmoins en distinguer au moins 7 : Ltat du processus : comme nous venons de le voir, cet tat est indispensable pour effectuer un ordonnancement efcace ; Le compteur dinstructions : cela permet au systme dexploitation de connatre la prochaine instruction excuter pour chaque processus ; Les registres du processeur : ces registres sont utiliss par la plupart des instructions et il est donc ncessaire de sauvegarder leur contenu pour pouvoir reprendre lexcution du programme du processus que lon souhaite interrompre ; 138

....

5.4. Lordonnancement des processus (scheduling) Des informations pour lordonnancement des processus : on y trouve, par exemple, la priorit du processus ou le temps pass dans ltat en attente ; Des informations pour la gestion de la mmoire : comme par exemple, ladresse des pages utilises ; Les statistiques : comme le temps total pass en excution ou le nombre de changements de contexte. Outre ces informations, il est frquent que des systmes volus, comme Unix, prcisent en plus le numro didentication du processus, le numro didentication du propritaire du processus et la liste des chiers ouverts par le processus. Nanmoins, il ne faut pas non plus que la table des processus prenne trop de mmoire et, donc, il ne faut pas que chaque bloc contienne trop de renseignements. En particulier, et pour des raisons videntes, il est toujours difcile de stocker ce type de table en mmoire secondaire et les systmes dexploitation ont tendance les garder toujours en mmoire principale. Pour limiter la taille de chaque bloc, de nombreux systmes reportent toutes les informations qui ne sont pas absolument indispensables la gestion des processus dans lespace mmoire de ceux-ci, dans le bloc de contrle. Ces informations pourront Les contraintes Les contraintes en particulier se retrouver (ventuellement) en mmoire secondaire et le systme dexploitation devra recharger les pages correspondantes en mmoire principale sil Rapidit des services Rapidit des services : : ! temps CPU utilis le systme = temps perdu ; ! temps CPU utilis par par le systme = temps perdu ; souhaite y accder.
! complexit rapport au au nombre de processus. ! complexit par par rapportnombre de processus. Temps Temps Temps CPU CPU utilis CPUutilis Temps Temps Temps CPU CPU utilis utilis CPU

utilis

utilis

croulement croulement de de la la machine machine Nombre Nombre Nombre de de processus processusprocessus de

Nombre Nombre Nombre de de processus processus de processus


ENSTA / IN ENSTA / IN 201 201 15 octobre 15 octobre 2002 2002 9 9

Jrme Gueydan Jrme Gueydan

F IGURE 5.9 La tenue en charge des systmes dexploitation dpend trs fortement de leur capacit grer les processus, notamment lorsque le nombre de processus augmente.

Nous reparlerons de laffectation des priorits et des informations destines lordonnancement des processus dans la section suivante qui lui est consacre. Les informations pour la gestion de la mmoire sont dtailles dans le chapitre suivant.

5.4

Lordonnancement des processus (scheduling)

Lordonnancement des processus est lordre dans lequel le systme dexploitation attribue, pour un quantum de temps, le processeur aux processus. Nous avons vu en dbut de chapitre que cet ordonnancement tait grandement facilit par lutilisation 139

Chapitre 5. La gestion des processus dune horloge qui dclenche rgulirement une interruption : chaque interruption, le systme dexploitation choisit le prochain processus qui bnciera du processeur. Dans ce chapitre, nous revenons sur lopration de changement de contexte, puis nous dtaillons les diffrentes politiques dordonnancement utilises par les systmes dexploitation.
Le changement de contexte

Le changement de contexte est lopration qui, dune part, sauvegarde le contexte du processus qui on retire le processeur et qui, dautre part, rtablit le contexte du processus qui vient dtre choisi pour bncier du processeur.
Processus C

Processus B

Processus A changement de contexte Systme d'exploitation

Temps

F IGURE 5.10 Changement de contexte.

Le changement de contexte assure un processus quil retrouvera toutes les informations dont il a besoin pour reprendre lexcution de son programme l o il a t interrompu. Les changements de contexte arrivent trs souvent : aprs chaque quantum de temps, chaque fois que le noyau du systme dexploitation prend la main et chaque fois quun processus est dessaisi du processeur avant la n du quantum qui lui tait attribu (parce quil attend une entre / sortie, par exemple). Lopration de changement de contexte doit donc tre trs rapide et, en pratique, elle est souvent assure en grande partie par les couches matrielles de la machine. Sur les processeurs Intel, par exemple, il existe un changement de contexte matriel depuis le 80386. Il est ainsi possible de charger le nouveau contexte depuis le TSS (Task State Segment). Cependant cette facilit nest utilise ni par Windows ni par Unix car certains registres ne sont pas sauvegards. Les machines les plus performantes effectuent un changement de contexte en quelques microsecondes et cette opration inue donc peu sur les quanta attribus aux processus (qui, eux, sont de lordre de la dizaine de millisecondes). 140

5.4. Lordonnancement des processus (scheduling)

Processus B

Processus A Systme d'exploitation

Temps

Interruption matrielle (horloge)

Appel systme Par exemple : read() appel par fscanf()...

F IGURE 5.11 Le systme dexploitation intervient chaque changement de contexte, cest--dire la n de chaque quantum de temps et lors de chaque appel systme.

Les politiques dordonnancement

La politique dordonnancement quun systme dexploitation applique rete lutilisation qui est faite de la machine que ce systme gre. Par exemple, le systme dexploitation dune machine destine faire de longs calculs sans aucune interaction avec les utilisateurs vitera dutiliser de petits quanta de temps : il peut considrer que le temps de raction de la machine nest pas important et laisser 1 seconde chaque processus pour excuter tranquillement son programme. Inversement, le systme dexploitation dune machine utilise pour des tches interactives, comme ldition de texte, devra interrompre trs souvent les processus pour viter quun utilisateur nattende trop avant de voir ses actions modier lafchage lcran. Si nous nous reportons lhistorique des systmes dexploitation (chapitre 3), nous pouvons distinguer 5 critres pour effectuer un bon ordonnancement : Efcacit : mme si cette contrainte est moins prsente aujourdhui, le but ultime est de ne jamais laisser le processeur inoccup ; Rendement : lordinateur doit excuter le maximum de programmes en un temps donn ; Temps dexcution : chaque programme doit sexcuter le plus vite possible ; Interactivit : les programmes interactifs doivent avoir un faible temps de rponse ; Equit : chaque programme a droit autant de quanta que les autres. Nous ne reviendrons pas sur le fait que certains de ces critres sont contradictoires et nous dirons donc quun bon algorithme dordonnancement essayera de remplir au mieux ces critres. 141

Chapitre 5. La gestion des processus

Processus C Processus B Processus A Temps

Processus C Processus B Processus A Temps

F IGURE 5.12 Deux politiques dordonnancement pratiques jadis : premier arriv, premier servi et le plus court dabord.

Ordonnancement par tourniquet (round robin)

Lordonnancement par tourniquet est celui que nous avons utilis comme exemple tout au long de ce document. Cest probablement lalgorithme le plus simple et le plus efcace : chaque processus se voit attribuer un laps de temps (quantum) pendant lequel il bncie du processeur et, donc, pendant lequel il peut excuter les instructions de son programme.
Ordonnancement par tourniquet Processus Processeur 7 6 2 5 4
ENSTA / IN 201 9 octobre 2003

Temps 1

11

Jrme Gueydan

F IGURE 5.13 Principe de lordonnancement des processus par tourniquet.

la n du quantum de temps, un autre processus dans ltat prt est slectionn. Si le processus sarrte avant la n de son quantum, le processeur est immdiatement attribu un autre processus, sans attendre la n du quantum. La gestion de lordonnancement par tourniquet est assez simple : il suft de maintenir une liste chane des diffrentes processus dans ltat prt . Si un processus vient de bncier du processeur, on le place en n de liste et on slectionne le processus suivant dans la liste (voir gure 5.14).
Ordonnancement par priorits

Linconvnient de lordonnancement par tourniquet est que tous les processus dans ltat prt sont considrs de la mme faon et il ny a donc pas de notion de priorit dexcution. Il est pourtant clair quil peut y avoir des processus plus importants que dautres et que lutilisation de priorits peut tre intressante. 142

5.4. Lordonnancement des processus (scheduling)


Processus actif (dispose du processeur) Processus en attente (tat prt)

t t +1 t+2

P1 P2 P3

P2 P3 P4

P3 P4 P1

P4 P1 P2 P5

F IGURE 5.14 Principe de lordonnancement des processus par tourniquet, en pratique.

Pour effectuer un ordonnancement par priorits, chaque processus se voit attribuer une priorit. Le systme dexploitation maintient alors autant de listes chanes de processus quil y a de priorits diffrentes et, chaque changement de processus, il parcourt lensemble des listes de la plus prioritaire la moins prioritaire en appliquant chaque liste un ordonnancement par tourniquet (voir gure 5.15).
Priorit 0 Priorit 1 Priorit 2 Priorit 3
P5 P6 P9 P2 P3 P4 P7 P8 P1

Priorit 0 Priorit 1 Priorit 2 Priorit 3


P2 P6 P9 P7 P3 P4 P5 P8 P1

F IGURE 5.15 Ordonnancement des processus par priorits.

La priorit ainsi affecte chaque processus ne peut pas tre statique, car sinon un processus de trs haute priorit pourrait tourner seul sur la machine sans jamais rendre la main : il suft quil soit le seul de cette priorit. Par exemple, la gure 5.17 montre lordonnancement effectu dans le cas o un processus A a une priorit 5 et un processus B une priorit 3 : tant que le processus A existe, A a la priorit la plus leve et il est donc le seul bncier du processeur. Il est donc ncessaire de mettre en place une politique de priorit dynamique, cest--dire de dterminer lalgorithme qui permet de calculer chaque quantum de temps la nouvelle priorit de chaque processus. Outre le maintien de listes chanes associes chaque priorit, le systme dexploitation doit alors, chaque changement de processus, recalculer toutes les priorits et rordonner les diffrentes listes avant de les parcourir. Supposons que la priorit de chaque processus est dcrmente pour chaque unit de temps pendant laquelle le processeur lui a t attribu et incrmente pour chaque unit de temps pendant laquelle le processeur a t attribu un autre processus. Cette 143

Chapitre 5. La gestion des processus

8 7 6 5 4 3 2 1 Temps

Processus A Processus B

F IGURE 5.16 Utilisation de priorits statiques.

politique de priorit permet bien dattribuer le processeur aux processus qui sont en attente depuis un certain temps, mais elle pose nanmoins deux problmes : les priorits absolues et les attentes dentres / sorties. Tout dabord, il devient difcile daffecter une priorit absolue un processus. Supposons en effet que nous ayons un processus A de priorit 8 seul sur une machine. La priorit de ce processus va baisser chaque unit de temps et, ainsi, A aura une priorit de 1 au bout de 7 units de temps. Si nous lanons alors un processus B de priorit 6, B bnciera du processeur pendant 3 units de temps avant que A ne puisse prtendre au processeur. Le dernier processus cr bncie ainsi de plus dunits de temps que les processus plus anciens, ce qui na a priori aucun rapport avec les priorits relles de ces deux processus. Par ailleurs, aprs les 3 premires units de temps, les priorits des deux processus A et B vont se stabiliser autour dune position dquilibre (en loccurrence la priorit 5) et lordonnancement de ces deux processus seffectuera dsormais par tourniquet (voir gure 5.17). La notion de priorit perd son sens et la politique de gestion par priorits dcrite ci-dessus nest donc pas adquate.
8 7 6 5 4 3 2 1 Temps

Processus A Processus B

F IGURE 5.17 Utilisation de priorits dynamiques conduisant un ordonnancement par tourniquet.

144

5.4. Lordonnancement des processus (scheduling) Une politique dordonnancement cohrente consisterait alors, par exemple, affecter deux fois plus de quanta A qu B. Pour raliser ce type de gestion, les processus ont souvent, en plus de leur priorit dynamique courante, une priorit statique qui dnit les quantits utilises pour incrmenter et dcrmenter la priorit dynamique du processus et qui donne une borne maximale et minimale de la priorit dynamique. Dans notre exemple, on peut ainsi imaginer que la priorit de A ne peut varier quentre 7 et 8 et celle de B entre 1 et 8. Ceci permet de corriger leffet de centrage autour dune priorit moyenne, mais ne permet pas dviter une stabilisation selon un ordonnancement par tourniquet (voir gure 5.18).
8 7 6 5 4 3 2 1 Temps

Processus A Pstat = 7

Processus B Pstat = 1

F IGURE 5.18 Borner les priorits dynamiques permet dviter une stabilisation autour de la priorit moyenne, mais conduit tout de mme rapidement un ordonnancement par tourniquet.

En revanche, si on incrmente en outre la priorit de A de 5 et si on dcrmente sa priorit de 1 chaque fois, tout en incrmentant la priorit de B de 1 et en la dcrmentant de 4, A ne devra attendre que 4 units de temps avant de reprendre la main (voir gure 5.19).
8 7 6 5 4 3 2 1 Temps

Processus A Pstat = 7 Incr. = 5 Dcr. = 1

Processus B Pstat = 1 Incr. = 1 Dcr. = 4

F IGURE 5.19 Borner les priorits dynamiques tout en utilisant des incrments et des dcrments diffrents permet davantager un processus par rapport un autre.

145

Chapitre 5. La gestion des processus Lattente des entres / sorties pose aussi un problme. Supposons que nous disposons dun processus qui effectue beaucoup dentres / sorties. Ces entres / sorties durent gnralement assez longtemps et le processus ne pourra jamais proter de ses quanta de temps : comme il passe de ltat prt ltat en attente , il est immdiatement dessaisi du processeur (cest le principe mme de la multi-programmation). Il parat donc normal que ce processus bncie de ses quanta de temps lorsquil a termin son entre / sortie : il peut ainsi excuter rapidement quelques instructions et passer lentre / sortie suivante. Il est alors frquent dattribuer des priorits leves aux processus venant deffectuer des entres / sorties, cest--dire aux processus repassant dans ltat prt . La priorit ainsi calcule dpend gnralement du temps pass attendre la n des entres / sorties, ce qui reprsente une bonne estimation du nombre de quanta perdus.
Ordonnancement par dures variables

Les deux politiques dordonnancement dnies ci-dessus supposent de nombreux changements de contexte et nous avons vu que cela pouvait tre nuisible pour les programmes non interactifs effectuant beaucoup de calculs. Pour viter dinterrompre sans arrt ces programmes, une solution consiste attribuer un nombre diffrent de quanta chaque processus : les processus interactifs auront droit un seul quantum et les processus non interactifs auront droit plusieurs quanta. An de ne pas pnaliser les processus interactifs, le nombre de quanta attribus chaque processus est inversement proportionnel sa priorit dynamique et cette priorit diminue chaque quantum coul. Ainsi, supposons que nous disposons dun processus qui a besoin de 50 quanta de temps pour terminer lexcution de son programme. Ce processus se verra attribuer la priorit maximale 100 et un seul quantum. Puis, une fois ce quantum utilis, il se verra attribuer la priorit 99 et deux quanta. On continue ainsi de suite jusqu la n de son excution. Ce processus aura donc droit a 63 quanta (1 + 2 + 4 + 8 + 16 + 32) et aura ncessit 7 changements de contexte (au lieu de 50 pour le tourniquet). Les changements de contexte prennent dsormais assez peu de temps (ce ne fut pas toujours le cas) et cette politique prsente un gros dsavantage : si un processus trs peu prioritaire prend la main pour 50 quanta et que, au bout dun quantum, un processus prioritaire ou interactif est lanc, il devra attendre 49 quanta avant davoir la main. Nanmoins, cette mthode avait lavantage de favoriser les programmes interactifs et de ne pas trop interrompre les programmes de calculs longs.
Un exemple concret : 4.4BSD

An dillustrer concrtememt les principes dordonnancement noncs dans les sections prcdentes, nous allons maintenant dtailler la politique dordonnancement applique par un systme Unix particulier : le systme 4.4BSD. Comme la grande 146

5.4. Lordonnancement des processus (scheduling) majorit des systmes Unix, 4.4BSD cherche favoriser linteractivit tout en tentant de ne pas pnaliser les programmes qui ont besoin de beaucoup de temps CPU. La prise en charge des changements de contexte par les couches matrielles de la machine et, donc, la grande rapidit de ceux-ci a grandement facilit lapplication de cette politique. Le systme 4.4BSD utilise un ordonnancement par priorits, tel quil a t dcrit prcdemment, et il fonctionne donc en affectant une priorit dynamique chaque processus, en maintenant autant de listes de processus quil ny a de priorits et en effectuant un ordonnancement par tourniquet au sein de chaque liste de processus. Nanmoins, le systme 4.4BSD (comme la grande majorit des systmes Unix) utilise non pas deux, mais trois priorits diffrentes an de tenir compte de laction (ou de ltat) des processus dans lordonnancement et, par exemple, affecter une priorit dynamique leve un processus qui vient deffectuer une entre / sortie. Ainsi, pour calculer la priorit p_dyn affecte chaque processus, le systme 4.4 BSD utilise les paramtres suivants : Une priorit statique p_nice qui permet de dterminer la priorit relle (en dehors de toute notion dordonnancement) du processus concern ; Une priorit dvnement p_event qui permet notamment de favoriser les processus qui nont pas pu bncier de lintgralit de leurs quanta de temps (voir les raisons plus loin) ; La dure p_estcpu pendant laquelle le processus a rcemment bnci du processeur. En pratique, les priorits sont indiques dans lordre inverse de celui que nous avons employ jusquici : plus basse est la priorit, plus prioritaire est le processus. Ainsi, la priorit statique p_nice varie entre -20 et 20 et la priorit dynamique p_dyn entre 0 et 127. La priorit dynamique dun processus est alors calcule par la formule :
p_dyn = min(127, max(p_event, p_event + p_estcpu

+ 2 p_nice))

Si nous faisons abstraction du paramtre p_event, nous pouvons constater que la formule ci-dessus permet effectivement datteindre les objectifs xs : Les processus qui ont bnci longuement du processeur ont un paramtre p_estcpu lev et ils deviennent ainsi moins prioritaires. Ceci permet de rpartir de faon quitable le temps CPU. Les processus qui possdent une priorit statique p_nice faible (voir ngative) bncie du processeur plus souvent, cest--dire jusqu ce que le terme p_estcpu annule le terme p_nice. Notons que chaque utilisateur peut modier la valeur par dfaut de la priorit statique attribue ses processus. Nanmoins, an dviter que tous les utilisateurs ne tentent de rendre leurs processus plus prioritaires que ceux du voisin, il est uniquement possible de rendre ces processus moins prioritaires. Seul le super-utilisateur a le pouvoir de rendre un processus plus prioritaire que les processus de priorit standard. 147

Chapitre 5. La gestion des processus Le terme p_event permet quant lui, dune part, de borner la priorit dynamique et, dautre part, de tenir compte des derniers vnements qui ont perturb lexcution du processus concern. Voici un extrait des valeurs usuellement attribues p_event en fonction des diffrents vnements qui peuvent survenir : PSWP PVM PRIBIO PZERO PWAIT PPAUSE PUSER 0 4 16 22 32 40 50 Lorsquun processus a t retir de la mmoire principale ( swap ), faute de place Lorsquun processus est en attente dune allocation mmoire Lorsquun processus attend la n dune entre / sortie Priorit de base des processus excuts en mode noyau Lorsquun processus attend la terminaison dun de ses ls (wait) Lorsquun processus attend larrive dun signal Priorit de base des processus excuts en mode utilisateur

Ainsi, un processus qui doit effectuer une entre / sortie ne pourra pas consommer jusquau bout le quantum de temps qui lui avait t attribu (principe de la multiprogrammation), mais, en contrepartie, il bnciera dune priorit dynamique leve ds quil aura termin cette entre / sortie. En loccurrence, sa priorit sera PRIBIO au lieu de PUSER pour un processus ayant consomm normalement ses quanta de temps.
La politique dordonnancement sous Windows

La cration de processus sous Windowsest plus complexe et plus coteuse que sous Unix. La raison voque par les dveloppeurs du noyau actuel (Windows XP, Windows Vista et Windows Seven) est la suivante : Cette cration na pas tre peu onreuse car elle intervient trs rarement, gnralement lorsquun utilisateur clique sur une icne de programme ! Le choix, plus intressant, qui est fait est de privilgier la cration des threads et non des processus. Coupl un ordonnancement en mode utilisateur des threads, ce choix permet dacclrer les choses. Dans les versions prcdant Windows Seven (notamment Windows XP) lordonnanceur avait parfois des comportements peu quitables. Le compteur de cycles ntant pas pris en compte, il arrivait que certains threads soient dfavoriss par rapport dautres. Ainsi, lorsque deux threads TA et TB de mme priorit sont en mode prt , le noyau en choisit un, TA par exemple. Supposons quun thread TC de priorit plus leve arrive dans ltat prt . Le noyau dcide alors dinterrompre TA pour laisser la place TC sans garder trace du nombre de cycles rellement obtenus par TA. la n de TC, TB obtiendra le CPU car le noyau aura gard en mmoire le fait que TA a obtenu lintgralit de son quantum de temps. Et si TB nest pas interrompu, il aura eu, au nal, le CPU plus longtemps que TA tout en ayant une priorit gale. Cette politique a t modie dans les versions ultrieures (Windows Seven). Cest pourquoi, malgr une cration de thread efcace, lordonnancement peu quitable pouvait parfois donner limpression que le systme tait g en ne laissant que peu de place certains processus interactifs. 148

5.5. Conclusion Un nouveau module de lordonnanceur a fait son apparition partir de Windows Vista. Il sagit du MMCSS (MultiMedia Class Scheduling Service). Ce service permet de donner des priorits plus leves aux applications de type multimdia, gourmandes en CPU, an dviter que le dmarrage dun programme anti-virus ne saccade le dcodage et lafchage (rappelons que la partie graphique fait partie de lOS) dun contenu multimdia. Nous reviendrons sur ce module dans le chapitre 8 traitant des threads.

5.5

Conclusion

En conclusion, il est important de retenir que, dune part, la politique dordonnancement des processus dpend de lutilisation qui va tre faite de la machine et que, dautre part, la politique dordonnancement mise en place sur les systmes Unix est un bon compromis pour une utilisation polyvalente dun ordinateur. Prcisons pour les amateurs dexpriences intressantes quil est trs difcile de mettre au point en pratique une politique dordonnancement efcace et que la formule prsente ci-dessus est le rsultat de longues annes dtudes.

149

6
La gestion de la mmoire

La mmoire principale dun ordinateur est une ressource partager entre les diffrents processus qui sexcutent. La qualit de cette rpartition joue de faon agrante sur lefcacit de la concurrence dexcution entre les processus et la gestion de la mmoire est donc une fonction essentielle de tout systme dexploitation. Nous avons vu dans le chapitre 1 que la mmoire principale est rapide, mais relativement coteuse et de taille limite. Les mmoires secondaires, comme les disques durs, sont en revanche beaucoup moins chres et, surtout, elles offrent des volumes de stockage beaucoup plus importants. Les temps daccs ce type de mmoire sont malheureusement beaucoup trop longs et ils restent inadapts la vitesse des processeurs. Lobjet de ce chapitre est de montrer comment les systmes dexploitation optimisent lemploi de la mmoire principale en utilisant, dune part, des adresses virtuelles pour dsigner les donnes et, dautre part, des supports plus lents en tant que mmoire secondaire comme le disque dur. Ce chapitre est en cours de dveloppement et reste donc assez succinct. Mots cls de ce chapitre : MMU, gestionnaire de mmoire, adresses virtuelles, adresses physiques, fragmentation, pagination. 151

Chapitre 6. La gestion de la mmoire

6.1

Les adresses virtuelles et les adresses physiques

Nous avons dj rapidement abord ce sujet dans le chapitre 1 en esquissant lutilisation et la programmation de la MMU. Nous illustrons ici lutilisation pratique de la mmoire virtuelle par les systmes dexploitation. Il es toutefois bon de rappeler que ce principe de virtualisation de la mmoire est trs ancien et principalement li la raret de cette denre (la RAM) au dbut de linformatique. On en retrouve lorigine dans un article de James Kilburn en 1962 qui dcrivait un moyen daugmenter virtuellement la mmoire disponible pour les processus en associant, au travers dune translation dadresses, une mmoire centrale (mmoire tore) et un disque dur. Remarquons que ce principe de translation se retrouve dans nombre de domaine, nous pouvons ainsi citer les images format GIF dans lesquelles on associe un pixel une valeur, cette valeur tant une entre dans une table de 256 lments contenant chacune les trois valeurs RVB dune couleur. Il en va de mme pour la mmoire virtuelle, chaque adresse virtuelle correspondant, au travers dune table, une adresse physique. . . ou un dfaut de page, que nous tudierons dans ce chapitre.

Le rle de la MMU

Lorsquun programme est excut par un utilisateur, le systme dexploitation doit allouer la quantit de mmoire ncessaire au processus correspondant ce dernier. Ce processus (que nous nommerons proc1) sera alors situ (par exemple) aux adresses physiques 0 999.

F IGURE 6.1 La segmentation a priori de la mmoire ne permet pas de garantir que tout processus aura assez de place, ni que de la place ne sera pas inutilement perdue.

Si un autre processus, nomm proc2, est ensuite cr, il se verra probablement attribuer les adresses physiques 1000 1999. Le processus proc1 ne pourra alors pas allouer une zone mmoire contigu la zone qui lui a t attribue et, en cas de demande, le systme dexploitation lui attribuera par exemple la zone situe entre ladresse physique 2000 et ladresse physique 2999 (voir gure 6.2). 152

6.1. Les adresses virtuelles et les adresses physiques

0: 1000: 2000: 3000: . . .

libre proc1 proc2

F IGURE 6.2 Etat dallocation de la mmoire.

Les zones occupes par des processus peuvent ne.. pas tre contigus et, par exemple, . . . . si le processus proc2 libre les zones mmoires qui lui ont t attribues, une petite zone de mmoire libre va apparatre. Cette zone ne pourra alors tre utilise que pour des allocations futures ne rclamant que peu de mmoire et toutes les allocations numro e table de page numro de e e demandant une zone importantededevront utiliser page11numro de loctet0 les adresses physiques situes au31 21 dessus de ladresse physique 3000.
r sappelle la table de pages m Ce phnomneepertoire de pages fragmentation et il est frquent emoire physique diffrentes que, suite 0: allocations et librations, la mmoire soit fragmente en une multitude de petites zones 1: difcilement utilisables. . . .

Lorsque la mmoire est trop fragmente pour tre utilise correctement, deux 3: adresse mthodes sont utilises de la table pour proposer des zones mmoire importantes et contigus : la dfragmentation de la mmoire et la simulation dallocation contigu.
. . . . . .

2:

adresses de la page

1023:

La dfragmentation de la mmoire

. . .

Le principe est simple : il suft de dplacer les zones mmoires alloues vers les Figure 1: Conversion des tasser la adresses basses (voir gure 6.3). Cela revient adresses virtuellesmmoire en liminant toutes les zones libres.
1

Cette mthode a nanmoins un inconvnient majeur : il faut prvenir tous les processus du dplacement des zones qui leur sont alloues. Certains systmes utilisent nanmoins ce procd. Par exemple, les Macintosh, avant Mac OS X, effectuent la dfragmentation de la mmoire par des handles. Un handle contient un pointeur sur ladresse dun bloc mmoire allou. Un processus utilisant une zone mmoire doit alors verrouiller cette zone (an quelle ne soit pas dplace) et utiliser le handle pour dcouvrir ladresse de la zone mmoire. Aprs usage, la zone mmoire doit tre dverrouille pour permettre de nouveau au systme de dplacer cette zone si ncessaire. 153

0: 1000: 2000: 3000:

libre proc1 proc2

Chapitre 6. La gestion de la mmoire

. . .

. . .

. . .

F IGURE 6.3 Dfragmentation de la mmoire.

numro de table de page numro de page numro de loctet e e e 31 21 11 0

rpertoire de pages e table La simulation dallocations contigus de pages 0:

mmoire physique e

Lors dune demande dallocation mmoire par un processus, le gestionnaire de 1: . mmoire cherche des zones mmoire libres sans se soucier de leurs positions et il . . adresses de la pageforme quune seule et mme zone 2: laisse ensuite croire au processus que ces zones ne (contigu !). 3: adresse de la table Cette illusion est assure par lutilisation dadresses virtuelles. Comme nous lavons vu dans le chapitre 1, chaque zone mmoire possde deux adresses : une adresse dite physique, qui correspond la position matrielle de la zone en mmoire et une adresse . . . . . . dite virtuelle qui est un nombre arbitraire auquel il est possible de faire correspondre une adresse physique. 1023: La conversion des adresses virtuelles utilises par un processus en ...adresses physiques connues par les couches matrielles a lieu constamment pendant lexcution dun processus. Cette conversion doit donc tre trs rapide (puisquelle conditionne la vitesse dexcution dun processus) et est assure par un composant lectronique log dans le processeur : la MMU 1 .1: Conversion des adresses virtuelles Figure Puisque nous allons, au travers de la MMU, utiliser une traduction, il serait fort peu judicieux dallouer chaque demande lexacte quantit souhaite par le processus. 1 La mmoire est donc pagine et la plus petite quantit que lon peut demander est en fait une page. Ceci est trs li larchitecture du microprocesseur et pas vraiment au systme dexploitation. Un Pentium II supportait des pages de 4096 octets. Par contre les processeurs Sparc autorisent des pages de 8192 octets et les derniers UltraSparc supportent de multiples tailles de pages. Ce dcoupage de la mmoire na strictement aucun effet sur les adresses. Supposons quune adresse mmoire soit code sur 16 bits. Cette adresse peut prendre une valeur dans lintervalle {0, 65535}. Si la taille de page est de 4096 octets (212 ), une adresse est donc simplement un numro de page sur 4 bits suivi dun dcalage dans la page cod sur 12 bits. La gure 6.4 rsume ce principe.
1. La MMU na pas toujours t loge directement dans le processeur, mais cest dsormais le cas.

154

6.1. Les adresses virtuelles et les adresses physiques La page tant lunit atomique de mmoire, un adressage virtuel doit maintenir lassociation entre ladresse virtuelle dune page et son adresse physique. Cest le rle de la MMU. Ce tableau associatif est appel table des pages, il est index par les adresses virtuelles.

F IGURE 6.4 Le dcoupage de la mmoire en pages na aucun effet sur les adresses.

Sur un systme capable dadresser 4 Go octets de mmoire (32 bits), le nombre de pages de 4 ko dont il faut garder ladresse est de 1048576 ! Et comme chaque processus possde son systme dadressage virtuel, il faut conserver lensemble des associations de pages par processus. An de ne garder en mmoire que le strict minimum, nous pouvons reproduire le dcoupage vu prcdemment an de convenir quune adresse est forme par : les 10 bit de poids fort permettant daccder 1024 tables de pages diffrentes au travers dun catalogue de tables de pages ; les 10 bits qui suivent permettent daccder aux 1024 pages diffrentes dune table de pages ; enn les 12 bits de poids faible permettent de se dplacer dans les 4096 octets dune page. Pour un processus et une adresse virtuelle donne, il faut donc avoir disposition le catalogue des tables de pages et la table de pages concerne. Les tables de pages inutilises peuvent trs bien ne pas se trouver en mmoire. . . celles qui sont utilises doivent tre imprativement en mmoire physique ! Le catalogue des tables de pages se trouve dans la mmoire physique, une adresse dont la valeur est place dans un registre spcial, le registre CR3. La gure 6.5 illustre ces tapes de traduction successives. An dacclrer la traduction des adresses on utilise un mcanisme de cache des diffrents catalogues et des diffrentes tables, le TLB ou Translation Lookaside Buffer . Le TLB contient la traduction de certaines adresses virtuelles en adresses physiques. Il est rgulirement tenu jour, ainsi lorsquun catalogue ou une table est modi, son entre dans le TLB est invalide. Dans ce cas on reprend le calcul vu 155

Chapitre 6. La gestion de la mmoire

F IGURE 6.5 Les tapes de traduction dune adresse virtuelle en adresse physique. Le registre CR3 donne ladresse physique du catalogue des tables de pages. On rajoute cette adresse loffset contenu dans les 10 premiers bits de ladresse virtuelle. On obtient cet endroit ladresse physique de la table des pages. cette adresse physique on rajoute les 10 bits intermdiaires de ladresse virtuelle et on obtient cet endroit ladresse physique de la page laquelle on rajoute les 12 bits de poids faible de ladresse virtuelle.

prcdemment pour aller chercher le catalogue ou la table et on valide cette recherche dans une entre du TLB. Les entres dans une table des pages sont organises de faon obtenir un certain nombre dinformations sur les pages mmoires physiques. Cette organisation est lie une architecture matrielle du processeur. Par exemple, sur un processeur de la famille Intel, outre ladresse recherche 2 , on trouvera les entres suivantes : _PAGE_PRESENT : indiquant si la page est prsente en mmoire physique ; _PAGE_RW : indique si la page est en lecture seule ; _PAGE_USER : indique si cette page est accessible en mode utilisateur ; ... Gardons en mmoire que le mcanisme que nous venons de dcrire sapplique en fait pour chaque processus. Un changement de contexte, du point de vue de la mmoire, doit donc sauvegarder le registre CR3 du processus courant, charger dans ce registre
2. Le lecteur curieux peut remarquer quune entre de la table des pages contient 32 bits. 12 bits sont rservs pour dcrire la page (prsence, lecture / criture, utilisateur, . . . ). Il reste donc 20 bits pour dcrire une adresse physique de 32 bits ! En fait, les adresses doivent tre alignes sur des multiples de 4 ko car chaque page contient 4096 octets. Donc les 12 bits de poids faible de toutes les adresses de dbut de page sont toujours nuls. La MMU prend donc les 20 bits de poids fort du mot de 4 octets contenu dans lentre de la table, rajoute 12 bits nuls et obtient ainsi ladresse physique de la page.

156

6.2. Les services du gestionnaire de mmoire la valeur sauvegarde du nouveau processus et soccuper du TLB. Nous comprenons aussi pourquoi parmi ce que ralise un systme dexploitation au dmarrage, la programmation de la MMU est fondamentale. Le gestionnaire de mmoire joue un rle essentiel.

6.2

Les services du gestionnaire de mmoire

Le gestionnaire de mmoire est charg dassurer la gestion optimale de la mmoire. Son fonctionnement varie suivant les systmes dexploitation, mais les systmes dexploitation modernes et efcaces proposent gnralement les services suivants : lallocation et la libration de mmoire, la protection de la mmoire, la mmoire partage, les chiers mapps et la pagination.
Lallocation et la libration de mmoire

Lallocation de mmoire permet un processus dobtenir une zone mmoire disponible. Nous avons vu dans le chapitre 5 comment lespace mmoire des processus tait organis et comment un processus pouvait demander lallocation dynamique, en cours dexcution, dune zone mmoire. An de rpondre aux demandes des processus, le gestionnaire de mmoire doit : maintenir jour une liste des pages mmoire libres ; chercher dans cette liste les pages allouer au processus ; programmer la MMU an de rendre ces pages disponibles ; librer les pages attribues un processus lorsque celui-ci nen a plus besoin. Le systme dexploitation Unix propose les appels systme brk() et sbrk() pour grer la mmoire. Toutefois, ces appels sont trs peu pratiques car ils ne fournissent que des blocs mmoire dont la taille est un multiple de la taille dune page. De plus, les pages alloues un processus forment un unique bloc dont les adresses logiques sont conscutives ; il nest pas possible de librer une page au milieu de ce bloc (voir aussi lexplication sur le tas, chapitre 5). Les fonctions malloc() et free() permettent une gestion plus souple de la mmoire. Elles utilisent les appels systme prcdents pour obtenir des pages, mais un mcanisme indpendant permet dallouer et de librer des zones mmoire situes dans ces pages.
La protection

La protection de la mmoire est un service essentiel des systmes dexploitation. Il nest pas acceptable aujourdhui quune erreur dans un programme ddition de textes, par exemple, provoque larrt dune machine essentiellement utilise pour faire des calculs. 157

Chapitre 6. La gestion de la mmoire De faon gnrale, il nest pas tolrable que le fonctionnement dune machine (quelle que soit son utilisation) soit mis en cause par des erreurs logicielles qui pourraient tre jugules. La principale fonction de la protection de la mmoire est dempcher un processus dcrire dans une zone mmoire attribue un autre processus et, donc, dempcher quun programme mal conu perturbe les autres processus. Pour accomplir cette tche, le systme dexploitation doit programmer la MMU an quun processus ne puisse avoir accs quaux pages mmoire qui lui appartiennent. Le systme dexploitation cre alors un rpertoire de pages par processus. Le rpertoire de pages dun processus contient les adresses des pages attribues ce processus et, comme les rpertoires et les tables de pages sont stockes dans lespace mmoire du systme dexploitation, seul le noyau peut les modier. Si un processus tente daccder une page qui ne lui appartient pas, la MMU prvient instantanment le systme dexploitation (via une interruption) et ce dernier peut envoyer un signal au processus fautif. Comment est-il alors possible, avec un tel systme dexploitation, de demander un dbugeur de lire les variables dun processus quil scrute ? La rponse est simple et nous esprons vidente : il suft de le demander au systme dexploitation grce lappel systme ptrace() qui permet un processus de prendre le contrle dun autre (aprs vrication par le systme dexploitation que toutes les oprations demandes sont licites, bien sr).
La mmoire partage

La mmoire partage permet deux processus dallouer une zone de mmoire commune. Cette zone peut tre ensuite utilise pour changer des donnes sans appel du systme dexploitation : ce mcanisme est donc trs rapide, mais ncessite en gneral dtre rgul par un mcanisme de synchronisation. Nous avons vu dans le chapitre 4 un exemple dutilisation de mmoire partage : le chargement en mmoire dune seule copie de code excute par plusieurs processus. Comme pour les autres services du gestionnaire de mmoire, cette tche est grandement simplie par lutilisation dadresses virtuelles. Un usage pratique de zones mmoire partages sous Unix est par exemple exploit par le jeu Doom port sur Linux. Cette version utilise un segment de mmoire partage pour changer les nouvelles images afcher avec le serveur X, ce qui acclre considrablement lafchage. La conduite par dfaut du gestionnaire de mmoire est dempcher tout processus daccder lespace mmoire dun autre. Il est donc indispensable de faire appel des fonctions particulires pour demander lallocation dune zone de mmoire partage. Le systme dexploitation Unix propose les fonctions suivantes : shmget() : cette fonction cre ou recherche un segment de mmoire partage. Une cl est ncessaire an dindiquer au systme quel segment on souhaite rcuprer. 158

6.2. Les services du gestionnaire de mmoire


shmat() : cette fonction associe le segment de mmoire partage une

partie de la mmoire (virtuelle) du processus. Un pointeur est retourn, permettant ainsi au processus de lire ou crire dans ce segment. shctl() : cette fonction permet diverses manipulations du segment de mmoire partage comme la suppression, la lecture des attributs ou la modication de ses attributs. Le programme suivant cre un segment de mmoire partage associ la cl 911 :

#include #include #include #include

<sys/types.h> <sys/ipc.h> <sys/shm.h> <stdio.h>

#define SHM_KEY 911 #define SHM_SIZE 1024 main() { int shmid; char *p; /* Je cree un segment de memoire partagee */ shmid = shmget(SHM_KEY,SHM_SIZE,IPC_CREAT|0666); if(shmid==-1) { printf("Memoire partagee non disponible\n"); exit(1); } /* Jaffecte une partie de ma memoire virtuelle a ce segment ladresse logique du segment est retournee dans p */ p=shmat(shmid,NULL,0); if(p== (char *) -1) { printf("La memoire partagee ne peut etre affectee\n"); exit(1); }

159

Chapitre 6. La gestion de la mmoire

strncpy(p,"Jecris dans la memoire partagee\n",SHM_SIZE); /* Je marrete... mais ne supprime pas le */ /* segment de memoire partagee!!! */ }

La prsence du segment de mmoire partage peut tre mis en vidence par la commande ipcs. Le programme suivant lit le contenu du segment de mmoire partage et supprime ce dernier.

#include #include #include #include

<sys/types.h> <sys/ipc.h> <sys/shm.h> <stdio.h>

#define SHM_KEY 911 #define SHM_SIZE 1024 main() { int shmid; char *p; /* Je cree un segment de memoire partagee */ shmid = shmget(SHM_KEY,SHM_SIZE,0666); if(shmid==-1) { printf("Memoire partagee non disponible\n"); exit(1); } /* Jaffecte une partie de ma memoire virtuelle a ce segment ladresse logique du segment est retournee dans p / * p=shmat(shmid,NULL,0); if(p== (char *) -1) {

160

6.2. Les services du gestionnaire de mmoire


printf("La memoire partagee ne peut etre affectee\n"); exit(1); } printf("Contenu de la memoire partagee : %s\n",p); /* Je supprime le segment de memoire partagee */ shmctl(shmid,IPC_RMID,NULL); }

Les chiers mapps

Les chiers mapps sont des chiers dont le contenu est associ une partie de lespace adressable dun processus. La lecture effective du chier nest ralise quau moment de la lecture de la mmoire. Par ailleurs, le systme est libre de rallouer la mmoire physique utilise aprs cette lecture, puisque le contenu de cette mmoire nest pas perdu : il est toujours possible de relire le chier. Ce service utilise une fois de plus la MMU : comme la MMU est capable dintercepter tous les accs des pages mmoires invalides, il suft de dclarer les pages mmoires associes un chier comme invalides. Lorsquun processus tentera de lire lune de ces pages, il produira un dfaut de page qui sera dtect par le systme dexploitation. Ce dernier, au lieu denvoyer un signal au processus comme il le fait habituellement, allouera une page de mmoire physique, chargera dans cette page le contenu du chier et rendra la page demande valide an que le processus puisse y accder. Ce service est utilis sur certains systmes Unix et sous Windows an de charger les bibliothques de fonctions utilises par la plupart des programmes. Lintrt est double : le systme peut toujours dcharger les bibliothques si cela est ncessaire et il na pas besoin de charger plusieurs fois les bibliothques utilises par plusieurs processus. Sous Unix, la fonction utilise pour mapper des chiers en mmoire sappelle mmap(). On utilisera munmap() pour supprimer la relation cre par mmap(). Vous pouvez observer lutilisation de ces fonctions en observant le lancement dun processus dans un shell laide de la commande
[moi@moi]~# strace prog_exe

Vous constaterez que les librairies partages sont bel et bien mappes en mmoire.
La pagination

Lutilisation dadresses virtuelles permet au systme dexploitation de grer la mmoire principale par zones de grandes tailles (habituellement 4 ko) appeles des 161

Chapitre 6. La gestion de la mmoire pages. Cette gestion permet dutiliser aussi bien des zones de la mmoire principale que des zones de la mmoire secondaire. Une consquence directe de principe est que les processus peuvent demander lallocation de plus de mmoire quil ny en a de disponible dans la mmoire principale. Certes, il serait possible dans ce cas-l de crer un grand chier et de le mapper en mmoire. Cette solution nest toutefois pas pratique pour plusieurs raisons : tout dabord, le programmeur doit explicitement demander une telle association ; ensuite, la commande malloc() ne peut tre utilise pour grer la mmoire associe au chier. Pour rsoudre ce problme, diffrentes solutions ont t proposes et la solution qui est aujourdhui la plus rpandue est la pagination. Dans les sections suivantes, nous abordons rapidement les solutions historiques et nous dtaillons le principe de pagination.

6.3

La gestion de la mmoire sans pagination

Le va-et-vient

Les premiers systmes permettant la multiprogrammation utilisaient des partitions de la mmoire de taille xe (OS/360 avec le MFT : Multiprogramming with a Fixed number of Tasks). chaque espace de la partition est associe une liste des tches pouvant y tre affectes de par leur taille. On peut aussi utiliser une liste unique et, lorsquun espace de la partition se libre, lui affecter la tche dont la taille utilise au mieux lespace libre. Remarquons que si on obtient ainsi un remplissage optimal de la mmoire centrale, lexcution des tches de tailles les plus faibles sont les moins prioritaires. La mise en place du temps partag ne rend plus possible lutilisation dune partition de la mmoire en zones de taille xe. En effet, le nombre des processus existant un moment donn peut, a priori, dpasser les capacits de la mmoire centrale de la machine. Il faut pouvoir organiser un va-et-vient (swapping) des processus entre la mmoire centrale et une mmoire secondaire, comme un disque. Lutilisation de partitions de taille xe ferait alors perdre beaucoup trop de mmoire centrale par suite de la diffrence entre tailles des processus et tailles des partitions. Trop peu de processus pourraient tre en mme temps en mmoire et trop de temps se trouverait perdu en va-et-vient. Il faut donc disposer de partitions dont la taille sadapte dynamiquement la cration de nouveaux processus.
Utilisation de partitions de taille variable

Avec des partitions de taille variable, lallocation de place mmoire des processus est plus complexe que dans le cas de partitions de taille xe. De plus, il serait possible de dplacer des processus au cours de leur existence de manire minimiser la fragmentation. En revanche, si un processus grandit au cours de son existence, lespace dont il dispose peut ne plus lui sufre et il devra tre dplac dans la mmoire. Si 162

6.3. La gestion de la mmoire sans pagination


Systme Processus 2 Processus 4

Processus 1

Processus 3

Processus 5

Copi sur une zone de mmoire secondaire (disque dur)

Excutables relogeables

F IGURE 6.6 Les zones mmoire des processus relogeables peuvent tre dplaces ou copies sur dautres zones mmoire, comme la mmoire secondaire.

la mmoire centrale sature, on peut faire intervenir le mcanisme de va-et-vient de manire utiliser lespace disque disponible. Si ce dernier arrive aussi saturation, il ne reste plus qu tuer le processus qui est demandeur de mmoire. De manire permettre des allocations de partitions de taille variable, la mmoire est divise en partitions lmentaire dallocation. Leur taille peut varier de quelques octets quelques kilooctets suivant les systmes. Lallocation seffectue alors sur un ensemble dunits voisines. Le problme consiste savoir chaque instant quelles sont les units occupes, et ce, de manire pouvoir trouver rapidement lemplacement demand par un nouveau processus. Il existe pour cela deux mthodes. Lutilisation dune table de bits, maintenue par le systme, dans laquelle chaque bit correspond chaque unit physique. La valeur de ce bit indique si lunit dallocation est dj occupe par un processus ou si elle est libre. La taille de la table dpend de la taille de lunit lmentaire, mais il est facile de voir que lemplacement occup par la table ne reprsente quune faible proportion de la mmoire mme pour des units de taille relativement petite. En revanche, lallocation dun processus ncessitant k units demande de trouver k units libres voisines dans la table et, donc, entrane une lecture complte de la table. Cette lenteur dans lallocation des processus importants fait que ce systme est peu employ. Lutilisation dune liste chane dans laquelle sont maintenues la liste des emplacements occups et la liste des emplacements libres. Un emplacement libre est une suite dunits lmentaires libres entre deux processus. Un emplacement occup est un ensemble dunits lmentaires occupes par un mme processus. La liste peut tre trie, par exemple, par ordre dadresses croissantes. Plusieurs algorithmes sont envisageables pour dterminer comment choisir la zone libre, o sera charg le processus cr ou dplac : Lalgorithme du premier emplacement libre (rst t) : on place le processus dans le premier emplacement libre rencontr dans le parcours de la liste, qui peut le contenir. Lalgorithme est rapide, puisque la recherche est courte. 163

Chapitre 6. La gestion de la mmoire En revanche, il cre potentiellement de nombreux petits emplacements libres, qui seront difciles allouer. Lalgorithme de lemplacement libre suivant (next t) : idem que lalgorithme du premier emplacement libre, mais la recherche reprend de lendroit o le parcours stait arrt la prcdente recherche. Les rsultats sont moins bons que pour lalgorithme du premier emplacement libre. Lalgorithme du meilleur ajustement (best t) : on parcourt entirement la liste la recherche de lemplacement libre dont la taille correspond le mieux avec la taille mmoire ncessaire au processus. Ceci permet dviter un gaspillage de mmoire par fractionnement de la mmoire libre, mais lallocation est plus longue du fait du parcours complet de la liste. Lalgorithme du plus grand rsidu (worst t) : on parcourt entirement de manire choisir lemplacement libre tel que lallocation de la mmoire ncessaire au processus dans cet emplacement donnera naissance un emplacement libre rsiduel de la taille la plus grande possible. La simulation montre que cet algorithme ne donne pas de bons rsultats. Plusieurs optimisations de ces algorithmes sont possibles. Par exemple, lalgorithme de placement rapide (quick t) peut utiliser plusieurs listes spares suivant la taille des zones libres, ce qui acclrent les recherches. Cependant, lors de la disparition dun processus de la mmoire centrale, le maintien dune seule liste pour les emplacements libres et occups permet par une simple consultation dans la liste deffectuer les fusions avec les emplacements libres voisins. Dans le cas de lutilisation de plusieurs listes, la recherche des fusions est plus longue. Le problme est le mme dans le cas dun classement par taille des emplacements libres (classement qui allie les avantages best t et rst t pour lallocation de la mmoire). Pour obtenir des rsultats satisfaisants aussi bien pour lallocation que pour la libration, il faut mettre au point un compromis.
Lespace de va-et-vient

Le principe du va-et-vient entrane pour certains processus la ncessit dtre recopis sur un disque en raison du manque de place en mmoire centrale. Certains systmes nallouent pas pour chaque processus cr une place sur le disque, mais rservent une zone de va-et-vient (swap area), destine accueillir les processus sur le disque (cest le cas dUnix). Lallocation de la mmoire dans cette zone du disque rpond au mme principe de gestion que la mmoire centrale.
Le mcanisme doverlay

Une autre contrainte est la taille des processus. Si les processus sont trop importants pour la mmoire centrale, il ne pourra gure y avoir quun seul processus la fois dans la mmoire centrale. Ce qui enlve tout intrt au problme des partitions qui vient dtre tudi. Cependant, pour un processus quelconque, le code tant excut de manire relativement linaire, les instructions et les donnes dont on doit disposer en 164

6.4. La pagination mmoire centrale ne reprsentent quune partie de la totalit de limage mmoire du processus. On pourrait, donc, se contenter davoir en mmoire centrale la partie utile de code et mettre sur disque, dans la zone de va-et-vient, le reste de limage mmoire du processus. Lutilisation de partitions serait, alors, de nouveau possible. Une premire ide consiste utiliser des segments de recouvrement (overlays). Cette mthode tait utilise par les programmeurs pour crire de gros programmes. Comme elle tait trs fastidieuse programmer, on chargea le systme de gestion de la mmoire de la mettre en uvre. Elle consiste dcouper le programme en parties cohrentes, qui sexcutent les unes aprs les autres en occupant successivement le mme emplacement de la mmoire centrale, chacune recouvrant son prdcesseur. Les segments non actifs dun processus sont stocks sur une mmoire secondaire comme un disque. Un processus en attente du chargement dun de ses segments est, en fait, en attente dentre/sorties, le processeur peut donc tre affect un autre processus pendant le chargement du segment. Ce procd nest, cependant, plus utilis en tant que tel aujourdhui. On ne le trouve plus que sur les systmes noffrant pas une possibilit de mmoire virtuelle. Lidal est en effet doffrir lutilisateur limpression que la mmoire centrale de lordinateur est beaucoup plus importante que ce quelle nest en ralit. Cest le concept de mmoire virtuelle : la taille mmoire de la machine apparat comme plus grande par une gestion ne dterminant quelles parties des codes des processus doivent se trouver en mmoire centrale, et quelles parties ne sont pas utilises et peuvent tre relgues sur disque. Deux mthodes existent pour effectuer cette gestion : la mthode des mmoires segmentes et la mthode des mmoires pagines.
Les mmoires segmentes

Le principe consiste adopter la vision logique des utilisateurs. chaque fonction du programme ou ensemble de donnes, on fait correspondre un espace mmoire. La partition logique dun processus correspond donc une partition logique en un ensemble despaces mmoires, les segments. Seuls les lments ncessaires au bon droulement du processus sont chargs en mmoire un instant donn. chacun de ces espaces, sont associes des donnes qui permettent au systme de dterminer la longueur du segment. Une telle mthode pour reprsenter une mmoire virtuelle est assez difcile mettre en uvre. Cest pourquoi les mmoires pagines sont beaucoup plus rpandues.

6.4

La pagination

Le principe

Le principe de la pagination est de dcouper limage mmoire dun processus en blocs de taille donne : les pages. Alors que le systme des segments de recouvrement suppose une unit logique du segment, ce nest pas le cas de la page. Logiquement, le 165

Chapitre 6. La gestion de la mmoire processus voit donc un espace dadressage linaire beaucoup plus vaste que la place physique dont il dispose dans la mmoire centrale. Mais le fait quune partie de cet espace se trouve sur un disque est compltement transparent pour le processus : on dit quil dispose dun espace dadressage virtuel et quil utilise des adresses virtuelles. Cest la traduction des adresses virtuelles en adresses physiques qui permet au systme dexploitation de grer le chargement des pages ncessaires la bonne excution du processus.

Dcouper la mmoire en pages (comme un livre)

F IGURE 6.7 Le principe de la pagination

La mmoire physique est partage en cases mmoire (page frames). sa cration, un processus se voit allouer un certain nombre de ces cases, elles reprsentent le nombre de pages dont le processus pourra disposer la fois en mmoire centrale. Un accs une adresse par le processus seffectue soit rellement si ladresse est situe sur une page rsidant en mmoire centrale, soit provoque une erreur, si la page adresse ne sy trouve pas, laccs mmoire attendra alors pour seffectuer le chargement depuis le disque de la page en question. Le processus tant en attente dune entre/sortie, le processeur pourra tre allou un autre processus pendant le chargement de la page. Ce droutement de laccs mmoire par le systme sappelle un dfaut de page (page fault). La gestion de la pagination est compose de mcanismes matriels constituant lunit de gestion mmoire, de manire tre plus efcace. Ainsi, par exemple, lutilisation de la mmoire virtuelle dans Unix 4.4BSD est largement dpendante des mcanismes matriels de gestion de la mmoire du VAX. En effet, la reprsentation de ladresse virtuelle comprend le numro de page et ladresse dans la page. Par un masque, lunit de gestion mmoire a alors sa disposition le numro de la page et peut examiner si la page en question est en mmoire en consultant la table des pages du processus, dont elle dispose.
Les systmes mixtes

Des systmes mixtes pagination-segmentation ont t mis au point. Multics a tent dlaborer un systme o, chaque segment, tait allou un objet logique (chier, zone de donnes, zone de texte...) et o chaque segment tait constitu de plusieurs pages. Les 36 bits dadressage de Multics se sont rvls insufsants pour la ralisation 166

6.4. La pagination de ce systme. Aujourdhui, les machines base de 68000 utilisent sous une forme simplie un concept de ce genre avec une mmoire virtuelle mettant la disposition de 16 processus 1024 pages de 2 ko ou 4 ko. Ainsi, pour des pages de 4 ko, chaque processus dispose de 4 Mo de mmoire virtuelle de 1024 pages. Au lieu dutiliser un tableau de pages 1024 entres, lunit de gestion mmoire dispose dune table de 16 sections (une section par processus en mmoire), chaque segment possde 64 entres de descripteurs de segments, chaque descripteur fournissant un pointeur vers une table de pages 16 entres. Un des avantages est de disposer de possibilits simples de partage de pages : il suft de faire pointer le pointeur du segment vers la mme table de pages.

Les algorithmes de remplacement de pages

Sil est facile de calculer quelle page on veut voir charge en mmoire, il est moins simple de dterminer quelle page dj charge va tre recouverte lors du chargement de la nouvelle page, en labsence de case mmoire libre pour le processus. Lefcacit de lunit de gestion de la mmoire va dpendre du choix de cet algorithme de remplacement. Lalgorithme optimal est facile dcrire : il sagit de laisser recouvrir la page qui ne sera pas rfrence avant le temps le plus long. Il est malheureusement impossible mettre en uvre. Il existe de nombreux algorithmes proposs : Lalgorithme de la page non rcemment utilise (NRU Not Recently Used) : retire une page au hasard de lensemble de numro le plus bas des ensembles suivants : Ensemble 0 : ensemble des pages qui nont jamais t lues, ni crites. Ensemble 1 : ensemble des pages qui nont jamais t lues, mais ont t crites. Ensemble 2 : ensemble des pages qui nont jamais t crites, mais ont t lues. Ensemble 3 : ensemble des pages qui ont t lues et crites. Lalgorithme premire entre, premire sortie (FIFO, First In, First Out) : retire la page qui a t charge depuis le plus longtemps. On peut lutiliser en mme temps que lalgorithme NRU, en lappliquant chaque ensemble, plutt que de choisir au hasard. Lalgorithme de la page la moins rcemment utilise (LRU, Least Recently Used) : on retire la page qui est reste inutilise pendant le plus longtemps. Lalgorithme NRU nest pas optimal, mais il est souvent sufsant. Il est, de plus, simple mettre en uvre, car il ne ncessite pas de matriel spcialis, au contraire de lalgorithme LRU. Lalgorithme FIFO est douteux, car il peut entraner le retrait dune page trs utile, rfrence souvent depuis le dbut dexcution du processus. 167

Chapitre 6. La gestion de la mmoire

6.5

Conclusion

Lintroduction de la mmoire virtuelle et lutilisation de composants ddis pour grer la traduction vers les adresses physiques offre de grandes possibilits, notamment le fait de pouvoir simuler une allocation continue de mmoire et permet ainsi le dplacement des processus sans quun quelconque mcanisme de rorganisation interne soit ncessaire. Ce principe de virtualisation permet de sabstraire des couches matrielles et nous verrons quil sapplique dautres fonctions dun systme dexploitation.

168

7
Le systme de chiers

Le systme de chiers dun systme dexploitation est la partie la plus couramment sollicite par lutilisateur. Par exemple, lcriture dun programme en langage C passe par la cration dun chier dans lequel les diffrentes lignes de code seront conserves et engendre gnralement la cration de plusieurs autres chiers (voir chapitre 4). De mme, les donnes dentres ou de sorties dun programme sont trs souvent conserves dans des chiers qui peuvent tre lus ou crits. Lorganisation du systme de chiers est donc primordiale et elle conditionne lutilisation efcace de la machine. Nanmoins, comme pour les autres services rendus par les systmes dexploitation, lobjectif est doffrir le service le plus efcace tout en proposant une vision simple lutilisateur qui ne veut pas se proccuper des dtails dorganisation physique du systme. Ce chapitre montrera ainsi que, au del des chiers traditionnels ou de la notion de chier traditionnelle auxquels lutilisateur est habitu, le systme de chier reprsente en fait une abstraction ultime de tout change et stockage de donnes, quelle que soit la forme quils peuvent prendre. Nous allons aborder dans ce chapitre les aspects gnraux des systmes de chiers en partant des plus visibles aux structures les plus internes. 169

Chapitre 7. Le systme de chiers

7.1

Les services dun systme de chier

Les chiers

La notion principale manipule lors de lutilisation dun systme de chiers est, on peut sy attendre, le chier. Un chier est une suite ordonne doctets 1 quun programme peut lire ou modier. Dans le cas dun programme crit en langage C ou un A texte destin tre typographi par LTEX, par exemple, chaque octet peut reprsenter un caractre ou participer cette reprsentation 2 ; diffrentes normes dnissent les valeurs associes chaque caractre (voir la section 4.1 du chapitre 4). Nous parlons dans ce cas dun chier texte par opposition aux chiers binaires crs selon dautres conventions (normes GIF, JPEG ou MP3, par exemple) Notons que souvent la plupart des systmes dexploitation ne font pas de distinction entres ces chiers : ce sont les programmes qui les manipulent qui doivent utiliser les bonnes conventions pour interprter correctement le contenu des chiers quils utilisent. An de rendre cette suite doctets disponible aprs arrt de lordinateur, cette suite est gnralement enregistre sur un support permanent que nous appellerons priphrique de stockage (disque dur dans la plupart des cas, mais nous pouvons aussi citer disque magnto-optique, mmoire permanente et, dans certains cas, les bandes magntiques). Chaque chier est associ un nom qui sera utilis ensuite pour lidentier. laide de ce nom, un programme peut demander au systme dexploitation la lecture ou lcriture du chier. Pour beaucoup dapplications, le chargement complet dun chier en mmoire nest ni utile ni prfrable (pour des raisons de cots, la capacit de la mmoire est trs souvent infrieure celle des priphriques de stockage). Dans ce cas, un programme utilisera le chier comme une bande magntique, en lisant successivement des petites parties dun chier. On parle dans ce cas dun accs squentiel. Pour certaines applications, il peut tre utile de lire les informations enregistres dans un ordre quelconque. Dans ce cas on parlera daccs alatoire. La plupart des systmes dexploitation ncessite avant les oprations de lecture et dcriture louverture dun chier. Cette ouverture pralable permet au systme dexploitation de ne rechercher le chier daprs son nom quune seule fois. Une structure est cre an de mmoriser des informations telles que : o est situ le chier, quelle est la position courante dans le chier (quelle partie du chier sera lue ou crite la prochaine opration). Selon les systmes dexploitation, le rsultat retourn lors de louverture est appel descripteur de chier (Unix) ou handle (Mac OS, Windows). Il sagit dune forme de pointeur sur les informations relatives au chier ouvert (voir les exemples du chapitre 11).
1. Notons lexception de Mac OS, o un chier est associ deux suites doctets appels Data fork et Ressource fork . 2. Nous avons vu quun caractre, selon le systme de codage UTF-8 peut tre cod sur plusieurs octets.

170

7.1. Les services dun systme de chier Les oprations gnralement offertes par la plupart 3 des systmes de chiers sont les suivantes : Louverture dun chier : cette opration consiste rechercher daprs son nom un chier sur le priphrique de stockage. Le rsultat de cette recherche sert initialiser une structure qui sera ncessaire aux manipulations suivantes du chier. Gnralement, les permissions daccs un chier sont vries ce moment, ce qui dispense le systme dune telle vrication lors de chacune des oprations suivantes. La cration dun chier : cette opration consiste crer sur le priphrique de stockage, un nouveau chier, vide. Ce chier est immdiatement ouvert (un descripteur de chier ou handle est retourn), puisque la cration dun chier vide est rarement une n en soi. Lcriture dans un chier : cette opration consiste modier le contenu dun chier partir dune position courante . Dans le cas o la position courante arrive au-del de la taille du chier, le chier saccroit an de permettre le stockage de ces octets. La lecture dans un chier : cette opration consiste lire une srie doctets dans un chier, partir dune position courante . En cas de dpassement de la taille du chier, une information n de chier est retourne. Le dplacement lintrieur dun chier : cette opration consiste changer la position courante. Cette opration permet un accs direct nimporte quelle partie dun chier sans lire ou crire tous les octets prcdents. La fermeture dun chier : consiste supprimer les structures du systme dexploitation associes un chier ouvert. La suppression dun chier : cette opration consiste supprimer sur le priphrique de stockage, les donnes associes un chier. Cette suppression libre une place sur le disque qui pourra tre rutilise pour agrandir des chiers existants ou crer dautres chiers 4 . La lecture ou la modication des caractristique dun chier : cette opration permet de lire les caractristiques associes un chier. Certaines caractristiques sont disponibles dans la plupart des systmes de chiers : taille, dates de cration et/ou de modication, propritaire, droits daccs. Parfois des caractristiques spciques supplmentaires peuvent tre associes : les droits daccs, un attribut archive utilis sous Windows pour indiquer si un chier est sauvegard, le type de chier sous Mac OS. Notons que certains systmes de chiers sont conus pour permettre la cration dattributs non prvus initialement.
3. Il existe quelques exceptions. Par exemple, un systme de chiers organis selon la norme ISO 9660, conue pour les CD-ROM, ne permet pas la modication de chiers. 4. Quelques systmes de chiers sont susceptibles de ne pas rendre rutilisable la place occupe par un chier cause de limitations du priphrique de stockage (disque WORM, bande magntique).

171

Chapitre 7. Le systme de chiers


Les rpertoires

Un nommage plat des chiers serait peu pratique grer au del dune centaines de chiers, ainsi tous les systmes de chiers 5 permettent la cration de rpertoires quun utilisateur utilisera pour rassembler un ensemble de chiers. Pour rendre le classement encore plus efcace, ces rpertoires peuvent contenir leur tour dautres rpertoires, ce qui donne une structure arborecente 6 . Louverture ou la cration dun chier ncessite, en plus du nom du chier, la liste des rpertoires parcourir pour trouver le nom du chier. Cette liste, appele chemin daccs se prsente sous la forme dune unique chane de caractres dont un caractre particulier est utilis pour sparer les noms de rpertoires et le nom du chier. Notons que chaque systme dexploitation utilise des conventions diffrentes ( / sous Unix, \ sous MS-DOS et Windows, : sous les anciennes versions de Mac OS). Pour faciliter laccs des chiers situs dans un mme rpertoire, une notion de rpertoire courant est gnralement utilise et permet louverture dun chier laide dun chemin relatif au rpertoire courant (ce chemin ne contient que le nom du chier si ce dernier est directement dans le rpertoire courant). Les oprations usuelles portant sur les rpertoires sont les suivantes : la cration dun rpertoire ; la suppression dun rpertoire ; la lecture dun rpertoire, an dobtenir la liste des chiers ; cette opration ncessite gnralement plusieurs oprations lmentaires, qui peuvent distinguer une ouverture de rpertoire, la lecture (rpte) dun nom de chier, et la fermeture ; le changement de nom ou le dplacement dun chier. Notons que les oprations portant sur les chiers portent parfois implicitement sur les rpertoires du chemin daccs associ ce chier. Par exemple, la cration dun chier dans un rpertoire est une modication du rpertoire.
Le systme de chiers virtuel

Chaque systme de chiers possde des spcicits propres, ainsi un systme de chiers dun Macintosh prcise la position des icnes associes chacun de ces chiers 7 , alors quun systme de chiers dun systme Unix prcisera le propritaire de chaque chier ainsi que les droits daccs. Ces diffrences ncessitent des fonctions de manipulation de chiers adaptes. Ainsi la fonction de lecture dun chier sera diffrente selon les systmes dexploitation.
5. Nous avons encore une exception : la version 1 de MS-DOS ne connaissait pas la notion de rpertoire, cela ne pouvait tre tolr que sur des disquettes de faibles capacits. 6. Cette fois nous pouvons noter le systme de chiers MFS des premiers Macintosh, dont les rpertoires ne pouvaient contenir dautres rpertoires. 7. De nombreux chiers sont ainsi disponibles sous Mac OS X lorsque lon observe le contenu dun rpertoire : .DS_Store,. . . Ces chiers sont utiliss par le Finder pour le rendu graphique des rpertoire et des chiers.

172

7.1. Les services dun systme de chier Pour permettre des changes de chiers entre systmes dexploitation diffrents, pour permettre aussi lutilisation de priphriques de stockage diffrents, les systmes dexploitation supportent plusieurs systmes de chiers et donc par exemple plusieurs fonctions de lecture. On pourrait imaginer les fonctions mac_read(), unix_read(), msdos_read(), etc. Ce jeu de fonctions serait peu pratique pour le programmeur : ce dernier devrait adapter son programme chaque systme de chier. Un tel programme serait par ailleurs incompatible avec tous les systmes de chiers qui auraient t dvelopps aprs lcriture du programme. Pour viter cela, le systme dexploitation prsente aux programmes et lutilisateur une prsentation unie des systmes de chiers supportes. Par exemple, la lecture est ralise par une unique fonction qui sadapte selon le cas. Cette prsentation unie est souvent appele systme de chiers virtuel . Dans certains systmes dexploitation, le systme de chiers virtuel prsente lensemble des systmes de chiers sous la forme dune arborescence unique. Lopration appel montage consiste associer un rpertoire dun systme de chier, un autre systme de chier. Dans les cas des systmes Unix, un systme de chiers particulier le systme de chiers racine sert de base cette arborescence. Ci-dessous, le rsultat (simpli) de la commande mount prsente la liste des systmes de chiers considrs par le systme dexploitation avec pour chacun le nom de priphrique utilis pour le stocker, le rpertoire permettant un programme daccder ce systme de chier, le type de systme de chiers :
/dev/wd0s1a on / (ufs) mfs:362 on /tmp (mfs) /dev/wd0s1f on /usr (ufs) /dev/wd0s1e on /var (ufs) procfs on /proc (procfs, local) /dev/cd0c on /cdrom (cd9660)

Nous remarquons en deuxime ligne, un systme de chiers stock en mmoire, cela permet dacclrer la cration de chiers temporaires, mais ces derniers ne survivront pas un redmarrage du systme. Par ailleurs, lavant-dernire ligne cite un systme de chiers stock nulle part, mais prsentant des informations sur les processus comme sils taient stocks dans des chiers. Dans le cas de Windows NT, une arborescence cre en mmoire linitialisation du systme permet le montage des systmes de chiers. Le rpertoire racine dun systme de chiers est alors associ au nom du priphrique le contenant (par exemple \Device\Floppy0). Notons que cette arborescence nest pas rendue visible lutilisateur qui continuera utiliser des conventions du systme MS-DOS (A: pour la disquette, C: pour le premier disque, etc.). La prsentation unie des chiers situs sur des priphriques diffrents tant bien pratique, elle est souvent gnralise aux priphriques mmes. Ainsi, un chier et 173

Chapitre 7. Le systme de chiers une bande magntique seront utilisables avec les mmes fonctions. Pour permettre des accs aux priphriques, des noms leur sont donns : Sous MS-DOS, certains priphriques sont associs des chanes de caractres 8 (ce qui peut rendre certains chiers inaccessibles lors de lajout dun pilote de priphrique ou rendre impossible la cration dun chier homonyme) ; Les systmes de chiers standards des systmes Unix permettent la cration des rfrences aux priphriques appels chiers spciaux . Par convention, ces chiers spciaux sont placs dans le rpertoire /dev ; Sous Windows NT, les pilotes de priphriques enregistrent les priphriques quils grent dans larborescence utilise pour le montage des systmes de chiers. Nous pouvons citer par exemple \Device\Floppy0, o un mme nom est utilis pour un priphrique et le rpertoire utilis pour monter le systme de chiers quil contient.
Autres services

Selon les systmes dexploitation, des services supplmentaires sont rendus par un systmes de chiers. Les services les plus frquents et/ou utiles sont prsents ci-dessous.
Le verrouillage

Laccs simultan un mme chier par deux processus peut corrompre ce chier. Par exemple, si deux processus sont charg de chercher dans une base de donnes une chambre dhtel libre et la rserver, ces deux processus risquent de trouver la mme chambre et, sans concertation, lassocier deux clients diffrents... Pour viter ces problmes, le systme dexploitation peut verrouiller un chier pour assurer un processus quaucun autre ne lutilise en mme temps. Deux types de verrous sont gnralement disponibles : les verrous exclusifs, qui empchent tout autre processus de verrouiller le mme chier et les verrous partags qui tolrent la pose dautres verrous partags. Gnralement, les verrous exclusifs sont utiliss lors des critures, et les verrous partags, lors des lectures : une lecture simultane par plusieurs processus ne risque pas de corrompre un chier. Selon les systmes dexploitation, les verrous sont impratifs (mandatory) ou indicatifs (advisory). Les premiers empchent non seulement la pose dautres verrous, mais aussi les accs au chier alors que les seconds ne protgent que les accs entres programmes qui prennent soin de poser des verrous lorsque ncessaire. Sous Unix, par exemple, les verrous sont indicatifs, partant du principe quune application qui ne verrouillerait pas correctement un chier est de toute manire susceptible de le corrompre.
8. Cela ne concerne pas tous les priphriques... certains ne sont pas grs de manire uniformise, cest au programmeur de choisir le jeu de fonctions adapt au priphrique utilis.

174

7.1. Les services dun systme de chier Par ailleurs, la pose dun verrou peut porter sur lensemble du chier (cest plus simple, mais imaginez que vous devez attendre la rservation dun chambre voisine pour consulter les tarifs de lhtel) ou sur une partie limite.
La notication dvolution

La notication dvolution permet un processus dtre averti de lvolution dun chier ou dun rpertoire. Cela peut tre utilis par une interface graphique pour rafrachir une fentre mesure quun rpertoire est modi. Linterface de programmation inotify fournit un mcanisme permettant de surveiller les vnements qui interviennent dans le systme de chiers. On peut la fois lutiliser pour surveiller un chier particulier ou un rpertoire entier. Cette interface propose, entre autres, les appels systmes suivants : inotify_init() : cette fonction retourne un descripteur de chier quil conviendra de lire pour observer la venue dvnements ; la liste dvnements est vide ; inotify_add_watch() : cette fonction ajoute des vnements surveiller ; Les vnements qui se produisent sur le systme de chiers (et relatifs aux chiers et/ou rpertoires surveills) seront lus de manire trs classique sur le descripteur de chier renvoy par lappel de inotify_init(). Cette API peut tre particulirement utile pour raliser une supervision de donnes utilisateurs et de donnes relatives un site web.
Les quotas

Les quotas permettent un administrateur de limiter pour chaque utilisateur le volume occup par ses chiers sur un disque.
La cration de liens

La cration de liens permet lassociation de plusieurs noms un mme chier. Les systmes Unix considrent les liens hard o un chier possde plusieurs noms qui sont considrs de la mme manire par le systme et les liens symboliques qui associent un nom de chier le nom dun autre chier. Le systme Mac OS ne gre que les liens symboliques, appels alias . Sous Windows, les liens symboliques sont simuls par linterface graphique pour pallier labsence de liens symboliques grs par le systme. Appels raccourcis, ces liens ne sont que des chiers normaux interprts de faon particulire par linterface graphique.
Le mapping en mmoire

Le mapping dun chier en mmoire permet laide du gestionnaire de la mmoire dassocier une partie de lespace dadressage dun processus un chier. Les accs au chier peuvent alors tre programms comme de simples accs un tableau en mmoire, le systme dexploitation chargeant les pages depuis le chier lorsque ncessaire. La 175

Chapitre 7. Le systme de chiers vrication de la prsence dune page est ralise de faon matrielle (par la MMU) chaque accs, cela peut permettre un gain de performance pour des applications utilisant beaucoup daccs alatoires un mme chier et dont beaucoup sont susceptibles de lire les mmes pages. Cest notamment le cas lors de lexcution dun programme qui est gnralement mapp avec les bibliothques de fonctions quil utilise.

Les accs asynchrones

Les accs asynchrones permettent un programme la planication daccs un chier sans attendre le rsultat. Un appel systme permet dattendre ensuite le rsultat demand au pralable. Certains systmes permettent aussi la demande dexcution dune fonction particulire la n de cette opration. Enn notons linterface POSIX qui permet la dnition de priorits associes aux accs asynchrones.

7.2

Lorganisation des systmes de chiers

Gnralits

Un disque dur prsente au systme dexploitation un ensemble de blocs de mme taille (gnralement 512 octets), accessibles daprs leurs numros. laide de ce service limit (les disques durs ne connaissent pas la notion de chiers), le systme dexploitation rservera certains blocs pour stocker les informations ncessaires la gestion dun systme de chiers. Ces informations, appeles mta-donnes ne sont pas accessibles directement par lutilisateur ou ses programmes. Les mta-donnes fournissent gnralement les informations suivantes : Les caractristiques du systme de chiers lui-mme (nombre de blocs total, nombre de blocs disponibles, nombre de chiers, etc.). Le terme superbloc est parfois utilis pour identier le bloc stockant toutes ces informations. De telles informations, indispensables lutilisation dun systme de chiers, sont souvent dupliques dans plusieurs blocs par mesure de scurit. Les caractristiques de chaque chier (taille, listes des blocs associs au chiers, etc.). Ltat libre ou allou de chaque bloc. An de faciliter la gestion des blocs, ces derniers ne sont pas toujours grs individuellement mais par agrgats (clusters) qui deviennent lunit minimum dallocation utilise par le systme de chiers. Certains systmes dexploitations (ou plutt leur documentation) utilisent le terme blocs pour dsigner ces agrgats. Le sens du terme bloc dpend alors du contexte : parle t-on du systme de chiers ou du priphrique ? Lusage des termes blocs logiques et blocs physiques permet de lever lambigut. 176

7.2. Lorganisation des systmes de chiers


Le systme de chiers Unix (UFS)

Le systme de chiers UFS (Unix File System) 9 place les caractristiques des chiers dans un zone de taille xe appele table des i-nodes. Cette table contient pour chaque chier une structure appele i-node fournissant les caractristiques dun chier, lexception du nom. Les noms des chiers sont situs dans les rpertoires qui ne sont quune liste de noms associs leur numro di-node. Une caractristique rare des systmes Unix est de permettre lassociation dun mme i-node (et donc dun mme chier) plusieurs noms (situs ventuellement dans plusieurs rpertoires). Un compteur de rfrences permet la suppression du chier (et la libration de li-node) aprs la suppression du dernier lien avec un nom de chier. La commande ls -i permet dobtenir les numros dinode. Le rsultat de la commande ls -li montre un rpertoire contenant 3 noms : a, b et c. Ces deux derniers sont associs un mme i-node (1159904) et donc un mme chier. Le compteur de rfrence associ ce chier a la valeur 2, ce qui vite la suppression du chier lors de la suppression dun des noms :
~/WORK/Cours/essai$ls total 0 1159903 -rw-r--r-- 1 1159904 -rw-r--r-- 2 1159904 -rw-r--r-- 2 ~/WORK/Cours/essai$rm ~/WORK/Cours/essai$ls total 0 1159903 -rw-r--r-- 1 1159904 -rw-r--r-- 1 -il loyer loyer loyer c -il loyer loyer loyer loyer loyer 0 12 nov 15:18 a 0 12 nov 15:18 b 0 12 nov 15:18 c

loyer loyer

0 12 nov 15:18 a 0 12 nov 15:18 b

Plusieurs types di-nodes sont considrs par le systme Unix : Les chiers normaux sont les plus courants : ce sont ceux que vous crez habituellement. Les rpertoires servent organiser les chiers. Les liens symboliques sont des rfrences dautres chiers. Les tuyaux nomms permettent deux programmes de schanger des donnes : lun crivant dans ce tuyaux et lautre lisant ce mme tuyaux. Vous pouvez crer de tels tuyaux avec la commande mkfifo. Le chapitre 15 aborde cette notion. Les sockets permettent un programme client dchanger des donnes avec un serveur. Lusage des sockets est plus complexe que celui des tuyaux nomms, mais permet une communication bidirectionnelle et individualise avec chaque programme
9. Lintrt de ce systme de chiers est plutt dordre historique, des optimisations importantes ayant ts introduites en 1984 pour former le Fast File System. Ces optimisations seront prsentes ensuite.

177

Chapitre 7. Le systme de chiers client. Aucune commande nest disponible pour crer de tels sockets : il sont crs par chaque serveurs utilisant ces sockets. Le chapitre 16 donne plus de dtails sur lutilisation des sockets. Les chiers spciaux sont associs des priphriques et permettent des accs ces derniers comparables aux accs aux chiers. Seul ladministrateur est habilit crer de tels chiers spciaux. Un i-node associ un chier normal contient la liste des 12 premiers blocs occups par le chier quil reprsente et, si ncessaire, le numro dun bloc dindirection simple contenant les numros des blocs suivants. Pour des chiers plus gros, li-node contient le numro dun bloc dindirection double contenant une liste de numros de blocs dindirection simple. Enn, un dernier numro de blocs dsigne un bloc dindirection triple dont le contenu est une liste de blocs dindirection double (voir gure 7.1).

F IGURE 7.1 Le systme de chiers sous Unix.

Soit un systme de chiers constitus de blocs de 4096 octets, chaque numro de blocs occupant 4 octets, un bloc dindirection simple permet de dsigner jusqu 1024 blocs dun chier et donc 4 Mo. De mme, un bloc dindirection double dsigne 178

7.2. Lorganisation des systmes de chiers jusqu 1024 blocs dindirection simple et donc (indirectement) jusqu 10242 blocs appartenant au chier. Cela reprsente 4 Go. Lusage de blocs dindirections triples porte la limite des tailles de chiers 4 To. Cette limite sufsant la plupart des usages, lusage de blocs dindirection quadruple nest pas prvu (au del de 4 To, il restera possible de doubler la taille des blocs...). Ce principe utilisant des indirections dordres diffrents permet une recherche rapide des numros de blocs associs de petits chiers, sans limiter la taille des chiers supports.
Le systme de chiers MS-DOS

Le systme de chiers MS-DOS place la description des chiers (lquivalent dun i-node) directement dans un rpertoire. Par ailleurs, nous ne trouvons que le numro du premier bloc du chier. Pour chercher le bloc suivant, il faut consulter une table, la FAT (File Allocation Table), associant chaque numro de bloc dun chier, le numro du bloc suivant. Cette mthode, simple et adapte la lecture squentielle dun chier ncessite, pour laccs un bloc quelconque, un nombre doprations proportionnel la taille du chier (au lieu de 3 lectures pour un systme de chiers UFS). Cela rend ce systme de chiers inadapt aux bases de donnes dont les accs alatoires sont frquents. Le systme de chiers MS-DOS a vu plusieurs volutions, portant la taille des numros de blocs de 12 bits 16, puis 32, et permettant lenregistrement de noms de chiers de plus de 11 caractres (en pratique, pour la compatibilit ascendante, un chier est associ un nom limit 11 caractres et des entres spciales fournissent des brides du nom long).
Le systme de chiers HFS de Mac OS

Comme son nom lindique, Hierarchical File System, le systme de chiers de Mac OS ajoute au prcdent la possibilit de ranger des rpertoires lintrieur dautres rpertoires. Cependant, ce systme de chiers se distingue par lusage rcurent de structures B-tree hrites des index de base de donnes. Ces structures permettent la cration de tables associant une structure une clef daccs. Comme pour les blocs dindirection sous Unix, les temps daccs cette structure sont proportionnels au logarithme du nombre de structures enregistres, mais un arbre B-tree peut tre utilis avec nimporte quel type de clef. En comparaison, les blocs dindirection ne peuvent tre utiliss que pour des suites dentiers conscutifs (des trous peuvent tre toutefois tolrs). Par ailleurs, les mta-donnes de ce systme de chiers sont places eux-mmes dans des chiers. Cela peut simplier laccroissement de lespace allou ces mtadonnes. En comparaison, la table des i-node sous Unix est alloue la cration du systme de chiers. Cela limite demble le nombre maximum de chiers. 179

Chapitre 7. Le systme de chiers Comme sous Unix, un chier est accessible par le systme dexploitation laide de son numro. En revanche linterface de programmation rend possible de tels accs aux programmes. Cela est utilis par les applications qui souhaitent accder leurs chiers mme aprs un dplacement ou un changement de nom. Pour trouver lemplacement dun bloc dun chier sur le disque, un arbre appele extents B-tree est utilis avec le numro de chier et le numro du bloc comme clef (ajoutons aussi un entier qui indique quelle moiti de chier (fork) est considre). Contrairement au systme de chiers Unix, les blocs ne sont pas points individuellement, mais par suites de blocs conscutifs appeles extents. Chaque extent peut tre caracteriss par le numro du premier bloc et le nombre de blocs. Lorsque quun chier peut tre plac dans des blocs conscutifs, lusage dextents permet un gain de place et limite le nombre de lectures du chier extents B-tree. En ce qui concerne les rpertoires, ces derniers ne sont pas stocks sous forme de chiers associes chacun un rpertoire. Au contraire, un unique chier, catalog B-tree, associe une paire (numro de rpertoire, nom du chier ou du rpertoire) une structure dcrivant le chier ou le rpertoire. La description dun chier fournit diverses informations telles que le type, lapplication qui la cr, les informations ncessaires au trac de sa reprsentation graphique (icne, position), taille, etc. De plus, cette structure inclut la position des premiers extents an dviter la lecture de du chier extents B-tree pour des petits chiers. Par ailleurs, un chier spcial appel Master Directory Block situ une place xe fournit les informations importantes du systme de chier. En particulier, la position des premiers blocs de lextents B-tree... les autres blocs sont situs laide de ce mme chier ! Des prcautions lors de laccroissement de ce chiers sont ncessaires pour viter que la recherche dun extent ne dpende directement ou non du rsultat de cette mme recherche.

Le nouveau systme : ZFS

Ce systme de chiers est apparu en 2005 lors de son intgration au systme dexploitation Solaris de Sun 10 Il sagit dun systme de chiers 128 bits, ce qui signie que chaque pointeur de bloc est cod sur 128 bits ce qui permet datteindre des tailles de chiers trs importantes (16 exbioctets sachant que lexbioctet vaut 260 octets soit pas moins dun million de traoctets !). ZFS intgre les notions de gestionnaire de volumes (voir la section suivante) et construit un ensemble de stockage 11 de manire hirarchique en intgrant des volumes physiques dans un premier temps, puis les volumes virtuels. Chaque feuille de larbre dcrivant ces volumes virtuels contient des informations relatives tous les parents dont elle descend comme le montre la gure 7.2
10. Sun a depuis t rachet par Oracle. 11. ZFS pool ou encore zpool.

180

7.2. Lorganisation des systmes de chiers

F IGURE 7.2 Les priphriques physiques, appels feuilles, sont regroups au sein de volumes virtuels, lesquels peuvent tre aussi regroups. Le VDEV1 est un volume virtuel dans lequel toute criture est ralise en miroir (RAID1) sur les disques physiques A et B. Il en est de mme pour le volume virtuel VDEV2. Le volume racine, not symboliquement root dev, est une agrgation des deux volumes virtuels (RAID0). Le volume rel ou physique C contient dans sa description des informations sur le disque D mais aussi sur le volume VDEV2. En lisant ces informations, le systme dexploitation peut ainsi connatre lorganisation des diffrents lments, mme si lun dentre eux fait dfaut.

Ayant disposition des volumes de stockage, la cration dun systme de chiers lintrieur est rendu, avec ZFS, particulirement simple et ressemble, sy mprendre, la cration dun rpertoire. Chaque systme de chiers cr est automatiquement mont ( moins quune option linterdise et quil faille recourir dans ce cas la traditionnelle commande mount). Chaque utilisateur dun systme dinformation pourrait ainsi avoir sa propre partition . ZFS permet la ralisation de snapshot, cest--dire dune photographie du systme de chiers place en lecture seule. Cette prise de vue ne doit pas tre confondue avec une simple copie lidentique puisque linstant de sa ralisation, une snapshot ne consomme pratiquement aucune place, tout est affaire dindirection dans les blocs de donnes. Lorsquun bloc de donnes change (lutilisateur modie un chier par exemple), le bloc original sera conserv pour la snapshot tandis quun nouveau bloc sera mis disposition de lcriture accueillant les changements. Ce mcanisme de Copy On Write , dj remarqu pour la gestion de la mmoire virtuelle, est particulirement 181

Chapitre 7. Le systme de chiers efcace et peu gourmand en occupation du disque. ZFS introduit aussi la d-duplication des blocs de donnes. Deux chiers diffrents peuvent trs bien possder des zones de donnes parfaitement identiques. La dduplication permet de faire pointer des blocs issus de deux inodes diffrentes vers la mme zone du disque (tant que ces blocs sont communs naturellement). Enn, un mcanisme de compression lcriture (utilisant une librairie trs rpandue, la libz) permet un gain de place assez consquent. Sur un systme hbergeant un logiciel de gestion des messages lectroniques, chaque utilisateur peut avoir une partition , cest--dire un systme de chiers pour lui. Les messages comportent trs souvent du texte et leur compression peut entraner un gain de lordre de 80 %. Associ au mcanisme de snapshot prcdemment cit, la restauration des messages, effacs par mgarde, devient trs rapide. On assiste hlas en ce moment une vritable guerre entre NetApp et Oracle, le premier soutenant que les ides et les principes sur lesquels reposent ZFS ayant fait lobjet de brevet, le second soutenant quil sagit dune cration originale. Lintgration de ZFS dans BSD est dj ralise, mais sa mise disposition dans le noyau Linux nest pas encore dactualit. Notons enn que, annonce dans Mac OS 10.6, ZFS nest pas encore disponible nativement pour les utilisateurs de la Pomme.

7.3

La gestion des volumes

Pour des raisons pratiques, il peut-tre utile de placer sur un mme disque, plusieurs systmes de chiers indpendant. Cest en particulier le cas pour faciliter les sauvegardes ou lors de lusage de plusieurs systmes dexploitation. Pour cela, des structures doivent tre utilises pour mmoriser les espaces allous aux diffrents systmes de chiers.
Les tables de partition

La structure la plus simple pour dcrire un espace allou sur le disque est la table des partitions 12 . Une telle table associe une partition identie par un numro un ensemble de blocs 13 conscutifs situs laide du numro du premier bloc et du nombre de blocs. Sur les architectures de type Intel on trouve sur le premier secteur du disque une table de partitions principale qui en plus des informations sur les diffrents secteurs allous aux partitions contient galement le programme damorage (appele pour cette architecture MBR ou Master Boot Record ). Cette table de partitions ne pouvant contenir que 4 partitions, lorsque le besoin sen fait sentir, la dernire partition est une
12. Sous MS-DOS, dune conception initiale limite, combine la compatibilit ascendante maintenue lors de chacune des volutions, rsultent des structures assez compliques. 13. Ici, il sagit des blocs dun priphrique, gnralement 512 octets pour un disque dur.

182

7.3. La gestion des volumes partition tendue ( Extended Boot Record ) et ne sera reconnue que par le systme dexploitation. Cest le BIOS qui a la charge de lire et dinterprter cette table principale.

F IGURE 7.3 La squence dallumage dun ordinateur utilisant lUEFI. Dans un premier temps lUEFI se charge de linitialisation des diffrents priphriques prsents sur la carte mre. Puis il charge les diffrentes applications qui permettent de congurer les composants, ce la demande de lutilisateur. Il dmarre enn son chargeur de dmarrage qui va aller rechercher le chargeur de dmarrage du systme dexploitation.

Les limitations imposes par le BIOS et surtout sa dpendance aux extensions propritaires ont ouvert le chemin lUEFI ( Unied Extensible Firmware Interface ) mais aussi Open Firmware. Cette interface entre le systme dexploitation et le matriel permet lutilisation dun nouveau modle de table de partitions, le format GPT mis pour Globally Unique IDentier Partition Table . Ce format permet daccder un volume de disque plus important que celui gr par MBR. Le format GPT permet de plus dassocier un numro unique chaque partition. Peu importe alors lordre dans lequel les priphriques sont vus par le systme dexploitation, une partition sera toujours affecte son point de montage correct grce cet identiant unique. Vous avez peut-tre dj remarqu cela lors des squences de boot dun ordinateur utilisant Linux (et ventuellement GPT) ainsi que dans le chier dcrivant les point de montage des partitions :
UUID=2532b597-dfa5-4766-8391-405b7126f8e8 UUID=8a0ada47-375f-4ab7-b01e-b86b1e00a280 UUID=218fe85e-94bb-4e97-8386-0eaa1fbdb50b UUID=5af07829-57de-4dad-9853-b24a7ac0b6d0 UUID=4dd9b92a-1842-432f-92df-c3760c3cb998 UUID=61d0ff68-8438-4418-96a1-2fba91314abb / /home /cours /opt /part none ext3 ext3 ext3 ext3 ext3 swap 0 0 0 0 0 0 1 1 1 1 1 0

Lutilisation de GUID pour marquer les partitions nest pas li une table de partitions GPT, mais par contre GPT utilise GUID pour identier les partitions et les disques. Quelques remarques sur lUEFI. Il sagit dune interface entre le matriel (les rmwares ) et le systme dexploitation. Une architecture conue pour exploiter lUEFI permet un dialogue beaucoup plus agrable entre lutilisateur et les composants matriels. En plus dtre utilis au dmarrage, lUEFI est aussi une interface de communication entre le systme dexploitation et le matriel pour certains aspects. 183

Chapitre 7. Le systme de chiers Enn lUEFI, comme on peut le remarquer dans la gure 7.3, permet de se dpartir des chargeurs de dmarrage des systmes dexploitations (tel que GRUB ou Lilo). LUEFI a la capacit didentier une partition EFI lintrieur de laquelle il pourra trouver un chargeur de dmarrage de second niveau (par exemple elilo.efi) qui permettra de charger un systme dexploitation avec des options passes en ligne de commandes. Une peur secoue actuellement la communaut libre. Depuis une version rcente (2.3.1), lUEFI permet de protger les systmes dexploitation amorables en vriant leur signature. La signature dun programme est obtenue en chiffrant laide dune cl prive (vendue 100$ par Microsoft) la somme MD5 (ou son quivalent). LUEFI compare alors cette signature en la dchiffrant laide la cl publique la signature obtenue en calculant la somme MD5 du programme. Si la fcontionnalit de Secure Boot nest pas dsactivable, un systme dexploitation non sign ne peut pas tre charg. La mise en place de cette infrastructure semble scuriser un ordinateur, mais elle peut aussi servir contrler ce que chaque particulier a le droit dutiliser sur son ordinateur. . . Si, dans le cas des architectures classiques, cette gestion de lespace disque suft pour des besoins limits o des systmes de chiers sont allous une fois pour toutes, des mcanismes plus complexes appels gestionnaires de volumes sont utiliss dans les plus gros systmes (cest le cas avec ZFS et les zpools). Ces gestionnaires sont comparables des systmes de chiers simplis. Cependant lallocation de volumes tant plus rare et portant sur des ensembles importants de blocs, les mta-donnes sont relativement peu nombreuses et sont charges en mmoire au dmarrage.

Les volumes

Un gestionnaire de volumes est gnralement capable de crer un volume laide de plusieurs ensembles de blocs conscutifs, ventuellement situs sur des disques diffrents. Par ailleurs certains gestionnaire sont associs une organisation redondante des donnes permettant de garantir laccs un volume aprs une panne dun disque dur. Ces organisations sont appele en miroir ou RAID 1 lorsque les donnes sont dupliques sur des disques diffrents. Dautres mcanismes appeles RAID 2 RAID 5 ajoutent aux donnes situes dans plusieurs disques leur bits de parit sur un disque supplmentaire. En cas de panne dun disque, chaque bit de ce disque pourra tre calcul partir des autres disques. Dans certains cas, le gestionnaire de volumes est coupl aux systmes de chiers, ce qui permet laccroissement dun systme de chier. Ce couplage est ncessaire puisquun tel accroissement doit modier les mta-donnes du systme du chiers an de dclarer les blocs ajouts. Une autre fonction disponible avec certains gestionnaire de volumes permet une copie logique instantane dun volume, ce qui peut tre utile en particulier pour des sauvegardes de chiers ouverts. Bien que les partitions ou volumes sont souvent utiliss pour y placer des systmes de chiers, dautres usages sont aussi frquents pour dautres structures de tailles importantes et alloues rarement (gnralement une fois, linstallation). Cette allocation, 184

7.4. Amliorations des systmes de chiers moins exible quavec un systme de chiers permet de meilleures performances 14 . Les usages les plus courants sont : lespace utilis par la gestion de la mmoire pour la pagination ; les bases de donnes.

7.4

Amliorations des systmes de chiers

Le cache

An dacclrer les accs aux disques, un mcanisme de cache est gnralement mis en place. Ce cache permet, en maintenant en mmoire les blocs dun chier lus, dviter lors de leurs relectures dautres accs au disque dur. Par ailleurs, les critures peuvent tre diffres an de regrouper les critures sur des blocs voisins et limiter le nombre daccs au disque dur en cas dcritures successives sur un mme bloc. La gestion du cache peut avoir des consquences importantes sur les performance dun systme. Une optimisation, read ahead consiste anticiper la lecture de blocs conscutifs lors de la lecture dun chier. Ces blocs seront fournis immdiatement aux programmes lorsque ncessaire. Cette optimisation prote des caractristiques des disques durs dont les dbits sont importants lors de la lecture de blocs conscutifs, mais dont le temps daccs (temps de positionnement dune tte de lecture sur le premier bloc) reste relativement grand 15 . Si le cache acclre les accs en lecture et criture, il peut avoir des consquences sur lintgrit des donnes. En effet, lappel systme write(), au lieu de modier un chier ne fait que planier cette modication. Pour des applications o la scurit des donnes est importante, un appel systme (fsync() sous Unix) permet la demande dcriture effective des blocs modis au pralable. Cela est en particulier utilis par le serveur de messagerie sendmail pour enregistrer rellement un courrier lectronique avant dindiquer sa prise en compte au serveur qui lenvoie. Un aspect important de la gestion du cache est ladaptation de la quantit de mmoire occupe par le cache. Cette dernire doit tre ajuste en fonction de la mmoire disponible : dans lidal, le cache utilise toute la mmoire disponible. . . et se rduit lorsque des applications demandent de la mmoire. Sur les systmes dexploitation moderne, la gestion du cache est intimement lie la gestion de la mmoire.
14. Dans certains systmes temps rels, le systme de chiers permet la cration de chiers contigus, ce qui permet la exibilit et des performances garanties. Cependant, lappel systme utilis pour cette cration peut chouer lorsque lopration nest pas possible. 15. Cela est malheureusement de plus en plus vrai : les dbits augmentent proportionnellement la densit des donnes et la vitesse de rotation alors que les temps daccs (environ 10 ms) samliorent trs lentement.

185

Chapitre 7. Le systme de chiers


La fragmentation

Des accroissements successifs dun chier sont susceptibles dallouer des blocs disperss sur la surface dun disque dur. Cette dispersion appele fragmentation (le chier est dcoup en fragments, sries de blocs conscutifs), rend ncessaire, pour la lecture du chier, des mouvements des ttes de lecture, ce qui ralentit les accs. Par opposition, une allocation de blocs conscutifs, permet la lecture dun chier de taille importante laide dun nombre limit de dplacements des ttes de lecture. Notons que les progrs les plus importants relatifs aux disques durs portent sur la densit des donnes et sur le dbit des lectures et critures de blocs conscutifs. En revanche les temps daccs moyens (principalement ds au dplacement de la tte de lecture et la vitesse de rotation) progressent trs lentement, ce qui rend la baisse relative de performance due la fragmentation de plus en plus importante. Lune des techniques les plus simples pour limiter la fragmentation consiste augmenter la taille des blocs. Cependant, cette technique rend inutilisable lespace situ entre la n dun chier et la limite du dernier bloc occup par ce chier. Cette perte despace est dautant plus importante que la taille moyenne de chiers est petite. Le systme de chiers FFS (Fast File System) trs utilis sous Unix sinspire de cette technique sans en avoir les inconvnients. Les blocs (typiquement 8 Ko ou 16 Ko) sont diviss en fragments (par exemple 1 Ko). Des chiers diffrents peuvent se voir allouer les fragments dun mme bloc, ce qui permet une conomie de place comparable un systme de chiers utilisant des petits blocs. Par ailleurs lors de laccroissement dun chier, les fragments sont allous dans un mme bloc jusquau remplissage du bloc. Ces fragments peuvent mme tre dplacs pour permettre de continuer lallocation lintrieur dun mme bloc. Ce principe dallocation deux niveaux permet de cumuler les avantages des petits blocs (perte moindre despace disque) et de gros blocs (fragmentation limite des chiers). videmment, lallocation dun nouveau bloc favorise les blocs les plus proches du bloc prcdent. Le systme de chiers ext3 privilgi sous Linux utilise un mcanisme plus simple : des petits blocs sont utiliss, mais laccroissement dun chier est toujours ralis en allouant plus de blocs que ncessaires (par dfaut 8)... lorsque de tels blocs sont conscutifs. Cela favorise lallocation de blocs conscutifs. An dviter un gaspillage despace, les blocs inutiliss sont librs la fermeture du chier. Une autre technique utilise (notamment dans le systme de chiers FFS) pour limiter limpact de la fragmentation consiste diviser un systme de chiers en groupes de cylindres (appels groupes de blocs sous Linux). chaque rpertoire et ses chiers est associe une partie du disque dans laquelle auront lieu prfrentiellement toutes les allocations. Le choix dun nouvel espace disque privilgie un usage quilibr de chaque groupe de cylindres. Ainsi, lorsquun chier doit tre agrandi, cette rpartition assure lexistence dun bloc libre non loin du dernier bloc de ce chier. De plus, une application qui manipule des chiers situs dans un mme rpertoire (cela est frquent) provoquera des dplacements de la tte de lecture de petites amplitudes, puisqu lintrieur dun mme groupe de cylindre. 186

7.4. Amliorations des systmes de chiers En revanche, les systmes Microsoft sont souvent dots de logiciels de dfragmentation. Le principe consiste rassembler aprs coup les fragments de chiers an daugmenter les performances. De plus, les chiers sont souvent rassembls au dbut du disque, ce qui permet dobtenir un espace libre non fragment important. En revanche, avec un systme de chiers ainsi tass , laccroissement dun chier risque dallouer un bloc assez loin du dbut de chier. . . dfaut quil faudra corriger nouveau. Si ce mcanisme est simple, il ncessite un systme assez disponible, puisque la dfragmentation implique beaucoup daccs au disque. Cela est gnralement le cas pour des serveurs peu sollicits en dehors des heures de travail. Par ailleurs, pour des systmes ne contenant que des chiers en lecture seule, ce mcanisme est le plus efcace.
La protection contre la corruption

Un arrt brutal du systme (plantage, panne, dfaut dalimentation) risque dentraner la corruption du systme de chiers, le disque dur ntant pas capable dassurer lcriture de plusieurs blocs simultanment. Par exemple, laccroissement dun chier comporte plusieurs oprations : lallocation dun bloc ; lallocation ventuelle de blocs dindirection ; lajout de ce bloc au chier (modication de numros de blocs) ; lcriture dans le chier ; le changement de li-node an denregistrer le changement de taille ; le changement du superbloc an de renseigner le nombre de blocs disponibles. Dans le cas dun arrt brutal du systme entre deux critures, les structures listes ci-dessus ne seront pas cohrentes, ce qui sappelle une corruption du systme de chiers. Cela peut provoquer des pertes despace disque (des blocs sont allous, mais nont pas t affects un chier), ou pire encore un comportement incohrent du systme de chiers (cest le cas si un bloc est affect un chier mais marqu comme disponible : il risque dtre affect un autre chier). Pour limiter les dgts, le systme de chiers FFS utilise des squences dcritures telles quun arrt brutal laisse le systme de chiers dans un tat sain : les seules consquences dune corruption sont la perte despace disque. An dviter un changement de cet ordonnancement par le cache, une criture effective est attendue avant toute criture suivante... limpact sur les performances est tel que le choix est laiss ladministrateur qui peut monter le systme de chiers en mode synchrone si lon souhaite privilgier la scurit (option par dfaut sur les systmes BSD) ou asynchrone si lon souhaite privilgier les performances (option par dfaut sur les systmes Linux). Notons quune volution rcente des systmes BSD, soft updates, modie le cache et lempche de changer un ordonnancement calcul pour scuriser le systme de chier... tout en permettant un fonctionnement asynchrone. Malgr un ordonnancement adquat des critures, la rcupration dun systme de chiers dans un tat normal ncessite une rparation, opration susceptible dtre 187

Chapitre 7. Le systme de chiers dautant plus longue que le systme de chiers est grand : cette rparation consiste entre autres faire linventaire des blocs affects un chier et le comparer la liste des blocs marqus comme non disponibles. En cas de divergence, une correction vidente simpose. Dautres dfauts sont aussi dtects tels que laffectation dun mme bloc deux chiers. An dviter de trop longues rparations, certains systmes de chiers (NTFS de Microsoft, JFS dIBM, XFS de Silicon Graphics, VxFS de Veritas, ReiserFS et Ext3 sous Linux 16 ) utilisent un mcanisme hrit des bases de donnes : la journalisation. Ce principe consiste dcrire dans un journal toute opration entreprise avant lexcution proprement dite. Cette opration est ensuite ralise. En cas darrt brutal, le journal est lu, et les oprations dcrites sont rejoues. Bien entendu, la journalisation ncessite des accs supplmentaires qui impactent les performances des systmes de chiers dont les mta-donnes sont trs sollicites. Il convient toutefois de faire attention, les systmes de chiers journaliss protgent contre des corruptions portant sur les mta-donnes du systme de chier, et non le contenu des chiers mmes. Une application qui doit se prmunir contre une corruption de ses chiers en cas darrt brutal devra prendre ses dispositions pour scuriser ses donnes, ce qui ncessite souvent un mcanisme de journalisation indpendant. Cest en particulier le cas des bases de donnes. Signalons le cas du systme de chiers LFS introduit dans 4.4BSD. Ce systme de chiers est conu comme un journal o les critures ont lieu dans des blocs successifs. Le pointeur dcriture revient au dbut une fois arriv la n du disque. Au dmarrage, il suft de se positionner sur le dernier ensemble dcritures formant un ensemble cohrent pour obtenir un tat normal. La modication dun bloc de donnes ncessite donc au pralable son dplacement la position dcriture courante, ce dplacement ncessite la modication dun bloc dindirection ou dune i-node, oprations provoquant leur tour dautres dplacements. Leffet de ces dplacements en chane est compens par la localisation des critures. An que la position courante pointe toujours sur des blocs libres, un dmon tourne en tche de fond et dplace les blocs situs devant la position courante la position courante. Notons que ce systme de chiers encore exprimental risque de fragmenter les chiers lors daccs alatoires en criture (ce qui peut ralentir des accs en lecture sur des bases de donnes), mais au contraire dfragmenter des chiers lors daccs squentiels en criture. Loptimisation des lectures ntait pas le but recherch, ces dernires doivent tre limites par le cache. Malgr toutes ces prcautions, il existe une erreur contre laquelle la plupart des systmes de chiers, en dehors de ZFS, ne savent pas se prmunir : la faute silencieuse dcriture. En temps normal, lutilisation de lappel systme write() et de fsync() tablit un dialogue entre le systme dexploitation et le contrleur du disque dur. Ce contrleur, lissue de lcriture effective sur le disque, rpond le systme dexploitation pour signier si lcriture a pu tre faite ou non. Il existe toutefois un cas
16. Notons que les systmes de chiers JFS et XFS sont aussi ports sous Linux.

188

7.4. Amliorations des systmes de chiers de gure impossible dtecter par des moyens matriels, le cas dune criture errone au bon endroit. Le matriel rpond en effet quil a russi crire, mais le bloc qui vient dtre crit ne correspond pas ce qui aurait d tre crit. ZFS se prmunit contre ces fautes silencieuses en associant chaque bloc un contrle derreur (une sorte de signature). Cela rajoute des critures lors de chaque accs au priphrique, mais cela garantit lintgrit des donnes.

189

8
Les architectures multi-processeurs et les threads

Nous allons volontairement mettre en parallle dans ce chapitre les notions darchitectures multi-processeurs et celles de threads (que lon traduit souvent par ls dactivit ). Si lutilisation de plusieurs processeurs a exist avant lutilisation des threads, ce concept et cette architecture matrielle vont maintenant de pair. Mais nous allons voir que la notion de thread nimplique pas celle de multi-curs ou multi-processeurs, et que la possession dune architecture multi-curs ne signie pas obligatoirement lutilisation de programmes conus autour des threads.

8.1

De la loi de Moore au multi-curs

Gagner en puissance de calcul

Le phnomne Andy giveth, and Bill taketh away est une faon de dire que peu importe la rapidit et la puissance des processeurs, de nouvelles applications arriveront toujours demander et consommer davantage de puissance de calcul. Sil y a 20 ans, la possibilit de disposer dune puissance formidable de deux millions doprations par seconde tait une chance pour un doctorant, aujourdhui, le moindre ordinateur de bureau, et le systme dexploitation qui va avec seraient incapables de fonctionner 191

Chapitre 8. Les architectures multi-processeurs et les threads correctement avec aussi peu doprations disponibles. Dans cette course la puissance, il est difcile de savoir si ce nest pas larrive de nouveaux processeurs plus rapides qui cre le besoin applicatif plutt que linverse. Au cours des trente dernires annes, les concepteurs de CPU ont ralis des gains en performance en agissant sur trois domaines : la vitesse dhorloge ; loptimisation de lexcution ; le cache. Laugmentation de la vitesse dhorloge permet simplement de faire les choses plus rapidement. Cependant on se heurte assez vite une barrire physique, celle de la propagation des ondes lectromagntiques lintrieur des circuits lectroniques. Augmenter lhorloge cest devoir imprativement diminuer la taille du circuit, donc probablement limiter le nombre de transistors prsents et donc diminuer la puissance ! Certes, les technologies de gravures actuelles nont pas forcment atteint leur maximum et les processeurs dhier, gravs 95 nanomtres, font gure de dinosaures face aux gravures 35 nanomtres.

F IGURE 8.1 Si le nombre de transistors continue daugmenter, on remarque que la frquence dhorloge et la puissance consomme stagnent. La performance ramene la vitesse de lhorloge stagne elle aussi.

Augmenter la taille du cache prsent ct du processeur permet de rester loign de la mmoire qui continue tre beaucoup moins rapide que les processeurs. Cela augmente naturellement les performances condition toutefois de savoir grer la fois la cohrence de cache (nous en reparlons plus loin) et le fait de placer les bonnes donnes dans le cache. Loptimisation de lexcution est une chose beaucoup plus intressante et qui passionne nombre de chercheurs. Il sagit en effet de raliser lexcution de plus de choses pendant le mme nombre de cycles. Cela implique de travailler sur le pipelining, 192

8.1. De la loi de Moore au multi-curs le rordonnancement des instructions et la possibilit dexcuter plusieurs instructions de manire parallle au cours du mme cycle dhorloge. Si lutilisation dun seul micro-processeur ne suft pas, pourquoi ne pas en utiliser plusieurs ? Pourquoi ne pas placer plusieurs processeurs au sein du mme circuit ? Il existe une diffrence fondamentale entre lutilisation de plusieurs processeurs sur la mme carte mre et lutilisation de plusieurs curs au sein dun mme circuit. Nous allons aborder ces deux architectures en examinant les modications quelles imposent au systme dexploitation et notamment aux concepts que nous avons dj voqus, savoir la gestion des processus et la gestion de la mmoire. Mais commenons tout dabord par bien apprendre diffrencier les deux types darchitectures.
Cluster versus SMP versus multi-curs

F IGURE 8.2 Organisation du cache et de la MMU dans une architecture Symmetric Multi-Processing (ou SMP) ( gauche) et dans une architecture double cur ( droite).

Un processeur est avant tout une unit dexcution dinstructions, et malgr les progrs voqus plus haut dans lorganisation du traitement des suites dinstructions (rordonnancement ou pipelining), une unit dexcution (un CPU en fait) est avant tout sriel. La premire rponse quil tait possible dapporter la demande croissante de puissance de calcul fut lemploi de plusieurs machines interconnectes par des liens rseaux haut dbit ainsi quun systme dexploitation adquat permettant de rpartir les processus sur les diffrentes machines : le paralllisme faiblement coupl (looselycoupled multiprocessing). Lexemple typique de ce paralllisme faiblement coupl est le cluster Beowulf. Il sagit dune architecture multi-ordinateurs comportant un nud central ou nud serveur et plusieurs nuds clients qui se trouvent connects les uns aux autres par un lien rseau (Ethernet, Fiber Channel. . . ). Le serveur et les clients utilisent un systme dexploitation Linux ainsi quune bibliothque de contrle et de communication telle que PVM (Parallel Virtual Machine) ou MPI (Message Passing Interface). Le serveur central se comporte vraiment comme une machine unique ayant disposition des units de calcul (les ordinateurs ou nuds clients). Nous pouvons 193

Chapitre 8. Les architectures multi-processeurs et les threads aussi citer Amoeba, un systme base de micro-noyaux qui permet de transformer un ensemble de stations (ou de serveurs) en un systme distribu. La deuxime rponse apporte fut lutilisation de plusieurs processeurs ou Symmetric Multi-Processing. Mais comme le montre la gure 8.2, il ne sagissait pas pour les constructeurs de re-dvelopper un processeur, mais bel et bien de faire coexister sur une mme carte mre plusieurs processeurs identiques. Cela signiait que chaque processeur possdait son cache et son gestionnaire de mmoire virtuelle. Nous avions presque deux ordinateurs disposition. Tant mieux ! En effet un processus pouvant accder lun des deux CPU devait attendre que le systme dexploitation charge le CR3 (voir, ou revoir 6.1) pour accder aux catalogues et aux pages mmoires le concernant et donc ralise toutes les oprations de changement de contexte. Deux processus diffrents pouvaient donc sexcuter en mme temps ce qui devait, thoriquement, augmenter la puissance par deux. . . Mais accder la mmoire physique de manire parallle au travers de deux MMU peut avoir des consquences dramatiques si cela nest pas correctement gr. Nous savons en effet que les donnes sont avant tout transfres de la mmoire centrale vers les diffrents caches mmoires. Dans une architecture multiprocesseurs, chaque processeur possde des caches qui lui sont propres. Il est impratif de grer une cohrence entre les caches des diffrents processeurs an davoir une vision cohrente de la mmoire. Dans une architecture SMP, cette cohrence de cache passe obligatoirement par la circuiterie de la carte mre dont les bus de communication ne permettent pas dobtenir la mme rapidit que les bus internes du processeur 1 . Le gain en performance sen trouve grev. Dans une architecture multi-curs, nous allons retrouver sur le mme substrat deux (ou plus) units de calcul. La gure 8.3 prsente une vue du circuit imprim de lIntel Conroe. Ces units de calcul vont pouvoir dialoguer entre elles sans sortir de la puce. On gagne donc en performance pour maintenir la cohrence de cache 2 , on gagne aussi en homognit puisque la gravure des deux processeurs est effectue en mme temps donc dans les mmes conditions, et enn on gagne en circuiterie sur la carte mre charge daccueillir une seule puce. Nous dtaillerons par la suite une solution utilisant le multi-curs mais rendant les accs la mmoire asymtriques.
Le rle du systme dexploitation

Jusqu prsent notre systme dexploitation maintenait une vision de la mmoire par processus sexcutant sur le CPU (au travers des catalogue de pages, du TLB
1. Nous avons omis de parler de certains types de rseaux dinterconnexion tels que les rseaux Crossbar. Cette architecture permet la connexion de diffrents processeurs diffrentes sources de mmoire de manire trs rapide et surtout uniforme, i.e., tous les processeurs sont gale distance de toutes les sources de mmoire. Ces rseaux dinterconnexion peuvent accueillir de nombreux processeurs mais sont bien plus onreux que les bus classiques. 2. Enn presque ! Souvenons-nous de la remarque sur les processeurs double cur Intel dans lesquels le cache mmoire de niveau deux se situait lextrieur de la puce. . .

194

8.1. De la loi de Moore au multi-curs

F IGURE 8.3 Vue densemble du substrat de larchitecture multi-curs dun processeur Intel Conroe (source wikimedia). On distingue clairement en haut gauche et droite les deux CPU, le bas du circuit tant occup par la mmoire cache et les bus de communication.

et grce la MMU). Lorsque plusieurs processeurs sont disponibles, et donc que plusieurs processus peuvent sexcuter en mme temps, le systme dexploitation doit tre modi pour grer cette situation nouvelle : il faut maintenir une cohrence de cache comme cela a dj t mentionn ; il semble impratif de synchroniser les accs la mmoire ; il faut grer les accs concurrents la mmoire. Nous aurons loccasion de revenir dans la partie traitant des threads sur la cohrence de cache et les accs concurrents. Examinons tout dabord la synchronisation des accs la mmoire physique. Lapproche traditionnelle est celle du Symmetric MultiProcessing dans laquelle chaque processeur possde son gestionnaire de mmoire, lattribution des pages mmoires dans la mmoire physique tant globalement, et pour chaque MMU, gre par le systme dexploitation. On voit donc quoutre la cohrence de cache (ceux de niveau 2 ou 3 lintrieur dun substrat ou dune puce) il faut soccuper de la cohrence des diffrentes tables de pages mmoire. La gure 8.4 donne une ide schmatique de lorganisation de la gestion de la mmoire. Le systme dexploitation rside dans une zone mmoire et soccupe dorganiser le dcoupage de la mmoire pour lattribuer aux diffrents processus. Hormis le fait de contrler et 195

Chapitre 8. Les architectures multi-processeurs et les threads de mettre jour plusieurs catalogues de pages et TLB lorsque quinterviennent des changements dans lorganisation de la mmoire physique, les mcanismes daccs la mmoire sont sensiblement identiques quelques dtails (trs importants !) prs. An de faire dialoguer les processeurs entre eux, le systme dexploitation dispose dun mcanisme dinterruptions : les IPI ou Inter Processor Interrupts . Lorsquune entre dun catalogue de pages change, chaque processeur doit imprativement rafrachir son TLB ou marquer cette entre comme invalide dans le catalogue des pages. Le processeur ayant provoqu ce changement sait comment faire cela de manire automatique, ce qui nest pas le cas des autres processeurs. Ainsi, le processeur ayant provoqu le changement doit envoyer une IPI aux autres processeurs an que ceux-ci ralisent les changements opportuns dans les catalogues de pages. Remarquons au passage quil faudra probablement interrompre le traitement des interruptions lorsque lon traite une telle interruption.

F IGURE 8.4 Laccs la mmoire dans une architecture SMP.

Pourtant le principal problme nest pas li au systme dexploitation lui-mme mais bel et bien laccs physique la mmoire au travers du bus mmoire et de son contrleur. Il est en effet difcile de maintenir un accs correct la mmoire en augmentant la fois la frquence dhorloge des processeurs ainsi que leur nombre ; la quantit de donnes devant circuler sur le bus de donnes devient alors trs grande. Rapidement la limite haute du nombre envisageable de processeurs pour un accs correct la mmoire fut atteinte : 8 processeurs saturent le NorthBridge. Si une rponse possible est lutilisation dun rseau dinterconnexion de type Crossbar, son cot reste prohibitif pour un faible nombre de processeurs et pour un usage de type ordinateur personnel. Les fabricants de processeurs proposrent alors de dsymtriser laccs la mmoire, ce qui tait en fait la mise en application, cot hardware, dune ide dj connue dans le paralllisme faiblement coupl. La mmoire fut donc dcoupe en banques, chacune de ces banques tant dvolue un processeur. Ce type daccs asymtrique est appel NUMA pour Non Uniform Memory Access en opposition larchitecture 196

8.1. De la loi de Moore au multi-curs prcdente qualie de UMA. An de faire adopter cette technologie, il est impratif de la rendre aussi transparente que possible aux yeux dun systme dexploitation. Un adressage global de la mmoire doit donc tre possible, quand le systme dexploitation nest pas conu pour utiliser une architecture NUMA ce qui est ralis par exemple dans la technologie HTT ( HyperTransport Technology ) dAMD. Chaque processeur reconnat la banque de mmoire qui lui est associe mais il la dclare aussi dans ladressage global. Qui plus est la cohrence de cache est maintenue automatiquement par les processeurs eux-mmes. Pour tirer des bnces dune architecture NUMA il est impratif de modier le systme dexploitation an que les processus soient excuts sur un couple processeur / banque mmoire. Loptimum de performance est atteint lorsque toute la mmoire alloue par un processus sexcutant sur un processeur est contenue dans la banque de mmoire de ce processeur, et encore mieux, si les threads issus de ce processus sont eux aussi excuts sur le mme processeur. An de raliser ceci, lordonnanceur de tches doit tre chang.
Un ordonnanceur multi-queues

Dans lapproche traditionnelle que nous avons vue dans le chapitre 5 page 121, le systme dexploitation maintient une table de processus, avec diffrentes les de priorit certes, mais ceci est mal adapt la gestion du paralllisme. Cette approche est connue sous Linux sous lacronyme DSS ou Default SMP Scheduler . Lorsque quun processeur doit slectionner un nouveau processus (il vient de rencontrer lexcution de sleep() ou de wait_for_IO() ou il a reu une interruption dun autre processeur), le systme dexploitation doit accder la queue de processus et pour ce faire, puisquil est lui-mme susceptible de sexcuter en parallle, il est impratif quil place un verrou (ces notions seront examines plus en dtail dans le cours sur les threads) an de garantir un accs unique et sriel cette structure. Des efforts sont toutefois consentis pour calculer la priorit des processus dans le cas dune architecture SMP : une partie de la priorit dpend du surplus de tches qui pourrait intervenir dans la cohrence de cache et dans le changement de la table des pages si un processus devait changer de processeur pour sexcuter. Cette pnalit au changement de processeur est naturellement totalement dpendante de larchitecture matrielle sous-jacente. Le verrouillage est nanmoins un goulet dtranglement pour le paralllisme (le fameux BKL ou Big Kernel Lock ). Le systme dexploitation doit donc voluer et cest ainsi quest apparu MQS ou Multiple Queue Scheduler . Au lieu dune seule queue de processus, il existe autant de queues que de processeurs. Chaque queue possde ses verrous mais ces derniers sont locaux au processeur auquel est attache la queue de processus. Le mcanisme dordonnancement intervient maintenant en deux tapes : 1. Acquisition du verrou local la queue de processus du processeur ayant entran lappel de lordonnanceur. La queue de processus du processeur est analyse an 197

Chapitre 8. Les architectures multi-processeurs et les threads

F IGURE 8.5 La queue locale au processeur CPU0 permet de choisir le premier processus de priorit avec afnit 200. Il est compar au premier processus distant du CPU1 de priorit sans afnit 150. Si le systme dexploitation ne parvient pas poser un verrou pour migrer ce processus distant dans la queue du processeur CPU0, cest le deuxime processus du processeur CPU0 qui sera lu.

de dterminer le processus possdant la meilleure priorit dexcution ainsi que celui venant immdiatement aprs dans ce classement. 2. Interrogation des queues non locales. Le processus candidat est compar aux candidats des autres CPUs et le processus gagnant acquiert le CPU. Naturellement un mcanisme permettant de renforcer ou de diminuer la priorit en fonction de la localit dun processus est mis en uvre, comme cela tait dj le cas dans le DSS. Deux valeurs de priorit sont affectes chaque processus, une priorit avec afnit qui tient compte de la localisation de lexcution et une priorit sans afnit. Le meilleur reprsentant local (tenant compte de lafnit) sera lu sil est plus prioritaire que le meilleur reprsentant distant sans tenir compte de lafnit. Lorsque cest un processus distant qui est lu, lordonnanceur peut trs bien chouer obtenir le verrou de la queue distante, et dans ce cas cest le deuxime processus local le plus prioritaire qui sera lu comme le rsume la gure 8.5.
Adaptation des programmes

Peut-on observer un gain de performance en changeant notre systme dexploitation pour quil utilise efcacement les multiples processeurs mis sa disposition sans pour autant changer les programmes (et donc la faon dont ils ont t conus) ? La rponse intuitive est oui mais il faut y apporter quelques prcisions. Lexcution de deux processus ne sera plus vraiment concurrente, chacun ayant son processeur pour drouler sa squence dinstructions. Nous allons donc constater une amlioration dans 198

8.2. Les threads la gestion des tches interactives. Sur un systme Unix avec interface graphique, un calcul intensif naffectera pas la uidit de linterface car automatiquement, par le biais des priorits, les processus mis en jeu dans la gestion des fentres trouveront un processeur libre pour les accueillir. De la mme manire, lencodage au format H.264 de deux squences vido sera en moyenne presque deux fois plus rapide. Malheureusement ce gain en performance sarrte ds que le nombre de processus importants ( nos yeux dutilisateur !) dpasse le nombre de processeurs. Chaque changement de contexte, mme sil prend place sur le mme processeur pour limiter les changements de cache ou du moins les acclrer, consomme du temps. Il faudrait disposer dun moyen pour rendre lintrieur de nos programmes parallle. Paradoxalement ce nest pas rellement le paralllisme qui a motiv la mise au point des ls 3 dactivit ou thread . Le fait dviter de passer par des mcanismes de dialogue sophistiqus (les smaphores par exemple) pour faire dialoguer deux processus intressait en effet nombre de personnes. Cest pourtant un moyen trs efcace de tirer parti dune architecture multi-processeurs ou multi-curs. Nous verrons de plus que des systmes dexploitation tels que Windows ou Solaris fondent leur politique dordonnancement sur la notion de thread plus que sur celle de processus.

8.2

Les threads

Introduction

Il fut un temps o linteraction entre les processus tait trs largement simplie par lutilisation de variables communes. Il tait alors trs simple de communiquer des donnes entre processus, la mmoire tait partage, au sens rel du terme. Ainsi, en 1965, sur le Berkeley Timesharing System, les processus partageaient rellement la mmoire et se servaient de ce partage pour dialoguer. La notion de thread, mme si la terminologie tait inconnue lpoque, tait bien l. Lavnement dUnix mit n ce partage et le dialogue entre les processus fut rendu plus complexe avec la protection de la mmoire : il fallait passer soit par des IPC (signal, pipe. . . ), soit par des segments de mmoire partags (ceux-ci vinrent plus tardivement). Au bout dun certain temps, les utilisateurs dUnix qui le partage de la mmoire entre processus manquait cruellement, furent lorigine de linvention des threads, des processus du mme genre que les processus classiques, mais partageant la mme mmoire virtuelle. Ces processus lgers, comme ils furent appels, apparurent la n des annes 70. Avant daborder les diffrents types de threads disponibles, nous allons commencer par dtailler lutilisation de la mmoire virtuelle, en pr-supposant quun thread est
3. Le terme l est prendre ici dans le sens celle. Le pluriel est particulirement ambigu en franais car il peut tre confondu avec le pluriel de ls au sens descendant mle en ligne directe !

199

Chapitre 8. Les architectures multi-processeurs et les threads avant tout attach un processus an de pouvoir aborder ultrieurement le problme de lordonnancement.

Le partage des ressources, mmoire et chiers


Que partager ?

Chaque processus sous Unix possde un accs virtuel la mmoire qui lui est propre, ainsi que le montre la gure 8.6. Un thread, an dacqurir pleinement le qualicatif de processus lger doit partager un certain nombre de choses avec le processus dont il est issu.

F IGURE 8.6 Un processus sous Unix. Une partie des informations (PID, tat. . . registres) fait partie de la gestion des processus organise par le systme dexploitation. Le reste est relatif au processus et de son accs la mmoire virtuelle (pile, tas, catalogue. . . ).

Nous avons plac en premier plan le fait de partager laccs la mmoire (nous en verrons le dtail aprs). Donc le catalogue des pages ainsi que les pages seront communs au processus et aux thread. Il en sera de mme pour la table des chiers 200

8.2. Les threads ouverts, ce qui nous met en alerte sur le fait que certaines prcautions devront tre prises an que les accs aux chiers ouverts ne soient pas incohrents. Puisquun thread doit possder une relative indpendance dexcution vis vis du processus dont il est issu an de pouvoir mettre en place une vritable politique dordonnancement en parallle, il est obligatoire que les informations conserves par le systme dexploitation (tat, registres, pointeurs de donnes ou dinstructions, de pile ou de tas. . . ) ne soient pas partages. Il faudra donc garder cette structure prive. Un thread est cr par lappel dune fonction systme et les instructions qui seront excutes lors de son activation sont naturellement comprises dans le code excutable du programme dont est issu le processus. Le segment de texte (ou code) dun thread est donc une partie du segment de texte du processus. Il parat donc naturel que cette zone soit commune aux deux structures. Il en va de mme pour les donnes statiques alloues (dclarations de variables globales, allocations statiques. . . ). Puisque le besoin initial est le partage de la mmoire, nous conviendrons ds lors quil doit tre possible de partager le tas (la mmoire alloue dynamiquement). Quen est-il de la pile ? Cette dernire sert conserver en mmoire les diffrentes variables locales lexcution dune fonction du programme. Le deuxime concept lorigine des threads intervient ici : lexcution simultane sur plusieurs processeurs. Une fonction pouvant tre appele par plusieurs threads sexcutant en parallle, il est impratif que chaque thread possde sa propre pile. Nous arrivons ainsi la structure de la gure 8.7.
Les prcautions prendre

Nous laissons toujours de ct le problme de lordonnancement pour aborder un point crucial qui sera repris plus en dtail dans un autre module (IN203). Nous avons maintenant la possibilit daccder la mmoire de manire asynchrone et parallle. Prenons lexemple dune bibliothque dont un fragment du code source est dvelopp ci-aprs :
int byte_per_line; int what_size(struct image_mylib *im) { if (im == NULL) return -1; if (im->pix_len <= 8) { byte_per_line = im->w*im->h; } else if (im->pix_len >= 24) { byte_per_line = 3*im->w*im->h; } } ... }

La variable globale byte_per_line est dclare de manire globale. Supposons que deux threads excutent cette fonction avec un lger dcalage temporel : 201

Chapitre 8. Les architectures multi-processeurs et les threads

F IGURE 8.7 Les threads Unix et les accs aux ressources. Les ches diriges vers la gauche accdent aux mmes ressources. Les structures exploites par le systme dexploitation (dans la table des processus) sont prives, ainsi que la pile an que chaque thread puisse prserver les variables locales aux fonctions quil excute.

/* thread 0 */ what_size(&image_source); do_it(); /* test */ if (byte_per_line == 3456) ...

/* thread 1 */ what_size(&image_dest); do_it(); /* test */ if (byte_per_line == 3456) ...

Nous remarquons dans le thread 0 qui se droule gauche, la variable est modie par lappel de la fonction what_size(). Mais lorsque le test a lieu, le contenu de cette variable a t modi par le thread 1 ! Il existe souvent dans un code source une partie qui ne peut pas tre rendue parallle. On appelle cette portion de code la section critique. Nous en avons donn un exemple trivial dans un programme abominable 4 . Le noyau Unix contient un certain nombre de
4. Lutilisation de variables globales est gnralement proscrire. Les premires versions de la bibliothque GIF possdaient ainsi un certain nombre de variables globales qui rendaient une exploitation

202

8.2. Les threads sections critiques. Il est impratif de les protger car elles sont loin dtre atomiques (excutables en une instruction sur le processeur). La premire solution adopte fut de masquer toutes les interruptions matrielles lorsque lune dentre elles est traite. Cela rend chaque appel systme bloquant pour le reste des processus ou des threads. La deuxime solution fut demployer de nouvelles instructions atomiques telles que CAS ( Compare And Swap ) ou TAS ( Test And Set ). Ces instructions permettent, en un cycle dhorloge, de comparer deux valeurs de registres et de les changer, ou de raliser un test et daffecter une valeur. Il devint possible de mettre au point des verrous infranchissables et ds lors de protger les sections critiques. Gardons toutefois en mmoire que ces protections par verrous sont des goulets dtranglement pour le systme dexploitation puisque les instructions protges par ces verrous ne peuvent tre excutes que par un seul processeur (do le nom de BKL ou Big Kernel Lock 5 ).
Les diffrents modles

Ce qui intressait les programmeurs tait avant tout le partage de la mmoire. Le fait de pouvoir raliser des excutions concurrentes sur plusieurs processeurs ntait pas encore au got du jours. Cest pourquoi les premiers modles de threads furent les threads utilisateurs encore appels ULT ( User-Level-Thread ). Mais la monte en puissance du calcul parallle ncessitait la mise disposition des KLT ( Kernel-LevelThread ).
Les threads utilisateurs

Dans ce cas de gure, le systme dexploitation nest pas au courant de lexistence des threads. Il continue de grer des processus. Lorchestration des threads est ralise par lapplication (le processus) au moyen dune bibliothque de fonctions. Le fait de changer de thread ne requiert aucun appel systme (donc pas de changement du mode utilisateur vers le mode superviseur) et lordonnancement est lui aussi relgu lapplication (donc la bibliothque). Ceci parat relativement avantageux, mais il faut regarder de plus prs. Lactivit du systme dexploitation se rsume, concernant les ULT aux actions suivantes : le noyau ne soccupe pas des threads mais continue grer les processus ; quand un thread (appelons le THR0) fait un appel systme, tout le processus sera bloqu en attendant le retour de lappel systme, mais la bibliothque de gestion des threads gardera en mmoire que THR0 tait actif et il reprendra son activit la sortie de lappel systme ;
multi-threade illusoire. 5. Le noyau Linux 2.0 grait le SMP mais avec peu de verrous. Le noyau tait donc, de manire image, un BKL lui tout seul ! Le 2.2 fut presque identique. Ce nest vraiment qu partir du 2.4 que toutes les oprations noyaux ont t multi-threades avec des verrous.

203

Chapitre 8. Les architectures multi-processeurs et les threads les threads de niveau utilisateur sont donc indpendants de ltat du processus. Nous pouvons donc dgager quelques avantages et inconvnients de ces ULT : le changement dactivit dun thread un autre ne requiert pas dappel systme, il ny a donc pas de changement de contexte ce qui rend cela plus rapide ; cest lapplication qui emploie la bibliothque de raliser lordonnancement de lactivit des diffrents threads, il est donc possible demployer un algorithme parfaitement adapt cette application prcise ; les threads de niveau utilisateur sont indpendant du systme dexploitation, nous avons seulement besoin de la bibliothque permettant leur emploi ; mais les appels systme sont bloquants au niveau des processus et ce sont donc tous les threads qui sont bloqus en prsence dun appel systme ; le systme dexploitation seul affecte chaque processus un processeur, donc deux threads de niveau utilisateur sexcutant au sein du mme processus ne pourront pas tre dploys sur deux processeurs diffrents. Cette dernire remarque ne plaide pas pour les ULT en dpit des avantages que ceux-ci procurent (libert de lOS, libert de lordonnancement. . . ).
Les threads systme

Cette fois lintgralit de la gestion des threads est ralise par le systme dexploitation. On accde aux threads par le biais dun ensemble dappels systme. Lordonnancement est ralis non plus sur la base du processus mais sur celle du thread. Dressons une liste rapide des avantages et inconvnients : le noyau peut rpartir les threads sur diffrents processeurs et lon accde ainsi un paralllisme dexcution ; les appels systme sont bloquants au niveau du thread et non du processus ; le noyau lui-mme peut tre rendu multi-thread ; mais le nombre de changements de contexte est multipli par le nombre de threads excutant les mmes instructions ce qui peut avoir des consquences nfastes sur la rapidit globale de lapplication.
Le mlange ULT / KLT

Il est naturellement tentant de mlanger les types de thread. Cest ce que ralise le systme dexploitation Solaris en offrant des threads noyau mais aussi une bibliothque de gestion de threads utilisateur. Ces derniers sont crs dans lespace utilisateur. Cest lapplication (au travers de son processus) qui se charge de grer leur ordonnancement ainsi que leur attachement un ou plusieurs threads noyau. Ceci permet aux concepteurs doptimiser lexcution des diffrents threads utilisateur. Un ensemble de threads utilisateur est donc encapsul dans un processus lger qui est maintenant gr par le systme dexploitation en tant que processus part entire et qui peut donc laffecter un processeur particulier. Cest au dveloppeur de faire les choix judicieux pour encapsuler le bon nombre de threads utilisateur dans un processus lger (ils se partageront 204

8.2. Les threads

F IGURE 8.8 Chaque processus (rectangles du haut) emploie un ou plusieurs threads utilisateur par le biais de la bibliothque. Cette dernire cre des processus lgers (petits rectangles superposs au rectangle symbolisant le bibliothque). Chaque processus lger est associ exactement un thread noyau. Le systme dexploitation se charge ensuite de rpartir les diffrents threads noyau sur les diffrents processeurs.

le temps de ce processus lger) et de laisser un thread utilisateur particulier tre gr comme un processus an de lui garantir une excution compltement concurrente des autres. La gure 8.8 rsume les diffrents agencements possibles. Nous ne pouvons pas terminer ce paragraphe sans citer les threads Java qui sont un bel exemple de migration russie du concept de thread utilisateur la premire machine virtuelle Java ne possdait que ce type de threads vers les threads noyau tout en offrant la possibilit demployer les threads utilisateur. Ceci parat quelque part normal, puisquil existe un point commun entre Solaris et Java : Sun Microsystems 6 .

Lordonnancement

Nous avons dj abord lordonnancement des processus sur une architecture multiprocesseurs laide des afnits entre processus et processeurs. On trouve cette notion dafnit dans le noyau Linux lintrieur du chier linux/kernel/sched.c :

6. Sun a t rachet en 2010 par Oracle.

205

Chapitre 8. Les architectures multi-processeurs et les threads


static inline int goodness(struct task_struct * p, int this_cpu, struct mm_struct *this_mm) { ... #ifdef __SMP__ /* Give a largish advantage to the same processor... */ /* (this is equivalent to penalizing other processors) */ if (p->processor == this_cpu) weight += PROC_CHANGE_PENALTY; ...

Mais ce calcul dafnit ne concerne que lligibilit dun processus vers un CPU. Le cur de lordonnancement se situe ailleurs et doit ncessairement tenir compte de la notion de thread. Nous examinons dans un premier temps lordonnanceur Linux pour examiner par la suite dautres politiques.
Lordonnanceur de threads Linux

Les threads sont lis un processus et doivent probablement se partager le temps doccupation lintrieur du contexte de ce processus. En y rchissant un peu plus, il est dailleurs abusif de parler de processus matre et de ses threads. Cette distinction na pas lieu dtre, un processus nutilisant pas dappels systme tel que pthread_create() possdent nanmoins un thread, lui mme. Il ny a donc pas de notion de matre. Restreindre lutilisation dun CPU aux threads pendant le quantum de temps durant lequel le processus auquel ils sont attachs est ligible parat peu efcace. . . mais pas forcment ! Examinons les deux politiques qui peuvent tre mises en place en nous plaant dans un premier temps lintrieur dune architecture mono-processeur. Le noyau Linux ne tient compte que des processus, chacun dentre eux tant considr comme un ensemble T de n threads. Sous Linux les threads sont mis la disposition de lutilisateur par lintermdiaire de lAPI POSIX libpthread. Cette bibliothque dappels permet de crer des threads en leur affectant un contexte dexcution parmi : PTHREAD_SCOPE_PROCESS : le thread est dpendant, toujours du point de vue de lordonnancement, du processus dont il fait partie ; PTHREAD_SCOPE_SYSTEM : le thread est directement attach un thread systme et donc indpendant, du point de lordonnancement, du processus dont il fait partie. Quand la cration se place dans le contexte processus , cest le processus dans son ensemble qui rentre en comptition pour laccs au CPU. Les threads qui composent ce processus rentreront en comptition les uns avec les autres pour accder au CPU lors de llection du processus. Cest ce que montre la gure 8.9. Remarquons que peu importe la faon dont chaque thread du deuxime processus sera lu pour accder au 206

8.2. Les threads

F IGURE 8.9 Chaque thread est cr avec un contexte li au processus dont il fait partie. Le processus 1 ne possde quun seul thread tandis que le deuxime possde 2 threads. Laccs au CPU est quirparti et ainsi lunique thread du processus 1 reoit 50 % des quantums de temps tandis quun thread du processus 2 nen reoit que 25 %.

CPU, soit en divisant le quantum allou au processus 2 pour drouler les instructions de

chaque thread, soit en allouant le quantum un seul thread alternativement. En effet, le changement de contexte entre deux threads du mme processus est quasi ngligeable. Il est toutefois beaucoup plus simple de ne pas dcouper chaque quantum mais plutt dattribuer un quantum (au sein dun processus) chaque thread tour de rle. Lors dune cration dans un contexte systme , soit le deuxime cas de gure prsent, tout se passe comme si le thread tait plac dans la table des processus et rentrait en comptition avec les autres processus pour obtenir un quantum de temps dexcution. Un processus possdant de nombreux threads est alors largement avantag par rapport un processus ne possdant quun seul thread comme le montre la gure 8.10. Mais par dfaut, la cration de thread utilise le contexte du processus.

F IGURE 8.10 Chaque thread rentre en comptition pour laccs au CPU indpendamment du processus auquel il appartient. Lunique thread du processus 1 obtient ainsi 33% du temps CPU, au mme titre que les deux threads du second processus.

Nous pouvons rsumer ces deux modes dordonnancement en disant que dans le cas SCOPE_SYSTEM chaque thread compte pour 1 processus, alors que dans le cas SCOPE_PROCESS chaque thread compte pour un tantime du processus dont il dpend. Il reste combiner cette gestion avec plusieurs processeurs et donc utiliser lordonnanceur multi-queues. La gure 8.11 donne un exemple sommaire et forcment simpliste du droulement des threads dans une structure deux curs. 207

Chapitre 8. Les architectures multi-processeurs et les threads

F IGURE 8.11 Cette gure reprsente de faon trs simple un processus dordonnancement dans une architecture multi-curs. Chaque processeur possde sa queue dordonnancement (un cercle) et il existe aussi une queue gnrale. On remarque deux types de thread, ceux dclars dans un contexte systme (S) et ceux dclars dans un contexte processus (P). Le processus bleu (marqu dun B ) et le processus vert (marqu dun V ) sexcutent en parallle. Labsence de changement de contexte gnral entre les threads du processus vert lui permette dtre lu assez facilement. Ceci montre bien la complexit de ralisation dun ordonnanceur car le processus vert pourrait fort bien affamer les autres processus.

Les autres ordonnanceurs

Le systme dexploitation Windows, du moins dans ces versions rcentes (i.e. partir de Windows Vista, axe sa politique dordonnancement autours des threads. Un processus est une structure complexe dont la cration est ralise ex nihilo et donc en gnral plus lentement que sous un Unix classique. La cration dun thread sous Windows Vista est par contre plus rapide que sous Linux. Rappelons que la cration dun processus par la fonction CreateProcess() permet de crer un processus sans relle notion de parent / ls et possdant ce que Windows Vista appelle un thread principal. Windows Vista ne possde aucun quivalent la fonction fork() et Microsoft annonce que son mulation serait trs difcile. Pourquoi ? Simplement parce que : fork est une fonction difcile utiliser dans un systme Unix de type SMP, car il faut penser tout et quil y a de nombreux problmes rsoudre avant de pouvoir programmer cette fonction correctement 7 ! Et de conclure sur le fait que la fonction fork nest pas vraiment approprie dans un environnement de type SMP ! Si le dveloppement des systmes dexploitation devait sarrter chaque fois quune chose est difcile, nous en serions encore utiliser des moniteurs rsidents !
7. extrait traduit de Windows System Programming, 3rd Edition de Johnson M. Hart.

208

8.2. Les threads Tout comme la plupart des systmes dexploitation, Windows Vista utilise un ordonnanceur multi-queues. Introduit assez rcemment, ce nouvel ordonnanceur permet lutilisateur, mais surtout aux dveloppeurs, de ranger certaines applications ainsi que leurs threads dans un niveau de priorit spcique : les applications multimdia. Ceci se fait par lintermdiaire de la base de registres. On trouve ainsi un service grant ces priorits et lappartenance de certaines applications cette queue spciale : le MMCSS ou Multimedia Class Scheduler Service. Le MMCSS permet daugmenter la priorit de toutes les threads et possde donc pour sa part une priorit trs leve de 27 (les priorits des threads sous Windows Vista vont de 0 31). Seul le thread de gestion de la mmoire possde une priorit plus leve de 28 ou 29. Cependant, mme sil est agrable de ne pas interrompre la lecture du dernier morceau de musique achet lgalement sur un site de vente de musique en ligne, il peut, accessoirement, tre utile de lire son courrier lectronique ou ventuellement davancer le dveloppement dun projet informatique. Il est donc primordial que ce systme de priorit leve pour les threads qualies de temps rel naffecte pas ou naffame pas les autres processus. Une partie du temps CPU est donc rserv par le MMCSS pour les autres threads, de lordre de 20 %, mais cette quantit peut tre modie par le biais de la base de registres. An dviter aux threads multimdia de prendre la main pendant ce laps de temps, le MMCSS leur affecte une priorit variant de 1 7. Le point le plus important est toutefois la mise en place dune mesure effective du temps CPU consomm par un thread. En effet, il est courant quun processus soit mis en attente par une interruption logicielle ou matrielle, puis quil reprenne. Le quantum de temps qui lui tait affect naura donc pas t intgralement consomm mais le systme dexploitation le tiendra pourtant pour acquis. Les nouveaux processeurs intgrent un registre de compteur de cycles et Windows Vista tire prot de ce compteur pour raffecter le CPU un thread dont la fentre dexcution a t interrompue. Ceci assure effectivement que chaque thread pourra drouler ses instructions en bnciant au moins une fois de la totalit de sa fentre dexcution. Le systme Solaris introduit 4 objets pour la gestion des threads : les threads noyau : cest ce qui est effectivement programm pour une excution sur le CPU ; les threads utilisateur : il sagit de ltat dun thread dans un processus lintrieur du mode utilisateur ; le processus : cest lobjet qui maintient lenvironnement dexcution dun programme ; le processus lger : il sagit du contexte dexcution dun thread utilisateur qui est toujours associ avec un thread noyau. Les services du noyau et diffrentes tches sont excuts en tant que threads en mode noyau. Lorsquun thread utilisateur est cr, le processus lger (ou lwp pour Light Weight Process) et les threads du mode noyau associs sont crs et associs au thread utilisateur. Solaris possde 170 niveaux de priorit diffrents. Comme sur la plupart des systmes dexploitation rcents, les niveaux de priorit correspondent des classes 209

Chapitre 8. Les architectures multi-processeurs et les threads diffrentes dordonnancement (les priorits de valeur faible sont les moins prioritaires) : TS (Time Sharing, temps partag) : il sagit de la classe par dfaut pour les processus et les threads du mode noyau associs. Les priorits lintrieur de cette classe vont de 0 59 ; IA (InterActive) : il sagit dune extension de la classe TS permettant certains threads associs des applications graphiques (fentre) de ragir plus vite ; FSS (Fair-sare Scheduler) : il sagit de lordonnanceur classique que lon trouve sous Unix et qui permet dajuster la priorit en fonction de donnes statiques mais aussi dynamiques ; FX (Fixed-priority) : la diffrence des threads prcdents, ceux-ci nont quune priorit xe ; SYS (systme) : il sagit des priorits des threads du mode noyau. Leur priorit varie de 60 99 ; RT (real-time) : les threads de cette classe possdent un quantum xe allou sur le CPU. Leur priorit varie entre 100 et 159 ce qui permet ces threads de prendre la main aux dpens des threads systme.

8.3

Conclusion

Larrive des processeurs multi-curs et des architectures multi-processeurs na pas radicalement chang le monde Unix. En effet, il existait dj tous les prmisses du calcul distribu dans les projets comme Beowulf et les diffrents problmes inhrents ce paralllisme (mmoire, ordonnancement) taient connus et des solutions mises en uvre. Il a fallu avant tout optimiser laccs la mmoire et rduire le temps consacr aux changements de contexte. Mme si ce nest pas de prime abord ce qui mena aux threads, le fait de partager la mmoire (tas, code, donnes) acclra grandement le changement de contexte. Lenjeu est maintenant de rduire les allers retours entre les processeurs et de dnir des afnits entre processeur et mmoire (NUMA).

210

9
La virtualisation des systmes dexploitation

VIROSE n. f. Infection due un virus. VIRTUALIT n. f. Caractre de ce qui VIRTUEL , ELLE

est virtuel. adj. (du latin virtus, force) 1. Qui nest quen puissance : potenadv. De faon virtuelle.

tiel, possible. . .
VIRTUELLEMENT

Ce rapide extrait du P ETIT L AROUSSE I LLUSTR nous montre labsence dans le dictionnaire du mot virtualisation. Que le lecteur nous pardonne, nous allons pourtant lemployer de manire abondante dans ce chapitre et nous lavons mme dj utilis dans les chapitres prcdents. En effet nous avons abord la virtualisation, ne serait-ce quau travers des accs la mmoire physique (cf. chapitre 6). Remarquons que laccs au systme de chiers (aux systmes. . . ) est, quelque part, lui aussi virtualis puisque du point de vue de lutilisateur, que le chier quil manipule se trouve sur le disque dur de lordinateur ou quelque part sur le rseau, peu importe, laccs reste identique au travers des appels systme open(),read(), write() et close(). De la mme manire les clusters de systmes (Beowulf) tels que ceux voqus dans le chapitre prcdent (cf. chapitre 8) offrent la vision dun ordinateur virtuel cumulant la puissance des nuds du cluster. Quest-ce quune machine virtuelle ? Il sagit avant tout dun systme lectronique sans existence matrielle, chaque composante dudit systme tant purement et simple211

Chapitre 9. La virtualisation des systmes dexploitation ment simule (nous apporterons plus de dtails et de bmols sur cette simulation ). Wikipdia 1 donne la dnition suivante dune machine virtuelle : La virtualisation consiste faire fonctionner sur un seul ordinateur plusieurs systmes dexploitation comme sils fonctionnaient sur des ordinateurs distincts. On appelle serveur priv virtuel (Virtual Private Server ou VPS) ou encore environnement virtuel (Virtual Environment ou VE) ces ordinateurs virtuels. Dans le cas dune utilisation grand public, telle que loffrent des solutions comme VirtualBox, QEMU ou encore VMware, le possesseur dun ordinateur unique sur lequel est install un systme dexploitation unique 2 peut se servir dun logiciel qui fera ofce de machine virtuelle et dmarrer ainsi un nouveau systme dexploitation totalement diffrent de celui quil emploie. Ce systme dexploitation virtualis lui permettra ainsi dutiliser danciens logiciels devenus incompatibles avec les systmes dexploitation rcents ou simplement daccder une offre logicielle diffrente (architecture PC vs Mac OS X). Le concept de virtualisation et les travaux associs ont commenc au sicle dernier en France, Grenoble, en 1965 dans le centre de recherche dIBM France !

9.1

Les intrts et les enjeux de la virtualisation

Les intrts dune machine virtuelle

Il peut sembler toutefois curieux de simuler un ordinateur pour des besoins autres que ceux lis la conception. Pourtant, disposer de machines virtuelles intresse diffrentes populations. Les spcialistes, experts, en scurit informatique peuvent disposer au travers dune machine virtuelle dun moyen sans danger et trs efcace dobserver le comportement de logiciels malveillants. Une fois la machine corrompue il est trs simple de revenir en arrire puisque tout ce qui la compose est simul et peut donc tre sauvegard un instant t quelconque. Lobservation de logiciels malveillants est ainsi parfaitement scurise dune part, et dautre part reproductible et ce de manire trs rapide et dterministe (cest la machine dans son ensemble qui fait un retour vers le pass). Il est aussi possible de travailler par diffrences et de comprendre le comportement du logiciel malveillant par une analyse avant / aprs. Pour une entreprise la virtualisation offre de nombreux avantages. Tout dabord, lutilisation de la virtualisation permet dhberger au sein dune seule machine physique plusieurs machines virtuelles. Lorsque lon sintresse au cot dun serveur et que lon regarde de plus prs lutilisation du CPU, on remarque que la plupart des serveurs
1. http://fr.wikipedia.org/wiki/Virtualisation 2. La majeure partie des ordinateurs personnels ne comportent pas de gestionnaire de boot et nhbergent quun systme dexploitation. Lutilisation de GRUB et de plusieurs partitions de dmarrage hbergeant des systmes dexploitation diffrents est encore peu rpandue.

212

9.1. Les intrts et les enjeux de la virtualisation sont largement sous-employs. La valeur moyenne dutilisation du processeur est de lordre de 10 %, ce qui reprsente donc un gchis norme, tant en investissements, quen ressources. Ce serveur doit en effet tre maintenu (les contrats de maintenances matriels sont gnralement trs chers), il doit tre plac dans un local climatis, il est raccord au rseau informatique de lentreprise par des cbles et enn il occupe une place non ngligeable dans une baie informatique.

F IGURE 9.1 Les baies informatiques permettent de ranger des serveurs physiques. Mais la place disponible nest gnralement pas extensible et les salles accueillant ces baies doivent tre climatises, places dans un environnement lectrique de conance (onduleur). La virtualisation peut apporter une solution. (image dune baie de serveurs lUniversit Toulouse 1 Capitole)

Un ensemble de serveurs virtuels peut tre hberg par un seul serveur physique. La moyenne actuelle dhbergement pour des serveurs physiques de moyenne gamme tant de lordre de 10 20 machines virtuelles par serveur, nous ralisons ainsi un gain en cblage (seules les alimentations et les interfaces rseaux de la machine physique sont connectes) et un gain de place (un emplacement l o il aurait fallu une baie entire). La climatisation reste par contre sensiblement constante puisque nous allons maintenant utiliser le CPU et les autres composants dune faon beaucoup plus rgulire. Mais nous venons de raliser un gain consquent sur notre march de maintenance puisque pour la valeur dun serveur et sa garantie pices et main duvre, nous avons 15 serveurs diffrents (ou identiques. . . mais ne brlons pas les tapes !). Le lecteur attentif nous objectera immdiatement que nous avons certes un seul contrat de maintenance sur une machine physique, mais que lorsque celle-ci tombe en panne ce sont 15 serveurs qui sarrtent subitement de fonctionner. Effectivement, la tolrance aux pannes est un sujet crucial dans les DSI actuelles et la reprise dactivit doit tre sinon immdiate, tout du moins la plus rapide possible. En rgle gnral il est trs dangereux de mettre lintgralit des machines virtuelles sur un seul serveur et le bon sens mne plutt vers une solution ou deux serveurs physiques hbergent le parc 213

Chapitre 9. La virtualisation des systmes dexploitation de serveurs virtuels. Nous arrivons en fait la mme solution que celle propose par un environnement physique comme montre la gure 9.2. La tolrance aux pannes est donc gre de la mme manire sur un parc virtuel et sur un parc rel. Nous pouvons enn utiliser cette virtualisation pour raliser des tests quil serait impensable de mener sur une machine de production. Lapplication dune mise jour ou dune rustine sur un logiciel peut parfois saccompagner de quelques surprises. Pire, le passage vers une nouvelle version du systme dexploitation peut se rvler trs dangereux, certaines applications cesseront de fonctionner correctement. Dans le cas du particulier aventureux, le passage dune Ubuntu 9.04 vers la version 10.04 est souvent vcu comme une longue suite dinterrogations mtaphysiques sur le sens donner la phrase /dev/sda - Une table de partition GPT a t repre. Que voulez-vous faire : Partitionnement libre ou avanc . La seule question qui se pose ce moment est vais-je perdre toutes mes donnes . Notre particulier aventureux a pourtant dj rencontr cette tape de formatage du disque dur et il a brillamment franchi ce cap, mais la prsentation tait diffrente, les questions poses diffremment (voir g. 9.3) et surtout ctait un nouvel ordinateur sans rien sur le disque, alors recommencer trente fois ne portait pas consquence. Maintenant, lenjeu nest plus le mme aprs un an dutilisation ! Une multitude dautres questions viendront aprs avoir surmont cette tape : mon imprimante, mon scanner, mon rseau, mon mail, les photos de Tata. . . .

Figure (a)

Figure (b)

F IGURE 9.2 [Fig. (a)] Dans un environnement de production les serveurs (Web, impression,. . . ) sont gnralement prsents en double an de permettre une reprise dactivit en cas de panne. La virtualisation permet de conserver cette redondance. Ici le parc de serveurs virtuels hbergs sur le serveur de gauche est identique celui du serveur de droite. Les deux serveurs physiques sont interchangeables. [Fig. (b)] Une solution plus avance place les chiers lis aux diffrentes machines virtuelles sur un espace de stockage accessible par diffrents serveurs physiques. La panne dun serveur, et le fait que les donnes des machines virtuelles nont pas tre dplaces, permet une reprise sur incident trs rapide.

Lutilisation dun logiciel de virtualisation permet de se familiariser avec les diffrentes tapes dune installation ou dun changement majeur de version de systme 214

9.1. Les intrts et les enjeux de la virtualisation dexploitation. La copie des anciens chiers de conguration, ou leur comparaison, dans cette nouvelle machine virtuelle permettra de vrier quils sont toujours compatibles avec les services mis jour.

F IGURE 9.3 La douloureuse tape de choix des partitions devient plaisante puisque lerreur est permise (Installation de Ubuntu-10.10amd64 sous Mac OS X laide de VirtualBox) !

Cest naturellement dans lentreprise que le recours aux serveurs virtuels permet daborder les mises jour sans crainte de devoir restaurer une conguration partir de sauvegardes sur disques (ou pire dune rinstallation complte partir dune image disque ). Un logiciel de virtualisation tant avant tout un logiciel, il permet en rgle gnrale de prendre un instantan dune machine virtuelle, i.e. une photographie de ltat de la machine, mmoire, disque, interruptions matrielles comprises ! La mise jour pourra tre applique sur cet instantan dans un contexte rseau diffrent, ou sur une autre machine virtuelle, puis elle sera mise lpreuve pour enn tre valide. Dans ce cas linterruption de service sera minime puisque cest linstantan qui sera mis en lieu et place du serveur initial. Ce ne sont pas les seuls avantages de la virtualisation. Un certain nombre denjeux doivent tre pris en considration.
Prparer lavenir

Dun point de vue strictement conomique, une entreprise (telle quune SSII) doit rester comptitive et dcliner son offre en tant la fois ractive et exible. La ractivit impose de pouvoir mener des tests logiciels sur des systmes dexploitation divers et varis. Maintenir autant de serveurs physiques que de versions de systmes dexploitation serait bien trop onreux. Dans la mme optique, sadapter facilement et simplement la demande, aux besoins des clients, est chose plus aise lorsque lon peut compter sur des environnements 215

Chapitre 9. La virtualisation des systmes dexploitation de dveloppements installs sans risque et sans grever le budget par lacquisition dun nouveau serveur. Mais dans ce domaine cest de loin la possibilit dinstaller et dadministrer distance des machines qui a un impact grandissant sur les cots. Un serveur virtuel peut en effet tre install, administr, arrt et redmarr depuis son socle comme le montre la gure 9.4 (la machine physique, ou du moins le logiciel de virtualisation).

F IGURE 9.4 La console dadministration dun hyperviseur (ici Vmware ESX) permet daccder aux diffrentes machines virtuelles hberges.

Toutefois il faut aussi rchir la ncessaire formation qui accompagne obligatoirement le passage dune solution totalement physique une solution virtualise. Les administrateurs doivent apprendre de nouvelles techniques, ltat desprit doit changer car la gestion dun parc de machines virtuelles nest pas du tout la mme que celle dun ensemble de machines physiques.

9.2

Les diffrentes solutions

Il existe diffrents moyens de raliser une virtualisation, ceux-ci dpendant gnralement de lutilisation que lon veut en faire, scurisation, hbergement de systmes dexploitation varis et diffrents, simulation. Nous allons examiner ces solutions, sans entrer en profondeur dans les dtails mais plutt en analysant les modications quelles imposent au systme dexploitation hte (la machine physique) et aux sys216

9.2. Les diffrentes solutions tmes invits (les machines virtuelles). Nous examinerons ainsi, le cloisonnement, la para-virtualisation, la virtualisation assiste par le matriel et la virtualisation complte. Prcisons quelques choix de vocabulaire : le systme qui hberge les machines virtuelles sera indiffremment appel : systme hte, systme physique, socle ; les systmes virtualiss seront appels : environnement virtuel, systme invit, systme hberg, instance virtuelle.

Le cloisonnement ou lisolation

F IGURE 9.5 Le principe des prisons permet de cloisonner diffrentes applications en leur associant de plus des utilisateurs et un administrateur (root) qui leur sont propres.

Une des techniques les plus simples mettre en uvre est le cloisonnement. Vous pouvez avoir chez vous, sur votre ordinateur, un ensemble de chiers relatifs aux corrigs des travaux dirigs du cours dIN201. Vous souhaiteriez les mettre disposition de certaines personnes par une connexion scurise telle que ssh 3 mais pour autant vous ne souhaitez pas mettre lintgralit de votre disque dur disposition de ces amis privilgis. Lappel systme chroot() permet de se dplacer dans un rpertoire qui devient le rpertoire racine du systme :

3. Secure SHell est un moyen de se connecter sur un ordinateur distant par lintermdiaire dune connexion chiffre. la diffrence dun serveur Web, cette connexion ne sera possible que si lutilisateur distant possde un compte sur la machine laquelle il veut accder et surtout elle est bien plus scurise quun simple serveur Web avec authentication.

217

Chapitre 9. La virtualisation des systmes dexploitation


$ cd / $ /bin/ls bin dev mnt root sys boot etc lib opt sbin cdrom home selinux usr initrd.img proc srv var $ chroot /usr/local/test /bin/ls / bin lib usr $

vmlinuz tmp media

Dans lexemple qui prcde, la commande "/bin/ls /" excute dans le contexte du chroot prend place lintrieur du rpertoire local /usr/local/test. Cette commande demande la liste des chiers et rpertoires prsents la racine du systme de chiers, et nous voyons clairement que la rponse obtenue est diffrente de celle de la commande excute normalement. Nous commenons voir comment tirer parti de cette commande pour isoler diffrents contextes dutilisation. Cest ce qui est mis en uvre dans les isolateurs tels que les zones sous Solaris ou les jails sous FreeBSD. Nous ne parlerons pas de virtualisation car un processus excut dans un environnement chroot naccde qu une partie restreinte du systme de chiers, mais il partage nanmoins les notions dutilisateurs, de groupes et bien dautres choses avec le systme dexploitation. Les jails amliorent la technique employe par chroot an de fournir aux processus chroots un environnement complet (utilisateurs, groupes, ressources rseaux, etc.). Ainsi une prison est caractrise par quatre lments : une arborescence dans le systme de chiers. Cest la racine de cette arborescence qui deviendra la racine de la prison ; un nom dhte ou FQDN (Fully Qualied Domain Name) ; une adresse IP qui viendra sajouter la liste des adresses gres par linterface rseau ; une commande qui sera excute lintrieur de la prison. Tous les processus qui seront excuts lintrieur de la prison seront grs de faon classique par lordonnanceur. Il en sera de mme de la mmoire. Chaque processus fera donc partie de la table des processus du systme. Nous retrouvons ce principe dans les zones Solaris. Il sagit ni plus ni moins dune amlioration 4 du chroot avec une gestion des processus centralise. Il est toutefois possible de grer de manire diffrente chaque systme invit, mais il faut pour cela intervenir sur le noyau du systme dexploitation hte. Cest ce que fait OpenVZ sans pour autant prsenter une abstraction du matriel comme le feront les mcanismes que nous tudierons aprs. An de confrer aux diffrentes instances virtuelles une vie propre et donc la gestion des processus, OpenVZ modie le noyau Linux pour fournir plusieurs tables des processus, une par instances, ainsi quune table pour lhte, et donc un ordonnanceur
4. Attention, lamlioration est toutefois trs importante car les zones tout comme les jails permettent bien plus de choses quun simple chroot !

218

9.2. Les diffrentes solutions

F IGURE 9.6 Sur un hte OpenVZ il nexiste quun seul systme dexploitation, celui de lhte. Les instances virtuelles nont pas de noyau mais bncient de systmes de chiers indpendants. Leurs processus sont grs dans diffrents contextes et font partie de lordonnancement gnral du noyau du socle. Chaque instance virtuelle peut donc bncier de tout le CPU disponible.

qui prendra en compte ces diffrentes tables. Chaque instance virtuelle possde ainsi une table des processus, mais cest le systme hte qui intgre dans son ordonnanceur cette table des processus en plus de la sienne. OpenVZ tant un isolateur, les instances virtuelles gres par lhte nont pas de systme dexploitation (voir gure 9.6). Le systme hte est lanc dans un contexte particulier (la notion de contexte faisant partie des ajouts noyau du socle raliss par OpenVZ), les diffrentes instances virtuelles excuteront leurs processus dans un autre contexte. Le mme partage existe pour les accs la mmoire. Chaque processus, quil appartienne au socle ou une instance virtuelle, possde ses accs la mmoire virtuelle au travers de sa table des pages. Cest donc toute la mmoire qui est partage entre le socle et les instances. Par contre, lutilisation de contexte (et de toutes les informations complmentaires ajoutes au noyau du socle) permet de limiter la mmoire mise disposition dune instance. Notons que le rseau est lui aussi virtualis, ceci permet chaque instance de possder sa propre adresse IP ainsi que ses propres rgles de pare-feu. Les avantages dun tel systme sont naturellement lis aux performances : 219

Chapitre 9. La virtualisation des systmes dexploitation un trs faible pourcentage du temps CPU est consacr la virtualisation puisquil nexiste quun seul noyau en fonctionnement ; chaque instance nest rien dautre quune arborescence de chiers telle quon la trouverait sur un systme Unix classique. Crer une instance revient donc copier des chiers ; chaque instance est parfaitement isole des autres et du systme hte. Ce dernier voit par contre lintgralit des instances et peut interagir avec chacune dentre elles. Il est par contre tout fait impossible dhberger une instance dun autre type quUnix puisque seul le systme de chiers est copi. Les appels systme doivent donc tre communs car seul le noyau de lhte est disponible pour les excuter ! De nombreuses instances peuvent tre places sur un hte :

[root@s-openvz ~]# /usr/sbin/vzlist -a VEID NPROC STATUS IP_ADDR 103 48 running 10.45.10.83 104 6 running 10.45.10.161 109 45 running 10.45.10.78 110 38 running 10.45.10.127 111 18 running 10.45.10.160 112 - stopped 10.32.10.162 115 30 running 10.45.10.248 116 - stopped 10.73.10.28 117 33 running 10.45.10.137 118 34 running 10.45.10.200 120 - stopped 10.44.10.43

HOSTNAME v-vir01.moi.org v-vir02.moi.org v-vir03.moi.org v-vir04.moi.org v-vir05.moi.org v-test1.moi.org v-vir07.moi.org v-kaput.moi.org v-vir08.moi.org v-enprod.moi.org v-entest.moi.org

Et linterrogation de lensemble des processus sexcutant sur lhte permet de voir les diffrents contextes dexcution : 220

9.2. Les diffrentes solutions


8909 10913 10989 10992 12807 12823 23313 13123 23209 13280 12871 10276 17091 16447 18569 24841 18667 29167 13144 13189 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Ss Ss SN SN Ss S S Ss S Ss S Ss Ss SN SN S Ss S S Sl 0:17 init [3] 0:26 \_ ... 0:00 \_ /sbin/bunch_agentd 0:30 | \_ /sbin/bunch_agentd 0:10 \_ /usr/libexec/postfix/master 0:01 | \_ qmgr 0:00 | \_ pickup 0:11 \_ /usr/sbin/httpd 0:37 | \_ /usr/sbin/httpd 0:11 \_ winbindd 0:00 \_ winbindd 0:01 init [2] 0:00 \_ ... 0:00 \_ /sbin/bunch_agentd 0:30 | \_ /sbin/bunch_agentd 0:00 | \_ /usr/sbin/winbindd 0:02 \_ /usr/sbin/apache2 0:00 | \_ /usr/sbin/apache2 _ /bin/sh /usr/bin/mysqld_safe 0:00 \ 0:09 \_ /usr/sbin/mysqld

La virtualisation complte

An dhberger des systmes dexploitation divers et varis, il est impratif de pouvoir excuter leur noyau. Les isolateurs montrent alors leurs limites et on doit faire appel une solution de virtualisation complte. Lintgralit dune machine physique est ainsi simule. Le noyau dun systme dexploitation nest rien dautre quun programme et virtualiser un systme dexploitation revient donc excuter un programme un peu particulier, le noyau, mais en faisant singulirement attention tous les appels quil dclenche vers le matriel. En effet seul lhte doit avoir la main sur le matriel, sinon chaque instance pourra sa guise raliser des modications relativement pnalisantes et parfois mme dangereuses (un systme dexploitation permet laccs au matriel et contrle laccs au matriel). Diffrentes solutions soffrent nous pour raliser un tel simulateur : Lmulation complte du noyau et des diffrents appels. Il conviendra danalyser chaque instruction pour vrier ce quelle dsire raliser et quels sont ses droits puisque le systme hbergs, mme sil crot fonctionner dans lanneau de privilge 0 est en fait plac dans lanneau 3. Cette solution apparat comme rapide et surtout ne requiert pas de modication du noyau invit. La rcriture des sources des noyaux invits parat peu envisageable mme si les performances seraient alors accrues (pas de ncessit de vrier chaque instruction). Nous examinerons cette solution plus tard. Utilisation de certaines technologies telles que Intel-Vtx ou AMD-V pour obtenir une acclration matrielle ! Nous reportons lexamen de cette solution pour la 221

Chapitre 9. La virtualisation des systmes dexploitation

F IGURE 9.7 On mule le matriel an de faire croire aux systmes dexploitation invits quils sexcutent sur une machine physique. On nonce par abus de language que les systmes hbergs nont pas conscience dtre virtualiss.

dernire partie.

F IGURE 9.8 Dans cet exemple, le logiciel de virtualisation remarque lutilisation par un noyau invit du registre CR3 qui contrle ladresse de la table des pages mmoire et intervient an de raliser une traduction en appelant une fonction prdnie qui se chargera de raliser, mais de manire scurise, ce changement.

Un logiciel de virtualisation nest pas un simulateur 5 , il est en effet beaucoup plus restrictif puisquil ne peut hberger que des systmes dexploitation sexcutant sur la
5. Nous avons dj parl de Rosetta qui permet de traduire des instructions pour PowerPC en instruction pour processeur Intel. Un simulateur doit raliser la traduction (par bloc dinstructions) du code que dsire excuter le noyau invit an de lui faire correspondre les instructions du processeur physique sous-jacent. Ce mcanisme introduit ncessairement une perte de performances lie au temps consomm par les efforts de traduction.

222

9.2. Les diffrentes solutions mme architecture matrielle que lui : lISA (Instruction Set Architecture) et donc les instructions qui seront excutes sur le CPU doivent tre identiques entre le systme hte et les systmes invits. Une fois ce pr-requis obtenu, le logiciel de virtualisation peut excuter, sans avoir les traduire, les instructions en provenance des systmes invits. Nous savons toutefois que certaines instructions, dangereuses car intervenant sur le matriel, ne peuvent sexcuter quen mode noyau. Leur demande dexcution par le systme invit entrane donc une interruption puisque ce dernier tourne dans lanneau de niveau 3. Le rle du logiciel de virtualisation consiste dtecter ces interruptions et raliser les traductions ou les remplacements adquats pour permettre ces appels daboutir sans pour autant les excuter sur le processeur. La gure 9.8 montre un exemple dinterception dune interruption et de la traduction assortie. Tous les accs au matriel virtuel, quil sagisse naturellement du CPU mais aussi des diffrents priphriques prsents, sont signals par des erreurs dinstructions illgales dans lanneau 3 et traduites pour tre rpercutes sur le matriel physique. Un logiciel de virtualisation doit donc tre capable dmuler (de simuler) un certain nombre de matriels, tels que contrleurs SCSI, cartes graphiques, lecteur de DVD, disques durs, . . . Il sera difcile de choisir par vous-mme les composants de votre machine virtuelle.

F IGURE 9.9 Un certain nombre de logiciels simulent la prsence de matriel pour linstance hberg. Dans ce panneau de conguration de VmWare ESX, on note la prsence dun contrleur SCSI sur lequel se situe le disque dur virtuel. Seuls deux types de contrleur sont proposs.

Tout serait relativement simple si toutes les instructions privilgies sexcutaient exclusivement dans lanneau 0. Le logiciel de virtualisation raliserait les actions adquates pour chaque erreur. Hlas lISA des processeurs Intel rvle 17 instructions ne sexcutant pas en mode noyau, mais en mode utilisateur (donc lanneau 3), qui sont nanmoins critiques du point de vue dun systme dexploitation (elles affectent des ressources matrielles). Ces instructions doivent donc tre interceptes ce qui 223

Chapitre 9. La virtualisation des systmes dexploitation signie que le logiciel de virtualisation doit analyser lintgralit des instructions. Cest une perte de performance. Nous verrons que lISA a su voluer pour conduire une virtualisation assiste par le matriel. Outre cette analyse au coup par coup des 17 instructions ne dclenchant par derreur 6 , le logiciel de virtualisation doit imprativement observer toutes les tentatives daccs la mmoire an de les rinterprter pour grer correctement le partage de la mmoire entre les diffrents systmes invits. Nous trouverons donc les pages mmoires doublement virtuelles des systmes invits, puis la mmoire virtuelle du systme hte et enn la mmoire physique. Une solution simple de gestion de la table des pages invite est de la marquer en lecture seule an dintercepter les erreurs gnres par tous les accs et de les rinterprter. Nous avons l encore une baisse de performance. Enn il est important de faire croire aux systmes invits que leur noyau sexcute dans lanneau de niveau 0. Toutes les instructions permettant de rvler lanneau sous-jacent doivent donc tre captures et rinterprtes. Cest une nouvelle baisse de performance.
La virtualisation assiste par le matriel

Pour supprimer la ncessit dune surveillance continuelle des instructions par le logiciel de virtualisation, Intel et AMD ont propos dans leurs nouveaux processeurs une extension de lISA 7 . Dans ces instructions on trouve, chez Intel, VMENTRY et VMLOAD, VMCALL chez AMD. Un nouvel anneau est ajout, ce qui porte leur nombre 5. Le logiciel de virtualisation (le systme dexploitation de lhte) sexcute dans ce nouvel anneau particulier, puis laide des nouvelles instructions, charge le contexte dexcution dun systme invit, bascule dans le mode virtuel et prsente les quatre anneaux classiques dexcution ce systme invit. La gure 9.10 prsente cette dmarche. Le gain que procure cette technique est trs consquent puisque le systme de contrle de lhte na plus besoin dexaminer chaque instruction pour vrier quelle ne fait pas appel au mode privilgi. Qui plus est, les systmes dexploitation invits ont lillusion de sexcuter en mode noyau, i.e. dans lanneau de niveau 0. La gestion de la mmoire virtuelle est elle aussi adresse par ce nouvel ISA. En rgle gnrale, un systme dexploitation utilise la virtualisation de la mmoire (voir le chapitre 6). Ceci devrait a priori nous rassurer et nous pourrions penser de prime abord que le logiciel de virtualisation naura aucun effort supplmentaire fournir pour que les systmes invits accdent la mmoire. Cependant nous avons dj voqu le fait quun systme dexploitation charge gnralement, voire toujours, son noyau partir de ladresse virtuelle la plus basse, i.e. celle dadresse nulle. Cela signie que
6. Le mcanisme permettant dintercepter les instructions fautives est souvent not trap and emulate virtualization . 7. Ils ont aussi propos une nouvelle structure de contrle, appele VMCS pour Virtual Machine Control Structure chez Intel et VMCB pour Virtual Machine Control Block chez AMD.

224

9.2. Les diffrentes solutions

F IGURE 9.10 Un mode spcial, parfois not anneau -1, permet lexcution du systme hte. Par le biais dinstructions spciques, on entre dans un mode moins privilgi et on charge les diffrentes composantes lies au systme invit qui va sexcuter. Ce mode moins privilgi prsente toutefois au systme invit quatre anneaux de virtualisation ce qui vite le mcanisme du trap and emulate. La demande dexcution dune instruction de lanneau 0 dclenchera alors une interruption matrielle qui sera gre par le systme hte aprs avoir provoqu la sortie du mode virtuel.

si rien nest fait, tous les systmes invits vont adresser directement la MMU de lhte en demandant la correspondance de ladresse 0x0 dans la mmoire physique et cette correspondance tant bijective, les problmes vont commencer. . . Il est donc ncessaire que le logiciel de virtualisation bloque tous les appels des systmes invits vers la MMU, rinterprte ces appels pour nalement interroger la MMU et retourner les adresses correctes. Cela se ralise gnralement par le biais dune table ombre des pages (traduction trs approximative de SPT ou Shadow Page Table !). La gure 9.11 dcrit ce fonctionnement. Ce type de fonctionnement est complexe puisquil ncessite une double indirection et donc plus de calculs. De plus, lorsque le logiciel de virtualisation donne accs au CPU un systme invit il doit aussi charger la table ombre associe. Tout cela peut ralentir fortement les systmes invits si ces derniers font trs rgulirement des accs la mmoire. An de palier cette gestion complexe par le logiciel de virtualisation, Intel et AMD ont mis respectivement en place les EPT ( Extended Page Tables ) et les NPT ( Nested Page Tables ). Il sagit ni plus ni moins de faire raliser par le matriel une gestion qui tait jusqu prsent dvolue au logiciel de virtualisation. La mmoire ddie auparavant pour le stockage des SPT est ainsi disponible pour le systme. Lorsquun systme invit requiert un accs la mmoire, celui-ci est gr au travers de ces tables 225

Chapitre 9. La virtualisation des systmes dexploitation

F IGURE 9.11 Chaque systme invit place son noyau ladresse 0x0. Il est donc indispensable que lhte dtecte les accs la mmoire physique dun invit et traduise au moyen dune table ombre des pages (associ cet invit) ces accs vers la mmoire physique de la machine relle.

et tous les dfauts de page 8 levs par lhte pourront tre transmis au systme invit. Ceci est une consquence trs bnque puisquelle permet de se dpartir dun nombre consquent de VmEnter / VMExit lis la gestion de la mmoire. Dautres amliorations matrielles ont permis une meilleure isolation des diffrentes machines virtuelles, notamment sur la gestion des priphriques et le DMA ( Direct Memory Access ). Le lecteur curieux trouvera sur la toile de nombreux articles traitant de ces techniques que nous naborderons pas dans le cadre de ce cours. Toutes ces amliorations signent-elles le dclin de la para-virtualisation ? Examinons cette technique avant de nous prononcer !
La para-virtualisation

La para-virtualisation essaye de rpondre la virtualisation en modiant les systmes dexploitation invits pour les faire dialoguer avec un hyperviseur qui rglera les accs aux matriels. Ce faisant, cette technique prsente lavantage majeure doptimiser totalement le dialogue entre les systmes invits (modis) et le matriel et doit donc offrir dexcellentes performances. Linconvnient de cette technique rside essentiellement dans les modications quil est ncessaire dapporter aux systmes dexploitations que lon dsire inviter. Cela signie que les diffrents appels systme
8. Rappel : les dfauts de page sont des interruptions qui signalent que laccs la mmoire a chou et que le systme dexploitation doit intervenir pour traiter cette interruption, soit pour fournir de nouvelles pages, soit pour tuer un processus ralisant une lecture / criture interdite.

226

9.2. Les diffrentes solutions

F IGURE 9.12 On parle trs souvent dhyperviseur de machines virtuelles pour qualier la couche intercale entre les systmes para-virtualiss et le matriel. Remarquons qu la diffrence des autres techniques de virtualisation il ny a ni systme dexploitation ni logiciel de virtualisation sur le socle. Cest un des atouts majeurs de la para-virtualisation

doivent tre remplacs par ceux intgrs dans une API ( Advanced Program Interface ou interface de programmation avance). Ces nouveaux appels sont parfois dnomms hypercall. Deux acteurs dominent le march de la para-virtualisation, Citrix avec Xen (XenServer est une solution libre et gratuite, mais il existe des solutions payantes telles que XenServer Advanced Edition. . . ) et Microsoft avec Hyper-V (solution ferme et payante). Notons au passage que lAPI dHyper-V ncessaire aux systmes invits est passe sous licence GPL 9 an de pouvoir tre intgre dans des systmes de type Unix / Linux. Un des inconvnients majeures de la para-virtualisation est la mise au point de diffrents drivers matriels permettant de construire la bibliothque dappels dun matriel spcique tel quun contrleur SCSI ou une carte vido, et son intgration dans le systme dexploitation invit. Mais ce pr-requis tend naturellement disparatre avec les nouveaux ISA puisque les appels au systme des invits sont maintenant associs un mode particulier du processeur la diffrence des instructions ralises par lhyperviseur. Il nest plus ncessaire de les traduire, que ce soit dans le logiciel de virtualisation (comme nous lavons vu prcdemment) ou dans lhyperviseur. La diffrence entre para-virtualisation et virtualisation complte se rduit donc peu peu.

9. Il sagit de la GNU Public Licence trs connue du logiciel libre.

227

Chapitre 9. La virtualisation des systmes dexploitation

9.3

Conclusion

Nous conclurons en largissant ce que nous venons de voir au domaine de la scurit des techniques de virtualisation. Lintrt de lutilisateur (particulier, entreprise) est dviter quune attaque unique sur lhte mette mal lintgralit des systmes invits et de faon duale cest sur ce point que se concentrera un pirate informatique an de rduire dune part la masse de systmes analyser pour raliser une compromission (les systmes invits peuvent tre relativement disparates ce qui dans le cas prsent est une qualit !). Les diffrentes techniques ne se comportent pas de la mme faon vis vis de ces attaques (nous voquerons principalement le DoS ou Deny of Service). Un isolateur est avant tout un systme dexploitation classique et possde ncessairement un point dentre pour permettre ladministration des systmes emprisonns. ce titre il est particulirement vulnrable une attaque par saturation de ses connexions rseau ainsi quaux failles de scurit du systme dexploitation de lhte et des services qui sy trouvent (ssh, dns, . . . ). La virtualisation complte, la virtualisation assiste par le matriel et la paravirtualisation noffrent pas daccs direct au socle. Ceci est particulirement vrai pour la para-virtualisation puisque lhyperviseur nest pas accessible, ce nest quune instance virtuelle particulre (appel le domaine 0) qui permet la gestion des autres instances. De la mme faon la virtualisation complte, mme si elle sappuie sur un systme dexploitation, nest pas directement accessible. Provoquer un dni de service du domaine 0 (dans le cas de Xen) ou de linterface dadministration (pour Vmware) ne permet pas de compromettre les machines virtuelles. Lautre point crucial est lisolement entre les diffrentes machines virtuelles. Si lon ne prend pas certaines prcautions, sortir dune prison est relativement simple (il suft de crer un priphrique disque dur identique au disque de lhte et de monter ce disque dur pour accder lintgralit du monde extrieur !). Un certain nombre de failles de scurit ont t trouves tant dans le domaine de la para-virtualisation (possibilit de schapper dune machine virtuelle pour aller sur lhyperviseur ou lunit de gestion des systmes invits) que de la virtualisation (utilisation de certains outils installs dans les systmes invits ou contournement des scurits des outils dadministration). Mais ces failles tendent devenir de moins en moins nombreuses avec lutilisation des technologies matrielles fournies avec les nouveaux processeurs. Il faut cependant garder lesprit que la compromission dun socle donne la main sur toutes les machines virtuelles et quil semble donc naturel dadopter les pratiques usuelles de sparation des services (web dun ct, messagerie de lautre, . . . ) utilises anciennement sur les machines physiques sur les machines virtuelles.

228

Deuxime partie Programmation systme en C sous Unix

10
Les processus sous Unix

10.1

Introduction

Les ordinateurs dots de systmes dexploitation modernes peuvent excuter en mme temps plusieurs programmes pour plusieurs utilisateurs diffrents : ces machines sont dites multi-tches (voir chapitre sur les processus) et multi-utilisateurs. Un processus (ou process en anglais) est un ensemble dinstructions se droulant squentiellement sur le processeur : par exemple, un de vos programmes en C ou le terminal de commande (shell en anglais) qui interprte les commandes entres au clavier. Sous Unix, la commande ps permet de voir la liste des processus existant sur une machine : ps -xu donne la liste des processus que vous avez lanc sur la machine, avec un certain nombre dinformations sur ces processus. En particulier, la premire colonne donne le nom de lutilisateur ayant lanc le processus et la dernire le contenu du tableau argv du processus. ps -axu donne la liste de tout les processus lancs sur la machine. Chaque processus, sa cration, se voit attribuer un numro didentication (le pid). Cest ce numro qui est utilis ensuite pour dsigner un processus. Ce numro se trouve dans la deuxime colonne de la sortie de la commande ps -axu. Un processus ne peut tre cr qu partir dun autre processus (sauf le premier, init, qui est cr par le systme au dmarrage de la machine). Chaque processus a donc un ou plusieurs ls, et un pre, ce qui cre une structure arborescente. noter que certains systmes dexploitation peuvent utiliser des processus pour leur gestion interne, dans ce cas le pid du processus init (le premier processus en mode utilisateur) sera suprieur 1.

10.2

Les entres / sorties en ligne

La faon la plus simple de communiquer avec un processus consiste passer des paramtres sur la ligne de commande au moment o lon excute le programme associ. Par exemple, lorsque lon tape des commandes comme ps -aux ou ls -alF, les paramtres en ligne -aux et -alF sont respectivement transmis aux programmes 231

Chapitre 10. Les processus sous Unix


ps et ls. Ce principe est trs gnral et peut stendre tout type et tout nombre de paramtres : xeyes -fg red -bg gray -center yellow -geometry 300x300

la n de son excution, chaque processus renvoie une valeur de retour qui permet notamment de dterminer si le programme a t excut correctement ou non. Cette valeur de retour, que lon nomme parfois pas abus de langage le code derreur, est ncessairement un entier et, par convention, une valeur nulle de retour indique que lexcution du programme a t conforme ce qui tait attendu. La valeur de retour dun programme donn (par exemple ls) est transmise au programme appelant qui a dclench lexcution du programme appel (ls). En gnral, le programme appelant est un terminal de commande (un shell en anglais), mais cela peut tre un autre programme comme nous le verrons plus tard. Dans le cas du terminal de commande, il est trs simple de visualiser la valeur de retour grce la variable 1 status (via la commande qui est note echo $? en bash et echo $status en csh), comme le montre lexemple ci-dessous :
menthe22> cd /usr/games/ menthe22> ls banner netrek.paradise christminster paradise.sndsrv.linux fortune pinfo menthe22> ls banner banner menthe22> echo $? 0 menthe22> ls fichier_qui_n_existe_pas ls: fichier_qui_n_existe_pas: No such menthe22> echo $? 1

runzcode sample.xtrekrc scottfree

trojka xzip

file or directory

Lorsque les programmes concerns sont crits en C, les entres / sorties en ligne seffectuent respectivement via les fonctions main() et exit() que nous allons dcrire dans la suite du chapitre. Attention, ce type dentres / sorties peut passer au premier abord comme une simple fonctionnalit du langage C, mais nous verrons dans le chapitre suivant quelles retent la faon dont le systme dexploitation Unix cre des processus.
La fonction main()

La fonction main() est la fonction appele par le systme 2 aprs quil a charg le programme en mmoire : cest le point dentre du programme. Elle se dclare de la manire suivante :
1. Un terminal de commande utilise pour son fonctionnement un certain nombre de variables et il est capable dinterprter un grand nombre de commande qui lui sont propres (comme history, par exemple), cest--dire qui ne sont pas des programmes indpendants. Ceci permet notamment de programmer les terminaux de commande partir de scripts appels shell scripts. Lutilisation en dtail de ces commandes et de ces variables peut tre trouve dans le manuel en ligne du terminal (man csh ou man bash par exemple) et na que peu dintrt ici. Il faut simplement retenir quil est possible daccder la valeur de retour du programme excut. 2. Le systme appelle en fait une autre fonction qui elle-mme appellera la fonction main().

232

10.2. Les entres / sorties en ligne

int main(int argc, char *argv[])

La ligne ci-dessus reprsente le prototype de la fonction main(), cest--dire la dclaration : du nombre darguments quadmet la fonction ; du type de ces arguments ; du type de la valeur de retour que renvoie la fonction. En loccurrence, le prototype de la fonction main est impos par le systme (et par le langage C) et il nest donc pas possible den utiliser un autre : la fonction main prend ncessairement deux arguments, un de type int et un de type char **, et elle retourne ncessairement un entier. La valeur de retour de la fonction main est renvoye grce un appel la fonction exit() (qui est aborde plus loin). Mme sil est possible dutiliser linstruction return cet effet, il est demand par convention de nutiliser que la fonction exit() (linstruction return tant utilise pour transmettre les valeurs de retour des fonctions autre que la fonction main()). La fonction main() accepte deux prototypes diffrents, selon que lon souhaite ou non passer des arguments :
int main(int argc, char **argv) ... int main(void)

Les noms des variables utilises dans le premier cas ne sont pas imposes par le systme ou le langage. Il est donc possible de choisir dautres noms que argc et argv. Nanmoins, ces noms sont systmatiquement utiliss par convention et ils permettent de lire facilement un programme en C en sachant quoi ils font rfrence (en loccurrence aux paramtres de la ligne de commande). La variable argc contient le nombre de paramtres passs sur la ligne de commande, sachant que le nom du programme compte comme un paramtre. Par exemple, si lon tape la commande ./a.out param dans un terminal de commande, la variable argc du programme a.out vaudra 2. Si on tape la commande ./a.out param1 param2, la variable argc vaudra 3. La variable argv est un tableau de chanes de caractres contenant les paramtres passs au programme. La taille du tableau argv est donne par la variable argc. Par exemple, si lon tape la commande ./a.out param dans un terminal de commande, la variable argc vaudra 2, argv[0] contiendra la chane de caractres "./a.out" (notez la prsence des guillemets signiant quil sagit dune chane de caractres) et argv[1] contiendra la chane de caractres "param". Si on tape la commande ./a.out param1 param2, la variable argc vaudra 3, argv[0] contiendra la chane de caractres "./a.out", argv[1] contiendra la chane de caractres "param1" et argv[2] contiendra la chane de caractres "param2". Lors du passage de paramtres en ligne, lutilisateur (i.e. le code du programme appel) na rien faire avant dutiliser les variables argc et argv : cest le programme 233

Chapitre 10. Les processus sous Unix appelant (en gnral, le terminal de commande) qui va recopier chaque paramtre de la ligne de commande dans un lment du tableau argv avant dexcuter le programme. Par exemple, lorsquon tape ./a.out param dans un terminal de commande, cest ce terminal qui va recopier les paramtres "./a.out" et "param" dans la variable argv du programme a.out. Insistons sur le fait que les paramtres passs sur la ligne de commande sont de type char *, ce sont donc des chanes de caractres (puisque argv est un tableau de chanes de caractres). Cela signie en particulier quil nest pas possible de passer directement un paramtre numrique sur la ligne de commande : celui-ci sera transform en chane de caractres et il faudra effectuer la transformation inverse dans le programme (diffrence entre la chane de caractres "2", le caractre 2 et lentier 2). Pour le terminal de commande, les paramtres sont des mots, cest--dire des groupes de caractres spars par des espaces. Il est cependant possible davoir des espaces dans un mot : en utilisant les caractres \ ou ". Par exemple, dans ./a.out param1 param2, a.out verra trois paramtres alors quil nen verra que deux dans ./a.out "param1 param2". Voici un petit programme dexemple (que nous appellerons ex11) qui se contente dafcher les valeurs de argc et argv : Listing 10.1 Afchage des arguments et de leur nombre
#include <stdlib.h> #include <stdio.h> int main(int argc, char *argv[]) { int i; printf("nombre darguments: %d\n", argc); for (i=0; i<argc; i++) { printf("argument %d: <%s>\n", i, argv[i]); } printf("\n"); exit(EXIT_SUCCESS); }

Lexcution donne :
menthe22> ./ex11 nombre darguments: 1 argument 0: <./ex11> menthe22> ./ex11 arg1 nombre darguments: 2 argument 0: <./ex11> argument 1: <arg1> menthe22> ./ex11 arg1 arg2 nombre darguments: 3 argument 0: <./ex11> argument 1: <arg1> argument 2: <arg2> menthe22> ./ex11 plein de parametres en plus

234

10.3. Les fonctions didentication des processus

nombre darguments: 6 argument 0: <./ex11> argument 1: <plein> argument 2: <de> argument 3: <parametres> argument 4: <en> argument 5: <plus> menthe22> ./ex11 "plein de parametres en plus" nombre darguments: 2 argument 0: <./ex11> argument 1: <plein de parametres en plus> menthe22> ./ex11 plein de "parametres en" plus nombre darguments: 5 argument 0: <./ex11> argument 1: <plein> argument 2: <de> argument 3: <parametres en> argument 4: <plus>

La manire dont les paramtres sont passs du programme appelant (ici le shell) au programme appel sera vu plus en dtail au cours du TP Recouvrement de processus sous Unix.
La fonction exit()

Les paramtres de la fonction main() permettent au programme appelant de passer des paramtres au programme appel. La fonction exit() permet au programme appel de retourner un paramtre au programme appelant. La fonction exit() termine le programme et prend comme paramtre un entier sign qui pourra tre lu par le programme appelant. La fonction exit() ne retourne donc jamais et toutes les lignes du programme situes aprs la fonction exit() ne servent donc rien. Traditionnellement, un exit(0) signie que le programme sest excut sans erreur, alors quune valeur non-nulle signie autre chose (par exemple quune erreur est survenue). Deux valeurs sont dnies dans /usr/include/stdlib.h : EXIT_SUCCESS qui vaut 0 ; EXIT_FAILURE qui vaut 1. Diffrentes valeurs peuvent dsigner diffrents types derreurs (voir le chier /usr/ include/sysexits.h).

10.3

Les fonctions didentication des processus

Ces fonctions sont au nombre de deux : pid_t getpid(void) Cette fonction retourne le pid du processus. pid_t getppid(void) Cette fonction retourne le pid du processus pre. Si le processus pre nexiste plus (parce quil sest termin avant le processus ls, par exemple), la valeur 235

Chapitre 10. Les processus sous Unix retourne est celle qui correspond au processus init (en gnral 1), anctre de tous les autres et qui ne se termine jamais. Le type pid_t est en fait quivalent un type int et a t dni dans un chier den-tte par un appel typedef. Le programme suivant afche le pid du processus cr et celui de son pre : Listing 10.2 pid du processus et de son pre
#include #include #include #include <sys/types.h> <unistd.h> <stdio.h> <stdlib.h>

int main(int argc, char *argv[]) { printf("mon pid est : %d\n", (int)getpid()); printf("le pid de mon pere est : %d\n", (int)getppid()); exit(EXIT_SUCCESS); }

La commande echo $$ tape au clavier retourne le pid du shell interprtant cette commande. Le programme suivant peut donc donner le rsultat suivant (les numros de processus tant uniques, deux excutions successives ne donneront pas le mme rsultat) :
menthe22>echo $$ 189 menthe22>./ex21 mon pid est : 3162 le pid de mon pere est : 189

10.4

Exercices

Question 1 crire une application qui afche les paramtres passs sur la ligne de commandes et qui retourne le nombre de ces paramtres. Vrier le rsultat grce la variable du shell qui stocke la valeur de retour (status en csh, ? en bash). Question 2 crire une application qui additionne les nombres placs sur la ligne de commande et qui afche le rsultat. On nommera ce programme addition. Question 3 crire un programme afchant son pid et celui de son pre. On nommera ce programme identite. Question 4 Reprendre le programme identite et ajouter un appel la fonction sleep() pour attendre 10 secondes avant dexcuter les appels getpid() et getppid(). unsigned int sleep(unsigned int s) suspend lexcution du programme pendant s secondes. 236

10.4. Exercices Vrier que le terminal de commande o le programme est lanc est bien le pre du processus correspondant. Relancer ensuite le programme, mais en tche de fond et en redirigeant la sortie dans un chier :
./exo >sortie &

et fermer la fentre en tapant exit. Attendre 10 secondes et regarder le contenu du chier contenant la sortie du programme. Remarques ?

237

Chapitre 10. Les processus sous Unix

10.5

Corrigs
Listing 10.3 Solution de la question 1

#include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { int i, s; s=0; for (i=1; i<argc; i++) { s = s + atoi(argv[i]); } printf("la somme est: %d\n", s); exit(EXIT_SUCCESS); }

Listing 10.4 Solution de la question 2


#include <unistd.h> #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { sleep(10); printf("mon pid est: %d\n", getpid()); printf("le pid de mon pere est: %d\n", getppid()); exit(EXIT_SUCCESS); }

Lorsque le pre nexiste plus (aprs avoir tap exit() dans le shell), la fonction getppid() retourne 1.

238

10.6. Corrections dtailles

10.6

Corrections dtailles

Comme cela est rappel au dbut du chapitre, les entres les plus simples que lon puisse faire dans un programme consistent passer des arguments sur la ligne de commandes . La ligne de commande est tout simplement lensemble des caractres que lon tape dans une console, i.e. un terminal ou encore une xterm. La ligne de commandes est gre par le shell, cest--dire linterprte de commandes. Comme son nom lindique il interprte les caractres taps avant de les transmettre la commande invoque. Vous avez tous dj remarqu que lorsque lon tape :
menthe22> ls *.tif toto1.tiff toto2.tif toto3.tif menthe22>

linterprte sest charg dinterprter le caractre * pour ensuite appeler la commande ls avec les arguments toto1.tif, toto2.tif et toto3.tif. Nous allons donc essayer de constater cela avec le premier programme.
Arguments transmis au programme

Le programme fait appel deux fonctions printf() dont le prototype est dans stdio.h et exit() dont le prototype est dans stdlib.h, ce qui explique les deux directives dinclusions. Listing 10.5 Afchage des arguments passs en ligne de commandes
#include <stdlib.h> #include <stdio.h> int main(int argc, char *argv[]) { int i ; printf("nombre darguments : %d\n", argc) ; for (i=0 ; i<argc ; i++) printf("argument %d : <%s>\n", i, argv[i]) ; printf("\n") ; exit(argc) ; }

Nous compilons ce programme pour obtenir lexcutable showarg que nous allons essayer sur le champ :
menthe22> showarg *.tif nombre darguments : 4 argument 0 : showarg argument 1 : toto1.tif argument 2 : toto2.tif argument 3 : toto3.tif menthe22>

239

Chapitre 10. Les processus sous Unix Linterprte de commandes a remplac le mtacaractre par la liste des chiers correspondant et ce nest pas 2 arguments qua reus le programme (showarg et *.tif) mais bel et bien 4. Nous pouvons alors demander linterprte de ne pas jouer son rle et de ne rien interprter en protgeant le caractre spcial * grce lantislash :
menthe22> showarg \*.tif nombre darguments : 2 argument 0 : showarg argument 1 : *.tif menthe22>

Si lon veut maintenant regarder comment sont construits les arguments, nous voyons que cest lespace qui sert de sparateur entre les mots de la ligne de commandes. Prenons de nouveau un exemple :
menthe22> showarg le\ premier puis le\ deuxieme nombre darguments : 4 argument 0 : showarg argument 1 : le premier argument 2 : puis argument 3 : le deuxieme menthe22>

Comme on le remarque facilement, lespace sparant le mot le du mot premier est prcd dun antislash. Cet espace est donc protg et linterprte de commandes nessaye donc pas de linterprter comme un sparateur. Ainsi pour lui, le premier argument aprs le nom du programme est bel et bien le premier. Nous pouvons aussi protger les diffrents arguments de linterprtation du shell en les entourant par des guillemets, (i.e. des double quote ) :
menthe22> showarg "le premier" puis "le deuxieme" nombre darguments : 4 argument 0 : showarg argument 1 : le premier argument 2 : puis argument 3 : le deuxieme menthe22>

Le fait de passer comme premier argument de la liste le nom du programme peut sembler un peu futile, et pourtant cela permet parfois de runir plusieurs programmes en un seul ! Prenons lexemple suivant : Listing 10.6 Le premier argument est utile !
#include <stdlib.h> #include <stdio.h> #include <string.h> #include <unistd.h> int main(int argc, char *argv[]) { int i ; if (strstr(argv[0],"showarg")!=NULL) {

240

10.6. Corrections dtailles

for (i=0 ; i<argc ; i++) printf("argument %d : <%s>\n", i, argv[i]) ; printf("\n") ; } else if (strstr(argv[0],"countarg")!=NULL) { printf("nombre darguments : %d\n", argc) ; } exit(EXIT_SUCCESS) ; }

Dans ce programme on utilise la fonction strstr() pour savoir si argv[0] contient showarg ou countarg et ainsi dterminer sous quel nom le programme a t excut. Nous allons compiler ce programme deux fois avec deux sorties diffrentes (il existe une faon plus lgante 3 ) :
menthe22> gcc -Wall -o countarg multiprog.c menthe22> gcc -Wall -o showarg multiprog.c

Nous avons maintenant notre disposition deux programmes, issus du mme code source, dont le fonctionnement nest pas le mme.
menthe22> countarg "le premier" puis "le deuxieme" nombre darguments : 4 menthe22> showarg "le premier" puis "le deuxieme" argument 0 : showarg argument 1 : le premier argument 2 : puis argument 3 : le deuxieme menthe22>

Ce genre de procd est couramment utilis, sinon comment expliquer quun programme de compression de chier ait exactement la mme taille quun programme de dcompression :
menthe22> ls -l /bin/gzip /bin/gunzip -rwxr-xr-x 3 root root 47760 dec 7 1954 /bin/gunzip -rwxr-xr-x 3 root root 47760 dec 7 1954 /bin/gzip

Argument renvoy linterprte

De la mme manire que linterprte de commandes passe des arguments au programme, le programme annonce linterprte comment sest droule son excution. Cest le rle de lappel la fonction exit() qui permet de transmettre un nombre linterprte de commandes. Par convention sous Unix, lorsquun programme se termine de faon satisfaisante, i.e. il a russi faire ce quon lui demandait, ce programme transmet une valeur nulle, 0, linterprte. Selon linterprte, cette valeur est place dans une variable spciale appele status (csh, bash,. . . ) ou ? sous bash. Reprenons notre programme, il transmet linterprte le nombre darguments quil a reus (exit(argc)) :
3. La solution lgante est de crer un lien symbolique : gcc -Wall -o countarg multiprog.c puis
ln -s countarg showarg

241

Chapitre 10. Les processus sous Unix

menthe22> showarg *.tif nombre darguments : 4 argument 0 : showarg argument 1 : toto1.tif argument 2 : toto2.tif argument 3 : toto3.tif menthe22>echo $? 4 menthe22>

Essayons maintenant avec un autre programme bien connu, ls, en lui demandant de lister un chier inexistant :
menthe22> ls ~/corrige/pasmoi.c ls: ~/corrige/pasmoi.c: No such file or directory menthe22>echo $? 1 menthe22>

On constate que la commande ls a retourn la valeur 1 linterprte, ce qui signie quil y a eu une erreur. Ainsi mme si je suis incapable de lire ce quune commande afche, je peux toujours contrler par lintermdiaire de son code de retour si elle sest bien droule. Deux valeurs sont dnies par dfaut dans le chier stdlib.h :
#define EXIT_SUCCESS 0 #define EXIT_FAILURE 1

Ceci tant rien ne vous empche de dnir vos propres codes derreur, mais gardez en tte quune excution sans problme doit retourner une valeur nulle linterprte. Il convient toutefois de faire attention ce que nous allons voir en examinant les quelques rsultats qui suivent. Prenons le programme trs simple suivant : Listing 10.7 Examiner le comportement de sortie
#include <unistd.h> #include <stdlib.h> int main(int argc, char *argv[]) { exit(argc>1?atoi(argv[1]):0) ; }

Ce programme retourne la valeur 0 si on ne lui donne aucun autre argument que le nom du programme et il retourne le deuxime argument converti en entier si cet argument existe. Une version moins dense pourrait tre : Listing 10.8 Version complte pour examiner la sortie
#include <unistd.h> #include <stdlib.h> int main(int argc, char *argv[]) {

242

10.6. Corrections dtailles

int val; if (argc > 1) { val = atoi(argv[1]); } else { val = 0; } exit(val) ; }

Examinons ce que linterprte rceptionne comme valeur de retour :


menthe22>./myexit menthe22>echo $? 0 menthe22>./myexit menthe22>echo $? 234 menthe22>./myexit menthe22>echo $? 0 menthe22>./myexit menthe22>echo $? 4 menthe22>./myexit menthe22>echo $? 250

234

256

260

-6

Le code de retour dun programme est en fait cod sur un seul octet et ne peut donc prendre que des valeurs comprises entre 0 et 255. Un code retour de -1 est donc quivalent un code de 255.
Addition de nombres en ligne

Le meilleur moyen de transmettre nimporte quel style darguments un programme quand on est un interprte, aussi bien des mots que des nombres entiers ou des rels, cest bel et bien de tout transmettre sous la forme de mots, savoir de chane de caractres. Rappelons brivement par un petit dessin (g. 10.6) les principales diffrences entre un caractre, un tableau de caractres reprsentant un mot et un nombre entier. Donc pour additionner les arguments de la ligne de commandes (sauf le premier qui sera le nom du programme) il est impratif de convertir ces mots en entiers. Cela nous oblige donc faire appel la fonction atoi() en remarquant toutefois dans la page de man que cette fonction ne dtecte pas les erreurs. Le programme aura la structure suivante : Listing 10.9 Addition des arguments
#include <stdlib.h> #include <stdio.h> int main(int argc, char *argv[]) { int i ; int tmp; int s ;

243

Chapitre 10. Les processus sous Unix

F IGURE 10.1 Un caractre, un tableau de caractres, un mot, le caractre 8, le mot 256 et un entier valant 256. Le caractre consiste en un seul octet dans la mmoire, peu importe ce quil y a aprs ou avant. Il en va de mme pour le tableau de caractres, peu importe ce qui prcde les 4 caractres tata ou ce qui suit. Le troisime schma correspond par contre un tableau reprsentant un mot et on distingue clairement la diffrence : la prsence dun caractre un peu spcial, \0, qui permet de faire savoir des fonctions comme printf() ou encore strlen() o sarrte le mot. Les trois derniers schmas insistent sur la diffrence entre le caractre 8 qui est un symbole de notre alphabet, le mot 256 et le nombre 256 qui est gal 28 et qui est donc reprsent sur 4 octets par la suite 0100 ( lire avec loctet de poids faible gauche !).

for (i=1,s=0;i<argc;i++) { s += (tmp = atoi(argv[i])); if (i<argc-1) printf("%d + ",tmp) ; else printf("%d = ",tmp) ; } printf("%d\n",s) ; exit(EXIT_SUCCESS) ; }

Son excution dans un contexte normal donne :


menthe22>addarg 34 56 78 34 + 56 + 78 = 168

Dans un contexte moins glorieux nous pourrions avoir des surprises :


menthe22>addarg 10 10.4 10.1 10 + 10 + 10 = 30

Enn si nous poussons le programme dans ses retranchements :


menthe22>addarg azeze erer 45 0 + 0 + 45 = 45

244

11
Les entres / sorties sous Unix

Dans cette partie, nous dcrivons les outils que le noyau du systme dexploitation met la disposition de lutilisateur pour manipuler des chiers. Pour utiliser un service pris en charge par le noyau du systme dexploitation, lutilisateur doit utiliser un appel systme et il est capital de bien distinguer les appels systme des fonctions de bibliothques. Nous parlerons ainsi des appels systmes associs lutilisation de chiers dans un premier temps. Puis les fonctions de la bibliothque standard dentres / sorties ayant des fonctionnalits comparables seront dcrites plus loin. Si lon se restreint aux appels systme, toutes les oprations dtailles sont des oprations bas niveau , adaptes certains types de manipulation de chiers, comme par exemple des critures brutales (i.e. sans formatage) de donnes stockes de faon contigu en mmoire. En marge de cette introduction, il est bon de mentionner une fonction qui nous sera particulirement utile dans lintgralit des travaux dirigs : perror(). La documentation peut tre consulte de la manire habituelle par man perror, mais en voici les grandes lignes.
void perror(const char *s);

Cette fonction afche sur la sortie standard derreur le message derreur consign par le systme dans une variable globale. Lorsquun appel au systme produit une erreur, le systme consigne un numro derreur dans une variable globale. Il existe un tableau associant ces numros un message (ce qui permet un afchage plus explicite que lerreur 12345 est survenue ). Si la chane de caractre s passe en paramtre perror() nest pas vide, perror() afche tout dabord la chane passe en paramtre, puis le symbole : et enn le message derreur consign par le systme. En considrant Le fragment de code qui suit :
... if ((fd = open(...)) == -1) { perror("Mon prog sarrete la!"); exit(EXIT_FAILURE); } ...

245

Chapitre 11. Les entres / sorties sous Unix un programme lutilisant et rvlant une erreur donnerait lafchage suivant :
moi@moi> ./monprogramme Mon prog sarrete la: file not found moi@moi>

Lutilisation de perror() est utile pour rvler la raison dun chec lors dun appel systme et bien souvent les pages de manuel offriront une ligne consacre son utilisation comme ici lextrait du manuel de open() : If successful, open() returns a non-negative integer, termed a le descriptor. It returns -1 on failure, and sets errno to indicate the error. La variable globale consignant le numro de lerreur est errno. On comprend donc que toute erreur provoque par une mauvaise utilisation de la fonction open() pourra tre afche sur la sortie standard derreur en utilisant perror().

11.1

Les descripteurs de chiers

Historiquement, les appels systme associs aux chiers taient ddis la communication de lordinateur avec ses priphriques (cran, clavier, disque, imprimante, etc.). Sur les systmes modernes, les diffrents priphriques sont grs par le systme lui-mme, qui fournit lutilisateur une interface abstraite, un descripteur de chier (le descriptor en anglais) pour accder aux chiers. Cette interface se prsente pour lutilisateur sous la forme dun simple entier : le systme dexploitation tient en fait jour une table 1 , appele table des chiers ouverts, o sont rfrencs tous les chiers utiliss, cest--dire tous les chiers en train dtre manipuls par un processus (cration, criture, lecture), et lentier mis disposition de lutilisateur correspond au numro de la ligne de la table faisant rfrence au chier concern. Le mot chier ne doit pas tre compris ici au sens chier sur le disque dur , mais comme une entit pouvant contenir ou transmettre des donnes. Un descripteur de chier peut aussi bien faire rfrence un chier du disque dur, un terminal, une connexion rseau ou un lecteur de bande magntique. Un certain nombre doprations gnriques sont dnies sur les descripteurs de chier, qui sont ensuite traduites par le systme en fonction du priphrique auquel se rapporte ce descripteur. Ainsi, crire une chane de caractres lcran ou dans un chier se fera pour lutilisateur de la mme manire. Dautres oprations sont spciques au type de descripteur de chier.

11.2

Les appels systme associs aux descripteurs de chier

Les dclarations des appels systme dcrits dans cette section se trouvent dans les chiers suivants :
1. En pratique, le systme utilise une table par processus.

246

11.2. Les appels systme associs aux descripteurs de chier


/usr/include/unistd.h /usr/include/sys/types.h /usr/include/sys/stat.h /usr/include/fcntl.h

Pour utiliser ces appels systme dans un programme C, il est donc ncessaire dinclure ces chiers en utilisant la directive #include (notez lutilisation des caractres < et > qui indique au compilateur que ces chiers se trouvent dans /usr/include) : Listing 11.1 Inclusion des chiers de dclaration
#include #include #include #include <unistd.h> <sys/types.h> <sys/stat.h> <fcntl.h>

int main(int argc, char *argv[]) { ... exit(EXIT_SUCCESS); }

Lappel systme open()

Lappel systme open() permet dassocier un descripteur de chier un chier que lon souhaite manipuler. En cas de succs, le systme dexploitation va crer une rfrence dans la table des chiers et va indiquer lentier correspondant. Une fois rfrenc dans la table des chiers, le chier est dit ouvert . Lappel systme open() sutilise de la manire suivante :
int open(const char *path, int flags, mode_t mode)

En cas de succs, lappel open() retourne un descripteur de chier qui pourra tre ensuite utilis dans le programme an de dsigner le chier frachement ouvert. En cas derreur, par exemple lorsque le chier dsign nexiste pas, open() retourne -1. Le paramtre path est une chane de caractres donnant le chemin daccs du chier. Le mot cl const prcise que la valeur pointe par le pointeur path ne sera pas modie par la fonction open() (ce qui serait possible, puisque cest un pointeur !). Pour nos travaux pratiques, path dsignera le plus souvent un nom de chier du disque dur, par exemple "/etc/motd" ou "/home/h82/in201/ex11.c". Le paramtre flags dtermine de quelle faon le chier va tre ouvert, cest--dire quels types doprations vont tre appliques ce chier. chaque type dopration correspond un entier (flags est de type int) et, an de rendre les programmes plus 247

Chapitre 11. Les entres / sorties sous Unix lisibles et plus faciles porter dun systme lautre, des constantes 2 sont utilises pour symboliser ces valeurs entires : O_RDONLY pour ouvrir un chier en lecture seule ; O_WRONLY pour ouvrir un chier en criture seule ; O_RDWR pour ouvrir un chier en lecture/criture ; O_APPEND pour ne pas craser un chier existant et placer les donnes qui seront ajoutes seront places la n de celui-ci ; O_CREAT pour crer le chier sil nexiste pas. Seules les principales valeurs sont dcrites ci-dessus et un utilisateur dsireux den savoir plus doit se rfrer la 2e section du manuel en ligne (man 2 open). La valeur de flags peut aussi tre une combinaison des valeurs ci-dessus (par la fonction ou bit bit |). Par exemple, O_RDWR | O_CREAT qui permet douvrir un chier en criture et en lecture en demandant sa cration sil nexiste pas. Le paramtre mode nest utilis que si le drapeau O_CREAT est prsent. Dans ce casl, mode indique les permissions 3 du chier cr : les 3 derniers chiffres reprsentent les permissions pour (de gauche droite) lutilisateur, le groupe et les autres. 4 reprsente un chier en mode lecture, 6 en lecture-criture et 7 en lecture-criture-excution. Le mode 0644 indique donc que le chier sera accessible en lecture-criture pour le propritaire et en lecture seule pour le groupe et les autres. Cette faon de dsigner les permissions des chiers est rapprocher de lutilisation de la commande chmod et des indications donnes par la commande ls -l. Notons aussi que les permissions attribues par dfaut un chier nouvellement cr peuvent tre directement modies par un masque (umask), lui-mme dni par lappel systme umask(). Lexemple ci-dessous montre comment utiliser lappel systme open(). Prcisons quil est toujours trs important de tester les valeurs de retour des appels systmes avant de continuer ! Listing 11.2 Ouverture de chiers par open()
#include #include #include #include #include #include <stdlib.h> <stdio.h> <unistd.h> <sys/types.h> <sys/stat.h> <fcntl.h>

int main(int argc, char *argv[]) { int f;

2. Ces constantes sont dnies dans un des chiers den-tte du systme (dans le chier /usr/include/
asm/fcntl.h pour les stations Unix) grce la commande #define.

3. Les permissions sont reprsentes par une valeur en octal (en base huit) code sur 4 chiffres. Le premier chiffre permet, dans le cas dun chier excutable (ou dun rpertoire), de placer lidentit de lutilisateur qui excute ce chier celle du chier (4), ou encore de changer son groupe (2). Il sert aussi signier au systme de garder le chier excutable en mmoire aprs son excution. Cette dernire ressource nest plus trs employe sur les chiers. Toutefois elle prsente un grand intrt sur les rpertoires puisquelle permet par exemple dautoriser quiconque crire dans le rpertoire /tmp mais de ne pouvoir effacer que ses propres chiers.

248

11.2. Les appels systme associs aux descripteurs de chier

f = open("/etc/passwd",O_RDONLY, NULL); if (f==-1) { printf("le fichier na pu etre ouvert\n"); exit(EXIT_FAILURE); } printf("le fichier a ete correctement ouvert\n"); /* * on utilise le fichier * ..... * puis ferme le fichier * (cf exemples suivants) */ exit(EXIT_SUCCESS); }

Lappel systme read()

Cet appel systme permet de lire des donnes dans un chier pralablement ouvert via lappel systme open(), cest--dire dans un chier reprsent par un descripteur. Lappel systme read() sutilise de la manire suivante :
int read(int fd, void *buf, size_t nbytes)

Le paramtre fd indique le descripteur de chier concern, cest--dire le chier dans lequel les donnes vont tre lues. Ce descripteur de chier a t en gnral renvoy par open() lorsque lon a procd louverture du chier. Le paramtre buf dsigne une zone mmoire dans laquelle les donnes lues vont tre stockes. Cette zone mmoire doit tre au pralable alloue soit de faon statique (via un tableau par exemple), soit de faon dynamique (via un appel la fonction malloc() par exemple). Le fait que buf soit un pointeur de type void * nimplique pas obligatoirement que les donnes soient sans type : cela signie simplement que les donnes seront lues octet par octet, sans interprtation ni formatage. Cela permet galement de relire des donnes (typiquement binaires) directement dans un conteneur (structure ou tableau) de mme type sans avoir faire de typage explicite (cast). Le paramtre nbytes indique le nombre doctets (et non le nombre de donnes) que lappel read() va essayer de lire (il se peut quil y ait moins de donnes que le nombre spci par nbytes). Le type size_t est en fait quivalent un type int et a t dni dans un chier den-tte par un appel typedef. La valeur retourne par read() est le nombre doctets effectivement lus. la n du chier (cest--dire quand il ny a plus de donnes disponibles), 0 est retourn. En cas derreur, -1 est retourn. Voici un exemple dutilisation lappel systme read() : Listing 11.3 Lecture avec read()
#include #include #include #include #include <stdlib.h> <stdio.h> <unistd.h> <sys/types.h> <sys/stat.h>

249

Chapitre 11. Les entres / sorties sous Unix

#include <fcntl.h> #define NB_CAR 50 int main(int argc, char *argv[]) { int f; int n; char *texte; f = open("/etc/passwd",O_RDONLY, NULL); if(f==-1) { printf("le fichier na pu etre ouvert\n"); exit(EXIT_FAILURE); } printf("le fichier a ete correctement ouvert\n"); texte = (char *)malloc(NB_CAR*sizeof(char)); if(texte == NULL) { printf("erreur dallocation memoire\n"); exit(EXIT_FAILURE); } n = read(f,texte, NB_CAR - 1); /* NB_CAR - 1 car il faut laisser de la place pour le caractere \0 ajoute en fin de chaine de caracteres */ if(n == -1) { printf("erreur de lecture du fichier\n"); exit(EXIT_FAILURE); } else if(n == 0) printf("tout le fichier a ete lu\n"); else printf("%d caracteres ont pu etre lus\n",n); exit(EXIT_SUCCESS); }

Lappel systme write()


int write(int fd, void *buf, size_t nbytes)

Lappel systme write() permet dcrire des donnes dans un chier reprsent par le descripteur de chier fd. Il sutilise de la mme faon que lappel systme read(). Le descripteur de chier fd a t en gnral renvoy par open() lorsque lon a procd louverture du chier. La valeur retourne est le nombre doctets effectivement crits. En cas derreur, -1 est retourn.
Lappel systme close()
int close(int fd)

Cet appel permet de fermer un chier prcdemment ouvert par lappel systme open(), cest--dire de supprimer la rfrence ce chier dans la table maintenue par 250

11.2. Les appels systme associs aux descripteurs de chier le systme (table des chiers ouverts). Cela indique ainsi au systme que le descripteur fd ne sera plus utilis par le programme et quil peut librer les ressources associes. En cas doubli, le systme dtruira de toutes faons en n de programme les ressources alloues pour ce chier. Il est nanmoins prfrable de fermer explicitement les chiers qui ne sont plus utiliss. Le programme ci-dessous prsente un exemple complet de programme utilisant les appels systme open(), read(), write() et close(). Ce programme est une version (trs) simplie de la commande cp qui permet de copier des chiers. Listing 11.4 Copie simplie de chiers
#include #include #include #include #include #include #include <stdlib.h> <stdio.h> <unistd.h> <sys/types.h> <sys/stat.h> <fcntl.h> <string.h>

#define NB_CAR 200 int main(int argc, char *argv[]) { int f; int n, m; char *texte; /* verification du nombre darguments */ if(argc < 3) { printf("pas assez darguments en ligne\n"); printf("usage : %s <fichier1> <fichier2>\n", argv[0]); exit(EXIT_FAILURE); } /* ouverture en lecture du premier fichier */ f = open(argv[1],O_RDONLY, NULL); if(f == -1) { printf("le fichier na pu etre ouvert\n"); exit(EXIT_FAILURE); } printf("le fichier a ete correctement ouvert\n"); /* allocation dune zone memoire pour la lecture */ texte = (char *)malloc(NB_CAR*sizeof(char)); if(texte == NULL) { printf("erreur dallocation memoire\n"); exit(EXIT_FAILURE); } /* lecture des caracteres */ n = read(f,texte, NB_CAR - 1); /* NB_CAR - 1 car il faut laisser de la place pour le caractere \0 ajoute en fin de chaine de caracteres */ if(n == -1) { printf("erreur de lecture du fichier\n"); exit(EXIT_FAILURE); }

251

Chapitre 11. Les entres / sorties sous Unix

else if(n == 0) printf("tout le fichier a ete lu\n"); else printf("%d caracteres ont pu etre lus\n",n); /* fermeture du premier fichier */ close(f); /* ouverture du second fichier */ f = open(argv[2], O_WRONLY | O_CREAT, 0644); if(f == -1) { printf("le fichier na pu etre ouvert\n"); exit(EXIT_FAILURE); } printf("le fichier a ete correctement ouvert\n"); /* ecriture des caracteres */ m = write(f, texte, strlen(texte)); if(m != n) { printf("Je nai pas ecrit le bon nombre de caracteres\n"); perror("Erreur "); } printf("%d caracteres ecrits\n",m); /* fermeture du second fichier */ close(f); exit(EXIT_SUCCESS); }

Les descripteurs de chier particuliers

Les descripteurs de chiers 0, 1 et 2 sont spciaux : ils reprsentent respectivement lentre standard, la sortie standard et la sortie derreur. Si le programme est lanc depuis un terminal de commande sans redirection, 0 est associ au clavier, 1 et 2 lcran. Ces descripteurs sont dnis de manire plus lisible dans le chier <unistd. h> : Listing 11.5 Dnition des descripteurs classiques de chiers
#define #define #define STDIN_FILENO STDOUT_FILENO STDERR_FILENO 0 1 2 /* standard input file descriptor */ /* standard output file descriptor */ /* standard error file descriptor */

Nous verrons dans les exercice sur les tuyaux quil est possible de rediriger les entres / sorties standard vers dautres chiers. Le programme suivant afche la chane bonjour lcran : Listing 11.6 Afcher bonjour !
#include <stdlib.h> #include <unistd.h> #include <string.h>

252

11.3. La bibliothque standard dentres / sorties

int main(int argc,char *argv[]) { char *s = "bonjour\n"; if(write(STDOUT_FILENO, s, strlen(s))==-1) exit(EXIT_FAILURE); else exit(EXIT_SUCCESS); }

11.3

La bibliothque standard dentres / sorties

Appel systme ou fonction de bibliothque ?

Les appels systme prsents prcdemment sont relativement lourds utiliser et, sils ne sont pas utiliss avec prcaution, ils peuvent conduire des programmes trs lents. Par exemple, un programme demandant 1000 fois de suite lcriture dun caractre lcran en utilisant lappel systme write() va provoquer 1000 interventions du noyau (donc 1000 passages en mode noyau et 1000 retours en mode utilisateur) et 1000 accs effectifs lcran... Des fonctions ont donc t crites en C an de pallier les inconvnients des appels systme. Ces fonctions, qui ont t standardises et regroupes dans la bibliothque standard dentres / sorties, utilisent bien entendu les appels systme dcrits ci-dessus pour accder aux chiers 4 et reprsentent, en quelque sorte, une surcouche plus fonctionnelle. Les fonctions dentres / sorties de la bibliothque standard ont les caractristiques suivantes : Les accs aux chiers sont asynchrones et bufferiss . Cela signie que les lectures ou les critures de donnes dans un chier nont pas lieu au moment o elles sont demandes, mais quelles sont dclenches ultrieurement. En fait, les fonctions de la bibliothque standard utilisent des zones de mmoire tampon (des buffers en anglais) pour viter de faire trop souvent appel au systme et elles vident ces tampons soit lorsque cela leur est demand explicitement, soit lorsquils sont pleins. Les accs aux chiers sont formats, ce qui signie que les donnes lues ou crites sont interprtes conformment un type de donnes (int, char, etc.). Ces fonctions sont particulirement indiques pour traiter un ot de donnes de type texte. Pour utiliser ces fonctions, il est indispensable dinclure le chier stdio.h dans le programme.
4. Il ny a de toutes faons pas dautres moyens !

253

Chapitre 11. Les entres / sorties sous Unix


Les descripteurs de chier

Les fonctions dentres / sortie de la bibliothque standard utilisent aussi des descripteurs de chier pour manipuler les chiers. Nanmoins, le descripteur de chier employ ici nest plus un entier faisant directement rfrence la table des chiers ouverts, mais un pointeur sur une structure de type FILE qui contient, dune part, le descripteur entier utilis par les appels systme et, dautre part, des informations spciques lutilisation de la bibliothque (comme par exemple la dsignation des zones de mmoire qui seront utilises comme tampon). En pratique, le dtail de la structure FILE importe peu 5 et le programmeur na pas modier, ni mme connatre ces donnes. Son emploi est tout--fait similaire celui des descripteurs de chier entiers dcrits prcdemment. En langage informaticien , les chiers sont souvent vus comme des ots de donnes (stream en anglais), cest--dire comme une suite de donnes arrivant les unes derrire les autres, et les descripteurs de chier de type FILE * sont alors souvent appels descripteurs de ot. Cette pratique verbale ne doit pas drouter le novice...
Les principales fonctions de la bibliothque standard des entres / sorties
La fonction fopen()
FILE *fopen(char *path, char *mode)

Elle ouvre le chier indiqu par la chane de caractres path et retourne soit un pointeur sur une structure de type FILE dcrivant le chier, soit la valeur symbolique NULL si une erreur est survenue. Cette fonction utilise naturellement lappel systme open() de manire sous-jacente. La chane mode indique le type douverture : "r" (pour read) ouvre le chier en lecture seule ; "r+" comme "r" mais ouvre le chier en lecture/criture ; "w" (pour write) cre le chier sil nexiste pas ou le tronque 0 et louvre en criture ; "w+" comme "w" mais ouvre le chier en lecture/criture ; "a" (pour append) cre le chier sil nexiste pas ou louvre en criture, en positionnant le descripteur la n du chier ; "a+" comme "a" mais ouvre le chier en lecture/criture. Listing 11.7 Ouverture dun chier
#include <stdlib.h> #include <stdio.h> int main(int argc, char *argv[]) { FILE *f;

5. Les curieux peuvent regarder dans /usr/include/stdio.h la structure complte !

254

11.3. La bibliothque standard dentres / sorties

f = fopen("/tmp/test","w"); if(f == NULL) { printf("erreur douverture du fichier\n"); exit(EXIT_FAILURE); } printf("le fichier a ete correctement ouvert\n"); exit(EXIT_SUCCESS); }

La fonction fprintf()
fprintf(FILE *stream, const char *format, ...)

Elle crit dans le chier dcrit par stream les donnes spcies par les paramtres suivants. La chane de caractres format peut contenir des donnes crire ainsi que la spcication dun format dcriture appliquer. Cette fonction sutilise de la mme manire que la fonction printf(). La valeur retourne est le nombre de caractres crits. Exemple : fprintf(f, "la valeur de i est : %d\n", i);
La fonction fscanf()
int fscanf(FILE *stream, const char *format, ...)

Cette fonction lit le chier dcrit par stream, selon le format spci par la chane format et stocke les valeurs correspondantes aux adresses spcies par les paramtres suivants. Attention, les adresse spcies doivent tre valides, cest--dire quelles doivent correspondre des zones mmoires pralablement alloues (soit de faon statique, soit de faon dynamique). Cette fonction fonctionne comme scanf() et il est impratif de ne pas oublier les & quand cela est ncessaire. La valeur retourne est soit le nombre de conversions effectues, soit la valeur symbolique EOF si aucune conversion na eu lieu. Exemple : fscanf(f, "%d", &i);
La fonction fgets()
char *fgets(char *str, int size, FILE *stream)

Cette fonction lit des caractres depuis le chier stream et les stocke dans la chane de caractres str. La lecture sarrte soit aprs size caractres, soit lorsquun caractre de n de ligne est rencontr, soit lorsque la n du chier est atteinte. La fonction fgets() retourne soit le pointeur sur str, soit NULL si la n du chier est atteinte ou si une erreur est survenue. Attention, dans les cas o cette fonction retourne NULL, il est frquent que la chane de caractre str ne soit pas modie par rapport sa valeur avant lappel. Les fonctions feof() et ferror() peuvent tre utilises pour savoir si la n du chier a t atteinte ou si une erreur est survenue. Elles seront prsentes dans les paragraphes qui suivent. 255

Chapitre 11. Les entres / sorties sous Unix Exemple :


char buf[255]; ... fgets(buf, 255, f); ...

La fonction feof()
int feof(FILE *stream)

Cette fonction retourne 0 si le chier dcrit par stream contient encore des donnes lire, une valeur diffrente de 0 sinon.
La fonction ferror()
int ferror(FILE *stream)

Cette fonction retourne une valeur diffrente de 0 si une erreur est survenue lors de la dernire opration sur le chier stream. Exemple :
... if (ferror(f) != 0) { printf("erreur en ecriture\n"); exit(EXIT_FAILURE); }

La fonction fclose()
int fclose(FILE *stream)

Cette fonction indique au systme que le chier stream ne sera plus utilis et que les ressources associes peuvent tre libres. La fonction retourne EOF en cas derreur ou 0 sinon. Exemple : fclose(f);
La fonction fdopen()
FILE *fdopen(int fildes, const char *mode)

Il est parfois utile de pouvoir transformer un descripteur entier retourn par la fonction open() en un pointeur vers une structure de type FILE. Cela nous servira particulirement lorsque nous manipulerons les tuyaux (cf 15). Elle prend comme argument le descripteur de chier entier fildes ainsi que le mode avec lequel le chier avait t ouvert, soit "w" pour une ouverture en criture et "r" lorsque le chier a t ouvert en lecture. Cette fonction retourne alors un pointeur vers une structure FILE qui nous permettra dutiliser les fonctions de la bibliothque. Lexemple qui suit donne une illustration. 256

11.3. La bibliothque standard dentres / sorties

... int fd; FILE *fp=NULL; ... if ((fd = open(myfile,O_WRONLY|O_CREAT,NULL)) == -1) { perror("Impossible douvrir le fichier"); exit(EXIT_FAILURE); } ... if ((fp = fdopen(fd,"w")) == NULL) { perror("Impossible dassocier un descripteur"); exit(EXIT_FAILURE); } ... /* On utilise soit fclose() */ /* soit close() mais pas les */ /* deux !! * fclose(fp); /* pas besoin de close(fd)*/ exit(EXIT_SUCCESS);

Les fonctions sprintf() et sscanf()

Les fonctions :
int sprintf(char *str, const char *format, ...) int sscanf(const char *str, const char *format, ...) fonctionnent de la mme manire que les fonctions fprintf() et fscanf(), mais

prennent ou stockent leurs donnes dans une chane de caractres (par exemple un tableau stock en mmoire) et non dans un chier. sscanf() est souvent utilise en combinaison avec fgets(), car fscanf() ne permet pas de dtecter simplement une n de ligne.
Les descripteurs de chier particuliers

Trois descipteurs de chiers particuliers sont prdnis : stdin qui correspond au descripteur de chier entier 0 (cf. appels systme), cest--dire lentre standard ; stdout qui correspond au descripteur de chier entier 1, cest--dire la sortie standard ; stderr qui correspond au descripteur de chier entier 2, cest--dire la sortie derreur standard. Les fonctions scanf() et printf() travaillent respectivement sur stdin et stdout. Ainsi, fprintf(stdout, "%d\n", i) est quivalent printf("%d\n", i). Voici un exemple de programmation de la commande (simplie) cat : elle afche lcran le contenu du chier dont le nom est pass en paramtre. Listing 11.8 Ouverture et lecture dun chier avec afchage 257

Chapitre 11. Les entres / sorties sous Unix

#include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { FILE *f; char line[255]; if (argc != 2) { fprintf(stderr,"il me faut uniquement le nom du "); fprintf(stderr,"fichier en argument\n"); exit(EXIT_FAILURE); } f = fopen(argv[1], "r"); if (f == NULL) { fprintf(stderr,"impossible douvrir le fichier\n"); exit(EXIT_FAILURE); } while (fgets(line, 255, f) != 0) { printf("%s", line); /* le caractere \n est deja dans line */ } if (ferror(f) != 0) { fprintf(stderr, "erreur de lecture sur le fichier\n"); exit(EXIT_FAILURE); } fclose(f); exit(EXIT_SUCCESS); }

11.4

Exercices

Question 1 Modier lapplication addition (voir le TD Les processus sous Unix) pour quelle prenne les nombres non plus sur la ligne de commande, mais dans un chier dont le nom est prcis sur la ligne de commande. Le chier contiendra un nombre par ligne. Question 2 Modier lapplication prcdente pour quelle prenne lentre standard si aucun nom de chier na t donn sur la ligne de commande.

258

11.5. Corrigs

11.5

Corrigs
Listing 11.9 Correction du premier exercice

#include <stdlib.h> #include <stdio.h> int main(int argc, char *argv[]) { int s,i; char buf[255]; FILE *f; if (argc != 2) { /* un argument et un seul */ fprintf(stderr,"arguments incorrects\n"); exit(EXIT_FAILURE); } f = fopen(argv[1], "r"); if (f == NULL) { fprintf(stderr, "impossible douvrir le fichier en lecture\n"); exit(EXIT_FAILURE); } s = 0; /* fgets fait une lecture ligne par ligne, et stocke */ /* la ligne lue (y compris le \n) dans buf */ /* il faut que la ligne fasse moins de 255 caracteres */ /* sinon seuls les 255 premiers caracteres sont lus */ while (fgets(buf, 255, f) != NULL) { /* fgets() retourne le pointeur NULL lorsquil */ /* ne peut plus lire. Ici on considere quil */ /* sagit dans ce cas de la fin du fichier */ if (sscanf(buf,"%d", &i) != 1) { fprintf(stderr, "erreur de conversion sur %s\n", buf); fclose(f); exit(EXIT_FAILURE); } /* une solution plus simple (mais sans verification) */ /* serait de faire : */ /* i = atoi(buf); */ s += i; } /* Un habitue du C aurait pu ecrire : */ /* while (fgets(buf,255,f)) s += atoi(buf); */ /* mais cest moins lisible */ if (ferror(f)) { fprintf(stderr, "erreur de lecture\n"); fclose(f); exit(EXIT_FAILURE); } printf("la somme est: %d\n", s); fclose(f); exit(EXIT_SUCCESS); }

Listing 11.10 Correction du deuxime exercice


#include <stdlib.h> #include <stdio.h> int main(int argc, char *argv[]) { int s,i; char buf[255];

259

Chapitre 11. Les entres / sorties sous Unix

FILE *f; if (argc != 2 && argc != 1) { /* zero ou un argument */ fprintf(stderr,"arguments incorrects\n"); exit(EXIT_FAILURE); } if (argc == 2) { /* un fichier est precise en parametre */ f = fopen(argv[1], "r"); if (f == NULL) { fprintf(stderr, "impossible douvrir le fichier en lecture\n"); exit(EXIT_FAILURE); } } else { /* pas de fichier en parametre, */ /* on utilise lentree standard */ f = stdin; /* Dans le cas de stdin, la fin du fichier sobtient */ /* en tapant ^d (Ctrl-d) qui est le marqueur de fin */ } s = 0; /* meme code que lexercice precedent */ while (fgets(buf, 255, f) != NULL) { if (sscanf(buf,"%d", &i) != 1) { fprintf(stderr, "erreur de conversion sur %s\n", buf); fclose(f); exit(EXIT_FAILURE); } s += i; } if (ferror(f)) { fprintf(stderr, "erreur de lecture\n"); fclose(f); exit(EXIT_FAILURE); } printf("la somme est: %d\n", s); fclose(f); exit(EXIT_SUCCESS); }

260

11.6. Corrections dtailles

11.6

Corrections dtailles

Lire de faon basique

Si nous voulons lire dans un chier les nombres que nous rentrions en ligne de commandes, il nous faut manipuler les appels systmes open(), read() et close(). Ces fonctions vont nous permettre dassocier un descripteur de chier (un simple entier) et un chier prsent sur le disque. Ceci se produit lorsque nous faisons appel la fonction open(). Le systme associe de manire unique dans notre programme en cours dexcution un numro et un chier. Ainsi lorsque nous voulons travailler sur le mme chier au long du programme, nous navons plus nous demander si le chemin est le bon, si le chier est sur le disque ou si le systme la plac dans une zone tampon en mmoire. Il est essentiel de remarquer que les appels systme read() et write() sont trs basiques. Ils ne savent que lire ou crire des octets, il nest donc pas question doublier ce que lon a crit dans un chier (un entier, deux ottants puis trois entiers) sous peine de relire nimporte quoi. Reste aussi savoir comment organiser le chier dont nous allons nous servir pour le programme daddition. Va-t-on y mettre des valeurs crites sous la mme forme que leur reprsentation dans la mmoire (criture sous forme doctets) ou choisira-t-on une reprsentation sous forme de mot (criture ASCII). Il y a en effet une diffrence entre lentier 234 qui sera crit ae00 sur quatre octets et le mot 234 qui scrit laide de trois symboles. Remarquons alors que la premire mthode nous impose dcrire un programme capable dcrire les diffrents nombres sous forme doctets dans un chier ! Ainsi pour apprendre lire, nous devons dj savoir crire. Nous allons donc commencer par la deuxime mthode, lire des mots et les traduire en nombres. Nous supposerons que sur chaque ligne du chier nous trouvons un nombre, i.e. chaque nombre est spar du suivant un caractre de saut la ligne. Nous allons donc lire le chier caractre par caractre et mettre le caractre lu dans un tableau (donc on avancera lindex) jusqu la lecture dun retour chariot 6 . Nous remplacerons alors le retour chariot par le caractre de terminaison de chane an de faire croire la fonction de conversion atoi() que la chane sarrte l. Le programme est le suivant : Listing 11.11 Lecture de nombres dans un chier
#include #include #include #include #include #include <sys/types.h> <sys/stat.h> <fcntl.h> <unistd.h> <stdio.h> <stdlib.h>

int main(int argc, char **argv) { int descr_file; int res;

6. Attention cest charrette qui prend 2 r

261

Chapitre 11. Les entres / sorties sous Unix

char texte[256]; int index; int somme; /* Un peu de prudence ne nuit pas ! */ if (argc !=2) { printf("Jai besoin dun nom de fichier\n"); exit(EXIT_FAILURE); } descr_file = open(argv[1],O_RDONLY,NULL); /* Un peu plus de prudence fait du bien */ if (descr_file == -1) { perror("Impossible douvrir le fichier"); exit(EXIT_FAILURE); } somme = 0; index = 0; while ((res = read(descr_file,texte+index,1)) >0) if (texte[index] == \n) { /* on est sur une fin de ligne, on peut lire /* un entier. On remplace le caractere de fin /* de ligne par \0 pour signaler a atoi() /* que le mot se termine. texte[index] = \0; somme += atoi(texte); index = 0; } else index++; } close(descr_file); printf("%d\n",somme); exit(EXIT_SUCCESS); }

{ */ */ */ */

Nous aurions pu pour des questions de rapidit lire lintgralit du chier. Mais dans ce cas il faut connatre sa taille, car tous les caractres lus devront trouver une place. On peut cette n utiliser la fonction stat() et ensuite procder une lecture du tableau de caractres chargs en mmoire. La fonction stat() est dclare comme suit :
int stat(const char *restrict path, struct stat *restrict buf);

Elle permet de recueillir dans une structure (struct stat) des informations sur un chier dont : le propritaire : st_uid ; la taille en octet : st_size. Le programme ressemblera alors ceci : Listing 11.12 Lecture en bloc
#include #include #include #include #include #include <sys/types.h> <sys/stat.h> <fcntl.h> <unistd.h> <stdio.h> <stdlib.h>

int main(int argc, char **argv) {

262

11.6. Corrections dtailles


int descr_file; int res; char *texte=NULL; int cur,endc; int somme; struct stat buf; /* Un peu de prudence ne nuit pas ! */ if (argc !=2) { printf("Jai besoin dun nom de fichier\n"); exit(EXIT_FAILURE); } descr_file = open(argv[1],O_RDONLY,NULL); /* Un peu plus de prudence fait du bien */ if (descr_file == -1) { perror("Impossible douvrir le fichier"); exit(EXIT_FAILURE); } /* On demande la taille, contenue */ /* dans la structure buf */ fstat(descr_file,&buf); /* On alloue la place necessaire */ /* pour lire le texte */ texte = malloc((size_t)buf.st_size + 1); /* On lit le texte, en verifiant */ /* que tout sest bien passe */ if ((res = read(descr_file,texte,(int)buf.st_size)) != (int)buf.st_size) { perror("impossible de lire tout le fichier"); exit(EXIT_FAILURE); } /* On peut fermer le fichier */ close(descr_file); /* On place un caractere de fin dans le tableau */ texte[(int)buf.st_size] = \0; somme = 0; cur = 0;endc = 0; while (texte[cur] != \0) { if (texte[cur] == \n) { /* on est sur une fin de ligne, on peut lire un entier */ /* on remplace le caractere de fin de ligne par \0 */ /* pour signaler a atoi() que le mot se termine. */ texte[cur] = \0; somme += atoi(texte+endc); endc = ++cur; } else cur++; } free(texte); printf("%d\n",somme); exit(EXIT_SUCCESS); }

la places des appels systme nous pouvons aussi utiliser les fonctions de la bibliothque fopen(), fscanf() et fclose. Si la premire et la dernire sont sensiblement identiques aux appels systme dun point de vue rdactionnel, la fonction fscanf() possde un degr de structuration bien plus grand que la fonction read(). En effet la 263

Chapitre 11. Les entres / sorties sous Unix fonction fscanf() accepte un argument de type format qui dcrit ce que lon cherche lire et un nombre variable darguments an de placer chaque lecture demande un endroit prcis. Le programme prcdent devient tout de suite beaucoup plus simple : Listing 11.13 Un grand pas vers la simplicit
#include <stdio.h> #include <stdlib.h> int main(int argc, char **argv) { FILE *fp=NULL; int somme, nombre; /* Un peu de prudence ne nuit pas ! */ if (argc !=2) { printf("Jai besoin dun nom de fichier\n"); exit(EXIT_FAILURE); } if ((fp = fopen(argv[1],"r")) == NULL) { perror("Impossible douvrir le fichier"); exit(EXIT_FAILURE); } somme = 0; while(fscanf(fp,"%d",&nombre) == 1) { somme += nombre; } fclose(fp); printf("%d\n",somme); exit(EXIT_SUCCESS); }

Choisir o lire

An dautoriser la saisie des nombres aprs le lancement du programme, nous allons choisir o lire ceux-ci, i.e. sur lentre standard ou dans un chier. Ceci se fait trs simplement en regardant combien nous avons darguments sur la ligne de commande et en plaant le descripteur de chier sur lentre standard si aucun chier ne peut tre ouvert. Listing 11.14 Choix de lendroit de lecture
#include <stdio.h> #include <stdlib.h> int main(int argc, char **argv) { FILE *fp=NULL; int somme, nombre; /* Un peu de prudence ne nuit pas ! */ if (argc !=2) { fp = stdin; } else {

264

11.6. Corrections dtailles

if ((fp = fopen(argv[1],"r")) == NULL) { perror("Impossible douvrir le fichier"); exit(EXIT_FAILURE); } } somme = 0; while(fscanf(fp,"%d",&nombre) == 1) { somme += nombre; } if (fp != stdin) fclose(fp); printf("%d\n",somme); exit(EXIT_SUCCESS); }

Lexcution donne ceci :


menthe22>addargbis 12 [return] 14 [return] 16 [^D] 42 menthe22>

Vous remarquez que lon signie au programme que lentre standard ne lui donnera plus rien en tapant le caractre de n de chier ^D.

265

12
Cration de processus sous Unix

Dans le chapitre prcdent, la notion de processus a t aborde, de mme que les appels systme permettant didentier des processus. Ce chapitre tudie la cration des processus, sans aller jusquau bout de la dmarche : celle-ci sera complte dans le chapitre suivant.

12.1

La cration de processus

La bibliothque C propose plusieurs fonctions pour crer des processus avec des interfaces plus ou moins perfectionnes. Cependant toute ces fonctions utilisent lappel systme fork() qui est la seule et unique faon de demander au systme dexploitation de crer un nouveau processus. pid_t fork(void) Cet appel systme cre un nouveau processus. La valeur retourne est le pid du ls pour le processus pre, ou 0 pour le processus ls. La valeur -1 est retourne en cas derreur. fork() se contente de dupliquer un processus en mmoire, cest--dire que la zone mmoire du processus pre est recopie dans la zone mmoire du processus ls. On obtient alors deux processus identiques, droulant le mme code en concurrence. Ces deux processus ont les mmes allocations mmoire et les mmes descripteurs de chiers. Seuls le pid, le pid du pre et la valeur retourne par fork() sont diffrents. La valeur retourne par fork() est en gnral utilise pour dterminer si on est le processus pre ou le processus ls et permet dagir en consquence. Lexemple suivant montre leffet de fork() : Listing 12.1 Leffet fork()
#include <unistd.h> #include <stdio.h> #include <stdlib.h> int main(int argc, char **argv) { int i; printf("[processus %d] je suis avant le fork\n", getpid());

267

Chapitre 12. Cration de processus sous Unix

i = fork(); printf("[processus %d] je suis apres le fork, il a retourne %d\n", getpid(), i); exit(EXIT_SUCCESS); }

Le rsultat de lexcution est :


menthe22>./ex22 [processus 1197] je suis avant le fork [processus 1197] je suis apres le fork, il a retourne 1198 [processus 1198] je suis apres le fork, il a retourne 0

Le premier printf() est avant lappel fork(). ce moment-l, il ny a quun seul processus, donc un seul message je suis avant le fork . En revanche, le second printf() se trouve aprs le fork() et il est donc excut deux fois par deux processus diffrents. Notons que la variable i, qui contient la valeur de retour du fork() contient deux valeurs diffrentes. Lors de lexcution, lordre dans lequel le ls et le pre afchent leurs informations nest pas toujours le mme. Cela est d lordonnancement des processus par le systme dexploitation.

12.2

Lappel systme wait()

Il est souvent trs pratique de pouvoir attendre la n de lexcution des processus ls avant de continuer lexcution du processus pre (an dviter que celui-ci se termine avant ses ls, par exemple). La fonction : pid_t wait(int *status) permet de suspendre lexcution du pre jusqu ce que lexcution dun des ls soit termine. La valeur retourne est le pid du processus qui vient de se terminer ou -1 en cas derreur. Si le pointeur status est diffrent de NULL, les donnes retournes contiennent des informations sur la manire dont ce processus sest termin, comme par exemple la valeur passe exit() (voir le manuel en ligne pour plus dinformations, man 2 wait). Dans lexemple suivant, nous compltons le programme dcrit prcdemment en utilisant wait() : le pre attend alors que le ls soit termin pour afcher les informations sur le fork() et ainsi saffranchir de lordonnancement alatoire des tches. Listing 12.2 Attente passive de la terminaison du ls
#include #include #include #include #include <unistd.h> <stdio.h> <stdlib.h> <sys/types.h> <sys/wait.h>

int main(int argc, char **argv) { int i,j;

268

12.3. Exercices

printf("[processus %d] je suis avant le fork\n", getpid()); i = fork(); if (i != 0) { /* i != 0 seulement pour le pere */ j = wait(NULL); printf("[processus %d] wait a retourne %d\n", getpid(), j); } printf("[processus %d] je suis apres le fork, il a retourne %d\n", getpid(), i); exit(EXIT_SUCCESS); }

Lexcution donne :
[processus [processus [processus [processus 1203] 1204] 1203] 1203] je suis avant le fork je suis apres le fork, il a retourne 0 wait a retourne 1204 je suis apres le fork, il a retourne 1204

Notons que cette fois-ci, lordre dans lequel les informations safchent est toujours le mme.

12.3

Exercices

Question 1 crire un programme crant un processus ls. Le processus pre afchera son identit ainsi que celle de son ls, le processus ls afchera son identit ainsi que celle de son pre. Question 2 Nous allons essayer de reproduire le phnomne d adoption des processus vu prcdemment. Pour cela, il faut ajouter un appel la fonction sleep(), de faon ce que le pre ait termin son excution avant que le ls nait appel getppid(). Question 3 Ajouter un appel la fonction wait(NULL) pour viter que le pre ne se termine avant le ls. Question 4 Dans lexercice prcdent, utiliser wait() pour obtenir le code de retour du ls et lafcher. Question 5 crire un programme crant 3 ls, faire en sorte que ceux-ci se terminent dans un autre ordre que lordre dans lequel ils ont t crs, puis demander un pre dattendre la n des 3 ls et dindiquer lordre dans lequel ils se terminent.

269

Chapitre 12. Cration de processus sous Unix

12.4

Corrigs
Listing 12.3 Corrig du premier exercice

#include <unistd.h> #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { int s; /* avant le fork(), un seul processus */ s = fork(); /* apres le fork(), deux processus */ /* dans le pere, continuation du processus de depart, s>0 */ /* dans le fils, s=0 */ if (s < 0) { /* erreur */ fprintf(stderr,"erreur dans fork\n"); exit(EXIT_FAILURE); } if (s == 0) { /* on est le fils */ printf("Je suis le fils, mon pid est %d,",getpid()); printf(" celui de mon pere est %d\n",getppid()); } else { /* on est le pere */ printf("Je suis le pere, mon pid est %d,"getpid()); printf(" celui de mon fils est %d\n",s); } /* cette ligne est executee par le fils et le pere */ exit(EXIT_SUCCESS); }

Listing 12.4 Corrig du deuxime exercice


#include <unistd.h> #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { int s; s = fork(); if (s < 0) { /* erreur */ fprintf(stderr,"erreur dans fork\n"); exit(EXIT_FAILURE); } if (s == 0) { /* on est le fils */ sleep(10); /* pendant ce temps, le pere se termine */ printf("Je suis le fils, mon pid est %d,",getpid()); printf(" celui de mon pere est %d\n",getppid()); } else { /* on est le pere */ printf("Je suis le pere, mon pid est %d,",getpid()); printf(" celui de mon fils est %d\n",s); /* Le pere se termine rapidement */ } /* cette ligne est executee par le fils et le pere */ exit(EXIT_SUCCESS); }

Listing 12.5 Corrig du troisime exercice 270

12.4. Corrigs

#include #include #include #include #include

<unistd.h> <stdio.h> <stdlib.h> <sys/types.h> <sys/wait.h>

int main(int argc, char *argv[]) { int s; s = fork(); if (s < 0) { /* erreur */ fprintf(stderr,"erreur dans fork\n"); exit(EXIT_FAILURE); } if (s == 0) { /* on est le fils */ sleep(10); printf(" Je suis le fils, mon pid est %d,",getpid()); printf("celui de mon pere est %d\n",getppid()); } else { /* on est le pere */ printf("Je suis le pere, mon pid est %d,",getpid()); printf(" celui de mon fils est %d\n",s); /* Le pere arrive ici pendant que le fils fait le sleep() */ if (wait(NULL) < 0) { /* bloque en attendant la fin du fils */ fprintf(stderr, "erreur dans wait\n"); exit(EXIT_FAILURE); } } /* cette ligne est executee par le fils et le pere */ exit(EXIT_SUCCESS); }

Listing 12.6 Corrig du quatrime exercice


#include #include #include #include #include <unistd.h> <stdio.h> <stdlib.h> <sys/types.h> <sys/wait.h>

int main(int argc, char *argv[]) { int s; int status; s = fork(); if (s < 0) { /* erreur */ fprintf(stderr,"erreur dans fork\n"); exit(EXIT_FAILURE); } if (s == 0) { /* on est le fils */ sleep(10); printf("Je suis le fils, mon pid est %d,",getpid()); printf(" celui de mon pere est %d\n",getppid()); /* le fils retourne les deux derniers chiffres de son pid */ /* en guise de valeur de retour */ /* (ce nest quun exemple) */ exit(getpid() % 100); } else { /* on est le pere */ printf("Je suis le pere, mon pid est %d,",getpid()); printf(" celui de mon fils est %d\n",s); /* Le pere arrive ici pendant que le fils fait le sleep() */ if (wait(&status) < 0) { /* bloque en attendant la fin du fils */ fprintf(stderr, "erreur dans wait\n"); exit(EXIT_FAILURE);

271

Chapitre 12. Cration de processus sous Unix

} printf("Mon fils a retourne la valeur %d\n",WEXITSTATUS(status)); } exit(EXIT_SUCCESS); /* cette ligne est executee le pere seulement */ }

272

12.5. Corrections dtailles

12.5

Corrections dtailles

Une des choses essentielles comprendre est le fait que la fonction fork() ralise vritablement une opration de clonage. Elle permet donc de faire une copie exacte du processus qui linvoque et si le cours est bien prsent votre esprit, il sagit de reproduire la fois le code, la pile, le tas mais aussi et cest important le marqueur indiquant o lon se trouve dans le processus. La deuxime remarque importante concerne la liation. Il sagit ici de raisonner de manire logique et naturelle. Si lon dtruit le pre dun processus, ce dernier va naturellement devenir un des nombreux ls du processus init. Cest ce que propose de faire le deuxime exercice.
Le numro de processus

Le programme afchant son pid est trs simple. Comme nous avons pris soin, ainsi quon nous la appris lcole primaire, de lire un nonc jusqu la n, nous allons directement crire le programme dans sa version chier. Nous prfrons cela une redirection de la sortie standard an de mettre en pratique certaines choses vues concernant les chiers. Mais an de ne pas se montrer extrmiste, nous allons quand mme laisser lutilisateur le choix de la sortie standard ! Nous aboutissons au code suivant : Listing 12.7 Afchage du numro de processus
/* getpid() */ #include <sys/types.h> #include <unistd.h> /* exit() */ #include <stdlib.h> /* fopen() fclose() fprintf() ... */ #include <stdio.h> int main(int argc, char **argv) { FILE *logfile; if (argc<2) { logfile = stdout; } else { if ((logfile = fopen(argv[1],"a+")) == NULL) { fprintf(stderr,"Impossible douvrir %s\n",v[1]); exit(EXIT_FAILURE); } } fprintf(logfile,"---\n[processus %5d]\n",getpid()); fprintf(logfile,"\tprocessus pere: %5d\n",getppid()); fclose(logfile); exit(EXIT_SUCCESS); }

Si le programme est excut sans argument autre que son nom, les rsultats safchent directement lcran, sinon ils seront placs dans le chier dont le nom est donn comme deuxime argument : 273

Chapitre 12. Cration de processus sous Unix

menthe22>./showpid [processus 3270] processus pere: 17603

An de trouver le processus pre de ce programme dont nous ne connaissons que le numro didentit, nous allons utiliser la commande ps -ux et la commande grep an de rduire lafchage des rsultats. Voici ce que nous obtenons :
menthe22>ps ux | grep 17603 yak 17603 pts4 S Oct02 yak 3278 pts4 S 16:17 menthe22> 0:00 -tcsh 0:00 grep 17603

Nous observons que le pre de notre processus nest rien dautre que linterprte de commandes, ici tcsh. Nous allons maintenant nous placer dans un contexte assez meurtrier et nous allons voir comment se passe la liation si le pre meurt avant le ls. Pour cela il nous faut disposer dun court instant pour tuer le pre (linterprte de commandes) de notre processus.
Paricide

La fonction sleep() permet de suspendre lexcution dun processus pendant un certain temps. Nous allons donc reprendre notre programme, faire en sorte quil afche les mmes donnes que prcdemment, le faire dormir, puis en proter pour tuer son pre et lui demander dafcher de nouveau les informations concernant son identit et celui de son pre. Le code qui en dcoule est trs simple : Listing 12.8 Afchage aprs la mort du pre
/* getpid() sleep() */ #include <sys/types.h> #include <unistd.h> /* exit() */ #include <stdlib.h> /* fopen() fclose() fprintf() ... */ #include <stdio.h> int main(int argc, char **argv) { FILE *logfile; if (argc<2) { logfile = stdout; } else { if ((logfile = fopen(argv[1],"a+")) == NULL) { fprintf(stderr,"Impossible douvrir %s\n",v[1]); exit(EXIT_FAILURE); } } fprintf(logfile,"---\n[processus %5d]\n",getpid()); fprintf(logfile,"\tprocessus pere: %5d\n",getppid()); sleep(10); fprintf(logfile,"Je me reveille, Papa ou es-tu?\n");

274

12.5. Corrections dtailles

fprintf(logfile,"\tprocessus pere: %5d\n",getppid()); fclose(logfile); exit(EXIT_SUCCESS); }

Pour lancer le programme nous allons dj ouvrir une nouvelle fentre an de disposer dun interprte de commandes tout neuf prt tre tu ! Nous lanons notre commande en tche de fond, et oui il faut que linterprte puisse tre tu et donc ne pas tre dans une situation dattente de la n de notre programme ! Linterprte de commandes est tu par la commande exit.
menthe22>showpidbis > /tmp/mylog & menthe22>exit ... argh!!!

Nous allons voir le rsultat dans le chier des vnements :


menthe22>more /tmp/mylog [processus 3270] processus pere: 17603 Je me reveille, Papa ou es-tu? processus pere: 1 menthe22>

Avant de tuer linterprte de commandes, lafchage ne change pas vraiment si ce nest que les numros didentit ont augment, dautres processus ont t lancs entre temps tout cela est bien normal 1 . Puis le processus sendort et nous tuons son pre. Nous remarquons qu son rveil, notre processus est maintenant devenu un descendant direct du processus pre de tous les autres init (ou presque, selon les systmes voir le cours). Ce choix parat trs logique dans la mesure o la cration de processus (hormis init) se ralise par clonage et liation. De plus aflier ce processus orphelin init lui assure davoir un pre tant que lordinateur est en marche ! Nous allons maintenant examiner la cration de processus.
Cration de processus

Crer un processus peut tre ralis par lutilisation de la commande systme


fork(). An que ce qui suit soit clair nous allons nous remettre en mmoire de faon

schmatique comment sont grs les processus sous Unix (gure 12.1). La commande fork() permet donc de recopier lintgralit de ce qui reprsente le processus qui y fait appel an de crer un nouveau processus en tous poins identique. Une fois lappel ralis, nous avons donc bel et bien deux processus qui vont accomplir la mme chose (bon nous verrons comment les diffrencier an de les particulariser aprs). La seule chose qui va nous permettre de les diffrencier dans un premier temps sera leur numro de processus. Nous pouvons dailleurs citer la page de manuel :
1. Si le correcteur avait travaill sans cesse sur la correction les numros auraient probablement t moins loigns les uns des autres mais bon...

275

Chapitre 12. Cration de processus sous Unix

F IGURE 12.1 Les deux processus reprsents sont des clones lun de lautre. Sil existe bien un lien de liation entre eux, ils ne partagent aucune zone mmoire et chacun vit sa vie dans la plus grande indiffrence de ce que peut faire lautre. Il est fondamental de bien comprendre que chaque processus possde son propre espace mmoire, son propre tas, sa propre pile, etc.

fork cre un processus ls qui diffre du processus parent uniquement par ses valeurs PID et PPID et par le fait que toutes les statistiques dutilisation

des ressources sont remises zro. Les verrouillages de chiers, et les signaux en attente ne sont pas hrits. Le programme utilisant fork() est trs simple faire partir de ce que nous avions dj fait. Nous allons simplement compliquer un peu la rsolution de lexercice pour montrer que le pre et le ls sont deux processus sexcutant dans deux contextes diffrents. Listing 12.9 Deux contextes bien diffrents
/* man getpid(), fork() */ #include <sys/types.h> #include <unistd.h> /* man exit(), malloc() */ #include <stdlib.h> /* man printf */ #include <stdio.h> /* Cette variable est globale */ /* a tout le programme. Cest */ /* sale mais cest pour la */ /* bonne cause !!*/ int tab_stat[2];

int main(int argc, char **argv) { int *tab_dyn; int i,le_numero_au_debut; le_numero_au_debut = getpid(); printf("[processus %5d] mon pere est : %d.\n", getpid(),getppid()); printf("[processus %5d] jalloue\n",getpid()); tab_dyn = malloc(2*sizeof(int));

276

12.5. Corrections dtailles

printf("[processus %5d] je remplis\n",getpid()); tab_stat[0] = 10; tab_stat[1] = 11; tab_dyn[0] = 20; tab_dyn[1] = 21; printf("[processus %5d] je me clone\n",getpid()); /* A partir de maintenant le code doit etre */ /* execute deux fois */ if (fork()==-1) { perror("Impossible de me cloner"); exit(EXIT_FAILURE); } printf("[processus %5d] Mon pere est : %d\n",getpid(), getppid()); printf("[processus %5d] Le numero enregistre : %d\n", getpid(), le_numero_au_debut); for(i=0;i<2;i++){ printf("[processus %5d] tab_stat : %d\n", getpid(),tab_stat[i]); printf("[processus %5d] tab_dyn : %d\n", getpid(),tab_dyn[i]); } exit(EXIT_SUCCESS); }

Lorsquon regarde lafchage rsultat de lexcution on aboutit ce qui suit. Attention nous avons volontairement accentu la sparation entre les lignes de code excutes deux fois et les lignes de code excutes une seule fois.
menthe22>clone1 [processus 18797] [processus 18797] [processus 18797] [processus 18797] [processus [processus [processus [processus [processus [processus [processus [processus [processus [processus [processus [processus menthe22> 18798] 18798] 18798] 18798] 18798] 18798] 18797] 18797] 18797] 18797] 18797] 18797] mon pere est : 18711. jalloue je remplis je me clone Mon pere est : 18797 Le numero enregistre : 18797 tab_stat : 10 tab_dyn : 20 tab_stat : 11 tab_dyn : 21 Mon pere est : 18711 Le numero enregistre : 18797 tab_stat : 10 tab_dyn : 20 tab_stat : 11 tab_dyn : 21

Nous remarquons bien que tant que fork() na pas t appel, un seul processus existe, celui dont le pid est 18797. Ensuite, deux processus sexcutent de manire indpendante et nous voyons alors apparatre deux fois chaque sortie sur lcran, mais avec quelques diffrences et notamment le numro de processus qui nest pas le mme. 277

Chapitre 12. Cration de processus sous Unix Jusque l rien de trs exceptionnel. Maintenant nous allons compliquer un peu les choses et crire dans un chier au lieu dcrire sur la sortie standard, et comme nous sommes vraiment curieux, nous allons procder louverture et la fermeture de ce chier aprs lappel fork(). Le programme est le suivant, remarquez bien louverture de chier qui utilise le drapeau "w+". Listing 12.10 Ecriture dans un chier partag
#include #include #include #include <sys/types.h> <unistd.h> <stdlib.h> <stdio.h>

/* Cette variable est globale */ /* a tout le programme. Cest */ /* sale mais cest pour la */ /* bonne cause !!*/ int tab_stat[2];

int main(int argc, char **argv) { FILE *fp=NULL; int *tab_dyn; int i,le_numero_au_debut; le_numero_au_debut = getpid(); printf("[processus %5d] mon pere est : %d.\n", getpid(),getppid()); printf("[processus %5d] jalloue\n",getpid()); tab_dyn = malloc(2*sizeof(int)); printf("[processus %5d] je remplis\n",getpid()); tab_stat[0] = 10; tab_stat[1] = 11; tab_dyn[0] = 20; tab_dyn[1] = 21; printf("[processus %5d] je me clone\n",getpid()); /* A partir de maintenant le code doit etre */ /* execute deux fois */ if (fork()==-1) { perror("Impossible de me cloner"); exit(EXIT_FAILURE); } if ((fp = fopen("curieux.txt","a+")) == NULL) { perror("Impossible douvrir curieux.txt"); exit(EXIT_FAILURE); } fprintf(fp,"[processus %5d] Mon pere est : %d\n", getpid(),getppid()); fprintf(fp,"[processus %5d] Le numero enregistre : %d\n", getpid(), le_numero_au_debut); for(i=0;i<2;i++){ fprintf(fp,"[processus %5d] tab_stat : %d\n", getpid(),tab_stat[i]); fprintf(fp,"[processus %5d] tab_dyn : %d\n", getpid(),tab_dyn[i]); } fclose(fp);

278

12.5. Corrections dtailles


exit(EXIT_SUCCESS); }

La sortie donne ceci :


menthe22>clone2 [processus 18859] mon pere est : 18711. [processus 18859] jalloue [processus 18859] je remplis [processus 18859] je me clone menthe22>more curieux.txt [processus 18859] Mon pere est : 18711 [processus 18859] Le numero enregistre : 18859 [processus 18859] tab_stat : 10 [processus 18859] tab_dyn : 20 [processus 18859] tab_stat : 11 [processus 18859] tab_dyn : 21 menthe22>

On remarque que dans le chier, un seul afchage subsiste, comme sil ny avait eu quun processus excut, le processus pre. Que sest-il pass ? En fait, cest la fois le hasard et la logique. Le hasard a voulu que lordonnanceur de tche fasse sexcuter les instructions du processus pre aprs celles du processus ls. Donc le processus ls ouvre le chier, crit dedans et le ferme. Puis le processus pre ouvre le mme chier, avec un drapeau qui va placer le descripteur de chier au dbut et donc vide le chier de son contenu, le pre crit puis ferme le chier. Toute trace du processus ls a disparu. Si notre logique est bonne, un drapeau tel que "a+" devrait remdier cette situation. Voici la sortie lorsque lon change le drapeau (noubliez pas deffacer le chier avant de lancer ce nouveau programme ! !) :
menthe22>rm -f curieux.txt menthe22>clone2 [processus 18905] mon pere est : 18711. [processus 18905] jalloue [processus 18905] je remplis [processus 18905] je me clone. menthe22>more curieux.txt [processus 18906] Mon pere est : 18905 [processus 18906] Le numero enregistre : 18905 [processus 18906] tab_stat : 10 [processus 18906] tab_dyn : 20 [processus 18906] tab_stat : 11 [processus 18906] tab_dyn : 21 [processus 18905] Mon pere est : 18711 [processus 18905] Le numero enregistre : 18905 [processus 18905] tab_stat : 10 [processus 18905] tab_dyn : 20 [processus 18905] tab_stat : 11 [processus 18905] tab_dyn : 21 menthe22>

On retrouve bien le fait que nos deux processus crivent dans le chier 2 . Nous allons maintenant tenter une autre exprience, celle de suivre un peu les pas de Fibonacci ! Nous allons donc faire une boucle lintrieur de laquelle nous trouverons
2. Au fait quoi servent les tableaux ? Un peu de patience nous allons les utiliser bientt !

279

Chapitre 12. Cration de processus sous Unix lappel fork(). Avant dcrire ce programme nous allons tout dabord rchir, car lordinateur tant avant tout un outil de travail, inutile de le dtruire prmaturment !

F IGURE 12.2 Chaque bote reprsente un processus avec un numro. Ainsi la premire bote reprsente le processus pre qui cre un ls la premire tape de la boucle. Puis le pre continue son excution avec la deuxime tape et une nouvelle cration de processus ls, et de son ct son premier ls cre son propre ls pour la deuxime tape de la boucle. Nous voyons que lors de la deuxime tape dans la boucle de chaque processus cr, quatre clones vont prendre naissance.

Le programme ressemble singulirement aux prcdents, si ce nest que les instructions dcriture dans le chier et surtout lappel fork() se trouvent maintenant dans une boucle de 3 itrations. Listing 12.11 Les consquences dune boucle et dun fork
#include #include #include #include <sys/types.h> <unistd.h> <stdlib.h> <stdio.h>

int main(int argc, char **argv) {

280

12.5. Corrections dtailles

int i; printf("[processus %5d] mon pere est : %d\n", getpid(),getppid()); printf("[processus %5d] je vais me cloner\n\n", getpid()); for(i=0;i<3;i++) { if (fork()==-1) { perror("Impossible de me cloner"); exit(EXIT_FAILURE); } printf("[processus %5d] Mon pere est : %d\n", getpid(),getppid()); } exit(EXIT_SUCCESS); }

Voici la sortie :
menthe22>clone2b [processus 18966] mon pere est : 18711. [processus 18966] je vais me cloner [processus [processus [processus [processus [processus [processus [processus [processus [processus [processus [processus [processus [processus [processus menthe22> 18967] 18968] 18969] 18968] 18967] 18970] 18967] 18966] 18971] 18972] 18971] 18966] 18973] 18966] Mon Mon Mon Mon Mon Mon Mon Mon Mon Mon Mon Mon Mon Mon pere pere pere pere pere pere pere pere pere pere pere pere pere pere est est est est est est est est est est est est est est : : : : : : : : : : : : : : 18966 18967 18968 18967 18966 18967 18966 18711 18966 18971 18966 18711 18966 18711

Au total 8 processus comme prvu, le pre et ses trois ls, puis ses trois petits ls et enn son petit petit ls. Conclusion, attention avant de mettre un appel fork() dans une boucle, le nombre de processus devient trs vite grand. Il y a bien sr des limites, le systme Linux par exemple, nautorise pas plus de 256 processus ls crs, mais remarquons que le pre na jamais cr que 3 ls, qui ds leur majorit atteinte ne se sont pas faits prier pour crer leur tour des ls, donc la limite tait loin dtre atteinte pour le pre. Nous verrons la n de ce corrig une faon de crer plusieurs ls. Nous allons maintenant nous servir des tableaux par lintermdiaire de la valeur retourne par fork(). Nous savons que la fonction fork() duplique le processus courant, mais nous savons aussi quelle retourne une valeur, et les choses tant bien faites, la valeur retourne dpend du processus dans lequel cette valeur est prise en compte : sil sagit du processus pre, la fonction renvoie une valeur non nulle qui est le pid du ls nouvellement cr, sinon, quand on se trouve dans le processus ls, la fonction renvoie une valeur nulle. Nous allons ainsi pouvoir grer diffrentes excutions selon le contexte. 281

Chapitre 12. Cration de processus sous Unix Nous reprenons les programmes prcdents pour bien voir que les zones mmoires sont totalement spares. Dans le programme qui suit, remarquez que lcriture dans le chier des variables globales, statiques ou alloues dynamiquement est ralise la n du programme par les deux processus. Par contre les modications sur les valeurs de ces variables sont diffrentes selon que lon se trouve dans le processus pre (fork()>0) ou le ls (fork()==0). Listing 12.12 Deux espaces totalement diffrents
#include #include #include #include <sys/types.h> <unistd.h> <stdlib.h> <stdio.h>

/* Cette variable est globale */ /* a tout le programme. Cest */ /* sale mais cest pour la */ /* bonne cause !!*/ int tab_stat[2];

int main(int argc, char **argv) { FILE *fp; int *tab_dyn; int le_numero_au_debut; int i; le_numero_au_debut = getpid(); printf("[processus %5d] mon pere est : %d.\n", getpid(),getppid()); printf("[processus %5d] jalloue\n",getpid()); tab_dyn = malloc(2*sizeof(int)); printf("[processus %5d] je remplis\n",getpid()); tab_stat[0] = 10; tab_stat[1] = 11; tab_dyn[0] = 20; tab_dyn[1] = 21; printf("[processus %5d] je me clone\n",getpid()); /* A partir de maintenant le code doit */ /* etre execute plusieurs fois */ if ((i=fork())==-1) { perror("Impossible de me cloner"); exit(EXIT_FAILURE); } if (i==0) { /* je suis le fils */ if ((fp = fopen("curieux.txt","a+")) == NULL) { perror("Impossible douvrir curieux.txt"); exit(EXIT_FAILURE); } le_numero_au_debut = 11; tab_stat[0] = 12; tab_stat[1] = 13; tab_dyn[0] = 22; tab_dyn[1] = 23;

282

12.5. Corrections dtailles

fprintf(fp,"[processus %5d] Fils de : %d\n", getpid(),getppid()); } else { /* je suis le pere */ if ((fp = fopen("curieux.txt","a+")) == NULL) { perror("Impossible douvrir curieux.txt"); exit(EXIT_FAILURE); } le_numero_au_debut = 101; tab_stat[0] = 102; tab_stat[1] = 103; tab_dyn[0] = 202; tab_dyn[1] = 203; } /* Ceci est execute par tout le monde */ fprintf(fp,"[processus %5d] : %d %d %d %d %d\n", getpid(), le_numero_au_debut, tab_stat[0],tab_stat[1], tab_dyn[0],tab_dyn[1]); fclose(fp); exit(EXIT_SUCCESS); }

La sortie donne ceci :


menthe22>clone4 [processus 19086] mon pere est : 18711. [processus 19086] jalloue [processus 19086] je remplis [processus 19086] je me clone menthe22>more curieux.txt [processus 19087] Fils de : 19086 [processus 19087] : 11 12 13 22 23 [processus 19086] : 101 102 103 202 203 menthe22>

Nous voyons donc que les variables sont reproduites dans chaque processus et quelles appartiennent des espaces mmoire diffrents.
Attendre ses ls

Nous allons nir en utilisant la fonction wait() qui permet au processus pre dattendre la n de son ls avant de continuer son excution. Le schma du programme est simple, on reprend le code prcdent et on ajoute, dans la partie excute par le pre, linstruction wait() avant toute autre instruction. On est alors certain que toutes les instructions spciques au pre seront effectues aprs que le ls ait termin sa tche. La sortie du programme donne la mme chose que prcdemment. Vous avez remarqu que la fonction wait() prend une adresse dentier comme argument. La fonction viendra placer cet endroit le code de retour du processus ls, savoir la valeur retourne par la fonction main() du ls. Voici un petit programme avec un pre. . . de famille. Listing 12.13 Pre de famille
#include <sys/types.h>

283

Chapitre 12. Cration de processus sous Unix

#include #include #include #include

<unistd.h> <sys/wait.h> <stdio.h> <stdlib.h>

int main(int argc, char **argv) { int i,spid[2]; int status; fprintf(stdout,"[%5d] je suis le pere\n",getpid()); /* A partir de maintenant le code doit */ /* etre execute plusieurs fois */ if ((i=fork())==-1) { perror("Impossible de me cloner"); exit(EXIT_FAILURE); } if (i==0) { /* je suis le fils */ fprintf(stdout,"[F1] je suis le premier fils (%d)\n", getpid()); fprintf(stdout,"[F1] je dors\n"); sleep(4); exit(21); } else { /* je suis le pere */ spid[0] = i; /* je cree un second fils */ if ((i=fork())==-1) { perror("Impossible de me double-cloner"); exit(EXIT_FAILURE); } if (i==0) { /* je suis le second fils */ fprintf(stdout,"[F2] je suis le second fils (%d)\n", getpid()); fprintf(stdout,"[F2] je dors\n"); sleep(2); exit(31); } else { spid[1] = i; status = 0; /* je suis le pere */ /* jattends mes fils */ i=wait(&status); fprintf(stdout, "[P0] mon fils F%c a retourne %d\n", i==spid[0]?1:2,WEXITSTATUS(status)); i=wait(&status); fprintf(stdout, "[P0] mon fils F%c a retourne %d\n", i==spid[0]?1:2,WEXITSTATUS(status)); } } fprintf(stdout,"[%5d] suis-je le pere?\n",getpid()); exit(EXIT_SUCCESS); }

La sortie donne ceci, sachant que F1 est le premier ls, F2 le deuxime ls et P0 le pre :
menthe22>clone5 [19273] je suis le pere

284

12.5. Corrections dtailles

[F1] je suis le premier fils (19274) [F1] je dors [F2] je suis le second fils (19275) [F2] je dors [P0] mon fils F1 a retourne 21 [P0] mon fils F2 a retourne 31 [19273] suis-je le pere? menthe22>

Nous voyons que le premier ls dort pendant 2 secondes. Le deuxime ls, cr aprs le premier fork(), dort quant lui 4 secondes. Donc le premier appel wait() va sortir avec la valeur de retour du premier ls qui se sera rveill en premier. Si lon change les deux valeurs (le premier ls dort 4 secondes et le deuxime 2 secondes) la sortie devient :
menthe22>clone5 [19273] je suis le pere [F1] je suis le premier fils (19274) [F1] je dors [F2] je suis le second fils (19275) [F2] je dors [P0] mon fils F2 a retourne 31 [P0] mon fils F1 a retourne 21 [19273] suis-je le pere? menthe22>

Regardons maintenant comment crer un nombre inconnu de ls lavance et que nous communiquerons au programme par la ligne de commandes. Nous allons observer une premire construction dans laquelle nous voulons que chaque ls puisse dormir pendant un temps alatoire. Voici le premier programme : Listing 12.14 Attente a priori alatoire. . .
#include <sys/types.h> #include <unistd.h> #include <sys/wait.h> #include <stdio.h> #include <stdlib.h> #define MAX_SON 12 int son(int num) { int retf,t_s; if ((retf=fork())==-1) { perror("Impossible de me cloner"); exit(EXIT_FAILURE); } if(retf) { return retf; } t_s = random()%10; fprintf(stdout, "[F%02d] je suis le fils de (%d) et je dors %ds\n", num,getppid(),t_s); sleep(t_s); exit(0); }

285

Chapitre 12. Cration de processus sous Unix

int main(int argc, char **argv) { int numf,i,*spid; int retf,status; srand(getpid()); fprintf(stdout,"[%5d] je suis le pere\n",getpid()); numf = (argc>1?atoi(argv[1]):2); numf = numf<=0?2:(numf>MAX_SON?MAX_SON:numf); spid = malloc(numf*sizeof(int)); i=0; while(i<numf) { spid[i] = son(i);i++; } while((retf=wait(&status))!=-1) { for(i=0;i<numf;i++) { if (retf == spid[i]) { fprintf(stdout, "[P0] mon fils (F%02d) sest termine\n",i); } } } fprintf(stdout,"[%5d] suis-je le pere?\n",getpid()); exit(EXIT_SUCCESS); }

Nous obtenons la sortie suivante :


menthe22>clone6 4 [20516] je suis le pere [F00] je suis le fils de (20516) et [F01] je suis le fils de (20516) et [F02] je suis le fils de (20516) et [F03] je suis le fils de (20516) et [P0] mon fils (F00) sest termine [P0] mon fils (F01) sest termine [P0] mon fils (F02) sest termine [P0] mon fils (F03) sest termine [20516] suis-je le pere?

je je je je

dors dors dors dors

6s 6s 6s 6s

Quelle admirable mdiocrit de la part de random(). Tous les ls ont exactement le mme temps de sommeil, franchement, impossible de faire conance un gnrateur alatoire ! moins que notre programme soit mal crit ? En fait cest la deuxime solution qui convient ! Le programme est mal crit ! Un processus de dpart, le pre et donc une seule initialisation de la graine par lappel srand(getpid()). Ensuite chaque ls fait appel la fonction random() une et une seule fois, donc cette fonction est excute de manire totalement identique par chaque ls, rappelons-nous que chaque ls est une copie lidentique , comme si la boule du loto avait t dupplique de faon exacte un instant du brassage. Chaque copie nous donnera donc le mme tirage ! Changeons tout cela laide du programme suivant : Listing 12.15 Attente vraiment alatoire
#include <sys/types.h> #include <unistd.h> #include <sys/wait.h>

286

12.5. Corrections dtailles

#include <stdio.h> #include <stdlib.h> #define MAX_SON 12 int son(int num, int t_s) { int retf; if ((retf=fork())==-1) { perror("Impossible de me cloner"); exit(EXIT_FAILURE); } if(retf) { return retf; } fprintf(stdout, "[F%02d] je suis le fils de (%d) et je dors %ds\n", num,getppid(),t_s); sleep(t_s); exit(0); } int main(int argc, char **argv) { int numf,i,*spid; int retf,status; int t_s; srand(getpid()); fprintf(stdout,"[%5d] je suis le pere\n",getpid()); numf = (argc>1?atoi(argv[1]):2); numf = numf<=0?2:(numf>MAX_SON?MAX_SON:numf); spid = malloc(numf*sizeof(int)); i=0; while(i<numf) { t_s = random()%5; spid[i] = son(i,t_s);i++; } while((retf=wait(&status))!=-1) { for(i=0;i<numf;i++) { if (retf == spid[i]) { fprintf(stdout, "[P0] mon fils (F%02d) sest termine\n",i); } } } fprintf(stdout,"[%5d] suis-je le pere?\n",getpid()); exit(EXIT_SUCCESS); }

Nous obtenons la sortie suivante :


menthe22>clone7 4 [20671] je suis le pere [F00] je suis le fils de (20671) et [F01] je suis le fils de (20671) et [F02] je suis le fils de (20671) et [F03] je suis le fils de (20671) et [P0] mon fils (F00) sest termine [P0] mon fils (F01) sest termine [P0] mon fils (F03) sest termine [P0] mon fils (F02) sest termine [20671] suis-je le pere? menthe22>

je je je je

dors dors dors dors

0s 1s 4s 2s

287

13
Recouvrement de processus sous Unix

Dans le chapitre prcdent, nous avons vu comment crer de nouveaux processus grce lappel systme fork(). Cet appel systme effectue une duplication de processus, cest--dire que le processus nouvellement cr contient les mmes donnes et excute le mme code que le processus pre. Le recouvrement de processus permet de remplacer par un autre code le code excut par un processus. Le programme et les donnes du processus sont alors diffrents, mais celui-ci garde le mme pid, le mme pre et les mme descripteurs de chiers. Cest ce mcanisme qui est utilis lorsque, par exemple, nous tapons la commande ls, ou que nous lanons un programme aprs lavoir compil en tapant ./a.out : le terminal de commande dans lequel cette commande est tape fait un fork() ; le processus ainsi cr (le ls du terminal de commande) est recouvert par lexcutable dsir ; pendant ce temps, le terminal de commande (le pre) attend que le ls se termine grce wait() si la commande na pas t lance en tche de fond.

13.1

Les appels systme de recouvrement de processus

Ces appels systme sont au nombre de 6 : int execl(char *path, char *arg0, char *arg1, ... ,
char *argn, (char *)0)

int execv(char *path, char *argv[]) int execle(char *path, char *arg0, char *arg1, ... ,
char *argn, (char *)0, char **envp)

int execlp(char *file, char *arg0, char *arg1, ... ,


char *argn, (char *)0)

int execvp(char *file, char *argv[]) int execve(char *path, char *argv[], char **envp) Notons que le code prsent aprs lappel une de ces fonctions ne sera jamais excut, sauf en cas derreur. Nous dcrirons ici uniquement les appels systme execv() et execlp() : 289

Chapitre 13. Recouvrement de processus sous Unix int execv(char *path, char *argv[]) Cette fonction recouvre le processus avec lexcutable indiqu par la chane de caractre path (path est le chemin daccs lexcutable). argv est un tableau de chanes de caractres contenant les arguments passer lexcutable. Le dernier lment du tableau doit contenir NULL. Le premier est la chane de caractres qui sera afche par ps et, par convention, on y met le nom de lexcutable. Exemple :
... argv[0] = "ls"; argv[1] = "-l"; argv[2] = NULL; execv("/bin/ls", argv);

int execlp(char *file, char *arg0, char *arg1, ... ,


char *argn, (char *)0)

Cette fonction recouvre le processus avec lexcutable spci par file (le chier utilis est cherch dans les rpertoires contenus dans la variable denvironnement $PATH si la chane file nest pas un chemin daccs absolu ou relatif). Les arguments suivants sont les chanes de caractres passes en argument lexcutable. Comme pour execv(), le premier argument doit tre le nom de lexcutable et le dernier NULL.

execlp("ls","ls","-l",NULL);

Notons quil nest pas ncessaire ici de spcier le chemin daccs au chier (/bin/ls) pour lexcutable. Pour recouvrir un processus avec la commande ps -aux, nous pouvons utiliser le code suivant : Listing 13.1 Un premier recouvrement
#include <unistd.h> #include <stdlib.h> #include <stdio.h> int main(int argc, char *argv[]) { char *arg[3]; arg[0] = "ps"; arg[1] = "-aux"; arg[2] = NULL; execv("/bin/ps", arg); /* lexecutable ps se trouve dans /bin */ fprintf(stderr, "erreur dans execv\n"); exit(EXIT_FAILURE); }

ou alors : Listing 13.2 Un recouvrement plus simple 290

13.2. Exercices

#include <unistd.h> #include <stdlib.h> #include <stdio.h> int main(int argc, char *argv[]) { execlp("ps", "ps", "-aux", NULL); fprintf(stderr, "erreur dans execlp\n"); exit(EXIT_FAILURE); }

Dans le premier chapitre consacr la programmation systme, nous utilisions les variables passes la fonction int main(int argc, char **argv) pour rcuprer les arguments de la ligne de commande. Ces valeurs sont en fait dtermines par le terminal de commande et cest lui qui les passe la commande excute, grce un appel exec(). Ainsi, lorsque nous tapons une commande comme ps -aux, le terminal de commande excute (de manire simplie) :
int res; ... res = fork(); if (res == 0) {/* on est le fils */ execlp("ps", "ps", "-aux", NULL); fprintf(stderr, "erreur\n"); /* si execlp retourne, quelque chose ne va pas */ exit(EXIT_FAILURE); }

13.2

Exercices

Question 1 Crer un processus et le recouvrir par la commande du -s -h. Attention, ce programme na pas besoin dutiliser lappel systme fork(). Question 2 Reprendre le premier exemple de la srie dexercices prcdente (lapplication afchant sont pid et celui de son pre). Nous appellerons ce programme identite. crire ensuite une application (semblable celle de la question 2 de la srie dexercices prcdente) crant un processus ls. Le pre afchera son identit, le processus ls sera recouvert par le programme identite. Question 3 crire une application myshell reproduisant le comportement dun terminal de commande. Cette application doit lire les commandes tapes au clavier, ou contenue dans un chier, crer un nouveau processus et le recouvrir par la commande lue. Par exemple :
menthe22> ./myshell ls extp3_1.c extp3_2.c soltp3_1.c soltp3_2.c ps

identite soltp3_3.c

myshell

291

Chapitre 13. Recouvrement de processus sous Unix

PID TT 521 std 874 p3 1119 p3 1280 p2 1283 p2 1284 std exit menthe22>

STAT S S S+ S S+ S+

TIME 0:00.95 0:00.02 0:00.56 0:00.03 0:00.04 0:00.01

COMMAND bash bash vi soltp3_3.c tcsh vi ./myshell

Pour la commodit dutilisation, on pourra faire en sorte que le terminal de commande afche une invite (un prompt) avant de lire une commande :
menthe22>./myshell myshell>ls extp3_1.c extp3_2.c soltp3_1.c soltp3_2.c myshell>exit menthe22>

identite soltp3_3.c

myshell

On pensera utiliser la fonction wait() an dattendre que lexcution dune commande soit termine avant de lire et de lancer la suivante. Que se passe-t-il si lon tape linvite ls -l ? Question 4 Ajouter au terminal de commande de la question prcdente la gestion des paramtres pour permettre lexcution de ls -l par exemple. Est-ce que la ligne ls *.c se comporte comme un vrai terminal de commande ? Question 5 Ajouter au terminal de commande de la question prcdente la gestion des excutions en tche de fond, caractrise par la prsence du caractre & en n de ligne de commande.

292

13.3. Corrigs

13.3

Corrigs
Listing 13.3 Correction du premier exercice

#include <unistd.h> #include <stdlib.h> #include <stdio.h> int main(int argc, char *argv[]) { execlp("du", "du", "-s", "-h", NULL); fprintf(stderr, "erreur dans execlp\n"); exit(EXIT_FAILURE); }

Listing 13.4 Correction du deuxime exercice


#include <unistd.h> #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { int s; s = fork(); if (s < 0) { /* erreur */ fprintf(stderr,"erreur dans fork\n"); exit(EXIT_FAILURE); } if (s == 0) { /* on est le fils */ execlp("./identite", "identite", NULL); fprintf(stderr, "erreur dans le execlp\n"); exit(EXIT_FAILURE); } else { /* on est le pere */ printf(" Je suis le pere, mon pid est %d,"); printf("celui de mon fils est %d\n", getpid(), s); exit(EXIT_SUCCESS); } }

Listing 13.5 Correction du troisime exercice


#include #include #include #include #include <stdio.h> <stdlib.h> <string.h> <unistd.h> <sys/wait.h>

int main (int argc , char *argv[]) { char ligne[80], *arg[2]; printf("myshell> ") ; while (fgets(ligne,sizeof(ligne),stdin) != NULL) { /* fgets lit egalement le caractere de fin de ligne */ if (strcmp(ligne,"exit\n") == 0) { exit(EXIT_SUCCESS); } /* on supprime le caractere de fin de ligne */ ligne[strlen(ligne)-1]=\0;

293

Chapitre 13. Recouvrement de processus sous Unix

arg[0] = ligne; /* nom de lexecutable */ arg[1] = NULL; /* fin des parametres */ /* creation du processus qui execute la commande */ switch (fork()) { case -1: /* erreur */ fprintf(stderr,"Erreur dans fork()\n"); exit(EXIT_FAILURE); case 0: /* fils */ /* on recouvre par la commande tapee */ execvp(arg[0],arg); /* on narrivera jamais ici, sauf en cas derreur */ fprintf(stderr,"Erreur dans execvp(\"%s\")\n",arg[0]); exit(EXIT_FAILURE); default: /* pere */ /* on attend la fin du fils avant de representer */ /* linvite de commande */ wait(NULL); } printf("myshell> "); } /* fin du while */ exit(EXIT_SUCCESS); }

En utilisant la fonction strtok() on peut crire le shell comme ceci : Listing 13.6 Construction dun shell
#include #include #include #include #include <stdio.h> <stdlib.h> <string.h> <unistd.h> <sys/wait.h>

int main (int argc , char *argv[]) { char ligne[80], *arg[10], *tmp; int i; printf("myshell> ") ; /* lecture de lentree standard ligne par ligne */ while (fgets(ligne,sizeof(ligne),stdin) != NULL) { /* fgets lit egalement le caractere de fin de ligne */ if (strcmp(ligne,"exit\n") == 0) { exit(EXIT_SUCCESS); } /* decoupage de la ligne aux espaces et tabulations */ /* un premier appel strtok() retourne le premier parametre */ /* chaque appel suivant se fait avec NULL comme premier */ /* argument, et retourne une chaine, ou NULL lorsque */ /* cest la fin */ for (tmp=strtok(ligne," \t\n"), i=0; tmp != NULL ; tmp=strtok(NULL," \t\n"), i++) { /* on remplit le tableau arg[] */ /* une simple affectation de pointeurs */ /* suffit car strtok() coupe la chaine ligne */ /* sans copie et sans ecraser les arg[] precedents */ arg[i] = tmp; } arg[i] = NULL; /* fin des parametres */ /* creation du processus qui execute la commande */ switch (fork()) {

294

13.3. Corrigs

case -1: /* erreur */ fprintf(stderr,"Erreur dans fork()\n"); exit(EXIT_FAILURE); case 0: /* fils */ /* on recouvre par la commande tapee */ execvp(arg[0],arg); /* on narrivera jamais ici, sauf en cas derreur */ fprintf(stderr,"Erreur dans execvp(\"%s\")\n",arg[0]); exit(EXIT_FAILURE); default: /* pere */ /* on attend la fin du fils avant de representer */ /* linvite de commande */ wait(NULL); } printf("myshell> "); } /* fin du while */ exit(EXIT_SUCCESS); }

Voici un exemple dune autre solution nutilisant pas strtok : Listing 13.7 Construction plus longue dun shell
#include #include #include #include #include #include <stdio.h> <stdlib.h> <string.h> <unistd.h> <sys/wait.h> <ctype.h>

void decoupe_commande(char *ligne, char *arg[]); int main (int argc , char *argv[]) { char ligne[80], *arg[10]; printf("myshell> ") ; /* lecture de lentree standard ligne par ligne */ while (fgets(ligne,sizeof(ligne),stdin) != NULL) { /* fgets lit egalement le caractere de fin de ligne */ if (strcmp(ligne,"exit\n") == 0) { exit(EXIT_SUCCESS); } /* La fonction qui decoupe la ligne de texte */ decoupe_commande(ligne, arg); /* creation du processus qui execute la commande */ switch (fork()) { case -1: /* erreur */ fprintf(stderr,"Erreur dans fork()\n"); exit(EXIT_FAILURE); case 0: /* fils */ /* on recouvre par la commande tapee */ execvp(arg[0],arg); /* on narrivera jamais ici, sauf en cas derreur */ fprintf(stderr,"Erreur dans execvp(\"%s\")\n",arg[0]); exit(EXIT_FAILURE); default: /* pere */ /* on attend la fin du fils avant de representer */ /* linvite de commande */ wait(NULL); } printf("myshell> "); } /* fin du while */

295

Chapitre 13. Recouvrement de processus sous Unix


exit(EXIT_SUCCESS); } /* Decoupage de la ligne en place (sans recopie) */ void decoupe_commande(char *ligne, char *arg[]) { int i=0; /* on avance jusquau premier argument */ while (isspace(*ligne)) ligne++; /* on traite les arguments tant que ce nest */ /* pas la fin de la ligne */ while (*ligne != \0 && *ligne != \n) { arg[i++]=ligne; /* on avance jusquau prochain espace */ while (!isspace(*ligne) && *ligne!=\0) ligne++; /* on remplace les espaces par \0 ce qui marque */ /* la fin du parametre precedent */ while (isspace(*ligne) && *ligne!=\0) *ligne++=\0; } arg[i]=NULL; }

296

14
Manipulation des signaux sous Unix

Les signaux constituent la forme la plus simple de communication entre processus 1 . Un signal est une information atomique envoye un processus ou un groupe de processus par le systme dexploitation ou par un autre processus. Lorsquun processus reoit un signal, le systme dexploitation linforme : tu as reu tel signal , sans plus. Un signal ne transporte donc aucune autre information utile. Lorsquil reoit un signal, un processus peut ragir de trois faons : Il est immdiatement drout vers une fonction spcique, qui ragit au signal (en modiant la valeur de certaines variables ou en effectuant certaines actions, par exemple). Une fois cette fonction termine, on reprend le cours normal de lexcution du programme, comme si rien ne stait pass. Le signal est tout simplement ignor. Le signal provoque larrt du processus (avec ou sans gnration dun chier core). Lorsquun processus reoit un signal pour lequel il na pas indiqu de fonction de traitement, le systme dexploitation adopte une raction par dfaut qui varie suivant les signaux : soit il ignore le signal ; soit il termine le processus (avec ou sans gnration dun chier core). Vous avez certainement toutes et tous dj utilis des signaux, consciemment, en tapant Control-C ou en employant la commande kill, ou inconsciemment, lorsquun de vos programme a afch
segmentation fault (core dumped)

14.1

Liste et signication des diffrents signaux

La liste des signaux dpend du type dUnix. La norme POSIX.1 en spcie un certain nombre, parmi les plus rpandus. On peut nanmoins dgager un grand nombre de signaux communs toutes les versions dUnix :
1. Au niveau du microprocesseur, un signal correspond une interruption logicielle.

297

Chapitre 14. Manipulation des signaux sous Unix


SIGHUP : rupture de ligne tlphonique. Du temps o certains terminaux

taient relis par ligne tlphonique un ordinateur distant, ce signal tait envoy aux processus en cours dexcution sur lordinateur lorsque la liaison vers le terminal tait coupe. Ce signal est maintenant utilis pour demander des dmons (processus lancs au dmarrage du systme et tournant en tche de fond) de relire leur chier de conguration. SIGINT : interruption (cest le signal qui est envoy un processus quand on tape Control-C au clavier). SIGFPE : erreur de calcul en virgule ottante, le plus souvent une division par zro. SIGKILL : tue le processus. SIGBUS : erreur de bus. SIGSEGV : violation de segment, gnralement cause dun pointeur nul. SIGPIPE : tentative dcriture dans un tuyau qui na plus de lecteurs. SIGALRM : alarme (chronomtre). SIGTERM : demande au processus de se terminer proprement. SIGCHLD : indique au processus pre quun de ses ls vient de se terminer. SIGWINCH : indique que la fentre dans lequel tourne un programme a chang de taille. SIGUSR1 : signal utilisateur 1. SIGUSR2 : signal utilisateur 2. Il nest pas possible de drouter le programme vers une fonction de traitement sur rception du signal SIGKILL, celui-ci provoque toujours la n du processus. Ceci permet ladministrateur systme de supprimer nimporte quel processus. chaque signal est associ un numro. Les correspondances entre numro et nom des signaux se trouvent gnralement dans le chier /usr/include/signal.h ou dans le chier /usr/include/sys/signal.h suivant le systme.

14.2

Envoi dun signal

Depuis un interprte de commandes

La commande kill permet denvoyer un signal un processus dont on connat le numro (il est facile de le dterminer grce la commande ps) :
kill -HUP 1664

Ici, on envoie le signal SIGHUP au processus numro 1664 (notez quen utilisant la commande kill, on crit le nom du signal sous forme abrge, sans le SIG initial). On aurait galement pu utiliser le numro du signal plutt que son nom : 298

14.3. Interface de programmation

kill -1 1664

On envoie le signal numro 1 (SIGHUP) au processus numro 1664.


Depuis un programme en C

La fonction C kill() permet denvoyer un signal un processus :


#include <sys/types.h> #include <signal.h> /* ... */ pid_t pid = 1664 ; /* ... */ if ( kill(pid,SIGHUP) == -1 ) { /* erreur : le signal na pas pu etre envoye */ }

Ici, on envoie le signal SIGHUP au processus numro 1664. On aurait pu directement mettre 1 la place de SIGHUP, mais lutilisation des noms des signaux rend le programme plus lisible et plus portable (le signal SIGHUP a toujours le numro 1 sur tous les systmes, mais ce nest pas le cas de tous les signaux).

14.3

Interface de programmation

Linterface actuelle de programmation des signaux (qui respecte la norme POSIX.1) repose sur la fonction sigaction(). Lancienne interface, qui utilisait la fonction signal(), est proscrire pour des raisons de portabilit. Nous en parlerons nanmoins rapidement pour indiquer ses dfauts.
La fonction sigaction()

La fonction sigaction() indique au systme comment ragir sur rception dun signal. Elle prend comme paramtres : 1. Le numro du signal auquel on souhaite ragir. 2. Un pointeur sur une structure de type sigaction. Dans cette structure, deux membres nous intressent : sa_handler, qui peut tre : un pointeur vers la fonction de traitement du signal ; SIG_IGN pour ignorer le signal ; SIG_DFL pour restaurer la raction par dfaut. sa_flags, qui indique des options lies la gestion du signal. tant donn larchitecture du noyau Unix, un appel systme interrompu par un signal est toujours avort et renvoie EINTR au processus appelant. Il faut alors relancer 299

Chapitre 14. Manipulation des signaux sous Unix cet appel systme. Nanmoins, sur les Unix modernes, il est possible de demander au systme de redmarrer automatiquement certains appels systme interrompus par un signal. La constante SA_RESTART est utilise cet effet. Le membre sa_mask de la structure sigaction indique la liste des signaux devant tre bloqus pendant lexcution de la fonction de traitement du signal. On ne veut gnralement bloquer aucun signal, cest pourquoi on initialise sa_mask zro au moyen de la fonction sigemptyset(). 3. Un pointeur sur une structure de type sigaction, structure qui sera remplie par la fonction selon lancienne conguration de traitement du signal. Ceci ne nous intresse pas ici, do lutilisation dun pointeur nul. La valeur renvoye par sigaction() est : 0 si tout sest bien pass. -1 si une erreur est survenue. Dans ce cas lappel sigaction() est ignor par le systme. Ainsi, dans lexemple ci-dessous, chaque rception du signal SIGUSR1, le programme sera drout vers la fonction TraiteSignal(), puis reprendra son excution comme si de rien ntait. En particulier, les appels systme qui auraient t interrompus par le signal seront relancs automatiquement par le systme. Listing 14.1 Traitement dun signal
#include #include #include #include #include <sys/types.h> <signal.h> <stdio.h> <stdlib.h> <unistd.h>

/* prototype de la fonction gestionnaire de signal */ /* le parametre est de type int (impose), et il ny */ /* a pas de valeur de retour */ void TraiteSignal(int sig); int main(int argc, char *argv[]) { struct sigaction act; /* une affectation de pointeur de fonction */ /* cest equivalent decrire act.sa_handler = &TraiteSignal */ act.sa_handler = TraiteSignal; /* le masque (ensemble) des signaux non pris en compte est mis */ /* a lensemble vide (aucun signal nest ignore) */ sigemptyset(&act.sa_mask); /* Les appels systemes interrompus par un signal */ /* seront repris au retour du gestionnaire de signal */ act.sa_flags = SA_RESTART; /* enregistrement de la reaction au SIGUSR1 */ if ( sigaction(SIGUSR1,&act,NULL) == -1 ) { /* perror permet dafficher la chaine avec */ /* le message derreur de la derniere commande */ perror("sigaction"); exit(EXIT_FAILURE); } printf("Je suis le processus numero %d.\n" ,getpid());

300

14.3. Interface de programmation

for(;;) { /* boucle infinie equivalente a while (1) */ sigset_t sigmask; /* variable locale a cette boucle */ sigemptyset(&sigmask); /* mask = ensemble vide */ /* on interromp le processus jusqua larrivee dun signal */ /* (mask sil netait pas vide correspondrait aux signaux ignores) */ sigsuspend(&sigmask); printf("Je viens de recevoir un signal et de le traiter\n"); } exit(EXIT_SUCCESS); } void TraiteSignal(int sig) { printf("Reception du signal numero %d.\n", sig); }

La fonction sigsuspend() utilise dans la boucle innie permet de suspendre lexcution du programme jusqu rception dun signal. Son argument est un pointeur sur une liste de signaux devant tre bloqus. Comme nous ne dsirons bloquer aucun signal, cette liste est mise zro au moyen de la fonction sigemptyset(). Les programmes rels se contentent rarement dattendre larrive dun signal sans rien faire, cest pourquoi la fonction sigsuspend() est assez peu utilise dans la ralit.
La fonction signal()

La vieille interface de traitement des signaux utilise la fonction signal() au lieu de sigaction(). Listing 14.2 Ancienne interface de traitement des signaux
#include #include #include #include #include <sys/types.h> <signal.h> <stdio.h> <stdlib.h> <unistd.h>

void TraiteSignal (int sig); int main(int argc, char *argv[]) { if ( signal(SIGUSR1,TraiteSignal ) == SIG_ERR ) { perror("signal"); exit(EXIT_FAILURE); } printf("Je suis le processus numero %d.\n", getpid()); for (;;) { pause(); } exit(EXIT_SUCCESS); } void TraiteSignal (int sig) { printf("Reception du signal numero %d.\n", sig); }

La fonction signal() indique au systme comment ragir sur rception dun signal. Elle prend comme paramtres : 1. Le numro du signal auquel on sintresse. 301

Chapitre 14. Manipulation des signaux sous Unix 2. Une valeur qui peut tre : un pointeur vers la fonction de traitement du signal ; SIG_IGN pour ignorer le signal ; SIG_DFL pour restaurer la raction par dfaut. De manire vidente, la fonction signal() est plus limite que la nouvelle interface offerte par la fonction sigaction() parce quelle ne permet pas dindiquer les options de traitement du signal, en particulier le comportement que lon souhaite pour les appels systme interrompus par la rception du signal. Elle a aussi un effet de bord trs vicieux par rapport la fonction sigaction, cest pourquoi il ne faut plus utiliser la fonction signal(). La norme POSIX.1 ( laquelle la fonction sigaction() est conforme) spcie que, si une fonction de traitement a t indique pour un signal, elle doit tre appele chaque rception de ce signal et cest bien ce qui se passe lorsquon utilise sigaction(). En revanche, lorsquon utilise la fonction signal(), le comportement du systme peut tre diffrent : Sur les Unix BSD, tout se passe comme si lon avait utilis sigaction() et la fonction de traitement est bien appele chaque fois quon reoit le signal. Sur les Unix System V, en revanche, la fonction de traitement est bien appele la premire fois quon reoit le signal, mais elle ne lest pas si on le reoit une seconde fois. Il faut alors refaire un appel la fonction signal() dans la fonction de traitement pour rafrachir la mmoire du systme. Cette diffrence de comportement oblige grer les signaux de deux manires diffrentes suivant la famille dUnix.

14.4

Conclusion

Les signaux sont la forme la plus simple de communication entre processus. Cependant, ils ne permettent pas dchanger des donnes. En revanche, de par leur traitement asynchrone, ils peuvent tre trs utiles pour informer les processus de conditions exceptionnelles (relecture de chier de conguration aprs modication manuelle, par exemple) ou pour synchroniser des processus.

302

14.5. Exercices

14.5

Exercices

Question 1 crire un programme sigusr1 qui, sur rception du signal SIGUSR1, afche le texte Reception du signal SIGUSR1 . Le programme principal sera de la forme :
for (;;) { /* boucle infinie equivalente a while (1) {} */ sigset_t sigmask ; sigemptyset(&sigmask); sigsuspend(&sigmask); }

et il serait judicieux de faire afcher au programme son numro de processus son lancement. Fonction utiliser :
#include <signal.h> int sigaction(int sig, struct sigaction *act, struct sigaction *oact)

Question 2 La fonction alarm() permet de demander au systme dexploitation denvoyer au processus appelant un signal SIGALRM aprs un nombre de secondes donn. crire un programme alarm qui demande lutilisateur de taper un nombre. An de ne pas attendre indniment, un appel alarm() sera fait juste avant lentre du nombre pour que le signal SIGALRM soit reu au bout de 5 secondes. Dans ce cas, on veut que lappel systme de lecture du clavier soit interrompu lors de la rception du signal, il faut donc initialiser le membre sa_flags de la structure sigaction 0. Fonctions utiliser :
#include <signal.h> #include <unistd.h> int sigaction(int sig, struct sigaction *act, struct sigaction *oact) int alarm(int secondes)

Question 3 crire un programme sigchld qui appelle fork(). Le pre mettra en place une fonction pour traiter le signal SIGCHLD (qui afchera, par exemple, Reception du signal SIGCHLD ), attendra 10 secondes, puis afchera Fin du pere . Le ls afchera Fin du fils dans 2 sec , puis se terminera deux secondes aprs. Quel est lintrt du signal SIGCHLD ? Quest-ce quun zombie ?

303

Chapitre 14. Manipulation des signaux sous Unix

14.6

Corrigs
Listing 14.3 Rception dun signal utilisateur

#include #include #include #include #include

<sys/types.h> <signal.h> <stdio.h> <stdlib.h> <unistd.h>

void TraiteSignal(int sig) { printf("Reception du signal SIGUSR1\n"); } int main(int argc, char *argv[]) { struct sigaction act; /* remplissage de la structure sigaction */ act.sa_handler = TraiteSignal; sigemptyset(&act.sa_mask); act.sa_flags = SA_RESTART; /* enregistrement de la reaction au SIGUSR1 */ /* on ne sinteresse pas au precedent gestionnaire de SIGUSR1 */ /* dou le troisieme argument a NULL */ if ( sigaction(SIGUSR1,&act,NULL) == -1 ) { perror("sigaction"); exit(EXIT_FAILURE); } printf("On peut menvoyer un signal SIGUSR1 au moyen de la commande\n"); printf("kill -USR1 %d\n",getpid()); for (;;) { /* boucle infinie en attente dun signal */ sigset_t sigmask; sigemptyset(&sigmask); /* le processus est bloque sans consommer de temps processeur */ /* jusqua larrivee dun signal. Une simple boucle while (1) {}; */ /* fonctionnerait aussi mais consommerait du temps processeur */ sigsuspend(&sigmask); printf("Je viens de recevoir un signal et de le traiter\n"); } exit(EXIT_SUCCESS); }

Listing 14.4 Temporisation dune saisie


#include #include #include #include #include <sys/types.h> <signal.h> <stdio.h> <stdlib.h> <unistd.h>

void TraiteSignal(int sig) { printf("\nReception du signal SIGALRM\n"); } int main(int argc, char *argv[]) { struct sigaction act; int nombre = 1664; /* remplissage de la structure sigaction */ act.sa_handler = TraiteSignal;

304

14.6. Corrigs
sigemptyset(&act.sa_mask); act.sa_flags = 0; /* un appel systeme interrompu nest pas repris */ if ( sigaction(SIGALRM,&act,NULL) == -1 ) { perror("sigaction"); exit(EXIT_FAILURE); } printf("Entrez un nombre [ce sera %d par defaut dans 5 sec] : ",nombre); /* fflush permet de forcer laffichage a lecran */ /* alors que normalement la bibliotheque standard attend le \n */ fflush(stdout); /* demande le reveil dans 5 sec */ alarm(5); /* scanf() se base sur un appel read(), qui sera interrompu */ /* par le SIGALARM sil arrive pendant son execution */ scanf("%d",&nombre); /* on eteint le reveil */ alarm(0); printf("Vous avez choisi la valeur %d\n",nombre); exit(EXIT_SUCCESS); }

Listing 14.5 Dialogue pre / ls par signaux


#include #include #include #include #include <sys/types.h> <signal.h> <stdio.h> <stdlib.h> <unistd.h>

void TraiteSignal(int sig) { printf("Reception du signal SIGCHLD\n"); } int main(int argc, char *argv[]) { struct sigaction act ; sigset_t sigmask ; act.sa_handler = TraiteSignal; sigemptyset(&act.sa_mask); act.sa_flags = SA_RESTART; if ( sigaction(SIGCHLD,&act,NULL) == -1 ) { perror("sigaction"); exit(EXIT_FAILURE); } switch (fork()) { case -1 : /* erreur */ perror("Erreur dans fork()"); exit(EXIT_FAILURE); case 0 : /* processus fils */ /* je ne fais rien, je meurs vite */ printf("Fin du fils dans 2 sec\n"); sleep(2); exit(EXIT_SUCCESS); default : /* processus pere */ sigemptyset(&sigmask); sigsuspend(&sigmask); printf("Un signal a ete recu, fin dans 10sec\n"); sleep(10);

305

Chapitre 14. Manipulation des signaux sous Unix

printf("Fin du pere\n"); exit(EXIT_SUCCESS); } /* switch */ }

306

15
Les tuyaux sous Unix

Les tuyaux 1 permettent un groupe de processus denvoyer des donnes un autre groupe de processus. Ces donnes sont envoyes directement en mmoire sans tre stockes temporairement sur disque, ce qui est donc trs rapide. Tout comme un tuyau de plomberie, un tuyau de donnes a deux cts : un ct permettant dcrire des donnes dedans et un ct permettant de les lire. Chaque ct du tuyau est un descripteur de chier ouvert soit en lecture soit en criture, ce qui permet de sen servir trs facilement, au moyen des fonctions dentre / sortie classiques. La lecture dun tuyau est bloquante, cest--dire que si aucune donne nest disponible en lecture, le processus essayant de lire le tuyau sera suspendu (il ne sera pas pris en compte par lordonnanceur et noccupera donc pas inutilement le processeur) jusqu ce que des donnes soient disponibles. Lutilisation de cette caractristique comme effet de bord peut servir synchroniser des processus entre eux (les processus lecteurs tant synchroniss sur les processus crivains). La lecture dun tuyau est destructrice, cest--dire que si plusieurs processus lisent le mme tuyau, toute donne lue par lun disparat pour les autres. Par exemple, si un processus crit les deux caractres ab dans un tuyau lu par les processus A et B et que A lit un caractre dans le tuyau, il lira le caractre a qui disparatra immdiatement du tuyau sans que B puisse le lire. Si B lit alors un caractre dans le tuyau, il lira donc le caractre b que A, son tour, ne pourra plus y lire. Si lon veut donc envoyer des informations identiques plusieurs processus, il est ncessaire de crer un tuyau vers chacun deux. De mme quun tuyau en cuivre a une longueur nie, un tuyau de donnes une capacit nie. Un processus essayant dcrire dans un tuyau plein se verra suspendu en attendant quun espace sufsant se libre. Vous avez sans doute dj utilis des tuyaux. Par exemple, lorsque vous tapez
menthe22> ls | wc -l

linterprte de commandes relie la sortie standard de la commande ls lentre standard de la commande wc au moyen dun tuyau.
1. Le terme anglais est pipe, que lon traduit gnralement par tuyau ou tube.

307

Chapitre 15. Les tuyaux sous Unix Les tuyaux sont trs utiliss sous UNIX pour faire communiquer des processus entre eux. Ils ont cependant deux contraintes : les tuyaux ne permettent quune communication unidirectionnelle ; les processus pouvant communiquer au moyen dun tuyau doivent tre issus dun anctre commun qui devra avoir cr le tuyau.

15.1

Manipulation des tuyaux

Lappel systme pipe()

Un tuyau se cre trs simplement au moyen de lappel systme pipe() :


#include <unistd.h> int tuyau[2], retour; retour = pipe(tuyau); if ( retour == -1 ) { /* erreur : le tuyau na pas pu etre cree */ }

Largument de pipe() est un tableau de deux descripteurs de chier (un descripteur de chier est du type int en C) similaires ceux renvoys par lappel systme open() et qui sutilisent de la mme manire. Lorsque le tuyau a t cr, le premier descripteur, tuyau[0], reprsente le ct lecture du tuyau et le second, tuyau[1], reprsente le ct criture. Un moyen mnmotechnique pour se rappeler quelle valeur reprsente quel ct est de rapprocher ceci de lentre et de la sortie standard. Lentre standard, dont le numro du descripteur de chier est toujours 0, est utilise pour lire au clavier : 0 lecture. La sortie standard, dont le numro du descripteur de chier est toujours 1, est utilise pour crire lcran : 1 criture. Nanmoins, pour faciliter la lecture des programmes et viter des erreurs, il est prfrable de dnir deux constantes dans les programmes qui utilisent les tuyaux :
#define LECTURE 0 #define ECRITURE 1

Mise en place dun tuyau

La mise en place dun tuyau permettant deux processus de communiquer est relativement simple. Prenons lexemple dun processus qui cre un ls auquel il va envoyer des donnes : 1. Le processus pre cre le tuyau au moyen de pipe(). 2. Puis il cre un processus ls grce fork(). Les deux processus partagent donc le tuyau. 3. Puisque le pre va crire dans le tuyau, il na pas besoin du ct lecture, donc il le ferme. 308

15.1. Manipulation des tuyaux

F IGURE 15.1 Une fois le tuyau cr, la duplication va offrir deux points dentre et deux points de sortie sur le tuyau puisque les descripteurs sont partags entre le processus pre et le processus ls. Nous avons donc un tuyau possdant deux lecteurs et deux crivains, il faut imprativement faire quelque chose !

4. De mme, le ls ferme le ct criture. 5. Le processus pre peut ds lors envoyer des donnes au ls. Le tuyau doit tre cr avant lappel la fonction fork() pour quil puisse tre partag entre le processus pre et le processus ls (les descripteurs de chiers ouverts dans le pre sont hrits par le ls aprs lappel fork()). Comme indiqu dans lintroduction, un tuyau ayant plusieurs lecteurs peut poser des problmes, cest pourquoi le processus pre doit fermer le ct lecture aprs lappel fork() (il nen a de toute faon pas besoin). Il en va de mme pour un tuyau ayant plusieurs crivains donc le processus ls doit aussi fermer le ct criture. Omettre de fermer le ct inutile peut entraner lattente innie dun des processus si lautre se termine. Imaginons que le processus ls nait pas ferm le ct criture du tuyau. Si le processus pre se termine, le ls va rester bloqu en lecture du tuyau sans recevoir derreur puisque son descripteur en criture est toujours valide. En revanche, sil avait ferm le ct criture, il aurait reu un code derreur en essayant de lire le tuyau, ce qui laurait inform de la n du processus pre. Le programme suivant illustre cet exemple, en utilisant les appels systme read() et write() pour la lecture et lcriture dans le tuyau : Listing 15.1 Utilisation des tuyaux
#include #include #include #include #include <sys/types.h> <stdio.h> <stdlib.h> <string.h> <unistd.h>

309

Chapitre 15. Les tuyaux sous Unix

#define LECTURE 0 #define ECRITURE 1 int main(int argc, char *argv[]) { int tuyau[2], nb, i; char donnees[10]; if (pipe(tuyau) == -1) { /* creation du pipe */ perror("Erreur dans pipe()"); exit(EXIT_FAILURE); } switch (fork()) { /* les deux processus partagent le pipe */ case -1 : /* erreur */ perror("Erreur dans fork()"); exit(EXIT_FAILURE); case 0 : /* processus fils, lecteur */ close(tuyau[ECRITURE]); /* on ferme le cote ecriture */ /* on peut alors lire dans le pipe */ nb = read(tuyau[LECTURE], donnees, sizeof(donnees)); for (i = 0; i < nb; i++) { putchar(donnees[i]); } putchar(\n); close(tuyau[LECTURE]); exit(EXIT_SUCCESS); default : /* processus pere, ecrivain */ close(tuyau[LECTURE]); /* on ferme le cote lecture */ strncpy(donnees, "bonjour", sizeof(donnees)); /* on peut ecrire dans le pipe */ write(tuyau[ECRITURE], donnees, strlen(donnees)); close(tuyau[ECRITURE]); exit(EXIT_SUCCESS); } }

Fonctions dentres / sorties standard avec les tuyaux

Puisquun tuyau sutilise comme un chier, il serait agrable de pouvoir utiliser les fonctions dentres / sorties standard (fprintf(), fscanf()...) au lieu de read() et write(), qui sont beaucoup moins pratiques. Pour cela, il faut transformer les descripteurs de chiers en pointeurs de type FILE *, comme ceux renvoys par fopen(). Nous pouvons le faire en utilisant la fonction fdopen() vue dans le chapitre consacr aux entres / sorties (cf 11.3). Rappelons quelle prend en argument le descripteur de chier transformer et le mode daccs au chier ("r" pour la lecture et "w" pour lcriture) et renvoie le pointeur de type FILE * permettant dutiliser les fonctions dentre/sortie standard. Lexemple suivant illustre lutilisation de la fonction fdopen() : Listing 15.2 Dialogue pre / ls
#include #include #include #include <sys/types.h> <stdio.h> <stdlib.h> <unistd.h>

310

15.1. Manipulation des tuyaux

#define LECTURE 0 #define ECRITURE 1 int main (int argc, char *argv[]) { int tuyau[2]; char str[100]; FILE *mon_tuyau ; if ( pipe(tuyau) == -1 ) { perror("Erreur dans pipe()"); exit(EXIT_FAILURE); } switch (fork()) { case -1 : /* erreur */ perror("Erreur dans fork()"); exit(EXIT_FAILURE); case 0 : /* processus fils, lecteur */ close(tuyau[ECRITURE]); /* ouvre un descripteur de flot FILE * a partir */ /* du descripteur de fichier UNIX */ mon_tuyau = fdopen(tuyau[LECTURE], "r"); if (mon_tuyau == NULL) { perror("Erreur dans fdopen()"); exit(EXIT_FAILURE); } /* mon_tuyau est un FILE * accessible en lecture */ fgets(str, sizeof(str), mon_tuyau); printf("Mon pere a ecrit : %s\n", str); /* il faut faire fclose(mon_tuyau) ou a la rigueur */ /* close(tuyau[LECTURE]) mais surtout pas les deux */ fclose(mon_tuyau); exit(EXIT_SUCCESS); default : /* processus pere, ecrivain */ close(tuyau[LECTURE]); mon_tuyau = fdopen(tuyau[ECRITURE], "w"); if (mon_tuyau == NULL) { perror("Erreur dans fdopen()"); exit(EXIT_FAILURE); } /* mon_tuyau est un FILE * accessible en ecriture */ fprintf(mon_tuyau, "petit message\n"); fclose(mon_tuyau); exit(EXIT_SUCCESS); } }

Il faut cependant garder lesprit que les fonctions dentres / sorties standard utilisent une zone de mmoire tampon lors de leurs oprations de lecture ou dcriture. Lutilisation de cette zone tampon permet doptimiser, en les regroupant, les accs au disque avec des chiers classiques mais elle peut se rvler particulirement gnante avec un tuyau. Dans ce cas, la fonction fflush() peut se rvler trs utile puisquelle permet dcrire la zone tampon dans le tuyau sans attendre quelle soit remplie. Il est noter que lappel fflush() tait inutile dans lexemple prcdent en raison de son appel implicite lors de la fermeture du tuyau par fclose(). Lexemple suivant montre lutilisation de la fonction fflush() : Listing 15.3 Forcer lcriture dans le tuyau 311

Chapitre 15. Les tuyaux sous Unix

#include #include #include #include

<sys/types.h> <stdio.h> <stdlib.h> <unistd.h>

#define LECTURE 0 #define ECRITURE 1 int main(int argc, char *argv[]) { int tuyau[2]; char str[100]; FILE *mon_tuyau; if ( pipe(tuyau) == -1 ) { perror("Erreur dans pipe()"); exit(EXIT_FAILURE); } switch (fork()) { case -1 : /* erreur */ perror("Erreur dans fork()"); exit(EXIT_FAILURE); case 0 : /* processus fils, lecteur */ close(tuyau[ECRITURE]); mon_tuyau = fdopen(tuyau[LECTURE], "r"); if (mon_tuyau == NULL) { perror("Erreur dans fdopen()"); exit(EXIT_FAILURE); } fgets(str, sizeof(str), mon_tuyau); printf("[fils] Mon pere a ecrit : %s\n", str); fclose(mon_tuyau); exit(EXIT_SUCCESS); default : /* processus pere, ecrivain */ close(tuyau[LECTURE]); mon_tuyau = fdopen(tuyau[ECRITURE], "w"); if (mon_tuyau == NULL) { perror("Erreur dans fdopen()"); exit(EXIT_FAILURE); } fprintf(mon_tuyau, "un message de test\n"); printf("[pere] Je viens decrire dans le tuyau,\n"); printf(" mais les donnees sont encore\n"); printf(" dans la zone de memoire tampon.\n"); sleep(5); fflush(mon_tuyau); printf("[pere] Je viens de forcer lecriture\n"); printf(" des donnees de la memoire tampon\n"); printf(" vers le tuyau.\n"); printf(" Jattends 5 secondes avant de fermer\n"); printf(" le tuyau et de me terminer.\n"); sleep(5); fclose(mon_tuyau); exit(EXIT_SUCCESS); } }

Redirection des entre et sorties standard

Une technique couramment employe avec les tuyaux est de relier la sortie standard dune commande lentre standard dune autre, comme quand on tape 312

15.1. Manipulation des tuyaux

ls | wc -l

Le problme, cest que la commande ls est conue pour afcher lcran et pas dans un tuyau. De mme pour wc qui est conue pour lire au clavier. Pour rsoudre ce problme, Unix fournit une mthode lgante. Les fonctions dup() et dup2() permettent de dupliquer le descripteur de chier pass en argument :
#include <unistd.h> int dup(int descripteur); int dup2(int descripteur, int copie);

Dans le cas de dup2(), on passe en argument le descripteur de chier dupliquer ainsi que le numro du descripteur souhait pour la copie. Le descripteur copie est ventuellement ferm avant dtre rallou. Pour dup() 2 , le plus petit descripteur de chier non encore utilis permet alors daccder au mme chier que descripteur. Mais comment dterminer le numro de ce plus petit descripteur ? Sachant que lentre standard a toujours 0 comme numro de descripteur et que la sortie standard a toujours 1 comme numro de descripteur, cest trs simple lorsquon veut rediriger lun de ces descripteurs (ce qui est quasiment toujours le cas). Prenons comme exemple la redirection de lentre standard vers le ct lecture du tuyau : 1. On ferme lentre standard (descripteur de chier numro 0 ou, de manire plus lisible, STDIN_FILENO, dni dans <unistd.h> ). Le plus petit numro de descripteur non utilis est alors 0. 2. On appelle dup() avec le numro de descripteur du ct lecture du tuyau comme argument. Lentre standard est alors connecte au ct lecture du tuyau. 3. On peut alors fermer le descripteur du ct lecture du tuyau qui est maintenant inutile puisquon peut y accder par lentre standard. La faon de faire pour connecter la sortie standard avec le ct criture du tuyau est exactement la mme. Le programme suivant montre comment lancer ls | wc -l en utilisant la fonction dup2() : Listing 15.4 Duplication des descripteurs
#include #include #include #include #include <sys/types.h> <stdio.h> <stdlib.h> <string.h> <unistd.h>

#define LECTURE 0 #define ECRITURE 1 int main(int argc, char *argv[]) { int tuyau[2];

2. Il est recommand dutiliser plutt dup2() que dup() pour des raisons de simplicit.

313

Chapitre 15. Les tuyaux sous Unix

if (pipe(tuyau) == -1) { perror("Erreur dans pipe()"); exit(EXIT_FAILURE); } switch (fork()) { case -1 : /* erreur */ perror("Erreur dans fork()"); exit(EXIT_FAILURE); case 0 : /* processus fils, ls , ecrivain */ close(tuyau[LECTURE]); /* dup2 va brancher le cote ecriture du tuyau */ /* comme sortie standard du processus courant */ if (dup2(tuyau[ECRITURE], STDOUT_FILENO) == -1) { perror("Erreur dans dup2()"); } /* on ferme le descripteur qui reste pour */ /* << eviter les fuites >> ! */ close(tuyau[ECRITURE]); /* ls en ecrivant sur stdout envoie en fait dans le */ /* tuyau sans le savoir */ if (execlp("ls", "ls", NULL) == -1) { perror("Erreur dans execlp()"); exit(EXIT_FAILURE); } default : /* processus pere, wc , lecteur */ close(tuyau[ECRITURE]); /* dup2 va brancher le cote lecture du tuyau */ /* comme entree standard du processus courant */ if (dup2(tuyau[LECTURE], STDIN_FILENO) == -1) { perror("Erreur dans dup2()"); } /* on ferme le descripteur qui reste */ close(tuyau[LECTURE]); /* wc lit lentree standard, et les donnees */ /* quil recoit proviennent du tuyau */ if (execlp("wc", "wc", "-l", NULL) == -1) { perror("Erreur dans execlp()"); exit(EXIT_FAILURE); } } exit(EXIT_SUCCESS); }

La squence utilisant dup2() :


if ( dup2(tuyau[ECRITURE], STDOUT_FILENO) == -1 ) { perror("Erreur dans dup2()"); } close(tuyau[ECRITURE]);

est quivalente celle-ci en utilisant dup() :


close(STDOUT_FILENO); if ( dup(tuyau[ECRITURE]) == -1 ) { perror("Erreur dans dup()"); } close(tuyau[ECRITURE]);

314

15.1. Manipulation des tuyaux


Synchronisation de deux processus au moyen dun tuyau

Un effet de bord intressant des tuyaux est la possibilit de synchroniser deux processus. En effet, un processus tentant de lire un tuyau dans lequel il ny a rien est suspendu jusqu ce que des donnes soient disponibles 3 . Donc, si le processus qui crit dans le tuyau le fait plus lentement que celui qui lit, il est possible de synchroniser le processus lecteur sur le processus crivain. Lexemple suivant met en uvre une utilisation possible de cette synchronisation : Listing 15.5 Synchronisation des lectures / critures
#include #include #include #include <sys/types.h> <stdio.h> <stdlib.h> <unistd.h>

#define LECTURE 0 #define ECRITURE 1 int main(int argc, char *argv[]) { int tuyau[2], i; char car; if (pipe(tuyau) == -1) { perror("Erreur dans pipe()"); exit(EXIT_FAILURE); } switch (fork()) { case -1 : /* erreur */ perror("Erreur dans fork()"); exit(EXIT_FAILURE); case 0 : /* processus fils, lecteur */ close(tuyau[ECRITURE]); /* on lit les caracteres un a un */ while (read(tuyau[LECTURE], &car, 1) != 0 ) { putchar(car); /* affichage immediat du caracter lu */ fflush(stdout); } close(tuyau[LECTURE]); putchar(\n); exit(EXIT_SUCCESS); default : /* processus pere, ecrivain */ close(tuyau[LECTURE]); for (i = 0; i < 10; i++) { /* on obtient le caractere qui represente le chiffre i */ /* en prenant le i-eme caractere a partir de 0 */ car = 0+i; /* on ecrit ce seul caractere */ write(tuyau[ECRITURE], &car, 1); sleep(1); /* et on attend 1 sec */ } close(tuyau[ECRITURE]); exit(EXIT_SUCCESS); } }

3. moins quil nait indiqu au systme, grce loption O_NONBLOCK de la fonction fcntl(), de lui renvoyer une erreur au lieu de le suspendre.

315

Chapitre 15. Les tuyaux sous Unix


Le signal SIGPIPE

Lorsquun processus crit dans un tuyau qui na plus de lecteurs (parce que les processus qui lisaient le tuyau sont termins ou ont ferm le ct lecture du tuyau), ce processus reoit le signal SIGPIPE. Comme le comportement par dfaut de ce signal est de terminer le processus, il peut tre intressant de le grer an, par exemple, davertir lutilisateur puis de quitter proprement le programme. Les lecteurs curieux pourront approfondir lutilisation des signaux en consultant les pages de manuel des fonctions suivantes : sigaction() ; sigemptyset() ; sigsuspend() ; ... Lquipe enseignante se tient leur disposition pour des complments dinformation.
Autres moyens de communication entre processus

Les tuyaux souffrent de deux limitations : ils ne permettent quune communication unidirectionnelle ; les processus pouvant communiquer au moyen dun tuyau doivent tre issus dun anctre commun. Ainsi, dautres moyens de communication entre processus ont t dvelopps pour pallier ces inconvnients : Les tuyaux nomms (FIFOs) sont des chiers spciaux qui, une fois ouverts, se comportent comme des tuyaux (les donnes sont envoyes directement sans tre stockes sur disque). Comme ce sont des chiers, ils peuvent permettre des processus quelconques (pas ncessairement issus dun mme anctre) de communiquer. Les sockets permettent une communication bidirectionnelle entre divers processus, fonctionnant sur la mme machine ou sur des machines relies par un rseau. Ce sujet est abord dans le TP 16. Les tuyaux nomms sutilisent facilement et sont abords dans le paragraphe suivant.
Les tuyaux nomms

Comme on vient de le voir, linconvnient principal des tuyaux est de ne fonctionner quavec des processus issus dun anctre commun. Pourtant, les mcanismes de communication mis en jeu dans le noyau sont gnraux et, en fait, seul lhritage des descripteurs de chiers aprs un fork() impose cette restriction. Pour sen affranchir, il faut que les processus dsirant communiquer puissent dsigner le tuyau quils souhaitent utiliser. Ceci se fait grce au systme de chiers. Un tuyau nomm est donc un chier : 316

15.1. Manipulation des tuyaux

menthe22> mkfifo fifo menthe22> ls -l fifo prw-r--r-- 1 in201

in201

0 Jan 10 17:22 fifo

Il sagit cependant dun chier dun type particulier, comme le montre le p dans lafchage de ls. Une fois cr, un tuyau nomm sutilise trs facilement :
menthe22> echo coucou > fifo & [1] 25312 menthe22> cat fifo [1] + done echo coucou > fifo coucou

Si lon fait abstraction des afchages parasites du shell, le tuyau nomm se comporte tout fait comme on sy attend. Bien que la faon de lutiliser puisse se rvler trompeuse, un tuyau nomm transfre bien ses donnes dun processus lautre en mmoire, sans les stocker sur disque. La seule intervention du systme de chiers consiste permettre laccs au tuyau par lintermdiaire de son nom. Il faut noter que : Un processus tentant dcrire dans un tuyau nomm ne possdant pas de lecteurs sera suspendu jusqu ce quun processus ouvre le tuyau nomm en lecture. Cest pourquoi, dans lexemple, echo a t lanc en tche de fond, an de pouvoir rcuprer la main dans le shell et dutiliser cat. On peut tudier ce comportement en travaillant dans deux fentres diffrentes. De mme, un processus tentant de lire dans un tuyau nomm ne possdant pas dcrivains se verra suspendu jusqu ce quun processus ouvre le tuyau nomm en criture. On peut tudier ce comportement en reprenant lexemple mais en lanant dabord cat puis echo. En particulier, ceci signie quun processus ne peut pas utiliser un tuyau nomm pour stocker des donnes an de les mettre la disposition dun autre processus une fois le premier processus termin. On retrouve le mme phnomne de synchronisation quavec les tuyaux classiques. En C, un tuyau nomm se cre au moyen de la fonction mkfifo() :
#include <sys/stat.h> int retour; retour = mkfifo("fifo", 0644); if ( retour == -1 ) { /* erreur : le tuyau nomme na pas pu etre cree */ }

Il doit ensuite tre ouvert (grce open() ou fopen()) et sutilise au moyen des fonctions dentres / sorties classiques. 317

Chapitre 15. Les tuyaux sous Unix

15.2

Exercices

Question 1 crire un programme tuyau1 qui met en place un tuyau et cre un processus ls. Le processus ls lira des lignes de caractres du clavier, les enverra au processus pre par lintermdiaire du tuyau et ce dernier les afchera lcran. Le processus ls se terminera lorsquon tapera Control-D (qui fera renvoyer NULL fgets()), aprs avoir ferm le tuyau. Le processus ls se terminera de la mme faon. Cet exercice permet de mettre en vidence la synchronisation de deux processus au moyen dun tuyau. Fonctions utiliser :
#include <sys/types.h> #include <stdio.h> #include <unistd.h> int pipe(int tuyau[2]) pid_t fork() FILE *fdopen(int fichier, char *mode) char *fgets(char *chaine, size_t taille, FILE *fichier)

Question 2 On se propose de raliser un programme mettant en communication deux commandes par lintermdiaire dun tuyau, comme si lon tapait dans un shell
ls | wc -l

La sortie standard de la premire commande est relie lentre standard de la seconde. crire un programme tuyau2 qui met en place un tuyau et cre un processus ls. Le processus pre excutera la commande avec option sort +4 -n grce la fonction execlp() et le processus ls excutera la commande avec option ls -l. Pour cela, il faut auparavant rediriger lentre standard du processus pre vers le ct lecture du tuyau et la sortie standard du processus ls vers le ct criture du tuyau au moyen de la fonction dup2(). Fonctions utiliser :
#include <sys/types.h> #include <unistd.h> int pipe(int tuyau[2]) pid_t fork() int dup2(int descripteur, int copie) int execlp(const char *fichier, const char *arg, ...)

Question 3 Reprenez le programme myshell du TP prcdent et modiez-le an quil reconnaisse aussi les commandes de la forme
ls -l | sort +4 -n

318

15.2. Exercices Fonctions utiliser :


#include <sys/types.h> #include <string.h> #include <unistd.h> char *strtok(char *chaine, char *separateurs) int pipe(int tuyau[2]) pid_t fork() int dup2(int descripteur, int copie) int execlp(const char *fichier, const char *arg, ...)

La fonction strtok() vous sera utile pour analyser la ligne tape au clavier. Si vous avez le temps, essayez de modier le programme pour quil reconnaisse un enchanement quelconque de commandes :
cat toto | grep tata | wc -l

Dans quel ordre vaut-il mieux lancer les processus ls ?

319

Chapitre 15. Les tuyaux sous Unix

15.3

Corrigs
Listing 15.6 Corrig du premier exercice

#include #include #include #include

<sys/types.h> <stdio.h> <stdlib.h> <unistd.h>

#define LECTURE 0 #define ECRITURE 1 int main(int argc, char *argv[]) { int tuyau[2]; FILE *mon_tuyau; char ligne[80]; if (pipe(tuyau) == -1) { perror("Erreur dans pipe()"); exit(EXIT_FAILURE); } switch (fork()) { case -1 : /* erreur */ perror("Erreur dans fork()"); exit(EXIT_FAILURE); case 0 : /* processus fils, ecrivain */ close(tuyau[LECTURE]); /* on ferme le cote lecture */ /* ouvre un descripteur de flot FILE * a partir */ /* du descripteur de fichier UNIX */ mon_tuyau = fdopen(tuyau[ECRITURE], "w"); if (mon_tuyau == NULL) { perror("Erreur dans fdopen()"); exit(EXIT_FAILURE); } printf("Je suis le fils, tapez des phrases svp\n"); /* on lit chaque ligne au clavier */ /* mon_tuyau est un FILE * accessible en ecriture */ while (fgets(ligne, sizeof(ligne), stdin) != NULL) { /* on ecrit la ligne dans le tuyau */ fprintf(mon_tuyau, "%s", ligne); fflush(mon_tuyau); } /* il faut faire fclose(mon_tuyau) ou a la rigueur */ /* close(tuyau[LECTURE]) mais surtout pas les deux */ fclose(mon_tuyau); exit(EXIT_SUCCESS); default : /* processus pere, lecteur */ close(tuyau[ECRITURE]); /* on ferme le cote ecriture */ mon_tuyau = fdopen(tuyau[LECTURE], "r"); if (mon_tuyau == NULL) { perror("Erreur dans fdopen()"); exit(EXIT_FAILURE); } /* on lit chaque ligne depuis le tuyau */ /* mon_tuyau est un FILE * accessible en lecture */ while (fgets(ligne, sizeof(ligne), mon_tuyau)) { /* et on affiche la ligne a lecran */ /* la ligne contient deja un \n a la fin */ printf(">>> %s", ligne); } fclose(mon_tuyau); exit(EXIT_SUCCESS);

320

15.3. Corrigs

} }

Listing 15.7 Corrig du deuxime exercice


#include #include #include #include <sys/types.h> <stdio.h> <stdlib.h> <unistd.h>

#define LECTURE 0 #define ECRITURE 1 int main(int argc, char *argv[]) { int tuyau[2]; if (pipe(tuyau) == -1) { perror("Erreur dans pipe()."); exit(EXIT_FAILURE); } switch (fork()) { case -1 : /* erreur */ perror("Erreur dans fork()."); exit(EXIT_FAILURE); case 0 : /* processus fils */ close(tuyau[LECTURE]); /* dup2 va brancher le cote ecriture du tuyau */ /* comme sortie standard du processus courant */ dup2(tuyau[ECRITURE],STDOUT_FILENO); /* on ferme le descripteur qui reste */ close(tuyau[ECRITURE]); if (execlp("ls", "ls", "-l", NULL) == -1) { perror("Erreur dans execlp()"); exit(EXIT_FAILURE); } default : /* processus pere */ close(tuyau[ECRITURE]); /* dup2 va brancher le cote lecture du tuyau */ /* comme entree standard du processus courant */ dup2(tuyau[LECTURE], STDIN_FILENO); /* on ferme le descripteur qui reste */ close(tuyau[LECTURE]); if (execlp("sort", "sort", "+4", "-n", NULL) == -1) { perror("Erreur dans execlp()"); exit(EXIT_FAILURE); } } exit(EXIT_SUCCESS); }

Listing 15.8 Corrig du troisime exercice


#include #include #include #include #include #include <sys/types.h> <stdio.h> <stdlib.h> <string.h> <unistd.h> <sys/wait.h> 0

#define LECTURE

321

Chapitre 15. Les tuyaux sous Unix

#define ECRITURE 1 int main(int argc, char *argv[]) { char ligne[80], *arg1[10], *arg2[10], *tmp; int i, tuyau[2]; printf("myshell> "); /* lecture de lentree standard ligne par ligne */ while (fgets(ligne, sizeof(ligne), stdin) != NULL) { /* fgets lit egalement le caractere de fin de ligne */ if (strcmp(ligne, "exit\n") == 0) { exit(EXIT_SUCCESS); } /* on decoupe la ligne en sarretant des que */ /* lon trouve un | : cela donne la premiere commande */ for (tmp = strtok(ligne, " \t\n"), i = 0; tmp != NULL && strcmp(tmp , "|") != 0; tmp = strtok(NULL, " \t\n"), i++) { arg1[i] = tmp; } arg1[i] = NULL; /* on decoupe la suite si necessaire pour obtenir la */ /* deuxieme commande */ arg2[0] = NULL; if (tmp != NULL && strcmp(tmp, "|") == 0) { for (tmp = strtok(NULL, " \t\n"), i = 0; tmp != NULL; tmp = strtok(NULL, " \t\n"), i++) { arg2[i] = tmp; } arg2[i] = NULL; } if (arg2[0] != NULL) { /* il y a une deuxieme commande */ pipe(tuyau); } switch (fork()) { case -1 : /* erreur */ perror("Erreur dans fork()"); exit(EXIT_FAILURE); case 0 : /* processus fils */ if (arg2[0] != NULL) { /* tuyau */ close(tuyau[LECTURE]); dup2(tuyau[ECRITURE], STDOUT_FILENO); close(tuyau[ECRITURE]); } execvp(arg1[0], arg1); /* on narrivera jamais ici, sauf en cas derreur */ perror("Erreur dans execvp()"); exit(EXIT_FAILURE); default : /* processus pere */ if (arg2[0] != NULL) { /* on recree un autre fils pour la deuxieme commande */ switch (fork()) { case -1 : /* erreur */ perror("Erreur dans fork()"); exit(EXIT_FAILURE); case 0 : /* processus fils */ close(tuyau[ECRITURE]); dup2(tuyau[LECTURE], STDIN_FILENO); close(tuyau[LECTURE]);

322

15.3. Corrigs

execvp(arg2[0], arg2); perror("Erreur dans execvp()"); exit(EXIT_FAILURE); default : /* processus pere */ /* on ferme les deux cote du tuyau */ close(tuyau[LECTURE]); close(tuyau[ECRITURE]); /* et on attend la fin dun fils */ wait(NULL); } } /* on attend la fin du fils */ wait(NULL); } printf("myshell> "); } exit(EXIT_SUCCESS); }

Listing 15.9 Corrig du quatrime exercice


#include #include #include #include #include #include #include <sys/types.h> <signal.h> <stdio.h> <stdlib.h> <string.h> <unistd.h> <sys/wait.h>

#define LECTURE 0 #define ECRITURE 1 /* le type liste chainee darguments */ typedef struct argument { char *arg; struct argument *suivant; } *t_argument; /* le type commande, liste doublement chainee */ typedef struct commande { int nombre; t_argument argument; struct commande *suivant; struct commande *precedent; } *t_commande; int main(int argc ,char *argv[]) { char ligne[80]; t_commande commande; commande = (t_commande)malloc(sizeof(struct commande)); printf("myshell> "); while (fgets(ligne, sizeof(ligne), stdin) != NULL) { char *tmp; t_commande com; t_argument arg, precedent; int tuyau[2], ecriture; pid_t pid, dernier; if (strcmp(ligne ,"exit\n") == 0) { exit(EXIT_SUCCESS); }

323

Chapitre 15. Les tuyaux sous Unix

/* premier maillon */ com = commande; com->argument = NULL ; com->nombre = 0 ; com->suivant = NULL ; com->precedent = NULL ; precedent = NULL ; do { /* remplissage des arguments dune commande */ /* avec allocation des maillons de la liste darguments */ for (tmp = strtok((commande->argument == NULL)?ligne:NULL, " \t\n"); tmp != NULL && strcmp(tmp, "|") != 0; tmp = strtok(NULL, " \t\n")) { arg = (t_argument)malloc(sizeof(struct argument)); if (arg == NULL) { perror("Erreur dans malloc()"); exit(EXIT_FAILURE); } /* chainage de la liste darguments */ if (precedent != NULL) { precedent->suivant = arg; precedent = arg; } else { com->argument = arg; precedent = arg; } arg->arg = tmp; arg->suivant = NULL; com->nombre++; } if (tmp != NULL) { /* vrai uniquement si on a un | */ /* allocation dune nouvelle commande */ com->suivant = (t_commande)malloc(sizeof(struct commande)); if (com->suivant == NULL) { perror("Erreur dans malloc()"); exit(EXIT_FAILURE); } /* chainage double du nouveau maillon */ com->suivant->precedent = com; com = com->suivant; com->argument = NULL; com->nombre = 0; com->suivant = NULL; precedent = NULL; } } while (tmp != NULL); /* do */ /* on cree les processus en remontant la ligne de commande */ for (; com != NULL; com = com->precedent) { int i; char **args; /* creation du tuyau */ if (com->precedent != NULL) { if (pipe(tuyau) == -1) { perror("Erreur dans pipe()"); exit(EXIT_FAILURE); } } /* creation du processus devant executer la commande */

324

15.3. Corrigs

switch (pid = fork()) { case -1 : /* erreur */ perror("Erreur dans fork()"); exit(EXIT_FAILURE); case 0 : /* processus fils : commande */ if (com->precedent != NULL) { /* redirection de stdin */ close(tuyau[ECRITURE]); dup2(tuyau[LECTURE], STDIN_FILENO); close(tuyau[LECTURE]); } if (com->suivant != NULL) { /* redirection de stdout */ /* ecriture est le cote ecriture du tuyau */ /* allant vers la commande plus a droite sur la ligne */ /* qui a ete lancee au tour de boucle precedent */ dup2(ecriture, STDOUT_FILENO); close(ecriture); } /* allocation du tableau de pointeurs darguments */ args = (char **)malloc((com->nombre+1)*sizeof(char *)); for (i = 0, arg = com->argument; arg != NULL; i++, arg = arg->suivant) { args[i] = arg->arg; } args[i] = NULL; /* recouvrement par la commande */ execvp(args[0], args); perror("erreur execvp"); exit(EXIT_FAILURE); default : /* processus pere : shell */ if (com->suivant == NULL) { dernier = pid; } /* on ferme les extremites inutiles des tuyaux */ if (com->suivant != NULL) { close(ecriture); } if (com->precedent != NULL) { close(tuyau[LECTURE]); /* attention, cest lentree du bloc de droite */ /* il va servir lors du tour de boucle suivant */ ecriture = tuyau[ECRITURE]; } else { /* on attend la fin du dernier processus directement */ waitpid(dernier, NULL, 0); } } } /* liberation de la memoire allouee */ /* on reavance jusqua la derniere commande */ for (com = commande; com->suivant != NULL; com = com->suivant) ; while (com != NULL) { /* pour toutes les commandes */ arg = com->argument; while (arg != NULL) { /* on libere les arguments */ t_argument suivant; suivant = arg->suivant; free(arg); arg = suivant; } com = com->precedent; if (com != NULL) {

325

Chapitre 15. Les tuyaux sous Unix

free(com->suivant); /* on libere la commande */ } } printf("myshell> "); } free(commande); exit(EXIT_SUCCESS); }

Enn voici une possibilit de code nutilisant ni strtok() ni allocation dynamique. La gestion complte de lentre standard pour le premier processus de la ligne est laisse aux soins du lecteur. Listing 15.10 Une deuxime version du corrig du quatrime exercice
#include #include #include #include #include #include #include #include <sys/types.h> <signal.h> <stdio.h> <stdlib.h> <string.h> <unistd.h> <ctype.h> <sys/wait.h>

#define LECTURE 0 #define ECRITURE 1 /* nombre maximal darguments dune commande */ #define MAXARG 10 /* compte le nombre darguments de la commande */ /* la plus a droite de la ligne, sarrete au | */ char *recherche_pipe(char *str, int *narg) { int l,i; int n = 1; l = strlen(str); for (i = l-1; str[i] != | && i > 0; i--) { if (isspace(str[i-1]) && !isspace(str[i])) n++; } *narg = n; if (str[i] == |) { str[i] = \0; return(str+i+1); } return(str); } /* Decoupage de la ligne en place (sans recopie) */ void decoupe_commande(char *ligne, char *arg[]) { int i=0; /* on avance jusquau premier argument */ while (isspace(*ligne)) ligne++; /* on traite les arguments tant que ce nest */ /* pas la fin de la ligne */ while (*ligne != \0 && *ligne != \n) { arg[i++]=ligne; /* on avance jusquau prochain espace */ while (!isspace(*ligne) && *ligne!=\0) ligne++; /* on remplace les espaces par \0 ce qui marque */ /* la fin du parametre precedent */ while (isspace(*ligne) && *ligne!=\0)

326

15.3. Corrigs

*ligne++=\0; } arg[i]=NULL; }

/* execute un processus prenant ecriture en entree */ int execute_commande(char *arg[], int *ecriture) { int tuyau[2]; int pid; pipe(tuyau); switch (pid = fork()) { case -1 : /* erreur */ perror("Erreur dans fork()"); exit(EXIT_FAILURE); case 0 : /* processus fils */ close(tuyau[ECRITURE]); dup2(tuyau[LECTURE], STDIN_FILENO); close(tuyau[LECTURE]); if (*ecriture >= 0) { dup2(*ecriture, STDOUT_FILENO); close(*ecriture); } execvp(arg[0], arg); /* on narrivera jamais ici, sauf en cas derreur */ perror("Erreur dans execvp()"); exit(EXIT_FAILURE); default : /* processus pere */ close(tuyau[LECTURE]); close(*ecriture); *ecriture = tuyau[ECRITURE]; return pid; } }

int main(int argc ,char *argv[]) { char ligne[80]; char *arg[MAXARG]; char *basecommande; int narg, letuyau; int i, pid; printf("myshell> "); while (fgets(ligne, sizeof(ligne), stdin) != NULL) { if (strcmp(ligne ,"exit\n") == 0) { exit(EXIT_SUCCESS); } /* on supprime le caractere de fin de ligne */ /* sil existe (fgets le lit) */ i=strlen(ligne)-1; if (ligne[i] == \n) ligne[i]=\0; basecommande = NULL; letuyau = -1; while (basecommande != ligne) { /* recherche la base de la commande la plus a droite */ basecommande = recherche_pipe(ligne, &narg); if (narg > MAXARG) { fprintf(stderr, "Trop de parametres\n"); exit(EXIT_FAILURE); } /* decoupe cette commande en tableau arg[] */

327

Chapitre 15. Les tuyaux sous Unix


decoupe_commande(basecommande, arg); /* lance la commande, en notant le pid de la derniere */ if (letuyau == -1) { pid = execute_commande(arg, &letuyau); } else { execute_commande(arg, &letuyau); } } /* le premier processus de la ligne na pas de stdin */ close(letuyau); waitpid(pid, NULL, 0); printf("myshell> "); } exit(EXIT_SUCCESS); }

328

15.4. Corrections dtailles

F IGURE 15.2 Le descripteur de chier entier ne permet quune manipulation octet par octet. Le descripteur de chier de type FILE permet quant lui une manipulation plus structure.

15.4

Corrections dtailles

Nous allons dans un premier temps revenir trs rapidement sur les chiers an que les diffrentes fonctions que nous utiliserons pour manipuler les tuyaux soient bien prsentes dans les esprits des lecteurs.

Les chiers sous Unix

Les chiers reprsentent en fait des ensembles de donnes manipules par lutilisateur. Un chier nest donc rien dautre quun rceptacle donnes. Le systme nous fournit diffrents moyens de manipuler ces rceptacles, car avant de pouvoir obtenir les donnes il faut avant tout pouvoir se saisir du bon rceptacle. Entre lutilisateur et le chier, linterface la plus simple est le descripteur de chier, cest--dire un entier : il sagit du numro de la ligne de la table des chiers ouverts faisant rfrence au chier souhait. Ce descripteur est obtenu trs simplement par lappel systme open(). Un descripteur de chier (point dentre dans la table des chiers ouverts) ne permet que des manipulations simples sur les chiers : la lecture et lcriture se feront par entit atomique, donc des octets, via les deux fonctions systme read() et write(). An de pouvoir manipuler des entits plus structures (des entiers, des mots, des double, etc.) il nous faut utiliser une autre interface que le descripteur entier comme le montre la gure 15.2. Linterface mise en place par le descripteur complexe FILE permet, outre une manipulation plus simple des entits lire ou crire, une temporisation des accs au chier. Ainsi au lieu dcrire octet par octet dans le chier, le systme placera dans un premier temps les diffrentes donnes dans une zone tampon. Quand la ncessit sen fera sentir, la zone tampon sera vide. Ceci vous explique pourquoi certaines critures ne se font jamais lorsquun programme commet une erreur et sarrte, mme si lerreur est intervenu aprs lappel fprintf(). 329

Chapitre 15. Les tuyaux sous Unix Nous pouvons aisment passer du descripteur entier au descripteur complexe en utilisant la fonction fdopen().
int descrip_trivial; FILE *descript_complexe; descript_trivial = open("Mon_beau_sapin.tex",O_RDONLY,NULL); descript_complexe = fdopen(descript_trivial,"r"); fscanf(descript_complexe,"%d %f %d", &entier,&reel,&autre_entier);

Lorsque nous utilisons la fonction fdopen(), nous nouvrons pas le chier une seconde fois, nous nous contentons simplement dobtenir une nouvelle faon de manipuler les donnes qui y sont stockes. Donc, lorsque nous fermons le chier, nous devons utiliser soit la fonction systme close(), soit la fonction de la bibliothque standard fclose(), mais certainement pas les deux ! Quel lien avec les tuyaux ? Et bien les tuyaux sont des chiers un peu particuliers donc mieux vaut savoir comment manipuler les chiers ! Il est important de garder lesprit quun descripteur de chier est obtenu en demandant une ouverture de chier avec un mode : lecture ou criture, il en sera de mme avec les tuyaux.
Les tuyaux

Un tuyau est une zone dchange de donnes entre deux ou plusieurs processus. Cette zone est cependant assez restrictive quant aux changes autoriss, ceux-ci sont unidirectionnels et destructifs : la communication ne peut se fait que dun groupe de processus (les crivains) vers dautres (les lecteurs). Une fois quun processus a choisit sa nature (crivain ou lecteur), impossible de revenir en arrire. toute donne lue par un lecteur nest plus disponible pour les autres lecteurs. Comme le nom le suggre assez bien, un tuyau doit possder deux descripteurs de chier, lun permettant dcrire, et lautre permettant de lire. La gure 15.3 dcrit de manire schmatique un tuyau et les deux descripteurs attachs. On subodore donc que la fonction permettant de crer un tel objet doit soit renvoyer un tableau de deux entiers, soit prendre en paramtre un tableau de deux entiers. Mais comme nous savons que les fonctions systmes retournent en gnral un code derreur, la bonne solution doit tre la seconde :
int pipe(int filedes[2]);

Il existe un moyen simple pour se souvenir quid des deux descripteurs est celui utilis en lecture (du coup lautre sera utilis en criture). Le priphrique servant lire nest rien dautre que le clavier, soit de manire un peu plus gnrale stdin, dont le numro est 0. Le descripteur utilis en lecture est donc fildes[0]. Nous allons commencer par utiliser un tuyau au sein dun seul processus, ce qui ne servira pas grand chose sinon xer les esprits sur les diffrents moyens mis notre disposition pour lire et crire dans un tuyau. 330

15.4. Corrections dtailles

F IGURE 15.3 Un tuyau permet de ranger (dans lordre darrive selon les critures) des donnes en mmoire pour les rcuprer. Il est donc fourni avec deux descripteurs de chier, lun servant crire et lautre lire.

Listing 15.11 Utilisation basique


#include <unistd.h> #include <stdlib.h> #include <stdio.h> int main(int argc, char **argv) { FILE *fpin,*fpout; int tuyau[2]; int retour,k; char phrase_in[256]; char phrase_out[256]; double pi; retour = pipe(tuyau); if (retour == -1) { perror("Impossible de creer le tuyau"); exit(EXIT_FAILURE); } for(k=0;k<10;k++) phrase_in[k] = a+k; write(tuyau[1],phrase_in,10); printf("Jecris dans mon tuyau\n"); sleep(1); read(tuyau[0],phrase_out,10); printf("Jai lu dans mon tuyau\n"); for(k=0;k<10;k++) printf("%c ",phrase_out[k]); printf("\n"); fpout = fdopen(tuyau[0],"r"); fpin = fdopen(tuyau[1],"w");

331

Chapitre 15. Les tuyaux sous Unix

fprintf(fpin,"Bonjour voici une phrase\n");fflush(fpin); sleep(1); fgets(phrase_out,255,fpout); printf("Jai lu: \"%s\" dans le tuyau\n",phrase_out); fprintf(fpin,"3.141592\n");fflush(fpin); sleep(1); fscanf(fpout,"%lf",&pi); printf("Jai lu pi dans le tuyau: %f\n",pi); close(tuyau[0]); close(tuyau[1]); exit(EXIT_SUCCESS); }

La cration du tuyau est trs simple, lappel systme pipe() nous renvoie un code derreur si le tuyau na pas pu tre cr. Si tout se passe bien nous obtenons nos deux descripteurs entiers dans le tableau tuyau[] pass en paramtre. Dans un premier temps nous allons simplement crire 10 octets dans le tuyau par lintermdiaire du descripteur dcriture. Nous utilisons la fonction write(), intermdiaire dcriture incontournable pour les descripteurs de chier entiers. Puis nous lisons, mais sur le ct lecture du tuyau, laide de la fonction read(). On lit exactement le bon nombre de caractres (ou moins la rigueur, mais surtout pas plus). An de faire appel des fonctions manipulant des donnes un peu plus volues que les octets, nous demandons une reprsentation plus complexe des deux descripteurs, et pour ce faire nous utilisons la fonction fdopen() qui nous renvoie un descripteur complexe (FILE *) partir dun descripteur entier. Nous pouvons alors crire et lire des donnes structures, comme le montre la suite du programme, avec toutefois un bmol important, lutilisation de la fonction fflush(), car nous le savons les descripteurs complexes utilisent des tampons et ne placent les donnes dans leur chier destination immdiatement.
Les tuyaux nomms

Lutilisation des tuyaux doit permettre diffrents processus de communiquer. Avant dutiliser les fonctions vues dans le prcdent chapitre (duplication et recouvrement), nous allons nous intresser une forme particulire de tuyaux, les tuyaux nomms. Puisque nous avons bien compris que les tuyaux ntaient ni plus ni moins que des sortes de chiers, il doit tre possible de crer vritablement des chiers qui se comportent comme des tuyaux. Pour cela, nous avons notre disposition la fonction mkfifo(). Elle permet de crer un chier qui se comporte comme un tuyau, et puisque ce chier possde un nom, nous pourrons crire et lire dedans en louvrant au moyen de son nom ! Les trois petits programmes qui suivent vous permettent de voir lutilisation et surtout le squencement des oprations dcriture et de lecture. Tout dabord le programme de cration, dcriture et puis de destruction du tuyau nomm : Listing 15.12 Cration simple, criture et fermeture 332

15.4. Corrections dtailles

#include #include #include #include #include

<unistd.h> <stdlib.h> <stdio.h> <sys/types.h> <sys/stat.h>

int main(int argc, char **argv) { int retour; FILE *fp; int k; retour = mkfifo("ma_fifo",0644); if (retour == -1) { perror("Impossible de creer le tuyaux nomme"); exit(EXIT_FAILURE); } fp = fopen("ma_fifo","w"); for(k=0;k<6;k++) { fprintf(fp,"Ecriture numero %d dans la fifo\n",k); fflush(fp); sleep(2); } fclose(fp); unlink("ma_fifo"); exit(EXIT_SUCCESS); }

Ensuite le programme de lecture qui prend un argument an de pouvoir tre identi car nous allons le lancer dans 2 fentres diffrentes : Listing 15.13 Le programme de lecture complmentaire au prcdent
#include <unistd.h> #include <stdlib.h> #include <stdio.h> int main(int argc, char **argv) { FILE *fp; char phrase[256]; if (argc < 2) { fprintf(stderr,"usage: %s <un nom>\n",argv[0]); exit(EXIT_FAILURE); } if ((fp = fopen("ma_fifo","r")) == NULL) { perror("P2: Impossible douvrir ma_fifo"); exit(EXIT_FAILURE); } while(fgets(phrase,255,fp)!=NULL) { printf("%s: \"%s\"\n",argv[1],phrase); } fclose(fp); exit(EXIT_SUCCESS); }

Nous commenons par lancer le programme de cration / criture. Comme lcriture dans un tuyau sans lecteur est bloquante, cela nous laisse le temps de lancer les deux 333

Chapitre 15. Les tuyaux sous Unix programmes lecteurs. Lafchage donne ceci (chaque fentre est identie par un numro diffrent dans linvite de linterprte de commandes) :
menthe31>p1 menthe22>p2 premier premier: "Ecriture numero 0 dans la fifo" premier: "Ecriture numero 2 dans la fifo" premier: "Ecriture numero 4 dans la fifo" menthe22> menthe12>p2 deuxieme deuxieme: "Ecriture numero 1 dans la fifo" deuxieme: "Ecriture numero 3 dans la fifo" deuxieme: "Ecriture numero 5 dans la fifo" menthe22>

Nous voyons tout dabord que le programme p1 reste bloqu tant quil reste des lecteurs. Ensuite nous remarquons que le programme p2 lanc dans deux fentres diffrentes, lit alternativement les diffrentes critures ce qui rete bien la notion de lecture destructive. Il est important de garder lesprit que les donnes changes ne sont jamais crites sur le disque. Le chier ma_fifo ne sert qu donner un nom, mais tous les changes restent conns la mmoire. Nous allons maintenant passer aux tuyaux permettant diffrents processus issus dun mme pre de partager des donnes.
Tuyaux et processus

Nous savons que lappel fork() permet de conserver les descripteurs de chier ouverts lors de la duplication. Nous allons tirer partie de cela pour crer un processus ls qui va pouvoir dialoguer avec son pre. Il est fondamental de bien comprendre que lappel fork() vient dupliquer les diffrents descripteurs et quune fois la duplication ralise, le tuyau possde deux points dentre et deux points de sortie comme le montre la gure 15.1. Nous allons mettre en uvre cela en rpondant la question 15.2. Notre programme va sarticuler autour dune fonction qui lira des caractres sur le clavier (lentre standard), une fonction qui les crira dans le tuyau et une fonction qui lira dans le tuyau. Dans un premier temps nous crons le tuyau, car il est impratif quil existe avant la duplication pour tre partag. la suite de la duplication, nous scinderons le programme en deux, la partie ls ralisera lenvoi des donnes dans le tuyau, la partie pre les lira et les afchera lcran. Listing 15.14 Utilisation de la duplication
#include #include #include #include <unistd.h> <stdlib.h> <stdio.h> <string.h>

#define LECTURE 0

334

15.4. Corrections dtailles

#define ECRITURE 1

int lire_clavier(char *ligne, int length) { int ret; ret = (int)fgets(ligne,length,stdin); return ret; } void proc_fils(int tuyau[]) { char ligne[256]; FILE *fp; /* Fermeture du tuyau en lecture */ close(tuyau[LECTURE]); if ((fp = fdopen(tuyau[ECRITURE],"w"))==NULL) { perror("Impossible dobtenir un descripteur decent"); close(tuyau[ECRITURE]); exit(EXIT_FAILURE); } while(lire_clavier(ligne,256)) { fprintf(fp,"%s",ligne);fflush(fp); } close(tuyau[ECRITURE]); fprintf(stdout,"Fin decriture tuyau\n"); exit(EXIT_SUCCESS); } void proc_pere(int tuyau[]) { char ligne[256]; FILE *fp; /* Fermeture du tuyau en ecriture */ close(tuyau[ECRITURE]); if ((fp = fdopen(tuyau[LECTURE],"r"))==NULL) { perror("Impossible dobtenir un descripteur decent"); close(tuyau[LECTURE]); exit(EXIT_FAILURE); } while(fgets(ligne,256,fp)!=NULL) { ligne[strlen(ligne)-1] = \0; fprintf(stdout,"Pere:%s\n",ligne); } close(tuyau[LECTURE]); fprintf(stdout,"Fin de lecture tuyau\n"); exit(EXIT_SUCCESS); } int main(int argc, char **argv) { int tuyau[2]; if (pipe(tuyau) == -1) { perror("Impossible de tuyauter"); exit(EXIT_FAILURE); } switch(fork()) {

335

Chapitre 15. Les tuyaux sous Unix

F IGURE 15.4 Une fois le tuyau cr et la duplication effective, on ferme lun des lecteurs et lun des crivains. Le tuyau ne possde plus quune entre (le ls) et une sortie (le pre).

case -1: perror("Impossible de forker"); exit(EXIT_FAILURE); case 0: /* processus fils */ proc_fils(tuyau); default: /* processus pere */ proc_pere(tuyau); } exit(EXIT_FAILURE); }

Il est essentiel de bien comprendre le fonctionnement dcrit par la gure 15.1. lissue de la fonction fork(), il existe un descripteur en criture pour le ls, un autre pour le pre, ainsi quun descripteur en lecture pour le ls et un autre pour le pre. Notre tuyau possde donc deux points dentre et deux points de sortie. Il est impratif de mettre n cette situation un peu dangereuse. En effet un tuyau ouvert en criture (avec un ou plusieurs points dentre) ne peut pas signier son (ou ses lecteurs) quil nest ncessaire dattendre des donnes. Le processus ls, qui est lcrivain dans le tuyau, va tout dabord fermer son ct lecteur. De la mme faon, le processus pre, qui est le lecteur du tuyau, va fermer son ct crivain. Ainsi nous obtenons, comme le montre le diagramme chronologique de la gure 15.4, le tuyau ne possde plus un instant donn quun crivain, le processus ls, et un lecteur, le processus pre.
Tuyaux et recouvrement

Une des utilisations possibles est de pouvoir faire communiquer des programmes entre eux, nous lavons vu avec les tuyaux nomms. Peut-on aboutir la mme chose 336

15.4. Corrections dtailles

F IGURE 15.5 La duplication de chaque descripteur de chier permet de faire aboutir un nouveau descripteur (ne ou nl) sur lentre ou la sortie dun tuyau. Si lon choisit par exemple de dupliquer stdin sur tuyau[0] alors un processus qui partage le tuyau et qui lit dans stdin lira en fait dans le tuyau.

sans ces tuyaux nomms ? Avec des programmes que nous concevons nous-mmes et que nous pouvons intgrer dans un dveloppement, la chose est strictement identique ce que nous venons de faire dans le programme prcdent. Par contre, comment procder lexcution de cette commande assez simple : ls -l | wc -l ? Il faut brancher la sortie standard de la commande ls sur lentre standard de la commande wc. Pour ce faire nous allons utiliser une nouvelle fonction : dup2(). Cette fonction cre une copie dun descripteur de chier, et en paraphrasant le manuel, aprs un appel russi cette fonction, le descripteur et sa copie peuvent tre utiliss de manire interchangeable. La solution est donc l : on cre un tuyau et on duplique son entre (le ct crivain) sur la sortie standard de la premire commande (ls -l). Puis on duplique sa sortie (le ct lecteur) sur lentre standard de la deuxime commande (wc -l). Ainsi, la premire commande crit dans le tuyau et la deuxime commande lit dans le tuyau. La gure 15.5 dcrit ce cheminement. Nous allons donc pouvoir programmer laide de fork() et dup2() lexercice de recouvrement. Nous avons deux fonctions et nous allons utiliser fork() pour obtenir deux processus. Lequel du pre ou du ls doit se charger de la premire fonction ? Il faudrait que le pre se termine aprs le ls, donc le pre doit attendre des informations du ls, ce qui implique ncessairement que le pre soit en charge de la dernire fonction et le ls de la premire. La gure 15.6 dcrit la chronologie des vnements. Listing 15.15 Tuyau et recouvrement
#include <unistd.h> #include <stdlib.h> #include <stdio.h>

337

Chapitre 15. Les tuyaux sous Unix

F IGURE 15.6 Aprs la duplication de processus, nous avons quatre descripteurs disponibles sur le tuyau, deux pour le pre et deux pour le ls. Dans le processus pre, lutilisation de la duplication de descripteur et deux fermetures adquates permettent de couper lentre en lecture du pre et de brancher lentre standard sur le tuyau en lecture. Le pre ferme aussi son entre en criture. Du ct du ls, la duplication de descripteur permet de brancher la sortie standard sur le ct criture du tuyau et de fermer lentre criture du ls. Le ls ntant pas un lecteur, il ferme lentre lecture du tuyau. On obtient donc bien un tuyau dans lequel il ny a quune entre en lecture (lentre standard qui servira au pre lors de son recouvrement) et une entre en criture (la sortie standard qui servira au ls lors de son recouvrement).

#include <sys/types.h> #include <sys/wait.h> #define LECTURE 0 #define ECRITURE 1

int main(int argc, char **argv) { int tuyau[2]; if (pipe(tuyau) == -1) { perror("Impossible de tuyauter"); exit(EXIT_FAILURE); } switch(fork()) { case -1: perror("Impossible de forker"); exit(EXIT_FAILURE); case 0: /* processus fils */ /* Il est juste ecrivain, donc fermeture du cote */ /* lecteur.*/ close(tuyau[LECTURE]); /* La premiere commande ls -l ecrit ses resultats */ /* dans le tuyau et non sur la sortie standard */ /* donc on duplique */ if (dup2(tuyau[ECRITURE],STDOUT_FILENO)==-1) { perror("Impossible du dupliquer un descripteur"); close(tuyau[ECRITURE]); exit(EXIT_FAILURE);

338

15.4. Corrections dtailles

} /* Comme le processus sera recouvert, le */ /* descripteur tuyau[ECRITURE] est inutile */ /* donc dangereux, on le ferme!*/ close(tuyau[ECRITURE]); /* On recouvre le processus avec "ls -l" */ execlp("ls","ls","-l",NULL); /* si on arrive ici cest que execlp va mal!*/ exit(EXIT_FAILURE); default: /* processus pere */ /* Il est juste lecteur, donc fermeture du cote */ /* ecrivain.*/ close(tuyau[ECRITURE]); /* la derniere commande wc -l doit lire ses */ /* arguments dans le tuyau et non */ /* sur lentree standard donc on duplique */ if (dup2(tuyau[LECTURE],STDIN_FILENO)==-1) { perror("Impossible du dupliquer un descripteur"); close(tuyau[LECTURE]); exit(EXIT_FAILURE); } /* Comme le processus sera recouvert, le */ /* descripteur tuyau[LECTURE] est inutile */ /* donc dangereux, on le ferme!*/ close(tuyau[LECTURE]); /* On recouvre le processus avec "wc -l" */ execlp("wc","wc","-l",NULL); /* si on arrive ici cest que execlp va mal!*/ exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); }

Linterprte de commandes

Le programme qui suit est relativement comment. Il est surtout important de comprendre que dans ce programme il y a plusieurs tuyaux et que la sortie (ct lecture) de lun est branche sur le ct criture dun autre an dlaborer la suite des commandes. Listing 15.16 Ecriture dun shell
#include #include #include #include #include #include #include #include <sys/types.h> <signal.h> <stdio.h> <stdlib.h> <string.h> <unistd.h> <sys/wait.h> <string.h>

#define LECTURE 0 #define ECRITURE 1 #define MAXCHAR_LIGNE 80 #define MAXARG 10 /* Structure de liste chainee pour les arguments */ /* dune commande */

339

Chapitre 15. Les tuyaux sous Unix

typedef struct argument { char *arg; /* largument (non recopie) */ struct argument *suivant; /* ladresse du maillon suivant */ } Argument; /* Structure de liste chainee pour les commandes */ typedef struct commande { int numero; /* le numero de la commande (4fun) */ int argc; /* le nombre darguments de la commande*/ Argument *argv; /* la liste chainee des arguments */ struct commande *suivante; /* ladresse du maillon suivant */ } Commande; /* Cette fonction ajoute un argument (char *) a la liste * chainee "l" des arguments. * Si la liste est vide (l==NULL) la fonction retourne * ladresse du nouveau maillon, sinon la fonction rajoute * largument a la fin de la liste en se rappelant elle meme * La liste construite a partir de : * l=NULL; * l = add_argument(l,arg1); * l = add_argument(l,arg2); * l = add_argument(l,arg3); * * sera donc : l(arg1) --> l(arg2) --> l(arg3) --> NULL */ Argument *add_argument(Argument *l, char *argvalue) { Argument *newarg; if (l==NULL) { if ((newarg = malloc(sizeof(Argument))) == NULL) { perror("Erreur dallocation dargument."); exit(EXIT_FAILURE); } newarg->arg = argvalue; newarg->suivant = NULL; return newarg; } if (l->suivant == NULL) { if ((newarg = malloc(sizeof(Argument))) == NULL) { perror("Erreur dallocation dargument."); exit(EXIT_FAILURE); } newarg->arg = argvalue; newarg->suivant = NULL; l->suivant = newarg; return l; } add_argument(l->suivant,argvalue); return l; } /* Cette fonction ajoute une commande darguments "arg" * a la liste chainee de commandes "l". * La nouvelle commande est rajoutee au debut de * la liste (a linverse de la commande add_argument) */ Commande *add_commande(Commande *l, Argument *arg, int argc) { Commande *newcmd; if (argc <= 0) return l; if ((newcmd = malloc(sizeof(Commande))) == NULL) {

340

15.4. Corrections dtailles

perror("Erreur dallocation de commande."); exit(EXIT_FAILURE); } newcmd->argv = arg; newcmd->argc = argc; newcmd->suivante = l; newcmd->numero = (l!=NULL?l->numero+1:0); return newcmd; } /* Liberation des maillons (pas du champ (char *)arg * car ce dernier nest pas recopie) de la liste * darguments "l" */ Argument *free_argliste(Argument *l) { Argument *toBfree; while(l != NULL) { toBfree = l; l=l->suivant; free(toBfree); } return NULL; } /* Liberation des maillons (champ Argument *argv compris) * de la liste des commandes "l" */ Commande *free_cmdliste(Commande *l) { Commande *toBfree; while(l != NULL) { toBfree = l; l=l->suivante; free_argliste(toBfree->argv); free(toBfree); } return NULL; } void show_argliste(Argument *l) { Argument *cur; int i=0; cur = l; while(cur != NULL) { fprintf(stdout,"\tArgument %3d: \"%s\" (%10p)\n", i++,cur->arg,cur->arg); cur = cur->suivant; } } void show_commande(Commande *cur) { fprintf(stdout,"Commande %3d avec %2d argument(s): \n", cur->numero,cur->argc); show_argliste(cur->argv); } void show_allcommande(Commande *c) { Commande *cur;

341

Chapitre 15. Les tuyaux sous Unix

cur = c; while(cur != NULL) { show_commande(cur); cur = cur->suivante; } } /* Fonction permettant de construire la liste chainee * darguments a partir dune chaine de caracteres. * Chaque argument est separe du suivant par " " ou par * un caractere de tabulation ou par un retour chariot (ce * qui ne devrait pas arriver */ Argument *parse_argument(char *ligne, int *argc) { char *curarg,*ptrtmp; Argument *listearg=NULL; *argc=0; while((curarg = strtok_r(listearg==NULL?ligne:NULL," \t\n",&ptrtmp)) != NULL) { listearg = add_argument(listearg,curarg); *argc += 1; } return listearg; } /* Fonction danalyse dune ligne de commandes. Chaque commande * est separee de la suivante par le caractere | (tuyau). Une * fois reperee la fin de la commande, les caracteres compris entre * le debut (start) et la fin (on remplace | par \0) sont * envoyes a parse_argument puis la nouvelle commande est placee * dans la liste chainee des commandes. */ Commande *parse_ligne(char *ligne) { Commande *com=NULL; Argument *args=NULL; int argc; char *start; int i,len; /* on supprime le dernier caractere: \n */ ligne[strlen(ligne)-1] = \0; len = strlen(ligne); for (i = 0,start=ligne;i<len;i++) { if (ligne[i] == |) { ligne[i] = \0; args = parse_argument(start,&argc); com = add_commande(com,args,argc); start = ligne+i+1;; } } args = parse_argument(start,&argc); com = add_commande(com,args,argc); return com; } /* Cette fonction prend une liste chainee darguments et construit * un tableau dadresses telles que chacune pointe sur un argument * de la liste chainee. Ceci permet dutiliser execvp() pour recouvrir * le processusen cours par la commande souhaitee avec ses arguments. */ char **construire_argv(Commande *curcom) {

342

15.4. Corrections dtailles

Argument *curarg; char **argv; int i; argv = malloc((curcom->argc+1)*sizeof(char *)); for(i=0,curarg=curcom->argv;i<curcom->argc; i++,curarg=curarg->suivant) { argv[i] = curarg->arg; } argv[i] = NULL; return argv; } /* * Cette fonction met en place lexecution dune commande. Elle * commence par creer un tuyau puis procede a un appel a fork(). * * Le processus fils ferme le cote ecriture, branche son entree * standard sur le cote lecture puis ferme le cote lecture. * Si le fils ne correspond pas au premier appel (donc a la * derniere commande de la ligne analysee), il branche sa sortie * standard sur le cote ecriture du tuyau precedent puis il ferme * le cote ecriture du tuyau precedent. Il se recouvre ensuite * avec la commande desiree. * * Le pere ferme le cote lecture du tuyau en cours et si il ne * sagit pas du premier appel (donc de la derniere commande de * la ligne analysee) il ferme le cote ecriture du tuyau precedent. * Il met a jour la variable prec_ecriture en disant que pour * les appels a venir, le tuyau precedent en ecriture est le tuyau * courant en ecriture. Il retourne enfin le pid de son fils. */ int execute_commande(char **argv, int *prec_ecriture) { int tuyau[2]; int pid; if (pipe(tuyau) == -1) { perror("Impossible de creer le tuyau"); exit(EXIT_FAILURE); } pid = fork(); switch (pid) { case -1: perror("Impossible de creer un processus"); exit(EXIT_FAILURE); case 0: /* pas besoin detre ecrivain dans ce tuyau */ close(tuyau[ECRITURE]); /* branchement de stdin sur la lecture du tuyau */ dup2(tuyau[LECTURE],STDIN_FILENO); close(tuyau[LECTURE]); /* branchement de stdout sur lecriture du tuyau precedent */ /* sil y a un tuyau precedent */ if (*prec_ecriture >= 0) { dup2(*prec_ecriture,STDOUT_FILENO); close(*prec_ecriture); } execvp(argv[0],argv); perror("Impossible de proceder au recouvrement"); fprintf(stderr,"commande: \"%s\"\n",argv[0]); exit(EXIT_FAILURE); default :

343

Chapitre 15. Les tuyaux sous Unix

/* pas besoin de lire sur le tuyau car on est le pere, * et on se contente de regarder passer les anges */ close(tuyau[LECTURE]); if (*prec_ecriture >= 0) { if(close(*prec_ecriture)) { fprintf(stderr,"Pere:Impossible de fermer"); fprintf(stderr,"le tuyau precedent en ecriture\n"); } } *prec_ecriture = tuyau[ECRITURE]; return pid; } exit(EXIT_FAILURE); } int main(int margc, char **margv) { char ligne[MAXCHAR_LIGNE]; Commande *com=NULL; Commande *curcom; char **argv; int prec_ecriture; int pid=-1; /* On affiche une invitation! */ printf("myshell>"); /* Tant que lon peut lire des trucs, on lit */ while (fgets(ligne, sizeof(ligne),stdin) != NULL) { /* Si lutilisateur veut sortir de ce magnifique interprete * on sort en tuant le processus en court */ if (!strncmp(ligne,"exit",4)) exit(EXIT_SUCCESS); /* On analyse la ligne de commandes afin de construire * les differentes listes chainees. */ com = parse_ligne(ligne); /* /* /* /* Les commandes sont maintenant rangees dans une liste */ la derniere commande de la ligne est en tete de liste */ puis vient la suivante, etc. jusqua la premiere de la */ ligne qui se trouve en fin de liste */

/* Le tuyau precedent nexiste pas encore, donc -1 */ prec_ecriture=-1; curcom = com; /* On analyse chaque commande de la ligne en commencant par * celle qui est la plus a droite */ while (curcom != NULL) { /* on construit le fameux tableau char *argv[] */ argv = construire_argv(curcom); /* Sil sagit de la premiere commande (celle de droite!) * on recupere son pid() histoire de pouvoir attendre quelle * soit finie avant de rendre la main a lutilisateur */ if (prec_ecriture < 0) pid = execute_commande(argv,&prec_ecriture);

344

15.4. Corrections dtailles

else execute_commande(argv,&prec_ecriture); free(argv); argv=NULL; curcom = curcom->suivante; } /* Fin danalyse de la ligne */ if (prec_ecriture >= 0) { if (close(prec_ecriture)) { fprintf(stderr,"Pere:Impossible de fermer "); fprintf(stderr,"le tuyau precedent en ecriture "); fprintf(stderr,"en fin de boucle\n"); } } if (pid >=0) { waitpid(pid,NULL,0); } com=free_cmdliste(com); printf("myshell>"); } exit(EXIT_SUCCESS); }

345

16
Les sockets sous Unix

16.1

Introduction

Les sockets reprsentent la forme la plus complte de communication entre processus. En effet, elles permettent plusieurs processus quelconques dchanger des donnes, sur la mme machine ou sur des machines diffrentes. Dans ce cas, la communication seffectue grce un protocole rseau dni lors de louverture de la socket (IP, ISO, XNS...). Ce document ne couvre que les sockets rseau utilisant le protocole IP (Internet Protocol), celles-ci tant de loin les plus utilises.

16.2

Les RFC request for comments

Il sera souvent fait rfrence dans la suite de ce chapitre des documents appels request for comments (RFC). Les RFC sont les documents dcrivant les standards de lInternet. En particulier, les protocoles de communication de lInternet sont dcrits dans des RFC. Les RFC sont numrots dans lordre croissant au fur et mesure de leur parution. Ils sont consultables sur de nombreux sites dont : <URL:ftp://ftp.isi.edu/in-notes/> (site de rfrence)

16.3

Technologies de communication rseau

Il existe rarement une connexion directe (un cble) entre deux machines. Pour pouvoir voyager dune machine lautre, les informations doivent gnralement traverser un certain nombre dquipements intermdiaires. La faon de grer le comportement de ces quipements a men la mise au point de deux technologies principales de communication rseau : La commutation de circuits ncessite, au dbut de chaque connexion, ltablissement dun circuit depuis la machine source vers la machine destination, travers un nombre dquipements intermdiaires x louverture de la connexion. 347

Chapitre 16. Les sockets sous Unix Cette technologie, utilise pour les communications tlphoniques et par le systme ATM (Asynchronounous Transfer Mode) prsente un inconvnient majeur. En effet, une fois le circuit tabli, il nest pas possible de le modier pour la communication en cours, ce qui pose deux problmes : si lun des quipements intermdiaires tombe en panne ou si le cble entre deux quipements successifs est endommag, la communication est irrmdiablement coupe, il ny a pas de moyen de la rtablir et il faut en initier une nouvelle ; si le circuit utilis est surcharg, il nest pas possible den changer pour en utiliser un autre. En revanche, la commutation de circuits prsente lavantage de pouvoir faire trs facilement de la rservation de bande passante puisquil suft pour cela de rserver les circuits adquats. La commutation de paquets repose sur le dcoupage des informations en morceaux, dits paquets, dont chacun est expdi sur le rseau indpendamment des autres. Chaque quipement intermdiaire, appel routeur, est charg daiguiller les paquets dans la bonne direction, vers le routeur suivant. Ce principe est appel routage. Il existe deux types de routage : Le routage statique, qui stipule que les paquets envoys vers tel rseau doivent passer par tel routeur. Le routage statique est enregistr dans la conguration du routeur et ne peut pas changer suivant ltat du rseau. Le routage dynamique, qui repose sur un dialogue entre routeurs et qui leur permet de modier le routage suivant ltat du rseau. Le dialogue entre routeurs repose sur un protocole de routage (tel que OSPF (RFC 2328), RIP (RFC 1058) ou BGP (RFC 1771) dans le cas des rseaux IP). Le routage dynamique permet la commutation de paquets de pallier les inconvnients de la commutation de circuits : si un routeur tombe en panne ou si un cble est endommag, le routage est modi dynamiquement pour que les paquets empruntent un autre chemin qui aboutira la bonne destination (sil existe un tel chemin, bien entendu) ; si un chemin est surcharg, le routage peut tre modi pour acheminer les paquets par un chemin moins embouteill, sil existe. En revanche, contrairement la commutation de circuits, il est assez difcile de faire de la rservation de bande passante avec la commutation de paquets. En contrepartie, celle-ci optimise lutilisation des lignes en vitant la rservation de circuits qui ne seront que peu utiliss.

16.4

Le protocole IP

IP (Internet Protocol, RFC 791 pour IPv4, RFC 2460 pour IPv6) est un protocole reposant sur la technologie de commutation de paquets.

348

16.4. Le protocole IP Chaque machine relie un rseau IP se voit attribuer une adresse, dite adresse IP, unique sur lInternet, qui nest rien dautre quun entier sur 32 bits 1 . An de faciliter leur lecture et leur mmorisation, il est de coutume dcrire les adresses IP sous la forme de quatre octets spars par des points. Ainsi, la machine ensta.ensta.fr a pour adresse IP 147.250.1.1. Les noms de machines sont cependant plus faciles retenir et utiliser mais, pour son fonctionnement, IP ne reconnat que les adresses numriques. Un systme a donc t mis au point pour convertir les noms en adresses IP et vice versa : le DNS (Domain Name System, RFC 1034 et RFC 1035). Chaque paquet IP contient deux parties : Len-tte, qui contient diverses informations ncessaires lacheminement du paquet. Parmi celles-ci, on peut noter : ladresse IP de la machine source ; ladresse IP de la machine destination ; la taille du paquet. Le contenu du paquet proprement dit, qui contient les informations transmettre.
IP ne garantit pas que les paquets mis soient reus par leur destinataire et neffectue aucun contrle dintgrit sur les paquets reus. Cest pourquoi deux protocoles plus volus ont t btis au-dessus dIP : UDP et TCP. Les protocoles applicatifs couramment utiliss, comme SMTP ou HTTP, sont btis au-dessus de lun de ces protocoles (bien souvent TCP). Ce systme en couches, o un nouveau protocole offrant des fonctionnalits plus volues est bti au-dessus dun protocole plus simple, est certainement lune des cls du succs dIP.

Le protocole UDP
UDP (User Datagram Protocol, RFC 768) est un protocole trs simple, qui ajoute deux fonctionnalits importantes au-dessus dIP : lutilisation de numros de ports par lmetteur et le destinataire ; un contrle dintgrit sur les paquets reus. Les numros de ports permettent la couche IP des systmes dexploitation des machines source et destination de faire la distinction entre les processus utilisant les communication rseau. En effet, imaginons une machine sur laquelle plusieurs processus sont susceptibles de recevoir des paquets UDP. Cette machine est identie par son adresse IP, elle ne recevra donc que les paquets qui lui sont destins, puisque cette adresse IP gure dans le champ destination de len-tte des paquets. En revanche, comment le systme dexploitation peut-il faire pour dterminer quel processus chaque paquet est destin ? Si lon se limite len-tte IP, cest impossible. Il faut donc ajouter, dans un en-tte spcique au paquet UDP, des informations permettant daiguiller les informations vers le bon processus. Cest le rle du numro de port destination.
1. Ceci est vrai pour IPv4. Les adresses IPv6 sont sur 128 bits.

349

Chapitre 16. Les sockets sous Unix Le numro de port source permet au processus destinataire des paquets didentier lexpditeur ou au systme dexploitation de la machine source de prvenir le bon processus en cas derreur lors de lacheminement du paquet. Un paquet UDP est donc un paquet IP dont le contenu est compos de deux parties : len-tte UDP, qui contient les numros de ports source et destination, ainsi que le code de contrle dintgrit ; le contenu du paquet UDP proprement parler. Au total, un paquet UDP est donc compos de trois parties : 1. len-tte IP ; 2. len-tte UDP ; 3. le contenu du paquet.
UDP ne permet cependant pas de grer la retransmission des paquets en cas de perte ni dadapter son dbit la bande passante disponible. Enn, le dcoupage des informations transmettre en paquets est toujours la charge du processus source qui doit tre capable dadapter la taille des paquets la capacit du support physique (par exemple, la taille maximale (MTU, Maximum Transmission Unit) dun paquet transmissible sur un segment Ethernet est de 1500 octets, en-ttes IP et UDP compris, voir RFC 894).

Le protocole TCP
TCP (Transmission Control Protocol, RFC 793) est un protocole tablissant un canal bidirectionnel entre deux processus. De mme quUDP, TCP ajoute un en-tte supplmentaire entre len-tte IP et les donnes transmettre, en-tte contenant les numros de port source et destination, un code de contrle dintgrit, exactement comme UDP, ainsi que diverses informations que nous ne dtaillerons pas ici. Un paquet TCP est donc un paquet IP dont le contenu est compos de deux parties :

len-tte TCP, qui contient, entre autres, les numros de ports source et destination, ainsi que le code de contrle dintgrit ; le contenu du paquet TCP proprement parler. Au total, un paquet TCP est donc compos de trois parties : 1. len-tte IP ; 2. len-tte TCP ; 3. le contenu du paquet. Contrairement UDP, qui impose aux processus voulant communiquer de grer eux-mme le dcoupage des donnes en paquets et leur retransmission en cas de perte, TCP apparat chaque processus comme un chier ouvert en lecture et en criture, ce qui lui confre une grande souplesse. cet effet, TCP gre lui-mme le dcoupage des 350

16.5. Interface de programmation donnes en paquets, le recollage des paquets dans le bon ordre et la retransmission des paquets perdus si besoin est. TCP sadapte galement la bande passante disponible en ralentissant lenvoi des paquets si les pertes sont trop importantes (RFC 1323) puis en r-augmentant le rythme au fur et mesure, tant que les pertes sont limites.

16.5

Interface de programmation

Linterface de programmation des sockets peut paratre complique au premier abord, mais elle est en fait relativement simple parce que faite dune succession dtapes simples.
La fonction socket()

La fonction socket() permet de crer une socket de type quelconque :


#include <sys/types.h> #include <sys/socket.h> int s , domaine , type , protocole ; /* initialisation des variables domaine , type et protocole */ s = socket ( domaine , type , protocole ) ; if ( s == -1 ) { /* erreur : la socket na pas pu etre creee */ }

Le paramtre domaine prcise la famille de protocoles utiliser pour les communications. Il y a deux familles principales :
PF_UNIX pour les communications locales nutilisant pas le rseau ; PF_INET pour les communications rseaux utilisant le protocole IP.

Nous ntudierons par la suite que la famille PF_INET. Le paramtre type prcise le type de la socket crer :
SOCK_STREAM pour une communication bidirectionnelle sre. Dans ce cas, la socket utilisera le protocole TCP pour communiquer. SOCK_DGRAM pour une communication sous forme de datagrammes. Dans ce cas, la socket utilise le protocole UDP pour communiquer. SOCK_RAW pour pouvoir crer soi-mme ses propres paquets IP ou les recevoir sans

traitement de la part de la couche rseau du systme dexploitation, ce qui est indispensable dans certains cas trs prcis. Le paramtre protocole doit tre gal zro. La valeur renvoye par la fonction socket() est -1 en cas derreur, sinon cest un descripteur de chier quon peut par la suite utiliser avec les fonctions read() et write(), transformer en pointeur de type FILE * avec fdopen(), etc. 351

Chapitre 16. Les sockets sous Unix Nous ntudierons que les sockets de type SOCK_STREAM par la suite, celles-ci tant les plus utilises.
Exemple de client TCP

La programmation dun client TCP est fort simple et se droule en cinq tapes : 1. cration de la socket ; 2. rcupration de ladresse IP du serveur grce au DNS ; 3. connexion au serveur ; 4. dialogue avec le serveur ; 5. fermeture de la connexion.
Cration de la socket

Elle se fait simplement laide de la fonction socket() :


#include <sys/types.h> #include <sys/socket.h> int client_socket ; client_socket = socket ( PF_INET , SOCK_STREAM , 0 ) ; if ( client_socket == -1 ) { /* erreur */ }

Interrogation du DNS

Le protocole IP ne connat que les adresses IP. Il est donc ncessaire dinterroger le DNS pour rcuprer ladresse IP du serveur partir de son nom. Il peut aussi tre utile de rcuprer le nom du serveur si le client est lanc avec une adresse IP comme argument. Pour cela, on utilise deux fonctions : gethostbyname() et gethostbyaddr().
La fonction gethostbyname()

La fonction gethostbyname() permet dinterroger le DNS de faon obtenir ladresse IP dune machine partir de son nom :
#include <netdb.h> struct hostent char *hostent ; *nom = "ensta.ensta.fr" ;

hostent = gethostbyname ( nom ) ; if ( hostent == NULL ) { /* erreur : le nom de la machine nest pas declare dans le DNS */ }

352

16.5. Interface de programmation Cette fonction prend en argument une chane de caractres (pointeur de type char ) contenant le nom de la machine et renvoie un pointeur vers une structure de type * hostent (dni dans le chier den-tte <netdb.h>). Cette structure contient trois membres qui nous intressent :
h_name est le nom canonique 2 de la machine (pointeur de type char *) ; h_addr_list est la liste des adresse IP 3 de la machine (pointeur de type char **). h_length est la taille de chacune des adresses stockes dans h_addr_list (ceci sert

en permettre la recopie sans faire de suppositions sur la taille des adresses).


La fonction gethostbyaddr()

La fonction gethostbyaddr() permet dinterroger le DNS de faon obtenir le nom canonique dune machine partir de son adresse IP :
#include <netdb.h> #include <netinet/in.h> #include <arpa/inet.h> struct hostent char unsigned long *hostent ; *ip = "147.250.1.1" ; adresse ;

adresse = inet_addr ( ip ) ; hostent = gethostbyaddr ( ( char * ) &adresse , sizeof ( adresse ) , AF_INET ) ; if ( hostent == NULL ) { /* erreur : le nom de la machine nest pas declare dans le DNS */ }

La fonction inet_addr() permet de transformer une chane de caractres reprsentant une adresse IP (comme 147.250.1.1) en entier long. La fonction gethostbyaddr() prend en argument : 1. un pointeur de type char * vers ladresse IP sous forme numrique (do la ncessit dutiliser inet_addr()) ; 2. la taille de cette adresse ; 3. le type dadresse utilise, AF_INET pour les adresses IP. Elle renvoie un pointeur vers une structure de type hostent, comme le fait la fonction examine au paragraphe prcdent gethostbyname().
Connexion au serveur TCP

La connexion au serveur TCP se fait au moyen de la fonction connect() :


2. Une machine peut avoir plusieurs noms, comme ensta.ensta.fr et h0.ensta.fr. Le nom canonique dune machine est son nom principal, ici ensta.ensta.fr. Les autres noms sont des alias. 3. Car un nom peuvent tre associes plusieurs adresses IP bien que ce soit assez rare en pratique.

353

Chapitre 16. Les sockets sous Unix

if ( connect ( client_socket , ( struct sockaddr * ) &serveur_sockaddr_in , sizeof ( serveur_sockaddr_in ) ) == -1 ) { /* erreur : connexion impossible */ }

Cette fonction prend en argument : 1. le descripteur de chier de la socket pralablement cre ; 2. un pointeur vers une structure de type sockaddr ; 3. la taille de cette structure. Dans le cas des communications IP, le pointeur vers la structure de type sockaddr est un fait un pointeur vers une structure de type sockaddr_in, dnie dans le chier den-tte <netinet/in.h> (do la conversion de pointeur). Cette structure contient trois membres utiles : sin_family est la famille de protocoles utilise, AF_INET pour IP ; sin_port est le port TCP du serveur sur lequel se connecter ; sin_addr.s_addr est ladresse IP du serveur. Le port TCP doit tre fourni sous un format spcial. En effet les microprocesseurs stockent les octets de poids croissant des entiers courts (16 bits) et des entiers longs (32 bits) de gauche droite (microprocesseurs dits big endian tels que le Motorola 68000) ou de droite gauche (microprocesseurs dits little endian tels que les Intel). Pour les communications IP, le format big endian a t retenu. Il faut donc ventuellement convertir le numro de port du format natif de la machine vers le format rseau au moyen de la fonction htons() (host to network short) pour les machines processeur little endian. Dans le doute, on utilise cette fonction sur tous les types de machines (elle est galement dnie sur les machines base de processeur big endian, mais elle ne fait rien). Ladresse IP na pas besoin dtre convertie, puisquelle est directement renvoye par le DNS au format rseau.
#include <netdb.h> #include <string.h> #include <netinet/in.h> struct hostent struct sockaddr_in unsigned short hostent ; serveur_sockaddr_in ; port ;

/* creation de la socket */ /* appel a gethostbyname() ou gethostbyaddr() */ memset ( &serveur_sockaddr_in , 0 , sizeof ( serveur_sockaddr_in ) ) ; serveur_sockaddr_in.sin_family = AF_INET ; serveur_sockaddr_in.sin_port = htons ( port ) ; memcpy ( &serveur_sockaddr_in.sin_addr.s_addr , hostent->h_addr_list[0] , hostent->h_length ) ;

354

16.5. Interface de programmation La fonction memset() permet dinitialiser globalement la structure zro an de supprimer toute valeur parasite.
Dialogue avec le serveur

Le dialogue entre client et serveur seffectue simplement au moyen des fonctions


read() et write().

Cependant, notre client devra lire au clavier et sur la socket sans savoir lavance sur quel descripteur les informations seront disponibles. Il faut donc couter deux descripteurs la fois et lire les donnes sur un descripteur quand elles sont disponibles. Pour cela, on utilise la fonction select() (cf chapitre 18). On indique donc les descripteurs qui nous intressent (client_socket et lentre standard) puis on appelle la fonction select() :
fd_set rset ; FD_ZERO ( &rset ) ; FD_SET ( client_socket , &rset ) ; FD_SET ( STDIN_FILENO , &rset ) ; if ( select ( client_socket + 1 , &rset , NULL , NULL , NULL ) == -1 ) { /* erreur */ }

Les arguments passs la fonction select() sont client_socket + 1, correspondant au plus grand numro de descripteur surveiller plus un puisque lentre standard a 0 comme numro de descripteur, et &rset, correspondant lensemble des descripteurs de chier surveiller. On utilise ensuite la fonction FD_ISSET pour dterminer quel descripteur est prt :
if ( FD_ISSET ( client_socket , &rset ) ) { /* lire sur la socket */ } if ( FD_ISSET ( STDIN_FILENO , &rset ) ) { /* lire sur lentree standard */ }

Fermeture de la connexion

La socket se ferme simplement au moyen de la fonction close(). On peut aussi utiliser la fonction shutdown(), qui permet une fermeture slective de la socket (soit en lecture, soit en criture).
Client TCP complet

Listing 16.1 Exemple de client 355

Chapitre 16. Les sockets sous Unix


<sys/types.h> <netdb.h> <stdio.h> <stdlib.h> <string.h> <unistd.h>

#include #include #include #include #include #include

#include <netinet/in.h> #include <arpa/inet.h> #include <sys/socket.h> /* ----------------------------------------------------- */ int socket_client (char *serveur , unsigned short port) { int client_socket ; struct hostent *hostent ; struct sockaddr_in serveur_sockaddr_in ; client_socket = socket ( PF_INET , SOCK_STREAM , 0 ) ; if ( client_socket == -1 ) { perror ( "socket" ) ; exit ( EXIT_FAILURE ) ; } if ( inet_addr ( serveur ) == INADDR_NONE ) { /* nom */ hostent = gethostbyname ( serveur ) ; if ( hostent == NULL ) { perror ( "gethostbyname" ) ; exit ( EXIT_FAILURE ) ; } } else /* adresse IP */ { unsigned long addr = inet_addr ( serveur ) ; hostent = gethostbyaddr (( char * ) &addr , sizeof(addr), AF_INET) ; if ( hostent == NULL ) { perror ( "gethostbyaddr" ) ; exit ( EXIT_FAILURE ) ; } } memset ( &serveur_sockaddr_in , 0 , sizeof ( serveur_sockaddr_in ) ) ; serveur_sockaddr_in.sin_family = AF_INET ; serveur_sockaddr_in.sin_port = htons ( port ) ; memcpy ( &serveur_sockaddr_in.sin_addr.s_addr , hostent->h_addr_list[0] , hostent->h_length) ; printf ( ">>> Connexion vers le port %d de %s [%s]\n" , port , hostent->h_name , inet_ntoa ( serveur_sockaddr_in.sin_addr ) ) ; if ( connect(client_socket, (struct sockaddr *) &serveur_sockaddr_in, sizeof ( serveur_sockaddr_in ) ) == -1 ) { perror ( "connect" ) ; exit ( EXIT_FAILURE ) ; } return client_socket ; } /* --------------------------------*/ int main ( int argc , char **argv ) {

356

16.5. Interface de programmation

char unsigned short int

*serveur ; port ; client_socket ;

if ( argc != 3 ) { fprintf ( stderr , "usage: %s serveur port\n" , argv[0] ) ; exit ( EXIT_FAILURE ) ; } serveur = argv[1] ; port = atoi ( argv[2] ) ; client_socket = socket_client ( serveur , port ) ; for ( ; ; ) { fd_set rset ; FD_ZERO ( &rset ) ; FD_SET ( client_socket , &rset ) ; FD_SET ( STDIN_FILENO , &rset ) ; if (select( client_socket+1, &rset, NULL, NULL, NULL) == -1 ) { perror ( "select" ) ; exit ( EXIT_FAILURE ) ; } if (FD_ISSET(client_socket, &rset) ) { int octets ; unsigned char tampon[BUFSIZ] ; octets = read(client_socket, tampon, sizeof(tampon)); if ( octets == 0 ) { close ( client_socket ) ; exit ( EXIT_SUCCESS ) ; } write(STDOUT_FILENO, tampon, octets); } if (FD_ISSET(STDIN_FILENO, &rset)) { int octets ; unsigned char tampon[BUFSIZ] ; octets = read(STDIN_FILENO, tampon, sizeof(tampon)); if ( octets == 0 ) { close ( client_socket ) ; exit ( EXIT_SUCCESS ) ; } write(client_socket, tampon, octets); } } exit ( EXIT_SUCCESS ) ; }

Exemple de serveur TCP

La programmation dun serveur TCP est somme toute fort semblable celle dun client TCP : 1. cration de la socket ; 2. choix du port couter ; 357

Chapitre 16. Les sockets sous Unix 3. attente dune connexion ; 4. dialogue avec le client ; 5. fermeture de la connexion.
Cration de la socket

La cration dune socket serveur se droule strictement de la mme faon que la cration dune socket client :
#include <sys/types.h> #include <sys/socket.h> int serveur_socket ; serveur_socket = socket ( PF_INET , SOCK_STREAM , 0 ) ; if ( serveur_socket == -1 ) { /* erreur */ }

On appelle cependant une fonction supplmentaire, setsockopt(). En effet, aprs larrt dun serveur TCP, le systme dexploitation continue pendant un certain temps couter sur le port TCP que ce dernier utilisait an de prvenir dventuels client dont les paquets se seraient gars de larrt du serveur, ce qui rend la cration dun nouveau serveur sur le mme port impossible pendant ce temps-l. Pour forcer le systme dexploitation rutiliser le port, on utilise loption SO_REUSEADDR de la fonction setsockopt() :
int option = 1 ; if ( setsockopt ( serveur_socket , SOL_SOCKET , SO_REUSEADDR , &option , sizeof ( option ) ) == -1 ) { /* erreur */ }

Choix du port couter

Le choix du port sur lequel couter se fait au moyen de la fonction bind() :


#include <netinet/in.h> struct sockaddr_in unsigned short serveur_sockaddr_in ; port ; sizeof ( serveur_sockaddr_in ) ) ; = AF_INET ; = htons ( port ) ; = INADDR_ANY ;

memset ( &serveur_sockaddr_in , 0 , serveur_sockaddr_in.sin_family serveur_sockaddr_in.sin_port serveur_sockaddr_in.sin_addr.s_addr

if ( bind ( serveur_socket , ( struct sockaddr * ) &serveur_sockaddr_in , sizeof ( serveur_sockaddr_in ) ) == -1 ) { /* erreur */ }

358

16.5. Interface de programmation Cette fonction prend en argument : 1. le descripteur de chier de la socket pralablement cre ; 2. un pointeur vers une structure de type sockaddr ; 3. la taille de cette structure. La structure de type sockaddr_in sinitialise de la mme faon que pour le client TCP (noubliez pas de convertir le numro de port au format rseau au moyen de la fonction htons()). La seule diffrence est lutilisation du symbole INADDR_ANY la place de ladresse IP. En effet, un serveur TCP est sens couter sur toutes les adresses IP dont dispose la machine, ce que permet INADDR_ANY. La fonction listen() permet dinformer le systme dexploitation de la prsence du nouveau serveur :
if ( listen ( serveur_socket , SOMAXCONN ) == -1 ) { /* erreur */ }

Cette fonction prend en argument : 1. le descripteur de chier de la socket pralablement cre ; 2. le nombre maximum de connexions pouvant tre en attente (on utilise gnralement le symbole SOMAXCONN qui reprsente le nombre maximum de connexions en attente autoris par le systme dexploitation).
Attente dune connexion

La fonction accept() permet dattendre quun client se connecte au serveur :


int struct sockaddr_in int client_socket ; client_sockaddr_in ; taille = sizeof ( client_sockaddr_in ) ;

client_socket = accept ( serveur_socket , ( struct sockaddr * ) &client_sockaddr_in , &taille ) ; if ( client_socket == -1 ) { /* erreur */ }

Cette fonction prend trois arguments : 1. le numro de descripteur de la socket ; 2. un pointeur vers une structure de type sockaddr, structure qui sera remplie avec les paramtres du client qui sest connect ; 3. un pointeur vers un entier, qui sera rempli avec la taille de la structure ci-dessus. En cas de succs de la connexion, la fonction accept() renvoie un nouveau descripteur de chier reprsentant la connexion avec le client. Le descripteur initial peut encore 359

Chapitre 16. Les sockets sous Unix servir de nouveaux clients pour se connecter au serveur, cest pourquoi les serveurs appellent ensuite gnralement la fonction fork() an de crer un nouveau processus charg du dialogue avec le client tandis que le processus initial continuera attendre des connexions. Aprs le fork(), chaque processus ferme le descripteur qui lui est inutile (exactement comme avec les tuyaux).
Dialogue avec le client

Le dialogue avec le client seffectue de la mme faon que dans le cas du client (voir paragraphe 16.5).
Fermeture de la connexion

La fermeture de la connexion seffectue de la mme faon que dans le cas du client (voir paragraphe 16.5).
Serveur TCP complet

Listing 16.2 Exemple de serveur


#include #include #include #include #include #include <sys/types.h> <netdb.h> <stdio.h> <stdlib.h> <string.h> <unistd.h>

#include <netinet/in.h> #include <arpa/inet.h> #include <sys/socket.h> /* -------------------------------------- */ int socket_serveur ( unsigned short port ) { int serveur_socket , option = 1 ; struct sockaddr_in serveur_sockaddr_in ; serveur_socket = socket ( PF_INET , SOCK_STREAM , 0 ) ; if ( serveur_socket == -1 ) { perror ( "socket" ) ; exit ( EXIT_FAILURE ) ; } if (setsockopt(serveur_socket, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option)) == -1 ) { perror ( "setsockopt" ) ; exit ( EXIT_FAILURE ) ; } memset ( &serveur_sockaddr_in , 0 , serveur_sockaddr_in.sin_family serveur_sockaddr_in.sin_port serveur_sockaddr_in.sin_addr.s_addr sizeof ( serveur_sockaddr_in ) ) ; = AF_INET ; = htons ( port ) ; = INADDR_ANY ;

360

16.5. Interface de programmation


if (bind(serveur_socket, (struct sockaddr *)&serveur_sockaddr_in, sizeof(serveur_sockaddr_in)) == -1 ) { perror ( "bind" ) ; exit ( EXIT_FAILURE ) ; } if (listen(serveur_socket, SOMAXCONN) == -1 ) { perror ( "listen" ) ; exit ( EXIT_FAILURE ) ; } return serveur_socket ; } /* ------------------------------ */ void serveur ( int client_socket ) { for ( ; ; ) { fd_set rset ; FD_ZERO ( &rset ) ; FD_SET ( client_socket , &rset ) ; FD_SET ( STDIN_FILENO , &rset ) ; if (select(client_socket+1, &rset, NULL, NULL, NULL) == -1) { perror ( "select" ) ; exit ( EXIT_FAILURE ) ; } if (FD_ISSET(client_socket, &rset) ) { int octets ; unsigned char tampon[BUFSIZ] ; octets = read(client_socket, tampon, sizeof(tampon)); if ( octets == 0 ) { close ( client_socket ) ; printf ( ">>> Deconnexion\n" ) ; exit ( EXIT_SUCCESS ) ; } write (STDOUT_FILENO, tampon, octets); } if (FD_ISSET(STDIN_FILENO, &rset)) { int octets ; unsigned char tampon[BUFSIZ] ; octets = read(STDIN_FILENO, tampon, sizeof(tampon)); if ( octets == 0 ) { close ( client_socket ) ; exit ( EXIT_SUCCESS ) ; } write(client_socket, tampon, octets); } } } /* ------------------------------- */ int main ( int argc , char **argv ) { unsigned short port ; int serveur_socket ; if ( argc != 2 ) {

361

Chapitre 16. Les sockets sous Unix


fprintf ( stderr , "usage: %s port\n" , argv[0] ) ; exit ( EXIT_FAILURE ) ; } port = atoi (argv[1]); serveur_socket = socket_serveur (port); for ( ; ; ) { fd_set rset ; FD_ZERO ( &rset ) ; FD_SET ( serveur_socket , &rset ) ; FD_SET ( STDIN_FILENO , &rset ) ; if (select(serveur_socket+1, &rset, NULL, NULL, NULL) == -1) { perror ( "select" ) ; exit ( EXIT_FAILURE ) ; } if (FD_ISSET(serveur_socket, &rset)) { int client_socket ; struct sockaddr_in client_sockaddr_in ; socklen_t taille = sizeof ( client_sockaddr_in ) ; struct hostent *hostent ; client_socket = accept (serveur_socket, (struct sockaddr *) &client_sockaddr_in, &taille); if ( client_socket == -1 ) { perror ( "accept" ) ; exit ( EXIT_FAILURE ) ; } switch (fork( )) { case -1 : /* erreur */ perror ( "fork" ) ; exit ( EXIT_FAILURE ) ; case 0 : /* processus fils */ close ( serveur_socket ) ; hostent = gethostbyaddr( (char *) &client_sockaddr_in.sin_addr.s_addr, sizeof(client_sockaddr_in.sin_addr.s_addr), AF_INET); if ( hostent == NULL ) { perror ( "gethostbyaddr" ) ; exit ( EXIT_FAILURE ) ; } printf ( ">>> Connexion depuis %s [%s]\n" , hostent->h_name , inet_ntoa ( client_sockaddr_in.sin_addr ) ) ; serveur (client_socket); exit(EXIT_SUCCESS); default : /* processus pere */ close (client_socket); } } } exit ( EXIT_SUCCESS ) ; }

362

17
Les threads POSIX sous Unix

17.1

Prsentation

Rappels sur les threads

Dans les architectures multi-processeurs mmoire partage (SMP pour Symetric Multi Processing), les threads peuvent tre utiliss pour implmenter un paralllisme dexcution. Historiquement, les fabricants ont implment leur propre version de la notion de thread et de ce point de vue la portabilit du code tait un rel souci pour la plupart des dveloppeurs. Les systmes Unix ont vu assez rapidement la naissance dune interface de programmation en langage C des threads et cette normalisation est maintenant connue comme les threads POSIX, ou encore les pthreads. Quest-ce quun thread ? Techniquement parlant, on peut voir un thread comme un ot dinstructions indpendant dont lexcution sera conduite sous la houlette du systme dexploitation. Du point de vue du dveloppeur, ce concept peut tre vu comme une procdure de son programme dont lexcution est indpendante du reste du droulement du programme (ou presque !). Pour aller un peu plus loin dans lesprit du dveloppeur, imaginons quun programme contiennent plusieurs procdures. La notion de thread permet dexcuter, au sein de ce processus de manire simultanne / indpendante, toutes ces procdures sous la haute gouvernance du systme dexploitation. Nous pourrions, si nous ne connaissions pas ce quest un processus sous UNIX, dire que ces procdures sont autant de processus indpendants. Sous certains UNIX, les threads sont grs lintrieur du processus (leur porte est limite celle du processus). Ces processus indpendants partagent nombre de choses avec le processus dont ils sont issus. Parmi ces choses nous retrouvons le pid ; lenvironnement ; le rpertoire de travail ; les instructions du programme (du moins la partie commune), i.e. la section texte ; 363

Chapitre 17. Les threads POSIX sous Unix le tas (les allocations mmoires) ; les descripteurs de chiers ouverts ; les bibliothques partages (branchement vers dautres segments de texte partags par dautres processus). Par contre, sont spciques chaque thread les donnes suivantes : les registres ; la pile ; le pointeur dinstruction. Un thread nest donc pas un processus au sens o nous lavons tudi, il na pas dentre dans la table des processus et il partage un espace mmoire avec le processus qui la cr.

Un thread permet dexcuter un ensemble dinstructions de manire simultane par plusieurs processeurs, quand larchitecture hte le permet. Cette simultanit impose quelques contraintes, notamment lors de laccs la mmoire. Certains accs ne peuvent pas tre raliss de manire concurrente (deux threads ne doivent pas essayer dcrire au mme moment au mme endroit). Lorsque la simultanit dexcution est impossible (ou tout simplement nfaste au bon droulement du programme) on entre dans ce que lon appelle une section critique 1 .
1. En effet, comme les diffrents threads partagent le mme tas, les mmes descripteurs de chiers, des accs mal matriss ces donnes partages peuvent avoir des effets de bord trs nfastes. Cest donc dans cette procdure que les choses deviennent critiques. De plus, lutilisation de mcanisme de synchronisation peut rendre cette section critique bloquante, limage de processus crivain/lecteur dans un tuyau dont le lecteur attend lcrivain qui lui mme attend le lecteur. . .

364

17.1. Prsentation
LAPI systme de manipulation des threads

Pour utiliser les threads POSIX il convient dinclure un chier den-tte : pthread. h. Il est ncessaire de lier les programmes avec la bibliothque qui contient le code des threads POSIX, savoir libpthread.* par la directive -lpthread passe au compilateur.
pthread_create() : cette fonction permet de crer un thread. Elle prend en

paramtre un pointeur vers un identiant de thread, un pointeur vers un ensemble dattributs, un pointeur vers la fonction excuter et enn un pointeur vers des donnes lequel sera transmis comme paramtre la fonction excuter :
int pthread_create(pthread_t *restrict thread, const pthread_attr_t *restrict attr, void *(*start_routine)(void *), void *restrict arg)

pthread_t *restrict thread : pointeur vers un type pthread qui sera rempli par la fonction et servira identier le thread ; const pthread_attr_t *restrict attr : pointeur vers une structure dattributs, peut tre le pointeur NULL dans ce cas les attributs par dfaut sont utiliss ; void *(*start_routine)(void *) : pointeur vers la fonction qui sera excute. Cette fonction accepte en paramtre une adresse et retourne une adresse ; void *restrict arg : adresse des donnes qui seront ventuellement passes la fonction excute. Si lon ne souhaite pas passer de donnes, on peut passer NULL. Lutilisation du mot cl restrict nest pas une chose utile au compilateur. Ce mot cl sert simplement attirer votre attention sur le fait que la variable laquelle est accol ce mot cl ne doit tre accde, du point de vue mmoire, que par cette fonction cet instant. Il est donc judicieux que lidentieur de thread, les attributs et les arguments soient uniques chaque appel de la fonction pthread_create(). Comme la plupart des appels systmes, cette fonction retourne 0 quand tout se passe bien et un nombre diffrent de zro en cas derreur. pthread_join() : cette fonction permet dattendre la n dun thread, un peu comme le ferait waitpid(). Cette fonction bloque jusqu ce que le thread retourne. :
int pthread_join(pthread_t thread, void **value_ptr);

pthread_t thread : identiant du thread dont on attend la terminaison ; void **value_ptr : pointeur permettant de rcuprer ladresse utilise par le thread pour consigner sa valeur de retour. Ce pointeur peut tre NULL si lon ne dsire pas connatre cette valeur de retour. Cette fonction ne doit tre appele quune seule fois par identiant de thread, sinon son comportement est indni. La fonction appele lors de la cration du thread renvoie une adresse mmoire alloue sur le tas dans laquelle elle place sa valeur de retour. Cest cette adresse qui sera place dans la variable value_ptr dont on donne ladresse. 365

Chapitre 17. Les threads POSIX sous Unix

Lexemple qui suit donne la marche suivre pour crer un thread et attendre sa terminaison avec la rcupration de sa valeur de terminaison. Il faut en effet bien avoir lesprit que lors de sa terminaison, tout ce qui se trouve dans la pile du thread (variables locales) est perdu. Retourner ladresse dune variable locale serait donc aberrant : Quand un thread se termine, le rsultat dun accs aux variables locales (auto) du thread est indni. Ainsi, une rfrence des variables locales du thread se terminant ne doit pas tre utilise comme paramtre de pthread_exit().
#include #include #include #include <pthread.h> <stdio.h> <stdlib.h> <unistd.h>

void *f(void *param) { int *num = (int *)param; int *retval=NULL; /*Allocation sur le tas*/ if ((retval = malloc(2*sizeof(int))) == NULL) { perror("malloc in thread failed"); return NULL; } if (*num == 0) { retval[0] = 1; retval[1] = 2; } else { retval[0] = 10; retval[1] = 20; } return (void *)retval; } int main(int argc, char **argv) { pthread_t t[2]; int n[2] = {0,1}; int ret; void *value_ptr; int *return_value_a; int *return_value_b; if ((ret = pthread_create(&(t[0]), NULL,f,(void *)(&(n[0])))) !=0) exit(EXIT_FAILURE); if ((ret = pthread_create(&(t[1]), NULL,f,(void *)(&(n[1])))) !=0)

366

17.1. Prsentation
exit(EXIT_FAILURE); if (pthread_join(t[0],&value_ptr) != 0) exit(EXIT_FAILURE); return_value_a = (int *)value_ptr; printf("Thread 0 returns with: %d %d (stored at %p)\n", return_value_a[0],return_value_a[1],value_ptr); if (pthread_join(t[1],&value_ptr) != 0) exit(EXIT_FAILURE); return_value_b = (int *)value_ptr; printf("Thread 1 ends with: %d %d (stored at %p)\n", return_value_b[0],return_value_b[1],value_ptr); exit(EXIT_SUCCESS); }

Synchronisation par verrou

Comme nous pouvons dnir des points de synchronisation entre processus en utilisant un dialogue par tuyau et des mcanismes bloquants de lecture / criture, il est possible de raliser la mme chose sur les threads mais par lutilisation de verrou selon un principe dexclusion mutuelle (do le nom de mutex). Le principe est assez simple, il sagit dessayer de prendre un verrou, lequel ne peut tre pris quune seule fois. La prise du verrou, qui est une opration bloquante, par le premier thread empche un autre thread davancer si ce dernier essaye lui aussi de prendre le mme verrou. On se sert gnralement de ces verrous an de raliser deux types doprations : la synchronisation de plusieurs threads par un autre (que nous appellerons le thread de synchronisation). Chaque thread essaye lissue de lexcution dun certain nombre dinstructions et avant dentamer le reste de leur travail de prendre un verrou. Ce verrou est pris ds le dpart par le thread de synchronisation, puis est relch un instant prcis (dans le temps ou par analyse de la valeur de certaines variables) ; la protection des accs en criture sur le tas (qui quivaut une synchronisation en mmoire). Si plusieurs threads crivent au mme endroit en mme temps (cela peut arriver avec une architecture SMP), le rsultat peut devenir imprvisible pour celui qui lit. En effet les lectures font appel des variables dont la valeur peut trs bien se situer dans le cache dun des processeurs (cest parfois mme demand par le compilateur). Il est donc impratif de synchroniser les caches mmoires avant de raliser cette lecture car il serait dangereux de croire que le contenu de la mmoire rete exactement le contenu du cache. On demande donc une synchronisation entre la mmoire et les caches des processeurs. Les diffrents threads devant accder la mme portion de la mmoire essaieront dans un premier temps de prendre un verrou, raliseront leur criture, puis relcheront le verrou. Cela aura pour cause de synchroniser ltat de la mmoire. Mais cest vritablement la combinaison unlock / lock qui ralisera ce que lon appelle une cohrence de cache. Cest aussi quelque part assez tranquillisant, imaginons 367

Chapitre 17. Les threads POSIX sous Unix en effet un seul instant que le verrou (situ quelque part dans le tas, donc en mmoire partage) se trouve dans un registre du processeur 1. . . Les verrous se grent laide de plusieurs appels systmes dont nous exploiterons les deux qui suivent. pthread_mutex_lock() permet de prendre un verrou et de le rendre ainsi indisponible pour les autres fonctions. Cette fonction est bloquante, donc si le verrou est dj pris, lappel pthread_mutex_lock() va suspendre lexcution du thread jusqu ce que le verrou puisse tre pris.
int pthread_mutex_lock(pthread_mutex_t *mutex)

pthread_mutex_t *mutex : adresse du verrou que lon dsire prendre. Cette adresse doit naturellement correspondre une adresse mmoire du tas ou de la zone de donnes mais srement pas une variable locale qui ne pourrait pas tre partage entre les threads. pthread_mutex_unlock() permet de relcher le verrou et de le rendre ainsi disponible pour dautres fonctions. Lorsque celles-ci prendront le verrou, il y aura (il devrait y avoir) une cohrence de cache an de garantir que la mmoire partage accde aprs la prise du verrou sera correcte.
int pthread_mutex_unlock(pthread_mutex_t *mutex);

pthread_mutex_t *mutex : adresse du verrou que lon dsire relcher. Mme remarque que pour la fonction prcdente.
Rveil de threads

Il est possible de suspendre lexcution dun thread et dattendre quune condition devienne valide pour entamer lexcution dune partie du code. Ce genre dattente est assez agrable car elle permet de rveiller plusieurs threads de manire simultane alors que la prise de verrou tait mutuellement exclusive. Cependant, les diffrentes fonctions que nous allons exposer doivent tre manipules avec rigueur, sinon le risque dimpasse (deadlock) est garanti ! En gnral les conditions sont initialises, puis chaque thread attend que la condition devienne correcte, un autre thread signale que la condition devient correct ce qui rveille les autres threads. Une condition est toujours associe un verrou et ce pour viter les courses la mort (race conditions). Imaginons en effet que pendant que le thread 1 se prpare attendre la condition, un autre thread la signale comme valide. Lopration de mise en attente tant un enchanement doprations, ces dernires, et notamment celle qui vrie la condition, sont places sous leffet de synchronisation dun verrou. Plusieurs fonctions systmes permettent de grer les conditions. pthread_cond_init() : cette fonction permet dinitialiser une condition. Elle sutilise trs souvent avec les attributs par dfaut.
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);

368

17.1. Prsentation pthread_cond_t *cond : ladresse dune variable de condition ; pthrad_condattr_t *cond_attr : les attributs lies la condition. Si lon veut choisir les attributs par dfaut on peut passer une adresse NULL. pthread_cond_wait() : cette fonction permet dattendre une condition. Cette fonction a besoin dun verrou pour se protger contre dventuelles courses la mort. Cette fonction va tout dabord relcher le verrou pass en paramtre et attendre le signal de condition sans consommer de CPU (suspension dactivit). la rception de ce dernier, elle reprendra le verrou et retournera la fonction appelante. Il est donc primordial davoir pris le verrou pass en paramtre avant dappeler cette fonction. Il est tout aussi primordial de relcher le verrou lorsque que la fonction dattente se termine. Voici un extrait du manuel : . . . pthread_cond_wait() dverrouille de manire atomique le mutex (comme le ferait pthread_mutex_unlock()) et attend que la variable de condition cond soit signale. Lexcution du thread est suspendue et ne consomme aucun temps CPU jusqu ce que le signal associ la variable de condition arrive. Le mutex doit tre verrouill par la thread appelant avant lappel de la fonction pthread_cond_wait. Avant de revenir la fonction appelante, pthread_cond_wait verrouille de nouveau le mutex (comme le ferait pthread_mutex_lock()). Dverrouiller le mutex et suspendre lactivit en attendant la variable de condition est une opration atomique. Ainsi, si tous les threads essayent dentre de jeu dacqurir le mutex avant de signaler la condition, cela garantit que la condition ne peut tre signale (et donc aussi ignore) entre le temps o le thread verrouille le mutex et le temps o il commence attendre le signal de condition. . . .
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

pthread_cond_t *cond : ladresse dune variable de condition ; pthread_mutex_t *mutex : ladresse dun verrou. pthread_cond_broadcast() permet de signaler tous les threads que la condition est leve et permet ainsi de tous les rveiller.
int pthread_cond_broadcast(pthread_cond_t *cond);

pthread_cond_t *cond : ladresse de la variable de condition. Il existe aussi une fonction permettant de ne rveiller quun unique thread parmi ceux qui sont suspendus dans lattente du signal associ une mme variable de condition.
int pthread_cond_signal(pthread_cond_t *cond);

pthread_cond_t *cond : ladresse de la variable de condition.

369

Chapitre 17. Les threads POSIX sous Unix

17.2

Exercices

Question 1 Ecrire un programme multi-thread permettant de calculer moyenne et cart type dune suite de nombres de manire parallle. La fonction main() initialisera un tableau de nombres entiers alatoires puis elle crera deux threads. Le premier calculera et retournera la somme des nombres, le second calculera et retournera la somme des carrs des nombres. La fonction main() attendra la n des deux threads et utilisera les valeurs de retour pour donner moyenne et cart type. On rappelle que = et 2 = 1 N 2 N i i 1 N i N i 1 N i N i
2

Ecrire une version simple et sans utiliser de thread de ce programme. Mesurer les temps dexcution (man time). Question 2 Reprendre la mme problmatique de calcul de moyenne et dcart type mais en divisant le tableau en N parties (N sera pass en ligne de commande). Crer 2 N threads qui travailleront chacun sur une partie du tableau. Analyser les performances en fonction de N. Question 3 On dsire peauner le programme prcdent. Un premier thread ralise linitialisation du tableau alatoire. Deux autres threads attendent une leve de condition pour dmarrer le calcul de la somme et de la somme des carrs. Lorsque le premier thread termine le remplissage du tableau, il met un signal de rveil destination des deux threads de calcul.

370

17.3. Corrigs

17.3

Corrigs

Premier exercice

Version squentielle sans utilisation de thread. On utilise un chronomtre dans la fonction main() pour mesure le temps consomm par les calculs squentielles. Ceci permet de ne pas prendre en compte la gnration du tableau de nombres. Listing 17.1 Calcul sans utilisation des threads
#include <stdio.h> #include <stdlib.h> #include <math.h> #include <sys/time.h> #define MAX_NUM 10000000 struct my_chrono { struct timeval tp_start; struct timeval tp_current; }; void start_chrono(struct my_chrono *c) { gettimeofday(&(c->tp_start),NULL); } int stop_chrono(struct my_chrono *c) { int res; gettimeofday(&(c->tp_current),NULL); res = (int)(((c->tp_current.tv_sec) (c->tp_start.tv_sec)) * 1000 + ((c->tp_current.tv_usec) (c->tp_start.tv_usec)) / 1000 ); return res; } void sequentiel(int *tt, float *m, float *e) { long k; float sum, sumc; for(k=0,sum = 0.0;k<MAX_NUM;k++) { sum += tt[k]; } for(k=0,sumc = 0.0;k<MAX_NUM;k++) { sumc += tt[k] * tt[k]; } *m = sum/MAX_NUM; *e = (float)sqrt(sumc/MAX_NUM - *m * *m); return; } int main(int argc, char **argv) { int *num = NULL; long k; int r; float m,e; struct my_chrono c; if ((num =malloc(MAX_NUM*sizeof(int))) == NULL) {

371

Chapitre 17. Les threads POSIX sous Unix


perror("allocation impossible"); exit(EXIT_FAILURE); } for(k=0;k<MAX_NUM;k++) { num[k] = (int)random() % 1000; } start_chrono(&c); sequentiel(num,&m,&e); r = stop_chrono(&c); print("Moyenne (S): %f Ecart type: %f Temps ecoule:%d ms\n",m,e,r); exit(EXIT_SUCCESS); }

Lexcution de ce programme donne les rsultats suivants (valeur non reproductible et totalement dpendante de la charge, et du CPU, voir ce propos le complment sur larchitecture donn en n de document) :
moi@ici:> time ./stat_noth Moyenne (S): 499.2311 Ecart type: 282.3061 Temps ecoule:810 ms moi@ici:>

Version multi-thread du programme (toujours avec un chronomtre) : Listing 17.2 Version parallle du calcul
#include <stdio.h> #include <stdlib.h> #include <math.h> #include <pthread.h> #include <sys/time.h> #define MAX_NUM 10000000 struct my_chrono { struct timeval tp_start; struct timeval tp_current; }; void start_chrono(struct my_chrono *c) { gettimeofday(&(c->tp_start),NULL); } int stop_chrono(struct my_chrono *c) { int res; gettimeofday(&(c->tp_current),NULL); res = (int)(((c->tp_current.tv_sec) (c->tp_start.tv_sec)) * 1000 + ((c->tp_current.tv_usec) (c->tp_start.tv_usec)) / 1000 ); return res; } void *calcul_sum(void * param) { int *tt=(int *)param; float *retval; long k; if ((retval = malloc(sizeof(float))) == NULL) return NULL; for(k=0,*retval = 0.0;k<MAX_NUM;k++) {

372

17.3. Corrigs

*retval += tt[k]; } return ((void *)retval); } void *calcul_sumc(void * param) { int *tt=(int *)param; float *retval; long k; if ((retval = malloc(sizeof(float))) == NULL) return NULL; for(*retval = 0.0,k=0;k<MAX_NUM;k++) { *retval += tt[k] * tt[k]; } return ((void *)retval); } int main(int argc, char **argv) { pthread_t t[2]; int ret; int *num = NULL; long k; int r; float *sum, *sumc; float m,e; struct my_chrono c; if ((num =malloc(MAX_NUM*sizeof(int))) == NULL) { perror("allocation impossible"); exit(EXIT_FAILURE); } for(k=0;k<MAX_NUM;k++) { num[k] = (int)random() % 1000; } start_chrono(&c); if ((ret = pthread_create(t+0,NULL,calcul_sum,(void *)num)) != 0) { perror("calcul_sum impossible"); exit(EXIT_FAILURE); } if ((ret = pthread_create(t+1,NULL,calcul_sumc,(void *)num)) != 0) { perror("calcul_sumc impossible"); exit(EXIT_FAILURE); } if ((ret = pthread_join(t[0],(void *)&sum)) != 0) { perror("calcul_sum non joignable"); exit(EXIT_FAILURE); } if ((ret = pthread_join(t[1],(void *)&sumc)) != 0) { perror("calcul_sumc non joignable"); exit(EXIT_FAILURE); } r = stop_chrono(&c); if (sum == NULL || sumc == NULL) { fprintf(stderr,"Terminaison manquee\n"); exit(EXIT_FAILURE); } m = *sum/MAX_NUM;

373

Chapitre 17. Les threads POSIX sous Unix


e = (float)sqrt(*sumc/MAX_NUM - m*m); free(sum);free(sumc); printf("Moyenne (T): %f Ecart type: %f Temps ecoule:%d ms\n",m,e,r); exit(EXIT_SUCCESS); }

Temps dexcution :
moi@ici:> time ./stat_th Moyenne (T): 499.2311 Ecart type: 282.3061 Temps ecoule:511 ms moi@ici:>

Deuxime exercice

On ne se sert plus de la valeur de retour des threads pour obtenir les sommes. Puisquil faut passer une structure plus complexe dans les paramtres dun thread, on glisse lintrieur de cette structure : laccs ladresse du tableau, cest un paramtre que tous les threads utilisent, mais il est accd en lecture ; laccs la valeur de la somme et de la somme des carrs, mais ce seront des paramtres locaux chaque thread ; laccs aux index de dbut et de n de calcul dans le tableau gnral, qui sont, certes, accds en lecture, mais diffrents dun thread lautre. Le programme se droule donc en plusieurs tapes. La premire tape concerne lallocation du tableau gnral, lallocation du tableau didentiant de thread et lallocation des structures de paramtres (avec mise en place des valeurs qui vont bien). La deuxime tape lance le remplissage du tableau, le dmarrage des threads puis leur attente et enn lafchage du rsultat et du temps coul. Listing 17.3 Passage de paramtre
#include <stdio.h> #include <stdlib.h> #include <math.h> #include <pthread.h> #include <sys/time.h> #define MAX_NUM 10000000 struct t_param { int *tt; long start_idx,end_idx; float sum,sumc; }; struct my_chrono { struct timeval tp_start; struct timeval tp_current; }; void start_chrono(struct my_chrono *c) { gettimeofday(&(c->tp_start),NULL);

374

17.3. Corrigs

} int stop_chrono(struct my_chrono *c) { int res; gettimeofday(&(c->tp_current),NULL); res = (int)(((c->tp_current.tv_sec) (c->tp_start.tv_sec)) * 1000 + ((c->tp_current.tv_usec) (c->tp_start.tv_usec)) / 1000 ); return res; } void *calcul_sum(void * param) { struct t_param *prm = (struct t_param *)param; long k; for(k=prm->start_idx;k<prm->end_idx;k++) { prm->sum += prm->tt[k]; } return (NULL); } void *calcul_sumc(void * param) { struct t_param *prm = (struct t_param *)param; long k; for(k=prm->start_idx;k<prm->end_idx;k++) { prm->sumc += (prm->tt[k]*prm->tt[k]); } return (NULL); }

int main(int argc, char **argv) { pthread_t *t=NULL; struct t_param *p=NULL; int *tt=NULL; int num_thread; struct my_chrono c; long k; int ret,r; float sum,sumc,m,e; num_thread = (argc >= 2 ? atoi(argv[1]):4); if ((p =malloc(num_thread*sizeof(struct t_param))) == NULL) { perror("allocation p impossible"); exit(EXIT_FAILURE); } if ((t =malloc(2*num_thread*sizeof(pthread_t))) == NULL) { perror("allocation t impossible"); exit(EXIT_FAILURE); } if ((tt =malloc(MAX_NUM*sizeof(int))) == NULL) { perror("allocation tt impossible"); exit(EXIT_FAILURE); } for(k=0;k<num_thread;k++) { p[k].tt = tt;

375

Chapitre 17. Les threads POSIX sous Unix


p[k].start_idx = k*MAX_NUM/num_thread; p[k].end_idx = (k+1)*MAX_NUM/num_thread; p[k].sum = p[k].sumc = 0.0; } for(k=0;k<MAX_NUM;k++) { tt[k] = (int)random() % 1000; } start_chrono(&c); for(k=0;k<num_thread;k++) { if ((ret = pthread_create(t+2*k,NULL, calcul_sum,(void *)(p+k))) != 0) { perror("calcul_sum impossible"); exit(EXIT_FAILURE); } if ((ret = pthread_create(t+2*k+1,NULL, calcul_sumc,(void *)(p+k))) != 0) { perror("calcul_sum impossible"); exit(EXIT_FAILURE); } } for(k=0;k<num_thread;k++) { if ((ret = pthread_join(t[2*k],NULL)) != 0) { perror("calcul_sum impossible"); exit(EXIT_FAILURE); } if ((ret = pthread_join(t[2*k+1],NULL)) != 0) { perror("calcul_sum impossible"); exit(EXIT_FAILURE); } } r = stop_chrono(&c); for(k=0,sum=0.0,sumc=0.0;k<num_thread;k++) { sum += p[k].sum; sumc += p[k].sumc; } m = sum/MAX_NUM; e = (float)sqrt(sumc/MAX_NUM - m*m); printf("Moyenne (T): %f Ecart type: %f Temps ecoule: %dms\n",m,e,r); exit(EXIT_SUCCESS); }

Cest naturellement le dernier thread qui xe larrt du programme. Le fait dattendre dans lordre les threads peut parfois entraner un certain retard dans la mesure o le thread 1 peut trs bien se terminer en dernier en raison de lordonnanceur. Il est impossible de prdire lequel des threads se terminera en premier.

Troisime exercice

La structure prcdente se voit complte par deux adresses, celle dun mutex et celle dune condition. Le thread dinitialisation va envoyer le signal de lever de condition tous les threads qui sont en attente, savoir les deux threads de calcul qui se sont arrts sur lattente de condition. On suit scrupuleusement le manuel qui signale quil faut imprativement avoir verrouill le mutex avant de se placer sur lattente de condition. On continue ce respect du manuel en dbloquant le verrou lorsque la 376

17.3. Corrigs fonction dattente conditionnelle retourne. La sortie du programme dont le code source suit est par exemple :
moi@ici:> ./waitsomeone init_tableau starts sum trie to start sumc trie to start init_tableau stops sum starts sumc starts sum stops sumc stops Moyenne (T): 171.7986 Ecart type: 382.6317 Temps ecoule: 3213ms

Listing 17.4 Utilisation des conditions


#include <stdio.h> #include <stdlib.h> #include <math.h> #include <pthread.h> #include <sys/time.h> #define MAX_NUM 100000000 struct t_param { int *tt; int numero; long num_nombre; float sum,sumc; pthread_mutex_t *mutex; pthread_cond_t *condition; }; struct my_chrono { struct timeval tp_start; struct timeval tp_current; }; void start_chrono(struct my_chrono *c) { gettimeofday(&(c->tp_start),NULL); } int stop_chrono(struct my_chrono *c) { int res; gettimeofday(&(c->tp_current),NULL); res = (int)(((c->tp_current.tv_sec) (c->tp_start.tv_sec)) * 1000 + ((c->tp_current.tv_usec) (c->tp_start.tv_usec)) / 1000 ); return res; } void *init_tableau(void * param) { struct t_param *prm = (struct t_param *)param; long k; fprintf(stderr,"init_tableau starts\n"); for(k=0;k<prm->num_nombre;k++) { prm->tt[k] = (int)random() % 1000; } fprintf(stderr,"init_tableau stops\n");

377

Chapitre 17. Les threads POSIX sous Unix


pthread_cond_broadcast(prm->condition); return (NULL); } void *calcul_sum(void * param) { struct t_param *prm = (struct t_param *)param; long k; fprintf(stderr,"sum trie to start\n"); pthread_mutex_lock(prm->mutex); pthread_cond_wait(prm->condition,prm->mutex); pthread_mutex_unlock(prm->mutex); fprintf(stderr,"sum starts\n"); for(k=0;k<prm->num_nombre;k++) { prm->sum += prm->tt[k]; } fprintf(stderr,"sum stops\n"); return (NULL); } void *calcul_sumc(void * param) { struct t_param *prm = (struct t_param *)param; long k; fprintf(stderr,"sumc trie to start\n"); pthread_mutex_lock(prm->mutex); pthread_cond_wait(prm->condition,prm->mutex); pthread_mutex_unlock(prm->mutex); fprintf(stderr,"sumc starts\n"); for(k=0;k<prm->num_nombre;k++) { prm->sumc += (prm->tt[k]*prm->tt[k]); } fprintf(stderr,"sumc stops\n"); return (NULL); }

int main(int argc, char **argv) { pthread_t *t=NULL; struct t_param *p=NULL; int *tt=NULL; int num_thread; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t condition = PTHREAD_COND_INITIALIZER; struct my_chrono c; long k; int ret,r; float sum,sumc,m,e; num_thread = 3; if ((p =malloc(num_thread*sizeof(struct t_param))) == NULL) { perror("allocation p impossible"); exit(EXIT_FAILURE); } if ((t =malloc(2*num_thread*sizeof(pthread_t))) == NULL) { perror("allocation t impossible"); exit(EXIT_FAILURE); } if ((tt =malloc(MAX_NUM*sizeof(int))) == NULL) { perror("allocation tt impossible");

378

17.4. Architectures et programmation parallle


exit(EXIT_FAILURE); } for(k=0;k<num_thread;k++) { p[k].tt = tt; p[k].numero = k; p[k].num_nombre = MAX_NUM; p[k].sum = p[k].sumc = 0.0; p[k].mutex = &mutex; p[k].condition = &condition; } start_chrono(&c); ret = pthread_create(t+0,NULL,init_tableau,(void *)(p+0)); if (ret != 0) { perror("init_tableau impossible"); exit(EXIT_FAILURE); } ret = pthread_create(t+1,NULL,calcul_sum,(void *)(p+1)); if (ret != 0) { perror("calcul_sum impossible"); exit(EXIT_FAILURE); } ret = pthread_create(t+2,NULL,calcul_sumc,(void *)(p+2)); if (ret != 0) { perror("calcul_sumc impossible"); exit(EXIT_FAILURE); } for(k=0;k<num_thread;k++) { if ((ret = pthread_join(t[k],NULL)) != 0) { perror("jonction impossible"); exit(EXIT_FAILURE); } } r = stop_chrono(&c); for(k=0,sum=0.0,sumc=0.0;k<num_thread;k++) { sum += p[k].sum; sumc += p[k].sumc; } m = sum/MAX_NUM; e = (float)sqrt(sumc/MAX_NUM - m*m); printf("Moyenne (T): %f Ecart type: %f Temps ecoule: %dms\n",m,e,r); exit(EXIT_SUCCESS); }

17.4

Architectures et programmation parallle

Deux types darchitectures matrielles apportant des gains en programmation parallle existent : les architectures de type multi-processeurs ; les architectures de type multi-curs . Les diffrences, mme si elles peuvent paratre minces vues de lextrieur, sont pourtant fondamentales. Dans le premier cas, chaque processeur est dot de sa batterie de caches (L1, L2), dun slot sur la carte mre, alors que dans le deuxime cas, la puce intgre deux processeurs qui peuvent partager leur cache de niveau 2 et ne ncessitent pas 379

Chapitre 17. Les threads POSIX sous Unix de passage par la carte mre pour la communication. Notons toutefois que si la srie Athlon 64X2 dAMD permettait un dialogue interne des deux curs, le Pentium D 820, lui, passait par la carte mre pour la communication entre les deux curs (louverture du boitier montrait clairement la prsence de 2 processeurs, donc pratiquement une architecture de type bi-processeur et non bi-cur). Cette diffrence est fondamentale dans la mesure o, nous le savons bien maintenant, le goulet dtranglement dans une architecture reste toujours la communication entre les diffrents constituants. Lorsque ce goulet porte sur le cur mme de larchitecture, i.e. le processeur, cela peut avoir des consquences assez dsastreuses sur les temps de calcul. Notons la fameuse srie des AMD tri-curs , conception permettant de recycler les puces quadri-curs dont un des curs tait dfectueux et dsactiv avant sa mise sur le march ! Le fait de devoir sortir dun processeur pour dialoguer avec lautre est extrmement coteux en latence. Lintgration des deux niveaux de cache dans les puces permet de gagner du temps. Le fait de partager le mme cache de niveau 2 apporte un rel gain de performance mais nous signale clairement que la synchronisation des accs en mmoire lors de lutilisation de threads est une chose fondamentale. Cest un gain car le fait de devoir faire circuler les synchronisations de cache lintrieur du circuit est beaucoup plus rapide que de devoir passer par une architecture extrieure aux processeurs comme cest le cas dans les architectures bi-processeurs . Rappelons que la synchronisation du cache mmoire (qui est une unit trs proche du cur) est fondamentale pour que les lectures dans la mmoire (laquelle a t rapatrie dans le cache de niveau 2) soient correctes. Le partage des caches est une chose trs intressante en matire de performance. Les architectures parallles intgrent des mcanismes de bus snooping qui vrient si une donne prsente dans le cache a fait lobjet de modications de la part dun processeur. Cela impose aux mcanismes dcriture de signaler (et donc denvoyer un message supplmentaire sur le bus systme) tous accs la mmoire. Partager le mme cache permet de saffranchir de cela. Cest une chose que ne pourront jamais raliser des architectures de type multi-processeurs . Pour analyser dans le dtail les performances de vos programmes, vous devriez presque rechercher dans lE NSTA ParisTech des machines darchitectures diffrentes an de trouver la mieux adapte , votre programme, votre systme dexploitation. . .

380

18
Surveillances des entres / sorties sous Unix

18.1

Introduction

Quelquefois, il est ncessaire quune application soit capable de grer plusieurs entres / sorties synchrones simultanment. Par exemple, un serveur sur un rseau doit gnralement tre capable de grer des connections simultanment avec plusieurs clients. La communication avec un client rseau seffectue comme la plupart des entres / sorties sous Unix, cest--dire via un descripteur de chier (cf. chapitre 16). Pour le cas du serveur, grer plusieurs connections revient donc lire / crire sur plusieurs descripteurs de chier la fois. Avec les fonctions vues dans les chapitres prcdents, deux mthodes sont possibles pour grer plusieurs entres / sorties synchrones : traiter les descripteurs de chier dans lordre : dans une boucle for, chaque descripteur est lu / crit dans un ordre prdni. Le problme de cette mthode est que, comme les entres / sorties sont synchrones (cest--dire que les mthodes read et write ne rendent la main que lorsque les donnes ont t lues / crites), les donnes dun client 2 ne seront traits quune fois que le client 1 aura lui mme envoy des donnes traiter ; crer un processus par descripteur de chier : bien que la cration dun nouveau processus ne soit pas difcile sous Unix (cf. chapitre 10), grer plusieurs clients avec cette mthode peut poser des problmes difciles de communication, de synchronisation (notamment pour laccs aux ressources) et savrer coteuse pour le systme lorsque de nombreux clients doivent tre grs. Certains systmes dexploitations offrent la possibilit de congurer les entres / sorties pour quelles soient asynchrones (cest--dire pour que les fonctions read et write soient non bloquantes). Une telle possibilit rsout partiellement le problme puisque dans le cas de lutilisation dune boucle for, il devient alors possible de ne pas rester bloqu sur un client. Cependant, lorsquaucun client ne communique, il est quand mme ncessaire de vrier en permanence ltat de la communication avec chaque client en utilisant de faon inutile le processeur alors que le processus devrait tre endormi dans lattente dune communication. 381

Chapitre 18. Surveillances des entres / sorties sous Unix Enn, il est possible de grer plusieurs communications avec plusieurs clients avec des signaux (cf. chapitre 14). Par exemple, le serveur est endormi dans lattente dun signal. Lorsquun client communique avec le serveur, il envoie les donnes communiquer puis rveille le serveur via lutilisation dun signal utilisateur. Cependant, cette mthode nest valable que si les processus se situent tous sur la mme machine (ce qui nest pas le cas dune architecture client / serveur rseau). Dans les possibilits que nous venons dnoncer, aucune ne permet donc vraiment de rsoudre parfaitement le problme de plusieurs entres / sorties synchrones.

18.2

Lappel systme select()

La fonction select() permet de surveiller plusieurs descripteurs de chier an de mettre en attente le processus tant que ltat dau moins un de ces descripteurs na pas chang. Il est ensuite possible de savoir quels sont les descripteurs dont ltat a t modi. Elle permet donc de grer simultanment plusieurs entres / sorties synchrones. De plus, elle est extrmement pratique puisquelle peut sutiliser avec tout type de descripteurs de chier (socket, tuyaux, entre standard...). On commence par indiquer, au moyen dune variable de type fd_set, quels descripteurs nous intressent :
fd_set rset ; FD_ZERO ( &rset ) ; FD_SET ( fileno_client01 , &rset ) ; FD_SET ( fileno_client02 , &rset ) ; ...

La fonction FD_ZERO() met la variable rset zro, puis on indique lensemble des descripteurs de chier surveiller avec la fonction FD_SET(). On appelle ensuite la fonction select() qui attendra que des donnes soient disponibles sur lun de ces descripteurs :
if ( select ( fileno_max + 1 , &rset , NULL , NULL , NULL ) == -1 ) { /* erreur */ }

La fonction select() prend cinq arguments : 1. le plus grand numro de descripteur de chier surveiller plus un. Lorsque les descripteurs sont ajouts dans le tableau rset, il est donc ncessaire, en plus, de garder le descripteur de chier le plus lev ; 2. un pointeur vers une variable de type fd_set reprsentant la liste des descripteurs sur lesquels on veut lire, il est possible dutiliser un pointeur nul lorsque cette possibilit nest pas utilise ; 3. un pointeur vers une variable de type fd_set reprsentant la liste des descripteurs sur lesquels on veut crire, il est possible dutiliser un pointeur nul lorsque cette possibilit nest pas utilise ; 382

18.3. Exercices 4. un pointeur vers une variable de type fd_set reprsentant la liste des descripteurs sur lesquels peuvent arriver des conditions exceptionnelles, il est possible dutiliser un pointeur nul lorsque cette possibilit nest pas utilise ; 5. un pointeur vers une structure de type timeval reprsentant une dure aprs laquelle la fonction select() doit rendre la main si aucun descripteur nest disponible. Dans ce cas, la valeur retourne par select() est 0, il est possible dutiliser un pointeur nul lorsque cette possibilit nest pas utilise. La fonction select() renvoie -1 en cas derreur, 0 au bout du temps spci par le cinquime paramtre et le nombre de descripteurs prts sinon. La fonction FD_ISSET permet de dterminer si un descripteur est prt :
if ( FD_ISSET ( fileno_client01 , &rset ) ) { /* lire sur le descripteur de fichier correspondant au client 01 */ } if ( FD_ISSET ( fileno_client02 , &rset ) ) { /* lire sur le descripteur de fichier correspondant au client 02 */ }

Enn, il est important de remarquer que les trois ensembles de descripteurs de chier passs la fonction select() sont modis par celle-ci. En effet, lorsque la fonction rend la main, seul les descripteurs dont ltat a chang sont conservs dans les ensembles passs en paramtre. Cest la raison pour laquelle il est ncessaire de reconstruire ces ensembles avant chaque appel. Plus dinformations sont disponibles concernant la fonction select() dans les pages man de select et select_tut.

18.3

Exercices

Question 1 crire un programme crant NB_FILS processus ls (NB_FILS tant une constante dnie au moyen du prprocesseur C). Chaque processus ls sera reli au processus pre au moyen dun tuyau, dirig du ls vers le pre. Les processus ls effectueront tous la mme action, contenue dans une fonction spare. Pour ce premier exercice, ils se contenteront dcrire en boucle innie leur PID dans le tuyau, en sparant les critures dun nombre de secondes choisi au hasard entre 2 et 10 ( cet effet, vous utiliserez la fonction random(). Le processus pre effectuera une attente passive sur lensemble des tuyaux au moyen de la fonction select() et afchera les informations lues ds quelles seront disponibles. Question 2 Reprendre le programme prcdent en modiant quelque peu le comportement des processus ls. Ils seront dsormais relis au processus pre au moyen de deux tuyaux, un dans chaque sens. Le processus pre enverra par ses tuyaux descendants une chane 383

Chapitre 18. Surveillances des entres / sorties sous Unix de caractres chaque processus ls. Chacun deux attendra un nombre de secondes compris entre 2 et 10 avant de renvoyer cette chane au processus pre au moyen du tuyau remontant. Ds rception dune chane, le processus pre lafchera, la renverra au mme ls et ainsi de suite. Question 3 Reprendre le programme prcdent en plaant le code des processus ls dans un excutable spar qui sera lanc par exec(). la fonction dup() permettra aux processus ls de relier les tuyaux leurs entre et sortie standard.

384

18.4. Corrigs

18.4

Corrigs
Listing 18.1 Corrig du premier exercice

#include <sys/types.h> #include #include #include #include <stdio.h> <stdlib.h> <time.h> <unistd.h>

#define LECTURE 0 #define ECRITURE 1 #define NB_FILS 5 struct identification { pid_t pid ; FILE *tuyau ; } ; struct identification enfant[NB_FILS] ; void cree_fils ( ) ; void fils ( FILE *tuyau ) ; int main ( int argc , char **argv ) { int i , plus_grand = 0 ; cree_fils ( ) ; for (i = 0;i< NB_FILS;i++) { if (fileno(enfant[i].tuyau) > plus_grand) { plus_grand = fileno(enfant[i].tuyau) ; } } for (;;) { fd_set rset ; FD_ZERO ( &rset ) ; for (i = 0;i < NB_FILS;i++) { FD_SET (fileno(enfant[i].tuyau),&rset); } if (select(plus_grand+1,&rset,NULL,NULL,NULL) == -1) { perror ( "Erreur dans select()" ); exit ( EXIT_FAILURE ); } for (i = 0;i < NB_FILS;i++) { if (FD_ISSET(fileno(enfant[i].tuyau),&rset)) { char ligne[100] ; if (fgets(ligne,sizeof(ligne),enfant[i].tuyau) == NULL) { perror("Erreur dans fgets()"); exit(EXIT_FAILURE); } printf("Recu du fils %d : %s",enfant[i].pid,ligne); } }

385

Chapitre 18. Surveillances des entres / sorties sous Unix

} } void cree_fils() { int i ; for(i = 0;i < NB_FILS;i++) { int tuyau[2] ; FILE *ecriture ; if (pipe(tuyau) == -1) { perror("Erreur dans pipe()"); exit(EXIT_FAILURE); } switch (enfant[i].pid = fork()) { case -1 : /* erreur */ perror("Erreur dans fork()"); exit(EXIT_FAILURE); case 0 : /* processus fils, ecrivain */ close (tuyau[LECTURE]) ; ecriture = fdopen(tuyau[ECRITURE],"w"); if (ecriture == NULL) { perror("Erreur dans fdopen()"); exit(EXIT_FAILURE); } fils (ecriture); break ; default : /* processus pere, lecteur */ printf ("Creation du fils %d\n",enfant[i].pid); close(tuyau[ECRITURE]); enfant[i].tuyau = fdopen(tuyau[LECTURE],"r"); if (enfant[i].tuyau == NULL) { perror("Erreur dans fdopen()"); exit(EXIT_FAILURE); } } } }

void fils ( FILE *tuyau ) { pid_t pid = getpid(); srandom (time(NULL)^pid); for (;;) { sleep(2+random()%9); printf("[fils %d]\n",pid); fprintf(tuyau,"%d\n",pid); fflush(tuyau); } }

Listing 18.2 Corrig du deuxime exercice


#include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <time.h>

386

18.4. Corrigs

#include <unistd.h> #define LECTURE 0 #define ECRITURE 1 #define NB_FILS 5 struct identification { pid_t pid ; FILE *lecture ; FILE *ecriture ; } ; struct identification enfant[NB_FILS] ; void cree_fils ( ) ; void fils ( FILE *lecture , FILE *ecriture ) ;

int main ( int argc , char **argv ) { int i , plus_grand = 0 ; cree_fils(); for ( i = 0;i < NB_FILS;i++) { fprintf(enfant[i].ecriture,"message au fils %d\n",enfant[i].pid); fflush(enfant[i].ecriture); } for (i = 0;i < NB_FILS;i++) { if (fileno(enfant[i].lecture) > plus_grand) { plus_grand = fileno(enfant[i].lecture); } } for (;;) { fd_set rset ; FD_ZERO(&rset); for (i = 0;i < NB_FILS;i++) { FD_SET(fileno(enfant[i].lecture),&rset); } if (select(plus_grand+1,&rset,NULL,NULL,NULL) == -1) { perror("Erreur dans select()"); exit(EXIT_FAILURE); } for (i = 0;i < NB_FILS;i++) { if (FD_ISSET(fileno(enfant[i].lecture),&rset)) { char ligne[100] ; if (fgets(ligne,sizeof(ligne),enfant[i].lecture) == NULL) { perror("Erreur dans fgets()"); exit(EXIT_FAILURE); } printf("Recu du fils %d : %s",enfant[i].pid,ligne); fprintf(enfant[i].ecriture,"%s",ligne); fflush(enfant[i].ecriture); } }

387

Chapitre 18. Surveillances des entres / sorties sous Unix

} } void cree_fils() { int i ; for (i = 0;i < NB_FILS;i++) { int tuyau1[2] , tuyau2[2] ; FILE *lecture , *ecriture ; if (pipe(tuyau1) == -1) { perror("Erreur dans pipe()"); exit(EXIT_FAILURE); } if (pipe(tuyau2) == -1) { perror("Erreur dans pipe()"); exit(EXIT_FAILURE); } switch (enfant[i].pid = fork()) { case -1 : /* erreur */ perror("Erreur dans fork()"); exit(EXIT_FAILURE); case 0 : /* processus fils */ close(tuyau1[LECTURE]); ecriture = fdopen(tuyau1[ECRITURE],"w"); if (ecriture == NULL) { perror("Erreur dans fdopen()"); exit(EXIT_FAILURE); } close(tuyau2[ECRITURE]); lecture = fdopen(tuyau2[LECTURE],"r"); if (lecture == NULL) { perror("Erreur dans fdopen()"); exit(EXIT_FAILURE); } fils(lecture,ecriture); break; default : /* processus pere */ printf("Creation du fils %d\n",enfant[i].pid); close(tuyau1[ECRITURE]); enfant[i].lecture = fdopen(tuyau1[LECTURE],"r"); if (enfant[i].lecture == NULL) { perror("Erreur dans fdopen()"); exit(EXIT_FAILURE); } close(tuyau2[LECTURE]); enfant[i].ecriture = fdopen(tuyau2[ECRITURE],"w"); if (enfant[i].ecriture == NULL) { perror("Erreur dans fdopen()"); exit(EXIT_FAILURE); } } } }

388

18.4. Corrigs

void fils ( FILE *lecture , FILE *ecriture ) { pid_t pid = getpid ( ) ; srandom ( time ( NULL ) ^ pid ) ; for ( ; ; ) { char ligne[100] ; if ( fgets ( ligne , sizeof ( ligne ) , lecture ) == NULL ) { perror ( "Erreur dans fgets()" ) ; exit ( EXIT_FAILURE ) ; } printf ( "[fils %d] lu du pere : %s" , pid , ligne ) ; sleep ( 2 + random ( ) % 9 ) ; printf ( "[fils %d] envoi au pere : %s" , pid , ligne ) ; fprintf ( ecriture , "%s" , ligne ) ; fflush ( ecriture ) ; } }

Listing 18.3 Corrig du troisime exercice


#include <sys/types.h> #include #include #include #include <stdio.h> <stdlib.h> <time.h> <unistd.h>

#define LECTURE 0 #define ECRITURE 1 #define NB_FILS 5 struct identification { pid_t pid ; FILE *lecture ; FILE *ecriture ; } ; struct identification enfant[NB_FILS] ; void cree_fils ( ) ; int main(int argc,char **argv) { int i , plus_grand = 0 ; cree_fils(); for (i = 0;i < NB_FILS;i++) { fprintf(enfant[i].ecriture,"message au fils %d\n",enfant[i].pid); fflush(enfant[i].ecriture); } for (i = 0;i < NB_FILS;i++) {

389

Chapitre 18. Surveillances des entres / sorties sous Unix


if (fileno(enfant[i].lecture) > plus_grand) { plus_grand = fileno(enfant[i].lecture); } } for (;;) { fd_set rset ; FD_ZERO(&rset); for (i = 0;i < NB_FILS;i++) { FD_SET(fileno(enfant[i].lecture),&rset); } if (select(plus_grand+1,&rset,NULL,NULL,NULL) == -1) { perror("Erreur dans select()"); exit(EXIT_FAILURE); } for (i = 0;i < NB_FILS;i++) { if (FD_ISSET(fileno(enfant[i].lecture),&rset)) { char ligne[100] ; if (fgets(ligne,sizeof(ligne),enfant[i].lecture) == NULL) { perror("Erreur dans fgets()"); exit(EXIT_FAILURE); } printf("Recu du fils %d : %s",enfant[i].pid,ligne); fprintf(enfant[i].ecriture,"%s",ligne); fflush(enfant[i].ecriture); } } } } void cree_fils ( ) { int i ; for (i = 0;i < NB_FILS;i++) { int tuyau1[2] , tuyau2[2] ; if (pipe(tuyau1) == -1) { perror("Erreur dans pipe()"); exit(EXIT_FAILURE); } if (pipe(tuyau2) == -1) { perror("Erreur dans pipe()"); exit(EXIT_FAILURE); } switch (enfant[i].pid = fork()) { case -1 : /* erreur */ perror("Erreur dans fork()"); exit(EXIT_FAILURE); case 0 : /* processus fils */ close(tuyau1[LECTURE]); if (dup2(tuyau1[ECRITURE],STDOUT_FILENO) == -1) { perror("Erreur dans dup2()"); exit(EXIT_FAILURE); }

390

18.4. Corrigs

close(tuyau2[ECRITURE]); if (dup2(tuyau2[LECTURE],STDIN_FILENO) == -1) { perror("Erreur dans dup2()"); exit(EXIT_FAILURE); } if (execl("select03-fils","select03-fils",NULL) == -1 ) { perror("Erreur dans exec()"); exit(EXIT_FAILURE); } default : /* processus pere */ printf("Creation du fils %d\n",enfant[i].pid); close(tuyau1[ECRITURE]); enfant[i].lecture = fdopen(tuyau1[LECTURE],"r"); if (enfant[i].lecture == NULL) { perror("Erreur dans fdopen()"); exit(EXIT_FAILURE); } close(tuyau2[LECTURE]); enfant[i].ecriture = fdopen(tuyau2[ECRITURE],"w"); if (enfant[i].ecriture == NULL ) { perror("Erreur dans fdopen()"); exit(EXIT_FAILURE); } } } }

Listing 18.4 Une autre version du troisime exercice


#include <sys/types.h> #include #include #include #include <stdio.h> <stdlib.h> <time.h> <unistd.h>

int main(int argc,char **argv) { pid_t pid = getpid(); srandom (time(NULL)^pid); for (;;) { char ligne[100] ; if (fgets(ligne,sizeof(ligne),stdin) == NULL) { perror("Erreur dans fgets()"); exit(EXIT_FAILURE); } sleep(2+random()%9); printf("%s",ligne); fflush(stdout); } }

391

19
Utilisation dun dbogueur

19.1

Introduction

Le dveloppement en informatique fonctionne principalement par tentatives / checs. Ainsi, le cycle usuel ralis par un dveloppeur pour concevoir un programme est compos des tapes suivantes : 1. dveloppement dune nouvelle fonctionnalit ; 2. test de cette fonctionnalit : si les tests sont valids alors retour ltape 1 ; sinon correction des erreurs puis retour ltape 2. La correction derreurs dans un logiciel fait donc gnralement partie intgrante de son cycle de dveloppement et de maintenance. Une erreur en informatique est appele bug, mot anglais faisant rfrence aux insectes qui se logeaient entre les contacteurs des tous premiers ordinateurs lampes et craient ainsi des dysfonctionnements 1 . Les bugs (ou bogues, selon la terminologie ofcielle franaise) tant quasiment invitables en informatique, la qualit dun dveloppeur se manifeste certes par la qualit du code crit mais aussi par sa capacit dtecter et corriger des bugs. Dans ce but, il existe des outils, appels dbogueurs (debugger), facilitant la tche. Un dbogueur est une application permettant de contrler lexcution dun programme. Ce chapitre indique comment utiliser concrtement un dbogueur et il est fortement conseill de prendre le temps de lire les quelques pages qui suivent : la matrise dun dbogueur permet de faire gagner beaucoup de temps dans la mise au point des programmes ! Rappelons nanmoins que lutilisation dun dbogueur est ltape ultime pour tester du code qui ne fonctionne pas et que cet outil ne dispense pas dcrire du code propre, par petits ajouts, test rgulirement. Le dbogueur aide le bon programmeur, mais ne le remplace pas !
1. Cette origine est parfois conteste et des rfrences au mot bug auraient t trouves bien avant cette poque, dans le milieu de ldition et de limpression. On peut aisment imaginer que lide est la mme : un insecte qui vient se glisser sous la presse Gutenberg au moment o celle-ci est sur le point dimprgner dencre une feuille de papier.

393

Chapitre 19. Utilisation dun dbogueur

19.2

gdb, xxgdb, ddd et les autres

Lutilisateur du dbogueur peut, par exemple : interrompre un programme en cours dexcution et regarder ltat courant des variables de ce programme ; excuter pas pas tout ou partie du programme (instructions ou fonctions) ; surveiller le contenu de la mmoire ou des registres du microprocesseur ; valuer des expressions de ce programme. Par la suite, nous dtaillerons lutilisation dun dbogueur appel gdb 2 dvelopp et maintenu par la communaut GNU 3 . gdb est un logiciel libre disponible sous Unix et Windows. gdb sutilise en ligne de commande , sans interface graphique, et cest cette utilisation que nous allons dtailler dans les sections suivantes : lutilisation en ligne de commande permet de bien comprendre le fonctionnement dun dbogueur et les apports de celui-ci dans la mise au point de code. Il existe nanmoins diffrents surcouches graphiques pour gdb, comme par exemple xxgdb ou ddd, et un utilisateur press pourra utiliser ces verions plus accessibles de prime abord. Il faut cependant retenir que ce ne sont que des surcouches, qui se contentent dinterprter des actions la souris (clic sur un bouton, slection dune zone, etc.) pour les envoyer gdb Il existe dautres dbogueurs, commerciaux ou non, dont certains sont intgrs dans des environnement complet de programmation (IDE ou integrated development environment). Lexprience montre que les dveloppeurs utilisant des IDE ont tendance mconnatre le fonctionnement rel des compilateurs, dbogueurs et autres outils de dveloppement, pour se reposer exclusivement sur leur unique IDE. Ce constat est particulirement agrant pour les dveloppeurs ayant appris programmer directement sous un IDE et, par voie de consquence, les auteurs du prsent document conseillent vivement lapprentissage indpendant des diffrents outils de dveloppement. Cest pourquoi le fonctionnement et lutilisation de gcc, gdb, make, etc. sont prsents sparment dans ce document, alors quil aurait t possible de proposer une vision intgre, par exemple au travers de lIDE eclipse (voir www.eclipse.org).

19.3

Utilisation de gdb

An dillustrer le fonctionnement de gdb, lexemple suivant sera utilis : Listing 19.1 Le programme de test
#include <stdio.h> #include <stdlib.h> int somme(int a, int b) { return a + b; }

2. http://www.gnu.org/software/gdb/gdb.html 3. http://www.gnu.org

394

19.3. Utilisation de gdb

int main(int argc, char *argv[]) { int i, s; s=0; for (i=1; i<argc; i++) { s = somme(s,atoi(argv[i])); } printf("la somme est: %d\n", s); exit(EXIT_SUCCESS); }

Ce programme calcule la somme des arguments passs lors du lancement du programme.


Compilation
gdb est capable de dboguer nimporte quelle application, mme si celle-ci na pas t prvu pour. Cependant, pour que les informations afches par gdb soit comprhensibles et puissent se rattacher aux chiers sources qui ont t compils, il est ncessaire de spcier au compilateur de gnrer des informations de dbogage dans les chiers objets produits. Lors de lexcution du programme, le dbogueur peut ainsi connatre le nom des variables, la ligne correspondante de linstruction en cours dans le chier source, ... Avec gcc, les informations de dbogage sajoutent avec loption -g, par exemple :
localhost> gcc -g -Wall ./gdb1.c -o ./gdb1

gcc permet de gnrer des informations de dbogage spciques gdb. Pour cela, il faut utiliser loption -ggdb plutt que -g, par exemple :
localhost> gcc -ggdb -Wall ./gdb1.c -o ./gdb1

videmment, plus le compilateur ajoute des informations de dbogage aux chiers compils, plus les chiers ainsi gnrs sont grands. An denlever des informations de dbogage un chier excutable (bibliothque ou application), la commande strip peut tre utilise. Attention, gcc est lun des rares compilateurs permettant dajouter des informations de dbogage en gnrant du code optimis (option -O). Il est dconseill de dboguer du code optimis par le compilateur. En effet, pendant la phase doptimisation, le compilateur a pu supprimer des variables, remplacer des instructions, rendant le code incohrent avec le source qui la produit.
Excution

Pour lancer gdb, il suft de taper la commande : 395

Chapitre 19. Utilisation dun dbogueur

localhost> gdb Current directory is /home/degris/Poly/TP/Debugger/ GNU gdb 6.2-2mdk (Mandrakelinux) Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i586-mandrake-linux-gnu". (gdb)

Un message indiquant le rpertoire courant et la version courante de gdb safche alors, indiquant que gdb est prt tre utilis. Astuce : il est possible de lancer gdb directement depuis Emacs en faisant M-X gdb. gdb se manipule alors exactement de la mme faon que depuis la console. Lutilisation du dbogueur depuis lditeur permet notamment un accs facilit certaines commandes du dbogueur par lintermdiaire des menus droulants de lditeur. Ces commandes restent naturellement accessibles directement depuis la ligne de commandes ! Avant de commencer lexcution dun programme, il est ncessaire de charger celui-ci depuis gdb. Ceci est ralis par la commande file, par exemple pour charger le programme gdb1 :
(gdb) cd /local/tmp/degris/gdb Working directory /local/tmp/degris/gdb. (gdb) file gdb1 Reading symbols from /local/tmp/degris/gdb/gdb1...done. Using host libthread_db library "/lib/tls/libthread_db.so.1".

Attention, si les informations de dbogage nont pas t gnres, gdb nafche pas systmatiquement un message dalerte. Une fois le programme charg, il peut tre excut par la commande run avec la liste des arguments passer aux programmes, par exemple :
(gdb) run 1 2 Starting program: /local/tmp/degris/gdb/gdb1 1 2 la somme est: 3 Program exited normally.

gdb lance alors lexcution du programme qui se termine normalement si aucun

vnement (par exemple un signal en provenance du systme) nest survenu. Une fois lexcution termine, gdb afche un message le conrmant. Si une valeur autre que 0 est passe la fonction exit() lors de la n de lexcution du programme, alors gdb afche cette valeur de retour.
Contrle de lexcution

Souvent, il est ncessaire dinterrompre lexcution dun programme an de pouvoir suivre pas pas son droulement. Ceci est ralis avec les points darrt. Par exemple, 396

19.3. Utilisation de gdb il est possible dassocier un point darrt la fonction somme. Dans ce cas, gdb interrompra lexcution du programme chaque appel de la fonction :
(gdb) break somme Breakpoint 1 at 0x80483df: file /home/degris/Poly/Src/gdb1.c, line 5. (gdb) run 1 2 Starting program: /local/tmp/degris/gdb/gdb1 1 2 Breakpoint 1, somme (a=0, b=1) at /home/degris/Poly/Src/gdb1.c:5 5 return a + b; (gdb)

gdb nous signale alors quil a suspendu lexcution du programme linstruction correspondant la ligne 5 dans le chier source gdb1.c. De plus, il nous signale que la fonction somme a t appele avec pour valeurs a=0 et b=1. Enn, gdb afche la ligne

correspondant la prochaine instruction excuter. Pour les (heureux) utilisateurs de gdb depuis Emacs, cette ligne est remplace par un pointeur afch directement dans une fentre afchant le code en cours de dbogage, rendant lutilisation de gdb beaucoup plus intuitive. Si lon continue lexcution du programme avec la commande cont, gdb arrtera lexcution lors du deuxime appel de la fonction somme :
(gdb) cont Continuing. Breakpoint 1, somme (a=1, b=2) at /home/degris/Poly/Src/gdb1.c:5 5 return a + b;

Il est possible denlever ce point darrt avec la commande delete en passant en argument le numro associ au point darrt. Dautre part, avec la mme commande break, il est possible de dnir un point darrt avec le nom du chier source et un numro de ligne :
(gdb) delete 1 (gdb) break gdb1.c:15 Breakpoint 2 at 0x804844a: file /home/degris/Poly/Src/gdb1.c, line 15. (gdb) cont Continuing. Breakpoint 2, main (argc=3, argv=0xbfffee54) at /home/degris/Poly/Src/gdb1.c:15 15 printf("la somme est: %d\n", s);

Lexcution du programme est alors suspendue linstruction correspondant la ligne 15 du chier gdb1.c. Lorsque lexcution est suspendue, il est parfois utile dexcuter la suite du programme pas pas. La commande next permet de poursuivre lexcution ligne par ligne. La commande step poursuit lexcution ligne par ligne mais en entrant dans les fonctions si une fonction est appele, par exemple :
Breakpoint 3, main (argc=3, argv=0xbfffee54) at /home/degris/Poly/Src/gdb1.c:12 12 for (i=1; i<argc; i++) { (gdb) next 13 s = somme(s,atoi(argv[i]));

397

Chapitre 19. Utilisation dun dbogueur

(gdb) 12 (gdb) 13 (gdb) somme 5 (gdb)

next for (i=1; i<argc; i++) { step s = somme(s,atoi(argv[i])); step (a=1, b=2) at /home/degris/Poly/Src/gdb1.c:5 return a + b;

De plus, lorsque le programme est suspendu, il est possible de changer ladresse du pointeur dinstruction avec la commande jump. En effet, cette fonction permet de spcier la prochaine commande excuter. Elle prend en argument une adresse ou un numro de ligne correspondant un chier source. Par exemple, si le programme est suspendu juste avant de se terminer :
(gdb) break gdb1.c:16 Breakpoint 1 at 0x804845d: file /home/degris/Poly/Src/gdb1.c, line 16. (gdb) run 2 3 4 Starting program: /local/tmp/degris/gdb/gdb1 2 3 4 la somme est: 9 Breakpoint 1, main (argc=4, argv=0xbfffee54) at /home/degris/Poly/Src/gdb1.c:16 16 exit(EXIT_SUCCESS);

La somme vaut actuellement 9. Si lon recommence lexcution de la boucle for :


(gdb) jump gdb1.c:12 Continuing at 0x804840a. la somme est: 18 Breakpoint 1, main (argc=4, argv=0xbfffee54) at /home/degris/Poly/Src/gdb1.c:16 16 exit(EXIT_SUCCESS);

La somme vaut maintenant 18 puisque la boucle a t excute deux fois.


valuation dexpressions

La commande print permet dvaluer une expression spcie en argument dans le langage dbogu (dans notre cas, la langage C). Lvaluation dune expression peut tre notamment utilise pour connatre le contenu dune variable. Dans ce cas, seules les variables locales au contexte courant peuvent tre utilises dans une expression. Par exemple, la variable s est locale au contexte dappels de la fonction main. Elle nest donc pas accessible directement si lexcution est suspendue dans la fonction somme. Cependant, les commandes up et down permettent de changer le contexte courant dans gdb et la commande bt permet dafcher la pile dappels. Par exemple, si le programme est suspendu dans la fonction somme :
#0 5 somme (a=1, b=2) at /home/degris/Poly/Src/gdb1.c:5 return a + b;

Afchage de la pile dappels : 398

19.3. Utilisation de gdb

(gdb) bt #0 somme (a=1, b=2) at /home/degris/Poly/Src/gdb1.c:5 #1 0x0804843d in main (argc=3, argv=0xbfffee54) at /home/degris/Poly/Src/gdb1.c:13

gdb afche une erreur concernant lafchage de la valeur de s puisque cette variable nest pas visible depuis le contexte de la fonction somme :
(gdb) print s No symbol "s" in current context.

On se dplace donc dans la pile dappels pour trouver un contexte o la variable s est visible (fonction main). Une fois le contexte correct, la valeur de la variable est afche :
(gdb) up #1 0x0804843d in main (argc=3, argv=0xbfffee54) at /home/degris/Poly/Src/gdb1.c:13 13 s = somme(s,atoi(argv[i])); (gdb) print s $1 = 1

Cependant, les variables qui taient visibles dans le contexte de la fonction somme (par exemple, largument a) ne le sont plus dans ce contexte. Il faut donc se dplacer dans la pile dappels une nouvelle fois pour accder au contexte de la fonction somme o largument a est visible :
(gdb) print a No symbol "a" in current context. (gdb) down #0 somme (a=1, b=2) at /home/degris/Poly/Src/gdb1.c:5 5 return a + b; (gdb) print a $2 = 1

Lorsque lexcution est suspendue, il est possible dvaluer des expressions faisant appel des fonctions avec la commande print, par exemple :
(gdb) print somme(34,56) $3 = 90 (gdb) print getpid() $4 = 6018

Une expression peut aussi tre utilise pour modier le contenu dune variable pendant lexcution :
(gdb) print s $2 = 18 (gdb) print s=4 $3 = 4 (gdb) print s $4 = 4

399

Chapitre 19. Utilisation dun dbogueur La commande display permet dvaluer une expression chaque pas dexcution, par exemple :
(gdb) break gdb1.c:13 Breakpoint 1 at 0x8048419: file /home/degris/Poly/Src/gdb1.c, line 13. (gdb) run 1 2 3 4 5 Starting program: /local/tmp/degris/gdb/gdb1 1 2 3 4 5 Breakpoint 1, main (argc=6, argv=0xbfffee44) at /home/degris/Poly/Src/gdb1.c:13 13 s = somme(s,atoi(argv[i])); (gdb) display s 1: s = 0 (gdb) cont Continuing. Breakpoint 1, main (argc=6, argv=0xbfffee44) at /home/degris/Poly/Src/gdb1.c:13 13 s = somme(s,atoi(argv[i])); 1: s = 1 (gdb) cont Continuing. Breakpoint 1, main (argc=6, argv=0xbfffee44) at /home/degris/Poly/Src/gdb1.c:13 13 s = somme(s,atoi(argv[i])); 1: s = 3 (gdb)

Gestion des signaux

Lorsquun signal est envoy par le systme lapplication, celui-ci est intercept par
gdb. Le comportement de gdb est alors dni par trois paramtres boolens lorsquun

signal est reu : lapplication est suspendue ou non ; gdb crit un message lors de la rception ou non ; le signal est pass lapplication ou non. Il est possible de connatre ltat courant de ces paramtres avec la commande info signals :
(gdb) info signals Signal Stop SIGHUP SIGINT SIGQUIT SIGILL SIGTRAP SIGABRT SIGEMT SIGSEGV ... Yes Yes Yes Yes Yes Yes Yes Yes Print Yes Yes Yes Yes Yes Yes Yes Yes Pass to program Description Yes No Yes Yes No Yes Yes Yes Hangup Interrupt Quit Illegal instruction Trace/breakpoint trap Aborted Emulation trap Segmentation fault

Par exemple, on peut constater que le signal SIGINT (le signal envoy lorsque ctrl-c est utilis) nest pas envoy au programme. En effet, il est utilis par gdb pour suspendre lexcution du programme en cours. 400

19.3. Utilisation de gdb Concernant le signal derreur de segmentation (SIGSEGV), on peut constater que gdb suspend le programme, afche la rception du signal et envoie le signal lapplication. Ceci est trs utile pour dboguer une application recevant un tel signal puisque gdb suspend lapplication lors de la rception du signal. Le programme suivant (gdb2.c) est utilis pour illustrer comment gdb peut faciliter la recherche des causes dune erreur de segmentation (par exemple) : Listing 19.2 Recherche dune violation mmoire
#include <stdlib.h> #include <stdio.h> int main(int argc, char *argv[]) { char* s = NULL; // Erreur de segmentation puisque s pointe sur 0 sprintf(s, " "); exit(EXIT_SUCCESS); }

Lorsque le programme est excut, il est interrompu par gdb lors de la rception du signal :
(gdb) file gdb2 Reading symbols from /local/tmp/degris/gdb/gdb2...done. Using host libthread_db library "/lib/tls/libthread_db.so.1". (gdb) run Starting program: /local/tmp/degris/gdb/gdb2 Program received signal SIGSEGV, Segmentation fault. 0x4008d025 in _IO_str_overflow () from /lib/tls/libc.so.6

Le signal a t reu lors de lappel dans une bibliothque. An de pouvoir accder au contenu des variables de notre application, il est ncessaire de remonter la pile dappels jusquau contexte qui nous intresse :
(gdb) bt #0 0x4008d025 in _IO_str_overflow () from /lib/tls/libc.so.6 #1 0x4008bcdf in _IO_default_xsputn () from /lib/tls/libc.so.6 #2 0x40066b17 in vfprintf () from /lib/tls/libc.so.6 #3 0x40081afb in vsprintf () from /lib/tls/libc.so.6 #4 0x4006f5db in sprintf () from /lib/tls/libc.so.6 #5 0x080483df in main (argc=1, argv=0xbfffee64) at /home/degris/enseignement/ensta2007/Poly/Src/gdb2.c:7 (gdb) up 5 #5 0x080483df in main (argc=1, argv=0xbfffee64) at /home/degris/enseignement/ensta2007/Poly/Src/gdb2.c:7 7 sprintf(s, " ");

Maintenant, le pointeur s est devenu visible, on peut vrier sa valeur, cause de lerreur de segmentation :
(gdb) print s $1 = 0x0

401

Chapitre 19. Utilisation dun dbogueur


Gestion de plusieurs processus

Lorsque le programme dbogu cre un processus ls via la fonction fork, le processus ls est alors excut sans dbogueur. Si lutilisateur a paramtr des points darrt dans le code du processus ls, ceux-ci ne seront donc pas pris en compte. Lexemple extp2_2.c du chapitre 10 sur les processus est utilis titre dillustration : Listing 19.3 Gestion du pre et du ls
#include <unistd.h> #include <stdio.h> #include <stdlib.h> int main(int argc, char **argv) { int i; printf("[processus %d] je suis avant le fork\n", getpid()); i = fork(); printf("[processus %d] je suis apres le fork, il a retourne %d\n", getpid(), i); exit(EXIT_SUCCESS); }

Lexcutable correspondant lexemple est charg dans gdb. Un point darrt est dni sur la ligne 11 (cette ligne est excute par le processus pre et le processus ls), puis le programme est dmarr :
(gdb) break extp2_2.c:11 Breakpoint 1 at 0x804844c: file /home/degris/Poly/Src/extp2_2.c, line 11. (gdb) run Starting program: /local/tmp/degris/gdb/extp2_2 [processus 27343] je suis avant le fork Detaching after fork from child process 27346. [processus 27346] je suis apres le fork, il a retourne 0 Breakpoint 1, main (argc=1, argv=0xbfffee54) at /home/degris/enseignement/ensta2007/Poly/Src/extp2_2.c:11 11 printf("[processus %d] je suis apres le fork, il a retourne %d\n", getpid(), i); (gdb) print getpid() $1 = 27343

On peut remarquer que seul le processus pre a t suspendu et que le processus ls a termin son excution. An de dboguer le processus ls, la commande attach peut tre utilise. Elle permet dattacher gdb nimporte quel processus en cours dexcution 4 . Le PID du processus auquel gdb doit sattacher est pass en paramtre de la commande. De plus, la commande attach provoque la n de lexcution du programme en cours de dbogage. Par consquent, si le processus pre est dj excut dans un premier dbogueur, il est ncessaire den utiliser un second. Par exemple, dans le corrig tuyau1.c du chapitre 15, le processus ls ne se termine pas immdiatement puisquil attend une entre sur lentre standard. Il est
4. condition davoir des droits daccs sufsants sur ce processus.

402

19.3. Utilisation de gdb alors possible dattacher gdb sur le processus ls. Par exemple, si le programme est lanc depuis un premier dbogueur :
(gdb) run Starting program: /local/tmp/degris/gdb/tuyau1 Detaching after fork from child process 28806. Je suis le fils, tapez des phrases svp

Malgr le message afch, gdb est attach au processus pre. An de dboguer le processus ls, on utilise un deuxime dbogueur que lon attache au processus ls :
localhost> gdb GNU gdb 6.2-2mdk (Mandrakelinux) (gdb) attach 28806 Attaching to process 28806 Reading symbols from /local/tmp/degris/gdb/tuyau1...done. Using host libthread_db library "/lib/tls/libthread_db.so.1". Reading symbols from /lib/tls/libc.so.6...done. Loaded symbols for /lib/tls/libc.so.6 Reading symbols from /lib/ld-linux.so.2...done. Loaded symbols for /lib/ld-linux.so.2 0xffffe410 in ?? () (gdb) print getpid() $1 = 28806

Cette mthode est gnrale pour dboguer un processus en cours dexcution.


Utilisation dun chier core

Lorsquun programme est interrompu par une erreur de segmentation, il est possible de gnrer un chier core. Ce chier est utilis pour enregistrer ltat courant du programme lorsque celui-ci effectue lerreur de segmentation. An dactiver cette fonctionnalit, la commande ulimit du shell est utilis. Sous bash, ulimit -c unlimited active la gnration dun chier core, ulimit -c 0 la dsactive. Une fois la fonctionnalit active, un message safche lors de lerreur de segmentation, indiquant lcriture du chier core. Par exemple, si on lance le programme compil partir de lexemple gdb2.c depuis une invite de commande :
localhost> ./gdb2 Segmentation fault (core dumped)

Le chier gnr sappelle en gnral core ou core.<pidduprocessus> et se situe dans le rpertoire courant du programme provoquant lerreur 5 . Sous NetBSD ce chier est gnralement nomm <nomduprogramme>.core. Ce nom est paramtrable laide de la commande sysctl :
localhost#> sysctl -w kern.defcorename = %n.core

5. condition davoir les droits en criture sur ce rpertoire.

403

Chapitre 19. Utilisation dun dbogueur Une fois le chier gnr, il peut tre lu par gdb avec la commande core-file an de consulter ltat des variables lorsque lerreur est survenue. Une fois charg, ltat de la pile dappels et ltat des variables peut tre consult. En reprenant le mme exemple :
localhost> ./gdb $ gdb (gdb) file gdb2 Reading symbols from /local/tmp/degris/gdb/gdb2...done. (gdb) core-file core.24513 Core was generated by ./gdb2. Program terminated with signal 11, Segmentation fault. Reading symbols from /lib/tls/libc.so.6...done. Loaded symbols for /lib/tls/libc.so.6 Reading symbols from /lib/ld-linux.so.2...done. Loaded symbols for /lib/ld-linux.so.2 #0 0x4008d025 in _IO_str_overflow () from /lib/tls/libc.so.6 (gdb) bt #0 0x4008d025 in _IO_str_overflow () from /lib/tls/libc.so.6 #1 0x4008bcdf in _IO_default_xsputn () from /lib/tls/libc.so.6 #2 0x40066b17 in vfprintf () from /lib/tls/libc.so.6 #3 0x40081afb in vsprintf () from /lib/tls/libc.so.6 #4 0x4006f5db in sprintf () from /lib/tls/libc.so.6 #5 0x080483df in main (argc=1, argv=0xbfffee74) at /home/degris/enseignement/ensta2007/Poly/Src/gdb2.c:7 (gdb) up 5 #5 0x080483df in main (argc=1, argv=0xbfffee74) at /home/degris/enseignement/ensta2007/Poly/Src/gdb2.c:7 7 sprintf(s, " "); (gdb) info locals s = 0x0

Enn, comme cest ralis dans lexemple, il est ncessaire de charger le chier excutable avec la commande file avant de charger le chier core. En effet, le chier core ne contient aucune information concernant les symboles de dbogage ncessaire son interprtation, contrairement au chier excutable.
Afchage dinformations sur ltat courant de lapplication et du systme

Il est possible dobtenir dautres informations sur ltat courant du programme avec laide de gdb, en voici quelques exemples : info all-registers : afche la liste de ltat des registres du processeur :
(gdb) info all-registers eax 0xfffffe00 ecx 0x40019000 edx 0x400 1024 ebx 0x0 0 esp 0xbfffec44 ... -512 1073844224

0xbfffec44

info locals : afche ltat des variables locales au contexte courant :


(gdb) info locals tuyau = {7, 8}

404

19.3. Utilisation de gdb


mon_tuyau = (FILE *) 0x804a008 ligne = "\230I..."

ptype : afche la dnition dun type de donne :


(gdb) ptype FILE type = struct _IO_FILE { int _flags; char *_IO_read_ptr; ... int _fileno; ... }

Dune manire gnrale, il est possible dobtenir une liste de lensemble des commandes ainsi que leur descriptif via laide de gdb accessible avec la commande help. De plus, un manuel est disponible ladresse suivante : http://www.gnu.org/ manual/. Enn, plusieurs interfaces graphiques existent pour faciliter lutilisation de gdb. Nous citerons notamment ddd 6 ou Eclipse 7 avec son extension CDT 8 .

6. http://www.gnu.org/software/ddd/ 7. http://www.eclipse.org 8. http://www.eclipse.org/cdt.

405

Troisime partie Projets

20
Projet de carte bleue

20.1

Introduction

Lobjectif du projet propos est de simuler les changes entre banques permettant un particulier de payer ses achats avec sa carte bancaire (sa carte bleue ), mme si celle-ci nest pas mise par la mme banque que celle du vendeur. Avant de prsenter le sujet, examinons le fonctionnement du paiement par carte bancaire.
Le principe du paiement par carte bancaire

Le paiement par carte bancaire met en relation plusieurs acteurs : le client, qui souhaite rgler un achat avec la carte bancaire quil possde et qui lui a t fournie par sa banque (le Crdit Chaton) ; le commerant, qui est quip dun terminal de paiement fourni par sa propre banque (la Bnp) ; la banque du commerant (la Bnp) laquelle vient se connecter le terminal de paiement ; la banque du client (le Crdit Chaton) qui va dire si la transaction est autorise (donc si le compte de son client est sufsamment provisionn ou non). Le terminal du commerant est reli la banque Bnp grce une simple ligne tlphonique. La banque Bnp est connecte toutes les autres banques installes en France, et notamment au Crdit Chaton, grce un rseau ddi : le rseau interbancaire (voir gure 20.1). Supposons maintenant que le client lambda se rend chez son revendeur de logiciels prfr pour acheter la toute dernire version du systme dexploitation FENETRES . Au moment de passer en caisse, il dgaine sa carte bancaire, le caissier linsre dans son terminal de paiement et le client doit, aprs avoir regard au passage la somme quil sapprte dbourser, saisir son code condentiel. Ce code est directement vri par la carte (plus exactement par la puce contenue dans la carte). Si le code est erron, la transaction sarrte l. Si le code est bon, en revanche, les oprations suivantes ont lieu : 409

Chapitre 20. Projet de carte bleue

F IGURE 20.1 Principe de connexion des terminaux et des banques

1. Le terminal se connecte (via le rseau tlphonique) au serveur de la banque Bnp et envoie le numro de la carte bancaire du client ainsi que le montant de la transaction. 2. Le serveur de la banque Bnp regarde le numro de la carte et, se rendant compte quil ne sagit pas dune des cartes quil a mises, envoie le numro de carte avec le montant de la transaction au serveur de la banque Crdit Chaton, via le rseau interbancaire permettant de relier les diffrentes banques. 3. Le serveur de la banque Crdit Chaton prend connaissance du numro de la carte bancaire et vrie que le compte correspondant ce numro dispose dun solde sufsant pour honorer la transaction. 4. Si cest le cas, il rpond la banque Bnp (toujours via le rseau interbancaire) que le paiement est autoris. Si ce nest pas le cas, il rpond le contraire. 5. Enn, le serveur de la banque Bnp transmet la rponse au terminal du commerant. 6. La transaction est valide ( paiement autoris ) ou refuse ( paiement non autoris ).
La demande dautorisation

La suite des oprations dcrites ci-dessus se nomme la demande dautorisation et a essentiellement pour but de vrier que le compte du client est bien provisionn (ou quil a une autorisation de dcouvert). Cette demande dautorisation nest pas 410

20.2. Cahier des charges systmatique et dpend du terminal 1 , lequel prend par exemple en compte le montant de la transaction. La demande dautorisation transite via deux serveurs diffrents : Le serveur dacquisition - Il sagit du serveur de la banque du commerant auquel se connecte le terminal via le rseau tlphonique. Une fois connect, le terminal envoie au serveur dacquisition toutes les informations concernant la transaction, notamment le montant, le numro de la carte et des donnes permettant dassurer la scurit de la transaction. Le serveur dautorisation - Il sagit du serveur de la banque du client auquel le serveur dacquisition transmet lautorisation de paiement mise par le terminal. La rponse la demande suit le chemin inverse, savoir serveur dautorisation de la banque du client serveur dacquisition de la banque du commerant terminal du commerant.
Le routage

Pour effectuer le routage des demandes dautorisation, cest--dire pour dterminer quelle banque chaque demande dautorisation doit tre transmise, le serveur dacquisition utilise les premiers numros de chaque carte bancaire concerne : ceux-ci indiquent la banque ayant mis cette carte. Dans ce projet, nous partirons des principes suivants : un numro de carte est constitu de seize chiffres dcimaux ; les quatre premiers correspondent un code spcique chaque banque ; les serveurs dacquisition des banques sont directement relis au rseau interbancaire. Chaque serveur dacquisition analyse donc le numro de la carte qui gure dans la demande dautorisation quil reoit, puis : si le client est dans la mme banque que le commerant (et que le serveur dacquisition), il envoie la demande directement au serveur dautorisation de cette banque ; si le client est dans une autre banque, le serveur dacquisition envoie la demande sur le rseau interbancaire, sans se proccuper de la suite du transit. Le rseau interbancaire nest donc pas un simple rseau physique : il doit aussi effectuer le routage des demandes dautorisation, cest--dire analyser les demandes qui lui sont fournies, envoyer chaque demande vers le serveur dautorisation de la banque correspondante et, enn, prendre en charge la transmission de la rponse lorsquelle lui revient.

20.2

Cahier des charges

Lobjectif de ce projet est de simuler les mcanismes dcrits ci-dessus, cest--dire :


1. Et bientt aussi des cartes bancaires.

411

Chapitre 20. Projet de carte bleue le terminal envoyant une demande dautorisation au serveur dacquisition de sa banque ; le serveur dacquisition effectuant le routage de la transaction vers le bon serveur dautorisation et effectuant le routage des rponses quil reoit en retour vers les terminaux ; le rseau interbancaire auquel sont connects les diffrents serveurs dacquisition, capable deffectuer le routage des demandes et des rponses relayes par les serveurs dacquisition ; le serveur dautorisation fournissant la rponse la demande dautorisation.
Remarque : cet exercice est avant tout acadmique , mais nest pas dnu dintrt ; en effet, des socits commercialisent toutes sortes de simulateurs pour tester le fonctionnement des nouveaux composants des systmes montiques, an de valider leur fonctionnement avant leur mise en production.

Le cahier des charges fonctionnel prcise larchitecture gnrale, les fonctionnalits devant tre programmes ainsi que les contraintes fonctionnelles respecter. Le cahier des charges technique fournit les restrictions concernant la mise en uvre.
Cahier des charges fonctionnelles
Architecture fonctionnelle

Le schma 20.2 prcise celui qui a t prsent plus haut et retranscrit la description que nous avons fournie ci-dessus.

F IGURE 20.2 Architecture fonctionnelle du projet.

Le terminal est reli via le rseau tlphonique (1) au serveur dacquisition de 412

20.2. Cahier des charges la banque du commerant. Celui-ci est connect au sein de la banque (2) au serveur dautorisation de cette mme banque. Le rseau interbancaire relie (3, 3) les serveurs dacquisition des diffrentes banques. Toutes les autres banques de la place sont galement relies au rseau interbancaire, mais ne sont pas reprsentes sur ce schma.

Le terminal

Dans le cadre de ce projet, il nest pas question dutiliser de vrais terminaux ou de vraies cartes. Le terminal tant le moyen denvoyer aux programmes des demandes dautorisation, il sera simul pour ce projet par une interface utilisateur en mode texte permettant simplement de saisir un numro de carte et un montant de transaction. Chaque terminal devra envoyer les informations saisies au serveur dacquisition, devra attendre la rponse du serveur dacquisition et lafcher lcran (i.e. paiement autoris ou non). Les changes entre le terminal et le serveur dacquisition ont lieu suivant un protocole bien dtermin : les informations sont formates dune certaine faon. Ce sont les constructeurs de terminaux qui imposent leurs protocoles et ce sont les serveurs dacquisition qui doivent sadapter pour parler les protocoles des diffrents terminaux qui lui sont connects. An de simplier le projet et de garantir linteroprabilit des diffrents projets en eux (voir en annexe pour une explication de ce point), un seul protocole de communication sera utilis. Celui-ci est dcrit en annexe de ce document.

Le serveur dacquisition

Le serveur dacquisition na quune fonction de routage : il doit pouvoir accepter des demandes dautorisation provenant de terminaux et du rseau interbancaire ; il doit pouvoir effectuer le routage des demandes dautorisation vers le serveur dautorisation de la banque ou bien vers le rseau interbancaire ; il doit pouvoir accepter les rponses provenant du rseau interbancaire ou du serveur dautorisation de la banque ; il doit pouvoir envoyer les rponses vers le rseau interbancaire ou le terminal (en tant capable dapparier chaque rponse la demande initiale). Le serveur dacquisition doit tre capable dutiliser le protocole de communication employ par les terminaux, ainsi que le protocole du rseau interbancaire et le protocole du serveur dautorisation (voir les protocoles dnis en annexe). An de pouvoir effectuer correctement le routage des messages, le serveur dacquisition doit connatre les 4 premiers chiffres des numros des cartes de sa banque. 413

Chapitre 20. Projet de carte bleue


Le serveur dautorisation

Le serveur dautorisation doit tre capable de fournir une rponse une demande dautorisation. Pour fonctionner, le serveur dautorisation doit donc avoir accs aux soldes des comptes des clients de la banque rfrencs par numro de carte. Dans le cadre de cette simulation, nous utiliserons une mthode simple : le serveur dautorisation possde la liste des numros de cartes mises par la banque, auxquels sont associs les soldes des comptes adosss ces cartes ; lorsquune demande dautorisation lui parvient, le serveur vrie que le numro de carte gure bien dans sa liste. Il contrle alors que le solde de compte est sufsant pour effectuer la transaction, si cest le cas il rpond oui, sinon, il rpond non.
Le rseau interbancaire

Le rseau interbancaire na quune fonction de routage : il doit pouvoir accepter les messages provenant des serveurs dacquisition ; il doit pouvoir analyser le contenu des messages pour dterminer vers quel serveur dacquisition il doit les envoyer. Pour fonctionner, le rseau interbancaire doit possder la liste des codes de 4 chiffres gurant en en-tte des cartes et les banques associes.
Cahier des charges techniques
Contraintes gnrales

Lensemble de la simulation fonctionnera sur une seule machine. Les programmes seront crits en langage C, en mettant en uvre les solutions et techniques apprises pendant les TD du cours et en proscrivant toute autre mthode.
Nombre de processus

Chaque composant fonctionnel tel quil a t prsent dans le cahier des charges fonctionnel correspondra un processus. On trouvera donc un processus pour le terminal (que lon nommera Terminal), un processus pour le serveur dacquisition (Acquisition), un processus pour le serveur dautorisation (Autorisation) et un processus pour le rseau interbancaire (Interbancaire). La simulation pouvant mettre en jeu plusieurs terminaux, plusieurs banques, etc. et on trouvera en fait un processus Terminal par terminal, un processus Acquisition par serveur dacquisition et un processus Autorisation par serveur dautorisation.
Paramtres

Les paramtres ncessaires au fonctionnement des diffrents processus seront fournis via la ligne de commande , lexception des paramtres suivants qui seront fournis sous forme de chier : 414

20.2. Cahier des charges la liste des banques et de leur code 4 chiffres ; la liste des cartes bancaires de chaque banque et le solde du compte associ. Ces chiers seront au format texte, chaque ligne contenant les 2 informations demandes (code sur 4 chiffres et banque associe ou numro de carte et solde du compte associ), spares par une espace.
Gestion des changes par tuyau

Les terminaux connects au mme serveur dacquisition (donc les terminaux dune mme banque) utiliseront chacun une paire de tuyaux pour dialoguer avec ce serveur. Le serveur dacquisition aura donc comme rle dorchestrer simultanment les lectures et les critures sur ces tuyaux. La synchronisation (demande, autorisation dcriture, ordre de lecture) sera mise en uvre par lintermdiaire de lappel systme select() (voir protocole de communication dni en annexe). Les changes entre un serveur dacquisition et un serveur dautorisation seront possibles au travers dune paire de tuyaux, fonctionnant dune faon trs classique, selon le procd vu en PC. Pour ce qui concerne le rseau interbancaire et les serveurs dacquisition, la problmatique est strictement identique celle des terminaux : il faudra utiliser une paire de tuyaux pour connecter chaque serveur dacquisition au rseau interbancaire. Ceci confre au processus Interbancaire un rle de chef dorchestre et implique de maintenir une table de routage.
Comment tester sans sy perdre ?

Le schma propos ci-dessus comporte un petit dfaut tant que les communications entre processus se feront sur la mme machine (donc sans utiliser de socket, dveloppement propos en option) : chaque commerant (en fait lutilisateur, donc vous ou bien le MC qui corrigera votre projet) va en pratique saisir ses demandes dautorisation sur un processus Terminal. Or, tous ces processus auront t lancs partir du mme terminal de commande (du mme shell, de la mme fentre xterm). Nous ne disposerons donc que dune seule et unique fentre (dun seul terminal de commande) pour simuler plusieurs terminaux bancaires et pour afcher ou saisir les informations de ces terminaux bancaires... An de faire bncier chaque processus Terminal dune entre et dune sortie standard qui lui sont propres, lastuce suivante peut tre utilise : lors de la cration dun nouveau Terminal par le processus Acquisition 2 , recouvrir le nouveau ls du processus Acquisition non pas par Terminal, mais par xterm -e Terminal 3 .
2. Ou par un anctre commun au processus Acquisition et chaque processus Terminal. 3. La commande xterm -e prog lance une fentre xterm qui excute en premier lieu lexcutable prog ; cette astuce nest pas trs esthtique et nest quun intermdiaire permettant de tester le fonctionnement du projet avant la mise en place des communications par socket

415

Chapitre 20. Projet de carte bleue Les commerants pourront ainsi taper des demandes, puis lire les rponses obtenues sans que les afchages des diffrents terminaux ninterfrent entre eux.

Livrables

Doivent tre livrs pour constituer ce projet : un ensemble de chiers C, amplement comments, correspondant aux diffrents programmes constituant la simulation ; un chier Makefile permettant de compiler aisment ces programmes an de crer les excutables correspondants ; des exemples de chiers de conguration, permettant de faire fonctionner un rseau interbancaire auquel seront connectes deux banques possdant chacune deux terminaux ; un mode demploi expliquant comment obtenir une application oprationnelle, comment lexcuter et comment la tester ; ne pas oublier ce sujet de prciser tous les paramtres modier ou communiquer aux programmes pour le faire fonctionner dans un environnement diffrent de celui de lE NSTA ParisTech 4 ; un manuel technique dtaillant larchitecture, le rle de chaque composant, expliquant comment tester leur bon fonctionnement de faon indpendante et justiant les choix techniques effectus ; ce manuel devra en particulier bien prciser comment le projet rpond au cahier des charges (ce quil sait faire, ce quil ne sait pas faire et mettre en exergue ses qualits ainsi que ses dfauts). Tous les documents produire sadressent des ingnieurs gnralistes et les rdacteurs doivent donc veiller donner des explications concises mais claires. Aucun chier excutable, chier objet ou chier de sauvegarde ne devra tre transmis avec les livrables. Aucun chier superu ou inutile ne devra tre transmis avec les livrables. Tous les chiers constituant les livrables sont au format texte et doivent tre lisibles par tout lecteur ou diteur de texte. Les schmas (et uniquement les schmas) au format PDF sont tolrs.

20.3

Conduite du projet

Les tapes qui vous sont suggres dans cette partie permettent une approche sereine de la programmation de la version minimale du projet.
4. Le correcteur nutilisera en effet probablement pas une station de travail de lE NSTA ParisTech. Ce nest pas au correcteur (ou client) de faire des efforts pour comprendre lintrt de votre projet (produit), mais vous de faire des efforts pour le convaincre. Si le correcteur est oblig de chercher longuement comment faire fonctionner votre projet, la note attribue risque de reter sa faible conviction.

416

20.3. Conduite du projet


Introduction
Un projet en plusieurs tapes

Le dveloppement de ce simulateur va tre ici prsent en plusieurs tapes qui devraient vous amener vers une application fonctionnelle minimale. Ce dcoupage conduit le dveloppeur crire des programmes indpendants les uns des autres qui vont communiquer entre eux par lintermdiaire de tuyaux ou/et socket, souvent aprs redirection des entres et sorties standard. Ces programmes devront rester assez gnriques pour rpondre essentiellement la fonctionnalit pour laquelle ils ont t crs.
Mthode : divide and conquer

La constitution de lapplication globale par criture de petits programmes indpendants qui communiqueront ensuite entre eux est un gage de russite : chaque petit programme gnrique peut tre crit, test et valid indpendamment et la ralisation du projet devient alors simple et trs progressive. La meilleure stratgie adopter consiste crire une version minimale de chaque brique du projet, faire communiquer ces briques entre elles, puis, ventuellement, enrichir au fur et mesure chacune des briques. La stratgie consistant dvelopper outrance une des briques pour ensuite sattaquer tardivement au reste du projet conduit gnralement au pire des rapports rsultat / travail. Avant toute programmation, conception ou ralisation de ce projet, il est fortement conseill de lire lintgralit de lnonc, y compris les parties que vous ne pensez pas raliser, et de sastreindre comprendre la structuration en tapes propose ici. La traduction de cet nonc sous forme de schma est un pralable indispensable. En particulier, il est particulirement important de dnir ds le dpart lensemble des ux de donnes qui vont transiter dun processus un autre, de dterminer comment il est possible de sassurer que ces ux sont bien envoys et reus, puis de programmer les fonctions dmission et de rception correspondantes. Ces fonctions seront ensuite utilises dans tous les processus du projet. Un schma clair et complet associ des communications correctement gres garantissent la russite de ce projet et simplient grandement son dveloppement ! Cest dit !
tape 1 : les fonctions de communication

La premire tape consiste programmer les diffrentes fonctions de communication permettant de lire et crire des demandes dautorisation et des rponses selon les protocoles dnis en annexe. Les problmes de synchronisation seront abords dans les tapes suivantes et il sagit pour linstant uniquement de traduire sous forme de programmes (de fonctions 417

Chapitre 20. Projet de carte bleue en C) les protocoles de communication : formatage des messages selon la structure dnie en annexe, rcupration des informations stockes sous cette forme, etc. Une fois ces fonctions crites, elles seront utilises par tous les processus constituant le projet.
tape 2 : ralisation de programmes indpendants

Dans cette seconde tape, il sagit de mettre en place les diffrentes briques du projet et de pouvoir les tester indpendamment.
Processus : Autorisation

Le processus Autorisation recevra sur son entre standard des demandes dautorisation auxquels il devra rpondre sur sa sortie standard. Lexcutable Autorisation prendra sur sa ligne de commande le nom du chier de paramtres, cest--dire celui comprenant la liste des soldes des comptes des clients de la banque, rfrencs par leurs numros de cartes. Le processus Autorisation pourra donc tre test indpendamment des autres processus.
Processus : Acquisition

Le processus Acquisition recevra des messages de demande dautorisation en provenance soit des terminaux, soit du rseau interbancaire, ou bien des rponses en provenance du serveur dautorisation ou du rseau interbancaire. Ces messages seront redirigs ( routs ) vers le rseau interbancaire, vers le serveur dacquisition ou bien vers les terminaux, conformment au cahier des charges. cette phase du projet on pourra utiliser : lentre et la sortie standard pour simuler le dialogue avec le serveur dautorisation ( la main) ; des chiers dans lesquels le processus Acquisition ira crire lorsquil est suppos envoyer un message ; des chiers dans lesquels le processus Acquisition ira lire les messages quil sattend recevoir ; ces derniers seront prpars la main. Ces chiers permettront de simuler les changes avec les processus Terminal et Interbancaire. Le programme devra accepter sur sa ligne de commande au moins un paramtre reprsentant les 4 premiers chiffres des cartes de la banque laquelle il appartient.
Processus : Terminal

Le processus Terminal offrira lutilisateur linterface dnitive permettant la saisie des informations pour une transaction (montant et numro de carte). 418

20.3. Conduite du projet An de pouvoir tester le fonctionnement du Terminal, les messages de demande dinformation destination du serveur dacquisition pourront tre crits dans un chier. Les rponses seront galement lues dans un chier qui aura t prpar lavance.
Etape 3 : cration du rseau bancaire
Prparation de la communication par tuyaux

En pratique, le processus Acquisition et chaque processus Terminal vont communiquer via une paire unique de tuyaux. Une fois ces 2 tuyaux crs, il est possible de modier simplement les programme Terminal et Acquisition pour quils utilisent non pas des chiers mais ces 2 tuyaux. Pour cela, on compltera le programme Terminal en permettant de lui passer sur la ligne de commande les descripteurs des tuyaux utiliser en lecture et en criture. On modiera le code pour que ces tuyaux soit utiliss en lieu et place des chiers employs la seconde tape.
Raccordement

Pour terminer la mise en place des communications au sein dune mme banque, il faut maintenant crer, dune part, une paire de tuyaux entre Acquisition et chaque Terminal (il y aura autant de paires de tuyaux que de terminaux), dautre part, une paire de tuyaux entre Acquisition et Autorisation, aprs avoir redirig leurs entres et sorties standard. crire un programme Banque acceptant sur sa ligne de commande le nombre de terminaux de la banque. Le programme devra galement accepter sur sa ligne de commande les paramtres suivants : le nom de la banque simuler ; les 4 chiffres associs cette banque ; le nom dun chier contenant les soldes des comptes des clients ; le nombre de terminaux crer. Crer les tuyaux ncessaires, oprer les clonages et recouvrement ncessaire pour crer les processus Terminal et Autorisation en nombre sufsant. Enn recouvrir Banque par le programme Acquisition sans oublier deffectuer les redirections ncessaires des tuyaux avec les entres et sorties standards.
Etape 4 : cration du rseau interbancaire
Processus : Interbancaire

Chaque serveur dacquisition sera reli au processus Interbancaire par une paire de tuyaux. Celle-ci permettra Interbancaire de recevoir les messages de demande dautorisation et de transmettre les rponses en retour, aprs les avoir routs. 419

Chapitre 20. Projet de carte bleue Larchitecture mettre en place entre Interbancaire et les diffrents processus Acquisition sera similaire celle mise en place entre chaque processus Acquisition et les processus Terminal qui y sont relis. Dans un premier temps, les messages transitant par Interbancaire seront simplement lus sur lentre standard et crits sur la sortie standard (ou lus et crits dans des chiers, sans quaucune communication inter-processus ne soit mise en place.
Raccordement

On crera un programme Dmarrage qui devra accepter sur sa ligne de commande un chier de conguration dans lequel on trouvera les informations suivantes : le nom dun chier contenant toutes les banques et les 4 chiffres associs chaque banque ; les noms des chiers ncessaires au fonctionnement de chaque banque ; le nombre de terminaux pour chaque banque. Le processus Dmarrage devra crer les tuyaux, effectuer les clnages et les recouvrements ncessaires an de crer lensemble des processus ncessaires la simulation.

20.4

volutions complmentaires et