Вы находитесь на странице: 1из 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 optionnelles

Multi-demandes

Dans cette version les processus Acquisition et Interbancaire doivent pouvoir grer plusieurs demandes en parallle. Il faudra donc utiliser toutes les possibilits didentication des messages proposs par les protocoles.
Cumul des transactions

On demande de rendre la simulation plus raliste en permettant au niveau du serveur dautorisation de comptabiliser lensemble des paiements effectus par une carte an de proposer une rponse la demande dautorisation qui tienne compte du solde du compte, mais galement de lencours carte.
Dlai dannulation (time out)

Dans cette version les processus Acquisition et Interbancaire doivent pouvoir grer une fonction dannulation : une transaction est annule lorsquil sest coul plus de 2 secondes depuis le relais de la demande, sans quune rponse ne soit parvenue. Si une rponse parvient ultrieurement, elle sera ignore. Ceci suppose que les processus Acquisition et Interbancaire conservent une trace date de toutes les demandes quils traitent et quils vrient rgulirement la date de premption de chaque demande. 420

20.5. Annexes Attention : lannulation dune demande pour dlai trop important ne dispense pas les processus Acquisition et Interbancaire denvoyer une rponse lmetteur de la demande.

Utilisation des sockets

Lutilisation des communications entre machines ne fait pas partie des objectifs de ce cours. Cependant, an de rendre le projet plus vivant et plus attractif, nous proposons aux plus dbrouillards dutiliser des sockets de sorte que la simulation soit plus raliste : chaque terminal communique avec son serveur dacquisition par des sockets ; chaque serveur dacquisition communique avec le rseau interbancaire par sockets. Les informations ncessaires lutilisation des sockets sont donnes dans le polycopi. Une introduction aux sockets est faite en annexe. Le travail de mise en uvre des communications par socket ne doit tre entrepris seulement aprs avoir russi programmer une application compltement oprationnelle avec des tuyaux. Attention : ne jamais modier un programme qui fonctionne et toujours travailler sur une copie de celui-ci.

20.5

Annexes

De lintrt des standards pour assurer une meilleure interoprabilit

Le recours des protocoles standardiss (ou pour le moins communs) a un double intrt : il permet de gagner du temps sur le dveloppement de ce projet et dillustrer par la pratique la faon dont les problmes sont traditionnellement rsolus en informatique ; il permettra de mlanger les projets entre eux an de tester le respect du protocole et, peut-tre, daider certains binmes avancer (il sufra dutiliser les processus dun autre binme 5 ). La capacit ainsi esquisse mlanger des composants issus de programmeurs ou prestataires diffrents pour constituer un systme dinformation unique se nomme linteroprabilit et est un des enjeux majeurs de linformatique actuelle.
5. Prcisons tout de suite que cette utilisation ne peut sentendre qu des ns de mise au point...

421

Chapitre 20. Projet de carte bleue


Spcications des protocoles de communication

Les messages sont constitus de caractres ASCII 6 . Chaque message est constitu de diffrents champs spars par les caractres | et termins par un caractre de n de message \n. Le premier caractre indique toujours la nature du message. Le rcapitulatif des changes est prsent sur la gure 20.3.

F IGURE 20.3 Rcapitulatif des changes.

Remarque : an de simplier au maximum le protocole, on considre quil nest pas possible que plus dune demande dautorisation ou rponse, correspondant une transaction faite avec une carte donne, circule sur le rseau. Cette hypothse (plutt raliste) permettra de simplier lappariement des demandes et des rponses au niveau des processus Acquisition et Interbancaire.
Un format de message unique

Quel que soit le message, son format est toujours le mme :


|I...I|C...C|A...A|B...B|\n avec :

I...I est lidentiant de lenvoyeur du message (longueur strictement suprieur 0 et indtermine a priori) ; C...C est le nom de la commande dcrite dans le message (longueur strictement suprieur 0 et indtermine a priori) ;
6. Lutilisation de caractres accentus dans les changes entre processus et dans les chiers de conguration est strictement dconseille. En fonction des paramtrages des stations de travail, lutilisation de ce type de caractres peut entraner des complications et imposer une programmation en C plus complexe. Rappelons que le cours IN201 nest pas un cours de programmation en C et que limportant est de russir mettre en oeuvre des changes structurs entre processus.

422

20.5. Annexes A...A est le premier argument (optionnel) de la commande (longueur indtermine a priori) ; B...B est le deuxime argument (optionnel) de la commande (longueur indtermine a priori). Ce format devrait permettre de traiter lensemble des cas possibles. An de simplier la mise en uvre, on supposera quun message comporte au plus 255 caractres. La suite des spcications du protocole consiste dnir lutilisation de ce format spciquement au contexte des communications numres dans le schma rcapitulatif ci-dessus.
Protocole du terminal

Les changes avec le terminal utilisent deux types de message, la demande dautorisation et la rponse la demande dautorisation. (1) Message de demande dautorisation Ce message est envoy par le terminal et rceptionn par le serveur dacquisition. Format : |I...I|DemandeAutorisation|A...A|B...B|\n Chaque lettre reprsente un caractre du message. I...I est lidentiant du terminal (par exemple son PID) ; A...A correspond au montant de la transaction exprim en centimes deuros ; B...B correspond au numro de la carte sur 16 chiffres dcimaux (longueur constante). (2) Message de rponse la demande dautorisation Ce message est envoy par le serveur dacquisition et rceptionn par le terminal. Format : |I...I|ReponseAutorisation|A...A||\n avec : I...I est lidentiant du serveur dacquisition (peut tre son PID) ; A...A est la chane de caractres : oui si la demande est accepte, non si la demande est refuse, timeout si le dlai imparti pour rceptionner la rponse est coul, probleme si un problme inattendu est survenu ;
Protocole du rseau interbancaire

(5) (7) Message de demande dautorisation Ce message peut tre mis par le rseau interbancaire et rceptionn par un serveur dacquisition ou bien mis par un serveur dacquisition et rceptionn par le rseau interbancaire. Format : |I...I|DemandeAutorisation|A...A|B...B|\n avec : I...I est lidentiant de lenvoyeur du message (peut tre son PID) ; A...A correspond au montant de la transaction exprim en centimes deuros ; B...B correspond au numro de la carte sur 16 chiffres dcimaux (longueur constante). 423

Chapitre 20. Projet de carte bleue (6) (8) Message de rponse Ce message est fourni par un serveur dacquisition au rseau interbancaire ou par le rseau interbancaire au serveur dacquisition. Format : |I...I|ReponseAutorisation|A...A|B...B|\n avec I...I est lidentiant de lenvoyeur du message (peut tre son PID) ; A...A est la chane de caractres : oui si la demande est accepte, non si la demande est refuse, timeout si le dlai imparti pour rceptionner la rponse est coul, probleme si un problme inattendu est survenu ; B...B est le numro de carte (16 chiffres) sur laquelle la transaction a t passe.
Le serveur dautorisation

Message de demande dautorisation Ce message est envoy par le serveur dacquisition au serveur dautorisation. Format : |I...I|DemandeAutorisation|A...A|B...B|\n avec : I...I est lidentiant du serveur dacquisition (peut tre sont PID) ; A...A correspond au montant de la transaction exprim en centimes deuros ; B...B correspond au numro de la carte sur 16 chiffres dcimaux (longueur constante). Message de rponse Ce message est envoy par le serveur dautorisation au serveur dacquisition. Format : |I...I|ReponseAutorisation|A...A|B...B|\n avec I...I est lidentiant du serveur dautorisation (peut tre son PID) ; A...A est la chane de caractres : oui si la demande est accepte, non si la demande est refuse, timeout si le dlai imparti pour rceptionner la rponse est coul, probleme si un problme inattendu est survenu ; B...B est le numro de carte (16 chiffres) sur laquelle la transaction a t passe.
Les socket

Dans cette partie, vous trouverez quelques explications simples sur le principe de fonctionnement des socket et sur les quelques programmes fournis.
Quest-ce quune socket ?

Un chapitre complet du polycopi (hors programme du cours) dcrit le fonctionnement et la mise en uvre des socket et les lves qui souhaitent rellement comprendre doivent sy rfrer. Les explications ci-dessous ne sont quun clairage rapide et, pour 424

20.5. Annexes rester concis, nous utiliserons lanalogie entre les communications par socket et les communications tlphoniques. Une socket est lquivalent de lensemble constitu par un poste tlphonique et par le cble de tlphone qui y arrive. Contrairement aux installations tlphoniques, que lon a tendance ne pas faire et dfaire tout le temps, les socket sont cres la demande et dtruites ds quon ne sen sert plus. Pour utiliser un poste tlphonique, il faut disposer dune ligne, rfrence par un numro unique (le numro de tlphone). Idem pour les socket, mme sil est possible dans certains cas dutiliser une socket sans quelle nait de numro associ. Le numro de tlphone qui est associ une ligne donne dpend de la localisation de cette ligne : les lignes tlphoniques dun mme quartier sont gres par le mme central tlphonique (aussi appel autocommutateur ou PABX) et la transmission des communications de central central seffectue justement en reprant les premiers chiffres du numro de tlphone : tous les numros en 01 45 52 MC DU, par exemple, sont grs par le central de la cit de lair 7 . Les chiffres MCDU sont alors dtermins en fonction des disponibilits, souvent de faon incrmentale, mais sans que cela nait rellement de sens : 54 01 pour le standard de lENSTA, 44 06 pour le DFR, 44 24 pour le DFR/A, etc. Pour les sockets, le rle du central tlphonique de quartier est tenu par le systme dexploitation de lordinateur sur lequel la socket est cre. Lquivalent du numro de ligne sappelle un numro de port 8 et lquivalent des 6 premiers chiffres du numro de tlphone est donn par le nom de lordinateur, ces deux lments tant par convention spars par le caractre : . Par exemple, une socket pourra tre accessible via le numro de ligne menthe2.ensta.fr:1234. Il est noter que, en pratique, tous les ordinateurs relis lInternet (et de faon gnrale tous les ordinateurs communiquant par protocole IP) sont dsigns par un numro unique 9 appel adresse IP . Par exemple, menthe2 a pour adresse IP 147.250.9.32, menthe3 a pour adresse IP 147.250.9.33, ivoire3 a pour adresse IP 147.250.9.103, www.ensta.fr a pour adresse 147.250.7.70, www.sncf.com a pour adresse 195.25.238.132, etc. La correspondance entre ladresse IP dune machine et son nom est tabli par une machine particulire, le DNS (pour Domain Name System),
7. En pratique, les choses sont un tout petit peu plus compliques que a : un numro de tlphone 10 chiffres est de la forme E Z ABPQ MCDU, o E dsigne loprateur longue distance charg dacheminer la communication (0 pour loprateur par dfaut de boucle locale), Z dsigne la zone concerne (1 5 et 9 pour les xes, 6 ou 7 pour les mobiles, 8 pour les numros spciaux), ABPQ dsigne le commutateur de rattachement et MCDU dsigne le numro de la ligne sur le commutateur de rattachement (Milliers Centaines Dizaines Units). 8. Le terme de port est frquemment utilis en informatique. De faon gnrale, il reprsente un point dentre ou de sortie pour un change de donnes entre deux zones informatiques diffrentes. rapprocher des ports maritimes qui sont les points dentre et de sortie des changes de marchandises entre la zone terrestre et la zone maritime. De mme que certains ports maritimes sont spcialiss dans certains types de marchandises, certains ports informatiques sont ddis certains types dchange de donnes (mail, WWW, etc.). 9. Au moins sur le rseau sur lequel est connect cette machine.

425

Chapitre 20. Projet de carte bleue et le rseau Internet contient un certain nombres dordinateurs assurant ensemble et de faon synchrone ce service pour lensemble des machines connectes lInternet. Lquivalent du numro de tlphone pour les socket est donc, en ralit, non pas le nom de la machine suivi du numro de port, mais ladresse IP de la machine suivie du numro de port : 147.250.9.32:1234 pour menthe2, par exemple. Nous conserverons par la suite le premier formalisme car il est plus simple et quil offre un avantage majeur : la correspondance entre le nom dune machine et son adresse IP (la rsolution de nom en jargon informatique) tant effectue chaque utilisation du nom, ladresse IP de lordinateur peut tre modie dans le temps sans que cela naffecte les communications.
Comment communique-t-on par socket ?

La communication par socket est similaire celle par tlphone : lun des postes tlphoniques doit mettre un appel, en indiquant le numro de la ligne atteindre, et lautre poste doit attendre lappel, puis dcrocher pour tablir la communication. Idem pour les socket, chaque action entreprendre (composer le numro, attendre la sonnerie, dcrocher, etc.) tant ralise au travers dun appel systme demand par un programme particulier. La faon dont les communications par socket sont gnralement tablies et traites sapparente cependant plutt une communication tlphonique entre un particulier et une entreprise : le particulier appelle le standard de lentreprise an de parler avec M. Untel, par exemple, la standardiste rpond et transfre la communication sur le poste de M. Untel, ce qui lui permet de rester disponible pour dautres appels. Ainsi, lorsque le programme ayant cr la socket que lon cherche joindre (programme que nous nommerons serveur pour la suite de lexplication) accepte dtablir la communication avec le programme qui la demande (programme que nous nommerons client pour la suite de lexplication), le serveur cre une autre socket et lui transmet la communication, de faon totalement similaire ce quaurait fait la standardiste ( ceci prs que les sockets sont cres la demande et dtruites ds quelles ne sont plus utilises). Le plus souvent, ceci est effectu via la cration dun ls (fork()) totalement ddi cette communication, ce qui permet au pre (le serveur) de rester disponible pour dautres communications (dautres connexions, dans le langage informatique). Cest ainsi que fonctionne la plupart des serveurs WWW par exemple.

426

21
Le Scaphandre et le Papillon

21.1

nonc

Ce projet est librement adapt de lcriture du livre Le Scaphandre et le Papillon. Lauteur de ce livre, Jean-Dominique Bauby, tait atteint du locked-in syndrome qui le paralysait presque totalement et il a dict lintgralit du texte par le seul battement de sa paupire gauche. An de laider dans cette tche, une personne numrait voix haute les lettres de lalphabet dans un ordre dtermin et attendait le signal de lauteur pour slectionner une lettre. Parfois, cette personne compltait delle-mme les mots et proposait le mot entier pour conrmation. Le but du projet est de faire faire par un ordinateur le travail de la personne charge de prendre la dicte, cest--dire : numrer les lettres de lalphabet dans un ordre donn (qui peut ventuellement changer au fur et mesure) ; dtecter les battements de paupire de lutilisateur (qui seront modliss au moyen dune touche du clavier) et arrter lnumration ; recueillir la lettre ainsi dsigne pour la concatner au mot courant ; consulter plusieurs dictionnaires an de proposer une terminaison probable au mot courant ; prvenir lutilisateur en cas derreur probable, notamment si aucune terminaison na pu tre trouve, et intervenir pour proposer une rectication ; passer au caractre suivant.

21.2

Contraintes gnrales

La ralisation de ce projet ne doit pas tre vue comme la simple mise en uvre des notions abordes tout au long de ce document : chaque projet doit prendre en compte le besoin rel du handicap et doit proposer des solutions qui peuvent effectivement tre appliques. Avant toute considration technique concernant les systmes dexploitation et la programmation en C, il convient donc deffectuer une analyse complte des besoins du handicap et de dnir les diffrentes fonctionnalits qui doivent tre prises en charge. 427

Chapitre 21. Le Scaphandre et le Papillon En particulier, il est capital de regrouper les fonctionnalits qui ne peuvent tre dissocies et de bien sparer les fonctionnalits qui nont pas de liens directs. Ceci permettra de dvelopper des projets modulaires qui pourront sadapter facilement aux conditions relles demploi et aux volutions des matriels utiliss. Ainsi, il serait peu judicieux de considrer que la dtection des clignements de paupire sera effectue par le mme biais (par le mme processus en loccurrence) que lafchage du mot courant... Formul diffremment, il est capital de ne pas faire de prsuppos sur lquipement dont dispose le handicap pour dicter son texte. Notons que ceci ne correspond pas une volont pdagogique de la part de lquipe de ce cours, mais bien une approche pratique des problmes qui se posent en ralit : la tche correspondant lafchage des lettres, par exemple, est totalement indpendante de celle visant complter les mots et lindpendance effective de ces deux tches permet de rduire les cots de dveloppement. Ainsi, si le handicap utilise dans un premier temps un minitel pour communiquer avec le programme principal et si, convaincu de lutilit dun tel programme, il dcide de sacheter un PC et un modem pour dialoguer avec lui, seuls les codes des tches dafchage et de communication sont rcrire. Ce raisonnement peut encore tre dvelopp : si les tches sont vritablement indpendantes et si elles dialoguent en utilisant un protocole de communication prdni (par exemple, un protocole standard), les diffrents programmes permettant dafcher, de communiquer, etc. peuvent tre choisis sur tagre , cest--dire quil nest pas ncessaire de les dvelopper 1 : il suft dacheter au plus bas prix de tels programmes.

21.3

Contraintes techniques

Un certain nombre de contraintes techniques sont imposes an de canaliser les efforts de dveloppement qui peuvent tre faits sur ce sujet. Certaines de ces contraintes dcoulent des contraintes gnrales dcrites ci-dessus (il serait judicieux de trouver lesquelles et de voir pourquoi), dautres visent simplement limiter la quantit de travail fournir. Insistons sur le fait que la dcomposition du projet en units fonctionnelles indpendantes est un travail qui peut paratre pnible au premier abord mais qui permet rapidement de se dgager des inconvnients de dveloppement des projets lourds : chaque unit fonctionnelle peut tre dveloppe indpendamment du reste du projet et, donc, peut tre dbogue et teste indpendamment. . . En pratique, les units fonctionnelles voques ci-dessus seront des processus indpendants et le projet sera donc constitu dun certain nombre de programmes diffrents, amens communiquer les uns avec les autres. Les contraintes techniques imposes sont les suivantes : Les diffrents programmes seront crits en C sans faire appel des bibliothques spcialises. En particulier, aucune bibliothque graphique nest autorise (la
1. Cette remarque sadresse aux futurs ingnieurs, mais pas aux actuels lves...

428

21.4. Services mettre en uvre biliothque termios peut tre utilise lexception des fonctionnalits rendant la saisie non bloquante). Un seul processus dialoguera avec lutilisateur. Ce processus se limitera la dtection des clignements (pourquoi ?). Tous les afchages lcran seront effectus par un seul et unique processus, dont ce sera la seule et unique fonction (pourquoi ?). La recherche dans les dictionnaires sera assure par plusieurs autres processus totalement indpendants du processus principal et fonctionnant avec nimporte quel dictionnaire. Si le projet ne fonctionne quavec un format particulier de dictionnaires (dictionnaires tris par ordre alphabtique, par exemple), il conviendra de vrier au moment de lexcution que les dictionnaires fournis conviennent. Les mcanismes de communication et de synchronisation dcrits dans ce document seront utiliss an dassurer que le handicap est toujours prioritaire, cest--dire quil na jamais besoin dattendre quoi que ce soit (et surtout pas la n dune recherche dans un dictionnaire) pour dicter du texte. Toute particularit du projet qui empcherait dutiliser des modules dj dvelopps et rpondant au cahier des charges (cf. remarque prcdente sur les achats sur tagre ) est proscrire. De mme, est proscrire toute particularit qui empcherait de transformer aisment ce projet en un projet distribu sur un rseau de machines. Formul diffremment, tous les processus mis en uvre par ce projet tourneront sur la mme machine an de simplier les dveloppements (sauf pour les lves ayant choisi dutiliser des sockets au lieu de tuyaux de communication), mais il est important de concevoir une architecture qui ne tire pas parti de cette simplication et qui considre que chaque processus peut tre sur une machine diffrente. Ainsi, en particulier, toute utilisation de chier sur support local (disque dur par exemple) comme moyen de communicatione est prohibe. Lutilisation de fonctions comme sleep(), system() ou tout autre fonction des bibliothques du langage C pouvant se substiter aux appels systme dcrits dans ce document est trs fortement dconseile car les programmes utilisant la fois de telles fonctions et les appels systme en question ont un comportement parfois difcilement comprhensible pour le nophyte. Larchitecture de votre programme tiendra compte des services mettre en uvre, mme si le temps imparti ne permet pas de les programmer. Notons que cette dernire contrainte nen est pas une si vous appliquez les rgles dictes ci-dessus...

21.4

Services mettre en uvre

Le projet comportera au minimum les services dcrits dans lnonc (section 21.1), savoir lnumration et la slection de lettres, la consultation dau moins 2 dictionnaires pour proposer des terminaisons et la concatnation des lments slectionns 429

Chapitre 21. Le Scaphandre et le Papillon par le handicap (lettre ou terminaison) pour crer des mots. Vous programmerez aussi dans lordre les services dcrits ci-dessous.
Correction des erreurs de dicte

Il est probable que des erreurs surviennent au cours de la dicte (arrt tardif de lnumration, changement davis, tourderie) et il convient donc de laisser la possibilit lutilisateur deffectuer des rectications. Dans le cas de Jean-Dominique Bauby, ces erreurs taient traites par des mimiques (regard horri pour signaler une erreur, condescendant pour approuver...) et simplies par la psychologie de lassistante. Le but tant de sen passer, vous aller au contraire proposer au handicap de rectier lui-mme le texte, avec des fonctions de correction (annulation du dernier caractre et du dernier mot). Vous vous limiterez trois touches, la troisime touche correspondant au clignement des deux paupires...
Utilisation dun alphabet spcialis

Parcourir lalphabet nest pas toujours la meilleure solution pour dicter un mot (calculez le nombre dnumrations ncessaires pour dicter le mot xylophone !). Pour amliorer le procd, vous utiliserez un alphabet spcialis retant la probabilit dutilisation des lettres de lalphabet dans le texte dict. Voici par exemple lordre utilis par Claude Mendibil et Jean-Dominique Bauby (qui correspond en fait la frquence dapparition des lettres dans la langue franaise) : ESARINTULOMDPCFBVHGJQZYXKW
Utilisation dun alphabet dynamique

Lutilisation dun alphabet spcialis permet probablement de gagner du temps en moyenne, mais peut savrer peu efcace dans certains cas (refaire le test avec lalphabet propos ci-dessus et le mot xylophone). An daugmenter encore lefcacit du logiciel, vous utiliserez des alphabets dynamiques dont lordre dapparition des lettres sera recalcul chaque slection dune lettre par le handicap, par exemple en fonction des terminaisons probables du mot courant. Attention, cette amlioration doit tenir compte de deux points importants : le programme doit pouvoir fonctionner avec tout type de dictionnaire et il ne faut pas empcher le handicap de crer des nologismes.
Utilisation dun dictionnaire spcialis

Avec le dictionnaire franais courant, votre programme ne devinera jamais des mots comme ensta ou babasse pratiqus rgulirement par un handicap. 430

21.5. Prcisions partir de maintenant, quand vous rencontrerez un mot nouveau, aprs la conrmation dusage, vous proposerez dinsrer ce mot dans un dictionnaire personnel... Ce dictionnaire se prsentera comme les autres du point de vue de la recherche lexicographique, mais sera plus subtil, puisquil pourra senrichir au fur et mesure de lexcution du programme.
Aide dun oprateur humain

Cette extension sadresse aux programmeurs avertis qui voudraient essayer la programmation des sockets. La terminaison automatique laide dun dictionnaire nest pas simple pour les mots conjugus ou les nologismes. Dans ce cadre l, la participation dun oprateur humain (qui peut deviner lintention du handicap) est un atout important. Nanmoins, il est toujours difcile de trouver des volontaires qui acceptent de se rendre au chevet du handicap... Lidal serait donc de permettre au dictionnaire humain dintervenir distance, via un rseau de communication, au travers dune interface similaire celle utilise par le handicap.

21.5

Prcisions

Des dictionnaires sont facilement accessibles sur la toile. Le point important de ce projet est dcrire des programmes indpendants les uns des autres qui vont communiquer entre eux par lintermdiaire de tuyaux. Il nest pas ncessaire de spcialiser ces programmes et chaque programme devrait tre interchangeable avec le programme crit par un autre lve pour ce projet. Signalons par ailleurs que ce projet est assez lourd et quil convient de le commencer trs tt. Ceci nest possible que si le projet est conu ds le dpart de faon modulaire : certains outils, en effet, sont abords trs tard dans les TP et il serait dangereux dattendre de tout matriser avant de dbuter le projet. En particulier, il nest pas ncessaire de comprendre le fonctionnement des tuyaux de communication pour crire les diffrents programmes indpendants qui raliseront les fonctions dnumration, de dtection de clignement, de recherche de terminaisons, etc. Dun point de vue technique, tous les processus indpendants peuvent utiliser lentre standard et la sortie standard pour changer des donnes. Ces entre et sortie standard seront ensuite rediriges dans des tuyaux de communication, ce qui permettra dtablir des communications sans avoir modier ces programmes...

21.6

Droulement du projet

Les conditions de remise du projet sont dcrites sur la page WWW du cours.

431

22
Projet de Jukebox

Ce projet propose la conception dun systme de slection et daudition de morceaux numriques de musique en environnement multi-utilisateurs.

22.1

nonc

Le but de ce projet est de concevoir un systme permettant dcouter de la musique dans une salle informatique o plusieurs personnes travaillent (typiquement une salle de cours). Une seule machine est charge de dcoder les chiers sons, stocks au format MPEG-I layer 3 (not frquemment MP3) et de les envoyer (via la carte son) un amplicateur audio an que tous en protent. Chaque utilisateur peut ainsi choisir depuis sa console quels morceaux de musique il souhaite couter et il dispose en retour des informations concernant le morceau en cours (afches sur son cran, avec au minimum le titre et un compteur de temps). La machine effectuant le dcodage des chiers de musique devra ainsi communiquer avec les diffrentes personnes de la salle (directement sur leur console), dcider des morceaux jouer en fonction des dsirs de chacun et renvoyer des informations en retour. Le fonctionnement de lensemble sera le plus dynamique possible et, notamment, les deux cas suivants devront tre traits : un utilisateur pourra arriver dans la salle alors que tout fonctionne, se connecter un des postes de travail et donner ses desiderata ; un utilisateur dj connect et ayant effectu des demandes dcoute de morceaux de musique pourra se dconnecter sans perturber les autres utilisateurs. An davoir le maximum de souplesse, on supposera que la musique est stocke et accessible (sous forme de chier) partir de la machine qui effectue la lecture, mais pas ncessairement depuis les consoles des personnes prsentes dans la salle.

22.2

Outils utiliss

Ce projet ne visant pas lcriture dun dcodeur MP3, un logiciel libre sera utilis cet effet : mpg123. Ce logiciel est fourni dans nombre de distributions de Linux et il 433

Chapitre 22. Projet de Jukebox est install sur toutes les machines Linux de lENSTA. Ses sources sont disponibles sur http://www.mpg123.de et il sera ncessaire de les parcourir : ce logiciel offre en effet lavantage de pouvoir tre pilot grce un jeu de commandes envoyes sur lentre standard (les retours de valeurs tant lues sur sa sortie standard), mais ces fonctionnalits ne sont pas dtailles dans la page de manuel qui sattache plutt montrer les diffrentes options utilisables en ligne de commande (cf. 22.3). Le contrle du volume sonore (et ventuellement dautres rglages, comme balance et graves/aigues) de la machine qui commande lamplicateur audio pourra tre fait directement avec les bons appels ioctl() appliqus au priphrique /dev/mixer. Ceci est spcique Linux et rechercher dans les pages de manuel et les documentations de OSS, le gestionnaire de son utilis sous Linux 1 . Dans un premier temps, on pourra se passer de commander la carte son ou on pourra utiliser le programme aumix (ou mixer) avec une ligne de commande adquate. Il ne sagit toutefois pas dune solution lgante car aumix doit tre excut pour chaque modication de volume... Les autres lments du projet sont programmer en C directement. Les tests du projet se feront au casque sur une machine disposant dune carte sonore adquate. Dans le pire des cas, il est possible de faire fonctionner le programme mpg123 sans carte son des ns de vrication.

22.3

Commandes de mpg123

Pour commander mpg123 depuis son entre/sortie standard, il faut le lancer ainsi :
mpg123 --aggressive -R -

Loption --aggressive est facultative, mais demande au programme dutiliser si possible les fonctionnalit dordonnancement temps rel 2 an de garantir un dcodage sans coupure de la musique (lire un chier son est une tche typiquement temps rel). -R dit au programme de fonctionner dans le mode tlcommand (remote). Le paramtre - est la faon habituelle sous Unix de dnommer lentre/sortie standard (cette option est prsente uniquement parce que mpg123 attend par dfaut un nom de chier). Remarque : pour faire fonctionner le programme sans carte son, on peut utiliser loption -t de test (mais les temps ne sont pas respects) ou -w <fichier.wav> qui crit le rsultat dans un chier. Voici les commandes que lon peut envoyer par lentre standard dans ce mode (il faut envoyer chaque commande sous la forme dune ligne de texte complte) :
1. Voir /usr/include/linux/soundcard.h et les sources dun programme de mixage simple comme
mixer.

2. Il semble que cest inutile lENSTA car le programme ne dispose pas de droits sufsant pour utiliser ces fonctionnalits.

434

22.3. Commandes de mpg123


QUIT : arrter tout et quitter le programme ; LOAD <f> : charger et jouer le chier <f> (fonctionne aussi avec une URL, mais

on ne lutilisera pas) ;
STOP : arrter la lecture sans quitter le programme ; PAUSE : mettre en pause / redmarrer la lecture ; JUMP [+|-] <n> : dplacement dans le chier de <n> trames.

Remarque : une trame MP3 fait 1152 chantillons sonores, donc une frquence dchantillonnage de 44100 Hz (correspondant un CD-audio), cela donne un peu plus de 38 trames/s. Quelques exemples pour JUMP :
JUMP +38 : avance de 38 trames (une seconde environ) ; JUMP -64 : recule de 64 trames (revient au dbut du chier si moins de 64 trames

se sont coules) ;
JUMP 0 : revient au dbut du chier courant ; JUMP 200 : saute directement la trame numro 200.

Le programme mpg123 retourne des rponses (et des lignes dinformations) dont le format suit. Les valeurs de retour sont envoyes sur la sortie standard, les erreurs sont renvoyes sur la sortie derreur (stderr), il faut donc observer les deux.
@R MPG123 : message envoy au dmarrage du programme. @I <a> : Message envoy au chargement dun chier, <a> est le nom du chier

sans son chemin ni extension. Cette rponse est renvoye aprs un LOAD pour un chier ne contenant pas dinformations ID3-tag (zone dinformation rserve dans le chier MP3).
@I ID3: <abcdef> : message envoy au chargement dun chier contenant un

ID3-tag ; <a> : titre (exactement 30 caractres), <b> : artiste (exactement 30 caractres), <c> : album (exactement 30 caractres), <d> : anne (exactement 4 caractres), <e> : commentaire (exactement 30 caractres), <f> : genre du morceau (chane de longueur non-xe).
@S <abcdefghijkl> : message donnant les principales informations extraites de

len-tte du chier lu, avec : <a> : type de MPEG (string), <b> : layer (int), <c> : frquence dchantillonnage (int), <d> : mode (string), <e> : extension de mode (int), <f> : taille de la trame (int), <g> : stereo (int), <h> : copyright (int), <i> : code correcteur derreur (int), 435

Chapitre 22. Projet de Jukebox


<j> : praccentuation (int), <k> : dbit (en kbits/s) (int), <l> : extension (int). @F <abcd> : message envoy chaque trame lue avec : <a> : numro de trame (int), <b> : nombres de trames restantes pour ce chier (int), <c> : secondes (float), <d> : secondes restantes (float). @P <a> : message de changement dtat avec : 0 : lecture arrte (commande STOP, ou la n normale du morceau), 1 : en pause (commande PAUSE), 2 : redmarrage aprs pause (commande PAUSE).

Voici un exemple de session, dans laquelle les chanes envoyes vers lentre standard de mpg123 sont prcdes de >> :
Idefix> mpg123 --aggressive -R @R MPG123 >> LOAD 03_Guide_vocal.mp3 @I ID3:Guide vocal Genesis Duke 1983 AlternRock @S 1.0 3 44100 Stereo 0 418 2 0 0 0 128 0 @F 1 3617 0.03 94.48 @F 2 3616 0.05 94.46 @F 3 3615 0.08 94.43 @F 4 3614 0.10 94.41 ... @F 94 3524 2.46 92.06 @F 95 3523 2.48 92.03 >> JUMP +32 @F 96 3522 2.51 92.00 @F 129 3489 3.37 91.14 @F 130 3488 3.40 91.12 ... @F 192 3426 5.02 89.50 >> PAUSE @P 1 >> PAUSE @P 2 @F 193 3425 5.04 89.47 @F 194 3424 5.07 89.44 ... @F 227 3391 5.93 88.58

436

22.4. Les ID3-tag


>> STOP @P 0 >> QUIT

Comme il y a un retour de valeur chaque trame dcode, il faudra commencer par crire un programme capable de lancer mpg123 et de dialoguer avec lui, en particulier pour absorber les valeurs de retour et stocker ltat du dcodeur en interne. On grera galement les cas o le programme se termine avec une erreur, tout particulirement lorsque lon demande le chargement dun chier inexistant (bien que ce cas ne se produise normalement pas dans le projet nal).

22.4

Les ID3-tag

An de proposer lutilisateur une liste de morceaux plus explicite que simplement les noms des chiers, on pourra utiliser les ID3-tags. Ceux-ci sont retourns par mpg123 au dbut de la lecture dun morceau, mais pour pouvoir prsenter lensemble des chiers au choix, il faut faire soi-mme la lecture, et ceci est trs simple. Les informations du ID3-tag sont stockes dans les 128 derniers octets du chier MP3. Ces 128 derniers octets se rpartissent comme suit dans lordre (cest lordre dans lequel mpg123 les retourne galement) : Les trois caractres TAG . Ceci permet de savoir si le chier dispose ou non dun ID3-tag ; Le titre du morceau sous la forme dun bloc de 30 caractres, le titre tant complt 30 caractres par des espaces ; Le nom de lartiste sur 30 caractres ; Le nom de lalbum sur 30 caractres ; Lanne (en texte) sous la forme de 4 caractres exactement ; Un commentaire sous la forme de 30 caractres (30 espaces sil ny a pas de commentaire) ; Un octet qui classe le type de musique selon une table standard (disponible sur la page du cours).

22.5

Suggestion dun plan daction

Dans un premier temps, il faudra crire un programme capable de lancer mpg123 et dinteragir avec lui, en particulier pour rcuprer en permanence les informations de trame lue (@F) sans bloquer le dcodage, et pour grer les arrts intempestifs (chier inexistant..) du programme. Les commandes de ce programme dencapsulation seront prises galement en entre et sortie standard, avec une syntaxe que lon pourra choisir similaire celle de mpg123. Ensuite on choisira une politique simple pour dcider du prochain morceau jouer et on crira linterface. Une solution en texte est sufsante dans un premier 437

Chapitre 22. Projet de Jukebox temps, avec par exemple des listes de choix et des numros. Il faudra identier clairement ce niveau les communications et leur format entre les interfaces et le juke-box. On fera fonctionner lensemble avec plusieurs interfaces concurrentes. Pour cela, on pourra utiliser des tuyaux nomms pour faire communiquer les interfaces avec le serveur, en lanant tous les processus sur la mme machine. Les plus courageux feront de la communication par sockets, ce qui permettra de faire fonctionner les interfaces sur des machines diffrentes du serveur.
Amliorations possibles

Afcher la liste des morceaux grce aux informations extraites des ID3-tags (et pas seulement les noms des chiers). Ajouter la commande du volume, de la balance... Cest loccasion galement de chercher des politiques ce niveau (le plus simple est par exemple de donner priorit celui qui souhaite baisser la volume, ou faire une moyenne de chacune des interfaces...). Amliorer le systme de choix des morceaux (minimisation de linsatisfaction moyenne, avec un vote sur une liste de morceaux proposs par exemple). Crer une liste des morceaux prvus la diffusion et dynamique, rafrachie sur chaque interface lors des volutions. Une jolie petite interface en ncurses, en Tk ou autres (mais ne pas passer trop de temps faire du X11).

438

23
Administration distante

Lnonc de ce projet a pour but de vous guider pas pas dans la ralisation du projet. Les diffrents services qui doivent tre rendus par vos programmes peuvent tre facilement programms de manire incrmentale et vris au fur et mesure. Ce projet a pour but dintroduire la notion dadministration distance de plusieurs stations. Nous nous proposons de mettre en place un systme simple permettant diffrentes actions : consulter une liste de chiers, copier des chiers dun client un autre ou dun serveur central vers les clients. . . Ces commandes seront accessibles depuis un terminal client (appel Interface) et devront tre analyses puis relayes par un serveur dadministration (appel Administrateur) et enn tre appliquer sur les diffrents clients (appels Machine). Lintgralit du projet prendra place sur une seule machine et nous nous servirons des tuyaux pour simuler des connexions TCP entre les diffrents acteurs. Aucune contrainte dorganisation des diffrents lments nest donne. Toutefois le cahier des charges dcrit ci-aprs devra tre scrupuleusement respect. Le projet en version minimale est simple. Les ventuelles amliorations ne doivent tre abordes quaprs avoir nalis le projet dans sa version minimale. Si un projet est rendu dans une version amliore incomplte (ou non fonctionnelle) seules les parties de la version minimale seront prises en compte pour la correction et ltablissement de la note.

23.1

Position du problme

On dsire pouvoir administrer (dans un sens trs restreint) plusieurs machines partir dun serveur central. Chaque machine sera simule par un processus qui trouvera dans un chier de conguration les informations relatives son fonctionnement. De la mme faon le serveur central sera simul par un processus. An daccder au serveur central on utilisera un dernier processus qui simulera une interface de dialogue. On dsire pouvoir accomplir les actions suivantes partir de lInterface : 439

Chapitre 23. Administration distante

F IGURE 23.1 Architecture gnrale. LInterface envoie des commandes lAdministrateur qui ralise le routage et ventuellement le traitement de commandes plus complexes.

obtenir le contenu dun rpertoire dune Machine, copier un chier dune Machine vers toutes les autres. Ceci permet par exemple, partir dun chier de conguration modle /etc/ssh/ sshd.conf, de placer une conguration gnrique sur toutes Machines nouvellement cres. De nombreuses commandes sont possibles mais nous nous limiterons dans un premier temps celles dcrites ci-dessus.

23.2

Le processus Machine

Conguration

Une machine distante est tout simplement un processus. An de simuler une machine, ce processus gre un rpertoire qui sera reprsentatif du rpertoire racine dune machine relle. Ce rpertoire sera inscrit dans un chier de conguration dont le nom sera donn par la ligne de commande. Voici un exemple :
$> more config1.conf RACINE: /net/utilisateur/bcollin/IN201/projet2008/reptest1 ... $> more config2.conf RACINE: /net/utilisateur/bcollin/IN201/projet2008/reptest2 ... $> machine config1.conf ... $> machine config2.conf

440

23.3. Le processus Administrateur

...

La gure 23.2 donne une vue gnrale de lensemble des chiers de congurations. Le processus doit tre capable de connatre les chiers et les rpertoires prsents dans son rpertoire racine . cette n on utilisera les fonctions systmes dcrites dans laide mmoire situ la n de ce document. Ces fonctions sont les suivantes : DIRP *opendir(const char *pathname) struct dirent *readdir(DIRP *fd) int closedir(DIRP *fd)
Les commandes interprter

Le processus Machine doit pouvoir dialoguer avec le serveur dadministration. Il devra lire les commandes sur son entre standard (stdin) et crire le rsultat sur sa sortie standard (stdout). Le mcanisme de cration de tuyaux et de duplication vu en petite classe permettra de mettre en place le dialogue partir du processus dadministration. Le processus Machine doit pouvoir comprendre a minima les commandes suivantes : ls : le processus retourne la liste des chiers et des rpertoires lendroit o il se trouve. send nom_file : le processus renvoie les donnes contenues dans le chier
nom_file

write nom_file size data : le processus crit les donnes data dont la taille totale est de size octets dans le chier nom_file dans le rpertoire o il se trouve. Le dialogue doit pouvoir grer lenvoi ou la rception de donnes quelconques. Il est donc impratif dtablir un protocole de dialogue. Ce protocole sera aborde ultrieurement.

23.3

Le processus Administrateur

Ce processus joue un rle central daiguillage des commandes reues depuis linterface. Quand une commande est interprte, il interagit avec les diffrentes Machines. Ladministrateur reoit ses commandes sur son entre standard (stdin) et il envoie certains des rsultats quil recevra des Machines sur sa sortie standard.
Conguration

Le processus Administrateur possde un chier de conguration dans lequel il trouvera la liste des clients quil doit administrer ainsi que leur chier de conguration. Les informations contenues dans ce chier lui permettront ainsi de crer les diffrents processus Machines. Ce chier peut tre constitu de la manire suivante : 441

Chapitre 23. Administration distante


machine1 /net/utilisateur/bcollin/IN201/projet2008/config1.conf machine2 /net/utilisateur/bcollin/IN201/projet2008/config2.conf

Les chiers config1.conf et config2.conf sont ceux dtaills dans la partie prcdente. Le chier de conguration sera communiqu au processus sur sa ligne de commande :
$> ./administrateur maconf.test

F IGURE 23.2 La rpartition des chiers de conguration. Ladministrateur possde un chier de conguration qui lui permet de savoir quelles sont les machines lancer ainsi que lemplacement de leur chier de conguration. Chaque machine reoit ainsi sur sa ligne de commande le nom de son chier de conguration. Dans ce chier se trouve inscrit le chemin de son rpertoire racine .

Les commandes interprter

Ladministrateur jouant le rle dun chef dorchestre, il doit tre en mesure dinteragir avec les Machines mais aussi avec lInterface. Comme nous lavons voqu, lAdministrateur dialogue avec lInterface partir de son entre standard et de sa 442

23.3. Le processus Administrateur sortie standard. Il dialogue avec les Machines partir des tuyaux quil aura mis en place en crant les processus correspondants. Puisquil dialogue avec les Machines, lAdministrateur doit tre capable dinterprter les rsultats renvoys par les Machines : lire une liste de chiers et de rpertoires retourne par une Machine (rsultat de la commande ls), lire lensemble des donnes (quelconques mais de taille prvue) renvoyes par une Machine (rsultat de la commande send), envoyer un ensemble de donnes (quelconques mais de taille prvue) une Machine (commande write). Il doit surtout aiguiller les commandes quil reoit de linterface. Ceci sera fait en plaant ladresse de la machine avant la commande destine tre envoye. On pourra adopter le protocole suivant : machine1@ls pour demander lAdministrateur denvoyer la commande la machine 1. On veut surtout pouvoir tablir un mcanisme de broadcast dans lenvoi de chier, i.e. ne lire quune seule fois un chier distant pour lenvoyer plusieurs Machines. Cette commande venant de linterface, on convient quelle pourra prendre la forme suivante : machine1@send* nom_file. Cette commande peut sinterprter de la manire suivante : aller chercher le chier nom_file sur la machine1 et lenvoyer toutes les autres. Nous devons aussi pouvoir fermer les connexions aux diffrentes machines. Ceci pourra prendre par exemple la forme suivante : machine1@close. Enn nous devons pouvoir quitter lAdministrateur et terminer par le mme coup lInterface. On peut convenir de la commande suivante : quit. Nous avons donc en rsum les dialogues suivants : Dialogue Administrateur / Machine envoyer ls et lire le rsultat, envoyer send et lire le rsultat, envoyer write, envoyer close, Dialogue Administrateur / Interface machine@ls : envoyer la commande ls machine, lire le rsultat et le renvoyer lInterface, machine1@send* nom_file : envoyer la commande send machine1 pour obtenir le chier nom_file, lire le rsultat et le renvoyer machine2 par la commande write nom_file size data, puis la machine3,. . . machine@close : signier au processus machine1 quil doit se terminer et fermer proprement la connexion. quit : fermer les connexions aux diffrentes Machines puis fermer lInterface et lAdministrateur. 443

Chapitre 23. Administration distante

23.4

Linterface

Linterface est cre par lAdministrateur. Elle nous permet dinteragir avec lAdministrateur. Linterface lit les commandes sur son entre standard et afche les rsultats sur sa sortie standard. Les commandes saisies (par vos soins) sont donc celles nonces dans la description du dialogue entre lAdministrateur et lInterface. La lecture des commandes sera faite ligne ligne, de la mme manire que le shell. Linterface pourrait donc simplement se comporter de la faon suivante : proposer une invite , tel que % ou encore $, lire lentre standard et la transmettre lInterface. Nous pourrions donc avoir lafchage suivant :
Bienvenue dans linterface dadministration %machine1@ls test1.txt test2.txt Mon_dossier %machine2@ls Sub1 Sub2 %machine1@send* test1.txt ok %machine2@ls test1.text Sub1 Sub2 %machine2@send* unknown.txt error: file not found %

23.5

Les dialogues

tablir un protocole

Comme nous lavons vu, diffrents dialogues existent. Il y a dune part lenvoi des commandes mais aussi la rception des donnes issues de lexcution dune commande ainsi que le traitement des erreurs (chier inexistant ou machine inexistante). On doit donc imprativement tablir un protocole de dialogue an de diffrencier ce qui est une rponse correcte dune erreur mais aussi prvoir la lecture de donnes quelconques. La transmission des commandes nest rien de plus que lenvoi dun ensemble doctets ! Il faut donc encapsuler ces octets dans quelque chose qui nous permettra de : savoir combien de donnes il convient de lire, si ce que nous devons lire est un rsultat ou si il sagit dune erreur. On peut convenir dadopter le protocole qui va suivre. Chaque envoi est constitu de plusieurs champs permettant de dcrire sil sagit dune rponse, dune erreur, quelle est la taille de ce quil faut lire et les donnes elles-mmes. Un paquet aura donc la forme suivante : 444

23.6. Consignes et cahier des charges


MXXXXXXXXdata.............

Le premier octet de lenvoi contient le mode : 0 sil sagit dune rponse, 2 sil sagit dun envoi et 1 sil sagit dune erreur. Les 8 octets suivants contiennent la taille des donnes lire. Les donnes prendront place partir du 10ime octet. Voyons quelques exemples. envoi de la commande ls :
2 2ls

soit 2 car il sagit dun envoi, 7 espaces suivies du caractre 2 pour dire que les donnes transmises tiennent sur 2 octets, et enn les 2 caractres de la commandes ls. rception dune liste de chiers et de rpertoires, par exemple file1, fic3 et Rep34 :
0 16file1\nfic3\nRep34

Il sagit dune rponse (0), nous devrons lire 16 caractres et ces caractres sont le1\nc3\nRep34 rception dune erreur car le chier unknown.txt nexiste pas.
1 27fichier absent: unknown.txt

soit 1 car il sagit dune erreur, 6 espaces suivies des caractres 27 car nous devons lire les 27 caractres de la rponse. rception du contenu dun chier en retour de la commande send. On supposera que ce chier contient les lettres de lalphabet. Attention ce chier se termine par un retour chariot !
0 27abcdefghijklmnopqrstuvwxyz\n

soit 0 car il sagit dune rponse, 6 espaces suivies des caractres 27 car nous devons lire les 27 caractres de la rponse et enn les donnes contenues dans le chier.
Entres / sorties des diffrents processus

On rappelle que pour tester vos diffrents processus mais aussi car cela est impos, il est impratif que les changes se fassent tel que dcrit dans la gure 23.3.

23.6

Consignes et cahier des charges

Consignes

Une attention particulire sera prise pour soigner la prsentation et la qualit du travail 1 . cette n, chaque projet sera envoy en temps et en heure au MdC de petite classe du groupe auquel vous appartenez. Un projet doit contenir les lments suivants :
1. Si vous ne pouvez pas le faire bien, faites le beau !

445

Chapitre 23. Administration distante

F IGURE 23.3 Linterface saisit sur stdin ce que vous rentrez et afche les rsultats sur stdout . Une machine lit les commandes sur stdin et crit les rsultats sur stdout . Enn ladministrateur lit ses commandes sur stdin et retourne les rsultats sur stdout .

lintgralit des chiers sources (*.c et *.h). Chaque chier devrait (conditionnel nature dinjonction forte) contenir des commentaires amples et varis permettant de comprendre facilement chaque fonction ainsi que les structures que vous jugerez bon de dnir. un chier Makefile permettant de compiler lintgralit des programmes par la commande make. lintgralit des chiers de conguration ncessaires au fonctionnement de vos programmes, un document au format PDF uniquement dcrivant votre projet. Ce document doit contenir une description de vos programmes, des ux qui circulent entre les processus, des exemples de fonctionnement, un exemple de ligne de commandes permettant dexcuter votre projet, ainsi que les points forts et faibles de votre projet.
Cahier des charges

Le projet minimal doit faire fonctionner au moins trois clients, un administrateur et une interface. Ceci signie quau total, en cours dexcution, le projet minimal comprend 5 programmes communiquant entre eux par des tuyaux (on nutilisera pas de tuyau nomm). Dans lidal, le chier de conguration de lAdministrateur doit permettre de dnir autant de machines que lon souhaite. La compilation des chiers sources ne doit donner ni erreur (rdhibitoire) ni avertissement en utilisant loption -Wall. 446

23.7. Amliorations possibles Les projets seront envoys par mail aux MdC. ce titre, lintgralit des documents ncessaires sera place dans une archive tar. On ne devra pas trouver dans cette archive des chiers temporaires (*~ ou *.o). Cette archive devra intgrer un rpertoire portant le nom des deux lves du binme :
% mkdir lemeilleur_leplusbeaux % cp -R tous_mon_projet/* lemeilleur_leplusbeaux/ %tar cvzf projet.tgz lemeilleur_leplusbeau/

Ainsi quand le MdC extrait la pice jointe, soit projet.tgz et dcompresse cette archive, il doit voir apparatre la structure suivante (il sagit dun exemple) :
% tar xvzf projet.tgz lemeilleur_leplusbeau/ lemeilleur_leplusbeau/source/ lemeilleur_leplusbeau/source/admin.c lemeilleur_leplusbeau/source/admin.h lemeilleur_leplusbeau/source/client.c lemeilleur_leplusbeau/source/client.h lemeilleur_leplusbeau/source/ihm.c lemeilleur_leplusbeau/source/ihm.h lemeilleur_leplusbeau/source/Makefile lemeilleur_leplusbeau/config/conf_adm lemeilleur_leplusbeau/config/conf_cl1 lemeilleur_leplusbeau/config/conf_cl2 lemeilleur_leplusbeau/config/conf_cl3 lemeilleur_leplusbeau/doc/README lemeilleur_leplusbeau/doc/projet.pdf

Veillez remplacer lemeilleur et leplusbeaux par vos noms. Ce ne sera pas vos futurs clients dorganiser les documents que vous leurs enverrez.

23.7

Amliorations possibles

Attention avant de vous lancer dans dventuelles amliorations, veillez tre en possession dun projet fonctionnel. La premire amlioration consiste naturellement tendre le jeu de commandes disponibles depuis linterface. Une deuxime amlioration consisterait permettre la navigation dans des sous rpertoires des diffrents clients. Une autre amlioration serait de protger certains chiers an dinterdire leur accs distant. Un chier .praccess situ la racine de chaque rpertoire pourrait contenir la liste des chiers accessibles (politique du refus par dfaut). Une amlioration de grande envergure serait de distribuer ce projet sur plusieurs machines en utilisant des sockets. 447

Chapitre 23. Administration distante

23.8

Aide mmoire

Lecture des entres dun catalogue

DIRP *opendir(const char *pathname) : cette fonction permet douvrir le rpertoire pathname et dassocier un descripteur de rpertoire. Elle agit sur un rpertoire un peu de la mme faon que la fonction open() agit avec les chiers. On peut alors utiliser le descripteur retourn pour lire les diffrentes entres prsentes dans le rpertoire. struct dirent *readdir(DIRP *fd) : cette fonction lit lentre courante et pointe sur la suivante. Si il ny plus dentre, la fonction retourne NULL. Les donnes lues sont places dans une structure dirent. Deux champs de cette structure nous intressent : d_type : ce champ contient le type de lentre, i.e. sil sagit dun rpertoire ou dun chier par exemple. d_name : ce champ contient le nom de lentre, i.e. le nom du chier ou du rpertoire. int closedir(DIRP *fd) : cette fonction ferme le descripteur de rpertoire. Un rpertoire est donc vu comme un chier contenant diffrentes entres qui seront dans notre cas : dautres rpertoires (type DT_DIR) des chiers (type DT_REG) Lextrait suivant donne un exemple de lutilisation de ces fonctions pour lister le contenu dun rpertoire. La fonction contenu_repertoire prend en arguments : char *rootdir : une chane de caractres donnant le chemin absolu du rpertoire dont on veut obtenir le contenu, int *length : un pointeur vers un entier (cet entier doit exister) qui contiendra la longueur de la chane alloue par la fonction pour retourner le contenu du rpertoire. Elle renvoie une chane de caractres dont la longueur est crite dans length, cette chane tant alloue par la fonction.
char *contenu_repertoire(char *rootdir, int *length) { DIR *dirp; struct dirent *nd; char *retval=NULL; if (rootdir == NULL || length == NULL) return NULL; *length = 0; if (rootdir[0] != / ) return NULL; if (strstr(rootdir,"..") != NULL) return NULL; if ((dirp = opendir(rootdir)) == NULL) { return NULL; } while((nd = readdir(dirp)) != NULL) { if (nd->d_type == DT_REG || nd->d_type == DT_DIR) { if (strncmp(nd->d_name, ".", 1) && strncmp(nd->d_name, "..", 2)) {

448

23.8. Aide mmoire

retval = realloc(retval,(retval==NULL?0:strlen(retval))+ strlen(nd->d_name)+2); strncat(retval,nd->d_name,NAME_MAX); strcat(retval,"\n"); } } } closedir(dirp); *length = strlen(retval) + 1; return retval; }

Ecriture dun paquet de donnes

La fonction qui suit permet denvoyer des donnes sur un ux, en organisant les donnes au sein dun paquet. La fonction prend en arguments : FILE *flux : le descripteur de chier vers lequel raliser lcriture, int mode : le mode denvoi dont il est question, i.e. une rponse, un envoi ou une erreur, int size : le nombre dlments contenus dans le tableau data, unsigned char *data : ladresse du tableau de donnes La fonction retourne 0 quand lcriture a eu lieu, et un entier non nul en cas derreur.
int write_paquet(FILE *flux, int mode, int size, unsigned char *data) { unsigned char entete[10]; size_t twri; /* Verification flux et donnees */ if (flux == NULL || data == NULL) return -1; /* Verification du mode */ if (mode < 0 || mode > 2) return -2; /* Verification de la taille */ if (size <= 0) return -3; /* Creer lentete */ snprintf((char *)entete,10,"%c%8d",mode+0,size); entete[9] = \0; if ((twri = fwrite((void *)entete,1,9,flux)) != 9) return -4; if ((twri = fwrite((void *)data,size,1,flux)) != 1) return -5; fflush(flux); return 0; }

Lecture dun paquet de donnes

Le court extrait qui suit est un exemple de fonction permettant de lire, en lanalysant, un paquet sur un ux. La fonction prend comme arguments : FILE *flux : le descripteur de chier partir duquel raliser la lecture, int *size : ladresse dun entier (qui doit exister) dans lequel la fonction placera le nombre doctets occups par les donnes, int *type : ladresse dun entier (qui doit exister) dans lequel la fonction placera le type denvoi (0,1 ou 2). 449

Chapitre 23. Administration distante La fonction retourne ladresse dun tableau de caractres allou et contenant les donnes. Ce tableau possde une taille gale au nombre doctets de donnes augment de un. Le dernier lment du tableau est le caractre nul \0. Le fait de placer ce caractre spcial permet de lire ce tableau de caractres comme une chane , et donc dutiliser des fonctions telles strncmp() ou encore sscanf().
unsigned char *read_paquet(FILE *flux, int *size, int *type) { unsigned char *data=NULL; unsigned char buf[9]; size_t tread; if (flux == NULL || size == NULL || type == NULL) { return NULL; } /* Lecture du mode*/ if ((tread = fread(buf,1,1,flux)) != 1) { return NULL; } if (buf[0] == 0 || buf[0] == 0) *type = 0; else if (buf[0] == 1 || buf[0] == 1) *type = 1; else if (buf[0] == 2 || buf[0] == 2) *type = 2; else return NULL; /* Lecture de la taille */ if ((tread = fread(buf,8,1,flux)) != 1) return NULL; buf[8] = \0; *size = atoi((char *)buf);if (*size <=0) return NULL; /* Lecture des donnees */ if ((data = malloc(*size+1)) == NULL) { *size = 0; return NULL; } if ((tread = fread(data,1,*size,flux)) != *size) { free(data); *size = 0; return NULL; } data[*size] = \0; return data; }

Lecture binaire dun chier

Les chiers transmettre doivent tre lus non pas ligne ligne mais de manire binaire. La fonction suivante permet de lire la totalit dun chier et de placer les donnes lues dans un tableau de caractres. Elle prend comme arguments : char *name : le chemin associ au chier int *size : ladresse dun entier (qui doit exister) dans lequel la fonction placera le nombre doctets occups par les donnes, La fonction retourne un tableau de caractres dont la taille est gale au nombre doctets lus augment de un. Le dernier lment du tableau est le caractre nul \0.
unsigned char *read_fichier(char *name, int *size) { FILE *flux; struct stat buf; unsigned char *data; size_t tread;

450

23.8. Aide mmoire

if (name == NULL || size == NULL) return NULL; if (stat(name,&buf)) return NULL; *size = (int)(buf.st_size); if (*size == 0) return NULL; if ((flux = fopen(name,"r")) == NULL) { *size = 0; return NULL; } if ((data = malloc(*size+1)) == NULL) { *size = 0; return NULL; } if ((tread = fread(data,1,*size,flux)) != *size) { free(data); *size = 0; return NULL; } fclose(flux); data[*size] = \0; return data; }

Sparation dune commande de lInterface

La fonction qui suit permet de sparer les diffrents champs dune commande de type machine@command. Attention, cette fonction modie la chane quon lui passe en entre. Elle prend pour arguments : char *cmd_in : la chane de caractres analyser qui sera modie, char **cmd_out : ladresse dun tableau de caractres qui pointera vers la sous-chane correspondant la commande, char **machine : ladresse dun tableau de caractres qui pointera vers la sous-chane de la machine. La fonction renvoie 0 si tout se passe bien et 1 dans le cas contraire.
#define MAX_STRING_LENGTH 256 int split_commande(char *cmd_in, char **cmd_out, char **machine) { int s; if (cmd_in == NULL || cmd_out == NULL || machine == NULL) return -1; if (strlen(cmd_in) >= MAX_STRING_LENGTH) return -1; *machine = cmd_in; s = 0; while(cmd_in[s] != @ && s<strlen(cmd_in)) s++; if (s==strlen(cmd_in)) return -1; cmd_in[s] = \0; *cmd_out = cmd_in + s + 1; return 0; } ... char cmd[256]; char *cmd_out=NULL, *machine=NULL; if (split_commande(cmd, &cmd_out, &machine)) { fprintf(stderr,"Erreur\n"); } ...

451

Chapitre 23. Administration distante


Sparation des arguments au sein dun paquet

On suppose avoir rcupr le contenu dun paquet et donc une chane de caractres reprsentant une commande. Nous traiterons ici de la commande write qui est constitue comme suit :
write nom_file size data

Nous devons donc sparer les arguments dune chane de la forme :


write test.txt 26 abcdefghijklmnopqrstuvwxyz!

Pour mmoire, le paquet intgrant cette chane serait de la forme suivante :


2 45write test.txt 26 abcdefghijklmnopqrstuvwxyz!

La fonction prend en arguments : unsigned char *data : les donnes issues de la lecture du paquet par la fonction read_paquet(), char *filename : ladresse dun tableau de caractres dont la longueur doit tre au moins gale une constante macro-dnie (comme on a pu le voir dans la fonction prcdente), int *datasize : ladresse dun entier (qui doit exister) dans lequel la fonction placera la taille du tableau de donnes quelle retournera. La fonction renvoie ladresse dun tableau doctets qui contiendra les donnes destines tre crites dans le chier filename.
unsigned char *split_command_write(unsigned char *data, char *filename, int *datasize) { int s,i,l; unsigned char *wdata=NULL; if (data == NULL || filename == NULL || datasize == NULL) { return NULL; } *datasize = -1; s = sscanf((char *)data,"write %s %d",filename,datasize); if (s !=2) { *datasize = 0; return NULL; } if (*datasize<=0) { *datasize = 0; return NULL; } if ((wdata = malloc(*datasize)) == NULL) { *datasize = 0; return NULL; } l = strlen((char *)data)-*datasize; for(i=0;i<*datasize;i++) { wdata[i] = data[l+i]; } return wdata;

452

23.8. Aide mmoire

} /* Exemple dutilisation */ FILE *fp; char filename[256]; int psize, ptype, datasize; pdata = read_paquet(stdin, &psize, &ptype); ... wdata = split_command_write(pdata, filename, &datasize); if ((fp = fopen(filename,"w+")) == NULL) { .... } if (fwrite((void *)wdata,datasize,1,fp) != 1) { .... } fclose(fp); free(wdata);

453

24
Parcours multi-agents dans un labyrinthe

Ce projet a pour but dintroduire la notion de systme multi-agents sur un exemple simple. Un systme multi-agents, dans lacceptation qui nous intresse, est une technique de rsolution de problme complexe qui permet dexplorer un ensemble de solutions (optimum local) beaucoup plus large que ne pourrait le faire un systme squentiel simple. Largement utilise en traitement dimages, cette technique permet par exemple de raliser des tches danalyse de scne en dcoupant le problme en sous-tches plus simples et en laissant le soin des programmes trs spcialiss (et donc relativement efcaces) de rsoudre ces mini problmes. On peut ainsi trouver un agent qui va estimer la nature du bruit prsent dans certaines parties de limage et qui laissera, en fonction du rsultat obtenu (gaussien, multiplicatif, additif), un autre (dautres) agent(s) procder au dbruitage. Cette population dagents doit naturellement possder le moyen de communiquer et surtout de sorganiser. Un des processus fondamentaux dun systme multi-agents est le contrleur qui a la charge de crer, de dialoguer et de tuer les agents. Nous allons mettre en uvre un systme multi-agents simple pour rsoudre la dcouverte dun chemin dans un labyrinthe. Une technique simple pour obtenir la solution est dutiliser un programme rcursif. chaque embranchement, un tel programme appelle la fonction de rsolution de chemin et si celle-ci dbouche sur une impasse, lappel prcdent poursuit sa marche dans lautre branche. Cette technique est tout fait correcte mais au lieu dutiliser un seul processus pour avancer dans le labyrinthe, nous allons demander un contrleur de grer une population dagents qui progresseront dans les diffrents chemins 1 .

1. Si lon effectue le parallle avec les parcours darbre, la technique rcursive est un parcours en profondeur dabord, la technique par agent un parcours en largeur dabord.

455

Chapitre 24. Parcours multi-agents dans un labyrinthe

24.1

Position du problme

Dnitions lies au labyrinthe

Un labyrinthe peut se prsenter sous diffrentes formes. Nous nutiliserons que des labyrinthes simples sans boucle et pour lesquels il nexiste quune seule solution. Les chemins prsents dans ces labyrinthe seront de largeur gale un. Toutefois, les curieux et les curieuses pourront lcher leurs agents dans des labyrinthes plus complexes. Nos labyrinthes seront des tableaux deux dimensions dans lesquels chaque case sera reprsente par le caractre 2 1 ou 0 selon quil sagit dun mur ou dune case libre. Par convention, et pour lintgralit du projet, nous conviendrons quune case 0 est une case libre, alors quune case de valeur 1 est un mur. La gure 24.1 donne un exemple de labyrinthe. Dans la suite de ce document, les symboles 0 , 1 et 2 dsignent les caractres 0, 1 et 2. 1 1 1 1 1 0 1 1 0 0 0 1 0 1 1 0 1 0 1 0 1 1 1 1 0 0 0 1 1 0 1 1 1 0 1 1 0 0 0 1 0 1 1 0 1 0 0 0 1 1 0 1 1 1 0 1 1 0 1 0 1 0 1 1 0 0 0 1 0 1 1 0 1 1 1 1 1

F IGURE 24.1 Une reprsentation sous forme d image dun labyrinthe. Les chemins sont reprsents en blanc. Le chier contient pour sa part les caractres 0 et 1.

Les labyrinthes utiliss seront dits 4-connexes . Les chemins dnis lintrieur de tels labyrinthes ne peuvent suivre que les quatre directions haut, bas, gauche et droite. Tout parcours diagonal est donc impossible. La gure 24.2 dtaille les diffrents chemins. Enn, pour nous concentrer sur la gestion du systme multi-agents, nous supposerons que lentre du labyrinthe se trouve sur le mur extrieur de gauche (mur ouest). Par souci de simplication, vous pourrez aussi supposer si cela vous arrange que la sortie se trouve sur le mur extrieur de droite (mur est).
La rpartition des tches

Le systme multi-agents est compos de trois entits diffrentes : le contrleur : il connat le labyrinthe et organise la vie des agents. Il a la charge de rpondre leurs demandes. Il obtiendra la solution du labyrinthe en remontant
2. On rappelle que dans le contexte du codage ASCII, un caractre est en fait reprsent dans la machine sous la forme dune valeur entire, ainsi le caractre 0 (zro) a la valeur 49 et le caractre 1 la valeur 50.

456

24.1. Position du problme

F IGURE 24.2 Les ches sur la gure de gauche reprsentent les chemins possibles pour certains embranchements. Toute traverse diagonale est interdite car elle brise la 4-connexit. Ces traverses diagonales interdites sont reprsentes sur la gure de droite.

le chemin suivi par lagent arriv sur la case de sortie. Il tient jour la progression des agents. Cest ce que reprsente la gure 24.8. lagent : il na du labyrinthe quune connaissance trs restreinte, savoir son environnement proche qui se rsume aux huit cases qui lentourent. Lagent sait aussi do il vient, i.e. la case o il tait au tour davant. Lagent ne peut pas rebrousser chemin, il doit toujours avancer. partir de son environnement il choisit le chemin quil suit, que celui-ci soit unique ou quil y ait plusieurs possibilits. Il dialogue avec le contrleur pour complter sa vision du labyrinthe et aussi pour donner son dplacement. linterface : elle a en charge de transmettre initialement au contrleur le labyrinthe et de recevoir de ce dernier les informations concernant la progression des diffrents agents. Cette interface doit de plus permettre un fonctionnement pas pas du systme. Elle dialogue donc avec le contrleur et ce dialogue bloquant doit permettre de synchroniser le contrleur et les agents. On pourrait trs bien utiliser les signaux pour raliser cette synchronisation, mais on prfre, dans le cadre de ce projet retenir une solution de lecture bloquante.
Les changes dans le systme

Les dialogues entre les agents et le contrleur sont tous bidirectionnels : lagent requiert des informations et il communique sa position, son intention de mourir ou une demande de cration lors de la dcouverte dun embranchement. Le contrleur envoie la nature des cases que les agents dcouvrent au fur et mesure de leurs progressions. An davoir un systme modulaire, nous devons ger les formats dchange qui seront utiliss. Voici les recommandations quil faudra suivre.
Dialogue Contrleur Interface

Linterface est le processus qui se chargera de lancer tous les autres. Linterface doit donc transmettre au processus contrleur le labyrinthe dans son intgralit. Cette 457

Chapitre 24. Parcours multi-agents dans un labyrinthe transmission ne sera pas faite sur la ligne de commande ni par un simple envoi dun nom de chier que le contrleur irait ensuite charger. Le labyrinthe, i.e. sa largeur, sa hauteur et les caractres qui le composent seront envoys via un tuyau sur lentre standard (stdin) du contrleur. Ainsi la premire tche du contrleur sera-t-elle de rcuprer cette structure. Cet envoi prendra donc la forme suivante :
largeur\n hauteur\n 111111111\n ..0011...\n .........\n 111111111\n

Lors dune avance pas pas, linterface envoie un message pour dbloquer le contrleur (nous verrons ceci plus en dtail dans le paragraphe li lorganisation). Ce message prendra la forme suivante : OK\n . Le contrleur qui tait auparavant bloqu par un appel de lecture dans un tuyau vide, va pouvoir ainsi passer ltape de gestion de ses ls. Le contrleur doit rendre compte linterface et donc envoyer, aprs chaque tape de gestion de ses agents, les identiants (typiquement le pid et un numro dordre de cration) de ses agents ainsi que leur position. Les positions sont numrotes de gauche droite pour les x et de haut en bas pour les y. Le numro dagent sera pris 0 pour lagent initial, et chaque nouvel agent prendra lentier immdiatement suprieur. La structure du message sera la suivante :
AGT pid_agent numero_agent x_agent y_agent\n

Pour signier que tous les agents ont t transmis, le contrleur signale linterface quil passe en mode dcoute des agents par le message RUN. On obtient donc une suite de messages de la forme :
AGT pid_agent_1 numero_agent_1 x_agent_1 y_agent_1\n AGT pid_agent_2 numero_agent_2 x_agent_2 y_agent_2\n RUN\n

Dialogue Agent Contrleur

Un agent, une fois quil connat son environnement 3 3 peut choisir quel chemin suivre. Il annonce alors au contrleur son choix tout en faisant une demande de nouvel environnement : pour un dplacement vers louest : HO pour un dplacement vers lest : HE pour un dplacement vers le sud : HS pour un dplacement vers le nord : HN 458

24.1. Position du problme

F IGURE 24.3 Les quatre demandes denvironnement. Lorsque lagent choisit de se dplacer, un tiers de son nouvel environnement lui fait dfaut. Selon son dplacement, il rclame donc au contrleur les trois cases qui lui manquent. Cette demande permet en outre au contrleur de connatre le chemin suivi par lagent.

Si plusieurs chemins sont possibles (lagent atteint une bifurcation ou un croisement), lagent annonce son choix et transmet au contrleur les autres chemins. La requte prend alors les formes suivantes : deux choix sont possibles : BXY avec X donnant la direction choisie par lagent, soit O, E, N ou S et Y la direction laisse. Le contrleur devra ragir en crant un nouvel agent qui prendra le chemin Y. trois choix sont possibles : CXYZ, X donnant la direction choisie par lagent et Y et Z les directions laisses sous le contrle du contrleur pour la cration de deux agents. Si un agent arrive dans une impasse, i.e. il na plus aucune solution pour avancer, il doit signaler son envie pressante de mourir. Le message quil envoie prend la forme suivante : T. Lagent qui reoit lenvironnement 222 a atteint la sortie (voir ci-dessous pour la dnition de lenvironnement). Il envoie alors au contrleur un message de russite. Ce message prend la forme suivante : F.
Dialogue Contrleur Agent

Le contrleur envoie aux agents lorsquils en font la demande lenvironnement que ces derniers doivent connatre. Cet environnement se limite aux trois cases qui jouxtent la nouvelle position comme le montre la gure 24.3. Attention, les trois cases sont envoyes dans le sens horaire. Ainsi pour une demande denvironnement est, sont envoyes les valeurs des cases nord-est, est puis sud-est. Pour une demande denvironnement ouest, sont envoyes les cases sud-ouest, ouest puis nord-ouest. Si une ou plusieurs des cases sortent du labyrinthe, le contrleur retourne la valeur 2 , dans les autres cas le contrleur retourne la valeur de la case, savoir 0 si la case est libre et 1 sil sagit dun mur. Un cas typique pourrait tre par exemple pour un 459

Chapitre 24. Parcours multi-agents dans un labyrinthe environnement est se prolongeant en ligne droite : 101. Pour la case de sortie par exemple, lenvironnement sera obligatoirement 222. Le message denvironnement prendra la forme suivante : Habc, o a, b et c prennent une des trois valeurs 0, 1 ou 2.

Le contrleur peut aussi envoyer des messages de terminaison aux agents qui vivent encore lorsque lun dentre eux a trouv la sortie. Ce message prendra la forme suivante : K.

La cration dun agent noccasionnera pas denvoi de message, lenvironnement 3 3 complet tant pass sur la ligne de commande de lagent. Ceci sera abord ultrieurement.

Rcapitulatif des messages

Ce paragraphe rsume les diffrents messages changs. Les messages (except lenvoi du labyrinthe qui est sur trois lignes) seront tous sous la forme dune ligne de texte termine par un \n 3 .

Agent vers Contrleur Dplacement (et demande denvironnement) ouest est nord sud
HO HE F HN HS

Contrleur vers Agent

Accs la case de sortie Arrive dans une impasse


T

ouest

Rponse denvironnement est nord sud

H??? H??? H??? H??? ? tant le caractre 0 , 1 ou 2

Envoi de la requte de terminaison


K

Arrive une bifurcation BOE BEO BNS ... Arrive un croisement CNES CONE CNOS ...

3. Ainsi vous pourrez tester les diffrentes briques (agent puis contrleur) en interagissant directement avec eux au clavier.

460

24.2. Lorganisation gnrale Interface Contrleur Envoi du labyrinthe (IC)


L\nH\n11..\n..1\n

Avance pas pas (IC)


OK

Arrt immdiat (IC)


FIN

Progression (CI)
AGT pid num x y

...
RUN

Solution (CI)
SOL x y

24.2

Lorganisation gnrale

La gure 24.4 donne une vue densemble de lorganisation des diffrents processus et des communications qui prennent place.

F IGURE 24.4 Les changes au sein du systme.

Processus

Comme le suggre son nom, le contrleur a une place centrale. Cest lui qui a la charge de crer les agents au fur et mesure du droulement de la recherche de solution et de communiquer les dplacements linterface, il joue donc le rle de chef dorchestre. Nanmoins, il sera cr en tant que ls du processus interface an que le processus interface puisse raliser ses afchages sur la console 4 Le processus interface recevra sur sa ligne de commande le nom du chier contenant le labyrinthe ainsi que la slection du droulement (mode pas pas ou mode
4. Cela pourra permettre galement de remplacer linterface simple que vous allez dvelopper par un quivalent graphique sans ncessiter dautre modication. Une telle interface ne fait pas partie de ce projet.

461

Chapitre 24. Parcours multi-agents dans un labyrinthe automatique). Il crera le processus contrleur en lui transmettant sur sa ligne de commande le type de mode choisi (pas pas ou automatique). Le processus contrleur devra imprativement rceptionner le labyrinthe sur son entre standard avant de crer le premier agent. La cration de lagent est aborde ci-aprs. Une fois lagent cr, et selon le mode de fonctionnement, le processus contrleur entamera deux boucles de gestion. La cration du premier agent ne rentre pas dans la boucle de gestion des agents. Gestion des agents 1 le contrleur lit les requtes des agents existants. 2 le contrleur rpond ces requtes et cre si besoin est de nouveaux agents. Il met jour la position des agents. Dialogue avec linterface 1 le contrleur envoie linterface le pid et la position des diffrents agents. Il signale linterface que la position du dernier agent a t envoye par le message RUN. 2 le contrleur lit la requte de linterface et prend les mesures qui simposent (uniquement en mode pas pas). Chaque agent sera un ls du contrleur. Lagent adopte toujours le mme schma de fonctionnement, savoir la rception de son environnement et de la direction de dmarrage puis une boucle de gestion organise comme suit : 1 lagent dcide son chemin, 2 lagent envoie sa requte au contrleur (il reste bloqu tant que le contrleur ne la lit pas), 3 lagent lit la rponse du contrleur puis il revient ltape 1. Les dialogues se font entre le contrleur et les agents comme le symbolise la gure 24.5. Lorsque le contrleur cre un agent, il lui fait parvenir sur la ligne de commande lensemble des paramtres dont il a besoin, savoir lenvironnement 3 3 complet et une direction de recherche 5 . Le programme agent aura donc une ligne de commande de la forme : > agent 111000101 E . Lenvironnement prendra la forme dune chane de 9 caractres. Le premier agent cr recevra par principe lenvironnement suivant : 221200221 ce qui reprsente lentre du labyrinthe situe comme nous lavons prcis sur le mur ouest. Lenvironnement sera constitu par les trois lignes lues de gauche droite et de bas en haut de la fentre 3 3 entourant lagent. La gure 24.6 donne un exemple de cration dagent et de la premire chane de caractres passe en ligne de commande. Pour simplier le dveloppement, il sera possible de transmettre un environnement diffrent du labyrinthe en interdisant (par le caractre 2 certaines cases an de contraindre le dmarrage de lagent dans une direction bien prcise. Dans ce cas, transmettre la direction sur la ligne de commande ne sera plus ncessaire.
5. Cette direction sera un caractre N=nord, E=est, S=sud, O=ouest

462

24.2. Lorganisation gnrale

F IGURE 24.5 Chaque agent reoit des informations sur son entre standard et met des requtes sur sa sortie standard.

Recherche du chemin par lagent

sa cration lagent a reu son environnement ainsi quune direction de recherche. Le labyrinthe tant construit en 4-connexit, un agent a au plus quatre possibilits de dplacement. La direction quil reoit sa cration lui bloque une de ces possibilits, celle qui le ferait revenir en arrire. Il lui reste donc trois possibilits : avancer dans la mme direction, i.e. aller tout droit tourner gauche tourner droite. La gure 24.7 rsume la technique de recherche de lagent.
Dialogue

Le dialogue entre le contrleur et un processus ls se fera au moyen dune paire de tuyaux. Les agents liront les messages sur leur entre standard (stdin) et enverront les requtes sur leur sortie standard (stdout). Il faudra donc veiller rediriger les entres/sorties des processus agents avant de les crer. Les agents doivent progresser et lire leur entre standard. Le fait de rendre la lecture bloquante permet de synchroniser tous les agents. Toute demande de lagent doit recevoir une rponse, lagent restant bloqu tant quil na pas cette rponse. Si le contrleur rpond par le message K, lagent doit se terminer immdiatement et le contrleur surveillera la bonne terminaison de son ls.
Afchage

Le contrleur envoie les diffrentes positions des agents linterface. Celle-ci afchera le labyrinthe entier avec la position des agents sur sa sortie standard (donc ici la console). An de rendre lafchage lisible, on se xe le format suivant : 463

Chapitre 24. Parcours multi-agents dans un labyrinthe

F IGURE 24.6 Demande de cration dagent. Sur la gure de gauche lagent se dplace vers lest. Il arrive donc dans la case o gure un cercle. En dcouvrant son environnement, il peut prendre deux directions, continuer vers lest ou descendre vers le sud. En choisissant de continuer vers lest il signale en plus au contrleur quil faut crer un agent partant de la case marque dun triangle (message envoy BES). Le contrleur transmet ce nouvel agent lenvironnement complet qui lentoure ainsi que la direction dans laquelle il doit essayer de progresser. Il bloque naturellement les cases que lagent ne peut pas prendre soit en raison dun chemin inverse, soit car dautres agents prendront ces directions lors dune cration multiple en raison dune bifurcation ou dun croisement. La chane passe sur la ligne de commande de ce nouvel agent sera donc celle reprsente sur la droite, soit 222101101. En plus de cette chane le contrleur transmet la direction sud soit le caractre S. Le nouvel agent, aprs rception de ces deux donnes na quune seule solution de dplacement, celle daller vers le sud.

une case mur sera afche par un caractre dise # une case libre sera afche par une espace un agent sera rendu par un chiffre reprsentant son numro. An de rester sur un caractre, les numros partir de 10 seront reprsents par les lettres de lalphabet minuscule.

Il faudra bien entendu sassurer que la console utilise une police dont tous les caractres font la mme largeur (police de type xed).

Le chemin solution

Si le contrleur note pour chaque dplacement des agents le chemin suivi avec un code dans une carte dtiquettes, il peut, en partant de lagent ayant atteint la case de sortie, revenir pas pas en arrire et obtenir ainsi lintgralit du chemin solution du labyrinthe. Cest ce que montre la gure 24.8. 464

24.3. Mise en uvre du projet

F IGURE 24.7 Lagent ne possde que 4 directions possibles pour se dplacer dans un environnement 4-connexe. Dans le cas de la gure (a), ayant reu la direction est il ne peut aller que vers le nord, lest ou le sud. Dans le cas de la gure (b), les seuls choix qui lui restent sont lest, le sud ou louest. Il doit ensuite, partir de ces trois directions en choisir une, choix qui dpend bien sr des cases (libres ou mures) du labyrinthe.

F IGURE 24.8 Lorsquun agent atteint la case de sortie, il suft dinverser le code de direction pour remonter le trajet jusqu la case de dpart. Sur le schma sont reprsents les codes inverss qui permettent de remonter de proche en proche. Ce schma sera construit et conserv par le contrleur au fur et mesure de la progression des agents dans le labyrinthe.

24.3

Mise en uvre du projet

Les fonctions disponibles

An de vous viter un travail fastidieux (mais pourtant votre porte !), certaines fonctions de lecture de message vous sont fournies titre gracieux et ofciel sous la forme dun chier den-tte fonction.h et dun chier objet fonction.o correspondant.
Structure Labyrinthe
struct _labyrinthe { int largeur,hauteur;

465

Chapitre 24. Parcours multi-agents dans un labyrinthe

char *carte; }; typedef struct _labyrinthe Labyrinthe;

Cette structure dnit un labyrinthe comme tant compos de 2 entiers et dun tableau monodimensionnel donnant la carte. On accde la valeur de la case de coordonnes x,y en utilisant par exemple :
#define CASE_LAB(L,x,y) (*((L)->carte+(y)*(L)->largeur+(x)))

Enumration Direction
enum _direction_lab {N=0,E=1,S=2,O=3}; typedef enum _direction_lab Direction;

char2dir()
Direction char2dir(char dir)

Cette fonction convertit le caractre de direction dir en entier, soit N 0, E 1, S 2 et O 3.Si la direction nest pas correcte, la fonction retourne 1.
lab_alloc()
Labyrinthe *lab_alloc(int l, int h)

Cette fonction alloue une structure Labyrinthe de largeur l et de hauteur h. Elle retourne ladresse de cette structure ou ladresse NULL en cas derreur.
lab_free()
Labyrinthe *lab_free(Labyrinthe *L)

Cette fonction libre la mmoire associe ladresse L. Cette fonction retourne toujours une adresse NULL.
lab_load()
Labyrinthe *lab_read(FILE *fp)

Cette fonction retourne ladresse dune structure Labyrinthe dment alloue et contenant le labyrinthe (ensemble de case de valeur 0 ou 1 lu dans le ux fp. En cas dchec la fonction retourne une adresse NULL. Le chier est organis comme suit : 466

24.3. Mise en uvre du projet


Largeur\n Hauteur\n 111111111111\n 011001110011\n 011001110011\n ..... 111111111111\n lab_write()
int lab_write(FILE *fp, Labyrinthe *L)

Cette fonction permet linterface dcrire dans le ux fp le labyrinthe L. Cette fonction est compatible avec la lecture de la fonction prcdente.
agent_write_mesg()
int agent_write_mesg(char *mesg, FILE *fp)

Cette fonction se contente dcrit dans le ux fp le message mesg en lui rajoutant un retour chariot (\n) puis force lcriture du tampon associ fp. La fonction renvoie 0 si tout sest bien droul et un code non nul dans le cas contraire.
agent_read_mesg()
int agent_read_mesg(char *en, int dir, FILE *fp)

Cette fonction lit un message (en provenance du contrleur) dans le ux fp. Selon la nature du message, la fonction met jour le tableau de 9 caractres (minimum) env en lisant 3 caractres sur fp dans le bon sens grce la direction dir. Si le message du contrleur est de type H??? la fonction retourne 1 aprs avoir fait les mises jour si tout sest bien pass. Sinon elle retourne un code ngatif. Si le message du contrleur est de type K la fonction retourne immdiatement le code 0 (zro). Cette fonction est parfaitement compatible avec la fonction qui suit.
ctrl_write_env()
int ctrl_write_env(Labyrinthe *L, int x, int y, int dir, FILE *fp)

Cette fonction permet au contrleur dcrire dans le ux fp la lettre H suivie des trois cases (dans le sens horaire) qui compose lenvironnement dcouvrir dans la direction dir lorsque lon est au point de coordonnes x,y du labyrinthe L. Il sagit donc 467

Chapitre 24. Parcours multi-agents dans un labyrinthe des cases (x-1,y-1), (x,y-1) et (x+1,y-1) pour la direction nord, des cases (x+1,y-1), (x+1,y) et (x+1,y+1) pour la direction est, des cases (x+1,y+1), (x,y+1) et (x-1,y+1) pour la direction sud, des cases (x-1,y+1), (x-1,y) et (x-1,y-1) pour la direction ouest,

Livrables et modalits

Le projet doit tre rendu avant lheure qui sera indique sur le site consacr lIN201
http://www.ensta.fr/ bcollin

Chaque jour de retard entrane une pnalit selon le barme qui suit : 1 jour 1 point 2 jours 2 points 3 jour 4 points 4 jours 8 points 5 jours 16 points 6 jours et au-del 20 points

Le projet doit tre envoy par courrier lectronique au MdC qui a encadr le groupe dans lequel vous vous trouvez. Cet envoi prendra la forme dune archive au format tar laquelle sera compose des lments suivants : Les programmes sources et en-ttes (.c et .h) amplement comments, un chier Makefile permettant de compiler vos chiers et dobtenir les trois excutables (le contrleur, linterface et lagent), un chier LISEZMOI dcrivant les chiers qui composent larchive et dcrivant la procdure dinstallation (quelle commande pour la compilation, quelle commande pour excuter le projet,. . . ). Ce chier doit tre au format texte. un rapport dcrivant les diffrents programmes que vous aurez rdigs, les structures manipules, lorganisation globale, les communications, les forces et faiblesses de votre projet, les diffrents tests que lutilisateur pourra mettre en uvre pour effectuer les vrications de bon fonctionnement et des illustrations pouvant claicir vos propos. Ce rapport devra imprativement tre remis dans un format lisible, savoir PDF, HTML ou format texte seul. On rappelle que ltape de compilation est importante et quil serait souhaitable que vos programmes puissent compiler lorsque les options -Wall -ansi -pedantic sont retenues. Il sera peut-tre utile dutiliser la dnition
#define _GNU_SOURCE

pour obtenir certaines dnitions de prototypes des fonctions de la bibliothque standard.


Dveloppements subsidiaires

utilisation de la fonction systme select() pour raliser les coutes sur les diffrents tuyaux mis en uvre an de pouvoir rajouter un temps dattente 468

24.4. Conseils en vrac alatoire diffrent dans chaque agent et de vrier que les agents russissent avancer (en mode automatique) sans se proccuper de leurs congnres ; gnralisation du systme de base pour grer des labyrinthes complexes base de boucles et chemin de largeur suprieure 1.

24.4

Conseils en vrac

Quelques conseils qui survivent danne en anne. . . Ne vous jetez pas dans le code, prenez le temps de rchir et dorganiser votre dveloppement. Ne vous lancez pas dans les dveloppements subsidiaires avant davoir ralis un projet de base en tat de marche ! vitez les programmes dont lexcutable sappelle test, ls, rm ! Chaque composant de ce projet peut tre dvelopp indpendamment des autres, cest lintrt de fonctionner par duplication de lentre et de la sortie standard. Mez-vous de la ressemblance troublante en le symbole O (la lettre O) et 0 (le chiffre 0). Faites de petites fonctions ne sachant pas faire grande chose, mais le faisant bien, plutt quune norme fonction capable de tout mais fonctionnant la manire dun clignotant (un coup a marche, un coup a marche pas !). Ne pensez pas avoir trouv une erreur dans malloc ou free, faites tout dabord votre mea culpa avant lhabeas corpus. Ne pensez pas que lon travaille mieux et plus vite en situation de stress intense, i.e. la veille du jour de remise du projet, dautant que ce jour l tata Hortense va srement vous tlphoner pour prendre de vos nouvelles. . .

469

25
Course de voitures multi-threads

Rsum Ce projet a pour but de dcouvrir la programmation par threads au travers de limplmentation dune course de voitures. lissue de votre travail vous devrez rendre un ensemble de chiers sources ainsi quun rapport.

25.1

Prsentation

Gnralits

La programmation multi-threads offre la possibilit dutiliser efcacement les architectures multi-processeurs ou multi-curs. Trs souvent, dans une programmation classique , les diffrents programmes qui changent des informations (au moyen de pipe ou de socket) sont synchroniss par des mcanismes de lecture et dcriture bloquants. Au sein des diffrents threads issus dun processus, et comme cela a t vu en cours, laccs la mmoire virtuelle du processus est intgral (tout comme les accs aux descripteurs de chiers). Il parat donc assez naturel de se passer dune communication externe et de faire en sorte que chaque thread crive les diffrentes informations ncessaires au droulement du programme dans une sorte de tableau noir partag par tous (principe du black board dans un systme multi-agents). Mais il devient impratif de protger ces accs concurrents la mmoire puisque nous ne disposerons pas (nous ne souhaitons pas disposer !) de la synchronisation par lecture/criture. Nous allons donc mettre en place une course de voitures dans laquelle chaque voiture sera anime par un thread tandis quun gestionnaire central aura la tche dafcher les positions de chaque voiture et deffectuer certains contrles. An de garantir la portabilit de notre code source, nous utiliserons les threads POSIX (la bibliothque libpthread.so) et nous veillerons ce que nos programmes compilent sans avertissement avec la directive -posix double des directives -ansi, -pedantic et -Wall. Les annexes donnent, ce sujet, quelques complments dinformations ncessaires une utilisation sous Linux 1 .
1. Vous pouvez programmer votre projet chez vous sur votre ordinateur. Cependant votre valuation sera ralise sur les ordinateurs de lE NSTA ParisTech et eux seuls.

471

Chapitre 25. Course de voitures multi-threads


Vue densemble

La course se droule sur une piste en boucle. Chaque voiture est en fait un thread dont le but est davancer sur la piste. Un Tv (les threads grant les voitures seront dnomms ainsi) regarde si la piste devant lui est libre an de gagner une nouvelle position. Il garde en mmoire sa nouvelle position et accde la piste pour effacer son numro de son ancienne position et sinscrire dans sa nouvelle position. Selon les impratifs de course dicts par loprateur (en fait lutilisateur au travers dune interface), les Tv pourront raliser dautres actions. Ces dernires seront listes plus loin. La vue densemble de la course sera ralise par un autre thread, appel le To . Ce dernier contrle le droulement de la course et interagit avec lutilisateur par le biais dune interface. Le contrle de la course signie que ce thread va rguler lavance des voitures. De plus, au travers de linterface, lutilisateur peut demander le dpart de la course, la mise en pause de la course, larrt de la course,. . . Il peut naturellement demander lafchage des diffrentes positions. On distingue donc : un thread par voiture (nomm Tv ), un thread pour le contrle (nomm To ), le thread principal, dont le rle est simplement de crer le To puis dattendre sa mort.
Structures gnrales

On peut distinguer plusieurs structures informatiques, mais nous ne rentrerons dans les dtails que de deux dentre elles. Lune permet de conserver les informations relatives aux voitures (voiture) et lautre permet dobtenir une vision globale de la course (course).
Loprateur

La course de voitures est organise sur une piste en boucle qui sera en fait un simple tableau deux dimensions 2 de largeur et de longueur xes par la ligne de commande. Chaque case de ce tableau contiendra : le nombre 1 : la case ne contient aucune voiture et la piste est donc considre comme libre cet endroit, le nombre 2 : la case contient une voiture accidente, la piste est donc occupe cet endroit, un numro compris entre 0 et 255 : il sagit du numro de la voiture occupant cette position. On convient de limiter le nombre maximum de voitures 256.
2. Lannexe donne aussi un exemple de tableau une dimension auquel on accde par deux indices.

472

25.1. Prsentation La piste est une donne qui sera partage en lecture et en criture entre le To et les
Tv :

le To accde la piste en lecture pour pouvoir raliser un afchage sur la demande de loprateur. les Tv accdent la piste en lecture pour voir si les cases quils convoitent sont libres mais surtout en criture pour effacer leur ancienne position et y inscrire la nouvelle. Le To accde de plus une structure de course lui permettant de dmarrer, mettre en pause, arrter la course. . . Cette structure nest accde quen lecture par les Tv qui peuvent ainsi prendre connaissance de ce quils doivent faire. On peut donc voir la course comme une structure informatique qui pourrait contenir les champs suivants, parmi lesquels on retrouve la piste (ce qui suit est donn titre dexemple) :
struct course { int etat_course; /* pause, start, stop*/ int largeur_piste; int longueur_piste; int *piste; int nb_voiture; pthread_t *thread_ident; ... };

Le To pourra tre constitu dune phase dinitialisation (cration de la piste et des voitures connues, . . . ), dune boucle innie qui rgle la course et interagit avec lutilisateur et dune phase de terminaison. Le programme principal ne doit pas commander larrt par un simple Ctrl-C ! On devra normalement se servir de la fonction select() prsente dans les annexes de manire grer les entres utilisateur sur stdin sans pour autant bloquer le droulement de la course. Le To rgule la circulation. Il ne doit pas permettre une voiture davancer de plus dune case par seconde, mais plusieurs voitures doivent pouvoir avancer toutes les secondes ! La temporisation doit tre faite au sein du To par lutilisation de la fonction nanosleep() dont on trouvera un exemple dutilisation en annexe. Il est impratif que la consommation de CPU soit aussi minimaliste que possible. Les Tv ne doivent pas se servir des fonctions de sommeil telles que nanosleep(), usleep() ou sleep(). Le To doit permettre de saisir a minima les commandes suivantes : ajout : permet dajouter une voiture dans la course. Si la course est dj dmarre, la voiture entre dans sa boucle de circulation immdiatement. Sinon, comme les autres, elle attend la condition de dmarrage. depart : permet de signaler le dpart de la course, les Tv peuvent donc entrer dans leur boucle innie de circulation. pause : permet de signaler la mise en pause de la course, les Tv voiture sont alors bloqus en attendant la reprise. voir : permet de voir loccupation de la piste par les diffrentes voitures. Lafchage devra affecter une lettre de lalphabet chaque voiture (limitant par l 473

Chapitre 25. Course de voitures multi-threads mme le nombre de voiture aux alentours de 52 !) et reprsenter les cases vides de la piste par une espace. fauxdepart : les voitures doivent sarrter et se repositionner dans la conguration initiale. La course ne reprendra qu lissue du signal de dpart. Les voitures arrives en cours de course seront limines. stop : permet darrter la course en signiant aux diffrents threads quils doivent se terminer. Chaque thread afchera en se terminant la distance parcourue par la voiture quil reprsente. On veillera ce que les afchages ninterfrent pas les uns avec les autres 3 .
Les voitures

Un Tv doit conserver son numro (ce qui distingue les voitures les unes des autres et permettra un afchage agrable) sa position ainsi que son tat (nous introduirons la notion daccidents). Nous pourrons aussi ajouter certaines informations telles que, la distance parcourue, le nombre daccs la structure de course, etc. Ces donnes sont intrinsques chaque Tv et nont donc pas vocation tre partages. Elle pourront (devront !) tre alloues au sein de chaque Tv . Nous pouvons donc dcrire cette structure par quelque chose comme ce qui suit (encore une fois, cette structure est donne titre dexemple) :
struct voiture { int numero; int etat; int x,y; int round; float distance; ... };

Dautres lments doivent probablement gurer dans cette structure, mais nous vous laissons le soin de les dcouvrir dans votre laboration du projet. Lorsquune voiture peut avancer (son thread est actif et en phase de boucle de course), elle doit avant tout veiller aux consignes de courses (par exemple la consigne faux dpart la remet sa position dorigine sur la grille de dpart comme nous lavons vu). Par contre un Tv ne doit pas utiliser dattente bloquante comme par exemple nanosleep(). Seuls les mcanismes de blocage par mutex sont autoriss au sein des Tv . Dans la boucle innie de parcours de piste dun Tv , et quand ce dernier est actif, un tirage alatoire est ralis an de simuler un accident. Selon le rsultat de ce tirage la voiture peut prendre un tat accident. Dans ce cas, elle ne pourra pas avancer pendant un certain temps (ce temps est mesur en secondes et dni par une macro dnition dans un chier den-ttes). Cette voiture bloque ainsi la progression des autres.
3. On rappelle quune criture, lorsquelle peut tre ralise de manire concurrente, doit imprativement tre protge par un mcanisme garantissant un seul accs la fois donc par lutilisation de mutex

474

25.1. Prsentation Si la voiture nest pas accidente, elle essaye davancer tout droit. Si cela nest pas possible elle essaye de doubler par la gauche et si vraiment cela nest pas possible elle essaye un dpassement illgal par la droite ! Si les trois cases situes sa droite sont toutes occupes, la voiture ne peut pas avancer et elle reste donc sa place. Lorsquelle arrive en bout de piste, la voiture repart du dbut de piste (oprateur modulo). Chaque Tv doit inscrire dans un chier commun tous les Tv (il conviendra de bien grer les accs concurrents ce chier) le numro du thread ainsi que linstant daccs au chier (donn en milliseconde partir du dmarrage du programme principal). Ce chier de log sera ouvert en criture par le To et son descripteur sera pass en paramtre aux Tv . Le To aura la charge de fermer le chier une fois les Tv termins. On peut se servir de lexemple donn dans lannexe pour accder aux structures de temps. On souhaite donc trouver dans ce chier de log un ensemble de lignes constitues comme suit :

#temps en millisecondes et numero du thread 4567 2 4768 1 4769 3 4769 0 5856 3 5856 0 5861 1 5862 2 ...

Nous remarquons que les interventions des diffrents Tv sont bien espaces dau moins une seconde. Un programme comme gnuplot nous offre une possibilit dafchage rapide des ns de contrle :

moi@ici:>gnuplot set xlabel "Numero de thread" set ylabel "Temps (ms)" plot "thrv_log" title "" set term png set output "ma_courbe.png" plot "thrv_log" title "" exit moi@ici:>

475

Chapitre 25. Course de voitures multi-threads

25.2

Cahier des charges

Contraintes structurelles

Il est fondamental de respecter les contraintes structurelles nonces ci-aprs. Elles conditionnent en effet llaboration de votre programme et votre valuation portera en grande part sur ce respect.

Le projet est ralis en binme ou en trinme. Chaque lve dun binme doit appartenir au mme groupe de petites classes.

Un Tv ne peut pas avancer de plus dune case par seconde. Il est interdit dutiliser une fonction de temporisation dans les Tv . Seul le To devra utiliser la temporisation. Une bonne organisation des blocages daccs doit permettre de satisfaire cette contrainte. Il est aussi possible dutiliser des tuyaux (fonction pipe()) et de faire en sorte que le To par le biais dcritures des instants stratgiques commande lactivit des Tv en attente de lecture sur ces tuyaux. Une prfrence sera toutefois donne lutilisation des fonctions de leve de condition telle que pthread_cond_broadcast(). 476

25.2. Cahier des charges

La fonction main() (M) cre le thread oprateur (O). Celui-ci cre son tour les threads voiture (V) (il peut en crer au dmarrage comme pendant la course) puis il attend la terminaison des diffrents Tv . Le thread principal attend quant lui la terminaison du To . Le programme principal devra crer un To . Le To devra crer un Tv par voiture. Le nombre de voitures et les dimensions de la piste seront donns en ligne de commande. On peut convenir, par exemple, que pour lancer une course de 10 voitures sur une piste de longueur 80 et de largeur 5, on aboutisse la ligne de commande suivante : ./course 10 80 5. Le To pourra toutefois, sur intervention de lutilisateur au travers de linterface de commandes, crer un nouveau Tv pendant la course (avant ou aprs le dmarrage). La course ne doit pas dmarrer tant que loprateur nen donne pas lordre et ne doit consommer que trs peu de CPU lors des diffrentes phases. Lutilisateur interagit avec le To par une interface minimaliste proposant de donner des ordres. Cette interface doit de plus tre capable dafcher la piste et son occupation. Une voiture accidente sera reprsente par le symbole * , les voitures en fonctionnement par une lettre. Lattente dune commande dans linterface ne doit en aucun cas tre bloquante pour la rgulation de course. Lutilisation de la fonction select() prsente dans les annexes est fortement conseille. Il est strictement dconseill de terminer le programme sans attendre la n des diffrents threads par lutilisation de pthread_join().
Programmation

Il est important de sparer dans plusieurs chiers les diffrents lments constituant le projet. On veillera rdiger aussi des chiers den-ttes. On veillera apporter un grand soin dans la rdaction des programmes, notamment en utilisant des commentaires. La compilation permettant dobtenir le chier excutable de votre projet devra utiliser un Makefile. Vous trouverez dans laide mmoire du C du poly des exemples de Makefile dont vous pourrez vous inspirer. Il est dconseill de croire que tout appel systme se droule correctement ! On veillera donc tester la valeur de retour de toutes fonctions systmes. On pourra alors 477

Chapitre 25. Course de voitures multi-threads utiliser la fonction perror() pour lafchage de lerreur 4 . Les diffrents chiers sources devront compiler sans avertissement en utilisant les options suivantes du compilateur gcc :
-ansi -posix -pedantic -Wall

Il ne faudra pas faire usage de variables globales, source deffets de bord souvent problmatiques. Si vous ne pouvez pas faire autrement, il faudra le justier de manire didactique dans votre rapport. En gnral, la recherche dune justication suft comprendre quil existe un moyen de se passer de cette variable globale !
Remise du projet

Votre projet se composera de lensemble des chiers sources (*.h,*.c et Makefile) ainsi que dun rapport. Le rapport doit contenir des explications concernant les diffrents choix que vous avez faits. Il doit contenir la description des diffrentes structures ainsi que lorganisation des fonctions que vous aurez programmes. Il doit aussi contenir un ensemble de graphiques permettant de dmontrer, partir de vos propres simulations, que vous respectez les contraintes de temps (un Tv nobtient la main quune fois par seconde). Vous devrez naturellement donner la mthode permettant de compiler votre programme ainsi que quelques exemples de lancement et de sorties observes. Le projet sera remis par courrier lectronique votre matre de petite classe, ladresse quil vous aura prcise. Ce courrier lectronique sera compos de la manire suivante : Sujet : Projet IN201 nom1,nom2 Pice jointe : une archive tar contenant exclusivement les chiers sources (.c,.h), le Makefile ainsi que le rapport au format PDF. Cette archive tar devra tre ralise de faon intgrer un rpertoire form par les deux noms du binme, soit :
$> mkdir jules_jim $> cp *.c *.h Makefile rapport.pdf jules_jim/ $> tar cvf projet_201.tar jules_jim/ .... $>

La date de remise de votre projet nest pas encore xe. Toutefois, un systme de pnalits de retard sera appliqu : 2 points pour 1 jour de retard, 4 points pour 2 jours de retard, 8 points pour 3 jours, 16 points pour 4 jours et plus. Dans une carrire dingnieur ou de dcideur, il est impratif, lorsque lon est dpendant de contraintes extrieures, de prvoir des solutions de contournement. Le prtexte fallacieux dune panne rseau / informatique / courrier lectronique / RATP / EDF ou dune crise nancire ne sera pas pris en compte pour justier un retard.
4. Lauteur de ce document na pas suivi cette recommandation dans les annexes prsentes, il a tort, mais il na pas faire le projet !

478

25.3. Annexes
Amliorations ventuelles

Nous vous proposons certaines amliorations. Toutefois, il est impratif davoir un projet fonctionnel avant denvisager dventuelles amliorations. En effet si vous ne parvenez pas faire aboutir un projet amlior , vous naurez rien prsenter, ce qui a des consquences sur votre note. Vous pouvez proposer dans votre rapport comment se droulerait une programmation dans laquelle chaque Tv serait remplac par un programme. Le To raliserait des appels fork() pour crer autant de ls que de voitures. Quelle mthode de communication pourriez-vous utiliser ? Vous pouvez gnrer des pistes avec obstacles et prvoir dans ce cas que les voitures puissent aller vers le bas ou le haut pour contourner les obstacles. Lutilisateur peut grer une des voitures et choisir comment cette dernire avance en fonction de sa vision de la course. Si lattente dune commande utilisateur est par exemple de deux secondes au dbut de la course, on peut aussi penser que ce temps diminue peu peu, laissant lutilisateur trs peu de temps pour rchir.

25.3

Annexes

La meilleure source de renseignements pour lutilisation correcte dune fonction de la bibliothque C est de loin la commande man :
moi@ici:> man pthread_cond_wait ... NAME pthread_cond_wait -- wait on a condition variable SYNOPSIS #include <pthread.h> int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); ... moi@ici:>

Les appels systme

Il serait trs judicieux que vos programmes utilisent au moins une fois les fonctions suivantes : pthread_create() : cration dun thread, pthread_join() : attente du retour dun thread, pthread_mutex_lock() : pose dun verrou, pthread_mutex_unlock() : dpose dun verrou, pthread_cond_wait() : attente bloquante dune condition, pthread_cond_signal() : signal de leve de condition (1 thread), pthread_cond_broadcast() : signal de leve de condition, (plusieurs threads), 479

Chapitre 25. Course de voitures multi-threads select() : surveillance de descripteurs de chier.


Piste 1D vs 2D

La piste, donne pour tre un tableau bidimensionnel est prsente dans une des structures comme un tableau monodimensionnel. Cela prsente un avantage, celui de rduire le mcanisme dallocation de la mmoire. Ensuite, une simple macro dnition permet de circuler dans ce tableau 1D comme dans un tableau 2D :
struct piste { int w,h; int *p; } #define CASE(P,X,Y) (*(((P)->p) + (Y)*((P)->w) + (X))) ... piste->p = malloc(largeur*longueur*sizeof(int)); CASE(piste,3,4) = 2; ...

Passage de paramtres un thread

Il est tout fait possible de dlivrer un certain nombre de paramtre un thread par lutilisation dune structure :
struct parametre { int numero; /* numero affecte*/ pthread_mutex_t mtx; /* mutex protegeant la condition */ pthread_cond_t cnd; /* condition */ int dummy; /* un entier pouvant servir */ } /* Fonction multi-threadee Cette fonction fait... Parametre: *param sert a passer le numero du thread Retour: la fonction retourne un pointeur NULL (pas de pthread_join avec attribut utilisable). */ void *thread_func(void *p) { struct parametre *lp; lp = (struct parametre *)p; if (lp->numero == 1) { ... } ... return NULL; } int main(int argc, char **argv) { ... struct parametre prm; prm.numero = 1; prm.mtx = PTHREAD_MUTEX_INITIALIZER;

480

25.3. Annexes
prm.cnd = PTHREAD_COND_INITIALIZER; prm.dummy = 26; ... pthread_create(&mon_thread, NULL, thread_func, (void*)(&prm)); ... }

Temporisation par sommeil

La fonction nanosleep() sutilise assez simplement. Cependant, en raison de lutilisation de diffrentes versions dOS, il convient de faire prcder les ventuelles inclusions den-ttes (tels que stdio.h, stdlib.h, . . . ) par les lignes qui suivent :
#define _BSD_SOURCE #define _POSIX_C_SOURCE 200112L #include <features.h> ... #include <time.h> ... /* Attente de 1,5 secondes */ treq.tv_sec = 1; treq.tv_nsec = 500000000; if (nanosleep(&treq,NULL) != 0) { perror("Impossible de dormir!"); ... }

Mesure du temps coul

Lexemple qui suit montre comment rclamer une attente de 1, 51 secondes et mesurer par lintermdiaire de la fonction gettimeofday() le nombre de millisecondes coules entre lappel nanosleep() et la sortie de veille.
int main(int argc, char **argv) { struct timeval tp_start; struct timeval tp_current; struct timespec request_sleep; request_sleep.tv_sec = 1; /* secondes */ request_sleep.tv_nsec = 510000000; /* nano-secondes */ gettimeofday(&tp_start,NULL); nanosleep(&request_sleep,NULL); gettimeofday(&tp_current,NULL); fprintf(stderr,"Temps ecoule: %d\n", (int)(((tp_current.tv_sec) - (tp_start.tv_sec)) * 1000 + ((tp_current.tv_usec) - (tp_start.tv_usec)) / 1000 )); exit(EXIT_SUCCESS); }

481

Chapitre 25. Course de voitures multi-threads


Surveillance de descripteurs

La fonction select() permet de surveiller des descripteurs de chiers sans pour autant tre bloquante. Si un descripteur est prt (en lecture ou en criture), la fonction retourne immdiatement et lon peut alors vrier lequel. Son utilisation est assez simple comme le montre lexemple ci aprs.

Listing 25.1 Utilisation de select()


#define _BSD_SOURCE #define _POSIX_C_SOURCE 200112L #include <features.h> #include #include #include #include <stdio.h> <stdlib.h> <string.h> <sys/select.h>

int main(int argc, char **argv) { fd_set surveille_input; struct timeval attente; char buffer[256]; int ret; fprintf(stdout,">"); while (1) { /*FD_ZERO vide lensemble des descripteurs surveilles*/ FD_ZERO(&surveille_input); /*FD_SET ajout le descripteur de numero 0, soit stdin*/ FD_SET(0, &surveille_input); attente.tv_sec = 0; attente.tv_usec = 500000; buffer[0] = \0; ret = select(1, &surveille_input,NULL,NULL,&attente); if (ret < 0) { perror("Probleme avec select:"); exit(EXIT_FAILURE); } if (ret == 0) { fprintf(stdout,"Soyez rapide!\n>"); } else if (ret == 1) { /*Ici FD_ISSET est inutile car on ne surveille quun*/ /* descripteur, donc si select() sort en etant > 0 */ /*cest que stdin contient quelque chose*/ if (FD_ISSET(0,&surveille_input)) { fgets(buffer,256,stdin); buffer[strlen(buffer) - 1] = \0; fprintf(stdout,"Vous etes bon (\"%s\")\n>",buffer); if (strncmp(buffer,"exit",strlen("exit")) == 0) { exit(EXIT_SUCCESS); } } } } exit(EXIT_FAILURE);

482

25.3. Annexes
Temporisation par attente de condition

La fonction pthread_cond_wait() prend comme paramtres un pointeur sur une condition (de type pthread_cond_t *) ainsi quun mutex (de type pthread_mutex *). Cette fonction commence par dverrouiller le mutex pass en paramtre puis rentre en attente de la condition. Le mutex sera verrouill de nouveau lorsque la fonction rceptionne le signal de condition, juste avant de sortir. Il est trs important que la fonction qui appelle pthread_cond_wait verrouille le mutex avant lappel. Le signal de condition est envoy par la fonction pthread_cond_signal() qui prend comme paramtre le pointeur sur la condition. Cette fonction ne rveille quun seul thread parmi ceux attendant la condition. Le signal peut aussi tre envoy tous les thread par une une autre fonction (cf. ci-aprs). Un exemple dutilisation pourrait tre le suivant : Listing 25.2 Signal de rveil
#include #include #include #include <pthread.h> <stdio.h> <stdlib.h> <unistd.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t condition = PTHREAD_COND_INITIALIZER; /* Fonction de calcul multi-threadee Cette fonction attend 2 signaux de conditions puis se termine. Parametre: *param ne sert pas, on peut passer NULL Retour: la fonction retourne un pointeur NULL (pas de pthread_join avec attribut utilisable). */ void *thread_func(void *param) { int k=2; fprintf(stderr,"Demarrage du thread\n"); while(k>0) { pthread_mutex_lock(&mutex); fprintf(stderr,"Attente\n"); pthread_cond_wait(&condition,&mutex); fprintf(stderr,"Travail\n"); pthread_mutex_unlock(&mutex); k--; } fprintf(stderr,"Fin du thread\n"); return NULL; } int main(int argc, char **argv) { pthread_t mon_thread; int k; fprintf(stderr,"######Creation du thread\n"); pthread_create(&mon_thread, NULL, thread_func, (void*)NULL); for(k=0;k<2;k++) { fprintf(stderr,"######Attente de 5s\n"); sleep(5); fprintf(stderr,"######Reveil du thread\n");

483

Chapitre 25. Course de voitures multi-threads


pthread_cond_signal(&condition); } pthread_join(mon_thread,NULL); fprintf(stderr,"######Fin\n"); exit(EXIT_SUCCESS); }

dont la sortie est :


moi@ici:> ./exemple ######Creation du thread Demarrage du thread Attente ######Attente de 5s ######Reveil du thread ######Attente de 5s Travail Attente ######Reveil du thread Travail Fin du thread ######Fin moi@ici:>

Voici un autre exemple avec cette fois lutilisation de la fonction


pthread_cond_broadcast()

ainsi quune mesure du temps dattente. Listing 25.3 Signal diffusion large
#define _BSD_SOURCE #define _POSIX_C_SOURCE 200112L #include <features.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/time.h> #include <pthread.h> pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t condition = PTHREAD_COND_INITIALIZER; pthread_mutex_t mutex_stderr = PTHREAD_MUTEX_INITIALIZER; /* Fonction de calcul multi-threadee Cette fonction attend 2 signaux de conditions puis se termine. Elle affiche ses temps dattente. Parametre: *param sert a passer le numero du thread comme parametre afin de differencier les affichages Retour: la fonction retourne un pointeur NULL (pas de pthread_join avec attribut utilisable). */ void *thread_func(void *param) { struct timeval tp_start;

484

25.3. Annexes
struct timeval tp_current; int *lparam = (int *)param; int k=2; int i,j; fprintf(stderr,"Demarrage du thread %d\n",*lparam); while(k>0) { pthread_mutex_lock(&mutex); gettimeofday(&tp_start,NULL); fprintf(stderr,"%d bloque\n",*lparam); pthread_cond_wait(&condition,&mutex); pthread_mutex_unlock(&mutex); gettimeofday(&tp_current,NULL); pthread_mutex_lock(&mutex_stderr); fprintf(stderr,"%d travaille (attente de %d)\n",*lparam, (int)(((tp_current.tv_sec) (tp_start.tv_sec)) * 1000 + ((tp_current.tv_usec) (tp_start.tv_usec)) / 1000 )); pthread_mutex_unlock(&mutex_stderr); k--; for(i=0;i<12000000;i++) j=random(); } pthread_mutex_lock(&mutex_stderr); fprintf(stderr,"Fin du thread %d\n",*lparam); pthread_mutex_unlock(&mutex_stderr); return NULL; } int main(int argc, char **argv) { pthread_t mon_thread[2]; int k; int thident[2]; thident[0] = 0; thident[1] = 1; fprintf(stderr,"######Creation des threads\n"); pthread_create(mon_thread+0, NULL, thread_func, (void*)(thident+0)); pthread_create(mon_thread+1, NULL, thread_func, (void*)(thident+1)); for(k=0;k<2;k++) { sleep(5); fprintf(stderr,"######Reveil du thread\n"); pthread_cond_broadcast(&condition); } pthread_join(mon_thread[0],NULL); pthread_join(mon_thread[1],NULL); fprintf(stderr,"######Fin\n"); exit(EXIT_SUCCESS); }

La sortie de ce programme donne quelque chose comme suit :


moi@ici:> ./wait_cond ######Creation des threads Demarrage du thread 0 Demarrage du thread 1 0 bloque 1 bloque ######Reveil du thread 0 travaille (attente de 5000) 1 travaille (attente de 5002)

485

Chapitre 25. Course de voitures multi-threads

0 bloque 1 bloque ######Reveil du thread 0 travaille (attente de 2278) 1 travaille (attente de 2285) Fin du thread 0 Fin du thread 1 ######Fin moi@ici:>

486

Quatrime partie Laide-mmoire du langage C

26
Aide-mmoire de langage C

Introduction
Ce document nest pas un manuel de programmation, ni un support de cours concernant le langage C. Il sagit simplement dun aide-mmoire qui devrait permettre tout lve ayant suivi le cours de C dcrire facilement des programmes. Il a t crit partir de lexcellent livre de rfrence de B. W. Kernighan et D. M. Ritchie intitul Le langage C, C ANSI. Le langage C dont il est question ici est celui dni par la norme ANSI ISO/IEC 9899:1990(E). Nanmoins, quelques ajouts seront faits, comme par exemple le type long long, car ils sont supports par la plupart des compilateurs. De plus la majorit de ces ajouts ont t intgrs dans la nouvelle norme ISO/IEC 9899:1999. Comme il est trs difcile de prsenter indpendamment les diffrents lments dun langage, il y aura parfois des rfrences des sections ultrieures dans le cours du texte. Cet aide-mmoire est un document de travail et il ne faut pas hsiter aller et venir de page en page. Pour une utilisation rgulire et efcace du langage, il ne saurait tre trop recommand de complter ce petit aide-mmoire par une lecture du livre de rfrence cit ci-dessus, en particulier la partie Annexe A : manuel de rfrence de la norme ANSI et Annexe B : La bibliothque standard, dont un rsum succinct est prsent la n de ce document. Le C a t dvelopp au dbut par Dennis Ritchie, puis Brian Kernighan, chez AT&T Bell Labs partir de 1972, pour une machine de lpoque, le PDP-11. Le but tait den faire un langage adapt pour crire un systme dexploitation portable. Le langage fut appel C car il hritait de fonctionnalits dun prcdent langage nomm B et tait inuenc par un autre langage nomm BCPL. Le C a servi immdiatement (en 1972-1974 par Ken Thompson et Dennis Ritchie) rimplanter le systme Unix (dont le dbut du dveloppement date de 1970). Les lois anti-trust empcheront AT&T de commercialiser Unix, et cest ainsi que le C et Unix se rpandront via les universits dans les annes 1980. Le C est un langage trs utilis du fait de sa relative facilit dutilisation, de son efcacit et de son adquation au dveloppement des systmes 489

Chapitre 26. Aide-mmoire de langage C dexploitations (grce en particulier larithmtique sur les pointeurs et au typage faible). On dit parfois avec humour que cest un langage qui combine llgance et la puissance de lassembleur avec la lisibilit et la facilit de maintenance de lassembleur ! Certaines des faiblesses du langage, en particulier des diffrences de plus en plus marques entre ses nombreuses implantations et la ncessit dune bote outils portable seront corriges par la norme ANSI de 1985. An dadapter le langage aux exigences de dveloppement de grande envergure, son successeur, restant autant que possible compatible au niveau source, sera le C++, dvelopp par Bjarne Stroustrup, avec une philosophie assez diffrente, mais en conservant la syntaxe.

490

26.1. lments de syntaxe

26.1

lments de syntaxe

Structure gnrale dun programme C

Pour respecter la tradition, voici le programme le plus simple crit en C : Listing 26.1 Le premier programme !
#include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { printf("Je vous salue, O maitre."); exit(EXIT_SUCCESS); }

Un programme C est constitu de : Directives du prprocesseur qui commencent par un # et dont nous reparlerons par la suite. Ici #include <stdio.h> permet de disposer des fonctions dentre-sortie standard STanDard Input/Output Header, en particulier de la fonction printf() qui permet lafchage lcran. Dclarations globales de types et de variables, ainsi que des prototypes de fonction, cest--dire des dclarations de fonctions o ne gurent que le nom de la fonction, les paramtres et le type de valeur de retour, mais sans le corps de la fonction. Il ny en a pas dans lexemple ci-dessus. Fonctions. Tous les sous-programmes en C sont des fonctions. Ce que lon appelle habituellement procdure (en Pascal par exemple) est ici une fonction qui ne retourne rien, ce qui se dit void en C. Le compilateur reconnat une fonction la prsence des parenthses qui suivent le nom de la fonction et lintrieur desquelles se trouve la liste des paramtres. Cette liste peut tre vide comme cest le cas ci-dessus pour main(). Une fonction particulire dont le nom est obligatoirement main qui est la fonction appele par le systme dexploitation lors du lancement du programme. Cest la seule fonction dans cet exemple.
Un exemple complet

Voici un exemple plus complet montrant la structure gnrale, avec une variable globale iglob visible de partout, les prototypes des fonctions (indispensable 1 pour la fonction somme() dans le cas prsent) et les fonctions. Ce programme calcule 5! dune faon un peu dtourne...
1. En fait, en labsence de prototype, et si le texte de la fonction na pas t rencontr jusqualors, le compilateur considre quelle retourne un int, ce qui fonctionne ici... mais nest pas recommand.

491

Chapitre 26. Aide-mmoire de langage C Listing 26.2 Calcul de factorielle


#include <stdio.h> /* variables globales */ int iglob; /* prototypes des fonctions */ int somme(int a, int b); int produit(int a, int b); /* fonctions */ int produit(int a, int b) { int sortie; if (a>1) { sortie = somme(produit(a-1, b) , b); } else { sortie = b; } return sortie; } int somme(int a, int b) { return a+b; } /* programme principal */ main() { int j; j=1; for (iglob=1;iglob<=5;iglob++) { /* appel de la fonction produit() */ j = produit(iglob, j); } printf("%d\n",j); }

On remarquera ds prsent quune fonction est forme dun type, du nom de la fonction suivi de la liste des paramtres entre parenthses puis dun bloc formant le corps de la fonction. On appelle bloc en C tout ensemble dlimit par des accolades {} et comportant des dclarations de variables (il peut ne pas y en avoir) suivies dexpressions. Il est tolr de ne pas faire gurer de type de retour pour une fonction (cest le cas du main() ici), mais cest une pratique fortement dconseille. 492

26.2. La compilation sous Unix Un prototype a la mme criture que la fonction, cela prs que le bloc du corps de la fonction est remplac par un point-virgule. Lorsquil sera fait rfrence des fonctions provenant dune bibliothque dans la suite de ce document, celles-ci seront prsentes par leur prototype. Ces prototypes permettent au compilateur de connatre les paramtres et la valeur de retour de la fonction avant mme de connatre son implantation. Comme on le verra par la suite, il est gnralement recommand de regrouper les prototypes de toutes les fonctions dans un ou des chiers spars du code source. Ainsi, on spare (au sens Ada) linterface de son implmentation, ce qui vite de replonger dans le code source pour trouver les arguments dune fonction et permet une vrication de cohrence par le compilateur.

26.2

La compilation sous Unix

Exemple lmentaire

Le compilateur C sous Unix fourni avec la machine sappelle gnralement cc. Nanmoins on utilise souvent gcc car il est prsent partout, il supporte la norme ANSI ainsi que quelques extensions bien pratiques, il est libre et trs efcace sur toutes les machines. Cest par ailleurs un des compilateurs C digne de ce nom disponibles sur les systmes dexploitation libres (Linux, FreeBSD, NetBSD). Il est important de mentionner deux autres compilateurs disponibles, Clang dvelopp partir de 2007 par Apple pour obtenir un compilateur supportant spciquement le C, le C++ et lObjective C (voir http://clang.llvm.org/ et PCC (Portable C Compiler) disponible pour les BSD (voir http://pcc.ludd.ltu.se/). Si ces deux derniers compilateurs afchent des performances nettement meilleures que gcc, ils ne supportent pas autant de langages et ce nest dailleurs pas leur but. La compilation se fait dans le cas le plus simple par :
Idefix> gcc <source.c> -o <executable>

o il faut bien entendu remplacer les chanes entre <> par les bons noms de chiers. Si des bibliothques sont ncessaires, par exemple la bibliothque mathmatique, on ajoute la ligne de commande prcdente -l<bibliotheque> (-lm pour la bibliothque mathmatique).
Options du compilateur gcc
gcc accepte de trs nombreuses options (voir la page de manuel man gcc). En voici quelques unes fort utiles : -g Compilation avec les informations utiles pour le dbogage (debug). Avec gcc on peut ainsi utiliser lexcellent dbogueur gdb (ou la version avec interface graphique xxgdb).

493

Chapitre 26. Aide-mmoire de langage C -ansi Force le compilateur tre conforme la norme ANSI. Les extensions comme les long long sont nanmoins acceptes. -pedantic Le compilateur refuse tout ce qui nest pas strictement conforme la norme ANSI. Ceci, combin avec -ansi permet de vrier quun programme est strictement conforme la norme et donc en thorie portable. -Wall Force lafchage de tous les messages dalerte lors de la compilation. -O Optimisation de la compilation. Il vaut mieux ne lutiliser que lorsque le programme fonctionne dj de manire correcte. Des optimisations encore plus pousses sont possibles avec -On, o n est un entier entre 1 et 3. Loptimisation conduisant parfois des dplacements dinstructions, il est dconseill dexcuter au dbogueur des programmes optimiss. -O0 Permet dempcher toute optimisation. -Os Optimisation pour la taille. Le compilateur essaye de produire lexcutable le plus petit possible, parfois aux dpens de la vitesse dexcution. -I<rep> Prcise le(s) rpertoire(s) o il faut chercher les chiers den-ttes (#include). -D<var=val> Permet de dnir des variables du prprocesseur. -DDEBUG=2 est quivalent mettre au dbut du source la ligne #define DEBUG 2. On peut galement crire -DMONOPTION qui est quivalent #define MONOPTION 2 . -S Le compilateur gnre uniquement le listing assembleur (et non du code objet ou un excutable), avec par dfaut le sufxe .s. Cest utile pour optimiser efcacement de trs petits morceaux de code critiques (ou par simple curiosit !). -M Permet dextraire des rgles de dpendance (envoyes sur la sortie standard) pour le programme make, de faon similaire makedepend (cf. page 498). -p Lexcutable gnr intgre des fonctions de mesure de temps pass, nombre dappels (proling), . . . Lors du lancement de cet excutable, un chier gmon.out est gnr, et les rsultats sont consultables ultrieurement avec : gprof <nom_de_lexcutable>. -static Demande au compilateur de produire un binaire qui sautosuft, cest-dire qui ne ncessite aucune bibliothque dynamique. Cette option nagit en fait que pour ldition de lien et ncessite de disposer de toutes les bibliothques sous leur forme archive (extension .a). De nombreuses autres options doptimisation sont disponibles et peuvent inuencer sensiblement la rapidit dexcution dans le cas de programmes trs calculatoires.
Compilation spare

La programmation modulaire consiste ne pas regrouper dans un seul chier tout le code source dun projet, mais au contraire le rpartir dans plusieurs chiers et compiler ces derniers sparment. Ceci permet une rutilisation simple du code car un ensemble de fonctions dusage gnral peut tre isol dans un chier et rutilis
2. Il sagit de directives du prprocesseur qui seront prsentes en partie 26.6.

494

26.2. La compilation sous Unix facilement dans dautres programmes. De plus, pour les gros dveloppements logiciels, la compilation est une tape longue chaque modication ; il est donc souhaitable de sparer les sources en plusieurs petits chiers, ainsi seuls les chiers effectivement modis devront tre recompils chaque fois. An de rendre le source dun projet le plus lisible possible, on adopte gnralement quelques conventions simples. On distingue ainsi trois types de chiers sources : Des chiers contenant des fonctions quon nommera avec une extension .c, mais ne contenant en aucun cas une fonction main(). Des chiers contenant les dclarations de type, les prototypes des fonctions (voir plus loin) et ventuellement des macros, correspondant aux sources prcdentes. On les nommera comme les sources auxquelles ils correspondent mais avec un sufxe .h, et ils seront inclus par des directives #include dans les sources des chiers .c. On les appelle des chiers den-ttes (headers). Des chiers avec une extension .c, contenant un main() et utilisant les fonctions des autres chiers (qui sont dclares par des inclusions des .h). Chaque chier de ce type donnera un excutable du projet nal. Il est recommand dadopter une convention claire de nommage de ces chiers an de les distinguer des premiers (par exemple les nommer m_<nom_excutable>.c ou main.c sil ny en a quun). La compilation dun chier source .c en un chier objet ayant le mme nom de base mais le sufxe .o se fait par une commande du type :
menthe22> gcc -c <source.c>

Rappelons quun chier objet est en fait un chier contenant le code compil du source correspondant, mais o gurent galement (entre autre) une table des variables et des fonctions exportes dnies dans le source, ainsi quune table des variables et des fonctions qui sont utilises mais non dnies. Les chiers objets (.o) sont alors lis ensemble, cest la phase ddition de liens, o sont galement ajoutes les bibliothques (ensemble de fonctions prcompiles). Dans cette phase, les tables de tous les chiers objets sont utilises pour remplacer les rfrences aux variables et aux fonctions inconnues par les vrais appels. Le rsultat est alors un chier excutable. Ceci se fait par une commmande du type :
menthe22> gcc <obj1.o> <obj2.o> ... <objn.o> -o <executable> -lm

Il convient de bien faire la diffrence entre la ligne #include <math.h> et loption -lm sur la ligne de commande. En effet, len-tte math.h contient tous les prototypes des fonctions mathmatiques, mais ne contient pas leur code excutable. Le code permettant de calculer le logarithme par exemple se trouve en fait dans une bibliothque prcompile. Cest loption ddition de lien -lm qui fournit la fonction elle-mme en ajoutant au programme le code de calcul des fonctions de la bibliothque mathmatique.

495

Chapitre 26. Aide-mmoire de langage C


Les bibliothques prcompiles sont stockes sous Unix comme des chiers archive portant lextension .a. Lors de lutilisation de -l<nom>, le chier recherch sappelle en fait lib<nom>.a et se trouve gnralement dans /usr/lib. Il est donc possible mais fortement dconseill de remplacer la ligne
menthe22> gcc <obj1.o> ... <objn.o> -o <executable> -lm

par
menthe22> gcc <obj1.o> ... <objn.o> /usr/lib/libm.a

En fait, sur la majorit des machines, gcc cre des excutables qui font appel des bibliothques dynamiques ( shared object extension .so). La commande ldd <excutable> permet dobtenir les bibliothques dynamiques dont un excutable aura besoin pour fonctionner. Dans ce mcanisme, ldition de lien nale se fait au moment du lancement de lexcutable, avec les bibliothques dynamiques qui peuvent ventuellement tre dj prsentes en mmoire. Pour compiler en incluant la bibliothque libm.a dans ce cas il faut ajouter loption -static :
menthe22> gcc -static <obj1.o> <obj2.o> ... <objn.o> -o <executable> -lm

Makele

Garder trace des dpendances 3 des chiers entre eux et raliser manuellement les phases de compilation serait fastidieux. Un utilitaire gnrique pour rsoudre ce genre de problmes de dpendances existe : make. Son utilisation dpasse largement la compilation de programmes en C. La version utilise ici est GNU make, accessible sous le nom make sur certains Unix (notamment sous Linux) et sous le nom gmake sur dautres. make fonctionne en suivant des rgles qui dnissent les dpendances et les actions effectuer. Il cherche par dfaut ces rgles dans un chier nomm Makefile dans le rpertoire courant. Voici un exemple simple qui permet de compiler les chiers fich.c et m_tst.c en un excutable tst : Listing 26.3 Un Makefile simple
## Makefile # Lexecutable depend des deux fichiers objets tst : fich.o m_tst.o rightarrowfillgcc fich.o m_tst.o -o tst #ligne de compilation a executer # Chaque fichier objet depend du source correspondant
3. On dit quun chier dpend dun autre, si le premier doit tre reconstruit lorsque le second change. Ainsi un chier objet dpend de son source et de tous les en-ttes inclus dans le source.

496

26.2. La compilation sous Unix


# mais egalement de len-tete contenant les prototypes # des fonctions fich.o : fich.c fich.h rightarrowfillgcc -c fich.c m_tst.o : m_tst.c fich.h rightarrowfillgcc -c m_tst.c

La premire ligne dune rgle indique le nom du chier concern (appel chier cible) suivi de deux-points (:) et de la liste des chiers dont il dpend. Si lun de ces chiers est plus rcent 4 que le chier cible, alors les commandes situes sur les lignes suivant la rgle sont excutes. Ainsi, si tst doit tre fabriqu, la premire rgle nous dit que fich.o et m_tst.o doivent dabord tre jour. Pour cela, make tente alors de fabriquer ces deux chiers. Une fois cette tache effectue (ce sont les deux autres rgles qui sen chargent), si le chier nomm tst est plus rcent que fich.o et m_tst.o, cest termin, dans le cas contraire, tst est refabriqu par la commande qui suit la rgle de dpendance : gcc fich.o m_tst.o -o tst.
Attention le premier caractre dune ligne de commande dans un Makefile est une tabulation (caractre TAB) et non des espaces.

Avec le Makefile ci-dessus, il suft ainsi de taper make tst (ou mme make, car tst est la premire rgle rencontre) pour fabriquer lexcutable tst. make nexcute que les commandes de compilation ncessaires grce au procd de rsolution des dpendances explicit ci-dessus.
make peut servir la gnration automatique de chiers dimpression partir de A sources LTEX... Il peut permettre dautomatiser peu prs nimporte quelle squence dactions systmatiques sur des chiers. Lenvoi par courrier lectronique du projet en C est un exemple ( amnager si vous souhaitez lutiliser) :

Listing 26.4 Un Makefile pour le projet !


# Envoi automatique de mail pour le projet # A remplacer par le bon nom NOM=monnom PROF=bcollin # Les sources du projet SRCS= Makefile interface.c calcul.c \ rightarrowfillfichiers.c m_projet.c mail.txt: explication.txt $(NOM).tar.gz.uu
4. Les dpendances sont vries partir de la date de dernire modication de chaque chier.

497

Chapitre 26. Aide-mmoire de langage C


rightarrowfillcat explication.txt $(NOM).tar.gz.uu > mail.txt mail: mail.txt rightarrowfillmail -s IN201 gueydan < mail.txt $(NOM).tar.gz.uu: $(SRCS) rightarrowfilltar cvf $(NOM).tar $(SRCS) rightarrowfillgzip -c $(NOM).tar > $(NOM).tar.gz rightarrowfilluuencode $(NOM).tar.gz $(NOM).tar.gz > $(NOM).tar.gz.uu

Voici enn un exemple gnrique complet pour compiler les sources interface.c, calcul.c, fichiers.c et m_projet.c. Les premires lignes de lexemple sont des commentaires (commenant par #), et expliquent le rle du projet. Il est fait usage ici de lutilitaire makedepend pour fabriquer automatiquement les dpendances dues aux directives #include. En effet, chaque chier objet dpend dun chier source mais aussi de nombreux chiers den-ttes dont il est fastidieux de garder la liste jour manuellement. makedepend parcourt le source la recherche de ces dpendances et ajoute automatiquement les rgles correspondantes la n du chier Makefile (les rgles ainsi ajoutes la n ne sont pas reproduites ci-dessous). makedepend est un outil faisant partie de X11, gcc -M peut jouer le mme rle mais son utilisation est un peu plus complexe. Lusage de variables (CFLAGS, LDFLAGS...) permet de regrouper les diffrentes options au dbut du chier Makefile. La variable contenant la liste des chiers objets est obtenue automatiquement par remplacement des extensions .c par .o. Des caractres spciaux dans les rgles permettent dcrire des rgles gnriques, comme ici la gnration dun chier objet ayant lextension .o partir du source correspondant. Un certain nombre de ces rgles sont connues par dfaut par make si elles ne gurent pas dans le chier Makefile. Listing 26.5 Un Makefile trs complet !
########################################################## # ECOLE : ENSTA # PROJET : Cours IN201 # FONCTION : Compilation du projet # CIBLE : UNIX / make ou GNU make # AUTEUR : MERCIER Damien # CREATION : Thu Sep 23 10:02:31 MET DST 1997 sur Obiwan # MAJ : Tue Jan 12 20:18:28 MET 1999 sur Idefix # Version : 1.02 ######################################################### # Le compilateur utilise CC=gcc # Options de compilation

498

26.2. La compilation sous Unix


CFLAGS=-I. -ansi -g -Wall # Options dedition de liens LDFLAGS=-lm # Le nom de lexecutable PROG = projet # La liste des noms des sources (le caractere \ permet de # passer a la ligne sans terminer la liste) SRCS = interface.c calcul.c \ rightarrowfillfichiers.c m_projet.c # La liste des objets OBJS = $(SRCS:.c=.o) ######################################################### # Regles de compilation ######################################################### # Regle par defaut : dabord les dependances, puis le projet all: dep $(PROG) # Ajout automatique des dependances entre les .c et les .h # a la fin de ce Makefile dep: rightarrowfillmakedepend -- $(CFLAGS) -- $(SRCS) # Comment compile ton un .c en un .o ? (Regle generique) %.o : %.c rightarrowfill$(CC) $(CFLAGS) -c $*.c # Ledition de liens $(PROG) : $(OBJS) rightarrowfill$(CC) $(CFLAGS) $(OBJS)

-o $(PROG) $(LDFLAGS)

# make clean fait un nettoyage par le vide clean: rightarrowfill\rm -f $(PROG) $(OBJS) Makefile.bak *~ #* core

499

Chapitre 26. Aide-mmoire de langage C

26.3

Types, oprateurs, expressions

Les types simples

Les types de base en C sont les nombres entiers (int), les nombres rels (float) et les caractres (char). Certains de ces types existent en plusieurs tailles : char reprsente un caractre cod gnralement sur un octet (8 bits) sur les machines actuelles ; int dsigne un entier cod sur la taille dun mot machine, gnralement 32 bits sur les machines actuelles ; short (on peut aussi crire short int) dsigne un entier sur 16 bits ; long (ou long int) dsigne un entier sur 32 bits ; oat est un nombre ottant (rel) simple prcision, gnralement cod sur 32 bits ; double est un ottant double prcision cod le plus souvent sur 64 bit ; long double est un ottant quadruple prcision. Il nest pas garanti que sa gamme soit suprieure un double. Cela dpend de la machine. Le mot cl unsigned plac avant le type permet de dclarer des nombres nonsigns. De la mme faon, signed permet de prciser que le nombre est sign. En labsence de spcication, tous les types sont signs, lexception de char qui est parfois non sign par dfaut 5 . Il convient de noter que le type char peut servir stocker des nombres entiers et peut servir comme tel dans des calculs. La valeur numrique correspondant un caractre donn est en fait son code ASCII. Les tailles cites ci-dessus sont valables sur une machine standard dont le mot lmentaire est 16 ou 32 bits et pour un compilateur standard : autant dire quil est prfrable de vrier avant...
Dclarations des variables

Une dclaration de variable se fait systmatiquement au dbut dun bloc avant toute autre expression 6 . La dclaration scrit <type> <nom>; o <type> est le type de la variable et <nom> est son nom. On peut regrouper plusieurs variables de mme type en une seule dclaration en crivant les variables spares par des virgules : int a, b, i, res; par exemple. Remarquer que ceci est diffrent des paramtres dune fonction, qui doivent tous avoir un type explicite (on crit int somme(int a, int b);). Toute variable dclare dans un bloc est locale ce bloc, cest--dire quelle nest pas visible en dehors de ce bloc. De plus, elle masque toute variable du mme nom venant dun bloc qui lenglobe. Ceci peut se voir sur un exemple :
5. Avec gcc, le type char est sign ou non selon les options de compilation utilises sur la ligne de commande : -fsigned-char ou -funsigned-char. 6. Cette limitation ne fait plus partie de la norme ISO de 1999, mais la plupart des compilateurs limpose encore.

500

26.3. Types, oprateurs, expressions Listing 26.6 Variables globales et locales


#include <stdio.h> int a, b; dummy(int a) { /* la variable a globale est ici masquee par le parametre a, en revanche b est globale */ /* on a a=3 a lappel 1 (provient du c du main()), */ /* b=2 (globale) */ /* et a=5 a lappel 2 (provient du d du sous-bloc), /* b=2 (globale) */ /* c et d ne sont pas visibles ici */ printf("%d %d\n",a,b); } main() { int c, d; a=1; b=2; c=3; d=4; dummy(c); /* appel 1 */ /* on a toujours a=1, b=2 globales et c=3, d=4 locales */ { /* sous-bloc */ int d, b; d=5; /* d est locale a ce bloc et masque le d de main() */ b=7; /* b est locale a ce bloc */ dummy(d); /* appel 2 */ } printf("%d\n",d); /* on revient a d=4 du bloc main() */ }

Ce programme afche dans lordre :


3 5 4 2 2

Attention, la simple dclaration dune variable ne provoque gnralement aucune initialisation de sa valeur. Si cela est nanmoins ncessaire, on peut crire linitialisation avec une valeur initiale sous la forme int a=3;.
Les classes de stockage

On appelle de ce nom barbare les options qui peuvent gurer en tte de la dclaration dune variable. Les valeurs possibles sont les suivantes : auto Il est facultatif et dsigne des variables locales au bloc, alloues automatiquement au dbut du bloc et libres en n de bloc. On ne lcrit quasiment jamais. register Cette dclaration est similaire auto, mais pour un objet accd trs frquemment. Cela demande au compilateur dutiliser si possible un registre du processeur pour cette variable. Il est noter que lon ne peut demander ladresse mmoire dune variable (par loprateur & prsent en partie 26.5) ayant cette spcication, celle-ci nexistant pas. 501

Chapitre 26. Aide-mmoire de langage C static Les variables non globales ainsi dclares sont alloues de faon statique en mmoire, et conservent donc leur valeur lorsquon sort des fonctions et des blocs et que lon y entre nouveau. Dans le cas des variables globales et des fonctions, leffet est diffrent : les variables globales dclares statiques ne peuvent pas tre accdes depuis les autres chiers source du programme. extern Lorsquun source fait appel une variable globale dclare dans un autre source, il doit dclarer celle-ci extern, an de prciser que la vraie dclaration est ailleurs. En fait une variable globale doit tre dclare sans rien dans un source, et dclare extern dans tous les autres sources o elle est utilise. Ceci peut se faire en utilisant le prprocesseur (cf. 26.6). En rgle gnrale, il est prfrable dviter lutilisation de la classe register et de laisser au compilateur le soin du choix des variables places en registre. An de simplier la tche de dveloppement et pour limiter les risques derreurs, il est recommand de dclarer avec le mot cl static en tte les variables et fonctions qui sont globales au sein de leur chier, mais qui ne doivent pas tre utilises directement dans les autres sources. En revanche, utiliser static pour des variables locales permet de conserver une information dun appel dune fonction lappel suivant de la mme fonction sans ncessiter de variable globale. Ceci est rserver quelques cas trs spciaux : ne pas en abuser !
La fonction strtok() de la bibliothque standard utilise ainsi une variable statique pour retenir dun appel lautre la position du pointeur de chane. Dautres qualicateurs existent, en particulier le terme volatile plac avant la dclaration permet de prciser au compilateur quil peut y avoir modication de la variable par dautres biais que le programme en cours (priphrique matriel ou autre programme). Il vitera ainsi de rutiliser une copie de la variable sans aller relire loriginal. Les cas dutilisation en sont assez rares dans des programmes classiques. Seuls les dveloppeurs du noyau ou de modules du noyau et ceux qui utilisent des mcanismes de mmoire partage entre processus en ont rgulirement besoin.

Types complexes, dclarations de type

Le C est un langage dont la vocation est dcrire du code de trs bas niveau (manipulation de bits), comme cela est ncessaire par exemple pour dvelopper un systme dexploitation 7 , mais il dispose galement de fonctionnalits de haut niveau comme les dnitions de type complexe. Trois familles de types viennent sajouter ainsi aux types de base, il sagit des types numrs, des structures et des unions. Ces trois types se dclarent de faon similaire. Notons aussi les tableaux dont la dclaration est ralise par :
/* un tableau de 16 entiers */ int un_tableau[16];

7. C t dvelopp au dpart comme le langage principal du systme Unix.

502

26.3. Types, oprateurs, expressions

/* un tableau de 32 pixels */ /* voir ci-apres */ struct pixel mes_pixels[32];

Le type numr est en fait un type entier dguis. Il permet en particulier de laisser des noms littraux dans un programme pour en faciliter la lecture. Les valeurs possibles dun type numr font partie dun ensemble de constantes portant un nom, que lon appelle des numrateurs. Lexemple suivant dnit un type numr correspondant des boolens, not bool, et dclare en mme temps une variable de ce type nomme bvar :
enum bool {FAUX, VRAI} bvar;

Aprs cette dclaration il est possible de dclarer dautres variables de ce mme type avec la syntaxe enum bool autrevar;.
Dans la premire dclaration, il est permis domettre le nom du type numr (dans ce cas seule la variable bvar aura ce type) ou bien de ne pas dclarer de variable sur la ligne de dclaration dnumration.

Dans une numration, si aucune valeur entire nest prcise (comme cest le cas dans lexemple prcdent), la premire constante vaut zro, la deuxime 1 et ainsi de suite. La ligne ci-dessus est donc quivalente (sans nommage du type)
enum {FAUX=0, VRAI=1} bvar;

Lorsqueelles sont prcises, les valeurs peuvent apparatre dans nimporte quel ordre :
enum {lun=1,ven=5,sam=6,mar=2,mer=3,jeu=4,dim=7} jour;

Une structure est un objet compos de plusieurs membres de types divers, portant des noms. Elle permet de regrouper dans une seule entit plusieurs objets utilisables indpendamment. Le but est de regrouper sous un seul nom plusieurs lments de types diffrents an de faciliter lcriture et la comprhension du source. Une structure pixel contenant les coordonnes dun pixel et ses composantes de couleur pourra par exemple tre :
struct pixel { unsigned char r,v,b; int x,y; };

Par la suite, la dclaration dune variable p1 contenant une structure pixel se fera par struct pixel p1;.
La remarque prcdente concernant les possibilits de dclaration des numrations sapplique galement aux structures et aux unions.

503

Chapitre 26. Aide-mmoire de langage C Les accs aux champs dune structure se font par loprateur point (.) . Rendre le point p1 blanc se fera ainsi par p1.r = 255 ; p1.v = 255 ; p1.b = 255 ; Une union est un objet qui contient, selon le moment, un de ses membres et un seul. Sa dclaration est similaire celle des structures :
union conteneur { int vali; float valf; char valc; }; union conteneur test;

En fait, il sagit dun objet pouvant avoir plusieurs types selon les circonstances. On peut ainsi utiliser test.vali comme un int, test.valf comme un float ou test.valc comme un char aprs la dclaration prcdente, mais un seul la fois ! La variable test est juste un espace mmoire assez grand pour contenir le membre le plus large de lunion et tous les membres pointent sur la mme adresse mmoire 8 . Il incombe donc au programmeur de savoir quel type est stock dans lunion lorsquil lutilise : cest une des raisons qui rendent son usage peu frquent.
Attention, il est fortement dconseill dutiliser la valeur dun membre dune union aprs avoir crit un autre membre de la mme union (crire un int et lire un char par exemple) car le rsultat peut diffrer dune machine lautre (et prsente en gnral peu dintrt, sauf si lon veut savoir lordre que la machine utilise pour stocker les octets dans un long...).
Dclaration de type

Celle-ci se fait au moyen du mot-cl typedef. Un nouveau type ainsi dni peut servir par la suite pour dclarer des variables exactement comme les types simples. On peut ainsi crire
typedef unsigned char uchar;

an dutiliser uchar la place de unsigned char dans les dclarations de variables... La dclaration de type est trs utile pour les numrations, les structures et les unions. Dans notre exemple prcdent, il est possible dajouter
typedef struct pixel pix;

pour ensuite dclarer pix p1,p2;. Une version abrge existe galement dans ce cas et permet en une seule fois de dclarer la sructure pixel et le type pix :
8. Si larchitecture de la machine utilise le permet.

504

26.3. Types, oprateurs, expressions


typedef struct pixel { unsigned char r,v,b; int x,y; } pix;

Le comportement est alors exactement le mme que ci-dessus : les dclarations struct pixel p1; et pix p1; sont quivalentes. Il est possible aussi de supprimer compltement le nom de structure dans ce cas en crivant :
typedef struct { unsigned char r,v,b; int x,y; } pix;

Comme prcdemment, les dclarations de variables ultrieures se feront par pix p1;.
Mais attention, cette dernire criture ne permet pas de raliser les structures autorfrentielles qui sont prsentes au 26.5.

criture des constantes

Les constantes entires sont crites sous forme dcimale, octale (base 8) ou hexadcimale (base 16). Lcriture dcimale se fait de faon naturelle, lcriture octale sobtient en faisant prcder le nombre dun 0 (par exemple 013 vaut en dcimal 11), et lcriture hexadcimale en faisant prcder le nombre du prxe 0x. De plus, il est possible dajouter un sufxe U pour unsigned et/ou L pour long dans le cas o la valeur de la constante peut prter confusion. Ainsi 0x1AUL est une constante de type unsigned long et valant 26. Les constantes de type caractre peuvent tre entres sous forme numrique (par leur code ASCII, car du point de vue du compilateur ce sont des entiers sur un octet), il est aussi possible de les crire entre apostrophes (e.g. A). Cette dernire criture accepte galement les combinaisons spciales permettant dentrer des caractres nonimprimables sans avoir mmoriser leur code ASCII (voir au paragraphe 26.5). Le caractre \0 vaut la valeur 0 (appele NUL en ASCII) et sert de marqueur spcial 9 . Il faut bien remarquer que le type caractre correspond un caractre unique (mme si certains caractres sont reprsents par des chanes dchappements comme \n) et non une chane de caractres. Attention ne pas utiliser des guillemets " la place des apostrophes, car "A" par exemple reprsente en C une chane de caractres constante contenant le caractere A. Cest donc un pointeur vers une adresse mmoire contenant A et non la valeur A !
9. Le chiffre 0 vaut en fait en ASCII 48

505

Chapitre 26. Aide-mmoire de langage C Les constantes ottantes (qui correspondent des nombres rels) peuvent utiliser une syntaxe dcimale ou une notation scientique avec la lettre e pour marquer lexposant. Il est possible daffecter une valeur une variable lors de sa dclaration. Ceci se fait le plus simplement possible par int a=3;. Dans le cas o cette variable est en fait une constante (cest un comble !), cest--dire quelle ne peut et ne doit pas tre modie dans le corps du programme, on peut prciser const devant la dclaration :
const float e2 = 7.3890561; const pi = 3.141592; const nav = 6.02e23;

const permet selon sa position de dclarer des valeurs non modiables ou des pointeurs non modiables. Lexemple suivant explicite les diffrents cas : char s,t; const char c=a; /* laffectation initiale est possible */ const char *d; char *const e=&t; c=b; s=c; d=&s; *d=c; *e=c; e=&s; /* /* /* /* /* /* erreur : c est const */ affectation correcte de caractre */ affectation correcte de pointeur */ erreur : *d est un caractre constant */ correct : *e est un char */ erreur : e est un pointeur constant */

Cette mme syntaxe daffectation initiale peut se faire pour les structures, les numrations et les tableaux. Pour les structures, les valeurs initiales doivent tre crites entre accolades, spares par des virgules et dans lordre de la structure. Il est possible domettre les derniers champs de la structure, qui prennent alors par dfaut la valeur zro :
typedef struct { unsigned char r,v,b; int x,y; } pix; pix p1={2, 3, 4, 5, 6}; /* r=2 v=3 b=4 x=5 y=6 */ pix p2={1, 2}; /* r=1 v=2 b=0 x=0 y=0 */ const sert galement prciser que certains arguments de fonction (dans la dclaration, et sil sagit de pointeurs, cf. 26.5) dsignent des objets que la fonction ne modiera pas 10 .
10. Lusage de const est particulirement important pour les systmes informatiques embarqus, o le code excutable et les constantes peuvent tre stocks en mmoire morte (ROM).

506

26.3. Types, oprateurs, expressions


Le transtypage (cast)

Lors de calculs dexpressions numriques, les types ottants sont dits absorbants car la prsence dun unique nombre ottant dans une expression entrane une valuation complte sous forme de ottant. Il est frquent quune variable ait besoin dtre utilise avec un autre type que son type naturel. Cela arrive par exemple si lon veut calculer le quotient de deux entiers et stocker le rsultat dans un rel (voir le paragraphe suivant). Ce transtypage sobtient en faisant prcder lexpression que lon souhaite utiliser avec un autre type par ce type entre parenthses. Cest par exemple le seul moyen de convertir un nombre ottant en entier, car une fonction floor() est bien dnie dans la bibliothque mathmatique, mais elle retourne un nombre ottant ! On crira ainsi i = (int)floor(f);.
Le transtypage dentier en ottant et rciproquement comme prsent ci-dessus est un cas trs particulier, qui pourrait laisser croire que toutes les conversions sont possibles par ce biais. Attention donc, dans la grande majorit des cas, le transtypage conduit juste interprter le contenu de la mmoire dune autre faon : cest le cas par exemple si lon essaie de transformer une chane de caractre char s[10] en un entier par (int)s, et le rsultat na pas dintrt (en fait cela revient lire ladresse mmoire laquelle est stocke s !). Pour effectuer les conversions, il convient dutiliser les fonctions adquates de la bibliothque standard.

Les oprateurs

Les oprations arithmtiques en C sont dcriture classique : +, -, *, / pour laddition, la soustration, la multiplication et la division. Il est noter que la division applique des entiers donne le quotient de la division euclidienne. Pour forcer un rsultat en nombre ottant (et donc une division relle), il faut convertir lun des oprandes en nombre ottant. Par exemple 3/2 vaut 1 et 3/2.0 ou 3/(float)2 vaut 11 1.5. Les affectations scrivent avec le signe =. Une chose importante est noter : une affectation peut elle-mme tre utilise comme un oprande dans une autre opration, la valeur de cette affectation est alors le rsultat du calcul du membre de droite. Ainsi on pourra par exemple crire a=(b=c+1)+2; dont laction est de calculer c+1, daffecter le rsultat b, dy ajouter 2 puis daffecter le nouveau rsultat a. Il est noter que ce type dcriture, sauf cas particuliers, rend la lecture du programme plus difcile et doit donc tre si possible vit. Les oprations boolennes permettent de faire des tests. Le C considre le 0 comme la valeur fausse et toutes les autres valeurs comme vraies. Pour combiner des valeurs en tant quexpression boolenne, on utilise le OU logique not || et le ET logique not &&. La ngation dune valeur boolenne sobtient par ! plac avant
11. Il sagit ici dun transtypage (cast).

507

Chapitre 26. Aide-mmoire de langage C la valeur. Il est important de retenir galement que les oprateurs logiques && et || arrtent lvaluation de leurs arguments ds que cela est possible 12 . Cette proprit peut tre intressante, par exemple dans des conditions de n de boucle du type SI mon indice na pas dpass les bornes ET quelque chose qui dpend de mon indice ALORS instructions. En effet dans ce cas le calcul qui dpend de lindice nEST PAS effectu si lindice a dpass les bornes, ce qui est prfrable ! Il reste engendrer des grandeurs boolennes qui reprsentent quelque chose ! Ceci ce fait avec les oprateurs de comparaison. Le test dgalit se fait par == (deux signes gal), le test dingalit (diffrent de) se fait par !=, la relation dordre par >, . >=, < et <=.
Attention une erreur classique est dcrire un seul signe gal dans un test. Comme le rsultat dune affectation est la valeur affecte et comme un nombre non nul est un boolen vrai, crire :
if (a == 2) { printf("a vaut 2\n"); }

ou crire :
if (a = 2) { printf("a vaut 2\n"); }

est trs diffrent ! (la deuxime version est un test toujours vrai, et a vaut 2 aprs lexcution).

Les oprations dincrmentation et de dcrmentation tant frquentes sur les entiers, il est possible (et mme recommand) dcrire i++ plutt que i=i+1. Comme dans le cas des affectations, lopration dincrmentation une valeur, qui est la valeur avant incrmentation si le ++ est droite de la variable, et est la valeur incrmente si cest ++i. Un oprateur de dcrmentation not -- existe galement. En rsum :
i = 2; a = ++i; b = a--; b++; /* /* /* /* /* valeurs apres la ligne */ i=2 */ i=3, a=3 */ b=3, a=2 */ b=4 */

On remarquera que, bien que loprateur ait une valeur de retour, celle-ci peut tre ignore. Ceci est vrai galement pour les fonctions. Par exemple printf() que nous avons utilis tout au dbut retourne normalement un int, mais le plus souvent on ignore cette valeur de retour.
12. Ceci sappelle de lvaluation paresseuse (lazy evaluation).

508

26.3. Types, oprateurs, expressions Dans la ligne des oprateurs dincrmentation, il existe toute une famille doprateurs de calcul-affectation qui modient une variable en fonction dune expression. Leur syntaxe est de la forme :
<variable> R= <expression> o R est une opration parmi +, -, *, /, %, >>, <<, &, | et ^. Lcriture prcdente est

alors quivalente :
<variable> = <variable> R <expression>.

Quelques oprateurs spciaux viennent sajouter cela : nous avons dj parl des fonctions, lappel de fonction est un oprateur (cest mme loprateur de priorit maximale). Il y a galement loprateur conditionnel dont la valeur rsultat provient dune des deux expressions quil contient selon une condition :
<Condition> ? <Expression si vraie> : <Expression si fausse>

La condition est une expression boolenne. Elle est systmatiquement value (avec un arrt ds que le rsultat est certain comme nous lavons vu prcdemment) et selon sa valeur, une des deux expressions et une seulement est value. Un exemple dusage est la construction dune fonction MIN :
int min(int a, int b) { return ( (a>b) ? b : a ); }

Un dernier oprateur souvent pass sous silence est loprateur virgule. Il permet de sparer une suite dexpressions qui seront ncessairement values lune aprs lautre de la gauche vers la droite, le rsultat nal de cette squence dexpression tant le rsultat de la dernire valuation (expression la plus droite).
Les oprateurs concernant les pointeurs et les tableaux seront prsents en partie 26.5.

Les oprateurs sur les bits dun mot sont extrmement utiles en programmation bas-niveau et permettent gnralement dviter la programmation en langage machine. Les oprations agissent bit bit sur la reprsentation binaire de leur(s) oprande(s). On dispose ainsi de la complmentation (~) de tous les bits dun mot, du ET bit bit, du OU et du XOR (OU eXclusif) nots respectivement &, | et ^. Les oprateurs de dcalage gauche (quivalent une multiplication par deux sur des entiers non signs) et droite (division par 2) se notent << et >>. Quelques exemples :
unsigned char c, d; c=0x5f; /* en binaire 0101 1111 */ d = ~c; /* d = 0xA0 1010 0000 */ d = d | 0x13; /* 0x13 = 0001 0011 -> d = 0xB3 1011 0011 */ c = d ^ (c << 2); /* c decale a gauche 2 fois : 0111 1100 */ resultat c= 0xCF 1100 1111 */

509

Chapitre 26. Aide-mmoire de langage C


Attention une autre erreur classique consiste oublier un signe et crire cond1 & cond2 au lieu de cond1 && cond2 dans un test. Le compilateur ne peut bien sr rien remarquer et lerreur est gnralement trs difcile trouver.

Lappel de fonction

En C, lappel de fonction se fait par le nom de la fonction suivi de la liste des paramtres entre parenthses. Une particularit trs importante du langage est que les paramtres passs en argument de la fonction sont recopis dans des variables locales la fonction avant le dbut de son excution. On appelle cela un passage par valeur. Ainsi, sans utiliser les pointeurs 13 , le seul rsultat qui puisse sortir dun appel de fonction est sa valeur de retour. La fonction se termine lorsque lexcution atteint laccolade fermante du bloc, et dans ce cas il ny a pas de valeur de retour (la fonction doit tre dclare de type void). Si la fonction doit retourner une valeur, celle-ci doit tre prcise par linstruction return valeur. Lexcution de cette instruction termine la fonction. Il est possible davoir plusieurs instructions return dans une mme fonction, par exemple dans le cas de tests.
Dans le cas dune fonction retournant void (sans valeur de retour), return ; permet de terminer la fonction.

Voici un exemple : Listing 26.7 Passage par valeur


#include <stdio.h> int test(int a, int b) { /* lors de lappel, a=2 et b=7 */ if (a==0) return b; /* la suite nest execute que si a != 0 */ a=b/a; b=a; return a; } int main(int argc, char *argv[]) { int x, y, z; x=2; y=7; z=test(x,y); /* z=3 mais x et y ne sont pas modifies */ /* seule leur valeur a ete copiee dans */ /* a et b de test() */ printf("%d %d %d %d\n",x,y,z,test(0,z)); /* affiche 2 7 3 3 */ }

Les fonctions peuvent bien sr tre rcursives, comme le montre lexemple du 26.1.
13. Voir 26.5. Ceux-ci permettent quelque chose de similaire au passage par variable (ou par rfrence).

510

26.3. Types, oprateurs, expressions


Les fonctions inline

Lcriture de programmes lisibles passe par le dcoupage des algorithmes en de nombreuses fonctions, chacune formant une unit logique. Certaines de ces fonctions ne servent quune fois, mais on prfre ne pas intgrer leur code lendroit o elles sont appeles. linverse, dautres fonctions sont trs simples (quelques lignes de programme, comme par exemple le calcul du minimum de deux valeurs) et sont utilises un grand nombre de fois. Dans les deux cas, chaque appel de fonction est une perte de temps par rapport crire le code directement lendroit de lappel (le compilateur sauvegarde le contexte au dbut de lappel de fonction, place ventuellement les arguments sur la pile, puis appelle la fonction) le retour de lappel est galement assez coteux. Pour viter ce gchis de puissance de calcul, la plupart des compilateurs essayent doptimiser en plaant le code de certaines fonctions directement la place de leur appel. Ceci a de nombreuses limitations : il faut compiler avec des optimisations fortes et les fonctions concernes doivent tre dans le mme chier source 14 . Pour aider le compilateur dans le choix des fonctions optimiser ainsi, il est possible de prciser le mot-clef inline au dbut de la dclaration de la fonction. Ceci prend alors effet mme si les optimisations ne sont pas demandes (ce nest quune indication, comme la classe register pour les variables). Ce mot-clef inline ne fait pas partie de la norme ANSI de 1989 mais a t, notre connaissance, intgr dans la norme de 1999. Il est support par gcc et de nombreux autres compilateurs.
Pour permettre lappel de fonction depuis dautres chiers objet a , le compilateur conserve tout de mme une version sous forme de fonction classique, sauf si la fonction est de plus dclare static inline.
a. Cest galement ncessaire si la fonction est appele quelque part par pointeur de fonction, cf. 26.5.

On pourra avec avantage dclarer ainsi une fonction minimum de deux entiers sous la forme de fonction inline (plutt que de macro, ce qui conduit de frquentes erreurs) :
static inline int min(int a, int b) { return ( (a>b) ? b : a ); }

Ces fonctions dclares static inline sont les seules fonctions que lon pourra crire dans les chiers .h an de pouvoir les utiliser comme on le fait avec des macros.
14. Ceci nempche pas la compilation spare, mais tout appel de fonction entre des chiers objets diffrents est obligatoirement effectu sous la forme dun vrai appel de fonction. cette n, le compilateur conserve toujours une version de la fonction dont lappel soit ralisable normalement.

511

Chapitre 26. Aide-mmoire de langage C


Les priorits

Voici un tableau rcapitulatif donnant tous les oprateurs et leur priorit (la premire ligne du tableau est la plus prioritaire, la dernire est la moins prioritaire), les oprateurs ayant des priorits gales gurent dans une mme ligne et sont valus dans le sens indiqu dans la colonne associativit : Oprateurs
() [] . ->

Fonction appel de fonction lment de tableau champ de structure ou dunion champ dsign par pointeur Fonction ngation boolen complmentation binaire oppos incrmentation dcrmentation adresse drfrenciation de pointeur transtypage Fonction multiplication division reste euclidien Fonction addition soustraction Fonction dcalage gauche dcalage droite Fonction strictement infrieur infrieur ou gal strictement suprieur suprieur ou gal 512

Associativit Associativit Associativit Associativit Associativit Associativit

priorit maximale

Oprateurs
! ~ ++ -& * (type)

priorit

Oprateurs
* / %

priorit

Oprateurs
+ -

priorit

Oprateurs
<< >>

priorit

Oprateurs
< <= > >=

priorit

26.3. Types, oprateurs, expressions Oprateurs


== !=

Fonction gal diffrent Fonction ET bit bit OU eXclusif (XOR) bit bit OU bit bit ET boolen OU boolen Fonction conditionnelle affectation affectations avec calcul

Associativit Associativit Associativit

priorit

Oprateurs
& ^ | && ||

priorit

Oprateurs
? : = *= /= %= -= += <<= >>= &= |= ^= ,

priorit

squence dexpressions

minimale

Les oprateurs concernant les pointeurs sont prsents au 26.5. Ce tableau est trs important, et son ignorance peut conduire parfois des erreurs difciles dtecter. Citons par example a=*p++ : les oprateurs ++ et * sont de mme priorit, donc lexpression est value dans lordre dassociativit (de droite gauche), et donc p est incrment, et lancienne valeur de p est drfrence, le rsultat tant stock dans a. Si lon veut incrmenter la valeur pointe par p (et non p lui-mme), il faudra crire a=(*p)++. Le rsultat stock dans a est le mme dans les deux cas. En cas de doute, il ne faut pas hsiter mettre quelques parenthses (sans abuser toutefois) car cela peut aider la comprhension. On vitera ainsi dcrire : a=++*p>b|c+3 ! On vitera aussi : a=((++(*p)>b)|(c+3)) et on prfrera quelque chose comme : a = ++(*p)>b | (c+3).

513

Chapitre 26. Aide-mmoire de langage C

26.4

Tests et branchements

Linstruction if

Nous avons vu lors de lexamen des oprateurs lexpression conditionnelle. Il existe galement bien sr une structure de contrle qui ralise lexcution conditionnelle. Sa syntaxe est la suivante :
if (<condition>) { <instructions ...> }

La condition est teste comme un boolen, cest--dire quelle est considre comme vraie si son valuation nest pas nulle. Dans le cas o une seule instruction est excuter quand la condition est vraie, on trouvera souvent cette instruction toute seule la place du bloc entre accolades. La version avec alternative :
if (<condition>) { <instructions ...> } else { <instructions ...> }

L encore il est possible de remplacer lun ou lautre (ou les deux) blocs entre accolades par une unique instruction. Attention dans ce cas, il faut terminer cette instruction par un point-virgule. Avec de multiples alternatives :
if (<cond1>) { <instructions ...> /* Si cond1 est vraie */ } else if (<cond2>) { <instructions ...> /* si cond1 fausse et cond2 vraie */ } else { <instructions ...> /* si cond1 et cond2 fausses */ } }

De nombreux programmes C utilisent des tests sans instruction de comparaison explicite, voire des tests qui sont le rsultat doprations daffectation, par exemple :
if (a = b - i++) i--; else a=b=i++;

Ceci utilise le fait quune valeur non nulle est considre comme vraie. Cest assez difcile lire 15 , aussi il sera prfrable dcrire cette version quivalente :
15. Lun des exemples les plus russis dans lillisibilit se trouve ici : http://www.cwi.nl/~tromp/
tetris.html

514

26.4. Tests et branchements


i++; a= b-i; if (a != 0) { i--; } else { i++; b = i; a = b; }

Linstruction switch()
switch (<expression>) { case <val 1>: <instructions ...> case <val 2>: <instructions ...> ... case <val n>: <instructions ...> default: } <instructions ...>

Linstruction switch permet de diriger le programme vers une squence dinstructions choisie parmi plusieurs, suivant la valeur dune expression qui doit tre de type entier. Le branchement se fait ltiquette case dont la valeur est gale lexpression de tte (il ne doit y avoir au plus quune tiquette qui correspond). Si aucune tiquette ne correspond, le saut se fait ltiquette default si elle existe. Attention, une fois le branchement effectu, le programme continue de sexcuter dans lordre, y compris les case qui peuvent suivre. Pour empcher ce comportement, on utilise souvent break la n des instructions qui suivent le case : son effet est de sauter directement aprs le bloc du switch (Voir 26.4). Un exemple complet dutilisation standard : Listing 26.8 Branchements
int val,c; c=1; val = mafonctiondecalcul(); /* une fonction definie ailleurs */ switch (val) { case 0: case 1: printf("Resultat 0 ou 1\n"); c=2; break; case 2: printf("Resultat 2\n"); break;

515

Chapitre 26. Aide-mmoire de langage C


case 4: printf("Resultat 4\n"); c=0; break; default: printf("Cas non prevu\n"); }

Les boucles
La boucle for

Cest une boucle beaucoup plus gnrale que dans dautres langages. Son rle ne se limite pas la simple rptition en incrmentant un entier. Son utilisation est la suivante :
for ( <instruction initiale> ; <condition de poursuite> ; <instruction davancement> ) { <instructions ...> }

Linstruction initiale est excute avant le dbut de la boucle, la condition de poursuite est teste chaque dbut de boucle et linstruction davancement est excute en dernire instruction chaque tour de boucle. Lquivalent de la boucle dincrmentation dune variable entire des autres langages scrira par exemple :
for (i=0; i<100; i++) { <instructions...> /* excutes pour i=0, i=1 ... i=99 */ }

On peut omettre nimporte laquelle des trois expressions de la boucle for. Si cest la condition de poursuite qui est omise, la boucle se comporte comme si cette condition tait toujours vraie. Ainsi une boucle innie 16 peut scrire :
for (;;) { <instructions ...> }
16. Qui ne se termine jamais si lon utilise pas les instructions de branchement qui seront prsentes dans le paragraphe traitant des branchements.

516

26.4. Tests et branchements


La boucle while
while (<condition de poursuite>) { <instruction ...> }

qui est compltement quivalente (remarquer que les expressions de la boucle for sont facultatives) :
for (;<condition de poursuite>;) { <instructions ...> }

De faon similaire, il est possible de rcrire une boucle for gnrique sous la forme dune boucle while. Lcriture gnrique prcdente est quivalente :
<inst. initiale>; while (<cond. de poursuite>) { <instructions...> <inst. davancement>; }

La boucle do...while

Ici le test est effectu en n de boucle. Cette boucle est donc, contrairement aux deux boucles prcdentes, excute au moins une fois, mme si la condition est fausse ds le dbut de la boucle. Voici la syntaxe :
do { <instructions ...> } while (<condition de poursuite>);

Le style dcriture habituel du C utilise extrmement rarement ce type de boucle.


Les branchements

Ce sont des instructions qui modient le droulement dun programme sans condition. Linstruction continue ne peut tre prsente qu lintrieur dune boucle. Elle conduit un branchement vers la n de la plus petite boucle qui lentoure, mais ne sort pas de la boucle (ainsi linstruction davancement si elle existe est effectue, puis la boucle reprend). Linstruction break peut apparatre dans une boucle ou dans une instruction switch. Elle effectue un saut immdiatement aprs la n de la boucle ou du switch. Son usage est indispensable dans le cas des boucles innies. 517

Chapitre 26. Aide-mmoire de langage C Linstruction goto <label> quant elle peut apparatre nimporte o. Elle effectue un saut linstruction qui suit ltiquette <label>: (cette tiquette se note par le label suivi de deux-points). Lusage du goto est fortement dconseill car il conduit frquemment un source illisible. Il peut dans quelques rares cas rendre service en vitant dimmenses conditions, ou de nombreuses rptitions dun mme petit morceau de code. Lexemple suivant montre lutilisation de break et de continue au sein dune boucle for. On trouvera plus frquemment linstruction break dans des boucles innies. Listing 26.9 Sortie de boucle prmature
for (i=0; i<100; i++) { /* si i<5 la fin de la boucle nest pas executee */ if (i<5) continue; if (i==10) break; /* si i vaut 10 la boucle se termine */ printf("%d\n",i); }

Le rsultat de cette boucle est lafchage des nombres 5, 6, 7, 8, 9. La condition de poursuite de la boucle for est en fait totalement inutile ici.

26.5

Tableaux et pointeurs

Dclaration et utilisation des tableaux

En C, un tableau une dimension de 10 entiers sera dclar par la syntaxe int x[10]. Il sagit dune zone de mmoire de longueur dix entiers qui est alloue automatiquement, x tant comme expliqu ci-dessous un pointeur constant vers le premier entier de cette zone. Les dix cellules du tableau sont alors accessibles par x[0] x[9]. Il convient de noter quaucune vrication nest faite sur la validit du numro de la case demande du tableau. Laccs une case au del du dernier lment du tableau (ou avant le premier, par exemple x[-1]) peut conduire une erreur, mais il se peut aussi que cela accde dautres variables du programme et dans ce cas, lerreur est trs difcile dtecter... Les tableaux de plusieurs dimensions existent galement, par exemple :
char pix[128][256]

Laccs aux cases de ce tableau se fait alors par pix[i][j], i et j tant des entiers. Il faut voir ce type dcriture comme laccs un tableau une seule dimension de 128 lments qui sont chacuns des tableaux de 256 caractres. Laffectation de valeurs initiales dans un tableau une dimension se fait au moyen dune liste de valeurs entre accolades : 518

26.5. Tableaux et pointeurs


int a[10]={1,2,3,4};

Dans cet exemple, a[0], a[1], a[2] et a[3] sont initialiss, a[4] a[9] ne le sont pas. On peut omettre dcrire la taille du tableau, le compilateur la calcule alors en comptant le nombre dlments fournis pour linitialisation : int a[]={1,2,3,4} conduit alors un tableau de quatre lments seulement. Pour les tableaux de plusieurs dimensions, le dernier indice correspond au nombre de colonnes du tableau :
int b[2][4]={{1,2,3,4}, {5,6,5,6}}; /* 2 lignes, 4 colonnes */

Dans ce cas, seul le premier indice peut tre omis, le compilateur comptant quil y a deux lignes, mais ne comptant pas les colonnes sur chaque ligne. Il est mme possible dans ce cas de ne pas assigner tous les lments de chaque ligne ! Ainsi
int b[][4]={{1,2}, {5,6,5}}; /* 2 lignes (comptees par le compilateur), 4 colonnes */

conduit un tableau de deux lignes et quatre colonnes dont seuls les deux premiers lments de la premire ligne sont affects, ainsi que les trois premiers lments de la deuxime ligne 17 .
Les pointeurs

Un pointeur est en fait une variable spciale dans laquelle on ne stocke pas la valeur que lon dsire conserver, mais seulement son adresse en mmoire. En C un pointeur sur un objet de type typ est une variable de type not typ *. Ainsi la dclaration int *x; dnit x comme un pointeur sur une valeur de type int, cest--dire comme ladresse mmoire dun entier. Laccs au contenu dun pointeur se fait par drfrenciation avec loprateur toile. La valeur pointe par x peut donc tre accde par lexpression *x. Un pointeur est une variable qui a sa propre adresse, et qui contient ladresse de la variable sur laquelle il pointe. Ainsi, la dclaration int *x; alloue la case mmoire correspondant au pointeur x, mais ninitialise pas sa valeur. Or cette valeur doit tre ladresse de lentier point par *x. En consquence, la drfrenciation de x provoquera probablement une erreur (ou un rsultat stupide...) tant que lon na pas crit explicitement dans le pointeur une adresse valide. Pour initialiser la valeur du pointeur x, il faut ainsi utiliser une adresse mmoire valide, par exemple, celle dune autre variable. cet effet, ladresse dune variable sobtient par loprateur &. Ainsi si lon a dclar
int y,*x;

on peut crire
17. Les variables tableaux globales sont par dfaut initialises zro, ainsi que les variables locales dclares static, en revanche les variables tableaux locales normales (non statiques) NE sont PAS initialises.

519

Chapitre 26. Aide-mmoire de langage C


x = &y;

Le pointeur x pointe alors sur la case mmoire correspondant la valeur de la variable y. Tant que le pointeur x nest pas modi, *x et y ont toujours la mme valeur (cest la mme case mmoire). On peut voir * comme loprateur inverse de &. On a ainsi *(&y) qui vaut y, et de mme &(*x) qui vaut x.
Un pointeur est en fait une variable presque comme les autres. Il sagit dun entier de la taille dune adresse mmoire de la machine (le plus souvent 32 bits). La variable pointeur x de notre exemple prcdent est un entier de signication spciale. Il est donc possible de prendre ladresse dun pointeur ! Le pointeur &x est alors un pointeur vers un pointeur sur un entier, il contient ladresse de ladresse de lentier. Son type est int **.

Ces possibilits sont tout particulirement intressantes pour les appels de fonction. En effet, si on passe en argument dune fonction ladresse dune variable, la fonction va pouvoir modier la variable locale par lintermdiaire de son adresse. Ladresse est recopie dans un pointeur local, on ne peut donc pas la modier, mais en revanche la case mmoire pointe est la mme et peut donc tre modie. Voici un exemple : Listing 26.10 Utilisation des pointeurs
int exemple(int x, int *y) { x += 3; *y += x; return(x); } main() { int a,b,c; a = 2; b = 1; c = exemple(a,&b); printf("a=%d b=%d c=%d\n"); exit(0); }

Dans cet exemple, le programme afche a=2 b=6 c=5. La variable b a t modie par lappel de fonction : on parle alors deffet de bord.
Pour crire une fonction qui modie la valeur dun pointeur (et pas seulement llment point comme ci-dessus), il faudra passer en paramtre cette fonction ladresse du pointeur modier, cest--dire un pointeur sur le pointeur.

520

26.5. Tableaux et pointeurs Lorsque lon dnit des pointeurs sur des structures, il existe un raccourci particulirement utile (st est ici un pointeur sur une structure contenant un champ nomm champ) : (*st).champ est quivalent st->champ, loprateur che sobtenant par la succession des signes - et >.
Les pointeurs sont galement utiles dans les appels de fonction mme si la fonction na pas modier la valeur pointe : il est en effet beaucoup plus efcace de passer une adresse une fonction que de recopier un gros bloc de mmoire (on vite ainsi gnralement de passer des structures entires en paramtre, on prfre un pointeur sur la structure). Laccs aux champs dune structure rfrence par un pointeur est ainsi le cas dcriture le plus gnral, do la syntaxe spciale. Lorsque la fonction ne modie pas lobjet point, on utilisera avantageursement le mot cl const qui permet au compilateur de vrier quaucune criture nest faite directement dans lobjet point (ce nest quune vrication rudimentaire). Une dernire utilisation commune des pointeurs est le cas de fonctions pouvant prendre en paramtre des zones mmoires dont le type nest pas impos. On fait cela au moyen du pointeur gnrique de type void *, qui peut tre affect avec nimporte quel autre type de pointeur. Un exemple typique de cette utilisation est la fonction memset() (dnie dans <string.h>) qui permet dinitialiser un tableau de donnes de type quelconque.

Allocation dynamique de mmoire

Pour pouvoir stocker en mmoire une valeur entire, il faut donc explicitement demander une adresse disponible. Il existe heureusement un autre moyen dobtenir des adresses mmoire valables, cest le rle de la fonction malloc(). Son prototype est void *malloc(size_t size);. Le type size_t est un type spcial, on pourra retenir quil sagit en fait dun entier codant un nombre doctets mmoire. La taille dun objet de type donn sobtient par loprateur sizeof(), appliqu la variable stocker ou son type. Ainsi on pourra crire
int *x; /* on peut aussi crire x=(int *)malloc(sizeof(*x)); */ x = (int *)malloc(sizeof(int)); *x = 2324;

ce qui : 1. Dclare la variable x comme un pointeur sur une valeur de type int. 2. Affecte x une adresse dans la mmoire qui est rserve pour le stockage dune donne de taille sizeof(int), donc de type int. 3. Place la valeur 2324 ladresse x. 521

Chapitre 26. Aide-mmoire de langage C Lappel malloc(s) rserve en mmoire un bloc de taille s. On remarque que la fonction malloc() retourne un pointeur de type void * : on appelle cela un pointeur gnrique (cest le type de pointeur que manipulent les fonctions qui doivent travailler indpendamment de lobjet point). Il faut bien entendu le convertir dans le type du pointeur que lon dsire modier et ceci se fait par transtypage. La mmoire ainsi alloue le reste jusqu la n du programme. Cest pour cela que lorsquon na plus besoin de cette mmoire, il convient de prciser quelle peut nouveau tre utilise. Ceci se fait par la fonction free() dont largument doit tre un pointeur gnrique. La taille na pas besoin dtre prcise car elle est mmorise lors de lappel malloc(). Dans le cas prsent, la libration de la mmoire se ferait par free((void *)x);. Du fait des mthodes utilises pour grer cette allocation dynamique, il est prfrable dviter de faire de trs nombreuses allocations de trs petits blocs de mmoire. Si cela est ncessaire, on crira sa propre fonction de gestion mmoire, en faisant les allocations et librations avec malloc() et free() par gros blocs 18 .
Arithmtique sur les pointeurs

Un pointeur est en fait un entier un peu spcial puisquil contient une adresse mmoire. Certaines oprations arithmtiques sont disponibles an de permettre la manipulation aise de blocs de mmoire contenant plusieurs lments conscutifs du mme type. On dispose ainsi de laddition dun pointeur p et dun entier n qui donne un pointeur de mme type que p, pointant sur le n-ime lment du bloc commenant ladresse pointe par p. Par exemple p+0 vaut p et pointe donc sur llment numro zro, p+1 pointe sur llment suivant... Ceci fonctionne quelle que soit la taille de llment point par p, p+n provoquant ainsi un dcalage dadresses correspondant en octets n fois la taille de lobjet point par p. Il est possible aussi deffectuer la soustraction de deux pointeurs de mme type, et cela donne un rsultat entier qui correspond au nombre dlments qui peuvent tre placs entre ces deux pointeurs. Lexemple suivant montre une faon dinitialiser un bloc mmoire (ce nest pas la mthode la plus simple pour y arriver, mais elle est efcace) : Listing 26.11 Manipulation dadresses
#define TAILLE 1000 typedef struct pixel { unsigned char r,v,b; int x,y;
18. Linefcacit de la gestion par petits blocs vient entre autre de lensemble des paramtres stocks pour chaque malloc() effectu, qui peut atteindre 16 octets ! Do, pour allouer un bloc de 1024*1024 lments de type char, environ 1 Mo si cela se fait par un appel malloc(1024*1024) et environ 17 Mo si cela est fait en 1024*1024 appels malloc(1)...

522

26.5. Tableaux et pointeurs


} pix; main() { pix *p; pix *q; p=(pix *)malloc(sizeof(pix)*TAILLE); for (q=p;q-p<TAILLE;q++) { q->r=q->v=q->b=0; q->x=q->y=0; } ... }

Il existe des similarits entre les tableaux et les pointeurs. En particulier, un tableau une dimension est en fait un pointeur constant (il nest pas possible de modier ladresse sur laquelle il pointe) vers la premire case du tableau. Ainsi il y a une quivalence totale entre les critures p[i] et *(p+i) lorsque p est un pointeur ou un tableau monodimensionnel et i un entier (char, int ou long) 19 .
Attention, il ny a pas quivalence simple entre une drfrenciation de pointeur et un accs un tableau plusieurs dimensions. Il faut en fait voir un tableau deux dimensions int tab[3][5] comme un tableau de trois lments qui sont des tableaux de cinq entiers (et non linverse). Ces trois lments sont stocks les uns la suite des autres en mmoire. Les critures tab[i][j], et *(tab[i]+j), *(*(tab+i) +j) *((int *)tab+5*i+j) sont alors quivalentes, mais il reste fortement conseill pour la lisibilit de se limiter la premire. De faon similaire, le passage dun tableau en paramtre une fonction peut se faire de faon quivalente sous la forme dun pointeur (void test(int *a)) et sous la forme dun tableau sans taille (void test(int a[])). Dans le cas de tableaux plusieurs dimensions... la deuxime solution est trs fortement dconseill, on vous aura prvenu ! Si le type est un simple pointeur vers le dbut du tableau deux dimensions, il faudra donc fournir galement la fonction la taille du tableau (tout ceci peut se mettre dans une jolie structure, comme on le fait par exemple pour des images).

Les chanes de caractres

Les chanes de caractres sont reprsentes en C sous la forme dun pointeur sur une zone contenant des caractres. La n de la chane est marque par le caractre spcial \0. Il est possible dutiliser la dclaration sous forme de tableau : char
19. On a mme quivalence avec i[p] bien que cette dernire criture soit viter !

523

Chapitre 26. Aide-mmoire de langage C


ch[256]; dnit une chane dont la longueur maximale est ici de 256 caractres, en

comptant le caractre de terminaison. En fait, par convention, toute chane de caractre se termine par le caractre nul (pas le chiffre 0, mais lentier 0 not souvent pour cette utilisation \0). La chane de caractres peut aussi se dclarer sous la forme dun pointeur char *, vers une zone quil faut allouer avec malloc(). On peut crire des chanes constantes dlimites par des guillemets. Lexpression "chaine\n" est donc une chane comportant le mot chaine suivi dun retour chariot, et comme toujours termine par un caractre \0. La fonction strlen() (dclare dans <string.h> dans la bibliothque standard) retourne la longueur de la chane s jusquau premier code \0 20 . La fonction strcmp(s1,s2) permet de comparer deux chanes. Elle retourne -1 si lexicographiquement s1<s2, 0 si les deux chanes sont identiques, et +1 si s1>s2.
Pour lanecdote, cette fonction est quivalente strlen() ( !) :
int mystrlen(char *s) { int longueur; for (longueur=0; *s++;longueur++) ; return longueur; }

Les structures autorfrentielles

Les structures autorfrentielles 21 sont des structures dont un des membres est un pointeur vers une structure du mme type. On les utilise couramment pour programmer des listes chanes et pour les arbres. Voici un exemple simple mais complet, dans lequel la liste initiale est alloue en un bloc conscutif et chane, puis ensuite utilise comme une liste chane quelconque : Listing 26.12 Structures autorfrentielles
#include <stdio.h> #include <stdlib.h> typedef struct liste { struct liste *suivant; int valeur; } tliste;

main() {
20. Il sagit du nombre de caractres effectivement utiles dans la chane et non de la taille du bloc mmoire allou comme le retournerait sizeof(). 21. On dit aussi parfois structures rcursives par abus de langage.

524

26.5. Tableaux et pointeurs


tliste *maliste; tliste *temp; int i,som; maliste=(liste *)malloc(100*sizeof(liste)); /* chainage des elements de la liste */ for(i=0;i<99;i++) { (maliste+i)->suivant=(maliste+i+1); (maliste+i)->valeur=i; }; (maliste+99)->valeur=99; (maliste+99)->suivant=NULL; som=0; /* somme des elements de la liste */ /* fonctionne par le chainage et */ /* non par les positions en memoire */ for (temp=maliste; temp; temp=temp->suivant) { som += temp->valeur; } printf("somme: %d\n",som); exit(0); }

Dans la dclaration dune structure autorfrentielle, il nest pas possible dutiliser le type tliste dni par le typedef comme on le fait pour les dclarations de variables du main(), il est possible en revanche dutiliser le type structur struct liste en cours de dclaration.
Les pointeurs de fonction

Il est parfois souhaitable quune fonction gnrique ait besoin de pouvoir effectuer le mme traitement peu de choses prs. Cest par exemple le cas dune fonction de tri gnrique, qui aura besoin dune fonction de comparaison, diffrente selon lapplication 22 . Pour passer une fonction en paramtre une autre fonction, il existe des pointeurs de fonctions, qui sont en fait des pointeurs contenant ladresse mmoire de dbut de la fonction 23 . An que le compilateur connaisse les arguments de la fonction pointe, ceux-ci font partie de la dclaration du pointeur. On aura par exemple :
22. Cest comme cela que fonctionne la fonction de tri qsort(). 23. Dans le cas o la fonction a t dclare inline, cest la version sous forme de fonction standard qui est utilise.

525

Chapitre 26. Aide-mmoire de langage C Listing 26.13 Pointeur de fonctions


#include <stdio.h> #include <stdlib.h> /* fonction qui effectue la derivation numerique partielle */ /* par difference finie dune fonction de x et y selon x */ /* fonc est le nom local de la fonction a deriver */ /* x,y est le point de derivation et h est lintervalle */ float numderiv(float (*fonc) (float, float), float x, float y, float h) { return (fonc(x+h,y)-fonc(x-h,y))/2/h; /* on pourrait aussi ecrire : */ /* return ((*fonc)(x+h,y)-(*fonc)(x-h,y))/2/h; */ /* mais attention aux parentheses ! */ } /* une fonction de deux variables */ float f1(float x, float y) { return x*y*y; } /* La meme fonction inversee */ float f2(float x, float y) { return f1(y,x); } /* une autre fonction */ float f3(float x, float y) { return 3*x*x; } main() { float x,y; x=3.0; y=2.0; printf("x:%f y:%f

df/dx(x,y):%f\n",x,y,f1(x,y), numderiv(f1, x, y, 0.1)); /* on pourrait aussi ecrire numderiv( &f1 , x, y, 0.1)); */ printf("x:%f y:%f f(x,y):%f df/dy(x,y):%f\n",x,y,f2(y,x), numderiv(f2, y, x, 0.1)); printf("x:%f y:%f f3(x,y):%f df3/dx(x,y):%f\n",x,y,f3(x,y), numderiv(f3, x, y, 0.1)); exit(0);

f(x,y):%f

Qui afche : 526

26.6. Le prprocesseur
x:3.000000 y:2.000000 f(x,y):12.000000 x:3.000000 y:2.000000 f(x,y):12.000000 x:3.000000 y:2.000000 f3(x,y):27.000000 df/dx(x,y):3.999996 df/dy(x,y):11.999994 df3/dx(x,y):17.999982

Il est possible de passer une fonction en argument (donc un pointeur de fonction) une autre fonction indiffremment par f1 ou &f1. Ceci car le compilateur sait quil sagit ncessairement dun pointeur, la fonction ntant pas une variable. De la mme faon, lors de son utilisation, les syntaxes fonc() et (*fonc) () sont quivalentes (noter les parenthses dans la deuxime criture, ncessaires du fait des priorits des oprateurs).
Pour les amateurs de programmes illisibles, il est possible dcrire les appels de fonctions normaux de plusieurs faon aussi (en utilisant gcc, ceci nest pas valable sur tous les compilateurs) : f1(y,x) est quivalent (*f1)(y,x), et mme (* (* (&f1))) (y,x).

26.6

Le prprocesseur

Lors de la compilation dun chier source en C, il se droule en fait plusieurs oprations successives. La premire phase est une passe de remplacement textuel uniquement et est effectue par le prprocesseur. Il est possible de voir le code source en sortie du prprocesseur avec la commande gcc -E. Les lignes que le prprocesseur interprte commencent par un dise (#). Les commmandes du prprocesseur stendent chacune sur une ligne entire et une seule ; la seule faon dcrire une commande du prprocesseur sur plusieurs lignes est dempcher linterprtation du caractre de n de ligne en le faisant prcder dun caractre \ (tout fait la n de la ligne). Trois types doprations sont effectues par le prprocesseur : linclusion de chiers, la substitution des macros et la compilation conditionnelle.
Linclusion de chiers

La directive #include <nom de fichier> permet dinclure le texte du chier rfrenc la place de la commande. Deux syntaxes existent pour cette commande : #include <toto.h> et #include "toto.h" qui diffrent par les rpertoires dans lesquels le prprocesseur va chercher toto.h. On retiendra en gnral #include <stdio.h> pour les en-ttes du systme (le compilateur ne va pas chercher dans le rpertoire courant, mais uniquement dans les rpertoires standards du systme tels que /usr/include), et #include "toto.h" pour les en-ttes du projet, cette dernire syntaxe forant le compilateur chercher le chier dabord dans le rpertoire courant. 527

Chapitre 26. Aide-mmoire de langage C


Les macros (#dene)

Une macro permet un remplacement dun mot particulier dans tout le texte qui suit. Par exemple, la dnition suivante remplace dans toute la suite le mot test par printf("Ceci est un test\n") :
#define test printf("Ceci est un test\n");

Il nest pas possible dans la suite du code de rednir de la mme faon test, il faut commencer pour cela par supprimer la dnition courante par #undef test. Appliquer #undef un identicateur inconnu nest pas une erreur, cest simplement sans effet.

Les macros permettent galement de dnir des remplacements plus intelligents, avec des arguments variables. Ainsi la fonction MIN dcrite prcdemment (paragraphe sur les oprateurs) peut scrire sous forme de macro :
#define MIN(a,b) ((a)>(b)?(b):(a)) Tout criture dans la suite du texte de MIN(expr1,expr2) fera le remplacement textuel en crivant lexpression de remplacement aprs avoir substitu a le texte de expr1 et b le texte de expr2. La prsence de nombreuses parenthses est chaudement

recommande car dans certains cas la priorit des oprations effectues dans la macro peut tre suprieure celle des oprations faites dans ses arguments. Par exemple, supposons que lon dnisse une macro PRODUIT ainsi :
#define PRODUIT(a,b) a*b

Alors une instance note PRODUIT(2+3, 7); donnera 2 + 3 7 cest dire 23, ce qui nest peut-tre pas leffet recherch. . . Dautres fonctionnalits de remplacement plus nes sont possibles, mais sortent du cadre de cet aide-mmoire. Le prprocesseur tient galement jour des macros spciales qui contiennent la date, lheure, le numro de la ligne en cours et le nom de chier en cours : __DATE__, __TIME__, __LINE__ et __FILE__, qui sont utiles dans le cas dafchage de messages derreur. Notons que les macros __DATE__ et __TIME__ sont remplaces par les valeurs correspondant la date et lheure de compilation et non celles dune ventuelle excution du programme !
Compilation conditionnelle

On souhaite parfois compiler ou non certaines parties dun programme, par exemple sil doit fonctionner sur des machines utilisant des systmes dexpoitation diffrents, mais aussi pour inclure ou non du code de dbogage par exemple. Pour cela le prprocesseur permet dinclure des parties de code en fonction de conditions. La syntaxe est :
#if <condition> <code C a compiler si la condition est vraie> #endif

528

26.6. Le prprocesseur Lorsquune alternative est possible, on peut crire :


#if <condition> <code C a compiler si la condition est vraie> #else <code C a compiler si la condition est fausse> #endif

Dans le cas dalternatives multiples, on a mme :


#if <condition 1> <code C a compiler si la condition 1 est vraie> #elif <condition 2> <code C a compiler si la condition 1 est fausse et 2 vraie> #else <code C a compiler si les deux conditions sont fausses> #endif

Les conditions sont des expressions C boolennes ne faisant intervenir que des identicateurs du prprocesseur. Les expressions de la forme defined(<identificateur>) sont remplaces par 1 si lidenticateur est connu du prprocesseur et par 0 sinon. Comme il est frquent dcrire #if defined(<id>) ou #if !defined(<id>), des formes abrges existent et se notent respectivement #ifdef <id> et #ifndef <id>. Citons deux autres utilisations importantes des remplacements par des macros, qui sont la compilation avec du code de dbogage (debug) optionnel, et la gestion des variables globales. Le petit morceau de code suivant effectue un appel printf() pour afcher un message, mais uniquement si le programme a t compil en dnissant DEBUG. Ceci peut avoir t fait dans un des chiers den-ttes (.h) inclus systmatiquement, ou bien par lajout de loption -DDEBUG pour gcc sur la ligne de compilation. Listing 26.14 Compilation conditionnelle
#include <stdio.h> #ifdef DEBUG #define DEB(_x_) _x_ #else #define DEB(_x_) #endif main() { DEB(printf("Ceci est un message de debug\n")); }

529

Chapitre 26. Aide-mmoire de langage C La macro DEB est ici remplace, soit par ce qui est entre parenthses si DEBUG est dni, soit par rien dans le cas contraire. Cest plus court et plus lisible que :
main() { #ifdef DEBUG printf("Ceci est un message de debug\n"); #endif }

Lautre utilisation importante concerne les variables globales. En effet, celles-ci doivent tre dclares dans UN SEUL chier source (si possible celui qui contient la fonction main()), et doivent avoir une dclaration prcde de extern dans tous les autres sources. Ceci se fait simplement en dclarant cette variable avec une macro EXT dans un chier den-ttes (.h) inclus dans tous les sources. La macro est ensuite dnie comme valant rien du tout si linclusion est faite dans le chier contenant le main(), et comme extern partout ailleurs. Voyons un petit exemple : Ici un chier general.h, inclus dans TOUS les sources :
/* EXT sera remplace par extern partout sauf si MAIN est defini */ #ifdef MAIN # define EXT #else # define EXT extern #endif EXT int monglob1; EXT char *glob2;

Voici le dbut de calcul.c :


#include "general.h" int trescomplique(int a) { return a+monglob1; }

Et le dbut de main.c :
#define MAIN #include "general.h" #include <stdio.h >

530

26.7. Fonctions dentre/sortie


main() { monglob1=3; printf("Resultat: %d\n",trescomplique(2)); }

26.7

Fonctions dentre/sortie

De nombreuses fonctions dentre/sortie existent dans la bibliothque stdio.h. Seules certaines sont dcrites dans cette partie, le reste est prsent dans la section 26.8. Une description plus dtaille de ces fonctions se trouve la n du manuel de rfrence de Kernighan et Ritchie, ou dans le man en ligne sous Unix.
Le passage darguments en ligne de commande

La fonction main() dun programme peut en fait prendre des arguments. La dclaration complte est (les types des arguments sont imposs) :
int main(int argc, char **argv)

On crit aussi frquemment : int main(int argc, char *argv[]) qui a le mrite de rappeler que argv est un tableau. Ces deux paramtres correspondent au nombre darguments de la ligne de commande en comptant le nom de lexcutable (argc) et la liste de ces arguments sous forme de chanes de caractres (argv[1], argv[2], . . ., argv[argc-1]). La premire chane argv[0] contient le nom dappel du programme. Il est ainsi possible de passer des arguments en ligne de commande un programme. Ces arguments peuvent tre convertis avec les fonctions de conversion de la bibliothque standard (#include <stdlib.h> : cf. Annexe) :
double atof(char *); int atoi(char *); long atol(char *);

Ou grce aux fonctions dentre mise en forme que nous allons voir maintenant.
Entre/sortie simples

Tout ce qui suit est subordonn linclusion du chier den-tte stdio.h qui dclare les fonctions dentre-sortie ainsi que la structure FILE. Les deux sections qui suivent donnent un aperu des fonctions dentre-sortie sans les dtailler. On se reportera lannexe qui suit pour de plus amples prcisions. Les fonctions dentre-sortie oprent sur des ots (de type FILE), qui doivent avoir t dclars au pralable. Lentre standard, la sortie standard et la sortie derreur sont 531

Chapitre 26. Aide-mmoire de langage C automatiquement ouverts et se nomment respectivement stdin, stdout et stderr 24 . Par dfaut lentre standard est le clavier, et les sorties sont lcran. Les oprateurs de redirection du shell permettent nanmoins daltrer ceci en redirigeant ces accs vers/depuis un chier/autre programme. Pour des ots associs des chiers, il faut au pralable les ouvrir avec la fonction
fopen(). Une bonne rgle respecter est que tout ot ouvert par un programme doit tre ferm par fclose() ds quil ne sert plus.

Les ots sont des abstractions qui permettent de ne pas avoir reprciser chaque utilisation dun chier tous les paramtres : une fois le chier ouvert, toutes les oprations ultrieures font rfrence ce chier par le ot, ce qui est beaucoup plus rapide car la bibliothque standard (et aussi le systme dexploitation) retient ltat courant du chier (position dans le chier, emplacement sur le disque...). Cette abstraction permet galement de travailler sur dautres objets squentiels en utilisant les mmes fonctions, cest le cas par exemple pour les tuyaux entre processus, pour les sockets rseau...

Les fonctions fread() et fwrite() permettent de lire des blocs de donnes dans un ot sans aucune interprtation (cf. section 26.8). Si lon souhaite plutt lire un chier ligne par ligne, il est prfrable dutiliser fgets() qui offre lavantage de sarrter aux ns de ligne (caractre \n), et garantit tout de mme que la longueur de la chane de stockage ne peut pas tre dpasse. Dans les cas o le but est de traiter les caractres un un sans attendre, il faut utiliser getc() et putc() (ou leurs quivalents getchar() et putchar() pour les ots dentre-sortie standards). La fonction ungetc() qui permet dimplanter simplement un look-ahead 25 de faon simple peut galement rendre service.

24. En fait, cette proprit nest assure que pour un systme dexploitation Unix, mme si la plupart des compilateurs C fonctionnant dans dautres environnements tentent de simuler ce comportement. 25. Littralement regarder en avant, cest une mthode de dcision du type de lexme qui suit partir de la lecture du premier caractre uniquement.

532

26.7. Fonctions dentre/sortie


Les fonctions fopen(), fread(), fprintf(), fflush()... sont des fonctions prsentes dans la bibliothque standard, et sont donc normalement disponibles sur tous les systmes dexploitation. Ces fonctions sont crites en C et prcompiles dans un chier darchive qui forme la bibliothque standard, et qui est ajout automatiquement lors de la compilation. crire fopen() est en fait un simple appel de fonction vers cette bibliothque, dont le code excutable fait partie du programme. son tour, ces fonctions (fopen() par exemple) utilisent des services fournis par le systme dexploitation pour effectuer leur tche. Ces services sont sollicits via un ou plusieurs appels systme qui demandent au noyau du systme dexploitation douvrir le chier voulu. Sous Unix, lappel systme (dont lutilisation est similaire un appel de fonction) utilis ainsi est open(), qui retourne un entier appel descripteur de chier Unix. Cette valeur de retour est stocke dans lobjet FILE an den disposer pour les manipulations ultrieures. Quelques autres appels systme concernant les chiers sous Unix sont read(), write(), close()... Il est fortement recommand de ne pas mlanger pour un mme ot des appels aux fonctions de la bibliothque standard et des appels systme Unix, car il ny aurait dans ce cas aucune garantie que lordre des oprations soit respect ( cause des tampons en mmoire maintenus par la bibliothque standard).

Entres/sorties avec format : fprintf(),

fscanf()

Pour obtenir des rsultats joliment mis en forme, la commande utiliser est
fprintf( flot, format, variables...). Lorsque flot est la sortie standard, un raccourci existe : printf( format, variables...). Le format est une chane

de caractres qui dcrit la faon dafcher les variables qui suivent ; par exemple printf("x=%d y=%f\n",x,y); afche x= suivi de la valeur de la variable entire x, puis une espace puis y= suivi de la valeur de la variable ottante y et un retour chariot. La fonction printf est une fonction particulire qui prend un nombre variable darguments. Les codes utiliser dans le format indiquent le type de la variable et sont prsents en annexe. Ainsi pour afcher un nombre complexe, on crira par exemple printf("z=%f+i%f\n",Re(z),Im(z));. La valeur de retour de fprintf() est le nombre de caractres effectivement crits, ou une valeur ngative en cas derreur. La lecture de valeur se fait par la fonction fscanf(flot, format, adresses des variables...). Le principe est similaire fprintf(), except que les adresses des variables doivent tre donnes an que fscanf() puisse aller modier leur valeur. Lorsque flot est lentre standard, on peut utiliser scanf(format, adresses des variables...). Ainsi pour lire en entre une variable ottante float x; suivie dune chane de caractres char cha[1024];, on crira scanf("%f%s",&x, cha); La variable x est passe par adresse, et la chane aussi puisquil sagit dun pointeur, donc dune adresse mmoire. La fonction fscanf() retourne le nombre de champs quelle a pu lire et convertir (ventuellement zro), ou la constant speciale EOF (qui vaut gnralement -1) si une 533

Chapitre 26. Aide-mmoire de langage C erreur de lecture ou la n du chier se produit avant la premiere conversion.
Les caractres autres que des caractres de formatage et les espaces prsents dans le format de scanf() doivent correspondre exactement aux caractres entrs au clavier. Il faut en particulier viter de mettre un espace ou un \n dans ce format. Les caractres despacement sont considrs par fscanf() comme des sparateurs de champs. Il nest donc pas possible de rcuprer dun coup une chane de caractres comportant des blancs avec cette fonction. Cest pourquoi on utilisera plutt char *fgets(char *s, int n, FILE *f) qui lit une ligne jusquau retour chariot ou jusqu ce quil y ait n caractres et stocke le rsultat dans s (elle retourne s galement sil ny a pas eu derreur).

Il ne faut pas perdre de vue que le systme de chiers dUnix est bufferis. Ainsi pour lentre standard, celle-ci nest prise en compte que ligne par ligne aprs un retour chariot 26 . De mme pour la sortie standard 27 . Lorsquon crit dans un chier, ou si la sortie standard est redirige, la bufferisation est encore plus importante puisque la taille du tampon peut atteindre plusieurs kilooctets. Pour forcer lcriture, on utilise fflush(). Cette fonction est utile, car si un programme narrive pas son terme (arrt intempestif de la machine, ou erreur lexcution par exemple) les buffers sont irrmdiablement perdus. Enn la fonction fclose() permet de fermer un chier, en vidant auparavant son buffer.

26.8

Complments : la bibliothque standard et quelques fonctions annexes

La bibliothque standard ne fait pas partie proprement parler du langage C, mais elle a t standardise par la norme ANSI et est devenue une norme ISO. Elle offre donc un ensemble de dclarations de fonctions, de dclarations de types et de macros qui sont communes tous les compilateurs qui respectent la norme, quel que soit le systme dexploitation. Quelques exemples prsents ici sont nanmoins des extensions de cette bibliothque standard spciques au monde Unix. De nombreuses fonctions de la bibliothque standard reposent sur des appels systmes Unix qui leur ressemblent, comme open(), close(), read() ou lseek() par exemple. Si la portabilit vers des environnements non-Unix nest pas de mise, il pourra tre intressant dans certains cas dutiliser plutt ces dernires fonctions. Toutes les dclarations de la bibliothque standard gurent dans des chiers dentte :
26. Il est possible, mais ceci est spcique Unix, de reparamtrer le terminal an de recevoir les caractres un un sans attendre un retour chariot. 27. L un fflush() permet de forcer lcriture nimporte quand. Il reste nanmoins prfrable dutiliser stderr pour les sorties derreurs non-bufferises.

534

26.8. Complments : la bibliothque standard et quelques fonctions annexes <assert.h> <ctype.h> <errno.h> <oat.h> <limits.h> <locale.h> <math.h> <setjmp.h> <signal.h> <stdarg.h> <stddef.h> <stdio.h> <stdlib.h> <string.h> <time.h>

Ces chiers doivent tre inclus avant toute utilisation dune fonction de la bibliothque par une directive du type #include <fichier_entete.h>. Ils peuvent tre inclus dans un ordre quelconque, voire plusieurs fois dans un mme source. Ils contiennent des dclarations de fonctions que le compilateur connat par dfaut pour la plupart, except pour <math.h> qui dclare des fonctions de la bibliothque mathmatique quil faut indiquer ldition de lien de faon explicite (voir 26.2). Les identicateurs masqus lutilisateur lintrieur de cette bibliothque ont des noms commenant par un caractre de soulignement _. Il est donc recommand dviter dutiliser ce type de dnomination pour viter dventuels conits. Seules les fonctions principales sont prsentes ici. Pour des informations plus dtailles, on se reportera aux pages de manuel en ligne sous Unix, accessibles par man fgets par exemple pour la fonction fgets(). Mais une fonction comme open() est galement le nom dune commande Unix, aussi man open ne retournera pas la page voulue. Dans ce cas, on prcisera la section du manuel correspondante, ici la section 2 (pour les appels systme) ou 3 (pour les fonctions de la bibliothque). Ceci scrit man 2 open ou man 3 fprintf.

<stdio.h> : entres/sorties
Il sagit de la partie la plus importante de la bibliothque. Sans cela un programme ne peut afcher ou lire des donnes de faon portable. La source ou la destination des donnes est dcrite par un objet de type FILE, appel ot. Ce type structur, dont il nest pas utile de connatre les diffrents champs (ils peuvent dailleurs diffrer entre les versions de la bibliothque) est associ un chier sur disque ou un autre priphrique comme le clavier ou lcran. Il existe des ots binaires et des ots de texte, mais sous Unix ceux-ci sont identiques 28 . Un ot est associ un chier par ouverture, ceci retourne un pointeur sur un type FILE. la n des oprations sur le ot, il faut le fermer. Au dbut de lexcution du programme, trois ots spciaux, nomms stdin pour lentre standard, stdout pour la sortie standard et stderr pour la sortie derreur sont dj ouverts. En labsence doprations spciales, stdin correspond au clavier, stdout et stderr correspondent lcran.
Ouverture/fermeture de chiers
FILE *fopen(const char *nomfich, const char *mode) Ouvre le chier indiqu (le nom est absolu ou relatif au rpertoire courant, mais sans remplacement des carac-

28. Sous DOS, les ots texte convertissent les squences CR-LF en LF seul (codes \r\n en \n seul), sous Mac OS X, ils convertissent le CR en LF (code \r en \n), sous Unix, ils convertissent les LF en LF !

535

Chapitre 26. Aide-mmoire de langage C tres spciaux comme ~, ? ou *) et retourne un ot, ou NULL en cas derreur. Largument mode est une chane de caractres parmi :
r w a r+ w+ a+

lecture seule, la lecture commence au dbut du chier. criture seule. Si le chier nexistait pas, il est cr ; sil existait, il est cras. (append) similaire w mais crit la n du chier sans lcraser sil existait. lecture et criture. Le ot est positionn au dbut du chier. le chier est cre (ou cras sil existait), et ouvert en lecture et criture ; le ot est positionn au dbut. similaire r+, mais le ot est initialement positionn la n du chier.

Dans le cas des modes lecture/criture, il faut appeler fflush() ou une fonction de dplacement entre les oprations de lecture et dcriture. Lajout de b la n du mode indique un chier binaire, cest--dire sans conversion automatique des ns de ligne (cela na aucun effet sous Unix, voir la note 28). FILE *freopen(const char *nomfich, const char *mode, FILE *flot) est une fonction qui sert associer aux ots prdnis stdin, stdout ou stderr un chier. int fflush(FILE *flot) Cette fonction nest appeler que sur un ot de sortie (ou dentre-sortie). Elle provoque lcriture des donnes mises en mmoire tampon. Elle retourne zro en labsence derreur, EOF sinon. int fclose(FILE *flot) Provoque lcriture des donnes en attente, puis ferme le ot. La valeur de retour est zro en labsence derreur, EOF sinon. FILE *fdopen(int fd, const char *mode) Ouvre un ot correspondant au descripteur de chier Unix fd. Ncessite un descripteur de chier valide (par exemple issu de open()). Le descripteur nest pas dupliqu, aussi il faut au choix soit terminer par close(fd) 29 , soit par fclose(flot), mais surtout pas les deux. Ne fait pas partie de la norme ANSI. int fileno( FILE *flot) Retourne lentier descripteur de chier Unix associ au ot. Ne fait pas partie de la norme ANSI.
Positionnement et erreurs
int fseek(FILE *flot, long decalage, int origine) Positionne la tte de lecture dans le ot flot. Lentier origine peut valoir SEEK_SET, SEEK_CUR ou SEEK_END selon que la position est donne respectivement partir du dbut du chier, de la position courante, ou de la n. decalage reprsente le dcalage demand en nombre doctets (attention au signe, une valeur ngative faisant revenir en arrire). fseek() retourne une valeur non nulle en cas derreur, zro en cas de succs.

29. Dans ce cas, les tampons de mmoire utiliss par les fonctions de la bibliothque dentres / sorties standard ne seront pas vids et il risque dy avoir perte de donnes.

536

26.8. Complments : la bibliothque standard et quelques fonctions annexes


long ftell(FILE *flot) Donne la valeur de la position courante dans le chier (en nombre doctets depuis le dbut du chier), ou la valeur -1L (-1 sous forme de long) en cas derreur. void rewind(FILE *flot) Retourne au dbut du chier. On pourrait raliser cette action en utilisant (void)fseek(flot, 0L, SEEK_SET). int feof(FILE *flot) Retourne une valeur non nulle si lindicateur de n de chier est actif, cest--dire si la dernire opration sur le ot a atteint la n du chier. int ferror(FILE *flot) Retourne une valeur non nulle si lindicateur derreur du ot est actif. Toutes les fonctions qui prennent en argument un ot (de type FILE *) ncessitent que ce ot soit valide. En particulier, il ne faut pas faire f=fopen(...) puis appeler immdiatement ferror(f), car f peut valoir NULL si louverture du chier a chou. la solution est alors :

FILE *f; f=fopen("mon_fichier","r"); if (f==NULL) { perror("Impossible douvrir le fichier\n"); exit(EXIT_FAILURE); }


void clearerr(FILE *flot) Remet zro les indicateurs de n de chier et derreur. Cest une fonction qui ne sert quasiment jamais. void perror(const char *s) Afche sur stderr la chane s et un message derreur correspondant lentier contenu dans la variable globale errno, donc issu de la dernire erreur rencontre. perror(s) est quivalent :

(void)fprintf(stderr, "%s : %s\n", strerror(errno) , s)


int fgetpos(FILE flot, fpos_t *pos) int fsetpos(FILE *flot, fpos_t *pos)

Ce sont dautres fonctions permettant de relire la position dans le chier (comme ftell(flot)) ou de limposer (comme fseek(flot,SEEK_SET)). Cette position est stocke dans un objet de type fpos_t (sous Unix, cest un long, mais ce peut tre une structure complexe sur dautres systmes dexploitation). Ces deux fonctions ne peuvent gure servir qu lire la position dans un chier pour y revenir plus tard. Il arrive parfois que lon souhaite raccourcir un chier dj ouvert. Cela nest pas possible avec les fonctions de la bibliothque standard mais peut se faire avec lappel ftruncate() (cf manuel en ligne). 537

Chapitre 26. Aide-mmoire de langage C


Entre/sortie directe
size_t est un type (correspondant un entier) qui exprime une taille mmoire (en octet, issue par exemple de sizeof(), comme pour malloc()) ou un nombre dlments.
size_t fread(void *ptr, size_t size, size_t nobj, FILE *flot)

Lit sur le ot nobj objets de taille size et les crit lemplacement mmoire point par ptr. La valeur de retour est le nombre dobjets lus avec succs, qui peut tre infrieur nobj. Pour dterminer sil y a eu erreur ou si la n du chier a t atteinte, il faut utiliser feof() ou ferror().
size_t fwrite(const void *ptr, size_t size, size_t nobj, FILE *flot)

crit sur le ot nobj objets de taille size pris lemplacement point par ptr. Retourne le nombre dobjets crits.
Entre/sortie de caractres
int fgetc(FILE *flot) Lit un caractre sur le ot (unsigned char converti en int) et le retourne. Retourne EOF en cas de n de chier ou derreur (EOF est une constante entire dnie par stdio.h, valant gnralement -1). int fputc(int c, FILE *flot) crit le caractre c (converti en unsigned char) sur le ot. Retourne ce caractre ou EOF en cas derreur. int getc(FILE *flot) quivalent fgetc(), mais ventuellement implant sous forme de macro. Plus efcace que fgetc en gnral. Attention, lvaluation de flot ne doit provoquer deffet de bord (utiliser une variable pour flot et non le retour dune fonction). int putc(int c, FILE *flot) quivalent fputc(). Mme remarque que pour getc(). int getchar(void) quivalent getc(stdin). int putchar(int c) quivalent putc(c, stdout). int ungetc(int c, FILE *flot) Remet le caractre c (converti en unsigned char) sur le ot, o il sera retrouv la prochaine lecture. Un seul caractre peut tre ainsi remis, et cela ne peut pas tre EOF. Retourne le caractre en question ou EOF en cas derreur. char *fgets(char *s, int n, FILE *flot) Lit une ligne complte de caractres (jusquau caractre de n de ligne compris) dau plus n 1 caractres et place cette chane partir de la case mmoire pointe par s. Si un caractre de n de ligne est lu, il est stock dans la chane. Le caractre \0 est alors ajout la chane prcdemment lue. Retourne s si tout sest bien pass, ou bien NULL en cas derreur ou si la n du chier est atteinte sans aucun caractre disponible le prcdant. int fputs(const char *s, FILE *flot) crit la chane de caractres s sur le ot (ncrit pas le caractre \0 de n de chane). Retourne une valeur positive ou nulle, ou bien EOF en cas derreur.

538

26.8. Complments : la bibliothque standard et quelques fonctions annexes Il existe une fonction gets() similaire fgets(), mais son usage est fortement dconseill. En effet cette fonction est quivalente fgets() avec n inni ce qui ouvre grand les portes du dbordement de mmoire.

Entre/sortie mises en forme


int fprintf(FILE *flot, const char *format, ...) Est la commande de sortie mise en forme. Il sagit dune fonction nombre variable darguments (Voir 26.8). La chane format dcrit le texte crire, dans lequel des suites de caractres spciaux commenant par % sont remplaces par les valeurs des arguments qui suivent dans lordre. La valeur entire de retour est le nombre de caractres crits, ou en cas derreur un nombre ngatif. La chane de caractres format contient des caractres ordinaires (sans %) qui sont copis tels quels sur le ot de sortie, et des directives de conversion dont chacune peut provoquer la conversion dans lordre dun argument de fprintf() suivant la chane de format. Chaque directive de conversion commence par le caractre % et se termine par un caractre de conversion parmi ceux prsents dans le tableau qui suit. Entre le % et le caractre de conversion peuvent apparatre dans lordre : Des drapeaux, parmi : # format de sortie alternatif. cadrage gauche du champ. + ajout systmatique du signe aux nombres. (espace) ajout dun caractre pour le signe des nombres, soit lespace pour les valeurs positives ou le signe - pour les valeurs ngatives. 0 (zro) complte le dbut du champ par des zros au lieu despaces pour les nombres. Un nombre prcisant la largeur minimale du champ dimpression en nombre de caractres. Une prcision sous la forme dun point suivi dun nombre (la valeur de cette prcision est considre comme zro en labsence de ce champ). Il sagit du nombre maximal de caractres imprimer pour une chane de caractres, ou du nombre minimal de chiffres pour les conversions d, i, o, u, x et X. Pour les conversions e, E et f cela donne le nombre de chiffres aprs le point dcimal, et sinon cela correspond au nombre maximal de chiffres signicatifs pour les conversions g et G. Enn une lettre h, l ou L qui modie le type de largument converti. h spcie un argument short, l spcie un argument long, et L un argument long double (ou long long pour les conversions entires, bien que ceci ne fasse pas partie de la norme ANSI). Voici enn les caractres de conversion :

539

Chapitre 26. Aide-mmoire de langage C


d, i, o, u, x, X

e, E

g, G c s p n

Variante dint. d : dcimal. u : dcimal non sign. o : octal. x : hexadcimal non sign utilisant abcdef. X : hexadcimal non sign utilisant ABCDEF. double crit en dcimal sous la forme [-]m.ddddddEdd. La prcision donne le nombre de chiffre aprs le point de la mantisse, la valeur par dfaut est 6. e utilise la lettre e et E utilise E. double crit sous la forme [-]ddd.ddd. La prcision donne le nombre de chiffre aprs le point dcimal, sa valeur par dfaut est 6. Si la prcision vaut 0, le point dcimal est supprim. double crit en dcimal comme par une conversion par f ou e selon la valeur de largument. int converti en unsigned char et afch comme un caractre ASCII. char *. Chane de caractres. void *. Pointeur crit en hexadcimal (comme avec %#lx). int *. Ne convertit pas dargument, mais crit dans largument le nombre de caractres afchs jusqu prsent. Afche simplement un % sans aucune conversion.

Chaque directive de conversion doit correspondre exactement un argument de type correct dans le mme ordre.
Les erreurs sur les types des arguments ou leur nombre ne sont gnralement pas dtects par le compilateur et peuvent provoquer des rsultats surprenants lexcution.
int fscanf(FILE *flot, const char *format, ...) Est la commande dentre mise en forme. Il sagit aussi dune fonction nombre variable darguments. La chane format dcrit les objets lire et leur type, et ceux-ci sont stocks dans les arguments qui suivent qui doivent tre obligatoirement des pointeurs. La valeur entire de retour est le nombre dlments dentre correctement assigns (compris entre 0 et le nombre de directives de conversion). Si la n de chier est atteinte avant toute lecture, la valeur EOF est renvoye. Si la n de chier est atteinte en cours de lecture, la fonction retourne le nombre dlments lus sans erreur. La chane de caractres format contient des caractres ordinaires (sans %) qui doivent correspondre exactement aux caractres lus sur le ot dentre (excepts les espaces et les tabulations qui sont ignors). Elle contient galement des directives de conversion formes dans lordre par : %,

540

26.8. Complments : la bibliothque standard et quelques fonctions annexes un caractre facultatif *, qui, lorsquil est prsent, empche laffectation de la conversion un argument (le champ est lu et sa valeur ignore, aucun pointeur nest utilis dans les arguments de la fonction), un caractre facultatif h, l ou L qui spcie une variante de taille de type (voir ci-dessous), un caractre de conversion parmi ceux prsents dans le tableau qui suit :
d i o u x f, e, g c

p n

[...]

[...]

variante dint *, lu sous forme dcimale variante dint *, la lecture est en octal si lentier commence par 0, en hexadcimal sil commence par 0x ou 0X, en dcimal sinon variante dint *, lu sous forme octale, quil commence ou non par un 0 Variante dunsigned int *, lu sous forme dcimale variante dunsigned int *, lu sous forme hexadcimale. float * (ou double *). Les trois caractres sont quivalents char *, lit les caractres suivants et les crit partir de ladresse indique jusqu la largeur du champ si celle-ci est prcise. En labsence de largeur de champ, lit 1 caractre. Najoute pas de \0. Les caractres despacement sont lus comme des caractres normaux et ne sont pas sauts char *, chane de caractres lue jusquau prochain caractre despacement, en sarrtant ventuellement la largeur du champ si elle est prcise. Ajoute un \0 la n void *, pointeur crit en hexadcimal int *, ne convertit pas de caractres, mais crit dans largument le nombre de caractres lus jusqu prsent. Ne compte pas dans le nombre dobjets convertis retourn par fscanf() char *, la plus longue chane de caractre non vide compose exclusivement de caractres gurant entre les crochets. Un \0 est ajout la n. Pour inclure ] dans cet ensemble, il faut le mettre en premier [ ]...] char *, la plus longue chane de caractre non vide compose exclusivement de caractres NE gurant PAS entre les crochets. Un \0 est ajout la n. Pour inclure ] dans cet ensemble, il faut le mettre en premier []...] lit le caractre %. Ne ralise aucune affectation

Les variantes de taille de type sont plus compltes que celles pour fprintf() : h prcise un short et l un long pour les conversions d, i, n, o, u, x. l prcise un double et L un long double pour les conversions e, f, g. Ainsi pour lire un double on utilise scanf("%lg",&d) mais pour limprimer on utilise printf("%g",d). Un champ en entre est dni comme une chane de caractres diffrents des caractres despacement ; il stend soit jusquau caractre despacement suivant, soit 541

Chapitre 26. Aide-mmoire de langage C jusqu ce que la largeur du champ soit atteinte si elle est prcise. En particulier, scanf() peut lire au-del des limites de lignes si ncessaire, le caractre de n de ligne tant un caractre comme les autres. Chaque directive de conversion doit correspondre exactement un pointeur vers un argument de type correct dans le mme ordre.
Mme remarque que pour fprintf(). De plus, si les pointeurs de destination nont pas le bon type (ou pire encore sil ne sont mme pas des pointeurs), le rsultat lexcution risque dtre droutant...
int printf(const char *format, ...)

est quivalent :

fprintf(stdout, format, ...)


int scanf(const char *format, ...)

est quivalent :

fscanf(stdin, format, ...)


int sprintf(char *s, const char *format, ...) est similaire fprintf() except que lcriture se fait dans la chane de caractres s plutt que dans un ot. Attention, la taille de la chane s doit tre sufsante pour stocker le rsultat (y compris le dernier caractre \0 invisible de n de chane). Dans le cas contraire, on risque davoir des erreurs surprenantes lexcution. int sscanf(char *s, const char *format, ...) Est similaire fscanf() except que la lecture se fait dans la chane de caractres s plutt que dans un ot.

<ctype.h> : tests de caractres


Ces fonctions retournent une valeur non nulle si largument remplit la condition, et zro sinon. Toutes ces fonctions ont comme prototype int isalnum(int c), et c, bien qutant un entier doit contenir une valeur reprsentable comme un unsigned char.
isalnum() isalpha() iscntrl() isgraph() islower() isprint() ispunct() isspace() isupper() isxdigit()

isalpha() ou isdigit() est vrai. isupper() ou islower() est vrai. caractre de contrle. caractre imprimable sauf lespace. lettre minuscule. caractre imprimable y compris lespace. caractre imprimable diffrent de lespace, des lettres et chiffres. espace, saut de page, de ligne, retour chariot, tabulation. lettre majuscule. chiffre hexadcimal. 542

26.8. Complments : la bibliothque standard et quelques fonctions annexes

<string.h> : chanes de caractres et blocs mmoire


Chanes de caractres
char *strcpy(char *s, const char *ct) Copie la chane ct, y compris le caractre \0 dans le chane s, retourne s. viter si les tailles de chanes ne sont pas connues et quil risque dy avoir dbordement. char *strncpy(char *s, const char *ct, size_t n) Copie au plus n caractres de ct dans s, en compltant par des \0 si ct comporte moins de n caractres. char *strcat(char *s, const char *ct) Concatne ct la suite de s, retourne s. viter si les tailles de chanes ne sont pas connues et quil risque dy avoir dbordement. char *strncat(char *s, const char *ct, size_t n) Concatne au plus n caractres de ct la suite de s, termine la chane rsultat par \0, retourne s. char *strcmp(const char *cs, const char *ct) Compare lexicographiquement cs et ct. Retourne une valeur ngative si cs<ct, nulle si cs==ct, positive sinon. char *strncmp(char *s, const char *ct, size_t n) Comme strcmp() mais limite la comparaison aux n premiers caractres. char *strchr(char *s, int c) Retourne un pointeur sur la premire occurrence de c (converti en char) dans cs, ou NULL si il ny en a aucune. char *strrchr(char *s, int c) Comme strchr mais pour la dernire occurrence. char *strstr(const char *cs,const char *ct) Retourne un pointeur sur la premire occurrence de la chane ct dans cs, ou NULL si il ny en a aucune. size_t strlen(const char *cs) Retourne la longueur de cs, sans compter le caractre de terminaison \0. char *strerror(int n) Retourne un pointeur sur la chane correspondant lerreur n. char *strtok(char *s, const char *ct) Dcomposition de s en lexmes spars par des caractres de ct. Le premier appel la fonction se fait en prcisant dans s la chane de caractres dcomposer. Il retourne le premier lexme de s compos de caractres nappartenant pas ct, et remplace par \0 le caractre qui suit ce lexme. Chaque appel ultrieur strtok() avec NULL comme premier argument retourne alors le lexme suivant de s selon le mme principe (le pointeur sur la chane est mmoris dun appel au suivant). La fonction retourne NULL lorsquelle ne trouve plus de lexme. Cette fonction conservant un tat dans une variable locale statique nest pas rentrante. Une version strtok_r() stockant ltat dans une variable fournie par le programmeur existe a cet effet.

Cette fonction, dusage peu orthodoxe (utilisant des variables locales statiques...) est extrmement efcace car elle est gnralement crite directement en assembleur. Ne pas croire la page de manuel Linux qui dit never use this function ! Il est quand mme recommand dutiliser strsep() ou strtok_r(). . .

543

Chapitre 26. Aide-mmoire de langage C


Blocs de mmoire

copie n octets de ct dans s et retourne s. Attention, les zones manipules ne doivent pas se chevaucher.
void *memmove(void *s, const void *ct, size_t n)

void *memcpy(void *s, const void *ct, size_t n)

mme rle que la fonction

memcpy(), mais fonctionne galement pour des zones qui se chevauchent. memmove() est plus lente que memcpy() lorsque les zones ne se chevauchent pas.
int memcmp(const void *cs, const void *ct, size_t n) compare les zones de la mmoire pointes par cs et ct concurrence de n octets. Les valeurs de retour suivent la mme convention que pour strcmp().

crit loctet c (un entier convertit en char) dans les n premiers octets de la zone pointe par s. Retourne s.

void *memset(void *s, int c, size_t n)

<math.h> : fonctions mathmatiques


La bibliothque correspondant ces fonctions nest pas ajoute automatiquement par le compilateur et doit donc tre prcise lors de ldition de liens par loption -lm. Lorsquune fonction devrait retourner une valeur suprieure au maximum reprsentable sous la forme dun double, la valeur retourne est HUGE_VAL et la variable globale derreur errno reoit la valeur ERANGE. Si les arguments sont en dehors du domaine de dnition de la fonction, errno reoit EDOM et la valeur de retour est non signicative. An de pouvoir tester ces erreurs, il faut aussi inclure <errno.h>. Toutes les fonctions trigonomtriques prennent leur argument ou retournent leur rsultat en radians. Les valeurs de retour sont toutes des double, ainsi que les arguments des fonctions du tableau suivant : 544

26.8. Complments : la bibliothque standard et quelques fonctions annexes


sin() cos() tan() asin() acos() atan() atan2(y,x) sinh() cosh() tanh() exp() log() log10() pow(x,y) sqrt() floor() ceil() fabs()

sinus. cosinus. tangente. arc sinus. arc cosinus. arc tangente (dans [/2, /2]). pseudo arc tangente de y/x, tenant compte des signes, dans [, ]. sinus hyperbolique. cosinus hyperbolique. tangente hyperbolique. exponentielle. logarithme nprien. logarithme base 10. x la puissance y. Attention au domaine : (x > 0) ou (x = 0, y > 0) ou (x < 0 et y entier). racine carre. Partie entire exprime en double. Partie entire plus 1 exprime en double. Valeur absolue.

Pour obtenir la partie entire dun float f sous la forme dun entier, on utilise le transtypage en crivant par exemple : (int)floor( (double)f ).

<stdlib.h> : fonctions utilitaires diverses


double atof(const char *s) Convertit s en un double. int atoi(const char *s) Convertit s en un int. long atol(const char *s) Convertit s en un long. void srand(unsigned int graine) Donne graine comme nouvelle amorce du gnrateur pseudo-alatoire. Lamorce par dfaut vaut 1. int rand(void) Retourne un entier pseudo-alatoire compris entre 0 et RAND_MAX, qui vaut au minimum 32767. void *calloc(size_t nobj, size_t taille) Allocation dune zone mmoire correspondant un tableau de nobj de taille taille. Retourne un pointeur sur la zone ou NULL en cas derreur. La mmoire alloue est initialise par des zros. void *malloc(size_t taille) Allocation dune zone mmoire de taille taille (issu de sizeof() par exemple). Retourne un pointeur sur la zone ou NULL en cas derreur. La mmoire alloue nest pas initialise. void *realloc(void *p, size_t taille) Change en taille la taille de lobjet point par p. Si la nouvelle taille est plus petite que lancienne, seul le dbut de la zone est conserv. Si la nouvelle taille est plus grande, le contenu de lobjet est conserv

545

Chapitre 26. Aide-mmoire de langage C et la zone supplmentaire nest pas initialise. En cas dchec, realloc() retourne un pointeur sur le nouvel espace mmoire, qui peut tre diffrent de lancien, ou bien NULL en cas derreur et dans ce cas le contenu point par p nest pas modi. Si p vaut NULL lors de lappel, le fonctionnement est quivalent malloc(taille). void free(void *p) Libre lespace mmoire point par p. Ne fait rien si p vaut NULL . p doit avoir t allou par calloc(), malloc() ou realloc(). void exit(int status) Provoque larrt du programme. Les fonctions atexit() sont appeles dans lordre inverse de leur enregistrement, les ot restant ouverts sont ferms. La valeur de status est retourn lappelant 30 . Une valeur nulle de status indique que le programme sest termin avec succs. On peut utiliser les valeurs EXIT_SUCCESS et EXIT_FAILURE pour indiquer la russite ou lchec. void abort(void) Provoque un arrt anormal du programme. int atexit(void (*fcn)(void)) Enregistre que la fonction fcn() devra tre appele lors de larrt du programme. Retourne une valeur non nulle en cas derreur. int system(const char *s) Sous Unix provoque lvaluation de la chane s dans un Bourne-shell (sh). La valeur de retour est la valeur de retour de la commande, ou bien 127 ou -1 en cas derreur. Lappel system(NULL) retourne zro si la commande nest pas utilisable. Cette commande, parfois utile en phase de prototype, est proscrire dans un projet nalis : elle peut bloquer les interruptions du programme et, passant des variables denvironnement /bin/sh, conduit frquemment des erreurs ou des problmes de scurit. On lui prfrera une solution moins intgre fonde sur fork() et exec(), plus efcace et plus sure. Comme pour wait() la valeur de retour est encode et il faudra donc utiliser WEXITSTATUS() pour obtenir la vraie valeur status. char *getenv(const char *nom) Rcupre la dclaration de la variable denvironnement dont le nom est nom. La valeur de retour est un pointeur vers une chane du type nom = valeur. fonction de tri (par lalgorithme Quicksort) du tableau base[0]..base[n 1] dont les objets sont de taille taille. La fonction de comparaison comp(x,y) utilise doit retourner un entier plus grand que zro si x > y, nul si x == y et ngatif sinon. permet la recherche parmi base[0]..base[n 1] (objets de taille taille tris par ordre croissant pour la fonction de comparaison fournie), dun objet sidentiant la cl *key. La valeur de retour est un pointeur sur llment identique *key, ou NULL sil nen existe aucun. La fonction de comparaison comp() utilise doit obir aux mmes rgles que pour qsort().
30. Sous Unix si le programme a t lanc depuis un shell csh ou tcsh, la valeur de status est stocke dans la variable du shell de mme nom ou dans la variable $? quand on utilise bash.
void *bsearch(const void *key, const void *base, size_t n,\ size_t taille, int (*comp) (const void *, const void *)) Cette fonction void qsort(void *base, size_t n, size_t taille, \ int (*comp) (const void *, const void *)) Il sagit dune

546

26.8. Complments : la bibliothque standard et quelques fonctions annexes Valeur absolue dun lment de type int. long labs(long l) Valeur absolue dun lment de type long. Pour les ottants, voir fabs() dans <math.h>.
int abs(int n)

<assert.h> : vrications lexcution


Il sagit de la dclaration dune macro assert permettant de vrier des conditions lors de lexcution. On crit pour cela dans le corps du programme :
assert( <expression> );

Lors de lexcution, si lvaluation de <expression> donne zro (expression fausse) la macro envoie sur stderr un message donnant lexpression, le chier source et la ligne du problme et arrte le programme en appelant abort(). Si NDEBUG est dni avant linclusion de <assert.h>, la macro assert na aucun effet.

<limits.h> , <oat.h> : constantes limites


<limits.h> CHAR_BIT CHAR_MIN, CHAR_MAX SCHAR_MIN, SCHAR_MAX UCHAR_MIN, UCHAR_MAX INT_MIN, INT_MAX UINT_MIN, UINT_MAX SHRT_MIN, SHRT_MAX USHRT_MIN, USHRT_MAX LONG_MIN, LONG_MAX ULONG_MIN, ULONG_MAX <oat.h> FLT_DIG FLT_EPSILON FLT_MIN FLT_MAX nombre de chiffres signicatifs pour un oat plus petit oat f tel que f + 1.0 = 1.0 plus petit nombre reprsentable plus grand nombre reprsentable nombre de bits par caractre valeurs min et max dun char valeurs min et max dun signed char valeurs min et max dun unsigned char valeurs min et max dun int valeurs min et max dun unsigned int valeurs min et max dun short valeurs min et max dun unsigned short valeurs min et max dun long valeurs min et max dun unsigned long

Les mmes constantes existent pour les double, en crivant DBL_ au lieu de FLT_. 547

Chapitre 26. Aide-mmoire de langage C

<stdarg.h> : fonctions nombre variable darguments


Les dclarations de <stdarg.h> permettent de construire des fonctions ayant une liste darguments de longueur inconnue et ayant des types inconnus la compilation. La dclaration dune telle fonction se fait comme une fonction normale, mis part que lon crit des points de suspension aprs le dernier argument. La fonction doit avoir au moins un argument nomm. On dclare ensuite lintrieur de la fonction une variable de type va_list, et on linitialise en appelant void va_start(va_list vl, last) avec comme arguments la va_list et le dernier argument nomm de la fonction. Pour accder ensuite chaque lment de la liste darguments, on appelle la macro va_arg(va_list vl, type) qui retourne la valeur du paramtre. type est le nom du type de lobjet lire. Enn il ne faut pas oublier dappeler void va_end(va_list vl) la n de la fonction. Comme petit exemple, voici une fonction qui ajoute tous ses arguments, chaque argument tant prcd de son type int ou double : Listing 26.15 Somme darguments en nombre variable
#include <stdio.h> #include <stdarg.h> /* un type enumere decrivant les types ! */ typedef enum {INT, DOUBLE} montype; /* chaque element est ecrit */ /* sous la forme "type" puis "valeur" */ /* le premier argument est le nombre delements */ double somme (int nb, ...) { va_list vl; float f=0.0; int i; int vali; float valf; va_start(vl, nb); for (i=0;i<nb;i++) { switch (va_arg(vl,montype)) { case INT: vali=va_arg(vl,int); printf("Ajout de lentier %d\n",vali); f += (float)vali; break; case DOUBLE:

548

26.8. Complments : la bibliothque standard et quelques fonctions annexes


valf=va_arg(vl,double); printf("Ajout du flottant %f\n",valf); f += valf; break; default: printf("Type inconnu\n"); } } va_end(vl); return f; } main() { int a,b,c,d; double e,f,g; double result; a=2; b=3; c=-5; d=10; e=0.1; f=-32.1; g=3.14e-2; result=somme(7, INT,a,DOUBLE,e,DOUBLE,f, INT,b,INT,c,INT,d,DOUBLE,g); printf("Somme: %f\n",result); exit(0); }

Qui afche lexcution :


Ajout de lentier Ajout du flottant Ajout du flottant Ajout de lentier Ajout de lentier Ajout de lentier Ajout du flottant Somme: -21.968597 2 0.100000 -32.099998 3 -5 10 0.031400

Un dernier exemple classique, une fonction similaire printf() : Listing 26.16 Un quivalent printf()
#include <stdio.h> #include <stdarg.h> /* mini fonction printf */ /* elle utilise printf ... mais */

549

Chapitre 26. Aide-mmoire de langage C


/* lidee est tout de meme la */ int monprintf(char *format, ...) { va_list vl; int nb=0; va_start(vl, format); do { if (*format != %) { nb++; putchar(*format); continue; } format++; switch (*format) { case d: nb+=printf("%d",(int)va_arg(vl,int)); break; case s: nb+=printf("%s",(char *)va_arg(vl,char *)); break; case f: nb+=printf("%f",(double)va_arg(vl,double)); break; default: fprintf(stderr,"Probleme de format"); } } while (*(++format) !=\0); va_end(vl); return nb; } main() { int a; int t; char s[30]; double b,c; a=1; b=2.0; c=a+b; sprintf(s,"CQFD");

550

26.8. Complments : la bibliothque standard et quelques fonctions annexes


monprintf("Voila : %d + %f = %f ; %s\n",a,b,c,s); exit(0); }

Lexcution du programme donne alors :


Voila : 1 + 2.000000 = 3.000000 ; CQFD

551

Index Gnral

Cet index regroupe les diffrents termes, expressions, acronymes, voqus dans la premire partie sur les systmes dexploitation.

Compare And Swap, 201 Crossbar, 192, 194 Kernel-Level-Thread, 201 Test And Set, 201 User-Level-Thread, 201 threads, 132, 148, 189, 193, 195, 197, 199, 201204, 206, 207 Mac OS X, 95, 172, 213 Solaris, 52, 90, 180, 197, 202, 207, 216 Windows 3.11, 91, 92 Windows 95, 90, 9294 Windows 98, 90, 92, 93 Windows NT, 90, 9294, 132, 173 Windows Server 2000, 90, 93, 94, 96 Windows Server 2003, 94 Windows Seven, 90, 94, 148 Windows Vista, 90, 94, 206 Windows Vista, 148 Windows XP, 86, 90, 94, 148 Windows, 206 BKL, 195 DSS, 195 IPI, 194 MPI, 191 MQS, 195 553

NUMA, 194 PVM, 191 UMA, 195 SMP, 192

adresse mmoire, 31, 154 adresse physique, 45 adresse virtuelle, 46 ALU, 28 appel systme, 58, 119 ASCII, 100 assemblage, 108 assembleur, 108 benchmark, 47 bibliothque dynamique, 114 bibliothque partage, 114 bibliothque statique, 113 BSB, 37 bus, 36 bus dadresses, 36 bus de commandes, 36 bus de communication, 36 bus de donnes, 36 cache, 31

Index Gnral changement de contexte, 83, 140 CISC, 43 compilateur, 104 compilation, 104, 107 compteur dinstructions, 122, 138 compteur ordinal, 122 context switching, 140 contrleur de priphrique, 33 dition de liens, 108, 111 tat des processus, 137 executable, 103 chier, 100 chier ASCII, 100 chier binaire, 100 chier executable, 103 chier texte, 100 ls dactivit, 189 FSB, 37 heap, 135 Hurd, 95 interruptions, 38 IRQ, 39 ISO 8859-1, 100 Java, 203 jeu dinstructions, 41 mmoire cache, 31 mmoire virtuelle, 89 mmoire vive, 30 Mach, 95 man, 59 microprocesseur, 28 MMU, 28 mode noyau, 40, 58 mode utilisateur, 40, 58 modem, 35 moniteur rsident, 77, 87 MS-DOS, 90 multi-tches, 81, 88 554 multi-utilisateurs, 88 multiprogrammation, 80 NorthBridge, 194 off-line, 78 ordonnancement, 123, 139, 141 ordonnancement dynamique, 80 ordonnancement par priorits, 142 ordonnancement par tourniquet, 142 parallle, 82 partage du temps, 81, 123 PCI, 37 PCIe, 37 priphrique, 27 perror errno, 244 man perror, 243 perror(), 243 pile, 133 pipeline, 44 prprocesseur, 105 preprocessing, 105 processus, 103, 122 programme, 104 ps man ps, 127 ps -ef, 127 ps -ax, 127 rentrant, 114 reboot, 89 registres, 28 ressource, 57 retour sur investissement, 76 RISC, 43 round robin, 142 scheduler, 123 scheduling, 123, 139 segment de code, 133 segment de donnes, 133 service, 66

Index Gnral SPOOL, 79 stack, 133 stat(), 260 table des processus, 138 tas, 135 temps partag, 123 TLB, 194 traitement hors-ligne, 78 traitement par lots, 76 Unix, 84 UTF-8, 101 ZFS, 180

555

Index Programmation

Cet index regroupe les entres (commandes, fonctions) de la quatrime partie de ce poly concernant laide mmoire du langage C.

cc, 419 Classes de stockage extern, 428 register, 427 static, 428 volatile, 428 Fonctions
abort(), 472 abs(), 473 atexit(), 472 atof(), 457, 472 atoi(), 457, 472 atol(), 457, 472 bsearch(), 473 calloc(), 472 clearerr(), 463 close(), 461 exit(), 472 fclose(), 458, 460, 462 fdopen(), 462 feof(), 463 ferror(), 463 fflush(), 458, 460, 462 fgetc(), 464 fgetpos(), 464

fgets(), 458, 465 fileno(), 463 fopen(), 458, 462 fprintf(), 458, 459, 465 fputc(), 464 fputs(), 465 fread(), 458, 464 free(), 472 freopen(), 462 fscanf(), 459, 466 fseek(), 463 fsetpos(), 464 ftell(), 463 fwrite(), 458, 464 getc(), 458, 464 getchar(), 458, 464 getenv(), 473 labs(), 473 lseek(), 461 malloc(), 447, 472 memcmp(), 471 memcpy(), 470 memmove(), 471 memset(), 471 open(), 461

557

Index Programmation
perror(), 463 printf(), 459, 469 putc(), 458, 464 putchar(), 458, 464 qsort(), 473 rand(), 472 read(), 461 realloc(), 472 rewind(), 463 scanf(), 469 sprintf(), 469 srand(), 472 sscanf(), 469 strcat(), 470 strchr(), 470 strcmp(), 450, 470 strcpy(), 469 strerror(), 470 strlen(), 450 strncat(), 470 strncmp(), 470 strncpy(), 470 strrchr(), 470 strsep(), 470 strstr(), 470 strtok(), 470 strtok_r(), 470 system(), 472 ungetc(), 458, 465 CFLAGS, 424 LDFLAGS, 424 make tst, 423

makedepend, 420, 424 Types


char, 425 double, 426 float, 426 int, 425 long double, 426 long, 426 short, 426

xxgdb, 419

gcc, 419
-lm, 421 gcc -E, 453 gcc -M, 420, 424 gcc -O, 420 gcc -Wall, 419 gcc -ansi, 419 gcc -g, 419 gcc -pedantic, 419 gcc -static, 420

gdb, 419 make, 420, 422 558

Bibliographie

[1]

Maurice J. BACH. The Design of the UNIX Operating System. Prentice Hall, mai 1986. URL : http://www.informit.com/store/product.aspx?isbn=0132017997. David R. B UTENHOF. Programming with POSIX R Threads. Addison-Wesley Professional, mai 1997. URL : http://www.informit.com/store/product.aspx?isbn=9780201633924. Donald L EWINE. POSIX Programmers Guide. OReilly Media., avril 1991. URL : http://oreilly.com/catalog/9780937175736/. Marshall Kirk M C K USICK, Keith B OSTIC, Michael J. K ARELS et John S. Q UARTERMAN. The Design and Implementation of the 4.4BSD Operating System. Addison-Wesley Professional, avril 1996. URL : http://www.informit.com/store/product.aspx?isbn=0201549794. Marshall Kirk M C K USICK et George V. N EVILLE -N EIL. The Design and Implementation of the FreeBSD Operating System. Addison-Wesley Professional, aot 2004. URL : http://www.informit.com/store/product.aspx?isbn=0201702452. Bradford N ICHOLS, Dick B UTTLAR et Jacqueline Proulx FARRELL. Pthreads Programming. OReilly Media., septembre 1996. URL : http://oreilly.com/catalog/9781565921153/.

[2]

[3]

[4]

[5]

[6]

559

Bibliographie [7] W. Richard S TEVENS. UNIX Network Programming. Networking APIs : Sockets and XTI. 2e dition. Tome 1. Prentice Hall, janvier 1998. URL : http://www.kohala.com/start/unpv12e.html. W. Richard S TEVENS. UNIX Network Programming. Interprocess Communications. 2e dition. Tome 2. Prentice Hall, septembre 1999. URL : http://www.kohala.com/start/unpv12e.html. W. Richard S TEVENS et Stephen A. R AGO. Advanced Programming in the UNIX R Environment. 2e dition. Addison-Wesley Professional, juin 2005. URL : http://www.apuebook.com/. Andrew S. TANENBAUM. Modern Operating Systems. 3e dition. Prentice Hall, dcembre 2007. URL : http://www.pearsonhighered.com/educator/product/ModernOperating-Systems/9780136006633.page. Andrew S. TANENBAUM et Albert S. W OODHULL. Operating Systems. Design and Implementation. 3e dition. Prentice Hall, janvier 2006. URL : http://www.pearsonhighered.com/educator/product/OperatingSystems-Design-and-Implementation/9780131429383.page.

[8]

[9]

[10]

[11]

560

Вам также может понравиться