Vous êtes sur la page 1sur 269

Programmation Avance sous Linux

Mark Mitchell Jerey Oldham Alex Samuel

Traduction : Sbastien Le Ray

Programmation Avance Sous Linux


PREMIRE DITION : Juin 2001 Copyright c 2001 New Riders Publishing. Ce document peut tre distribu selon les termes et conditions de lOpen Publication License, Version 1, sans ajout (la dernire version de cette licence est disponible sur http://www.opencontent.org/openpub/). ISBN : 0-7357-1043-0 Numro Catalogue de la Bibliothque du Congrs : 00-105343 05 04 03 02 01 7 6 5 4 3 2 1 Interprtation du code : Le nombre deux chires lextrme droite est lanne dimpression du livre ; le nombre un chire lextrme droite est le rang dimpression du livre. Par exemple, le code 01-1 signie que la premire impression du livre a t ralise en 2001.

Marques Dposes
Toutes les marques cites sont proprits de leurs auteurs respectifs. PostScript est une marque dpose par Adobe Systems, Inc. Linux est une marque dpose par Linus Torvalds.

Non-Responsabilit et Avertissement
Ce livre est destin fournir des informations sur la Programmation Avance Sous Linux. Un soin particulier t apport sa compltude et sa prcision, nanmoins, aucune garantie nest fournie implicitement. Les informations sont fournies en ltat . Les auteurs, traducteurs et New Riders Publishing ne peuvent tre tenus pour responsables par quiconque de toute perte de donnes ou dommages occasionns par lutilisation des informations prsentes dans ce livre ou par celle des disques ou programmes qui pourraient laccompagner.

Traduction
Le plus grand soin a t apport la traduction en Franais de ce livre, toutefois, des erreurs de traduction peuvent avoir t introduites en plus des ventuelles erreurs initialement prsente. Les traducteurs ne peuvent tre tenus pour responsables par quiconque de toute perte de donnes ou dommages occasionns par lutilisation des informations prsentes dans ce livre. La version originale de ce document est disponible sur le site des auteurs http://www. advancedlinuxprogramming.com

ii

Table des matires


I Programmation UNIX Avance avec Linux
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1
5 5 7 10 12 14 17 17 28 34 41 41 43 48 50 55 56 62 64 68 80 82

1 Pour Commencer 1.1 Lditeur Emacs . . . . . . . . . . . . . . 1.2 Compiler avec GCC . . . . . . . . . . . . 1.3 Automatiser le Processus avec GNU Make 1.4 Dboguer avec le Dbogueur GNU (GDB) 1.5 Obtenir Plus dInformations . . . . . . . .

2 crire des Logiciels GNU/Linux de Qualit 2.1 Interaction Avec lEnvironnement dExcution . . . . . . . . . . . . . . . . . . . . 2.2 Crer du Code Robuste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.3 crire et Utiliser des Bibliothques . . . . . . . . . . . . . . . . . . . . . . . . . . 3 Processus 3.1 Aperu des Processus 3.2 Crer des Processus . 3.3 Signaux . . . . . . . . 3.4 Fin de Processus . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

4 Threads 4.1 Cration de Threads . . . . . . . . . . . . . . . 4.2 Annulation de Thread . . . . . . . . . . . . . . 4.3 Donnes Propres un Thread . . . . . . . . . . 4.4 Synchronisation et Sections Critiques . . . . . . 4.5 Implmentation des Threads sous GNU/Linux 4.6 Comparaison Processus/Threads . . . . . . . . 5 Communication Interprocessus 5.1 Mmoire Partage . . . . . . . 5.2 Smaphores de Processus . . . 5.3 Mmoire Mappe . . . . . . . . 5.4 Tubes . . . . . . . . . . . . . . 5.5 Sockets . . . . . . . . . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . .

. . . . .

. . . . .

. . . . . iii

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

85 . 86 . 90 . 93 . 98 . 104

II

Matriser Linux

115
119 120 120 121 123 126 131 132 135 136 137 145 146 148 151 153 154 155 156 158 159 161 161 163 164 166 167 168 169 171 171 173 174 174 175 179 179

6 Priphriques 6.1 Types de Priphriques . 6.2 Numros de Priphrique 6.3 Fichiers de Priphriques 6.4 Priphriques Matriels . 6.5 Priphriques Spciaux . . 6.6 PTY . . . . . . . . . . . . 6.7 ioctl . . . . . . . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

7 Le Systme de Fichiers /proc 7.1 Obtenir des Informations partir 7.2 Rpertoires de Processus . . . . . 7.3 Informations sur le Matriel . . . 7.4 Informations sur le Noyau . . . . 7.5 Lecteurs et Systmes de Fichiers 7.6 Statistiques Systme . . . . . . .

de /proc . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

8 Appels Systme Linux 8.1 Utilisation de strace . . . . . . . . . . . . . . . . . . . . 8.2 access : Tester les Permissions dun Fichier . . . . . . . 8.3 fcntl : Verrous et Oprations sur les Fichiers . . . . . . . 8.4 fsync et fdatasync : Purge des Tampons Disque . . . . . 8.5 getrlimit et setrlimit : Limites de Ressources . . . . . . . 8.6 getrusage : Statistiques sur les Processus . . . . . . . . . 8.7 gettimeofday : Heure Systme . . . . . . . . . . . . . . . 8.8 La famille mlock : Verrouillage de la Mmoire Physique 8.9 mprotect : Dnir des Permissions Mmoire . . . . . . . 8.10 nanosleep : Pause en Haute Prcision . . . . . . . . . . . 8.11 readlink : Lecture de Liens Symboliques . . . . . . . . . 8.12 sendle : Transferts de Donnes Rapides . . . . . . . . . 8.13 setitimer : Crer des Temporisateurs . . . . . . . . . . . 8.14 sysinfo : Rcupration de Statistiques Systme . . . . . 8.15 uname . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 Code Assembleur en Ligne 9.1 Quand Utiliser du Code Assembleur . . . . 9.2 Assembleur en Ligne Simple . . . . . . . . . 9.3 Syntaxe Assembleur Avance . . . . . . . . 9.4 Problmes dOptimisation . . . . . . . . . . 9.5 Problmes de Maintenance et de Portabilit iv

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

10 Scurit 10.1 Utilisateurs et Groupes . . . . . . . 10.2 User et Group ID de Processus . . 10.3 Permissions du Systme de Fichiers 10.4 ID Rels et Eectifs . . . . . . . . 10.5 Authentier les utilisateurs . . . . 10.6 Autres failles de scurit . . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

181 181 183 184 188 191 194

III

Annexes

201

A Autres Outils de Dveloppement 205 A.1 Analyse Statique de Programmes . . . . . . . . . . . . . . . . . . . . . . . . . . . 205 A.2 Dtection des Erreurs dAllocation Dynamique . . . . . . . . . . . . . . . . . . . 206 A.3 Prolage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214 B E/S B.1 B.2 B.3 B.4 B.5 B.6 de Bas Niveau Lire et crire des Donnes . . . . . . . . stat . . . . . . . . . . . . . . . . . . . . criture et Lecture Vectorielles . . . . . Lien avec les Functions dE/S Standards Autres Oprations sur les Fichiers . . . Lire le Contenu dun Rpertoire . . . . . 225 225 233 235 237 237 238 241

. . . . . . . . . du C . . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

C Tableau des Signaux

D Ressources en Ligne 243 D.1 Informations Gnrales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243 D.2 Informations sur les Logiciels GNU/Linux . . . . . . . . . . . . . . . . . . . . . . 243 D.3 Autres Sites . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244 E Open Publication License Version 1.0 I. Conditions Applicables aux Versions Modies ou II. Copyright . . . . . . . . . . . . . . . . . . . . . . III. Porte de cette Licence . . . . . . . . . . . . . . . IV. Conditions Applicables aux Travaux Modis . . V. Bonnes Pratiques . . . . . . . . . . . . . . . . . . VI. Options Possibles . . . . . . . . . . . . . . . . . . VII. Annexe la Licence Open Publication . . . . . . F Licence Publique Gnrale GNU 245 245 245 246 246 246 247 247 249

Non . . . . . . . . . . . . . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

Propos des Auteurs


Mark Mitchell a obtenu un bachelor of arts en informatique Harvard en 1994 et un master of science Standford en 1999. Ses recherches se concentrent sur la complexit de calcul et la scurit informatique. Mark a particip activement au dveloppement de la GNU Compiler Collection et sapplique dvelopper des logiciels de qualit. Jerey Oldham a obtenu un bachelor of arts en informatique lUniversit Rice en 1991. Aprs avoir travaill au Center for Research on Parallel Computation, il a obtenu un doctorat en philosophie Standford en 2000. Ses recherches sont centres sur la conception dalgorithmes en particulier les algorithmes de ux et combinatoires. Il travaille sur GCC et des logiciels de calcul scientique. Alex Samuel a t diplm de Harvard en 1995 en physique. Il a travaill en tant quingnieur informatique BBN avant de retourner tudier la physique Caltech et au Standford Linear Accelerator Center. Alex administre le projet Software Carpentry et travaille sur divers autres projets comme les optimisations dans GCC. Mark et Alex ont fond ensemble CodeSourcery LLC en 1999. Jerey a rejoint lentreprise en 2000. Les missions de CodeSourcery sont de fournir des outils de dveloppement pour GNU/Linux et dautres systme dexploitation ; faire de lensemble des outils GNU une suite de qualit commerciale respectant les standards ; et de fournir des services gnraux de conseil et dingnierie. Le site Web de CodeSourcery est disponible ladresse http://www.codesourcery. com

vi

Propos des Relecteurs Techniques


Ces relecteurs ont mis disposition leur expertise tout au long du processus de cration du livre Programmation Avance sous Linux. Au fur et mesure de lcriture du livre, ces professionnels ont pass en revue tous les documents du point de vue de leur contenu technique, de leur organisation et de leur enchanement. Leur avis a t critique an de nous assurer que Programmation Avance sous Linux corresponde aux besoins de nos lecteurs avec une information technique de grande qualit. Glenn Becker a beaucoup de diplmes, tous dans le thtre. Il travaille actuellement en tant que producteur en ligne pour SCIFI.COM, la composante en ligne de la chane SCI FI, New York. Chez lui, il utilise Debian GNU/Linux et est obsd par des sujets comme ladministration systme, la scurit, linternationalisation de logiciels et XML. John Dean a obtenu un BSc(Hons) lUniversit de Sheeld en 1974, en sciences thoriques. Lors de son premier cycle Sheeld, John a prouv de lintrt pour linformatique. En 1986, il a obtenu un MSc au Craneld Institute of Science and Technology dans le domaine des commandes et systmes commands. Alors quil travaillait pour Rolls Royces et Associs, John sest engag dans le dveloppement de logiciels de contrle pour linspection assiste par ordinateur des units de production de vapeur nuclaires. Depuis quil a quitt RR&A en 1978, il a travaill pour lindustrie ptrochimique dans le dveloppement et la maintenance de logiciels de contrle de processus. John a travaill en tant que dveloppeur volontaire pour MySQL de 1996 Mai 2000, il a alors rejoint MySQL comme employ plein temps. Le champ de responsabilits de John concerne MySQL sous MS Windows et le dveloppement de nouveaux clients graphiques pour MySQL utilisant le kit de dveloppement Qt de Trolltech, sous Windows et sur les plateformes disposant de X-11..

vii

Remerciements
Nous apprcions grandement le travail de pionniers de Richard Stallman, sans qui il ny aurait jamais eu de projet GNU et de Linus Torvalds, sans qui il ny aurait jamais eu de noyau Linux. Sans compter ceux qui ont travaill sur le systme dexploitation GNU/Linux, nous les remercions tous. Nous remercions les facults de Harvard et Rice pour nos premiers cycles et Caltech et Standford pour notre prparation au diplme. Sans tous ceux qui nous ont enseign, nous naurions jamais os enseigner aux autres ! W. Richard Stevens a crit trois livres excellents sur la programmation sous UNIX et nous les avons consults sans retenue. Roland McGrath, Ulrich Drepper et beaucoup dautres ont crit la bibliothque GNU C et la documentation laccompagnant. Robert Brazile et Sam Kendall ont relu les premiers jets de ce livre et ont fait des suggestions pertinentes sur le ton et le contenu. Nos diteurs techniques et relecteurs (en particulier Glenn Becker et John Dean) ont dbusqu nos erreurs, fait des suggestions et nous ont continuellement encourag. Bien sr, sil reste des erreurs, ce nest pas de leur faute ! Merci Ann Quinn, de New Riders, pour la gestion de tous les dtails concernant la publication dun livre ; Laura Loveall, galement de New Riders, de ne pas nous avoir laiss dpasser de trop nos deadlines ; et Stphanie Wall, elle aussi de New Riders, pour avoir t la premire nous encourager crire ce livre !

viii

Dites nous ce que vous en pensez !


En tant que lecteur de ce livre, vous tes les critiques et les commentateurs les plus importants. Nous tenons compte de votre opinion et voulons savoir ce que nous faisons bien et ce que nous pourrions mieux faire, dans quels domaines vous aimeriez que nous publiions et tout autre conseil que vous voudriez nous communiquer. En tant quditeur Excutif pour lquipe de Dveloppement Web au sein de New Riders Publishing, jattends vos commentaires. Vous pouvez me contacter par fax, email ou mcrire directement pour me faire savoir ce que vous aimez ou pas dans ce livre mais galement ce que nous pourrions faire pour rendre nos livres plus pertinents. Notez sil vous plat que je ne peux pas vous aider sur des problmes techniques lis au contenu de ce livre, et quen raison du volume important de courrier que je reois, il ne mest pas forcment possible de rpondre tous les messages. Lorsque vous crivez, assurez-vous sil vous plat dinclure le titre et lauteur de ce livre, ainsi que votre nom et votre numro de tlphone ou de fax. Je lirai vos commentaires avec attention et les partagerai avec lauteur et les diteurs qui ont travaill sur ce livre. Fax : Courriel : Adresse : 317-581-4663 Stephanie.Wall@newriders.com Stephanie Wall Executive Editor New Riders Publishing 201 West 103rd Street Indianapolis, IN 46290 USA

ix

Introduction
GNU/Linux a envahi le monde des ordinateurs comme une tempte. Il fut un temps o les utilisateurs dordinateurs taient obligs de faire leur choix parmi des systmes dexploitation et des applications propritaires. Ils navaient aucun moyen de corriger ou damliorer ces programmes, ne pouvaient pas regarder sous le capot , et taient souvent forcs daccepter des licences restrictives. GNU/Linux et dautres systmes open source ont chang cet tat de faits dsormais, les utilisateurs de PC, les administrateurs et les dveloppeurs peuvent choisir un environnement complet gratuit avec des outils, des applications et laccs au code source. Une grande partie du succs de GNU/Linux est lie sa nature open source. Comme le code source des programmes est disponible pour le public, tout un chacun peut prendre part au dveloppement, que ce soit en corrigeant un petit bogue ou en dveloppant et distribuant une application majeure complte. Cette opportunit a attir des milliers de dveloppeurs comptents de par le monde pour contribuer de nouveaux composants et amliorations pour GNU/Linux, un tel point que les systmes GNU/Linux modernes peuvent rivaliser avec les fonctionnalits de nimporte quel systme propritaire et les distributions incluent des milliers de programmes et dapplications se rpartissant sur plusieurs CD-ROM ou DVD. Le succs de GNU/Linux a galement avalis la philosophie UNIX. La plupart des Interfaces de Programmation dApplication (Application Programming Interfaces, API) introduites dans les UNIX AT&T et BSD survivent dans Linux et constituent les fondations sur lesquelles sont construits les programmes. La philosophie UNIX de faire fonctionner de concert beaucoup de petits programmes orients ligne de commande est le principe dorganisation qui rend GNU/Linux si puissant. Mme lorsque ces programmes sont encapsuls dans des interfaces graphiques simples utiliser, les commandes sous-jacentes sont toujours disponibles pour les utilisateurs conrms et les scripts. Une application GNU/Linux puissante exploite la puissance de ces API et de ces commandes au niveau de son fonctionnement interne. Les API GNU/Linux donnent accs des fonctionnalits sophistiques comme la communication interprocessus, le multithreading et la communication rseau hautes performances. Et beaucoup de problmes peuvent tre rsolus simplement en assemblant des commandes et des programmes existants au moyen de scripts.

GNU et Linux
Do vient le nom GNU/Linux ? Vous avez certainement dj entendu parler de Linux auparavant et vous avez peut-tre entendu parler du Projet GNU. Vous navez peut-tre jamais xi

entendu le nom GNU/Linux, bien que vous soyez probablement familier avec le systme auquel il se rfre. Linux vient de Linus Torvalds, le crateur et lauteur original du noyau1 la base du fonctionnement dun systme GNU/Linux. Le noyau est le programme qui eectue les oprations de base dun systme dexploitation : il contrle et fait linterface avec le matriel, gre lallocation de la mmoire et des autres ressources, permet plusieurs programmes de sexcuter en mme temps, gre le systme de chiers et caetera. Le noyau en lui-mme nore pas de fonctionnalits utiles lutilisateur. Il ne peut mme pas acher une invite pour que celui-ci entre des commandes lmentaires. Il nore aucun moyen de grer ou dditer les chiers, de communiquer avec dautres ordinateurs ou dcrire des programmes. Ces tches requirent lutilisation dune large gamme dautres produits comme les shells de commande, les utilitaires de chiers, les diteurs et les compilateurs. La plupart de ces programmes utilisent leur tour des bibliothques de fonctions comme la bibliothque contenant les fonctions standards du C qui ne sont pas incluses dans le noyau. Sur les systmes GNU/Linux, la plupart de ces programmes sont des logiciels dvelopps dans le cadre du Projet GNU2 . Une grande partie de ces logiciels ont prcd le noyau Linux. Le but du projet GNU est de dvelopper un systme dexploitation de type UNIX qui soit un logiciel libre (daprs le site Web du Projet GNU, http://www.gnu.org). Le noyau Linux et les logiciels du Projet GNU ont prouv quils sont une combinaison puissante. Bien que cette association soit souvent appele Linux par raccourci, le systme complet ne pourrait fonctionner sans les logiciels GNU, pas plus quils ne pourraient fonctionner sans le noyau. Pour cette raison, dans ce livre, nous ferons rfrence au systme complet par le nom GNU/Linux, except lorsque nous parlerons spciquement du noyau Linux.

La Licence Publique Gnrale de GNU


Les codes sources contenus dans ce livre son couverts par la Licence Publique Gnrale GNU (GNU General Public Licence, GPL), qui est reproduite dans lAppendice F, Licence Publique Gnrale GNU . Un nombre important de logiciels libres, en particulier ceux pour GNU/Linux, sont couverts par celle-ci. Par exemple, le noyau Linux lui-mme est sous GPL, comme beaucoup dautres programmes et bibliothques GNU que vous trouverez dans les distributions GNU/Linux. Si vous utilisez des sources issues de ce livre, soyez sr davoir bien lu et bien compris les termes de la GPL. Le site Web du Projet GNU comprend une discussion dans le dtail de la GPL (http://www. gnu.org/copyleft/copyleft.fr.html) et des autres licences de logiciels libres. Vous trouverez plus dinformations sur les licences open source sur http://opensource.org/licenses/.

Qui Est Destin ce Livre ?


Ce livre est destin trois types de lecteurs :
1 2

NdT : le terme original kernel est parfois employ. Nous conserverons noyau dans la suite de ce livre. GNU est un acronyme rcursif, il signie GNUs Not UNIX (GNU nest pas UNIX).

xii

Les dveloppeurs ayant dj lexprience de la programmation sous GNU/Linux et voulant en savoir plus sur certaines de ses fonctionnalits et possibilits avances. Vous pouvez tre intress par lcriture de programmes plus sophistiqus avec des fonctionnalits comme le multiprocessing, le multithreading, la communication interprocessus et linteraction avec les priphriques matriels. Vous pouvez vouloir amliorer vos programmes en les rendant plus rapides, plus ables et plus scuriss ou en les concevant de faon ce quils interagissent mieux avec le reste du systme GNU/Linux. Les dveloppeurs ayant de lexprience avec un autre systme de type UNIX intresss par le dveloppement de logiciels pour GNU/Linux. Vous pouvez tre dj familier avec des API standards comme celles de la spcication POSIX. Pour dvelopper des logiciels GNU/Linux, vous devez connatre les particularits du systme, ses limitations, les possibilits supplmentaires quil ore et ses conventions. Les dveloppeurs venant dun systme non-UNIX, comme la plate-forme Win32 de Microsoft. Vous devriez dj connatre les principes permettant dcrire des logiciels de bonne qualit, mais vous avez besoin dapprendre les techniques spciques que les programmes GNU/Linux utilisent pour interagir avec le systme et entre eux. Et vous voulez tre sr que vos programmes sintgrent naturellement au sein du systme GNU/Linux et se comportent selon les attentes des utilisateurs. Ce livre na pas pour but dtre un guide ou une rfrence pour tous les aspects de la programmation GNU/Linux. Au lieu de cela, nous aurons une approche didactique, en introduisant les concepts et les techniques les plus importants et en donnant des exemples sur la faon de les utiliser. La Section 1.5, Obtenir Plus dInformations , dans le Chapitre 1, Pour Commencer , prsente dautres sources de documentation, o vous pourrez trouver des dtails plus complets sur les dirents aspects de la programmation GNU/Linux. Comme ce livre traite de sujets avancs, nous supposerons que vous tes dj familier avec le langage C et que vous savez comment utiliser les fonctions de la bibliothque standard du C dans vos programmes. Le langage C est le langage le plus utilis dans le dveloppement de logiciels GNU/Linux ; la plupart des commandes et des bibliothques dont traite ce livre, et le noyau Linux lui-mme, sont crits en C. Les informations prsentes dans ce livre sont galement applicables au C++ car il sagit grossirement dun sur-ensemble du C. Mme si vous programmez dans un autre langage, vous trouverez ces informations utiles car les API en langage C et leurs conventions constituent le sabir de GNU/Linux. Si vous avez dj programm sur un autre systme de type UNIX, il y a de bonnes chances que vous sachiez vous servir des fonctions dE/S de bas niveau de Linux (open, read, stat, etc). Elles sont direntes des fonctions dE/S de la bibliothque standard du C (fopen, fprintf, fscanf, etc). Toutes sont utiles dans le cadre de la programmation GNU/Linux et nous utiliserons les deux jeux de fonctions dE/S dans ce livre. Si vous ntes pas familiers avec les fonctions dE/S de bas niveau, allez la n de ce livre et lisez lAppendice B, E/S de bas niveau , avant de commencer le Chapitre 2, crire des Logiciels GNU/Linux de Qualit . Ce livre nore pas une introduction gnrale aux systmes GNU/Linux. Nous supposons que vous avez dj une connaissance basique de la faon dinteragir avec un systme GNU/Linux et deectuer des oprations lmentaires dans des environnements en ligne de commande ou xiii

xiv graphiques. Si vous tes nouveau sur GNU/Linux, commencez avec un des nombreux excellents livres dintroduction, comme Inside Linux de Michael Tolber (New Riders Publishing, 2001).

Conventions
Ce livre suit quelques conventions typographiques : Un nouveau terme est prsent en italique la premire fois quil apparat. Les listings de programme, les fonctions, les variables et autres termes informatiques sont prsents en police chasse xe par exemple, printf("Hello, World !\bksl \n"). Les noms des commandes, des chiers et des rpertoires sont galement en police chasse xe par exemple, cd /. Lorsque nous montrons des interactions avec un shell de commandes, nous utiliserons % comme invite shell (votre shell est probablement congur pour utiliser une invite dirente). Tout ce qui se trouve aprs linvite est ce que vous tapez, alors que les autres lignes de texte constituent la rponse du systme. Par exemple, dans cette squence :
% uname Linux

le systme fournit une invite %. Vous entrez la commande uname. Le systme rpond en achant Linux. Le titre de chaque listing inclut un nom de chier entre parenthses. Si vous saisissez le listing, sauvegardez-le dans un chier portant ce nom. Vous pouvez galement tlcharger les codes sources depuis le site Web3 de Programmation Avance Sous Linux (http:// www.newriders.com ou http://www.advancedlinuxprogramming.com). Nous avons crit ce livre et dvelopp les programmes quil prsente en utilisant la distribution Red Hat 6.2 de GNU/Linux. Cette distribution comprend la version 2.2.14 du noyau Linux, la version 2.1.3 de la bibliothque GNU C et la version EGCS 1.1.2 du compilateur C GNU. Les informations et programmes de ce livre devraient tre valables pour les autres versions et distibutions de GNU/Linux, y compris les sries 2.4 du noyau Linux et les sries 2.2 de la bibliothque GNU C.

En anglais.

Premire partie

Programmation UNIX Avance avec Linux

Table des Matires


1 2 3 4 5 Pour Commencer crire des Logiciels GNU/Linux de Qualit Processus Threads Communication Interprocessus 5 17 41 55 85

TABLE DES MATIRES

Chapitre 1

Pour Commencer Ce chapitre prsente les tapes de base ncessaires la cration dun programme
Linux en C ou C++. En particulier, il explique comment crer et modier un code source C ou C++, compiler ce code et dboguer le rsultat. Si vous tes dj familier avec la programmation sous Linux, vous pouvez aller directement au Chapitre 2, crire des Logiciels GNU/Linux de Qualit ; lisez attentivement la Section 2.3, crire et Utiliser des Bibliothques, pour plus dinformations sur la comparaison des ditions de liens statique et dynamique que vous ne connaissez peut-tre pas. Dans la suite de ce livre, nous supposerons que vous tes familier avec le langage de programmation C ou C++ et avec les fonctions les plus courantes de la bibliothque C standard. Les exemples de code source dans ce livre sont en C, except lorsquils montrent une fonctionnalit ou une dicult propre au C++. Nous supposerons galement que vous savez comment eectuer les oprations lmentaires avec le shell de commande Linux, comme crer des rpertoires et copier des chiers. Comme beaucoup de programmeurs Linux ont commenc programmer dans un environnement Windows, nous soulignerons occasionnellement les similitudes et les contrastes entre Windows et Linux.

1.1

Lditeur Emacs

Un diteur est le programme que vous utilisez pour diter le code source. Beaucoup dditeurs dirents sont disponibles sous Linux, mais le plus populaire et celui orant le plus de fonctionnalits est certainement GNU Emacs.

Si vous tes familier avec un autre diteur, vous pouvez certainement lutiliser la place. Rien dans le reste du livre ne dpend de lutilisation dEmacs. Si vous navez pas dj un diteur favori sous Linux, vous pouvez continuer avec le mini-didacticiel fourni ici. Si vous aimez Emacs et voulez en savoir plus sur ses fonctionnalits avances, vous pouvez lire un des nombreux livres disponibles sur le sujet. Un excellent didacticiel, Introduction GNU Emacs, a t crit par Debra Cameron, Bill Rosenblatt et Eric Raymond (OReilly 1997). 5

6 propos dEmacs

CHAPITRE 1. POUR COMMENCER

Emacs est beaucoup plus quun simple diteur. Il sagit dun programme incroyablement puissant, tel point que chez CodeSourcery, il est appel aectueusement le Seul Vrai Programme (One True Program), ou simplement OTP pour faire court. Vous pouvez crire et lire vos e-mails depuis Emacs et vous pouvez le personnaliser et ltendre de faon trop vaste pour que nous en parlions ici. Vous pouvez mme surfer sur le Web depuis Emacs !

1.1.1

Ouvrir un Fichier Source C ou C++

Vous pouvez lancer Emacs en saisissant emacs suivi de la touche Entre dans votre terminal. Lorsque Emacs a dmarr, vous pouvez utiliser les menus situs dans la partie suprieure pour crer un nouveau chier source. Cliquez sur le menu File, slectionnez Open File puis saisissez le nom du chier que vous voulez ouvrir dans le minibuer au bas de lcran1 . Si vous voulez crer un chier source C, utilisez un nom de chier se terminant par .c ou .h. Si vous dsirez crer un chier C++, utilisez un nom de chier se terminant par .cpp, .hpp, .cxx, .hxx, .C ou .H. Lorsque le chier est ouvert, vous pouvez taper comme vous le feriez dans un programme de traitement de texte. Pour sauvegarder le chier, slectionnez lentre Save Buer dans le menu File. Lorsque vous avez termin dutiliser Emacs, vous pouvez choisir loption Exit Emacs dans le menu File. Si vous naimez pas cliquer, vous pouvez utiliser les raccourcis clavier pour ouvrir ou fermer un chier et sortir dEmacs. Pour ouvrir un chier, saisissez C-x C-f (C-x signie de maintenir la touche Control enfonce tout en appuyant sur la touche x). Pour sauvegarder un chier, saisissez C-x C-s. Pour sortir dEmacs, saisissez simplement C-x C-c. Si vous voulez devenir un peu plus familier avec Emacs, slectionnez lentre Emacs Tutorial dans le menu Help. Le didacticiel vous propose quantit dastuces sur lutilisation ecace dEmacs.

1.1.2

Formatage Automatique

Si vous tes un habitu de la programmation dans un Environnement de Dveloppement Intgr (Integrated Development Evironment, IDE), vous tes habitu lassistance au formatage fourni par lditeur. Emacs peut vous orir le mme type de fonctionnalit. Si vous ouvrez un chier C ou C++, Emacs devine quil contient du code, pas simplement du texte ordinaire. Si vous appuyez sur la touche Tab sur une ligne blanche, Emacs dplace le curseur au point dindentation appropri. Si vous appuyez sur la touche Tab sur une ligne contenant dj du texte, Emacs indente le texte. Donc, par exemple, supposons que vous ayez saisi ce qui suit :
int main () { printf ( " Hello , world \ n " ) ; }

Si vous pressez la touche Tab sur la ligne de lappel printf, Emacs reformatera votre code comme suit :
1

Si vous nutilisez pas un systme X Window, vous devrez appuyer sur F10 pour accder aux menus.

1.2. COMPILER AVEC GCC


int main () { printf ( " Hello , world \ n " ) ; }

Remarquez comment la ligne a t correctement indente. En utilisant Emacs, vous verrez comment il peut vous aider eectuer toutes sortes de tches de formatage compliques. Si vous tes ambitieux, vous pouvez programmer Emacs pour eectuer littralement tout formatage que vous pourriez imaginer. Des gens ont utilis ces fonctionnalits pour implmenter des modications dEmacs pour diter peu prs nimporte quelle sorte de documents, implmenter des jeux2 et des interfaces vers des bases de donnes.

1.1.3

Coloration Syntaxique

En plus de formater votre code, Emacs peut faciliter la lecture du code C et C++ en colorant les dirents lments de sa syntaxe. Par exemple, Emacs peut colorer les mots cls dune certaine faon, les types intgrs comme int dune autre et les commentaires dune autre encore. Utiliser des couleurs facilite la dtection de certaines erreurs de syntaxe courantes La faon la plus simple dactiver la coloration est dditer le chier ~/.emacs et dy insrer la chane suivante :
( global - font - lock - mode t )

Sauvegardez le chier, sortez dEmacs et redmarrez-le. Ouvrez un chier C ou C++ et admirez ! Vous pouvez avoir remarqu que la chane que vous avez insr dans votre .emacs ressemble du code LISP. Cest parce-quil sagit de code LISP ! La plus grande partie dEmacs est crite en LISP. Vous pouvez ajouter des fonctionnalits Emacs en crivant en LISP.

1.2

Compiler avec GCC

Un compilateur transforme un code source lisible par un humain en code objet lisible par la machine qui peut tre excut. Les compilateurs de choix disponibles sur les systmes Linux font tous partie de la GNU Compiler Collection, plus communment appele GCC3 . GCC inclut galement des compilateurs C, C++, Java, Objective-C, Fortran et Chill. Ce livre se concentre plus particulirement sur la programmation C et C++. Supposons que vous ayez un projet comme celui du Listing 1.2 avec un chier source C++ (reciprocal.cpp) et un chier source C (main.c) comme dans le Listing 1.1. Ces deux chiers sont supposs tre compils puis lis entre eux pour produire un programme appel reciprocal4 . Ce programme calcule linverse dun entier. Listing 1.1 (main.c) Fichier source C
1 2 3

# include < stdio .h >

Essayez la commande M-x dunnet si vous voulez jouer un jeu daventures en mode texte lancienne. Pour plus dinformations sur GCC, visitez http://gcc.gnu.org/. 4 Sous Windows, les excutables portent habituellement des noms se terminant en .exe. Les programmes Linux par contre, nont habituellement pas dextension. Donc, lquivalent Windows de ce programme sappellerait probablement reciprocal.exe ; la version Linux est tout simplement reciprocal.

8
2 3 4 5 6 7 8 9 10 11 12

CHAPITRE 1. POUR COMMENCER


# include < stdlib .h > # include " reciprocal . hpp " int main ( int argc , char ** argv ) { int i ; i = atoi ( argv [1]) ; printf ( " L inverse de % d est % g \ n " , i , reciprocal ( i ) ) ; return 0; }

Listing 1.2 (reciprocal.cpp) Fichier source C++


1 2 3 4 5 6 7 8

# include < cassert > # include " reciprocal . hpp " double reciprocal ( int i ) { // i doit tre diffrent de zro assert ( i != 0) ; return 1.0/ i ; }

Il y a galement un chier dentte appel reciprocal.hpp (voir Listing 1.3). Listing 1.3 (reciprocal.hpp) Fichier dentte
1 2 3 4 5 6 7 8 9

# ifdef __cplusplus extern " C " { # endif extern double reciprocal ( int i ) ; # ifdef __cplusplus } # endif

La premire tape est de traduire le code C et C++ en code objet.

1.2.1

Compiler un Fichier Source Isol

Le nom du compilateur C est gcc. Pour compiler un chier source C, utilisez loption -c. Donc par exemple, cette commande compile le chier source main.c :
% gcc -c main.c

Le chier objet rsultant est appel main.o. Le compilateur C++ sappelle g++. Son mode opratoire est trs similaire gcc ; la compilation de reciprocal.cpp seectue via la commande suivante :
% g++ -c reciprocal.cpp

Loption -c indique g++ de ne compiler le chier que sous forme dun chier objet ; sans cela, g++ tenterait de lier le programme an de produire un excutable. Une fois cette commande saisie, vous obtenez un chier objet appel reciprocal.o. Vous aurez probablement besoin de quelques autres options pour compiler un programme dune taille raisonnable. Loption -I est utilise pour indiquer GCC o rechercher les chiers

1.2. COMPILER AVEC GCC

dentte. Par dfaut, GCC cherche dans le rpertoire courant et dans les rpertoires o les enttes des bibliothques standards sont installs. Si vous avez besoin dinclure des chiers dentte situs un autre endroit, vous aurez besoin de loption -I. Par exemple, supposons que votre projet ait un rpertoire appel src, pour les chiers source, et un autre appel include. Vous compileriez reciprocal.cpp comme ceci pour indiquer g++ quil doit utiliser en plus le rpertoire include pour trouver reciprocal.hpp :
% g++ -c -I ../include reciprocal.cpp

Parfois, vous pourriez vouloir dnir des macros au niveau de la ligne de commande. Par exemple, dans du code de production, vous ne voudriez pas du surcot de lassertion prsente dans reciprocal.cpp ; elle nest l que pour vous aider dboguer votre programme. Vous dsactivez la vrication en dnissant la macro NDEBUG. Vous pourriez ajouter un #define explicite dans reciprocal.cpp, mais cela ncessiterait de modier la source elle-mme. Il est plus simple de dnir NDEBUG sur la ligne de commande, comme ceci :
% g++ -c -D NDEBUG reciprocal.cpp

Si vous aviez voulu donner une valeur particulire NDEBUG, vous auriez pu saisir quelque chose de ce genre :
% g++ -c -D NDEBUG=3 reciprocal.cpp

Si vous tiez rellement en train de compiler du code de production, vous voudriez probablement que GCC optimise le code an quil sexcute aussi rapidement que possible. Vous pouvez le faire en utilisant loption en ligne de commande -O2 (GCC a plusieurs niveaux doptimisation ; le second niveau convient pour la plupart des programmes). Par exemple, ce qui suit compile reciprocal.cpp avec les optimisations actives :
% g++ -c -O2 reciprocal.cpp

Notez que le fait de compiler avec les optimisations peut rendre votre programme plus dicile dboguer avec un dbogueur (voyez la Section 1.4, Dboguer avec le Dbogueur GNU (GDB)). De plus, dans certaines circonstances, compiler avec les optimisations peut rvler des bogues qui napparaissaient pas auparavant. Vous pouvez passer un certain nombre dautres options gcc et g++. Le meilleur moyen den obtenir une liste complte est de consulter la documentation en ligne. Vous pouvez le faire en saisissant ceci linvite de commandes :
% info gcc

1.2.2

Lier les Fichiers Objet

Maintenant que vous avez compil main.c et reciprocal.cpp, vous devez les lier. Vous devriez toujours utiliser g++ pour lier un programme qui contient du code C++, mme sil contient galement du code C. Si votre programme ne contient que du code C, vous devriez utiliser gcc la place. Comme ce programme contient la fois du code C et du code C++, vous devriez utiliser g++, comme ceci :

10
% g++ -o reciprocal main.o reciprocal.o

CHAPITRE 1. POUR COMMENCER

Loption -o donne le nom du chier gnrer lissue de ltape ddition de liens. Vous pouvez maintenant lancer reciprocal comme ceci :
% ./reciprocal 7 Linverse de 7 est 0.142857

Comme vous pouvez le voir, g++ a automatiquement inclus les bibliothques dexcution C standards contenant limplmentation de printf. Si vous aviez eu besoin de lier une autre bibliothque (comme un kit de dveloppement dinterfaces utilisateur), vous auriez indiqu la bibliothque avec loption -l. Sous Linux, les noms des bibliothques commencent quasiment toujours par lib. Par exemple, la bibliothque du Module dAuthentication Enchable (Pluggable Authentication Module, PAM) est appele libpam.a. Pour inclure libpam.a lors de ldition de liens, vous utiliserez une commande de ce type :
% g++ -o reciprocal main.o reciprocal.o -lpam

Le compilateur ajoutera automatiquement le prxe lib et le suxe .a. Comme avec les chiers dentte, lditeur de liens recherche les bibliothques dans certains emplacements standards, ce qui inclut les rpertoires /lib et /usr/lib qui contiennent les bibliothques systme standards. Si vous voulez que lditeur de liens cherche en plus dans dautres rpertoires, vous devez utiliser loption -L, qui est lquivalent de loption -I dont nous avons parl plus tt. Vous pouvez utiliser cette commande pour indiquer lditeur de liens de rechercher les bibliothques dans le rpertoire /usr/local/lib/pam avant de les rechercher dans les emplacements habituels :
% g++ -o reciprocal main.o reciprocal.o -L/usr/local/lib/pam -lpam

Bien que vous nayez pas utiliser loption -I pour que le prprocesseur eectue ses recherches dans le rpertoire courant, vous devez utiliser loption -L pour que lditeur de liens le fasse. Par exemple, vous devrez utiliser ce qui suit pour indiquer lditeur de liens de rechercher la bibliothque test dans le rpertoire courant :
% gcc -o app app.o -L. -ltest

1.3

Automatiser le Processus avec GNU Make

Si vous tes habitu la programmation pour le systme dexploitation Windows, vous avez probablement lhabitude de travailler avec un Environnement de Dveloppement Intgr (IDE). Vous ajoutez les chiers votre projet puis lIDE compile ce projet automatiquement. Bien que des IDE soient disponibles pour Linux, ce livre nen traite pas. Au lieu de cela, il vous montre comment vous servir de GNU Make pour recompiler votre code automatiquement, comme le font en fait la majorit des programmeurs Linux. Lide de base derrire make est simple. Vous indiquez make quelles cibles vous dsirez compiler puis donnez des rgles expliquant comment les compiler. Vous pouvez galement spcier des dpendances qui indiquent quand une cible particulire doit tre recompile.

1.3. AUTOMATISER LE PROCESSUS AVEC GNU MAKE

11

Dans notre projet exemple reciprocal, il y a trois cibles videntes : reciprocal.o, main.o et reciprocal lui-mme. Vous avez dj lesprit les rgles ncessaires la compilation de ces cibles sous forme des lignes de commande donnes prcdemment. Les dpendances ncessitent un minimum de rexion. Il est clair que reciprocal dpend de reciprocal.o et main.o car vous ne pouvez pas passer ltape ddition de liens avant davoir compil chacun des chiers objets. Les chiers objets doivent tre recompils chaque fois que le chier source correspondant est modi. Il y a encore une subtilit : une modication de reciprocal.hpp doit entraner la recompilation des deux chiers objets car les deux chiers source incluent ce chier dentte. En plus des cibles videntes, il devrait toujours y avoir une cible clean. Cette cible supprime tous les chiers objets gnrs an de pouvoir recommencer sur des bases saines. La rgle pour cette cible utilise la commande rm pour supprimer les chiers. Vous pouvez fournir toutes ces informations make en les plaant dans un chier nomm Makefile. Voici ce quil contient :
reciprocal : main . o reciprocal . o g ++ $ ( CFLAGS ) -o reciprocal main . o reciprocal . o main . o : main . c reciprocal . hpp gcc $ ( CFLAGS ) -c main . c reciprocal . o : reciprocal . cpp reciprocal . hpp g ++ $ ( CFLAGS ) -c reciprocal . cpp clean : rm -f *. o reciprocal

Vous pouvez voir que les cibles sont listes sur la gauche, suivies de deux-points puis des dpendances. La rgle pour la construction dune cible est place sur la ligne suivante (ignorez le $(CFLAGS) pour linstant). La ligne dcrivant la rgle doit commencer par un caractre de tabulation ou make ne sy retrouvera pas. Si vous ditez votre Makefile dans Emacs, Emacs vous assistera dans le formatage. Si vous supprimez les chiers objets que vous avez dj cr et que vous tapez simplement :
% make

sur la ligne de commande, vous obtiendrez la sortie suivante :


% make gcc -c main.c g++ -c reciprocal.cpp g++ -o reciprocal main.o reciprocal.o

Vous constatez que make a automatiquement compil les chiers objet puis les a lis. Si vous modiez maintenant main.c dune faon quelconque puis saisissez make de nouveau, vous obtiendrez la sortie suivante :
% make gcc -c main.c g++ -o reciprocal main.o reciprocal.o

Vous constatez que make recompile main.o et rdite les liens, il ne recompile pas reciprocal.cpp car aucune des dpendances de reciprocal.o na chang.

12

CHAPITRE 1. POUR COMMENCER

$(CFLAGS) est une variable de make. Vous pouvez dnir cette variable soit dans le Makefile lui-mme soit sur la ligne de commande. GNU make substituera la variable par sa valeur lorsquil excutera la rgle. Donc, par exemple, pour recompiler avec les optimisations actives, vous procderiez de la faon suivante :
% make clean rm -f *.o reciprocal % make CFLAGS=-O2 gcc -O2 -c main.c g++ -O2 -c reciprocal.cpp g++ -O2 -o reciprocal main.o reciprocal.o

Notez que le drapeau -O2 a t insr la place de $(CFLAGS) dans les rgles. Dans cette section, nous navons prsent que les capacits les plus basiques de make. Vous pourrez en apprendre plus grce la commande suivante :
% info make

Dans ce manuel, vous trouverez des informations sur la faon de rendre un Makefile plus simple maintenir, comment rduire le nombre de rgles crire et comment calculer automatiquement les dpendances. Vous pouvez galement trouver plus dinformations dans GNU, Autoconf, Automake et Libtool de Gary V. Vaughan, Ben Ellitson, Tom Tromey et Ian Lance Taylor (New Riders Publishing, 2000).

1.4

Dboguer avec le Dbogueur GNU (GDB)

Le dbogueur est le programme que vous utilisez pour trouver pourquoi votre programme ne se comporte pas comme vous pensez quil le devrait. Vous y aurez souvent recours5 . Le dbogueur GNU (GNU debugger, GDB) est le dbogueur utilis par la plupart des programmeurs Linux. Vous pouvez utiliser GDB pour excuter votre code pas pas, poser des points darrt et examiner les valeurs des variables locales.

1.4.1

Compiler avec les Informations de Dbogage

Pour utiliser GDB, vous devez compiler en activant les informations de dbogage. Pour cela, ajoutez loption -g sur la ligne de commande de compilation. Si vous utilisez un Makefile comme nous lavons expliqu plus haut, vous pouvez vous contenter de positionner CFLAGS -g lors de lexcution de make, comme ceci :
% make gcc -g g++ -g g++ -g CFLAGS=-g -c main.c -c reciprocal.cpp -o reciprocal main.o reciprocal.o

Lorsque vous compilez avec -g, le compilateur inclut des informations supplmentaires dans les chiers objets et les excutables. Le dbogueur utilise ces informations pour savoir quelle adresse correspond quelle ligne et dans quel chier source, acher les valeurs des variables et ctera.
5

. . . moins que votre programme ne fonctionne toujours du premier coup.

1.4. DBOGUER AVEC LE DBOGUEUR GNU (GDB)

13

1.4.2

Lancer GDB

Vous pouvez dmarrer gdb en saisissant :


% gdb reciprocal

Lorsque GDB dmarre, il ache linvite :


(gdb)

La premire tape est de lancer votre programme au sein du dbogueur. Entrez simplement la commande run et les arguments du programme. Essayez de lancer le programme sans aucun argument, comme ceci :
(gdb) run Starting program: reciprocal Program received signal SIGSEGV, Segmentation fault. __strtol_internal (nptr=0x0, endptr=0x0, base=10, group=0) at strtol.c:287 287 strtol.c: No such file or directory. (gdb)

Le problme est quil ny a aucun code de contrle derreur dans main. Le programme attend un argument, mais dans ce cas, il nen a reu aucun. Le message SIGSEGV indique un plantage du programme. GDB sait que le plantage a eu lieu dans une fonction appele __strtol_internal. Cette fonction fait partie de la bibliothque standard, et les sources ne sont pas installes ce qui explique le message No such le or directory (Fichier ou rpertoire inexistant). Vous pouvez observer la pile en utilisant la commande where :
(gdb) where #0 __strtol_internal (nptr=0x0, endptr=0x0, base=10, group=0) at strtol.c:287 #1 0x40096fb6 in atoi (nptr=0x0) at ../stdlib/stdlib.h:251 #2 0x804863e in main (argc=1, argv=0xbffff5e4) at main.c:8

Vous pouvez voir daprs cet extrait que main a appel la fonction atoi avec un pointeur
NULL ce qui est la source de lerreur.

Vous pouvez remonter de deux niveaux dans la pile jusqu atteindre main en utilisant la commande up :
(gdb) up 2 #2 0x804863e in main (argc=1, argv=0xbffff5e4) at main.c:8 8 i = atoi (argv[1]);

Notez que GDB est capable de trouver le chier source main.c et quil ache la ligne contenant lappel de fonction erron. Vous pouvez inspecter la valeurs des variables en utilisant la commande print :
(gdb) print argv[1] $2 = 0x0

Cela conrme que le problme vient dun pointeur NULL pass atoi. Vous pouvez placer un point darrt en utilisant la commande break :

14
(gdb) break main Breakpoint 1 at 0x804862e: file main.c, line 8.

CHAPITRE 1. POUR COMMENCER

Cette commande place un point darrt sur la premire ligne de main6 . Essayez maintenant de relancer le programme avec un argument, comme ceci :
(gdb) run 7 Starting program: reciprocal 7 Breakpoint 1, main (argc=2, argv=0xbffff5e4) at main.c:8 8 i = atoi (argv[1])

Vous remarquez que le dbogueur sest arrt au niveau du point darrt. Vous pouvez passer linstruction se trouvant aprs lappel atoi en utilisant la commande next :
9 gdb) next printf ("Linverse de %d est %g\n", i, reciprocal (i));

Si vous voulez voir ce qui se passe lintrieur de la fonction reciprocal, utilisez la commande step, comme ceci :
(gdb) step reciprocal (i=7) at reciprocal.cpp:6 6 assert (i != 0);

Vous tes maintenant au sein de la fonction reciprocal. Vous pouvez trouver plus commode dexcuter gdb au sein dEmacs plutt que de le lancer directement depuis la ligne de commande. Utilisez la commande M-x gdb pour dmarrer gdb dans une fentre Emacs. Si vous stoppez au niveau dun point darrt, Emacs ouvre automatiquement le chier source appropri. Il est plus facile de se rendre compte de ce qui se passe en visualisant le chier dans son ensemble plutt quune seule ligne.

1.5

Obtenir Plus dInformations

Quasiment toutes les distributions Linux disposent dune masse importante de documentation. Vous pourriez apprendre la plupart des choses dont nous allons parler dans ce livre simplement en lisant la documentation de votre distribution Linux (bien que cela vous prendrait probablement plus de temps). La documentation nest cependant pas toujours trs bien organise, donc la partie la plus subtile est de trouver ce dont vous avez besoin. La documentation date quelquefois un peu, aussi, prenez tout ce que vous y trouvez avec un certain recul. Si le systme ne se comporte pas comme le dit une page de manuel, cest peut-tre parce que celle-ci est obsolte. Pour vous aider naviguer, voici les sources dinformation les plus utiles sur la programmation avance sous Linux.
Certaines personnes ont fait la remarque que break main (NdT. littralement casser main ) est amusant car vous ne vous en servez en fait uniquement lorsque main a dj un problme.
6

1.5. OBTENIR PLUS DINFORMATIONS

15

1.5.1

Pages de Manuel

Les distributions Linux incluent des pages de manuel pour les commandes les plus courantes, les appels systme et les fonctions de la bibliothque standard. Les pages de manuel sont divises en sections numrotes ; pour les programmeurs, les plus importantes sont celles-ci : (1) Commandes utilisateur (2) Appels systme (3) Fonctions de la bibliothque standard (8) Commandes systme/dadministration Les numros indiquent les sections des pages de manuel. Les pages de manuel de Linux sont installes sur votre systme ; utilisez la commande man pour y accder. Pour accder une page de manuel, invoquez simplement man nom, o nom est un nom de commande ou de fonction. Dans un petit nombre de cas, le mme nom apparat dans plusieurs sections ; vous pouvez indiquer explicitement la section en plaant son numro devant le nom. Par exemple, si vous saisissez la commande suivante, vous obtiendrez la page de manuel pour la commande sleep (dans la section 1 des pages de manuel Linux) :
% man sleep

Pour visualiser la page de manuel de la fonction sleep de la bibliothque standard, utilisez cette commande :
% man 3 sleep

Chaque page de manuel comprend un rsum sur une ligne de la commande ou fonction. La commande whatis nom liste toutes les pages de manuel (de toutes les sections) pour une commande ou une fonction correspondant nom. Si vous ntes pas sr de la commande ou fonction utiliser, vous pouvez eectuer une recherche par mot-cl sur les rsums via man -k mot-cl. Les pages de manuel contiennent des informations trs utiles et devraient tre le premier endroit vers lequel vous orientez vos recherches. La page de manuel dune commande dcrit ses options en ligne de commande et leurs arguments, ses entres et sorties, ses codes derreur, sa conguration et ce quelle fait. La page de manuel dun appel systme ou dune fonction de bibliothque dcrit les paramtres et valeurs de retour, liste les codes derreur et les eets de bord et spcie le chier dentte inclure si vous utilisez la fonction.

1.5.2

Info

Le systme de documentation Info contient des informations plus dtailles pour beaucoup de composants fondamentaux du systme GNU/Linux et quelques autres programmes. Les pages Info sont des documents hypertextes, similaires aux pages Web. Pour lancer le navigateur texte Info, tapez simplement info linvite de commande. Vous obtiendrez un menu avec les documents Info prsents sur votre systme (appuyez sur Ctrl+H pour acher les touches permettant de naviguer au sein dun document Info). Parmi les documents Info les plus utiles, on trouve :

16

CHAPITRE 1. POUR COMMENCER

gcc Le compilateur gcc libc La bibliothque C GNU, avec beaucoup dappels systme gdb Le dbogueur GNU emacs Lditeur de texte Emacs info Le systme Info lui-mme Presque tous les outils de programmation standards sous Linux (y compris ld, lditeur de liens ; as, lassembleur et gprof, le proler) sont accompagns de pages Info trs utiles. Vous pouvez accder directement un document Info en particulier en indiquant le nom de la page sur la ligne de commandes :
% info libc

Si vous programmez la plupart du temps sous Emacs, vous pouvez accder au navigateur Info intgr en appuyant sur M-x info ou C-h i.

1.5.3

Fichiers dentte

Vous pouvez en apprendre beaucoup sur les fonctions systme disponibles et comment les utiliser en observant les chiers dentte. Ils sont placs dans /usr/include et /usr/include/sys. Si vous obtenez des erreurs de compilation lors de lutilisation dun appel systme, par exemple, regardez le chier dentte correspondant pour vrier que la signature de la fonction est la mme que celle prsente sur la page de manuel. Sur les systmes Linux, beaucoup de dtails obscurs sur le fonctionnement des appels systmes apparaissent dans les chiers dentte situs dans les rpertoires /usr/include/bits, /usr/include/asm et /usr/include/linux. Ainsi, le chier /usr/include/bits/signum.h dnit les valeurs numriques des signaux (dcrits dans la Section 3.3, Signaux du Chapitre 3, Processus). Ces chiers dentte constituent une bonne lecture pour les esprits curieux. Ne les incluez pas directement dans vos programmes, cependant ; utilisez toujours les chiers dentte de /usr/include ou ceux mentionns dans la page de manuel de la fonction que vous utilisez.

1.5.4

Code Source

Nous sommes dans lOpen Source, non ? Le juge en dernier ressort de la faon dont doit fonctionner le systme est le code source, et par chance pour les programmeurs Linux, ce code source est disponible librement. Il y a des chances pour que votre systme Linux comprenne tout le code source du systme et des programmes fournis ; si ce nest pas le cas, vous avez le droit, selon les termes de la Licence Publique Gnrale GNU, de les demander au distributeur (le code source nest toutefois pas forcment install. Consultez la documentation de votre distribution pour savoir comment linstaller). Le code source du noyau Linux lui-mme est habituellement stock sous /usr/src/linux. Si ce livre vous laisse sur votre faim concernant les dtails sur le fonctionnement des processus, de la mmoire partage et des priphriques systme, vous pouvez toujours apprendre directement partir du code. La plupart des fonctions dcrites dans ce livre sont implmentes dans la bibliothque C GNU ; consultez la documentation de votre distribution pour connatre lemplacement du code source de la bibliothque C.

Chapitre 2

crire des Logiciels GNU/Linux de Qualit


e chapitre prsente quelques techniques de base utilises par la plupart des programmeurs GNU/Linux. En suivant grossirement les indications que nous allons prsenter, vous serez mme dcrire des programmes qui fonctionnent correctement au sein de lenvironnement GNU/Linux et correspondent ce quattendent les utilisateurs au niveau de leur faon de fonctionner.

2.1

Interaction Avec lEnvironnement dExcution

Lorsque vous avez tudi pour la premire fois le langage C ou C++, vous avez appris que la fonction spciale main est le point dentre principal pour un programme. Lorsque le systme dexploitation excute votre programme, il ore un certain nombre de fonctionnalits qui aident le programme communiquer avec le systme dexploitation et lutilisateur. Vous avez probablement entendu parler des deux paramtres de main, habituellement appels argc et argv, qui reoivent les entres de votre programme. Vous avez appris que stdin et stdout (ou les ux cin et cout en C++) fournissent une entre et une sortie via la console. Ces fonctionnalits sont fournies par les langages C et C++, et elles interagissent avec le systme dune certaine faon. GNU/Linux fournit en plus dautres moyens dinteragir avec lenvironnement dexcution.

2.1.1

La Liste dArguments

Vous lancez un programme depuis linvite de commandes en saisissant le nom du programme. ventuellement, vous pouvez passer plus dinformations au programme en ajoutant un ou plusieurs mots aprs le nom du programme, spars par des espaces. Ce sont des arguments de ligne de commande (vous pouvez passer un argument contenant des espaces en le plaant entre guillemets). Plus gnralement, on appelle cela la liste darguments du programme car ils ne viennent pas ncessairement de la ligne de commande. Dans le Chapitre 3, Processus , 17

18

CHAPITRE 2. CRIRE DES LOGICIELS GNU/LINUX DE QUALIT

vous verrez un autre moyen dinvoquer un programme, avec lequel un programme peut indiquer directement la liste darguments dun autre programme. Lorsquun programme est invoqu depuis la ligne de commande, la liste darguments contient toute la ligne de commande, y compris le nom du programme et tout argument qui aurait pu lui tre pass. Supposons, par exemple, que vous invoquiez la commande ls depuis une invite de commandes pour acher le contenu du rpertoire racine et les tailles de chiers correspondantes au moyen de cette commande :
% ls -s /

La liste darguments que le programme ls reoit est compose de trois lments. Le premier est le nom du programme lui-mme, saisi sur la ligne de commande, savoir ls. Les second et troisime lment sont les deux arguments de ligne de commande, -s et /. La fonction main de votre programme peut accder la liste darguments via ses paramtres argc et argv (si vous ne les utilisez pas, vous pouvez simplement les omettre). Le premier paramtre, argc, est un entier qui indique le nombre dlments dans la liste. Le second paramtre, argv, est un tableau de pointeurs sur des caractres. La taille du tableau est argc, et les lments du tableau pointent vers les lments de la liste darguments, qui sont des chanes termines par zro. Utiliser des arguments de ligne de commande consiste examiner le contenu de argc et argv. Si vous ntes pas intress par le nom du programme, noubliez pas dignorer le premier lment. Le Listing 2.1 montre lutilisation de argv et argc. Listing 2.1 (arglist.c) Utiliser argc et argv
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

# include < stdio .h > int main ( int argc , char * argv []) { printf ( " Le nom de ce programme est % s .\ n " , argv [0]) ; printf ( " Ce programme a t invoqu avec % d arguments .\ n " , argc - 1) ; /* A -t - on spcifi des arguments sur la ligne de commande ? */ if ( argc > 1) { /* Oui , les afficher . */ int i ; printf ( " Les arguments sont :\ n " ) ; for ( i = 1; i < argc ; ++ i ) printf ( " % s \ n " , argv [ i ]) ; } return 0; }

2.1.2

Conventions de la Ligne de Commande GNU/Linux

Presque tous les programmes GNU/Linux obissent un ensemble de conventions concernant linterprtation des arguments de la ligne de commande. Les arguments attendus par un programme sont classs en deux catgories : les options (ou drapeaux 1 ) et les autres arguments. Les options modient le comportement du programme, alors que les autres arguments fournissent des entres (par exemple, les noms des chiers dentre).
1

NdT. ags en anglais

2.1. INTERACTION AVEC LENVIRONNEMENT DEXCUTION

19

Les options peuvent prendre deux formes : Les options courtes sont formes dun seul tiret et dun caractre isol (habituellement une lettre en majuscule ou en minuscule). Elles sont plus rapides saisir. Les options longues sont formes de deux tirets suivis dun nom compos de lettres majuscules, minuscules et de tirets. Les options longues sont plus faciles retenir et lire (dans les scripts shell par exemple). Gnralement, un programme propose une version courte et une version longue pour la plupart des options quil prend en charge, la premire pour la brivet et la seconde pour la lisibilit. Par exemple, la plupart des programmes acceptent les options -h et help et les traitent de faon identique. Normalement, lorsquun programme est invoqu depuis la ligne de commande, les options suivent immdiatement le nom du programme. Certaines options attendent un argument immdiatement leur suite. Beaucoup de programmes, par exemple, acceptent loption output foo pour indiquer que les sorties du programme doivent tre rediriges vers un chier appel foo. Aprs les options, il peut y avoir dautres arguments de ligne de commande, typiquement les chiers ou les donnes dentre. Par exemple, la commande ls -s / ache le contenu du rpertoire racine. Loption -s modie le comportement par dfaut de ls en lui demandant dacher la taille (en kilooctets) de chaque entre. Largument / indique ls quel rpertoire lister. Loption size est synonyme de -s, donc la commande aurait pu tre invoque sous la forme ls size /. Les Standards de Codage GNU dressent la liste des noms doptions en ligne de commande couramment utiliss. Si vous avez lintention de proposer des options identiques, il est conseill dutiliser les noms prconiss dans les standards de codage. Votre programme se comportera de faon similaire aux autres et sera donc plus simple prendre en main pour les utilisateurs. Vous pouvez consulter les grandes lignes des Standards de Codage GNU propos des options en ligne de commandes via la commande suivante depuis une invite de commande sur la plupart des systmes GNU/Linux :
% info "(standards)User Interfaces"

2.1.3

Utiliser getopt_long

Lanalyse des options de la ligne de commande est une corve. Heureusement, la bibliothque GNU C fournit une fonction que vous pouvez utiliser dans les programmes C et C++ pour vous faciliter la tche (quoiquelle reste toujours quelque peut ennuyeuse). Cette fonction, getopt_long , interprte la fois les options courtes et longues. Si vous utilisez cette fonction, incluez le chier den-tte <getopt.h>. Supposons par exemple que vous criviez un programme acceptant les trois options du Tableau 2.1. Le programme doit par ailleurs accepter zro ou plusieurs arguments supplmentaires, qui sont les noms de chiers dentre. Pour utiliser getopt_long, vous devez fournir deux structures de donnes. La premire est une chane contenant les options courtes valables, chacune sur une lettre. Une option qui requiert un argument est suivie par deux-points. Pour notre programme, la chane ho :v indique que les options valides sont -h, -o et -v, la seconde devant tre suivie dun argument.

20

CHAPITRE 2. CRIRE DES LOGICIELS GNU/LINUX DE QUALIT

Forme Courte -h -o nom fichier -v

Tab. 2.1 Exemple dOptions pour un Programme Forme Longue Fonction help Ache laide-mmoire et quitte output nom fichier Indique le nom du chier de sortie verbose Ache des messages dtaills

Pour indiquer les options longues disponibles, vous devez construire un tableau dlments struct option. Chaque lment correspond une option longue et dispose de quatre champs. Gnralement, le premier champ est le nom de loption longue (sous forme dune chane de caractres, sans les deux tirets) ; le second est 1 si loption prend un argument, 0 sinon ; le troisime est NULL et le quatrime est un caractre qui indique loption courte synonyme de loption longue. Tous les champs du dernier lment doivent tre zro. Vous pouvez construire le tableau comme ceci :
const struct option long_options [] = { { " help " , 0 , NULL , h } , { " output " , 1 , NULL , o } , { " verbose " , 0 , NULL , v } , { NULL , 0 , NULL , 0 } };

Vous invoquez la fonction getopt_long en lui passant les arguments argc et argv de main , la chane de caractres dcrivant les options courtes et le tableau dlments struct option dcrivant les options longues. chaque fois que vous appelez getopt_long, il nanalyse quune seule option et renvoie la lettre de loption courte pour cette option ou -1 sil ny a plus doption analyser. Typiquement, vous appelez getopt_long dans une boucle, pour traiter toutes les options que lutilisateur a spci et en les grant au sein dune structure switch. Si getopt_long rencontre une option invalide (une option que vous navez pas indique comme tant une option courte ou longue valide), il ache un message derreur et renvoie le caractre ? (un point dinterrogation). La plupart des programmes sinterrompent dans ce cas, ventuellement aprs avoir ach des informations sur lutilisation de la commande. Lorsque le traitement dune option requiert un argument, la variable globale optarg pointe vers le texte constituant cet argument. Une fois que getopt_long a ni danalyser toutes les options, la variable globale optind contient lindex (dans argv) du premier argument qui nest pas une option. Le Listing 2.2 montre un exemple dutilisation de getopt_long pour le traitement des arguments. Listing 2.2 (getopt_long.c) Utilisation de getopt_long
1 2 3 4 5 6 7 8

# include < getopt .h > # include < stdio .h > # include < stdlib .h > /* Nom du programme . */ const char * program_name ; /* Envoie les informations sur l utilisation de la commande vers STREAM ( typiquement stdout ou stderr ) et quitte le programme avec EXIT_CODE . Ne retourne jamais . */

2.1. INTERACTION AVEC LENVIRONNEMENT DEXCUTION


9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70

21

void print_usage ( FILE * stream , int exit_code ) { fprintf ( stream , " Utilisation : % s options [ fichierentre ...]\ n " , program_name ) ; fprintf ( stream , " -h -- help Affiche ce message .\ n " " -o -- output filename Redirige la sortie vers un fichier .\ n " " -v -- verbose Affiche des messages dtaills .\ n " ) ; exit ( exit_code ) ; } /* Point d entre du programme . ARGC contient le nombre d lments de la liste d arguments ; ARGV est un tableau de pointeurs vers ceux - ci . */ int main ( int argc , char * argv []) { int next_option ; /* Chane listant les lettres valides pour les options courtes . */ const char * const short_options = " ho : v " ; /* Tableau dcrivant les options longues valides . */ const struct option long_options [] = { { " help " , 0 , NULL , h } , { " output " , 1 , NULL , o } , { " verbose " , 0 , NULL , v } , /* Requis la fin du tableau . */ { NULL , 0 , NULL , 0 } }; /* Nom du fichier vers lequel rediriger les sorties , ou NULL pour la sortie standard . */ const char * o u t p u t _ fi l e n a m e = NULL ; /* Indique si l on doit afficher les messages dtaills . */ int verbose = 0; /* Mmorise le nom du programme , afin de l intgrer aux messages . Le nom est contenu dans argv [0]. */ program_name = argv [0]; do { next_option = getopt_long ( argc , argv , short_options , long_options , NULL ) ; switch ( next_option ) { case h : /* -h or -- help */ /* L utilisateur a demand l aide - mmoire . L affiche sur la sortie standard et quitte avec le code de sortie 0 ( fin normale ) . */ print_usage ( stdout , 0) ; case o : /* -o ou -- output */ /* Cette option prend un argument , le nom du fichier de sortie . */ o u t pu t _ f i l e na m e = optarg ; break ; case v : /* -v ou -- verbose */ verbose = 1; break ; case ? : /* L utilisateur a saisi une option invalide . */ /* Affiche l aide - mmoire sur le flux d erreur et sort avec le code de sortie un ( indiquant une fin anormale ) . */ print_usage ( stderr , 1) ; case -1: /* Fin des options . */ break ; default : /* Quelque chose d autre : inattendu . */ abort () ; } } while ( next_option != -1) ; /* Fin des options . OPTIND pointe vers le premier argument qui n est pas une option . des fins de dmonstration , nous les affichons si l option verbose est spcifie . */

22
71 72 73 74 75 76 77 78

CHAPITRE 2. CRIRE DES LOGICIELS GNU/LINUX DE QUALIT


if ( verbose ) { int i ; for ( i = optind ; i < argc ; ++ i ) printf ( " Argument : % s \ n " , argv [ i ]) ; } /* Le programme principal se place ici . */ return 0; }

Lutilisation de getopt_long peut sembler ncessiter beaucoup de travail, mais crire le code ncessaire lanalyse des options de la ligne de commandes vous-mme vous prendrait encore plus longtemps. La fonction getopt_long est trs sophistique et permet une grande exibilit dans la spcication des types doptions acceptes. Cependant, il est bon de se tenir lcart des fonctionnalits les plus avances et de conserver la structure doptions basiques dcrite ici.

2.1.4

E/S Standards

La bibliothque standard du C fournit des ux dentre et de sortie standards (stdin et stdout respectivement). Il sont utiliss par printf, scanf et dautres fonctions de la bibliothque. Dans la tradition UNIX, lutilisation de lentre et de la sortie standard est frquente pour les programmes GNU/Linux. Cela permet lenchanement de plusieurs programmes au moyen des pipes2 et de la redirection des entres et sorties (consultez la page de manuel de votre shell pour savoir comment les utiliser). La bibliothque C fournit galement stderr, le ux derreurs standard. Il est dusage que les programmes envoient les messages derreur et davertissement vers la sortie des erreurs standard plutt que vers la sortie standard. Cela permet aux utilisateurs de sparer les messages normaux des messages derreur, par exemple, en redirigeant la sortie standard vers un chier tout en laissant les erreurs sacher sur la console. La fonction fprintf peut tre utilise pour crire sur stderr, par exemple :
% fprintf ( stderr , " Erreur : ... " ) ;

Ces trois ux sont galement accessibles via les commandes dE/S UNIX de bas niveau (read, write, etc), par le biais des descripteurs de chiers. Les descripteurs de chiers sont 0 pour stdin, 1 pour stdout et 2 pour stderr. Lors de lappel dun programme, il peut tre utile de rediriger la fois la sortie standard et la sortie des erreurs vers un chier ou un pipe. La syntaxe utiliser dire selon les shells ; la voici pour les shells de type Bourne (y compris bash, le shell par dfaut sur la plupart des distributions GNU/Linux) :
% programme > fichier_sortie.txt 2>&1 % programme 2>&1 | filtre

La syntaxe 2>&1 indique que le descripteur de chiers 2 (stderr) doit tre fusionn avec le descripteur de chiers 1 (stdout). Notez que 2>&1 doit tre plac aprs une redirection vers un chier (premier exemple) mais avant une redirection vers un pipe (second exemple). Notez que stdout est buerise. Les donnes crites sur stdout ne sont pas envoyes vers la console (ou un autre dispositif si lon utilise la redirection) avant que le tampon ne soit plein,
2

NdT. appels aussi parfois tubes ou canaux.

2.1. INTERACTION AVEC LENVIRONNEMENT DEXCUTION

23

que le programme ne se termine normalement ou que stdout soit ferm. Vous pouvez purger explicitement le tampon de la faon suivante :
fflush ( stdout ) ;

Par contre, stderr nest pas buerise ; les donnes crites sur stderr sont envoyes directement vers la console3 . Cela peut conduire des rsultats quelque peu surprenants. Par exemple, cette boucle nache pas un point toutes les secondes ; au lieu de cela, les points sont placs dans le tampon, et ils sont achs par groupe lorsque le tampon est plein.
while (1) { printf ( " . " ) ; sleep (1) ; }

Avec cette boucle, par contre, les points sont achs au rythme dun par seconde :
while (1) { fprintf ( stderr , " . " ) ; sleep (1) ; }

2.1.5

Codes de Sortie de Programme

Lorsquun programme se termine, il indique son tat au moyen dun code de sortie. Le code de sortie est un entier court ; par convention, un code de sortie zro indique une n normale, tandis quun code dirent de zro signale quune erreur est survenue. Certains programmes utilisent des codes de sortie dirents de zro varis pour distinguer les direntes erreurs. Avec la plupart des shells, il est possible dobtenir le code de sortie du dernier programme excut en utilisant la variable spciale $ ?. Voici un exemple dans lequel la commande ls est invoque deux fois et son code de sortie est ach aprs chaque invocation. Dans le premier cas, ls se termine correctement et renvoie le code de sortie 0. Dans le second cas, ls rencontre une erreur (car le chier spci sur la ligne de commande nexiste pas) et renvoie donc un code de sortie dirent de 0.
% ls / bin coda etc lib misc nfs proc boot dev home lost+found mnt opt root % echo $? 0 % ls fichierinexistant ls: fichierinexistant: Aucun fichier ou rpertoire de ce type % echo $? 1

Un programme C ou C++ donne son code de sortie en le retournant depuis la fonction main. Il y a dautres mthodes pour fournir un code de sortie et des codes de sortie spciaux sont assigns aux programmes qui se terminent de faon anormale (sur un signal). Ils sont traits de manire plus approfondie dans le Chapitre 3.
3 En C++, la mme distinction sapplique cout et cerr, respectivement. Notez que le token endl purge un ux en plus dy envoyer un caractre de nouvelle ligne ; si vous ne voulez pas purger le ux (pour des raisons de performances par exemple), utilisez une constante de nouvelle ligne, n, la place.

24

CHAPITRE 2. CRIRE DES LOGICIELS GNU/LINUX DE QUALIT

2.1.6

LEnvironnement

GNU/Linux fournit tout programme sexcutant un environnement. Lenvironnement est une collection de paires variable/valeur. Les variables denvironnement et leurs valeurs sont des chanes de caractres. Par convention, les variables denvironnement sont en majuscules dimprimerie. Vous tes probablement dj familier avec quelques variables denvironnement courantes. Par exemple : USER contient votre nom dutilisateur. HOME contient le chemin de votre rpertoire personnel. PATH contient une liste de rpertoires spars par deux-points dans lesquels Linux recherche les commandes que vous invoquez. DISPLAY contient le nom et le numro dachage du serveur X Window sur lequel apparaissent les fentres des programmes graphiques X Window. Votre shell, comme nimporte quel autre programme, dispose dun environnement. Les shells fournissent des mthodes pour examiner et modier lenvironnement directement. Pour acher lenvironnement courant de votre shell, invoquez le programme printenv. Tous les shells nutilisent pas la mme syntaxe pour la manipulation des variables denvironnement ; voici la syntaxe pour les shells de type Bourne : Le shell cre automatiquement une variable shell pour chaque variable denvironnement quil trouve, an que vous puissiez accder aux valeurs des variables denvironnement en utilisant la syntaxe $nomvar. Par exemple :
% echo $USER samuel % echo $HOME /home/samuel

Vous pouvez utiliser la commande export pour exporter une variable shell vers lenvironnement. Par exemple, pour positionner la variable denvironnement EDITOR, vous utiliserez ceci :
% EDITOR=emacs % export EDITOR

Ou, pour faire plus court :


% export EDITOR=emacs

Dans un programme, vous pouvez accder une variable denvironnement au moyen de la fonction getenv de <stdlib.h>. Cette fonction accepte le nom dune variable et renvoie la valeur correspondante sous forme dune chane de caractres ou NULL si cette variable nest pas dnie dans lenvironnement. Pour positionner ou supprimer une variable denvironnement, utilisez les fonctions setenv et unsetenv, respectivement. numrer toutes les variables de lenvironnement est un petit peu plus subtil. Pour cela, vous devez accder une variable globale spciale appele environ, qui est dnie dans la bibliothque C GNU. Cette variable, de type char**, est un tableau termin par NULL de pointeurs vers des chanes de caractres. Chaque chane contient une variable denvironnement, sous la forme VARIABLE=valeur.

2.1. INTERACTION AVEC LENVIRONNEMENT DEXCUTION

25

Le programme du Listing 2.3, par exemple, ache tout lenvironnement en bouclant sur le tableau environ. Listing 2.3 (print-env.c) Acher lEnvironnement dExcution
1 2 3 4 5 6 7 8 9 10

# include < stdio .h > /* La variable ENVIRON contient l environnement . */ extern char ** environ ; int main () { char ** var ; for ( var = environ ; * var != NULL ; ++ var ) printf ( " % s \ n " , * var ) ; return 0; }

Ne modiez pas environ vous-mme ; utilisez plutt les fonctions setenv et getenv. Lorsquun nouveau programme est lanc, il hrite dune copie de lenvironnement du programme qui la invoqu (le shell, sil a t invoqu de faon interactive). Donc, par exemple, les programmes que vous lancez depuis le shell peuvent examiner les valeurs des variables denvironnement que vous positionnez dans le shell. Les variables denvironnement sont couramment utilises pour passer des paramtres de conguration aux programmes. Supposons, par exemple, que vous criviez un programme qui se connecte un serveur Internet pour obtenir des informations. Vous pourriez crire le programme de faon ce que le nom du serveur soit saisi sur la ligne de commande. Cependant, supposons que le nom du serveur ne soit pas quelque chose que les utilisateurs changent trs souvent. Vous pouvez utiliser une variable denvironnement spciale disons SERVER_NAME pour spcier le nom du serveur ; si cette variable nexiste pas, une valeur par dfaut est utilise. Une partie de votre programme pourrait ressembler au Listing 2.4. Listing 2.4 (client.c) Extrait dun Programme Client Rseau
1 2 3 4 5 6 7 8 9 10 11 12 13

# include < stdio .h > # include < stdlib .h > int main () { char * server_name = getenv ( " SERVER_NAME " ) ; if ( server_name == NULL ) /* La variable SERVER_NAME n est pas dfinie . Utilisation de la valeur par dfaut . */ server_name = " server . my - company . com " ; printf ( " Accs au serveur % s \ n " , server_name ) ; /* Accder au serveur ici ... */ return 0; }

Supposons que ce programme sappelle client. En admettant que vous nayez pas dni la variable SERVER_NAME, la valeur par dfaut pour le nom du serveur est utilise :
% client Accs au serveur server.my-company.com

Mais il est facile de spcier un serveur dirent :


% export SERVER_NAME=backup-server.elsewhere.net % client Accs au serveur backup-server.elsewhere.net

26

CHAPITRE 2. CRIRE DES LOGICIELS GNU/LINUX DE QUALIT

2.1.7

Utilisation de Fichiers Temporaires

Parfois, un programme a besoin de crer un chier temporaire, pour stocker un gros volume de donnes temporairement ou passer des informations un autre programme. Sur les systmes GNU/Linux, les chiers temporaires sont stocks dans le rpertoire /tmp. Lors de lutilisation de chiers temporaires, vous devez viter les piges suivants : Plus dune copie de votre programme peuvent tre lances simultanment (par le mme utilisateur ou par des utilisateurs dirents). Les copies devraient utiliser des noms de chiers temporaires dirents an dviter les collisions. Les permissions du chier devraient tre dnies de faon viter quun utilisateur non autoris puisse altrer la manire dont sexcute le programme en modiant ou remplaant le chier temporaire. Les noms des chiers temporaires devraient tre gnrs de faon imprvisible de lextrieur ; autrement, un attaquant pourrait exploiter le dlai entre le test dexistence du nom de chier et louverture du nouveau chier temporaire. GNU/Linux fournit des fonctions, mkstemp et tmpfile, qui soccupent de ces problmes votre place (en plus de fonctions qui ne le font pas). Le choix de la fonction dpend de lutilisation que vous aurez du chier, savoir le passer un autre programme ou utiliser les fonctions dE/S UNIX (open, write, etc.) ou les fonctions de ux dE/S de la bibliothque C (fopen, fprintf, etc.). Utilisation de mkstemp La fonction mkstemp cre un nom de chier temporaire partir dun modle de nom de chier, cre le chier avec les permissions adquates an que seul lutilisateur courant puisse y accder, et ouvre le chier en lecture/criture. Le modle de nom de chier est une chane de caractres se terminant par "XXXXXX" (six X majuscules) ; mkstemp remplace les X par des caractres an que le nom de chier soit unique. La valeur de retour est un descripteur de chier ; utilisez les fonctions de la famille de write pour crire dans le chier temporaire. Les chiers temporaires crs par mkstemp ne sont pas eacs automatiquement. Cest vous de supprimer le chier lorsque vous nen avez plus besoin (les programmeurs devraient tre attentifs supprimer les chiers temporaires ; dans le cas contraire, le systme de chiers /tmp pourrait se remplir, rendant le systme inutilisable). Si le chier temporaire nest destin qu tre utilis par le programme et ne sera pas transmis un autre programme, cest une bonne ide dappeler unlink sur le chier temporaire immdiatement. La fonction unlink supprime lentre de rpertoire correspondant un chier, mais comme le systme tient jour un dcompte du nombre de rfrences sur chaque chier, un chier nest pas eac tant quil reste un descripteur de chier ouvert pour ce chier. Comme Linux ferme les descripteurs de chiers quand un programme se termine, le chier temporaire sera eac mme si votre programme se termine de manire anormale. Les deux fonctions du Listing 2.5 prsentent lutilisation de mkstemp. Utilises ensemble, ces fonctions facilitent lcriture dun tampon mmoire vers un chier temporaire (an que la mmoire puisse tre libre ou rutilise) et sa relecture ultrieure.

2.1. INTERACTION AVEC LENVIRONNEMENT DEXCUTION Listing 2.5 (temp_le.c) Utiliser mkstemp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47

27

# include < stdlib .h > # include < unistd .h > /* Handle sur un fichier temporaire cr avec w r i te _ t e m p _ fi l e . Avec cette implmentation , il s agit d un descripteur de fichier . */ typedef int t e m p _ f i l e _ h a n d l e ; /* crit LENGTH octets de BUFFER dans un fichier temporaire . Unlink est appel immdiatement sur le fichier temporaire . Renvoie un handle sur le fichier temporaire . */ t e m p _ f i l e _ h a n d l e w r i t e _t e m p _ f il e ( char * buffer , size_t length ) { /* Cre le nom du fichier et le fichier . XXXXXX sera remplac par des caractres donnant un nom de fichier unique . */ char temp_filename [] = " / tmp / temp_file . XXXXXX " ; int fd = mkstemp ( temp_filename ) ; /* Appelle unlink immdiatement afin que le fichier soit supprim ds que le descripteur sera ferm . */ unlink ( temp_filename ) ; /* crit le nombre d octets dans le fichier avant tout . */ write ( fd , & length , sizeof ( length ) ) ; /* crit des donnes proprement dites . */ write ( fd , buffer , length ) ; /* Utilise le descripteur de fichier comme handle sur le fichier temporaire . */ return fd ; } /* Lit le contenu du fichier temporaire TEMP_FILE cr avec w r i te _ t e m p _ fi l e . La valeur de retour est un tampon nouvellement allou avec ce contenu , que l appelant doit librer avec free . * LENGTH est renseign avec la taille du contenu , en octets . Le fichier temporaire est supprim . */ char * re ad _t emp _f il e ( t e m p _ f i l e _ h a n d l e temp_file , size_t * length ) { char * buffer ; /* Le handle sur TEMP_FILE est le descripteur du fichier temporaire . */ int fd = temp_file ; /* Se place au dbut du fichier . */ lseek ( fd , 0 , SEEK_SET ) ; /* Lit les donnes depuis le fichier temporaire . */ read ( fd , length , sizeof (* length ) ) ; /* Alloue un buffer et lit les donnes . */ buffer = ( char *) malloc (* length ) ; read ( fd , buffer , * length ) ; /* Ferme le descripteur de fichier ce qui provoque la suppression du fichier temporaire . */ close ( fd ) ; return buffer ; }

Utilisation de tmple Si vous utilisez les fonctions dE/S de la bibliothque C et navez pas besoin de passer le chier temporaire un autre programme, vous pouvez utiliser la fonction tmpfile. Elle cre et ouvre un chier temporaire, et renvoie un pointeur de chier. Le chier temporaire a dj t trait par unlink, comme dans lexemple prcdent, an dtre supprim automatiquement lorsque le pointeur sur le chier est ferm (avec fclose) ou lorsque le programme se termine.

28

CHAPITRE 2. CRIRE DES LOGICIELS GNU/LINUX DE QUALIT

GNU/Linux propose diverses autres fonctions pour gnrer des chiers temporaires et des noms de chiers temporaires, par exemple, mktemp, tmpnam et tempnam. Nutilisez pas ces fonctions, cependant, car elles sourent des problmes de abilit et de scurit mentionns plus haut.

2.2

Crer du Code Robuste

crire des programmes sexcutant correctement dans des conditions dutilisation normales est dur ; crire des programmes qui se comportent avec lgance dans des conditions derreur lest encore plus. Cette section propose quelques techniques de codage pour trouver les bogues plus tt et pour dtecter et traiter les problmes dans un programme en cours dexcution. Les exemples de code prsents plus loin dans ce livre nincluent dlibrment pas de code de vrication derreur ou de rcupration sur erreur car cela risquerait dalourdir le code et de masquer la fonctionnalit prsente. Cependant, lexemple nal du Chapitre 11, Une Application GNU/Linux dExemple , est l pour montrer comment utiliser ces techniques pour produire des applications robustes.

2.2.1

Utiliser assert

Un bon objectif conserver lesprit en permanence lorsque lon code des programmes est que des bogues ou des erreurs inattendues devraient conduire un crash du programme, ds que possible. Cela vous aidera trouver les bogues plus tt dans les cycles de dveloppement et de tests. Il est dicile de reprer les dysfonctionnements qui ne se signalent pas deux-mmes et napparaissent pas avant que le programme soit la disposition de lutilisateur. Une des mthodes les plus simples pour dtecter des conditions inattendues est la macro C standard assert. Elle prend comme argument une expression boolenne. Le programme sarrte si lexpression est fausse, aprs avoir ach un message derreur contenant le nom du chier, le numro de ligne et le texte de lexpression o lerreur est survenue. La macro assert est trs utile pour une large gamme de tests de cohrence internes un programme. Par exemple, utilisez assert pour vrier la validit des arguments passs une fonction, pour tester des prconditions et postconditions lors dappels de fonctions (ou de mthodes en C++) et pour tester des valeurs de retour inattendues. Chaque utilisation de assert constitue non seulement une vrication de condition lexcution mais galement une documentation sur le fonctionnement du programme au cur du code source. Si votre programme contient une instruction assert(condition) cela indique quelquun lisant le code source que condition devrait toujours tre vraie ce point du programme et si condition nest pas vraie, il sagit probablement dun bogue dans le programme. Pour du code dans lequel les performances sont essentielles, les vrications lexcution comme celles induites par lutilisation de assert peuvent avoir un cot signicatif en termes de performances. Dans ce cas, vous pouvez compiler votre code en dnissant la macro NDEBUG, en utilisant loption -DNDEBUG sur la ligne de commande du compilateur. Lorsque NDEBUG est dnie, le prprocesseur supprimera les occurrences de la macro assert. Il est conseill de ne le faire que lorsque cest ncessaire pour des raisons de performances et uniquement pour des chiers sources concerns par ces questions de performances.

2.2. CRER DU CODE ROBUSTE

29

Comme il est possible que le prprocesseur supprime les occurrences de assert, soyez attentif ce que les expressions que vous utilisez avec assert naient pas deet de bord. En particulier, vous ne devriez pas appeler de fonctions au sein dexpressions assert, y aecter des valeurs des variables ou utiliser des oprateurs de modication comme ++. Supposons, par exemple, que vous appeliez une fonction, do_something, de faon rptitive dans une boucle. La fonction do_something renvoie zro en cas de succs et une valeur dirente de zro en cas dchec, mais vous ne vous attendez pas ce quelle choue dans votre programme. Vous pourriez tre tent dcrire :
for ( i = 0; i < 100; ++ i ) assert ( do_something () == 0) ;

Cependant, vous pourriez trouver que cette vrication entrane une perte de performances trop importante et dcider plus tard de recompiler avec la macro NDEBUG dnie. Cela supprimerait totalement lappel assert, lexpression ne serait donc jamais value et do_something ne serait jamais appele. Voici un extrait de code eectuant la mme vrication, sans ce problme :
for ( i = 0; i < 100; ++ i ) { int status = do_something () ; assert ( status == 0) ; }

Un autre lment conserver lesprit est que vous ne devez pas utiliser assert pour tester les entres utilisateur. Les utilisateurs napprcient pas lorsque les applications plantent en achant un message derreur obscur, mme en rponse une entre invalide. Vous devriez cependant toujours vrier les saisies de lutilisateur et acher des messages derreurs comprhensibles. Nutilisez assert que pour des tests internes lors de lexcution. Voici quelques exemples de bonne utilisation dassert : Vrication de pointeurs nuls, par exemple, comme arguments de fonction invalides. Le message derreur gnr par {assert (pointer != NULL)},
Assertion pointer != ((void *)0) failed.

est plus utile que le message derreur qui serait produit dans le cas du drfencement dun pointeur nul :
Erreur de Segmentation

Vrication de conditions concernant la validit des paramtres dune fonction. Par exemple, si une fonction ne doit tre appele quavec une valeur positive pour le paramtre foo, utilisez cette expression au dbut de la fonction :
assert ( foo > 0) ;

Cela vous aidera dtecter les mauvaises utilisations de la fonction, et montre clairement quelquun lisant le code source de la fonction quil y a une restriction quant la valeur du paramtre. Ne vous retenez pas, utilisez assert librement partout dans vos programmes.

2.2.2

Problmes Lors dAppels Systme

La plupart dentre nous a appris comment crire des programmes qui sexcutent selon un chemin bien dni. Nous divisons le programme en tches et sous-tches et chaque fonction

30

CHAPITRE 2. CRIRE DES LOGICIELS GNU/LINUX DE QUALIT

accomplit une tche en invoquant dautres fonctions pour eectuer les oprations correspondant aux sous-tches. On attend dune fonction qutant donn des entres prcises, elle produise une sortie et des eets de bord corrects. Les ralits matrielles et logicielles simposent face ce rve. Les ordinateurs ont des ressources limites ; le matriel subit des pannes ; beaucoup de programmes sexcutent en mme temps ; les utilisateurs et les programmeurs font des erreurs. Cest souvent la frontire entre les applications et le systme dexploitation que ces ralits se manifestent. Aussi, lors de lutilisation dappels systme pour accder aux ressources, pour eectuer des E/S ou dautres ns, il est important de comprendre non seulement ce qui se passe lorsque lappel fonctionne mais galement comment et quand lappel peut chouer. Les appels systmes peuvent chouer de plusieurs faons. Par exemple : Le systme na plus de ressources (ou le programme dpasse la limite de ressources permises pour un seul programme). Par exemple, le programme peut tenter dallouer trop de mmoire, dcrire trop de donnes sur le disque ou douvrir trop de chiers en mme temps. Linux peut bloquer un appel systme lorsquun programme tente deectuer une opration non permise. Par exemple, un programme pourrait tenter dcrire dans un chier en lecture seule, daccder la mmoire dun autre processus ou de tuer un programme dun autre utilisateur. Les arguments dun appel systme peuvent tre invalides, soit parce-que lutilisateur a fourni des entres invalides, soit cause dun bogue dans le programme. Par exemple, le programme peut passer une adresse mmoire ou un descripteur de chier invalide un appel systme ; ou un programme peut tenter douvrir un rpertoire comme un chier rgulier ou passer le nom dun chier rgulier un appel systme qui attend un rpertoire. Un appel systme peut chouer pour des raisons externes un programme. Cela arrive le plus souvent lorsquun appel systme accde un priphrique matriel. Ce dernier peut tre dfectueux, ne pas supporter une opration particulire ou un lecteur peut tre vide. Un appel systme peut parfois tre interrompu par un vnement extrieur, comme larrive dun signal. Il ne sagit pas tout fait dun chec de lappel, mais il est de la responsabilit du programme appelant de relancer lappel systme si ncessaire. Dans un programme bien crit qui utilise abondamment les appels systme, il est courant quil y ait plus de code consacr la dtection et la gestion derreurs et dautres circonstances exceptionnelles qu la fonction principale du programme.

2.2.3

Codes dErreur des Appels Systme

Une majorit des appels systme renvoie zro si tout se passe bien ou une valeur dirente de zro si lopration choue (toutefois, beaucoup drogent la rgle ; par exemple, malloc renvoie un pointeur nul pour indiquer une erreur. Lisez toujours la page de manuel attentivement lorsque vous utilisez un appel systme). Bien que cette information puisse tre susante pour dterminer si le programme doit continuer normalement, elle ne lest certainement pas pour une rcupration able des erreurs.

2.2. CRER DU CODE ROBUSTE

31

La plupart des appels systme utilisent une variable spciale appele errno pour stocker des informations additionnelles en cas dchec4 . Lorsquun appel choue, le systme positionne errno une valeur indiquant ce qui sest mal pass. Comme tous les appels systme utilisent la mme variable errno pour stocker des informations sur une erreur, vous devriez copier sa valeur dans une autre variable immdiatement aprs lappel qui a chou. La valeur de errno sera crase au prochain appel systme. Les valeurs derreur sont des entiers ; les valeurs possibles sont fournies par des macros prprocesseur, libelles en majuscules par convention et commenant par E ? par exemple, EACCESS ou EINVAL. Utilisez toujours ces macros lorsque vous faites rfrence des valeurs de errno plutt que les valeurs entires. Incluez le chier dentte <errno.h> si vous utilisez des valeurs de errno. GNU/Linux propose une fonction utile, strerror, qui renvoie une chane de caractres contenant la description dun code derreur de errno, utilisable dans les messages derreur. Incluez <string.h> si vous dsirez utiliser strerror. GNU/Linux fournit aussi perror, qui envoie la description de lerreur directement vers le ux stderr. Passez perror une chane de caractres ajouter avant la description de lerreur, qui contient habituellement le nom de la fonction qui a chou. Incluez <stdio.h> si vous utilisez perror. Cet extrait de code tente douvrir un chier ; si louverture choue, il ache un message derreur et quitte le programme. Notez que lappel de open renvoie un descripteur de chier si lappel se passe correctement ou -1 dans le cas contraire.
fd = open ( " inputfile . txt " , O_RDONLY ) ; if ( fd == -1) {

/* Louverture a chou, affiche un message derreur et quitte. */


fprintf ( stderr , " erreur lors de l ouverture de : % s \ n " , strerror ( errno ) ) ; exit (1) ; }

Selon votre programme et la nature de lappel systme, laction approprie lors dun chec peut tre dacher un message derreur, dannuler une opration, de quitter le programme, de ressayer ou mme dignorer lerreur. Il est important cependant davoir du code qui gre toutes les raisons dchec dune faon ou dune autre. Un code derreur possible auquel vous devriez particulirement vous attendre, en particulier avec les fonctions dE/S, est EINTR. Certaines fonctions, comme read, select et sleep, peuvent mettre un certain temps sexcuter. Elles sont considres comme tant bloquantes car lexcution du programme est bloque jusqu ce que lappel se termine. Cependant, si le programme reoit un signal alors quun tel appel est en cours, celui-ci se termine sans que lopration soit acheve. Dans ce cas, errno est positionne EINTR. En gnral, vous devriez relancer lappel systme dans ce cas. Voici un extrait de code qui utilise lappel chown pour faire de lutilisateur user_id le propritaire dun chier dsign par path. Si lappel choue, le programme agit selon la valeur de errno. Notez que lorsque nous dtectons ce qui semble tre un bogue, nous utilisons abort ou assert, ce qui provoque la gnration dun chier core. Cela peut tre utile pour un dbogage
En ralit, pour des raisons disolement de threads, errno est implmente comme une macro, mais elle est utilise comme une variable globale.
4

32

CHAPITRE 2. CRIRE DES LOGICIELS GNU/LINUX DE QUALIT

postmortem. Dans le cas derreurs irrcuprables, comme des conditions de manque de mmoire, nous utilisons plutt exit et une valeur de sortie dirente de zro car un chier core ne serait pas vraiment utile.
rval = chown ( path , user_id , -1) ; if ( rval != 0) {

/* Sauvegarde errno car il sera cras par le prochain appel systme */


int error_code = errno ;

/* Lopration a chou ; chown doit retourner -1 dans ce cas. */


assert ( rval == -1) ;

/* Effectue laction approprie en fonction de la valeur de errno. */


switch ( error_code ) { case EPERM : /* Permission refuse. */ case EROFS : /* PATH est sur un systme de fichiers en lecture seule */ case ENAMETOOLONG : /* PATH est trop long. */ case ENOENT : /* PATH nexiste pas. */ case ENOTDIR : /* Une des composantes de PATH nest pas un rpertoire */ case EACCES : /* Une des composantes de PATH nest pas accessible. */

/* Quelque chose ne va pas. Affiche un message derreur. */


fprintf ( stderr , " erreur lors du changement de propritaire de % s : % s \ n " , path , strerror ( error_code ) ) ;

/* Ninterrompt pas le programme ; possibilit de proposer lutilisateur de choisir un autre fichier... */


break ; case EFAULT :

/* PATH contient une adresse mmoire invalide. Il sagit srement dun bogue */
abort () ; case ENOMEM :

/* Plus de mmoire disponible. */


fprintf ( stderr , " % s \ n " , strerror ( error_code ) ) ; exit (1) ; default :

/* Autre code derreur innatendu. Nous avons tent de grer tous les codes derreur possibles ; si nous en avons oubli un il sagit dun bogue */
abort () ; }; }

Vous pourriez vous contenter du code suivant qui se comporte de la mme faon si lappel se passe bien :
rval = chown ( path , user_id , -1) ; assert ( rval == 0) ;

Mais en cas dchec, cette alternative ne fait aucune tentative pour rapporter, grer ou reprendre aprs lerreur. Lutilisation de la premire ou de la seconde forme ou de quelque chose entre les deux dpend des besoins en dtection et rcupration derreur de votre programme.

2.2.4

Erreurs et Allocation de Ressources

Souvent, lorsquun appel systme choue, il est appropri dannuler lopration en cours mais de ne pas terminer le programme car il peut tre possible de continuer lexcution suite cette

2.2. CRER DU CODE ROBUSTE

33

erreur. Une faon de le faire est de sortir de la fonction en cours en renvoyant un code de retour qui indique lerreur. Si vous dcidez de quitter une fonction au milieu de son excution, il est important de vous assurer que toutes les ressources alloues prcdemment au sein de la fonction sont libres. Ces ressources peuvent tre de la mmoire, des descripteurs de chier, des pointeurs sur des chiers, des chiers temporaires, des objets de synchronisation, etc. Sinon, si votre programme continue sexcuter, les ressources alloues pralablement lchec de la fonction seront perdues. Considrons par exemple une fonction qui lit un chier dans un tampon. La fonction pourrait passer par les tapes suivantes : 1. Allouer le tampon ; 2. Ouvrir le chier ; 3. Lire le chier dans le tampon ; 4. Fermer le chier ; 5. Retourner le tampon. Le Listing 2.6 montre une faon dcrire cette fonction. Si le chier nexiste pas, ltape 2 chouera. Une rponse approprie cet vnement serait que la fonction retourne NULL. Cependant, si le tampon a dj t allou ltape 1, il y a un risque de perdre cette mmoire. Vous devez penser librer le tampon dans chaque bloc de la fonction qui en provoque la sortie. Si ltape 3 ne se droule pas correctement, vous devez non seulement librer le tampon mais galement fermer le chier. Listing 2.6 (readle.c) Librer les Ressources
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

# include < fcntl .h > # include < stdlib .h > # include < sys / stat .h > # include < sys / types .h > # include < unistd .h > char * re ad _f rom _f il e ( const char * filename , size_t length ) { char * buffer ; int fd ; ssize_t bytes_read ; /* Alloue le tampon . */ buffer = ( char *) malloc ( length ) ; if ( buffer == NULL ) return NULL ; /* Ouvre le fichier . */ fd = open ( filename , O_RDONLY ) ; if ( fd == -1) { /* L ouverture a chou . Libre le tampon avant de quitter . */ free ( buffer ) ; return NULL ; } /* Lit les donnes . */ bytes_read = read ( fd , buffer , length ) ; if ( bytes_read != length ) { /* La lecture a chou . Libre le tampon et ferme fd avant de quitter . */ free ( buffer ) ; close ( fd ) ; return NULL ; }

34
30 31 32 33

CHAPITRE 2. CRIRE DES LOGICIELS GNU/LINUX DE QUALIT


/* Tout va bien . Ferme le fichier et renvoie le tampon . */ close ( fd ) ; return buffer ; }

Linux libre la mmoire, ferme les chiers et la plupart des autres ressources automatiquement lorsquun programme se termine, il nest donc pas ncessaire de librer les tampons et de fermer les chiers avant dappeler exit. Vous pourriez nanmoins devoir librer manuellement dautres ressources partages, comme les chiers temporaires et la mmoire partage, qui peuvent potentiellement survivre un programme.

2.3

crire et Utiliser des Bibliothques

Pratiquement tous les programmes sont lis une ou plusieurs bibliothques. Tout programme qui utilise une fonction C (comme printf ou malloc) sera li la bibliothque dexcution C. Si votre programme a une interface utilisateur graphique (Graphical User Interface, GUI), il sera li aux bibliothques de fentrage. Si votre programme utilise une base de donnes, le fournisseur de base de donnes vous fournira des bibliothques permettant daccder la base de donne de faon pratique. Dans chacun de ces cas, vous devez dcider si la bibliothque doit tre lie de faon statique ou dynamique. Si vous choisissez la liaison statique, votre programme sera plus gros et plus dicile mettre jour, mais probablement plus simple dployer. Si vous optez pour la liaison dynamique, votre programme sera petit, plus simple mettre jour mais plus compliqu dployer. Cette section explique comment eectuer une liaison statique et dynamique, examine les deux options en dtail et donne quelques rgles empiriques pour dcider quelle est la meilleure dans votre cas.

2.3.1

Archives

Une archive (ou bibliothque statique) est tout simplement une collection de chiers objets stocke dans un seul chier objet (une archive est en gros quivalent un chier .LIB sous Windows). Lorsque vous fournissez une archive lditeur de liens, il recherche au sein de cette archive les chiers dont il a besoin, les extrait et les lie avec votre programme comme si vous aviez fourni ces chiers objets directement. Vous pouvez crer une archive en utilisant la commande ar. Les chiers archives utilisent traditionnellement une extension .a plutt que lextension .o utilise par les chiers objets ordinaires. Voici comment combiner test1.o et test2.o dans une seule archive libtest.a :
% ar cr libtest.a test1.o test2.o

Le drapeau cr indique ar de crer larchive5 . Vous pouvez maintenant lier votre programme avec cette archive en utilisant loption -ltest avec gcc ou g++, comme le dcrit la Section 1.2.2, Lier les Fichiers Objets du Chapitre 1, Pour Commencer .
Vous pouvez utiliser dautres options pour supprimer un chier dune archive ou eectuer dautres oprations sur larchive. Ces oprations sont rarement utilises mais sont documentes sur la page de manuel de ar.
5

2.3. CRIRE ET UTILISER DES BIBLIOTHQUES

35

Lorsque lditeur de liens dtecte une archive sur la ligne de commande, il y recherche les dnitions des symboles (fonctions ou variables) qui sont rfrencs dans les chiers objets qui ont dj t traits mais ne sont pas encore dnis. Les chiers objets qui dnissent ces symboles sont extraits de larchive et inclus dans lexcutable nal. Comme lditeur de liens eectue une recherche dans larchive lorsquil la rencontre sur la ligne de commande, il est habituel de placer les archives la n de la ligne de commande. Par exemple, supposons que test.c contienne le code du Listing 2.7 et que app.c contienne celui du Listing 2.8. Listing 2.7 (test.c) Contenu de la bibliothque
1 2 3 4

int f () { return 3; }

Listing 2.8 (app.c) Programme Utilisant la Bibliothque


1 2 3 4

int main () { return f () ; }

Supposons maintenant que test.o soit combin un autre chier objet quelconque pour produire larchive libtest.a. La ligne de commande suivante ne fonctionnerait pas :
% gcc -o app -L. -ltest app.o app.o: In function "main": app.o(.text+0x4): undefined reference to "f" collect2: ld returned 1 exit status

Le message derreur indique que mme si libtest.a contient la dnition de f, lditeur de liens ne la trouve pas. Cest d au fait que libtest.a a t inspecte lorsquelle a t dtecte pour la premire fois et ce moment lditeur de liens navait pas rencontr de rfrence f. Par contre, si lon utilise la ligne de commande suivante, aucun message derreur nest mis :
% gcc -o app app.o -L. -ltest

La raison en est que la rfrence f dans app.o oblige lditeur de liens inclure le chier objet test.o depuis larchive libtest.a.

2.3.2

Bibliothques Partages

Une bibliothque partage (galement appele objet partag ou bibliothque dynamique) est similaire une archive en ceci quil sagit dun groupe de chiers objets. Cependant, il y a beaucoup de dirences importantes. La dirence la plus fondamentale est que lorsquune bibliothque partage est lie un programme, lexcutable nal ne contient pas vraiment le code prsent dans la bibliothque partage. Au lieu de cela, lexcutable contient simplement une rfrence cette bibliothque. Si plusieurs programmes sur le systme sont lis la mme bibliothque partage, ils rfrenceront tous la bibliothque, mais aucun ne linclura rellement. Donc, la bibliothque est partage entre tous les programmes auxquels elle est lie.

36

CHAPITRE 2. CRIRE DES LOGICIELS GNU/LINUX DE QUALIT

Une seconde dirence importante est quune bibliothque partage nest pas seulement une collection de chiers objets, parmi lesquels lditeur de liens choisit ceux qui sont ncessaires pour satisfaire les rfrences non dnies. Au lieu de cela, les chiers objets qui composent la bibliothque sont combins en un seul chier objet an quun programme li la bibliothque partage inclut toujours tout le code de la bibliothque plutt que de ninclure que les portions ncessaires. Pour crer une bibliothque partage, vous devez compiler les objets qui constitueront la bibliothque en utilisant loption -fPIC du compilateur, comme ceci :
% gcc -c -fPIC test1.c

Loption -fPIC indique au compilateur que vous allez utiliser test1.o en tant qulment dun objet partag.

Code Indpendant de la Position (Position-Independent Code, PIC) PIC signie code indpendant de la position. Les fonctions dune bibliothque partage peuvent tre charges direntes adresses dans dirents programmes, le code de lobjet partag ne doit donc pas dpendre de ladresse (ou position) laquelle il est charg. Cette considration na pas dimpact votre niveau, en tant que programmeur, except que vous devez vous souvenir dutiliser loption -fPIC lors de la compilation du code utilis pour la bibliothque partage.

Puis, vous combinez les chiers objets au sein dune bibliothque partage, comme ceci :
% gcc -shared -fPIC -o libtest.so test1.o test2.o

Loption -shared indique lditeur de liens de crer une bibliothque partage au lieu dun excutable ordinaire. Les bibliothques partages utilisent lextension .so, ce qui signie objet partag (shared object). Comme pour les archives statiques, le nom commence toujours par lib pour indiquer que le chier est une bibliothque. Lier un programme une bibliothque partage se fait de la mme manire que pour une archive statique. Par exemple, la ligne suivante liera le programme libtest.so si elle est dans le rpertoire courant ou dans un des rpertoires de recherche de bibliothques du systme :
% gcc -o app app.o -L. -ltest

Supposons que libtest.a et libtest.so soient disponibles. Lditeur de liens doit choisir une seule des deux bibliothques. Il commence par rechercher dans chaque rpertoire (tout dabord ceux indiqus par loption -L, puis dans les rpertoires standards). Lorsque lditeur de liens trouve un rpertoire qui contient soit libtest.a soit libtest.so, il interrompt ses recherches. Si une seule des deux variantes est prsente dans le rpertoire, lditeur de liens la slectionne. Sinon, il choisit la version partage moins que vous ne lui spciiez explicitement le contraire. Vous pouvez utiliser loption -static pour utiliser les archives statiques. Par exemple, la ligne de commande suivante utilisera larchive libtest.a, mme si la bibliothque partage libtest.so est disponible :

2.3. CRIRE ET UTILISER DES BIBLIOTHQUES


% gcc -static -o app app.o -L. -ltest

37

La commande ldd indique les bibliothques partages lies un programme. Ces bibliothques doivent tre disponibles lexcution du programme. Notez que ldd indiquera une bibliothque supplmentaire appele ld-linux.so qui fait partie du mcanisme de liaison dynamique de GNU/Linux. Utiliser LD_LIBRARY_PATH Lorsque vous liez un programme une bibliothque partage, lditeur de liens ne place pas le chemin daccs complet la bibliothque dans lexcutable. Il ny place que le nom de la bibliothque partage. Lorsque le programme est excut, le systme recherche la bibliothque partage et la charge. Par dfaut, cette recherche nest eectue que dans /lib et /usr/lib par dfaut. Si une bibliothque partage lie votre programme est place un autre endroit que dans ces rpertoires, elle ne sera pas trouve et le systme refusera de lancer le programme. Une solution ce problme est dutiliser les options -Wl, -rpath lors de ldition de liens du programme. Supposons que vous utilisiez ceci :
% gcc -o app app.o -L. -ltest -Wl,-rpath,/usr/local/lib

Dans ce cas, lorsquon lance le programme app, recherche les bibliothques ncessaires dans /usr/local/lib. Une autre solution ce problme est de donner une valeur la variable denvironnement LD_LIBRARY_PATH au lancement du programme. Tout comme la variable denvironnement PATH, LD_LIBRARY_PATH est une liste de rpertoires spars par deux-points. Par exemple, si vous positionnez LD_LIBRARY_PATH /usr/local/lib:/opt/lib alors les recherches seront eectues au sein de /usr/local/lib et /opt/lib avant les rpertoires standards /lib et /usr/lib. Vous devez tre conscient que si LD_LIBRARY_PATH est renseign, lditeur de liens recherchera les bibliothques au sein des rpertoires qui y sont spcis avant ceux passs via loption -L lorsquil cre un excutable6 .

2.3.3

Bibliothques Standards

Mme si vous ne spciez aucune bibliothque lorsque vous compilez votre programme, il est quasiment certain quil utilise une bibliothque partage. Car GCC lie automatiquement la bibliothque standard du C, libc, au programme. Les fonctions mathmatiques de la bibliothque standard du C ne sont pas inclues dans libc ; elles sont places dans une bibliothque spare, libm, que vous devez spcier explicitement. Par exemple, pour compiler et lier un programme compute.c qui utilise des fonctions trigonomtriques comme sin et cos, vous devez utiliser ce code :
% gcc -o compute compute . c - lm

Si vous crivez un programme C++ et le liez en utilisant les commandes c++ ou g++, vous aurez galement la bibliothque standard du C++, libstdc++.
Vous pourriez voir des rfrences LD_RUN_PATH dans certaines documentations en ligne. Ne croyez pas ce que vous lisez, cette variable na en fait aucun eet sous GNU/Linux.
6

38

CHAPITRE 2. CRIRE DES LOGICIELS GNU/LINUX DE QUALIT

2.3.4

Dpendances entre Bibliothques

Les bibliothques dpendent souvent les unes des autres. Par exemple, beaucoup de systmes GNU/Linux proposent libtiff, une bibliothque contenant des fonctions pour lire et crire des chiers images au format TIFF. Cette bibliothque utilise son tour les bibliothques libjpeg (routines de manipulation dimages JPEG) et libz (routines de compression). Le Listing 2.9 prsente un trs petit programme qui utilise libtiff pour ouvrir une image TIFF. Listing 2.9 (titest.c) Utilisation de libti
1 2 3 4 5 6 7 8 9

# include < stdio .h > # include < tiffio .h > int main ( int argc , char ** argv ) { TIFF * tiff ; tiff = TIFFOpen ( argv [1] , " r " ) ; TIFFClose ( tiff ) ; return 0; }

Sauvegardez ce chier sous le nom de tifftest.c. Pour le compiler et le lier libtiff, spciez -ltiff sur votre ligne de commande :
% gcc -o tifftest tifftest.c -ltiff

Par dfaut, cest la version partage de la bibliothque libtiff qui sera utilise, il sagit du chier /usr/lib/libtiff.so. Comme libtiff utilise libjpeg et libz, les versions partages de ces bibliothque sont galement lies (une bibliothque partage peut pointer vers dautres bibliothques partages dont elle dpend). Pour vrier cela, utilisez la commande ldd :
% ldd tifftest libtiff.so.3 => /usr/lib/libtiff.so.3 (0x4001d000) libc.so.6 => /lib/libc.so.6 (0x40060000) libjpeg.so.62 => /usr/lib/libjpeg.so.62 (0x40155000) libz.so.1 => /usr/lib/libz.so.1 (0x40174000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000) ...

Pour lier ce programme de faon statique, vous devez spcier les deux autres bibliothques :
% gcc -static -o tifftest tifftest.c -ltiff -ljpeg -lz -lm

De temps en temps, deux bibliothques dpendent mutuellement lune de lautre. En dautres termes, la premire archive fait rfrence des symboles dnis dans la seconde et vice versa. Cette situation rsulte gnralement dune mauvaise conception mais existe. Dans ce cas, vous pouvez fournir une mme bibliothque plusieurs fois sur la ligne de commande. Lditeur de liens recherchera les symboles manquants chaque apparition de la bibliothque. Par exemple, cette ligne provoque deux recherches de symboles au sein de libfoo :
% gcc -o app app.o -lfoo -lbar -lfoo

Donc, mme si libfoo.a fait rfrence des symboles de libbar.a et vice versa, le programme sera compil avec succs.

2.3. CRIRE ET UTILISER DES BIBLIOTHQUES

39

2.3.5

Avantages et Inconvnients

Maintenant que vous savez tout propos des archives statiques et des bibliothques partages, vous vous demandez probablement lesquelles utiliser. Il y a quelques lments garder lesprit. Un avantage majeur des bibliothques partages est quelles permettent dconomiser de la place sur le systme o le programme est install. Si vous installez dix programmes et quils utilisent tous la mme bibliothque partage, vous conomiserez beaucoup despace utiliser une bibliothque partage. Si vous utilisez une archive statique la place, larchive est incluse dans les dix programmes. Donc, utiliser des bibliothques partages permet dconomiser de lespace disque. Cela rduit galement le temps de tlchargement si votre programme est distribu via le Web. Un avantage li aux bibliothques partages est que les utilisateurs peuvent mettre niveau les bibliothques sans mettre niveau tous les programmes qui en dpendent. Par exemple, supposons que vous criez une bibliothque partage qui gre les connexions HTTP. Beaucoup de programmes peuvent dpendre de cette bibliothque. Si vous dcouvrez un bogue dans celleci, vous pouvez mettre niveau la bibliothque. Instantanment, tous les programmes qui en dpendent bncieront de la correction ; vous navez pas repasser par une tape ddition de liens pour tous les programmes, comme vous le feriez avec une archive statique. Ces avantages pourraient vous faire penser que vous devez toujours utiliser les bibliothques partages. Cependant, il existe des raisons valables pour utiliser les archives statiques. Le fait quune mise jour de la bibliothque aecte tous les programmes qui en dpendent peut reprsenter un inconvnient. Par exemple, si vous dveloppez un logiciel critique, il est prfrable de le lier avec une archive statique an quune mise jour des bibliothques sur le systme hte naecte pas votre programme (autrement, les utilisateurs pourraient mettre jour les bibliothques, empchant votre programme de fonctionner, puis appeler votre service client en disant que cest de votre faute !). Si vous ntes pas sr de pouvoir installer vos bibliothques dans /lib ou /usr/lib, vous devriez dnitivement rchir deux fois avant dutiliser une bibliothque partage (vous ne pourrez pas installer vos bibliothques dans ces rpertoires si votre logiciel est destin pouvoir tre install par des utilisateurs ne disposant pas des droits dadministrateur). De plus, lastuce de loption -Wl, -rpath ne fonctionnera pas si vous ne savez pas o seront places les bibliothques en dnitive. Et demander vos utilisateurs de positionner LD_LIBRARY_PATH leur impose une tape de conguration supplmentaire. Comme chaque utilisateur doit le faire individuellement, il sagit dune contrainte additionnelle non ngligeable. Il faut bien peser ces avantages et inconvnients pour chaque programme que vous distribuez.

2.3.6

Chargement et Dchargement Dynamiques

Il est parfois utile de pouvoir charger du code au moment de lexcution sans lier ce code explicitement. Par exemple, considrons une application qui supporte des plugins , comme un navigateur Web. Le navigateur permet des dveloppeurs tiers de crer des plugins pour fournir des fonctionnalits supplmentaires. Ces dveloppeurs crent des bibliothques partages

40

CHAPITRE 2. CRIRE DES LOGICIELS GNU/LINUX DE QUALIT

et les placent un endroit prdni. Le navigateur charge alors automatiquement le code de ces bibliothques. Cette fonctionnalit est disponible sous Linux en utilisant la fonction dlopen. Vous ouvrez une bibliothque appele libtest.so en appelant dlopen comme ceci :
dlopen ( " libtest . so " , RTLD_LAZY )

(Le second paramtre est un drapeau qui indique comment lier les symboles de la bibliothque partage. Vous pouvez consulter les pages de manuel de dlopen pour plus dinformations, mais RTLD_LAZY est gnralement loption conseille). Pour utiliser les fonctions de chargement dynamique, incluez lentte <dlfcn.h> et liez avec loption -ldl pour inclure la bibliothque libdl. La valeur de retour de cette fonction est un void * utilis comme rfrence sur la bibliothque partage. Vous pouvez passer cette valeur la fonction dlsym pour obtenir ladresse dune fonction charge avec la bibliothque partage. Par exemple, si libtest.so dnit une fonction appele my_function, vous pourriez lappeler comme ceci :
void * handle = dlopen ( " libtest . so " , RTLD_LAZY ) ; void (* test ) () = dlsym ( handle , " my_function " ) ; (* test ) () ; dlclose ( handle ) ;

Lappel systme dlsym peut galement tre utilis pour obtenir un pointeur vers une variable static de la bibliothque partage. dlopen et dlsym renvoient toutes deux NULL si lappel choue. Dans ce cas, vous pouvez appeler dlerror (sans paramtre) pour obtenir un message derreur lisible dcrivant le problme. La fonction dlclose dcharge la bibliothque. Techniquement, dlopen ne charge la bibliothque que si elle nest pas dj charge. Si elle lest, dlopen se contente dincrmenter le compteur de rfrences pour la bibliothque. De mme, dlclose dcrmente ce compteur et ne dcharge la bibliothque que sil a atteint zro. Si vous crivez du code en C++ dans votre bibliothque partage, vous devriez dclarer les fonctions et variables que vous voulez rendre accessible de lextrieur avec le modicateur extern "C". Par exemple, si la fonction C++ my_function est dans une bibliothque partage et que vous voulez la rendre accessible par dlsym, vous devriez la dclarer comme suit :
extern " C " void my_function () ;

Cela vite que le compilateur C++ ne modie le nom de la fonction ce qui rsulterait en un nom dirent laspect sympathique qui encode des informations supplmentaires sur la fonction. Un compilateur C ne modie pas les noms ; il utilise toujours les noms que vous donnez votre fonction ou variable.

Chapitre 3

Processus Une instance dun programme en cours dexcution est appele un processus.
Si vous avez deux fentres de terminal sur votre cran, vous excutez probablement deux fois le mme programme de terminal vous avez deux processus de terminal. Chaque fentre de terminal excute probablement un shell ; chaque shell en cours dexcution est un processus indpendant. Lorsque vous invoquez un programme depuis le shell, le programme correspondant est excut dans un nouveau processus ; le processus shell reprend lorsque ce processus ce termine. Les programmeurs expriments utilisent souvent plusieurs processus cooprant entre eux au sein dune mme application an de lui permettre de faire plus dune chose la fois, pour amliorer sa robustesse et utiliser des programmes dj existants. La plupart des fonctions de manipulation de processus dcrites dans ce chapitre sont similaires celles des autres systmes UNIX. La majorit dentre elles est dclare au sein de lentte <unistd.h> ; vriez la page de manuel de chaque fonction pour vous en assurer.

3.1

Aperu des Processus

Mme lorsque tout ce que vous faites est tre assis devant votre ordinateur, des processus sont en cours dexcution. Chaque programme utilise un ou plusieurs processus. Commenons par observer les processus dj prsents sur votre ordinateur.

3.1.1

Identiants de Processus

Chaque processus dun systme Linux est identi par son identiant de processus unique, quelquefois appel pid (process ID). Les identiants de processus sont des nombres de 16 bits assigns de faon squentielle par Linux aux processus nouvellement crs. Chaque processus a galement un processus parent (sauf le processus spcial init, dcrit dans la Section 3.4.3, Processus Zombies ). Vous pouvez donc vous reprsenter les processus dun systme Linux comme un arbre, le processus init tant la racine. Lidentiant de processus parent (parent process ID), ou ppid, est simplement lidentiant du parent du processus. Lorsque vous faites rfrence aux identiants de processus au sein dun programme C ou C++, utilisez toujours le typedef pid_t, dni dans <sys/types.h>. Un programme peut obtenir 41

42

CHAPITRE 3. PROCESSUS

lidentiant du processus au sein duquel il sexcute en utilisant lappel systme getpid() et lidentiant de son processus parent avec lappel systme getppid(). Par exemple, le programme du Listing 3.1 ache son identiant de processus ainsi que celui de son parent. Listing 3.1 (print-pid.c) Acher lIdentiant de Processus
1 2 3 4 5 6 7 8 9

# include < stdio .h > # include < unistd .h > int main { printf printf return } () ( " L identifiant du processus est % d \ n " , ( int ) getpid () ) ; ( " L identifiant du processus parent est % d \ n " , ( int ) getppid () ) ; 0;

Notez que si vous invoquez ce programme plusieurs fois, un identiant de processus dirent est indiqu chaque fois car chaque invocation cre un nouveau processus. Cependant, si vous linvoquez toujours depuis le mme shell, lidentiant du processus parent (cest--dire lidentiant de processus du shell) est le mme.

3.1.2

Voir les Processus Actifs

La commande ps ache les processus en cours dexcution sur votre systme. La version GNU/Linux de ps dispose dun grand nombre doptions car elle tente dtre compatible avec les versions de ps de plusieurs autres variantes dUNIX. Ces options contrlent les processus lists et les informations les concernant qui sont aches. Par dfaut, invoquer ps ache les processus contrls par le terminal ou la fentre de terminal la commande est invoque. Par exemple :
% ps PID TTY TIME CMD 21693 pts/8 00:00:00 bash 21694 pts/8 00:00:00 ps

Cette invocation de ps montre deux processus. Le premier, bash, est le shell sexcutant au sein du terminal. Le second est linstance de ps en cours dexcution. La premire colonne, PID, indique lidentiant de chacun des processus. Pour un aperu plus dtaill des programmes en cours dexcution sur votre systme GNU/Linux, invoquez cette commande :
% ps -e -o pid,ppid,command

Loption -e demande ps dacher tous processus en cours dexcution sur le systme. Loption -o pid,ppid,command indique ps les informations acher sur chaque processus dans ce cas, lidentiant de processus, lidentiant du processus parent et la commande correspondant au processus.

Voici les premires et dernires lignes de la sortie de cette commande sur mon systme. Elles peuvent direr des vtres, selon les programmes en cours dexcution sur votre systme :

3.2. CRER DES PROCESSUS Formats de Sortie ps

43

Avec loption -o de la commande ps, vous indiquez les informations sur les processus que vous voulez voir sacher sous forme dune liste de valeurs spares par des virgules. Par exemple, ps -o pid,user,start_time,command ache lidentiant de processus, le nom de lutilisateur propritaire du processus, lheure de dmarrage du processus et la commande sexcutant au sein du processus. Consultez la page de manuel de ps pour une liste complte des codes. Vous pouvez utiliser les options -f (full listing, listing complet), -l (long listing) ou -j (jobs listing) pour obtenir trois formats de listing prdnis.

% ps -e -o pid,ppid,command PID PPID COMMAND 1 0 init [5] 2 1 [kflushd] 3 1 [kupdate] ... 21725 21693 xterm 21727 21725 bash 21728 21727 ps -e -o pid,ppid,command

Notez que lidentiant du processus parent de la commande ps, 21727, est lidentiant du processus de bash, le shell depuis lequel jai invoqu ps. Lidentiant du processus parent de bash est 21725, lidentiant du processus du programme xterm dans lequel le shell sexcute.

3.1.3

Tuer un Processus

Vous pouvez tuer un processus en cours dexcution grce la commande kill. Spciez simplement sur la ligne de commande lidentiant du processus tuer. La commande kill envoie un signal1 SIGTERM, ou de terminaison au processus. Cela termine le processus, moins que le programme en cours dexcution ne gre ou ne masque le signal SIGTERM. Les signaux sont dcrits dans la Section 3.3, Signaux .

3.2

Crer des Processus

Deux techniques courantes sont utilises pour crer de nouveaux processus. La premire est relativement simple mais doit tre utilise avec modration car elle est peu performante et prsente des risques de scurit considrables. La seconde technique est plus complexe mais ore une exibilit, une rapidit et une scurit plus grandes.

3.2.1

Utiliser system

La fonction system de la bibliothque standard propose une manire simple dexcuter une commande depuis un programme, comme si la commande avait t tape dans un shell. En fait, system cre un sous-processus dans lequel sexcute le shell Bourne standard (/bin/sh) et passe
Vous pouvez galement utiliser la commande kill pour envoyer dautres signaux un processus. Reportezvous la Section 3.4, Fin de Processus
1

44

CHAPITRE 3. PROCESSUS

la commande ce shell pour quil lexcute. Par exemple, le programme du Listing 3.2 invoque la commande ls pour acher le contenu du rpertoire racine, comme si vous aviez saisi ls -l / dans un shell. Listing 3.2 (system.c) Utiliser la Fonction system
1 2 3 4 5 6

int main () { int return_value ; return_value = system ( " ls -l / " ) ; return return_value ; }

La fonction system renvoie le code de sortie de la commande shell. Si le shell lui-mme ne peut pas tre lanc, system renvoie 127 ; si une autre erreur survient, system renvoie -1. Comme la fonction system utilise un shell pour invoquer votre commande elle est soumises aux fonctionnalits, limitations et failles de scurit du shell systme. Vous ne pouvez pas vous reposer sur la disponibilit dune version spcique du shell Bourne. Sur beaucoup de systmes UNIX, /bin/sh est en fait un lien symbolique vers un autre shell. Par exemple, sur la plupart des systmes GNU/Linux, /bin/sh pointe vers bash (le Bourne-Again SHell) et des distributions direntes de GNU/Linux utilisent direntes version de bash. Invoquer un programme avec les privilges root via la fonction system peut donner des rsultats dirents selon le systme GNU/Linux. Il est donc prfrable dutiliser la mthode de fork et exec pour crer des processus.

3.2.2

Utiliser fork et exec

Les API DOS et Windows proposent la famille de fonctions spawn. Ces fonctions prennent en argument le nom du programme excuter et crent une nouvelle instance de processus pour ce programme. Linux ne dispose pas de fonction eectuant tout cela en une seule fois. Au lieu de cela, Linux ore une fonction, fork, qui produit un processus ls qui est lexacte copie de son processus parent. Linux fournit un autre jeu de fonctions, la famille exec, qui fait en sorte quun processus cesse dtre une instance dun certain programme et devienne une instance dun autre. Pour crer un nouveau processus, vous utilisez tout dabord fork pour crer une copie du processus courant. Puis vous utilisez exec pour transformer un de ces processus en une instance du programme que vous voulez crer. Appeler fork et exec Lorsquun programme appelle fork, une copie du processus, appele processus ls, est cre. Le processus parent continue dexcuter le programme partir de lendroit o fork a t appel. Le processus ls excute lui aussi le mme programme partir du mme endroit. Quest-ce qui direncie les deux processus alors ? Tout dabord, le processus ls est un nouveau processus et dispose donc dun nouvel identiant de processus, distinct de celui de lidentiant de son processus parent. Un moyen pour un programme de savoir sil fait partie du processus pre ou du processus ls est dappeler la mthode getpid. Cependant, la fonction fork renvoie des valeurs direntes aux processus parent et enfant un processus rentre dans le fork et deux en ressortent avec des valeurs de retour direntes. La valeur de retour

3.2. CRER DES PROCESSUS

45

dans le processus pre est lidentiant du processus ls. La valeur de retour dans le processus ls est zro. Comme aucun processus na lidentiant zro, il est facile pour le programme de dterminer sil sexcute au sein du processus pre ou du processus ls. Le Listing 3.3 est un exemple dutilisation de fork pour dupliquer un processus. Notez que le premier bloc de la structure if nest excut que dans le processus parent alors que la clause else est excute dans le processus ls. Listing 3.3 (fork.c) Utiliser fork pour Dupliquer un Processus
1 2 3 4 5 6 7 8 9 10 11 12 13

int main () { pid_t child_pid ; printf ( " ID de processus du programme principal : % d \ n " ,( int ) getpid () ) ; child_pid = fork () ; if ( child_pid != 0) { printf ( " je suis le processus parent , ID : % d \ n " , ( int ) getpid () ) ; printf ( " Identifiant du processus fils : % d \ n " , ( int ) child_pid ) ; } else printf ( " je suis le processus fils , ID : % d \ n " , ( int ) getpid () ) ; return 0; }

Utiliser la Famille de exec Les fonctions exec remplacent le programme en cours dexcution dans un processus par un autre programme. Lorsquun programme appelle la fonction exec, le processus cesse immdiatement dexcuter ce programme et commence lexcution dun autre depuis le dbut, en supposant que lappel exec se droule correctement. Au sein de la famille de exec existent plusieurs fonctions qui varient lgrement quant aux possibilits quelles proposent et la faon de les appeler. Les fonctions qui contiennent la lettre p dans leur nom (execvp et execlp) reoivent un nom de programme quelles recherchent dans le path courant ; il est ncessaire de passer le chemin daccs complet du programme aux fonctions qui ne contiennent pas de p. Les fonctions contenant la lettre v dans leur nom (execv, execvp et execve) reoivent une liste darguments passer au nouveau programme sous forme dun tableau de pointeurs vers des chanes termin par NULL. Les fonctions contenant la lettre l (execl, execlp et execle) reoivent la liste darguments via le mcanisme du nombre darguments variables du langage C. Les fonctions qui contiennent la lettre e dans leur nom (execve et execle) prennent un argument supplmentaire, un tableau de variables denvironnement. Largument doit tre un tableau de pointeurs vers des chanes termin par NULL. Chaque chane doit tre de la forme "VARIABLE=valeur". Dans la mesure o exec remplace le programme appelant par un autre, on nen sort jamais moins que quelque chose ne se droule mal. Les arguments passs un programme sont analogues aux arguments de ligne de commande que vous transmettez un programme lorsque vous le lancez depuis un shell. Ils sont accessibles par le biais des paramtres argc et argv de main. Souvenez-vous que lorsquun programme est

46

CHAPITRE 3. PROCESSUS

invoqu depuis le shell, celui-ci place dans le premier lment de la liste darguments (argv[0]) le nom du programme, dans le second (argv[1]) le premier paramtre en ligne de commande, etc. Lorsque vous utilisez une fonction exec dans votre programme, vous devriez, vous aussi, passer le nom du programme comme premier lment de la liste darguments. Utiliser fork et exec Un idiome courant pour lexcution de sous-programme au sein dun programme est deectuer un fork puis dappeler exec pour le sous-programme. Cela permet au programme appelant de continuer sexcuter au sein du processus parent alors quil est remplac par le sousprogramme dans le processus ls. Le programme du Listing 3.4, comme celui du Listing 3.2, liste le contenu du rpertoire racine en utilisant la commande ls. Contrairement lexemple prcdent, cependant, il utilise la commande ls directement, en lui passant les arguments -l et / plutt que de linvoquer depuis un shell. Listing 3.4 (fork-exec.c) Utiliser fork et exec
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38

# include < stdio .h > # include < stdlib .h > # include < sys / types .h > # include < unistd .h > /* Cre un processus fils excutant un nouveau programme . PROGRAM est le nom du programme excuter ; le programme est recherch dans le path . ARG_LIST est une liste termine par NULL de chanes de caractres passer au programme comme liste d arguments . Renvoie l identifiant du processus nouvellement cr . */ int spawn ( char * program , char ** arg_list ) { pid_t child_pid ; /* Duplique ce processus . */ child_pid = fork () ; if ( child_pid != 0) /* Nous sommes dans le processus parent . */ return child_pid ; else { /* Excute PROGRAM en le recherchant dans le path . */ execvp ( program , arg_list ) ; /* On ne sort de la fonction execvp uniquement si une erreur survient . */ fprintf ( stderr , " une erreur est survenue au sein de execvp \ n " ) ; abort () ; } } int main () { /* Liste d arguments passer la commande " ls ". */ char * arg_list [] = { " ls " , /* argv [0] , le nom du programme . */ " -l " , "/", NULL /* La liste d arguments doit se terminer par NULL . */ }; /* Cre un nouveau processus fils excutant la commande " ls ". Ignore l identifiant du processus fils renvoy . */ spawn ( " ls " , arg_list ) ; printf ( " Fin du programme principal \ n " ) ;

3.2. CRER DES PROCESSUS


39 40

47

return 0; }

3.2.3

Ordonnancement de Processus

Linux ordonnance le processus pre indpendamment du processus ls ; il ny a aucune garantie sur celui qui sera excut en premier ou sur la dure pendant laquelle le premier sexcutera avant que Linux ne linterrompe et ne passe la main lautre processus (ou unprocessus quelconque sexcutant sur le systme). Dans notre cas, lorsque le processus parent se termine, la commande ls peut stre excute entirement, partiellement ou pas du tout2 . Linux garantit que chaque processus nira par sexcuter aucun processus ne sera cours de ressources. Vous pouvez indiquer quun processus est moins important et devrait avoir une priorit infrieure en lui assignant une valeur de priorit dordonnancement 3 plus grande. Par dfaut, chaque processus a une priorit dordonnancement de zro. Une valeur plus leve signie que le processus a une priorit dexcution plus faible ; inversement un processus avec une priorit dordonnancement plus faible (cest--dire, ngative) obtient plus de temps dexcution. Pour lancer un programme avec une priorit dordonnancement dirente de zro, utilisez la commande nice, en spciant la valeur de la priorit dordonnancement avec loption -n. Par exemple, voici comment vous pourriez invoquer la commande sort input.txt > output.txt, une opration de tri longue, avec une priorit rduite an quelle ne ralentisse pas trop le systme :
% nice -n 10 sort input.txt > output.txt

Vous pouvez utiliser la commande renice pour changer la priorit dordonnancement dun processus en cours dexcution depuis la ligne de commande. Pour changer la priorit dordonnancement dun processus en cours dexcution par programmation, utilisez la fonction nice. Son argument est une valeur dajustement qui est ajoute la valeur de la priorit dordonnancement du processus qui lappelle. Souvenez-vous quune valeur positive augmente la priorit dordonnancement du processus et donc rduit la priorit dexcution du processus. Notez que seuls les processus disposant des privilges root peuvent lancer un processus avec une priorit dordonnancement ngative ou rduire la valeur de la priorit dordonnancement dun processus en cours dexcution. Cela signie que vous ne pouvez passer des valeurs ngatives aux commandes nice et renice que lorsque vous tes connect en tant que root et seul un processus sexcutant avec les privilges root peut passer une valeur ngative la fonction nice. Cela vite que des utilisateurs ordinaires puissent sapproprier tout le temps dexcution.
Une mthode visant excuter les deux processus de manire squentielle est prsente dans la Section 3.4.1, Attendre la Fin dun Processus . 3 NdT. le terme original est niceness (gentillesse), ce qui explique que plus la valeur est grande, plus le processus est gentil et donc moins il est prioritaire.
2

48

CHAPITRE 3. PROCESSUS

3.3

Signaux

Les signaux sont des mcanismes permettant de manipuler et de communiquer avec des processus sous Linux. Le sujet des signaux est vaste ; nous traiterons ici quelques uns des signaux et techniques utiliss pour contrler les processus. Un signal est un message spcial envoy un processus. Les signaux sont asynchrones ; lorsquun processus reoit un signal, il le traite immdiatement, sans mme terminer la fonction ou la ligne de code en cours. Il y a plusieurs douzaines de signaux dirents, chacun ayant une signication dirente. Chaque type de signal est caractris par son numro de signal, mais au sein des programmes, on y fait souvent rfrence par un nom. Sous Linux, ils sont dnis dans /usr/include/bits/signum.h (vous ne devriez pas inclure ce chier directement dans vos programmes, utilisez plutt <signal.h>). Lorsquun processus reoit un signal, il peut agir de direntes faons, selon laction enregistre pour le signal. Pour chaque signal, il existe une action par dfaut, qui dtermine ce qui arrive au processus si le programme ne spcie pas dautre comportement. Pour la plupart des signaux, le programme peut indiquer un autre comportement soit ignorer le signal, soit appeler un gestionnaire de signal, fonction charge de traiter le signal. Si un gestionnaire de signal est utilis, le programme en cours dexcution est suspendu, le gestionnaire est excut, puis, une fois celui-ci termin, le programme reprend. Le systme Linux envoie des signaux aux processus en rponse des conditions spciques. Par exemple, SIGBUS (erreur de bus), SIGSEGV (erreur de segmentation) et SIGFPE (exception de virgule ottante) peuvent tre envoys un programme essayant deectuer une action non autorise. Laction par dfaut pour ces trois signaux est de terminer le processus et de produire un cher core. Un processus peut galement envoyer des signaux un autre processus. Une utilisation courante de ce mcanisme est de terminer un autre processus en lui envoyant un signal SIGTERM ou SIGKILL4 . Une autre utilisation courante est denvoyer une commande un programme en cours dexcution. Deux signaux dnis par lutilisateur sont rservs cet eet : SIGUSR1 et SIGUSR2. Le signal SIGHUP est galement parfois utilis dans ce but, habituellement pour rveiller un programme inactif ou provoquer une relecture du chier de conguration. La fonction sigaction peut tre utilise pour paramtrer laction eectuer en rponse un signal. Le premier paramtre est le numro du signal. Les deux suivants sont des pointeurs vers des structures sigaction ; la premire contenant laction eectuer pour ce numro de signal, alors que la seconde est renseigne avec laction prcdente. Le champ le plus important, que ce soit dans la premire ou la seconde structure sigaction est sa_handler. Il peut prendre une des trois valeurs suivantes : SIG_DFL, qui correspond laction par dfaut pour le signal ; SIG_IGN , qui indique que le signal doit tre ignor ; Un pointeur vers une fonction de gestion de signal. La fonction doit prendre un paramtre, le numro du signal et tre de type void.
4 Quelle est la dirence ? Le signal SIGTERM demande au processus de se terminer ; le processus peut ignorer la requte en masquant ou ignorant le signal. Le signal SIGKILL tue toujours le processus immdiatement car il est impossible de masquer ou ignorer SIGKILL.

3.3. SIGNAUX

49

Comme les signaux sont asynchrones, le programme principal peut tre dans un tat trs fragile lorsque le signal est trait et donc pendant lexcution du gestionnaire de signal. Cest pourquoi vous devriez viter deectuer des oprations dentres/sorties ou dappeler la plupart des fonctions systme ou de la bibliothque C depuis un gestionnaire de signal. Un gestionnaire de signal doit eectuer le minimum ncessaire au traitement du signal, puis repasser le contrle au programme principal (ou terminer le programme). Dans la plupart des cas, cela consiste simplement enregistrer que le signal est survenu. Le programme principal vrie alors priodiquement si un signal a t reu et ragit en consquence. Il est possible quun gestionnaire de signal soit interrompu par larrive dun autre signal. Bien que cela puisse sembler tre un cas rare, si cela arrive, il peut tre trs dicile de diagnostiquer et rsoudre le problme (cest un exemple de conditions de concurrence critique, trait dans le Chapitre 4, Threads , Section 4.4, Synchronisation et Sections Critiques ). Cest pourquoi vous devez tre trs attentif ce que votre programme fait dans un gestionnaire de signal. Mme laectation dune valeur une variable globale peut tre dangereuse car elle peut en fait tre eectue en deux instructions machine ou plus, et un second signal peut survenir, laissant la variable dans un tat corrompu. Si vous utilisez une variable globale pour indiquer la rception dun signal depuis un gestionnaire de signal, elle doit tre du type spcial sig_atomic_t . Linux garantit que les aectations de valeur des variables de ce type sont eectues en une seule instruction et ne peuvent donc pas tre interrompues. Sous Linux, sig_atomic_t est un int ordinaire ; en fait, les aectations de valeur des types de la taille dun int ou plus petits ou des pointeurs, sont atomiques. Nanmoins, si vous voulez crire un programme portable vers nimporte quel systme UNIX standard, utilisez le type sig_atomic_t. Le squelette de programme du Listing 3.5, par exemple, utilise une fonction de gestion de signal pour compter le nombre de fois o le programme reoit le signal SIGUSR1, un des signaux utilisables par les applications. Listing 3.5 (sigusr1.c) Utiliser un Gestionnaire de Signal
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

# include < signal .h > # include < stdio .h > # include < string .h > # include < sys / types .h > # include < unistd .h > sig_atomic_t sigusr1_count = 0; void handler ( int signal_number ) { ++ sigusr1_count ; } int main () { struct sigaction sa ; memset (& sa , 0 , sizeof ( sa ) ) ; sa . sa_handler = & handler ; sigaction ( SIGUSR1 , & sa , NULL ) ; /* Faire quelque chose de long ici . */ /* ... */ printf ( " SIGUSR1 a t reu % d fois \ n " , sigusr1_count ) ; return 0; }

50

CHAPITRE 3. PROCESSUS

3.4

Fin de Processus

Dans des conditions normales, un processus peut se terminer de deux faons : soit le programme appelle la fonction exit, soit la fonction main du programme se termine. Chaque processus a un code de sortie : un nombre que le processus renvoie son Le code de sortie est largument pass la fonction exit ou la valeur retourne depuis main. Un processus peut galement se terminer de faon anormale, en rponse un signal. Par exemple, les signaux SIGBUS, SIGSEGV et SIGFPE voqus prcdemment provoquent la n du processus. Dautres signaux sont utiliss pour terminer un processus explicitement. Le signal SIGINT est envoy un processus lorsque lutilisateur tente dy mettre n en saisissant Ctrl+C dans son terminal. Le signal SIGTERM est envoy par la commande kill. Laction par dfaut pour ces deux signaux est de mettre n au processus. En appelant la fonction abort, un processus senvoie lui-mme le signal SIGABRT ce qui termine le processus et produit un chier core. Le signal de terminaison le plus puissant est SIGKILL qui met n un processus immdiatement et ne peut pas tre bloqu ou gr par un programme. Chacun de ces signaux peut tre envoy en utilisant la commande kill en passant une option supplmentaire sur la ligne de commande ; par exemple, pour terminer un programme fonctionnant mal en lui envoyant un SIGKILL, invoquez la commande suivante, o pid est un identiant de processus :
% kill -KILL pid}

Pour envoyer un signal depuis un programme, utilisez la fonction kill. Le premier paramtre est lidentiant du processus cible. Le second est le numro du signal ; utilisez SIGTERM pour simuler le comportement par dfaut de la commande kill. Par exemple, si child_pid contient lidentiant du processus ls, vous pouvez utiliser la fonction kill pour terminer un processus ls depuis le pre en lappelant comme ceci : kill (child_pid, SIGTERM) ; Incluez les enttes <sys/types.h> et <signal.h> si vous utilisez la fonction kill. Par convention, le code de sortie est utilis pour indiquer si le programme sest excut correctement. Un code de sortie zro indique une excution correcte, alors quun code dirent de zro indique quune erreur est survenue. Dans ce dernier cas, la valeur renvoye peut donner une indication sur la nature de lerreur. Cest une bonne ide dadopter cette convention dans vos programmes car certains composants du systme GNU/Linux sont bass dessus. Par exemple, les shells supposent lutilisation de cette convention lorsque vous connectez plusieurs programmes avec les oprateurs && (et logique) et || (ou logique). Cest pour cela que vous devriez renvoyer zro explicitement depuis votre fonction main, moins quune erreur ne survienne. Avec la plupart des shells, il est possible dobtenir le code de sortie du dernier programme excut en utilisant la variable spciale $ ? Voici un exemple dans lequel la commande ls est invoque deux fois et son code de sortie est ach aprs chaque invocation. Dans le premier cas, ls se termine correctement et renvoie le code de sortie 0. Dans le second cas, ls rencontre une erreur (car le chier spci sur la ligne de commande nexiste pas) et renvoie donc un code de sortie dirent de 0.
% ls / bin coda etc lib misc nfs proc

3.4. FIN DE PROCESSUS


boot dev home lost+found mnt opt root % echo $? 0 % ls fichierinexistant ls: fichierinexistant: Aucun fichier ou rpertoire de ce type % echo $? 1

51

Notez que mme si le paramtre de la fonction exit est de type int et que la fonction main renvoie un int, Linux ne conserve pas les 32 bits du code de sortie. En fait, vous ne devriez utiliser que des codes de sortie entre 0 et 127. Les codes de sortie au dessus de 128 ont une signication spciale lorsquun processus se termine cause dun signal, son code de sortie est zro plus le numro du signal.

3.4.1

Attendre la Fin dun Processus

Si vous avez saisi et excut lexemple de fork et exec du Listing 3.4, vous pouvez avoir remarqu que la sortie du programme ls apparat souvent aprs la n du programme principal. Cela est d au fait que le processus ls, au sein duquel sexcute ls, est ordonnanc indpendamment du processus pre. Comme Linux est un systme dexploitation multitche, les deux processus semblent sexcuter simultanment et vous ne pas prdire si le programme ls va sexcuter avant ou aprs le processus pre. Dans certaines situations, cependant, il est souhaitable que le processus pre attende la n dun ou plusieurs processus ls. Pour cela, il est possible dutiliser les appels systmes de la famille de wait. Ces fonctions vous permettent dattendre la n dun processus et permettent au processus parent dobtenir des informations sur la faon dont sest termin son ls. Il y a quatre appels systme dirents dans la famille de wait ; vous pouvez choisir de rcuprer peu ou beaucoup dinformations sur le processus qui sest termin et vous pouvez indiquer si vous voulez savoir quel processus ls sest termin.

3.4.2

Les Appels Systme wait

La fonction la plus simple sappelle simplement wait. Elle bloque le processus appelant jusqu ce quun de ses processus ls se termine (ou quune erreur survienne). Elle retourne un code de statut via un pointeur sur un entier, partir duquel vous pouvez extraire des informations sur la faon dont sest termin le processus ls. Par exemple, la macro WEXITSTATUS extrait le code de sortie du processus ls. Vous pouvez utiliser la macro WIFEXITED pour dterminer si un processus sest termin correctement partir de son code de statut (via la fonction exit ou la sortie de main) ou est mort cause dun signal non intercept. Dans ce dernier cas, utilisez la macro WTERMSIG pour extraire le numro du signal ayant caus la mort du processus partir du code de statut. Voici une autre version de la fonction main de lexemple de fork et exec. Cette fois, le processus parent appelle wait pour attendre que le processus ls, dans lequel sexcute la commande ls, se termine.
int main () { int child_status ;

52
/* Liste darguments passer la commande "ls". */
char * arg_list [] = { " ls " , /* argv[0], " -l " , "/", /* La liste NULL };

CHAPITRE 3. PROCESSUS

le nom du programme. */ darguments doit se terminer par NULL. */

/* Cre un nouveau processus fils excutant la commande "ls". Ignore lidentifiant du processus fils renvoy. */
spawn ( " ls " , arg_list ) ;

/* Attend la fin du processus fils. */


wait (& child_status ) ; if ( WIFEXITED ( child_status ) ) printf ( " processus fils termin normalement , le code de sortie % d \ n " , WEXITSTATUS ( child_status ) ) ; else printf ( " processus fils termin anormalement \ n " ) ; return 0; }

Plusieurs appels systme similaires sont disponibles sous Linux, ils sont plus exibles ou apportent plus dinformations sur le processus ls se terminant. La fonction waitpid peut tre utilise pour attendre la n dun processus ls spcique au lieu dattendre nimporte quel processus ls. La fonction wait3 renvoie des statistiques sur lutilisation du processeur par le processus ls se terminant et la fonction wait4 vous permet de spcier des options supplmentaires quant au processus dont on attend la n.

3.4.3

Processus Zombies

Si un processus ls se termine alors que son pre appelle la fonction wait, le processus ls disparat et son statut de terminaison est transmis son pre via lappel wait. Mais que se passe-t-il lorsquun processus se termine et que son pre nappelle pas wait ? Disparat-il tout simplement ? Non, car dans ce cas, les informations concernant la faon dont il sest termin comme le fait quil se soit termin normalement ou son code de sortie seraient perdues. Au lieu de cela, lorsquun processus ls se termine, il devient un processus zombie. Un processus zombie est un processus qui sest termin mais dont les ressources nont pas encore t libres. Il est de la responsabilit du processus pre de librer les ressources occupes par ses ls zombies. La fonction wait le fait, il nest donc pas ncessaire de savoir si votre processus ls est toujours en cours dexcution avant de lattendre. Supposons, par exemple, quun programme cre un processus ls, fasse un certain nombre dautres oprations puis appelle wait. Si le processus ls nest pas encore termin ce moment, le processus parent sera bloqu dans lappel de wait jusqu ce que le processus ls se termine. Si le processus ls se termine avant que le pre nappelle wait, il devient un processus zombie. Lorsque le processus parent appelle wait, le statut de sortie du processus ls est extrait, le processus ls est supprim et lappel de wait se termine immdiatement. Que se passe-t-il si le pre ne libre pas les ressources de ses ls ? Ils restent dans le systme, sous la forme de processus zombies. Le programme du Listing 3.6 cre un processus ls qui se termine immdiatement, puis sinterrompt pendant une minute, sans jamais librer les ressources du processus ls.

3.4. FIN DE PROCESSUS Listing 3.6 (zombie.c) Crer un Processus Zombie


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

53

# include < stdlib .h > # include < sys / types .h > # include < unistd .h > int main () { pid_t child_pid ; /* Cre un processus fils . */ child_pid = fork () ; if ( child_pid > 0) { /* Nous sommes dans le processus parent . Attente d une minute . */ sleep (60) ; } else { /* Nous sommes dans le processus fils . Sortie immdiate . */ exit (0) ; } return 0; }

Compilez ce chier en un excutable appel make-zombie. Excutez-le, et pendant ce temps, listez les processus en cours dexcution sur le systme par le biais de la commande suivante dans une autre fentre :
% ps -e -o pid,ppid,stat,cmd

Elle dresse la liste des identiants de processus, de processus pres, de leur statut et de la ligne de commande du processus. Observez quen plus du processus pre make-zombie, un autre processus make-zombie est ach. Il sagit du processus ls ; notez que lidentiant de son pre est celui du processus make-zombie principal. Le processus ls est marqu comme <defunct> et son code de statut est Z pour zombie. Que se passe-t-il lorsque le programme principal de make-zombie se termine sans appeler wait ? Le processus zombie est-il toujours prsent ? Non essayez de relancer ps et notez que les deux processus make-zombie ont disparu. Lorsquun programme se termine, un processus spcial hrite de ses ls, le programme init, qui sexcute toujours avec un identiant de processus valant 1 (il sagit du premier processus lanc lorsque Linux dmarre). Le processus init libre automatiquement les ressources de tout processus zombie dont il hrite.

3.4.4

Librer les Ressources des Fils de Faon Asynchrone

Si vous utilisez un processus ls uniquement pour appeler exec, il est susant dappeler wait immdiatement dans le processus parent, ce qui le bloquera jusqu ce que le processus ls se termine. Mais souvent, vous voudrez que le processus pre continue de sexcuter alors quun ou plusieurs ls sexcutent en parallle. Comment tre sr de librer les ressources occupes par tous les processus ls qui se sont termins an de ne pas laisser de processus zombie dans la nature, ce qui consomme des ressources ? Une approche envisageable serait que le processus pre appelle priodiquement wait3 et wait4 pour librer les ressources des ls zombies. Appeler wait de cette faon ne fonctionne pas de manire optimale car si aucun ls ne sest termin, lappelant sera bloqu jusqu ce que ce soit le cas. Cependant, wait3 et wait4 prennent un paramtre doption supplmentaire auquel

54

CHAPITRE 3. PROCESSUS

vous pouvez passer le drapeau WNOHANG. Avec ce drapeau, la fonction sexcute en mode non bloquant elle librera les ressources dun processus ls termin sil y en a un ou se terminera sil ny en a pas. La valeur de retour de lappel est lidentiant du processus ls stant termin dans le premier cas, zro dans le second. Une solution plus lgante est dindiquer au processus pre quand un processus ls se termine. Il y a plusieurs faons de le faire en utilisant les mthodes prsentes dans le Chapitre 5, Communication Interprocessus , mais heureusement, Linux le fait pour vous, en utilisant les signaux. Lorsquun processus ls se termine, Linux envoie au processus pre le signal SIGCHLD. Laction par dfaut pour ce signal est de ne rien faire, cest la raison pour laquelle vous pouvez ne jamais lavoir remarqu auparavant. Donc, une faon lgante de librer les ressources des processus ls est de grer SIGCHLD. Bien sr, lorsque vous librez les ressources du processus ls, il est important de stocker son statut de sortie si cette information est ncessaire, car une fois que les ressources du processus ls ont t libres par wait, cette information nest plus disponible. Le Listing 3.7 montre quoi ressemble un programme utilisant un gestionnaire pour SIGCHLD an de librer les ressources de ses processus ls. Listing 3.7 (sigchld.c) Librer les Ressources des Fils via SIGCHLD
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

# include < signal .h > # include < string .h > # include < sys / types .h > # include < sys / wait .h > sig_atomic_t c h i l d _ e x i t _ s t a t u s ; void c l e a n _ u p _ c h i l d _ p r o c e s s ( int signal_number ) { /* Nettoie le ou les processus fils . */ int status ; while ( waitpid ( -1 , & status , WNOHANG ) ) ; /* Stocke la statut de sortie du dernier dans une variable globale . c h i l d _ e x i t _ s t a t u s = status ; } int main () { /* Gre SIGCHLD en appelent c l e a n _ u p _ c h i l d _ p r o c e s s . */ struct sigaction sig ch ld _ac ti on ; memset (& sigchld_action , 0 , sizeof ( sig ch ld _ac ti on ) ) ; si gc hl d_a ct ion . sa_handler = & c l e a n _ u p _ c h i l d _ p r o c e s s ; sigaction ( SIGCHLD , & sigchld_action , NULL ) ; /* Faire diverses choses , entre autres crer un processus fils . */ /* ... */ return 0; }

*/

Notez comment le gestionnaire de signal stocke le statut de sortie du processus ls dans une variable globale, laquelle le programme principal peut accder. Comme la variable reoit une valeur dans un gestionnaire de signal, elle est de type sig_atomic_t.

Chapitre 4

Threads Les threads , comme les processus,sont un mcanisme permettant un pro1

gramme de faire plus dune chose la fois. Comme les processus, les threads semblent sexcuter en parallle ; le noyau Linux les ordonnance de faon asynchrone, interrompant chaque thread de temps en temps pour donner aux autres une chance de sexcuter. Conceptuellement, un thread existe au sein dun processus. Les threads sont une unit dexcution plus ne que les processus. Lorsque vous invoquez un programme, Linux cre un nouveau processus et, dans ce processus, cre un thread, qui excute le processus de faon squentielle. Ce thread peut en crer dautres ; tous ces threads excutent alors le mme programme au sein du mme processus, mais chaque thread peut excuter une partie dirente du programme un instant donn. Nous avons vu comment un programme peut crer un processus ls. Celui-ci excute immdiatement le programme de son pre, la mmoire virtuelle, les descripteurs de chiers, etc. de son pre tant copis. Le processus ls peut modier sa mmoire, fermer les descripteurs de chiers sans que cela aecte son pre, et vice versa. Lorsquun programme cre un nouveau thread, par contre, rien nest copi. Le thread crateur et le thread cr partagent tous deux le mme espace mmoire, les mmes descripteurs de chiers et autres ressources. Si un thread modie la valeur dune variable, par exemple, lautre thread verra la valeur modie. De mme, si un thread ferme un descripteur de chier, les autres threads ne peuvent plus lire ou crire dans ce chier. Comme un processus et tous ses threads ne peuvent excuter quun seul programme la fois, si un thread au sein dun processus appelle une des fonctions exec, tous les autres threads se terminent (le nouveau programme peut, bien sr, crer de nouveaux threads). GNU/Linux implmente lAPI de threading standard POSIX (appele aussi pthreads). Toutes les fonctions et types de donnes relatifs aux threads sont dclars dans le chier dentte < pthread.h>. Les fonctions de pthread ne font pas partie de la bibliothque standard du C. Elles se trouvent dans libpthread, vous devez donc ajouter -lpthread sur la ligne de commande lors de ldition de liens de votre programme.
1

NdT. Appels aussi processus lgers .

55

56

CHAPITRE 4. THREADS

4.1

Cration de Threads

Chaque thread dun processus est caractris par un identiant de thread. Lorsque vous manipulez les identiants de threads dans des programmes C ou C++, veillez utiliser le type pthread_t. Lors de sa cration, chaque thread excute une fonction de thread. Il sagit dune fonction ordinaire contenant le code que doit excuter le thread. Lorsque la fonction se termine, le thread se termine galement. Sous GNU/Linux, les fonctions de thread ne prennent quun seul paramtre de type void* et ont un type de retour void*. Ce paramtre est largument de thread : GNU/Linux passe sa valeur au thread sans y toucher. Votre programme peut utiliser ce paramtre pour passer des donnes un nouveau thread. De mme, il peut utiliser la valeur de retour pour faire en sorte que le thread renvoie des donnes son crateur lorsquil se termine. La fonction pthread_create cre un nouveau thread. Voici les paramtres dont elle a besoin : 1. Un pointeur vers une variable pthread_t, dans laquelle lidentiant du nouveau thread sera stock ; 2. Un pointeur vers un objet dattribut de thread. Cet objet contrle les dtails de linteraction du thread avec le reste du programme. Si vous passez NULL comme argument de thread, le thread est cr avec les attributs par dfaut. Ceux-ci sont traits dans la Section 4.1.5, Attributs de Threads ; 3. Un pointeur vers la fonction de thread. Il sagit dun pointeur de fonction ordinaire de type : void* (*)(void*) ; 4. item Une valeur dargument de thread de type void*. Quoi que vous passiez, largument est simplement transmis la fonction de thread lorsque celui-ci commence sexcuter. Un appel pthread_create se termine immdiatement et le thread original continue excuter linstruction suivant lappel. Pendant ce temps, le nouveau thread dbute lexcution de la fonction de thread. Linux ordonnance les deux threads de manire asynchrone et votre programme ne doit pas faire dhypothse sur lordre dexcution relatif des instructions dans les deux threads. Le programme du Listing 4.1 cre un thread qui ache x de faon continue sur la sortie des erreurs. Aprs lappel de pthread_create, le thread principal ache des o indniment sur la sortie des erreurs. Listing 4.1 (thread-create.c) Crer un thread
1 2 3 4 5 6 7 8 9 10 11 12 13 14

# include < pthread .h > # include < stdio .h > /* Affiche des x sur stderr . Paramtre inutilis . Ne finit jamais . */ void * print_xs ( void * unused ) { while (1) fputc ( x , stderr ) ; return NULL ; } /* Le programme principal . */ int main () { pthread_t thread_id ; /* Cre un nouveau thread . Le nouveau thread excutera la fonction

4.1. CRATION DE THREADS


15 16 17 18 19 20 21

57

print_xs . */ pt hr ea d_c re ate (& thread_id , NULL , & print_xs , NULL ) ; /* Affiche des o en continue sur stderr . */ while (1) fputc ( o , stderr ) ; return 0; }

Compilez ce programme en utilisant la commande suivante :


% cc -o thread-create thread-create.c -lpthread

Essayez de le lancer pour voir ce qui se passe. Notez que le motif form par les x et les o est imprvisible car Linux passe la main alternativement aux deux threads. Dans des circonstances normales, un thread peut se terminer de deux faons. La premire, illustre prcdemment, est de terminer la fonction de thread. La valeur de retour de la fonction de thread est considre comme la valeur de retour du thread. Un thread peut galement se terminer explicitement en appelant pthread_exit. Cette fonction peut tre appele depuis la fonction de thread ou depuis une autre fonction appele directement ou indirectement par la fonction de thread. Largument de pthread_exit est la valeur de retour du thread.

4.1.1

Transmettre des Donnes un Thread

Largument de thread est une mthode pratique pour passer des donnes un thread. Comme son type est void*, cependant, vous ne pouvez pas passer beaucoup de donnes directement en lutilisant. Au lieu de cela, utilisez largument de thread pour passer un pointeur vers une structure ou un tableau de donnes. Une technique couramment utilise est de dnir une structure de donnes pour chaque argument de thread, qui contient les paramtres attendus par la fonction de thread. En utilisant largument de thread, il est facile de rutiliser la mme fonction pour dirents threads. Ils excutent alors tous les mmes traitements mais sur des donnes direntes. Le programme du Listing 4.2 est similaire lexemple prcdent. Celui-ci cre deux nouveaux threads, lun ache des x et lautre des o. Au lieu de les acher indniment, cependant, chaque thread ache un nombre prdtermin de caractres puis se termine en sortant de la fonction de thread. La mme fonction de thread, char_print, est utilise par les deux threads mais chacun est congur diremment en utilisant une struct char_print_parms. Listing 4.2 (thread-create2.c) Crer Deux Threads
1 2 3 4 5 6 7 8 9 10 11 12 13

# include < pthread .h > # include < stdio .h > /* Paramtres de la fonction print . */ struct c h a r _ p r i n t _ p a r m s { /* Caractre afficher . */ char character ; /* Nombre de fois o il doit tre affich . */ int count ; }; /* Affiche un certain nombre de caractres sur stderr , selon le contenu de PARAMETERS , qui est un pointeur vers une struct c h a r _ p r i n t _ p a r m s . */ void * char_print ( void * parameters )

58
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38

CHAPITRE 4. THREADS
{ /* Effectue un transtypage du pointeur void vers le bon type . */ struct c h a r _ p r i n t _ p a r m s * p = ( struct c h a r _ p r i n t _ p a r m s *) parameters ; int i ; for ( i = 0; i < p - > count ; ++ i ) fputc (p - > character , stderr ) ; return NULL ; } /* Programme principal . */ int main () { pthread_t thread1_id ; pthread_t thread2_id ; struct c h a r _ p r i n t _ p a r m s thread1_args ; struct c h a r _ p r i n t _ p a r m s thread2_args ; /* Cre un nouveau thread affichant 30 000 x . */ thread1_args . character = x ; thread1_args . count = 30000; pt hr ea d_c re ate (& thread1_id , NULL , & char_print , & thread1_args ) ; /* Cre un nouveau thread affichant 20 000 o . */ thread2_args . character = ? o ?; thread2_args . count = 20000; pt hr ea d_c re ate (& thread2_id , NULL , & char_print , & thread2_args ) ; return 0; }

Mais attendez ! Le programme du Listing 4.2 est srieusement bogu. Le thread principal (qui excute la fonction main) cre les structures passes en paramtre aux threads (thread1_args et thread2_args) comme des variables locales puis transmet des pointeurs sur ces structures aux threads quil cre. Quest-ce qui empche Linux dordonnancer les trois threads de faon ce que main termine son excution avant que lun des deux autres threads sexcutent ? Rien ! Mais si cela se produit, la mmoire contenant les structures de paramtres des threads sera libre alors que les deux autres threads tentent dy accder.

4.1.2

Synchroniser des Threads

Une solution possible est de forcer main attendre la n des deux autres threads. Ce dont nous avons besoin est une fonction similaire wait qui attende la n dun thread au lieu de celle dun processus. Cette fonction est pthread_join, qui prend deux arguments : lidentiant du thread attendre et un pointeur vers une variable void* qui recevra la valeur de retour du thread stant termin. Si la valeur de retour du thread ne vous est pas utile, passez NULL comme second argument. Le Listing 4.3 montre la fonction main sans le bogue du Listing 4.2. Dans cette version, main ne se termine pas jusqu ce que les deux threads achant des o et des x se soient eux-mme termins, an quils nutilisent plus les structures contenant les arguments. Listing 4.3 (thread-create2a.c) Fonction main de thread-create2.c corrige
1 2 3 4 5 6

int main () { pthread_t thread1_id ; pthread_t thread2_id ; struct c h a r _ p r i n t _ p a r m s thread1_args ; struct c h a r _ p r i n t _ p a r m s thread2_args ;

4.1. CRATION DE THREADS


7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

59

/* Cre un nouveau thread affichant 30 000 x . */ thread1_args . character = ? x ?; thread1_args . count = 30000; pt hr ea d_c re ate (& thread1_id , NULL , & char_print , & thread1_args ) ; /* Cre un nouveau thread affichant 20 000 o . */ thread2_args . character = ? o ?; thread2_args . count = 20000; pt hr ea d_c re ate (& thread2_id , NULL , & char_print , & thread2_args ) ; /* S assure que le premier thread est termin . */ pthread_join ( thread1_id , NULL ) ; /* S assure que le second thread est termin . */ pthread_join ( thread2_id , NULL ) ; /* Nous pouvons maintenant quitter en toute scurit . */ return 0; }

Morale de lhistoire : assurez vous que toute donne que vous passez un thread par rfrence nest pas libre, mme par un thread dirent, moins que vous ne soyez sr que le thread a ni de lutiliser. Cela sapplique aux variables locales, qui sont libres lorsquelles sont hors de porte, ainsi quaux variables alloues dans le tas, que vous librez en appelant free (ou en utilisant delete en C++).

4.1.3

Valeurs de Retour des Threads

Si le second argument que vous passez pthread_join nest pas NULL, la valeur de retour du thread sera stocke lemplacement point par cet argument. La valeur de retour du thread, comme largument de thread, est de type void*. Si vous voulez renvoyer un simple int ou un autre petit nombre, vous pouvez le faire facilement est convertissant la valeur en void* puis en le reconvertissant vers le type adquat aprs lappel de pthread_join2 . Le programme du Listing 4.4 calcule le nime nombre premier dans un thread distinct. Ce thread renvoie le numro du nombre premier demand via sa valeur de retour de thread. Le thread principal, pendant ce temps, est libre dexcuter dautres traitements. Notez que lalgorithme de divisions successives utilis dans compute_prime est quelque peu inecace ; consultez un livre sur les algorithmes numriques si vous avez besoin de calculer beaucoup de nombres premiers dans vos programmes. Listing 4.4 (primes.c) Calcule des Nombres Premiers dans un Thread
1 2 3 4 5 6 7 8 9 10 11 12 13 2

# include < pthread .h > # include < stdio .h > /* Calcules des nombres premiers successifs ( trs inefficace ) . Renvoie le Nime nombre premier o N est la valeur pointe par * ARG . */ void * compute_prime ( void * arg ) { int candidate = 2; int n = *(( int *) arg ) ; while (1) { int factor ; int is_prime = 1; /* Teste si le nombre est premier par divisions successives . */ for ( factor = 2; factor < candidate ; ++ factor )

Notez que cette faon de faire nest pas portable et quil est de votre responsabilit de vous assurer que la valeur peut tre convertie en toute scurit vers et depuis void* sans perte de bit.

60
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41

CHAPITRE 4. THREADS
if ( candidate % factor == 0) { is_prime = 0; break ; } /* Est - ce le nombre premier que nous cherchons ? */ if ( is_prime ) { if ( - - n == 0) /* Renvoie le nombre premier dsir via la valeur de retour du thread . */ return ( void *) candidate ; } ++ candidate ; } return NULL ; } int main () { pthread_t thread ; int which_prime = 5000; int prime ; /* Dmarre le thread de calcul jusqu au 5 000 me nombre premier . */ pt hr ea d_c re ate (& thread , NULL , & compute_prime , & which_prime ) ; /* Faire autre chose ici ... */ /* Attend la fin du thread de calcul et rcupre le rsultat . */ pthread_join ( thread , ( void *) & prime ) ; /* Affiche le nombre premier calcul . */ printf ( " Le % dme nombre premier est % d .\ n " , which_prime , prime ) ; return 0; }

4.1.4

Plus dInformations sur les Identiants de Thread

De temps autre, il peut tre utile pour une portion de code de savoir quel thread lexcute. La fonction pthread_self renvoie lidentiant du thread depuis lequel elle a t appele. Cet identiant de thread peut tre compar un autre en utilisant la fonction pthread_equal. Ces fonctions peuvent tre utiles pour dterminer si un identiant de thread particulier correspond au thread courant. Par exemple, un thread ne doit jamais appeler pthread_join pour sattendre lui-mme (dans ce cas, pthread_join renverrait le code derreur EDEADLK). Pour le vrier avant lappel, utilisez ce type de code :
if (! pthread_equal ( pthread_self () , other_thread ) ) pthread_join ( other_thread , NULL ) ;

4.1.5

Attributs de Thread

Les attributs de thread proposent un mcanisme pour contrler nement le comportement de threads individuels. Souvenez-vous que pthread_create accepte un argument qui est un pointeur vers un objet dattributs de thread. Si vous passez un pointeur nul, les attributs par dfaut sont utiliss pour congurer le nouveau thread. Cependant, vous pouvez crer et personnaliser un objet dattributs de threads pour spcier vos propres valeurs. Pour indiquer des attributs de thread personnaliss, vous devez suivre ces tapes : 1. Crez un objet pthread_attr_t. La faon la plus simple de le faire est de dclarer une variable automatique de ce type.

4.1. CRATION DE THREADS

61

2. Appelez pthread_attr_init en lui passant un pointeur vers cet objet. Cela initialise les attributs leur valeur par dfaut. 3. Modiez lobjet dattributs pour quils contiennent les valeurs dsires. 4. Passez un pointeur vers lobjet cr lors de lappel pthread_create. 5. Appelez pthread_attr_destroy pour librer lobjet dattributs. La variable pthread_attr_t nest pas libre en elle-mme ; elle peut tre rinitialise avec pthread_attr_init. Un mme objet dattributs de thread peut tre utilis pour dmarrer plusieurs threads. Il nest pas ncessaire de conserver lobjet dattributs de thread une fois quils ont t crs. Pour la plupart des applications GNU/Linux, un seul attribut de thread a gnralement de lintrt (les autres attributs disponibles sont principalement destins la programmation en temps rel). Cet attribut est ltat de dtachement (detach state) du thread. Un thread peut tre un thread joignable (par dfaut) ou un thread dtach. Les ressources dun thread joignable, comme pour un processus, ne sont pas automatiquement libres par GNU/Linux lorsquil se termine. Au lieu de cela, ltat de sortie du thread reste dans le systme (un peu comme pour un processus zombie) jusqu ce quun autre thread appelle pthread_join pour rcuprer cette valeur de retour. Alors seulement, les ressources sont libres. Les ressources dun thread dtach, au contraire, sont automatiquement libres lorsquil se termine. Cela impliquer quun autre thread ne peut attendre quil se termine avec pthread_join ou obtenir sa valeur de retour. La fonction pthread_attr_setdetachstate permet de dnir ltat de dtachement dans un objet dattributs de thread. Le premier argument est un pointeur vers lobjet dattributs de thread et le second est ltat dsir. Comme ltat joignable est la valeur par dfaut, il nest ncessaire deectuer cet appel que pour crer des threads dtachs ; passez PTHREAD_CREATE_DETACHED comme second argument. Le code du Listing 4.5 cre un thread dtach en activant lattribut de dtachement de thread : Listing 4.5 (detached.c) Programme Squelette Crant un Thread Dtach
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

# include < pthread .h > void * th r e a d _ fu n c t i o n ( void * thread_arg ) { /* Effectuer les traitements ici ... */ } int main () { pt hr ea d_a tt r_t attr ; pthread_t thread ; p t h r e a d _ a t t r _ i n i t (& attr ) ; p t h r e a d _ a t t r _ s e t d e t a c h s t a t e (& attr , P T H R E A D _ C R E A T E _ D E T A C H E D ) ; pt hr ea d_c re ate (& thread , & attr , & thread_function , NULL ) ; p t h r e a d _ a t t r _ d e s t r o y (& attr ) ; /* Effectuer les traitements ici ... */ /* Pas besoin d attendre le deuxime thread . */ return 0; }

Mme si un thread est cr dans un tat joignable, il peut tre transform plus tard en un thread dtach. Pour cela, appelez pthread_detach. Une fois quun thread est dtach, il ne peut plus tre rendu joignable.

62

CHAPITRE 4. THREADS

4.2

Annulation de Thread

Dans des circonstances normales, un thread se termine soit en arrivant la n de la fonction de thread, soit en appelant la fonction pthread_exit. Cependant, il est possible pour un thread de demander la n dun autre thread. Cela sappelle annuler un thread. Pour annuler un thread, appelez pthread_cancel, en lui passant lidentiant du thread annuler. Un thread annul peut tre joint ultrieurement ; en fait, vous devriez joindre un thread annul pour librer ses ressources, moins que le thread ne soit dtach (consultez la Section 4.1.5, Attributs de Thread ). La valeur de retour dun thread annul est la valeur spciale PTHREAD_CANCELED. Souvent un thread peut tre en train dexcuter un code qui doit tre excut totalement ou pas du tout. Par exemple, il peut allouer des ressources, les utiliser, puis les librer. Si le thread est annul au milieu de ce code, il peut ne pas avoir lopportunit de librer les ressources et elles seront perdues. Pour viter ce cas de gure, un thread peut contrler si et quand il peut tre annul. Un thread peut se comporter de trois faons face lannulation. Il peut tre annulable de faon asynchrone. Cest--dire quil peut tre annul nimporte quel moment de son excution. Il peut tre annulable de faon synchrone. Le thread peut tre annul, mais pas nimporte quand pendant son excution. Au lieu de cela, les requtes dannulation sont mises en le dattente et le thread nest annul que lorsquil atteint un certain point de son excution. Il peut tre impossible annuler. Les tentatives dannulation sont ignores silencieusement. Lors de sa cration, un thread est annulable de faon synchrone.

4.2.1

Threads Synchrones et Asynchrones

Un thread annulable de faon asynchrone peut tre annul nimporte quel point de son excution. Un thread annulable de faon synchrone, par contre, ne peut tre annul qu des endroits prcis de son excution. Ces endroits sont appels points dannulation. Le thread mettra les requtes dannulation en attente jusqu ce quil atteigne le point dannulation suivant. Pour rendre un thread annulable de faon asynchrone, utilisez pthread_setcanceltype. Cela naecte que le thread eectuant lappel. Si vous souhaitez rendre le thread annulable de faon asynchrone le premier argument doit tre PTHREAD_CANCEL_ASYNCHRONOUS, si, vous prfrez revenir immdiatement en tat dannulation synchrone passez PTHREAD_CANCEL_DEFERRED. Le second argument, sil nest pas NULL, est un pointeur vers une variable qui recevra le type dannulation prcdemment supporte par le thread. Cet appel, par exemple, rend le thread appelant annulable de faon asynchrone.
p t h r e a d _ s e t c a n c e l t y p e ( PTH READ_ CANCEL _ASYN CHRONO US , NULL ) ;

A quoi ressemble un point dannulation et o doit-il tre plac ? La faon la plus directe de crer un point dannulation est dappeler pthread_testcancel. Cette fonction ne fait rien part traiter une annulation en attente dans un thread annulable de faon synchrone. Vous devriez appeler pthread_testcancel priodiquement durant des calculs longs au sein dune fonction de

4.2. ANNULATION DE THREAD

63

thread, aux endroits o le thread peut tre annul sans perte de ressources ou autres eets de bord. Un certain nombre dautres fonctions sont galement des points dannulation implicites. Elles sont listes sur la page de manuel de pthread_cancel. Notez que dautres fonctions peuvent y faire appel en interne et donc constituer des points dannulation implicites.

4.2.2

Sections Critiques Non-Annulables

Un thread peut dsactiver son annulation avec la fonction pthread_setcancelstate. Comme pthread_setcanceltype, elle aecte le thread appelant. La dsactivation de lannulation se fait en passant PTHREAD_CANCEL_DISABLE comme premier argument tandis que PTHREAD_CANCEL_ENABLE permet de la ractiver. Le second argument, sil nest pas NULL, pointe vers une variable qui recevra ltat prcdent de lannulation. Cet appel, par exemple, dsactive lannulation du thread appelant.
p t h r e a d _ s e t c a n c e l s t a t e ( PTHREAD_CANCEL_DISABLE , NULL ) ;

Lutilisation de pthread_setcancelstate vous permet dimplmenter des sections critiques. Une section critique est une squence de code qui doit tre excute entirement ou pas du tout ; en dautres termes, si un thread commence lexcution dune section critique, il doit atteindre la n de la section critique sans tre annul. Par exemple, supposons que vous criviez une routine pour un programme bancaire qui transfre de largent dun compte un autre. Pour cela, vous devez ajouter la valeur au solde dun compte et la soustraire du solde de lautre. Si le thread excutant votre routine est annul entre les deux oprations, le programme aura augment de faon incorrecte largent dtenu par la banque en ne terminant pas la transaction. Pour viter cela, placez les deux oprations au sein dune section critique. Vous pourriez implmenter le transfert avec une fonction comme process_transaction prsente dans le Listing 4.6. Cette fonction dsactive lannulation de thread pour dmarrer une section critique avant toute modication de solde. Listing 4.6 (critical-section.c) Transaction avec Section Critique
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

# include < pthread .h > # include < stdio .h > # include < string .h > /* Tableau des soldes de comptes , index par numro de compte . */ float * a c c o u n t _ b a l a n c e s ; /* Transfre DOLLARS depuis le compte FROM_ACCT vers TO_ACCT . Renvoie 0 si la transaction s est bien droule ou 1 si le solde de FROM_ACCT est trop faible . */ int p r o c e s s _ t r a n s a c t i o n ( int from_acct , int to_acct , float dollars ) { int o l d _ c a n c e l _ s t a t e ; /* Vrifie le solde de FROM_ACCT . */ if ( a c c o u n t _ b a l a n c e s [ from_acct ] < dollars ) return 1; /* Dbut de la section critique . */ p t h r e a d _ s e t c a n c e l s t a t e ( PTHREAD_CANCEL_DISABLE , & o l d _ c a n c e l _ s t a t e ) ; /* Transfre l argent . */ a c c o u n t _ b a l a n c e s [ to_acct ] += dollars ; a c c o u n t _ b a l a n c e s [ from_acct ] -= dollars ;

64
20 21 22 23

CHAPITRE 4. THREADS
/* Fin de la section critique . */ p t h r e a d _ s e t c a n c e l s t a t e ( old_cancel_state , NULL ) ; return 0; }

Notez quil est important de restaurer lancien tat de lannulation la n de la section critique plutt que de le positionner systmatiquement PTHREAD_CANCEL_ENABLE. Cela vous permet dappeler la fonction process_transaction en toute scurit depuis une autre section critique dans ce cas, votre fonction remettrait ltat de lannulation la mme valeur que lorsquelle a dbut.

4.2.3

Quand Utiliser lAnnulation de Thread ?

En gnral, ce nest pas une bonne ide dutiliser lannulation de thread pour en terminer lexcution, except dans des circonstances anormales. En temps normal, il est mieux dindiquer au thread quil doit se terminer, puis dattendre quil se termine de lui-mme. Nous reparlerons des techniques de communication avec les threads plus loin dans ce chapitre et dans le Chapitre 5, Communication Interprocessus .

4.3

Donnes Propres un Thread

Contrairement aux processus, tous les threads dun mme programme partagent le mme espace dadressage. Cela signie que si un thread modie une valeur en mmoire (par exemple, une variable globale), cette modication est visible dans tous les autres threads. Cela permet plusieurs threads de manipuler les mmes donnes sans utiliser les mcanismes de communication interprocessus (dcrits au Chapitre 5). Nanmoins, chaque thread a sa propre pile dappel. Cela permet chacun dexcuter un code dirent et dutiliser des sous-routines la faon classique. Comme dans un programme monothread, chaque invocation de sous-routine dans chaque thread a son propre jeu de variables locales, qui sont stockes dans la pile de ce thread. Cependant, il peut parfois tre souhaitable de dupliquer certaines variables an que chaque thread dispose de sa propre copie. GNU/Linux supporte cette fonctionnalit avec une zone de donnes propres au thread. Les variables stockes dans cette zone sont dupliques pour chaque thread et chacun peut modier sa copie sans aecter les autres. Comme tous les threads partagent le mme espace mmoire, il nest pas possible daccder aux donnes propres un thread via des rfrences classiques. GNU/Linux fournit des fonction ddies pour placer des valeurs dans la zone de donnes propres au thread et les rcuprer. Vous pouvez crer autant dobjets de donnes propres au thread que vous le dsirez, chacune tant de type void*. Chaque objet est rfrenc par une cl. Pour crer une nouvelle cl, et donc un nouvel objet de donnes pour chaque thread, utilisez pthread_key_create. Le premier argument est un pointeur vers une variable pthread_key_t. Cette cl peut tre utilise par chaque thread pour accder sa propre copie de lobjet de donnes correspondant. Le second argument de pthread_key_create est une fonction de libration des ressources. Si vous passez un pointeur sur une fonction, GNU/Linux appelle celle-ci automatiquement lorsque chaque thread se termine en lui passant la valeur de la donne propre au thread correspondant la cl. Ce mcanisme

4.3. DONNES PROPRES UN THREAD

65

est trs pratique car la fonction de libration des ressources est appele mme si le thread est annul un moment quelconque de son excution. Si la valeur propre au thread est nulle, la fonction de libration de ressources nest pas appele. Si vous navez pas besoin de fonction de libration de ressources, vous pouvez passer NULL au lieu dun pointeur de fonction. Une fois que vous avez cr une cl, chaque thread peut positionner la valeur correspondant cette cl en appelant pthread_setspecific. Le premier argument est la cl, et le second est la valeur propre au thread stocker sous forme de void*. Pour obtenir un objet de donnes propre au thread, appelez pthread_getspecific, en lui passant la cl comme argument. Supposons, par exemple, que votre application partage une tche entre plusieurs threads. des ns daudit, chaque thread doit avoir un chier journal spar dans lequel des messages davancement pour ce thread sont enregistrs. La zone de donnes propres au thread est un endroit pratique pour stocker le pointeur sur le chier journal dans chaque thread. Le Listing 4.7 prsente une implmentation dun tel mcanisme. La fonction main de cet exemple cre une cl pour stocker le pointeur vers le chier propre chaque thread puis la stocke au sein de la variable globale thread_lock_key. Comme il sagit dune variable globale, elle est partage par tous les threads. Lorsquun thread commence excuter sa fonction, il ouvre un chier journal et stocke le pointeur de chier sous cette cl. Plus tard, nimporte lequel de ces thread peut appeler write_to_thread_log pour crire un message dans le chier journal propre au thread. Cette fonction obtient le pointeur de chier du chier log du thread depuis les donnes propres au thread et y crit le message. Listing 4.7 (tsd.c) Fichiers Logs par Thread avec Donnes Propres au Thread
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

# include < malloc .h > # include < pthread .h > # include < stdio .h > /* Cl utilise pour associer un pointeur de fichier chaque thread . */ static pthread_key_t thr ea d_ log _k ey ; /* crit MESSAGE vers le fichier journal du thread courant . */ void w r i t e _ t o _ t h r e a d _ l o g ( const char * message ) { FILE * thread_log = ( FILE *) p t h r e a d _ g e t s p e c i f i c ( t hr ea d_l og _k ey ) ; fprintf ( thread_log , " % s \ n " , message ) ; } /* Ferme le pointeur vers le fichier journal THREAD_LOG . */ void c l o s e _ t h r e a d _ l o g ( void * thread_log ) { fclose (( FILE *) thread_log ) ; } void * th r e a d _ fu n c t i o n ( void * args ) { char t h r e a d _ l o g _ f i l e n a m e [20]; FILE * thread_log ; /* Gnre le nom de fichier pour le fichier journal de ce thread . */ sprintf ( thread_log_filename , " thread % d . log " , ( int ) pthread_self () ) ; /* Ouvre le fichier journal . */ thread_log = fopen ( thread_log_filename , " w " ) ; /* Stocke le pointeur de fichier dans les donnes propres au thread sous la cl th re ad _lo g_ key . */ p t h r e a d _ s e t s p e c i f i c ( thread_log_key , thread_log ) ; w r i t e _ t o _ t h r e a d _ l o g ( " Dmarrage du Thread . " ) ; /* Placer les traitements ici ... */ return NULL ; } int main ()

66
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46

CHAPITRE 4. THREADS
{ int i ; pthread_t threads [5]; /* Cre une cl pour associer les pointeurs de fichier journal dans les donnes propres au thread . Utilise c l o s e _ t h r e a d _ l o g pour librer les pointeurs de fichiers . */ p t h r e a d _ k e y _ c r e a t e (& thread_log_key , c l o s e _ t h r e a d _ l o g ) ; /* Cre des threads pour effectuer les traitements . */ for ( i = 0; i < 5; ++ i ) pt hr ea d_c re ate (&( threads [ i ]) , NULL , thread_function , NULL ) ; /* Attend la fin de tous les threads . */ for ( i = 0; i < 5; ++ i ) pthread_join ( threads [ i ] , NULL ) ; return 0; }

Remarquez que thread_function na pas besoin de fermer le chier journal. En eet, lorsque la cl du chier a t cre, close_thread_log a t spcie comme fonction de libration de ressources pour cette cl. Lorsquun thread se termine, GNU/Linux appelle cette fonction, en lui passant la valeur propre au thread correspondant la cl du chier journal. Cette fonction prend soin de fermer le chier journal.
Gestionnaires de Libration de Ressources

Les fonctions de libration de ressources associes aux cls des donnes spciques au thread peuvent tre trs pratiques pour sassurer que les ressources ne sont pas perdues lorsquun thread se termine ou est annul. Quelquefois, cependant, il est utile de pouvoir spcier des fonctions de libration de ressources sans avoir crer un nouvel objet de donnes propre au thread qui sera dupliqu pour chaque thread. Pour ce faire, GNU/Linux propose des gestionnaires de libration de ressources. Un gestionnaire de libration de ressources est tout simplement une fonction qui doit tre appele lorsquun thread se termine. Le gestionnaire prend un seul paramtre void* et la valeur de largument est spcie lorsque le gestionnaire est enregistr cela facilite lutilisation du mme gestionnaire pour librer plusieurs instances de ressources. Un gestionnaire de libration de ressources est une mesure temporaire, utilis pour librer une ressource uniquement si le thread se termine ou est annul au lieu de terminer lexcution dune certaine portion de code. Dans des circonstances normales, lorsquun thread ne se termine pas et nest pas annul, la ressource doit tre libre explicitement et le gestionnaire de ressources supprim. Pour enregistrer un gestionnaire de libration de ressources, appelez pthread_cleanup_push, en lui passant un pointeur vers la fonction de libration de ressources et la valeur de son argument void*. Lenregistrement du gestionnaire doit tre quilibr par un appel pthread_cleanup_pop qui le supprime. Dans une optique de simplication, pthread_cleanup_pop prend un indicateur int en argument ; sil est dirent de zro, laction de libration de ressources est excut lors de la suppression. Lextrait de programme du Listing 4.8 montre comment vous devriez utiliser un gestionnaire de libration de ressources pour vous assurer quun tampon allou dynamiquement est libr si le thread se termine.

4.3. DONNES PROPRES UN THREAD

67

Listing 4.8 (cleanup.c) Extrait de Programme Montrant lUtilisation dun Gestionnaire de Libration de Ressources
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

# include < malloc .h > # include < pthread .h > /* Alloue un tampon temporaire . */ void * al l o c a t e_ b u f f e r ( size_t size ) { return malloc ( size ) ; } /* Libre un tampon temporaire . */ void d e a l l o c a t e _ b u f f e r ( void * buffer ) { free ( buffer ) ; } void do_some_work () { /* Alloue un tampon temporaire . */ void * temp_buffer = al l o c a t e _b u f f e r (1024) ; /* Enregistre un gestionnaire de libration de ressources pour ce tampon pour le librer si le thread se termine ou est annul . */ p t h r e a d _ c l e a n u p _ p u s h ( deallocate_buffer , temp_buffer ) ; /* Placer ici des traitements qui pourraient appeler pthread_exit ou tre annuls ... */ /* Supprime le gestionnaire de libration de ressources . Comme nous passons une valeur diffrente de zro , la libration est effectue par l appel de d e a l l o c a t e _ b u f f e r . */ p t h r e a d _ c l e a n u p _ p o p (1) ; }

Comme largument de pthread_cleanup_pop est dirent de zro dans ce cas, la fonction de libration de ressources deallocate_buffer est appele automatiquement et il nest pas ncessaire de le faire explicitement. Dans ce cas simple, nous aurions pu utiliser la fonction de la bibliothque standard free comme fonction de libration de ressources au lieu de deallocate_buffer.

4.3.1

Libration de Ressources de Thread en C++

Les programmeurs C++ sont habitus avoir des fonctions de libration de ressources gratuitement en plaant les actions adquates au sein de destructeurs. Lorsque les objets sont hors de porte, soit cause de la n dun bloc, soit parce quune exception est lance, le C++ sassure que les destructeurs soient appels pour les variables automatiques qui en ont un. Cela ore un mcanisme pratique pour sassurer que le code de libration de ressources est appel quelle que soit la faon dont le bloc se termine. Si un thread appelle pthread_exit, cependant, le C++ ne garantit pas que les destructeurs soient appels pour chacune des variables automatiques situes sur la pile du thread. Une faon intelligente dobtenir le mme comportement est dinvoquer pthread_exit au niveau de la fonction de thread en lanant une exception spciale. Le programme du Listing 4.9 dmontre cela. Avec cette technique, une fonction indique son envie de quitter le thread en lanant une ThreadExitException au lieu dappeler pthread_exit directement. Comme lexception est intercepte au niveau de la fonction de thread, toutes les variables locales situes sur la pile du thread seront dtruites proprement lorsque lexception est remonte.

68

CHAPITRE 4. THREADS Listing 4.9 (cxx-exit.cpp) Implmenter une Sortie de Thread en C++
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37

# include < pthread .h > class T h r e a d E x i t E x c e p t i o n { public : /* Cre une exception signalant la fin d un thread avec RETURN_VALUE . */ T h r e a d E x i t E x c e p t i o n ( void * return_value ) : t h r e a d _ r e t u r n _ v a l u e _ ( return_value ) { } /* Quitte le thread en utilisant la valeur de retour fournie dans le constructeur . */ void * DoThreadExit () { pthread_exit ( t h r e a d _ r e t u r n _ v a l u e _ ) ; } private : /* Valeur de retour utilise lors de la sortie du thread . */ void * t h r e a d _ r e t u r n _ v a l u e _ ; }; void do_some_work () { while (1) { /* Placer le code utile ici ... */ if ( s h o u l d _ e x i t _ t h r e a d _ i m m e d i a t e l y () ) throw T h r e a d E x i t E x c e p t i o n ( /* thread ? s return value = */ NULL ) ; } } void * th r e a d _ fu n c t i o n ( void *) { try { do_some_work () ; } catch ( T h r e a d E x i t E x c e p t i o n ex ) { /* Une fonction a signal que l on devait quitter le thread . */ ex . DoThreadExit () ; } return NULL ; }

4.4

Synchronisation et Sections Critiques

Programmer en utilisant les threads demande beaucoup de rigueur car la plupart des programmes utilisant les threads sont des programmes concurrents. En particulier, il ny a aucun moyen de connatre la faon dont seront ordonnancs les threads les uns par rapport aux autres. Un thread peut sexcuter pendant longtemps ou le systme peut basculer dun thread un autre trs rapidement. Sur un systme avec plusieurs processeurs, le systme peut mme ordonnancer plusieurs threads an quils sexcutent physiquement en mme temps. Dboguer un programme qui utilise les threads est compliqu car vous ne pouvez pas toujours reproduire facilement le comportement ayant caus le problme. Vous pouvez lancer le programme une premire fois sans quaucun problme ne survienne et la fois suivante, il plante. Il ny a aucun moyen de faire en sorte que le systme ordonnance les threads de la mme faon dune fois sur lautre.

4.4. SYNCHRONISATION ET SECTIONS CRITIQUES

69

La cause la plus vicieuse de plantage des programmes utilisant les threads est lorsque ceux-ci tentent daccder aux mmes donnes. Comme nous lavons dit prcdemment, il sagit dun des aspects les plus puissants des threads mais cela peut galement tre dangereux. Si un thread na pas termin la mise jour dune structure de donnes lorsquun autre thread y accde, il sen suit une situation imprvisible. Souvent, les programmes bogus qui utilisent les threads contiennent une portion de code qui ne fonctionne que si un thread a la main plus souvent ou plus tt quun autre. Ces bogues sont appels conditions de concurrence critique ; les threads sont en concurrence pour modier la mme structure de donnes.

4.4.1

Conditions de Concurrence Critique

Supposons que votre programme ait une srie de tches en attente traites par plusieurs threads concurrents. La le dattente des tches est reprsente par une liste chane dobjets struct job. Aprs que chaque thread a ni une opration, il vrie la le pour voir si une nouvelle tche est disponible. Si job_queue nest pas NULL, le thread supprime la tte de la liste chane et fait pointer job_queue vers la prochaine tche de la liste. La fonction de thread qui traite les tches de la liste pourrait ressembler au Listing 4.10. Listing 4.10 (job-queue1.c) Fonction de Thread Traitant une File de Tches
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

# include < malloc .h > struct job { /* Champ de chanage . */ struct job * next ; /* Autres champs dcrivant la tche ... */ }; /* Liste chane de tches en attente . */ struct job * job_queue ; /* Traite les tches jusqu ce que la file soit vide . */ void * th r e a d _ fu n c t i o n ( void * arg ) { while ( job_queue != NULL ) { /* Rcupre la tche suivante . */ struct job * next_job = job_queue ; /* Supprime cette tche de la liste . */ job_queue = job_queue - > next ; /* Traite la tche . */ process_job ( next_job ) ; /* Libration des ressources . */ free ( next_job ) ; } return NULL ; }

Supposons maintenant que deux threads nissent une tche peu prs au mme moment, mais quil ne reste quune seule tche dans la liste. Le premier thread regarde si job_queue est nul ; comme ce nest pas le cas, le thread entre dans la boucle et stocke le pointeur vers la tche dans next_job. ce moment, Linux interrompt le thread et passe la main au second. Le second thread vrie galement job_queue, comme elle nest pas NULL, aecte la mme valeur que le premier next_job. Par une malheureuse concidence, nous avons deux threads excutant la mme tche.

70

CHAPITRE 4. THREADS

Pire, un des thread va positionner job_queue NULL pour supprimer lobjet de la liste. Lorsque lautre valuera job_queue->next, il en rsultera une erreur de segmentation. Cest un exemple de condition de concurrence critique. En d heureuses circonstances, cet ordonnancement particulier des threads ne surviendra jamais et la condition de concurrence critique ne sera jamais rvle. Dans des circonstances direntes, par exemple dans le cas dun systme trs charg (ou sur le nouveau serveur multiprocesseur dun client important !) le bogue peut apparatre. Pour liminer les conditions de concurrence critique, vous devez trouver un moyen de rendre les oprations atomiques. Une opration atomique est indivisible et impossible interrompre ; une fois quelle a dbut, elle ne sera pas suspendue ou interrompue avant dtre termine, et aucune autre opration ne sera accomplie pendant ce temps. Dans notre exemple, nous voulons vrier la valeur de job_queue et si elle nest pas NULL, supprimer la premire tche, tout cela en une seule opration atomique.

4.4.2

Mutexes

La solution pour notre problme de concurrence critique au niveau de la le de tches est de nautoriser quun seul thread accder la le. Une fois que le thread commence observer la le dattente, aucun autre thread ne doit pouvoir y accder jusqu ce que le premier ait dcid sil doit traiter une tche, et si cest le cas, avant quil nait supprim la tche de la liste. Limplmentation de ce mcanisme requiert laide du systme dexploitation. GNU/Linux propose des mutexes, raccourcis de MUTual EXclusion locks (verrous dexclusion mutuelle). Un mutex est un verrouillage spcial quun seul thread peut utiliser la fois. Si un thread verrouille un mutex puis quun second tente de verrouiller le mme mutex, ce dernier est bloqu ou suspendu. Le second thread est dbloqu uniquement lorsque le premier dverrouille le mutex ce qui permet la reprise de lexcution. GNU/Linux assure quil ny aura pas de condition de concurrence critique entre deux threads tentant de verrouiller un mutex ; seul un thread obtiendra le verrouillage et tous les autres seront bloqus. On peut faire lanalogie entre un mutex et une porte de toilettes. Lorsque quelquun entre dans les toilettes et verrouille la porte, si une personne veut entrer dans les toilettes alors quils sont occups, elle sera oblige dattendre dehors jusqu ce que loccupant sorte. Pour crer un mutex, crez une variable pthread_mutex_t et passez pthread_mutex_init un pointeur sur cette variable. Le second argument de pthread_mutex_init est un pointeur vers un objet dattributs de mutex. Comme pour pthread_create, si le pointeur est nul, ce sont les valeurs par dfaut des attributs qui sont utilises. La variable mutex ne doit tre initialise quune seule fois. Cet extrait de code illustre la dclaration et linitialisation dune variable mutex :
p t h re a d _ m u t ex _ t mutex ; p t h r e a d _ m u t e x _ i n i t (& mutex , NULL ) ;

Une faon plus simple de crer un mutex avec les attributs par dfaut est de linitialiser avec la valeur spciale PTHREAD_MUTEX_INITIALIZER. Aucun appel pthread_mutex_init nest alors ncessaire. Cette mthode est particulirement pratique pour les variables globales (et les membres static en C++). Lextrait de code prcdent pourrait tre crit comme suit :
p t h re a d _ m u t ex _ t mutex = P T H R E A D _ M U T E X _ I N I T I A L I Z E R ;

4.4. SYNCHRONISATION ET SECTIONS CRITIQUES

71

Un thread peut essayer de verrouiller un mutex en appelant pthread_mutex_lock dessus. Si le mutex tait dverrouill, il devient verrouill et la fonction se termine immdiatement. Si le mutex tait verrouill par un autre thread, pthread_mutex_lock bloque lexcution et ne se termine que lorsque le mutex est dverrouill par lautre thread. Lorsque le mutex est dverrouill, seul un des threads suspendus (choisi alatoirement) est dbloqu et autoris accder au mutex ; les autres threads restent bloqus. Un appel pthread_mutex_unlock dverrouille un mutex. Cette fonction doit toujours tre appele par le thread qui a verrouill le mutex. Listing 4.11 montre une autre version de la le de tches. Dsormais, celle-ci est protge par un mutex. Avant daccder la le (que ce soit pour une lecture ou une criture), chaque thread commence par verrouiller le mutex. Ce nest que lorsque la squence de vrication de la le et de suppression de la tche est termine que le mutex est dbloqu. Cela vite lapparition des conditions de concurrence ciritique cites prcdemment. Listing 4.11 (job-queue.c) Fonction de Thread de File de Tches, Protge par un Mutex
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39

# include < malloc .h > # include < pthread .h > struct job { /* Pointeur vers la tche suivante . */ struct job * next ; }; /* Liste chane des tches en attente . */ struct job * job_queue ; /* Mutex protgeant la file de tches . */ p t h re a d _ m u t ex _ t j o b _ qu e u e _ m u te x = P T H R E A D _ M U T E X _ I N I T I A L I Z E R ; /* Traite les tches en attente jusqu ce que la file soit vide . */ void * th r e a d _ fu n c t i o n ( void * arg ) { while (1) { struct job * next_job ; /* Verrouille le mutex de la file de tches . */ p t h r e a d _ m u t e x _ l o c k (& jo b _ q u e u e_ m u t e x ) ; /* Il est maintenant sans danger de vrifier si la file est vide . */ if ( job_queue == NULL ) next_job = NULL ; else { /* Rcupre la tche suivante . */ next_job = job_queue ; /* Supprime cette tche de la liste . */ job_queue = job_queue - > next ; } /* Dverrouille le mutex de la file de tches car nous en avons fini avec la file pour l instant . */ p t h r e a d _ m u t e x _ u n l o c k (& j o b _ qu e u e _ m ut e x ) ; /* La file tait vide ? Si c est la cas , le thread se termine . */ if ( next_job == NULL ) break ; /* Traite la tche . */ process_job ( next_job ) ; /* Libration des ressources . */ free ( next_job ) ; } return NULL ; }

72

CHAPITRE 4. THREADS

Tous les accs job_queue, le pointeur de donnes partag, ont lieu entre lappel pthread_mutex_lock et celui pthread_mutex_unlock. Laccs un objet dcrivant une tche, stock dans next_job, a lieu en dehors de cette zone uniquement une fois que lobjet a t supprim de la liste et a donc t rendu inaccessible aux autres threads. Notez que si la le est vide (cest--dire que job_queue est NULL), nous ne quittons pas la boucle immdiatement car dans ce cas le mutex resterait verrouill empchant laccs la le de tche par un quelconque autre thread. Au lieu de cela, nous notons que cest le cas en mettant next_job NULL et en ne quittant la boucle quaprs avoir dverrouill le mutex. Lutilisation de mutex pour verrouiller job_queue nest pas automatique ; cest vous dajouter le code pour verrouiller le mutex avant daccder la variable et le dverrouiller ensuite. Par exemple, une fonction dajout de tche la le pourrait ressembler ce qui suit :
void enqueue_job ( struct job * new_job ) { p t h r e a d _ m u t e x _ l o c k (& jo b _ q u e u e_ m u t e x ) ; new_job - > next = job_queue ; job_queue = new_job ; p t h r e a d _ m u t e x _ u n l o c k (& j o b _ qu e u e _ m ut e x ) ; }

4.4.3

Interblocage de Mutexes

Les mutexes orent un mcanisme permettant un thread de bloquer lexcution dun autre. Cela ouvre la possibilit lapparition dune nouvelle classe de bogues appels interblocages (deadlocks). Un interblocage survient lorsque un ou plusieurs threads sont bloqus en attendant quelque chose qui naura jamais lieu. Un type simple dinterblocage peut survenir lorsquun mme thread tente de verrouiller un mutex deux fois dal. Ce qui se passe dans un tel cas dpend du type de mutex utilis. Trois types de mutexes existent : Le verrouillage dun mutex rapide (le type par dfaut) provoquera un interblocage. Une tentative de verrouillage sur le mutex est bloquante jusqu ce que le mutex soit dverrouill. Mais comme le thread est bloqu sur un mutex quil a lui-mme verrouill, le verrou ne pourra jamais tre supprim. Le verrouillage dun mutex rcursif ne cause pas dinterblocage. Un mutex rcursif peut tre verrouill plusieurs fois par le mme thread en toute scurit. Le mutex se souvient combien de fois pthread_mutex_lock a t appel par le thread qui dtient le verrou ; ce thread doit eectuer autant dappels pthread_mutex_unlock pour que le mutex soit eectivement dverrouill et quun autre thread puisse y accder. GNU/Linux dtectera et signalera un double verrouillage sur un mutex vrication derreur qui causerait en temps normal un interblocage. Le second appel pthread_mutex_lock renverra le code derreur EDEADLK. Par dfaut, un mutex GNU/Linux est de type rapide. Pour crer un mutex dun autre type, commencez par crer un objet dattributs de mutex en dclarant une variable de type pthread_mutexattr_t et appelez pthread_mutexattr_init en lui passant un pointeur dessus. Puis dnissez le type de mutex en appelant pthread_mutexattr_setkind_np ; son premier argument est un pointeur vers lobjet dattributs de mutex et le second est PTHREAD_MUTEX_RECURSIVE_NP

4.4. SYNCHRONISATION ET SECTIONS CRITIQUES

73

pour un mutex rcursif ou PTHREAD_MUTEX_ERRORCHECK_NP pour un mutex vrication derreurs. Passez un pointeur vers lobjet dattributs de mutex pthread_mutex_init pour crer un mutex du type dsir, puis dtruisez lobjet dattributs avec pthread_mutexattr_destroy. Le code suivant illustre la cration dun mutex vrication derreurs, par exemple :
p t h r e a d _ m u t e x a t t r _ t attr ; p t h re a d _ m u t ex _ t mutex ; p t h r e a d _ m u t e x a t t r _ i n i t (& attr ) ; p t h r e a d _ m u t e x a t t r _ s e t k i n d _ n p (& attr , P T H R E A D _ M U T E X _ E R R O R C H E C K _ N P ) ; p t h r e a d _ m u t e x _ i n i t (& mutex , & attr ) ; p t h r e a d _ m u t e x a t t r _ d e s t r o y (& attr ) ;

Comme le suggre le suxe np, les mutexes rcursifs et vrication derreurs sont spciques GNU/Linux et ne sont pas portables. Ainsi, il est gnralement dconseill de les utiliser dans vos programmes (les mutexes vrication derreurs peuvent cependant tre utiles lors du dbogage).

4.4.4

Vrication de Mutex non Bloquante

De temps en temps, il peut tre utile de tester si un mutex est verrouill sans tre bloqu sil lest. Par exemple, un thread peut avoir besoin de verrouiller un mutex mais devoir faire autre chose au lieu de se bloquer si le mutex est dj verrouill. Comme pthread_mutex_lock ne se termine pas avant le dverrouillage du mutex, une autre fonction est ncessaire. GNU/Linux fournit pthread_mutex_trylock pour ce genre de choses. Si vous appelez pthread_mutex_trylock sur un mutex dverrouill, vous verrouillerez le mutex comme si vous aviez appel pthread_mutex_lock et pthread_mutex_trylock renverra zro. Par contre, si le mutex est dj verrouill par un autre mutex, pthread_mutex_trylock ne sera pas bloquante. Au lieu de cela, elle se terminera en renvoyant le code derreur EBUSY. Le verrou sur le mutex dtenu par lautre thread nest pas aect. Vous pouvez ressayer plus tard dobtenir un verrou.

4.4.5

Smaphores pour les Threads

Dans lexemple prcdent, dans lequel plusieurs threads traitent les tches dune le, la fonction principale des threads traite la tche suivante jusqu ce quil nen reste plus, ce moment, le thread se termine. Ce schma fonctionne si toutes les tches sont mises dans la le au pralable ou si de nouvelles tches sont ajoutes au moins aussi vite que la vitesse de traitement des threads. Cependant, si les threads fonctionnent trop rapidement, la le de tches se videra et les threads se termineront. Si de nouvelles tches sont mises dans la le plus tard, il ne reste plus de thread pour les traiter. Ce que nous devrions faire est de mettre en place un mcanisme permettant de bloquer les threads lorsque la le se vide et ce jusqu ce que de nouvelles tches soient disponibles. Lutilisation de smaphores permet de faire ce genre de choses. Un smaphore est un compteur qui peut tre utilis pour synchroniser plusieurs threads. Comme avec les mutexes, GNU/Linux garantit que la vrication ou la modication de la valeur dun smaphore peut tre accomplie en toute scurit, sans risque de concurrence critique. Chaque smaphore dispose dune valeur de compteur, qui est un entier positif ou nul. Un smaphore supporte deux oprations de base :

74

CHAPITRE 4. THREADS

Une opration dattente (wait), qui dcrmente la valeur du smaphore dune unit. Si la valeur est dj zro, lopration est bloquante jusqu ce que la valeur du smaphore redevienne positive (en raison dune opration de la part dun autre thread). Lorsque la valeur du smaphore devient positive, elle est dcrmente dune unit et lopration dattente se termine. Une opration de rveil (post) qui incrmente la valeur du smaphore dune unit. Si le smaphore tait prcdemment zro et que dautres threads taient en attente sur ce mme smaphore, un de ces threads est dbloqu et son attente se termine (ce qui ramne la valeur du smaphore zro). Notez que GNU/Linux fournit deux implmentations lgrement direntes des smaphores. Celle que nous dcrivons ici est limplmentation POSIX standard. Utilisez cette implmentation lors de la communication entre threads. Lautre implmentation, utilise pour la communication entre les processus, est dcrite dans la Section 5.2, Smaphores pour les Processus . Si vous utilisez les smaphores, incluez <semaphore.h>. Un smaphore est reprsent par une variable sem_t. Avant de lutiliser, vous devez linitialiser par le biais de la fonction sem_init, laquelle vous passez un pointeur sur la variable sem_t. Le second paramtre doit tre zro3 , et le troisime paramtre est la valeur initiale du smaphore. Si vous navez plus besoin dun smaphore, il est conseill de librer les ressources quil occupe avec sem_destroy. Pour vous mettre en attente sur un smaphore, utilisez sem_wait. Pour eectuer une opration de rveil, utilisez sem_post. Une fonction permettant une mise en attente non bloquante, sem_trywait, est galement fournie. Elle est similaire pthread_mutex_trylock si lattente devait bloquer en raison dun smaphore zro, la fonction se termine immdiatement, avec le code derreur EAGAIN, au lieu de bloquer le thread. GNU/Linux fournit galement une fonction permettant dobtenir la valeur courante dun smaphore, sem_getvalue, qui place la valeur dans la variable int pointe par son second argument. Vous ne devriez cependant pas utiliser la valeur du smaphore que vous obtenez partir de cette fonction pour dcider dune opration dattente ou de rveil sur un smaphore. Utiliser ce genre de mthode peut conduire des conditions de concurrence critique : un autre thread peut changer la valeur du smaphore entre lappel de sem_getvalue et lappel une autre fonction de manipulation des smaphores. Utilisez plutt les fonctions doprations dattente et de rveil atomiques. Pour revenir notre exemple de le dattente de tches, nous pouvons utiliser un smaphore pour compter le nombre de tches en attente dans la le. Le Listing 4.12 contrle la le avec un smaphore. La fonction enqueue_job ajoute une nouvelle tche la le dattente. Listing 4.12 (job-queue3.c) File de Tches Contrle par un Smaphore
1 2 3 4 5 6 3

# include < malloc .h > # include < pthread .h > # include < semaphore .h > struct job { /* Champ de chanage . struct job * next ;

*/

Une valeur dirente de zro indiquerait que le smaphore peut tre partag entre les processus, ce qui nest pas support sous GNU/Linux pour ce type de smaphore.

4.4. SYNCHRONISATION ET SECTIONS CRITIQUES


7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68

75

/* Autres champs dcrivant la tche ... */ }; /* Liste chane des tches en attente . */ struct job * job_queue ; /* Mutex protgeant job_queue . */ p t h re a d _ m u t ex _ t j o b _ qu e u e _ m ut e x = P T H R E A D _ M U T E X _ I N I T I A L I Z E R ; /* Smaphore comptant le nombre de tches dans la file . */ sem_t j o b _ qu e u e _ c o un t ; /* In itia lisat ion de la file de tches . */ void i n i t i a l i z e _ j o b _ q u e u e () { /* La file est initialement vide . */ job_queue = NULL ; /* Initialise le smaphore avec le nombre de tches dans la file . Sa valeur initiale est zro . */ sem_init (& job_queue_count , 0 , 0) ; } /* Traite les tches en attente jusqu ce que la file soit vide . */ void * th r e a d _ fu n c t i o n ( void * arg ) { while (1) { struct job * next_job ; /* Se met en attente sur le smaphore de la file de tches . Si sa valeur est positive , indiquant que la file n est pas vide , dcrmente le total d une unit . Si la file est vide , bloque jusqu ce qu une nouvelle tche soit mise en attente . */ sem_wait (& j ob _ q u e u e _c o u n t ) ; /* Verrouille le mutex sur la file de tches . */ p t h r e a d _ m u t e x _ l o c k (& jo b _ q u e u e_ m u t e x ) ; /* cause du smaphore , nous savons que la file n est pas vide . Rcupre donc la prochaine tche disponible . */ next_job = job_queue ; /* Supprime la tche de la liste . */ job_queue = job_queue - > next ; /* Dverrouille le mutex de la file d attente car nous en avons fini avec celle - ci pour le moment . */ p t h r e a d _ m u t e x _ u n l o c k (& j o b _ qu e u e _ m ut e x ) ; /* Traite la tche . */ process_job ( next_job ) ; /* Libration des ressources . */ free ( next_job ) ; } return NULL ; } /* Ajoute une nouvelle tche en tte de la file . */ void enqueue_job ( /* Passez les donnes sur la tche ici */ ) { struct job * new_job ; /* Alloue un nouvel objet job . */ new_job = ( struct job *) malloc ( sizeof ( struct job ) ) ; /* Positionner les autres champs de la struct job ici ... */ /* Verrouille le mutex de la file de tches avant d y accder . */ p t h r e a d _ m u t e x _ l o c k (& jo b _ q u e u e_ m u t e x ) ; /* Ajoute la nouvelle tche en tte de file . */ new_job - > next = job_queue ; job_queue = new_job ; /* Envoie un signal de rveil smaphore pour indiquer qu une nouvelle tche est disponible . Si des threads sont bloqus en attente sur le smaphore , l un d eux sera dbloqu et pourra donc traiter la tche . */ sem_post (& j ob _ q u e u e _c o u n t ) ; /* Dverrouille le mutex de la file de tches . */ p t h r e a d _ m u t e x _ u n l o c k (& j o b _ qu e u e _ m ut e x ) ;

76
69

CHAPITRE 4. THREADS
}

Avant de prlever une tche en tte de le, chaque thread se mettra en attente sur le smaphore. Si la valeur de celui-ci est zro, indiquant que la le est vide, le thread sera tout simplement bloqu jusqu ce que le smaphore devienne positif, indiquant que la tche a t ajoute la le. La fonction enqueue_job ajoute une tche la le. Comme thread_function, elle doit verrouiller le mutex de la le avant de la modier. Aprs avoir ajout la tche la le, elle envoie un signal de rveil au smaphore, indiquant quune nouvelle tche est disponible. Dans la version du Listing 4.12, les threads qui traitent les tches ne se terminent jamais ; si aucune tche nest disponible pendant un certain temps, ils sont simplement bloqus dans sem_wait.

4.4.6

Variables de Condition

Nous avons montr comment utiliser un mutex pour protger une variable contre les accs simultans de deux threads et comment utiliser les smaphores pour implmenter un compteur partag. Une variable de condition est un troisime dispositif de synchronisation que fournit GNU/Linux ; avec ce genre de mcanisme, vous pouvez implmenter des conditions dexcution plus complexes pour le thread. Supposons que vous criviez une fonction de thread qui excute une boucle innie, accomplissant une tche chaque itration. La boucle du thread, cependant, a besoin dtre contrle par un indicateur : la boucle ne sexcute que lorsquil est actif ; dans le cas contraire, la boucle est mise en pause. Le Listing 4.13 montre comment vous pourriez limplmenter au moyen dune simple boucle. chaque itration, la fonction de thread vrie que lindicateur est actif. Comme plusieurs threads accdent lindicateur, il est protg par un mutex. Cette implmentation peut tre correcte mais nest pas ecace. La fonction de thread utilisera du temps processeur, que lindicateur soit actif ou non, vrier et revrier cet indicateur, verrouillant et dverrouillant le mutex chaque fois. Ce dont vous avez rellement besoin, est un moyen de mettre le thread en pause lorsque lindicateur nest pas actif, jusqu ce quun certain changement survienne qui pourrait provoquer lactivation de lindicateur. Listing 4.13 (spin-condvar.c) Implmentation Simple de Variable de Condition
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

# include < pthread .h > int thread_flag ; p t h re a d _ m u t ex _ t t h r e a d _ f l a g _ m u t e x ; void i n i ti a l i z e _ fl a g () { p t h r e a d _ m u t e x _ i n i t (& thread_flag_mutex , NULL ) ; thread_flag = 0; } /* Appelle do_work de faon rpte tant que l indicateur est actif ; sinon , tourne dans la boucle . */ void * th r e a d _ fu n c t i o n ( void * thread_arg ) { while (1) { int flag_is_set ; /* Protge l indicateur avec un mutex . */ p t h r e a d _ m u t e x _ l o c k (& t h r e a d _ f l a g _ m u t e x ) ;

4.4. SYNCHRONISATION ET SECTIONS CRITIQUES


17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32

77

flag_is_set = thread_flag ; p t h r e a d _ m u t e x _ u n l o c k (& t h r e a d _ f l a g _ m u t e x ) ; if ( flag_is_set ) do_work () ; /* Rien faire sinon , part boucler . */ } return NULL ; } /* Positionne la valeur de l indicateur de thread FLAG_VALUE . void s e t _t h r e a d _ fl a g ( int flag_value ) { /* Protge l indicateur avec un verrouillage de mutex . */ p t h r e a d _ m u t e x _ l o c k (& t h r e a d _ f l a g _ m u t e x ) ; thread_flag = flag_value ; p t h r e a d _ m u t e x _ u n l o c k (& t h r e a d _ f l a g _ m u t e x ) ; } */

Une variable de condition vous permet de spcier une condition qui lorsquelle est remplie autorise lexcution du thread et inversement, une condition qui lorsquelle est remplie bloque le thread. Du moment que tous les threads susceptibles de modier la condition utilisent la variable de condition correctement, Linux garantit que les threads bloqus cause de la condition seront dbloqus lorsque la condition change. Comme pour les smaphores, un thread peut se mettre en attente sur une variable de condition. Si le thread A est en attente sur une variable de condition, il est bloqu jusqu ce quun autre thread, le thread B, valide la mme variable de condition. Contrairement un smaphore, une variable de condition ne dispose pas de compteur ou de mmoire ; le thread A doit se mettre en attente sur la variable de condition avant que le thread B ne la valide. Si le thread B valide la condition avant que le thread A ne fasse un wait dessus, la validation est perdue, et le thread A reste bloqu jusqu ce quun autre thread ne valide la variable de condition son tour. Voici comment vous utiliseriez une variable de condition pour rendre lexemple prcdent plus ecace : La boucle dans thread_function vrie lindicateur. Sil nest pas actif, le thread se met en attente sur la variable de condition. La fonction set_thread_flag valide la variable de condition aprs avoir chang la valeur de lindicateur. De cette faon, si thread_function est bloque sur la variable de condition, elle sera dbloque et vriera la condition nouveau. Il y a un problme avec cette faon de faire : il y a une concurrence critique entre la vrication de la valeur de lindicateur et la validation ou lattente sur la variable de condition. Supposons que thread_function ait vri lindicateur et ait dtermin quil ntait pas actif. ce moment, lordonnancer de Linux suspend ce thread et relance le principal. Par hasard, le thread principal est dans set_thread_flag. Il active lindicateur puis valide la variable de condition. Comme aucun thread nest en attente sur la variable de condition ce moment (souvenez-vous que thread_function a t suspendue avant de se mettre en attente sur la variable de condition), la validation est perdue. Maintenant, lorsque Linux relance lautre thread, il commence attendre la variable de condition et peut tre bloqu pour toujours. Pour rsoudre ce problme, nous avons besoin de verrouiller lindicateur et la variable de condition avec un seul mutex. Heureusement, GNU/Linux dispose exactement de ce mcanisme.

78

CHAPITRE 4. THREADS

Chaque variable de condition doit tre utilise en conjugaison avec un mutex, pour viter ce type de concurrence critique. Avec ce principe, la fonction de thread suit les tapes suivantes : 1. La boucle dans thread_function verrouille le mutex et lit la valeur de lindicateur. 2. Si lindicateur est actif, elle dverrouille le mutex et excute la fonction de traitement. 3. Si lindicateur nest pas actif, elle dverrouille le mutex de faon atomique et se met en attente sur la variable de condition. La fonctionnalit critique utilise se trouve dans ltape 3, GNU/Linux vous permet de dverrouiller le mutex et de vous mettre en attente sur la variable de condition de faon atomique, sans quun autre thread puisse intervenir. Cela limine le risque quun autre thread ne change la valeur de lindicateur et ne valide la variable de condition entre le test de lindicateur et la mise en attente sur la variable de condition de thread_function. Une variable de condition est reprsente par une instance de pthread_cond_t. Souvenezvous que chaque variable de condition doit tre accompagne dun mutex. Voici les fonctions qui manipulent les variables de condition : pthread_cond_init initialise une variable de condition. Le premier argument est un pointeur vers une variable pthead_cond_t. Le second argument, un pointeur vers un objet dattributs de variable de condition, est ignor par GNU/Linux. Le mutex doit tre initialis part, comme indiqu dans la Section 4.4.2, Mutexes . pthread_cond_signal valide une variable de condition. Un seul des threads bloqus sur la variable de condition est dbloqu. Si aucun thread nest bloqu sur la variable de condition, le signal est ignor. Largument est un pointeur vers la variable pthread_cond_t . Un appel similaire, pthread_cond_broadcast, dbloque tous les threads bloqus sur une variable de condition, au lieu dun seul pthread_cond_wait bloque lappelant jusqu ce que la variable de condition soit valide. Largument est un pointeur vers la variable pthread_cond_t. Le second argument est un pointeur vers la variable pthread_mutex_t. Lorsque pthread_cond_wait est appele, le mutex doit dj tre verrouill par le thread appelant. Cette fonction dverrouille automatiquement le mutex et se met en attente sur la variable de condition. Lorsque la variable de condition est valide et que le thread appelant est dbloqu, pthread_cond_wait racquiert automatiquement un verrou sur le mutex. Lorsque votre programme eectue une action qui pourrait modier la condition que vous protgez avec la variable de condition, il doit suivre les tapes suivante (dans notre exemple, la condition est ltat de lindicateur du thread, donc ces tapes doivent tre suivies chaque fois que lindicateur est modi) : 1. Verrouiller le mutex accompagnant la variable de condition. 2. Eectuer laction qui pourrait modier la condition (dans notre exemple, activer lindicateur). 3. Valider ou eectuer un broadcast sur la variable de condition, selon le comportement dsir. 4. Dverrouiller le mutex accompagnant la variable de condition.

4.4. SYNCHRONISATION ET SECTIONS CRITIQUES

79

Le Listing 4.14 reprend lexemple prcdent, en utilisant une variable de condition pour protger lindicateur. Notez quau sein de thread_function, un verrou est pos sur le mutex avant de vrier la valeur de thread_flag. Ce verrou est automatiquement libr par pthread_cond_wait avant quil ne se bloque et automatiquement racquis ensuite. Notez galement que set_thread_flag verrouille le mutex avant de dnir la valeur de thread_flag et de valider le mutex. Listing 4.14 (condvar.c) Contrler un Thread avec une Variable de Condition
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48

# include < pthread .h > int thread_flag ; pt hr ea d_c on d_t thr ea d_ fla g_ cv ; p t h re a d _ m u t ex _ t t h r e a d _ f l a g _ m u t e x ; void i n i ti a l i z e _ fl a g () { /* Initialise le mutex et la variable de condition . */ p t h r e a d _ m u t e x _ i n i t (& thread_flag_mutex , NULL ) ; p t h r e a d _ c o n d _ i n i t (& thread_flag_cv , NULL ) ; /* Initialise la valeur de l indicateur . */ thread_flag = 0; } /* Appelle do_work de faon rpte tant que l indicateur est actif ; bloque si l indicateur n est pas actif . */ void * th r e a d _ fu n c t i o n ( void * thread_arg ) { /* Boucle infinie . */ while (1) { /* Verrouille le mutex avant d accder la valeur de l indicateur . */ p t h r e a d _ m u t e x _ l o c k (& t h r e a d _ f l a g _ m u t e x ) ; while (! thread_flag ) /* L indicateur est inactif . Attend la validation de la variable de condition , indiquant que la valeur de l indicateur a chang . Lorsque la validation a lieu et que le thread se dbloque , boucle et teste nouveau l indicateur . */ p t h r e a d _ c o n d _ w a i t (& thread_flag_cv , & t h r e a d _ f l a g _ m u t e x ) ; /* Lorsque nous arrivons ici , nous savons que l indicateur est actif . Dverrouille le mutex . */ p t h r e a d _ m u t e x _ u n l o c k (& t h r e a d _ f l a g _ m u t e x ) ; /* Actions utiles . */ do_work () ; } return NULL ; } /* Dfinit la valeur de l indicateur FLAG_VALUE . */ void s e t _t h r e a d _ fl a g ( int flag_value ) { /* Verrouille le mutex avant d accder la valeur de l indicateur . */ p t h r e a d _ m u t e x _ l o c k (& t h r e a d _ f l a g _ m u t e x ) ; /* Dfinit la valeur de l indicateur , puis valide la condition , si jamais t h r ea d _ f u n c ti o n est bloque en attente de l activation de l indicateur . Cependant , t h r ea d _ f u n c ti o n ne peut pas rellement tester la valeur de l indicateur tant que le mutex n est pas dverrouill . */ thread_flag = flag_value ; p t h r e a d _ c o n d _ s i g n a l (& t hr ead _f la g_c v ) ; /* Dverrouille le mutex . */ p t h r e a d _ m u t e x _ u n l o c k (& t h r e a d _ f l a g _ m u t e x ) ; }

La condition protge par une variable de condition peut tre dune complexit quelconque. Cependant, avant deectuer une opration qui pourrait modier la condition, un verrouillage

80

CHAPITRE 4. THREADS

du mutex doit tre demand, aprs quoi la variable de condition doit tre valide. Une variable de condition peut aussi tre utilise sans condition, simplement pour bloquer un thread jusqu ce quun autre thread le rveille . Un smaphore peut galement tre utilis pour ce faire. La principale dirence est quun smaphore se souvient de lappel de rveil, mme si aucun thread ntait bloqu en attente ce moment, alors quune variable de condition ignore les appels de rveil, moins quun thread ne soit bloqu en attente ce moment. Qui plus est, un smaphore ne ractive quun thread par signal de rveil ; avec pthread_cond_broadcast, un nombre quelconque et inconnu de threads bloqus peuvent tre relancs en une fois.

4.4.7

Interblocage avec Deux Threads ou Plus

Des interblocages peuvent survenir lorsque deux threads (ou plus) sont bloqus, chacun attendant une validation de condition que seul lautre peut eectuer. Par exemple, si le thread A est bloqu sur une variable de condition attendant que le thread B la valide et le thread B est bloqu sur une variable de condition attendant que le thread A la valide, un interblocage survient car aucun thread ne pourra jamais valider la variable attendue par lautre. Vous devez tre attentif an dviter lapparition de telles situations car elles sont assez diciles dtecter. Une situation courante menant un interblocage survient lorsque plusieurs threads tentent de verrouiller le mme ensemble dobjets. Par exemple, considrons un programme dans lequel deux threads dirents, excutant deux fonctions de thread distinctes, ont besoin de verrouiller les deux mmes mutexes. Supposons que le thread A verrouille le mutex 1, puis le mutex 2 et que le thread B verrouille le mutex 2 avant le mutex 1. Avec un scnario dordonnancement pessimiste, Linux pourrait donner la main au thread A susamment longtemps pour quil verrouille le mutex 1 puis donne la main au thread B qui verrouille immdiatement le mutex 2. Dsormais, aucun thread ne peut plus avancer cas chacun est bloqu sur un mutex que lautre maintient verrouill. Cest un exemple de problme dinterblocage gnral qui peut impliquer non seulement des objets de synchronisation, comme les mutex, mais galement dautres ressources, comme des verrous sur des chiers ou des priphriques. Le problme survient lorsque plusieurs threads tentent de verrouiller le mme ensemble de ressources dans des ordres dirents. La solution est de sassurer que tous les threads qui verrouillent plus dune ressource le font dans le mme ordre.

4.5

Implmentation des Threads sous GNU/Linux

Limplmentation des threads POSIX sous GNU/Linux dire de limplmentation des threads sous beaucoup de systmes de type UNIX sur un point important : sous GNU/Linux, les threads sont implments comme des processus. Lorsque vous appelez pthread_create pour crer un nouveau thread, Linux cre un nouveau processus qui excute ce thread. Cependant, ce processus nest pas identique ceux crs au moyen de fork ; en particulier, il partage son espace dadressage et ses ressources avec le processus original au lieu den recevoir des copies. Le programme thread-pid du Listing 4.15 le dmontre. Le programme cre un thread ; le thread original et le nouveau appellent tous deux la fonction getpid et achent leurs identiants de processus respectifs, puis bouclent indniment.

4.5. IMPLMENTATION DES THREADS SOUS GNU/LINUX Listing 4.15 (thread-pid.c) Ache les Identiants de Processus des Threads
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

81

# include < pthread .h > # include < stdio .h > # include < unistd .h > void * th r e a d _ fu n c t i o n ( void * arg ) { fprintf ( stderr , " L identifiant du thread fils est % d \ n " , ( int ) getpid () ) ; /* Boucle indfiniment . */ while (1) ; return NULL ; } int main () { pthread_t thread ; fprintf ( stderr , " L identifiant du thread principal est % d \ n " ,( int ) getpid () ) ; pt hr ea d_c re ate (& thread , NULL , & thread_function , NULL ) ; /* Boucle indfiniment . */ while (1) ; return 0; }

Lancez le programme en arrire-plan puis invoquez ps x pour acher vos processus en cours dexcution. Noubliez pas de tuer le programme thread-pid ensuite il consomme beaucoup de temps processeur pour rien. Voici quoi la sortie pourrait ressembler :
% cc thread-pid.c -o thread-pid -lpthread % ./thread-pid & [1] 14608 main thread pid is 14608 child thread pid is 14610 % ps x PID TTY STAT TIME COMMAND 14042 pts/9 S 0:00 bash 14608 pts/9 R 0:01 ./thread-pid 14609 pts/9 S 0:00 ./thread-pid 14610 pts/9 R 0:01 ./thread-pid 14611 pts/9 R 0:00 ps x % kill 14608 [1]+ Terminated ./thread-pid

Notications de Contrle des Tches dans le Shell Les lignes dbutant par [1] viennent du shell. Lorsque vous excutez un programme en arrireplan, le shell lui assigne un numro de tche dans ce cas, 1 et ache lidentiant de processus du programme. Si une tche en arrire-plan se termine, le shell le signale lorsque vous invoquez une nouvelle commande.

Remarquez quil y a trois processus excutant le programme thread-pid. Le premier, avec le pid 14608, est le thread principal du programme ; le troisime, avec le pid 14610, est le thread que nous avons cr pour excuter thread_function. Quen est-il du second thread, avec le pid 14609 ? Il sagit du thread de gestion (manager thread) qui fait partie de limplmentation interne des threads sous GNU/Linux. Le thread

82

CHAPITRE 4. THREADS

de contrle est cr la premire fois quun programme appelle pthread_create pour crer un nouveau thread.

4.5.1

Gestion de Signaux

Supposons quun programme multithread reoive un signal. Dans quel thread est invoqu le gestionnaire de signal ? Le comportement de linteraction entre les threads et les signaux varie dun type dUNIX lautre. Sous GNU/Linux, ce comportement est dict par le fait que les threads sont implments comme des processus. Comme chaque thread est un processus distinct, et comme un signal est dlivr un processus particulier, il ny a pas dambigut au niveau du thread qui va recevoir le signal. Typiquement, les signaux envoys de lextrieur du programme sont envoys au processus correspondant au thread principal du programme. Par exemple, si un processus se divise et que le processus ls excute un programme multithread, le processus pre conservera lidentiant de processus du thread principal du programme du processus ls et lutilisera pour envoyer des signaux son ls. Il sagit gnralement dun bonne convention que vous devriez suivre lorsque vous envoyez des signaux un programme multithread. Notez que cet aspect de limplmentation de pthreads sous GNU/Linux va lencontre du standard de threads POSIX. Ne vous reposez pas sur ce comportement au sein de programmes destins tre portables. Au sein dun programme multithread, il est possible pour un thread denvoyer un signal un autre thread bien dni. Utilisez la fonction pthread_kill pour cela. Son premier paramtre est lidentiant du thread et le second, le numro du signal.

4.5.2

Lappel Systme clone

Bien que les threads GNU/Linux crs dans le mme programme soient implments comme des processus spars, ils partagent leur espace mmoire virtuel et leurs autres ressources. Un processus ls cr avec fork, cependant, copie ces objets. Comment est cr le premier type de processus ? Lappel systme clone de Linux est une forme hybride entre fork et pthread_create qui permet lappelant de spcier les ressources partages entre lui et le nouveau processus. clone ncessite galement que vous spciiez la rgion mmoire utilise pour la pile dexcution du nouveau processus. Bien que nous mentionnions clone pour satisfaire la curiosit du lecteur, cet appel systme ne doit pas tre utilis au sein de programmes. Utilisez fork pour crer de nouveau processus ou pthread_create pour crer des threads.

4.6

Comparaison Processus/Threads

Pour certains programmes tirant partie du paralllisme, le choix entre processus et threads peut tre dicile. Voici quelques pistes pour vous aider dterminer le modle de paralllisme qui convient le mieux votre programme : Tous les threads dun programme doivent excuter le mme code. Un processus ls, au contraire, peut excuter un programme dirent en utilisant une fonction exec.

4.6. COMPARAISON PROCESSUS/THREADS

83

Un thread peut endommager les donnes dautres threads du mme processus car les threads partagent le mme espace mmoire et leurs ressources. Par exemple, une criture sauvage en mmoire via un pointeur non initialis au sein dun thread peut corrompre la mmoire dun autre thread. Un processus corrompu par contre, ne peut pas agir de cette faon car chaque processus dispose de sa propre copie de lespace mmoire du programme. Copier le contenu de la mmoire pour un nouveau processus a un cot en performances par rapport la cration dun nouveau thread. Cependant, la copie nest eectue que lorsque la mmoire est modie, donc ce cot est minime si le processus ls ne fait que lire la mmoire. Les threads devraient tre utiliss pour les programmes qui ont besoin dun paralllisme nement contrl. Par exemple, si un problme peut tre dcompos en plusieurs tches presque identiques, les threads peuvent tre un bon choix. Les processus devraient tre utiliss pour des programmes ayant besoin dun paralllisme plus grossier. Le partage de donnes entre des threads est trivial car ceux-ci partagent le mme espace mmoire (cependant, il faut faire trs attention viter les conditions de concurrence critique, comme expliqu plus haut). Le partage de donnes entre des processus ncessite lutilisation de mcanismes IPC, comme expliqu dans le Chapitre 5. Cela peut tre plus complexe mais diminue les risques que les processus sourent de bugs lis au paralllisme.

84

CHAPITRE 4. THREADS

Chapitre 5

Communication Interprocessus
e Chapitre 3, Processus traitait de la cration de processus et montrait comment il est possible dobtenir le code de sortie dun processus ls. Il sagit de la forme la plus simple de communication entre deux processus, mais en aucun cas de la plus puissante. Les mcanismes prsents au Chapitre 3 ne fournissent aucun moyen au processus parent pour communiquer avec le ls except via les arguments de ligne de commande et les variables denvironnement, ni aucun moyen pour le processus ls de communiquer avec son pre, except par le biais de son code de sortie. Aucun de ces mcanismes ne permet de communiquer avec le processus ls pendant son excution, ni nautorise une communication entre les processus en dehors de la relation pre-ls. Ce chapitre prsente des moyens de communication interprocessus qui dpassent ces limitations. Nous prsenterons direntes faons de communiquer entre pre et ls, entre des processus sans liens et mme entre des processus sexcutant sur des machines distinctes. La communication interprocessus (interprocess communication, IPC) consiste transfrer des donnes entre les processus. Par exemple, un navigateur Internet peut demander une page un serveur, qui envoie alors les donnes HTML. Ce transfert utilise des sockets dans une connexion similaire celle du tlphone. Dans un autre exemple, vous pourriez vouloir imprimer les noms des chiers dun rpertoire en utilisant une commande du type ls | lpr. Le shell cre un processus ls et un processus lpr distincts et connecte les deux au moyen dun tube (ou pipe) reprsent par le symbole "|". Un tube permet une communication sens unique entre deux processus. Le processus ls crit les donnes dans le tube et le processus lpr les lit partir du tube. Dans ce chapitre, nous traiterons de cinq types de communication interprocessus : La mmoire partage permet aux processus de communiquer simplement en lisant ou crivant dans un emplacement mmoire prdni. La mmoire mappe est similaire la mmoire partage, except quelle est associe un chier. Les tubes permettent une communication squentiel dun processus lautre. Les les FIFO sont similaires aux tubes except que des processus sans lien peuvent communiquer car le tube reoit un nom dans le systme de chiers. 85

86

CHAPITRE 5. COMMUNICATION INTERPROCESSUS

Les sockets permettent la communication entre des processus sans lien, pouvant se trouver sur des machines distinctes. Ces types dIPC dirent selon les critres suivants : Ils restreignent ou non la communication des processus lis (processus ayant un anctre commun), des processus partageant le mme systme de chiers ou tout ordinateur connect un rseau. Un processus communiquant nest limit qu la lecture ou qu lcriture de donnes. Le nombre de processus pouvant communiquer. Les processus qui communiquent sont synchroniss par lIPC par exemple, un processus lecteur sinterrompt jusqu ce quil y ait des donnes lire. Dans ce chapitre, nous ne traiteront pas des IPC ne permettant quune communication limite un certain nombre de fois, comme communiquer en utilisant la valeur de sortie du ls.

5.1

Mmoire Partage

Une des mthodes de communication interprocessus les plus simples est dutiliser la mmoire partage. La mmoire partage permet deux processus ou plus daccder la mme zone mmoire comme sils avaient appel malloc et avaient obtenu des pointeurs vers le mme espace mmoire. Lorsquun processus modie la mmoire, tous les autres processus voient la modication.

5.1.1

Communication Locale Rapide

La mmoire partage est la forme de communication interprocessus la plus rapide car tous les processus partagent la mme mmoire. Laccs cette mmoire partage est aussi rapide que laccs la mmoire non partage du processus et ne ncessite pas dappel systme ni dentre dans le noyau. Elle vite galement les copies de donnes inutiles. Comme le noyau ne coordonne pas les accs la mmoire partage, vous devez mettre en place votre propre synchronisation. Par exemple, un processus ne doit pas eectuer de lecture avant que des donnes aient t crites et deux processus ne doivent pas crire au mme emplacement en mme temps. Une stratgie courante pour viter ces conditions de concurrence est dutiliser des smaphores, ce dont nous parlerons dans la prochaine section. Nos programmes dillustration, cependant, ne montrent quun seul processus accdant la mmoire, an de se concentrer sur les mcanismes de la mmoire partage et viter dobscurcir le code avec la logique de synchronisation.

5.1.2

Le Modle Mmoire

Pour utiliser un segment de mmoire partage, un processus doit allouer le segment. Puis, chaque processus dsirant accder au segment doit lattacher. Aprs avoir ni dutiliser le segment, chaque processus le dtache. un moment ou un autre, un processus doit librer le segment. La comprhension du modle mmoire de Linux aide expliquer le processus dallocation et dattachement. Sous Linux, la mmoire virtuelle de chaque processus est divise en pages. Chaque

5.1. MMOIRE PARTAGE

87

processus conserve une correspondance entre ses adresses mmoire et ces pages de mmoire virtuelle, qui contiennent rellement les donnes. Mme si chaque processus dispose de ses propres adresses, plusieurs tables de correspondance peuvent pointer vers la mme page, permettant le partage de mmoire. Les pages mmoires sont examines de plus prs dans la Section 8.8, La Famille mlock : Verrouiller la Mmoire Physique , du Chapitre 8, Appels Systme Linux . Lallocation dun nouveau segment de mmoire partage provoque la cration de nouvelles pages de mmoire virtuelle. Comme tous les processus dsirent accder au mme segment de mmoire partage, seul un processus doit allouer un nouveau segment de mmoire partage. Allouer un segment existant ne cre pas de nouvelles pages mais renvoie lidentiant des pages existantes. Pour quun processus puisse utiliser un segment de mmoire partage, il doit lattacher, ce qui ajoute les correspondances entre sa mmoire virtuelle et les pages partages du segment. Lorsquil en a termin avec le segment, ces correspondances sont supprimes. Lorsque plus aucun processus na besoin daccder ces segments de mmoire partage, un processus exactement doit librer les pages de mmoire virtuelle. Tous les segments de mmoire partage sont allous sous forme de multiples entiers de la taille de page du systme, qui est le nombre doctets dans une page mmoire. Sur les systmes Linux, la taille de page est de 4 Ko mais vous devriez vous baser sur la valeur renvoye par getpagesize.

5.1.3

Allocation

Un processus alloue un segment de mmoire partage en utilisant shmget ( SHared Memory GET , obtention de mmoire partage). Son premier paramtre est une cl entire qui indique le segment crer. Des processus sans lien peuvent accder au mme segment partag en spciant la mme valeur de cl. Malheureusement, dautres processus pourraient avoir choisi la mme valeur de cl x, ce qui provoquerait un conit. Utiliser la constante spciale IPC_PRIVATE comme valeur de cl garantit quun nouveau segment mmoire est cr. Le second paramtre indique le nombre doctets du segment. Comme les segments sont allous en utilisant des pages, le nombre doctets eectivement allous est arrondi au multiple de la taille de page suprieur. Le troisime paramtre est un ou binaire entre des indicateurs dcrivant les options demandes shmget. Voici ces indicateurs : IPC_CREAT Cet indicateur demande la cration dun nouveau segment. Cela permet la cration dun nouveau segment tout en spciant une valeur de cl. IPC_EXCL Cet indicateur, toujours utilis avec IPC_CREAT, provoque lchec de shmget si la cl de segment spcie existe dj. Donc, cela permet au processus appelant davoir un segment exclusif . Si cette option nest pas prcise et que la cl dun segment existant est utilis, shmget renvoie le segment existant au lieu den crer un nouveau. Indicateurs de mode Cette valeur est constitue de 9 bits indiquant les permissions du propritaire, du groupe et des autres utilisateurs pour contrler laccs au segment. Les bits dexcution sont ignors. Une faon simple de spcier les permissions est dutiliser les constantes dnies dans <sys/stat.h> et documentes dans la page de manuel de section 2

88

CHAPITRE 5. COMMUNICATION INTERPROCESSUS

de stat1 . Par exemple, S_IRUSR et S_IWUSR spcient des permissions de lecture et criture pour le propritaire du segment de mmoire partage et S_IROTH et S_IWOTH spcient des permissions de lecture et criture pour les autres utilisateurs. Lappel suivant shmget cre un nouveau segment de mmoire partage (ou accde un segment existant, si shm_key est dj utilis) qui peut tre lu et crit par son propritaire mais pas par les autres utilisateurs.
int segment_id = shmget ( shm_key , getpagesize () , IPC_CREAT | S_IRUSR | S_IWUSR ) ;

Si lappel se passe bien, shmget renvoie un identiant de segment. Si le segment de mmoire partage existe dj, les permissions daccs sont vries et le systme sassure que le segment nest pas destin tre dtruit.

5.1.4

Attachement et Dtachement

Pour rendre le segment de mmoire partage disponible, un procesus doit utiliser shmat ( SHared Memory ATtach , attachement de mmoire partage) en lui passant lidentiant du segment de mmoire partage SHMID renvoy par shmget. Le second argument est un pointeur qui indique o vous voulez que le segment soit mis en correspondance dans lespace dadressage de votre processus ; si vous passez NULL, Linux slectionnera une adresse disponible. Le troisime argument est un indicateur, qui peut prendre une des valeurs suivantes : SHM_RND indique que ladresse spcie par le second paramtre doit tre arrondie un multiple infrieur de la taille de page. Si vous nutilisez pas cet indicateur, vous devez aligner le second argument de shmat sur un multiple de page vous-mme. SHM_RDONLY indique que le segment sera uniquement lu, pas crit. Si lappel se droule correctement, il renvoie ladresse du segment partag attach. Les processus ls crs par des appels fork hritent des segments partags attachs ; il peuvent les dtacher sils le souhaitent. Lorsque vous en avez ni avec un segment de mmoire partage, le segment doit tre dtach en utilisant shmdt ( SHared Memory DeTach , Dtachement de Mmoire Partage) et lui passant ladresse renvoye par shmat. Si le segment na pas t libr et quil sagissait du dernier processus lutilisant, il est supprim. Les appels exit et toute fonction de la famille dexec dtachent automatiquement les segments.

5.1.5

Contrler et Librer la Mmoire Partage

Lappel shmctl ( SHared Memory ConTroL , contrle de la mmoire partage) renvoie des informations sur un segment de mmoire partage et peut le modier. Le premier paramtre est lidentiant dun segment de mmoire partage. Pour obtenir des informations sur un segment de mmoire partage, passez IPC_STAT comme second argument et un pointeur vers une struct shmid_ds.
Ces bits de permissions sont les mmes que ceux utiliss pour les chiers. Ils sont dcrits dans la Section 10.3, Permissions du Systme de Fichiers .
1

5.1. MMOIRE PARTAGE

89

Pour supprimer un segment, passez IPC_RMID comme second argument et NULL comme troisime argument. Le segment est supprim lorsque le dernier processus qui la attach le dtache. Chaque segment de mmoire partage devrait tre explicitement libr en utilisant shmctl lorsque vous en avez termin avec lui, an dviter de dpasser la limite du nombre total de segments de mmoire partage dnie par le systme. Linvocation de exit et exec dtache les segments mmoire mais ne les libre pas. Consultez la page de manuel de shmctl pour une description des autres oprations que vous pouvez eectuer sur les segments de mmoire partage.

5.1.6

Programme Exemple

Le programme du Listing 5.1 illustre lutilisation de la mmoire partage. Listing 5.1 (shm.c) Utilisation de la Mmoire Partage
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37

# include < stdio .h > # include < sys / shm .h > # include < sys / stat .h > int main () { int segment_id ; char * shared_memory ; struct shmid_ds shmbuffer ; int segment_size ; const int s h a r e d _ s e g m e n t _ s i z e = 0 x6400 ; /* Alloue le segment de mmoire partage . */ segment_id = shmget ( IPC_PRIVATE , shared_segment_size , IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR ) ; /* Attache le segment de mmoire partage . */ shared_memory = ( char *) shmat ( segment_id , 0 , 0) ; printf ( " mmoire partage attache l adresse % p \ n " , shared_memory ) ; /* Dtermine la taille du segment . */ shmctl ( segment_id , IPC_STAT , & shmbuffer ) ; segment_size = shmbuffer . shm_segsz ; printf ( " taille du segment : % d \ n " , segment_size ) ; /* crit une chane dans le segment de mmoire partage . */ sprintf ( shared_memory , " Hello , world . " ) ; /* Dtache le segment de mmoire partage . */ shmdt ( shared_memory ) ; /* Rattache le segment de mmoire partage une adresse diffrente . */ shared_memory = ( char *) shmat ( segment_id , ( void *) 0 x5000000 , 0) ; printf ( " mmoire partage rattache l adresse % p \ n " , shared_memory ) ; /* Affiche la chane de la mmoire partage . */ printf ( " % s \ n " , shared_memory ) ; /* Dtache le segment de mmoire partage . */ shmdt ( shared_memory ) ; /* Libre le segment de mmoire partage . shmctl ( segment_id , IPC_RMID , 0) ; return 0; } */

90

CHAPITRE 5. COMMUNICATION INTERPROCESSUS

5.1.7

Dbogage

La commande ipcs donne des informations sur les possibilits de communication interprocessus, y compris les segments de mmoire partage. Utilisez loption -m pour obtenir des informations sur la mmoire partage. Par exemple, ce code illustre le fait quun segment de mmoire partage, numrot 1627649, est utilis :
% ipcs -m ------ Segments de mmoire partage -------cl shmid propritaire perms octets nattch tat 0x00000000 1627649 user 640 25600 0

Si ce segment de mmoire avait t oubli par erreur par un programme, vous pouvez utiliser la commande ipcrm pour le supprimer.
% ipcrm shm 1627649

5.1.8

Avantages et Inconvnients

Les segments de mmoire partage permettent une communication bidirectionnelle rapide entre nimporte quel nombre de processus. Chaque utilisateur peut la fois lire et crire, mais un programme doit dnir et suivre un protocole pour viter les conditions de concurrence critique comme craser des informations avant quelles ne soient lues. Malheureusement, Linux ne garantit pas strictement laccs exclusif mme si vous crez un nouveau segment partag avec IPC_PRIVATE. De plus, lorsque plusieurs processus utilisent un segment de mmoire partage, ils doivent sarranger pour utiliser la mme cl.

5.2

Smaphores de Processus

Nous lavons voqu dans la section prcdente, les processus doivent se coordonner pour accder la mmoire partage. Comme nous lavons dit dans la Section 4.4.5, Smaphores pour les Threads , du Chapitre 4, Threads , les smaphores sont des compteurs qui permettent la synchronisation de plusieurs threads. Linux fournit une implmentation alternative distincte de smaphores qui peuvent tre utiliss pour synchroniser les processus (appels smaphores de processus ou parfois smaphores System V). Les smaphores de processus sont instancis, utiliss et librs comme les segments de mmoire partage. Bien quun seul smaphore soit susant pour quasiment toutes les utilisations, les smaphores de processus sont regroups en ensembles. Tout au long de cette section, nous prsentons les appels systme relatifs aux smaphores de processus, en montrant comment implmenter des smaphores binaires isols les utilisant.

5.2.1

Instanciation et Libration

Les appels semget et semctl instancient et librent des smaphores, ils sont analogues shmget et shmctl pour la mmoire partage. Invoquez semget avec une cl correspondant un ensemble de smaphores, le nombre de smaphores dans lensemble et des indicateurs de

5.2. SMAPHORES DE PROCESSUS

91

permissions, comme pour shmget ; la valeur de retour est un identiant densemble de smaphores. Vous pouvez obtenir lidentiant dun ensemble de smaphores existant en passant la bonne valeur de cl ; dans ce cas, le nombre de smaphores peut tre zro. Les smaphores continuent exister mme aprs que tous les processus les utilisant sont termins. Le dernier processus utiliser un ensemble de smaphores doit le supprimer explicitement an de sassurer que le systme dexploitation ne tombe pas court de smaphores. Pour cela, invoquez semctl avec lidentiant de lensemble de smaphores, le nombre de smaphores quil contient, IPC_RMID en troisime argument et nimporte quelle valeur dunion semun comme quatrime argument (qui est ignor). Lidentiant dutilisateur eectif du processus appelant doit correspondre celui de linstanciateur du smaphore (ou lappelant doit avoir les droits root). Contrairement aux segments de mmoire partage, la suppression dun jeu de smaphores provoque sa libration immdiate par Linux. Le Listing 5.2 prsente les fonctions dallocation et de libration dun smaphore binaire. Listing 5.2 (sem_all_deall.c) Allouer et Librer un Smaphore Binaire
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

# include < sys / ipc .h > # include < sys / sem .h > # include < sys / types .h > /* Nous devons dfinir l union semun nous - mmes . */ union semun { int val ; struct semid_ds * buf ; u n s i g n e d short int * array ; struct seminfo * __buf ; }; /* Obtient l identifiant d un smaphore binaire , l alloue si ncessaire . */ int b i n a r y _ s e m a p h o r e _ a l l o c a t i o n ( key_t key , int sem_flags ) { return semget ( key , 1 , sem_flags ) ; }

/* Libre un smaphore binaire . Tous les utilisateurs doivent avoir fini de s en servir . Renvoie -1 en cas d chec . */ int b i n a r y _ s e m a p h o r e _ d e a l l o c a t e ( int semid ) { union semun i g n o r e d _ a r g u m e n t ; return semctl ( semid , 1 , IPC_RMID , i g n o r e d _ a r g u m e n t ) ; }

5.2.2

Initialisation des Smaphores

Linstanciation et linitialisation des smaphores sont deux oprations distinctes. Pour initialiser un smaphore, utilisez semctl en lui passant zro comme second argument et SETALL comme troisime paramtre. Pour le quatrime argument, vous devez crer un objet union semun et faire pointer son champ array vers un tableau de valeurs unsigned short. Chaque valeur est utilise pour initialiser un smaphore dans lensemble.

92

CHAPITRE 5. COMMUNICATION INTERPROCESSUS Le Listing 5.3 prsente une fonction initialisant un smaphore binaire. Listing 5.3 (sem_init.c) Initialiser un Smaphore Binaire
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

# include < sys / types .h > # include < sys / ipc .h > # include < sys / sem .h > /* Nous devons dfinir l union semun nous - mmes . */ union semun { int val ; struct semid_ds * buf ; u n s i g n e d short int * array ; struct seminfo * __buf ; }; /* Initialise un smaphore binaire avec une valeur de 1. */ int b i n a r y _ s e m a p h o r e _ i n i t i a l i z e ( int semid ) { union semun argument ; u n s i g n e d short values [1]; values [0] = 1; argument . array = values ; return semctl ( semid , 0 , SETALL , argument ) ; }

5.2.3

Oprations dAttente et de Rveil

Chaque smaphore contient une valeur positive ou nulle et supporte des oprations dattente et de rveil. Lappel systme semop implmente les deux oprations. Son premier paramtre est lidentiant dun ensemble de smaphores. Son second paramtre est un tableau dlments struct sembuf qui dnit les oprations que vous voulez accomplir. Le troisime paramtre est la taille de ce tableau. Les champs de la struct sembuf sont les suivants : sem_num est le numro du smaphore dans lensemble sur lequel est eectue lopration. sem_op est un entier spciant lopration accomplir. Si sem_op est un entier positif, ce chire est ajout la valeur du smaphore immdiatement. Si sem_op est un nombre ngatif, la valeur absolue de ce chire est soustraite de la valeur du smaphore. Si cela devait rendre la valeur du smaphore ngative, lappel est bloquant jusqu ce que la valeur du smaphore atteigne la valeur absolue de sem_op (par le biais dincrmentations eectues par dautres processus). Si sem_op est zro, lopration est bloquante jusqu ce que la valeur atteigne zro. sem_flg est un indicateur. Positionnez-le IPC_NOWAIT pour viter que lopration ne soit bloquante ; au lieu de cela, lappel semop choue si elle devait ltre. Si vous le positionnez SEM_UNDO, Linux annule automatiquement lopration sur le smaphore lorsque le processus se termine. Le Listing 5.4 illustre les oprations dattente et de rveil pour un smaphore binaire.

5.3. MMOIRE MAPPE Listing 5.4 (sem_pv.c) Attente et Rveil pour un Smaphore Binaire
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33

93

# include < sys / types .h > # include < sys / ipc .h > # include < sys / sem .h > /* Se met en attente sur un smaphore binaire . Bloque jusqu ce que la valeur du smaphore soit positive , puis le dcrmente d une unit . */ int b i n a r y _ s e m a p h o r e _ w a i t ( int semid ) { struct sembuf operations [1]; /* Utilise le premier ( et unique ) smaphore . */ operations [0]. sem_num = 0; /* Dcrmente d une unit . */ operations [0]. sem_op = -1; /* Autorise l annulation . */ operations [0]. sem_flg = SEM_UNDO ; return semop ( semid , operations , 1) ; } /* Envoie un signal de rveil un smaphore binaire : incrmente sa valeur d une unit . Sort de la fonction immdiatement . */ int b i n a r y _ s e m a p h o r e _ p o s t ( int semid ) { struct sembuf operations [1]; /* Utilise le premier ( et unique ) smaphore . */ operations [0]. sem_num = 0; /* Incrmente d une unit . */ operations [0]. sem_op = 1; /* Autorise l annulation . */ operations [0]. sem_flg = SEM_UNDO ; return semop ( semid , operations , 1) ; }

Passer lindicateur SEM_UNDO permet de traiter le problme de la n dun processus alors quil dispose de ressources alloues via un smaphore. Lorsquun processus se termine, volontairement ou non, la valeur du smaphore est automatiquement ajuste pour annuler les actions du processus sur le smaphore. Par exemple, si un processus qui a dcrment le smaphore est tu, la valeur du smaphore est incrmente.

5.2.4

Dbogage des Smaphores

Utilisez la commande ipcs -s pour acher des informations sur les ensembles de smaphores existants. Utilisez la commande ipcrm sem pour supprimer un ensemble de smaphores depuis la ligne de commande. Par exemple, pour supprimer lensemble de smaphores ayant lidentiant 5790517, utilisez cette commande :
% ipcrm sem 5790517

5.3

Mmoire Mappe

La mmoire mappe permet dirents processus de communiquer via un chier partag. Bien que vous puissiez concevoir lutilisation de mmoire mappe comme tant celle dun

94

CHAPITRE 5. COMMUNICATION INTERPROCESSUS

segment de mmoire partage avec un nom, vous devez tre conscient quil existe des dirences techniques. La mmoire mappe peut tre utilise pour la communication interprocessus ou comme un moyen pratique daccder au contenu dun chier. La mmoire mappe cre une correspondance entre un chier et la mmoire dun processus. Linux divise le chier en fragments de la taille dune page puis les copie dans des pages de mmoire virtuelle an quelles puissent tre disponibles au sein de lespace dadressage dun processus. Donc le processus peut lire le contenu du chier par le biais daccs mmoire classiques. Cela permet un accs rapide aux chiers. Vous pouvez vous reprsenter la mmoire mappe comme lallocation dun tampon contenant la totalit dun chier, la lecture du chier dans le tampon, puis (si le tampon est modi) lcriture de celui-ci dans le chier. Linux gre les oprations de lecture et dcriture votre place. Il existe dautres utilisations des chiers de mmoire mappe que la communication interprocessus. Quelques unes dentre elles sont traites dans la Section 5.3.5, Autres utilisations de mmap .

5.3.1

Mapper un Fichier Ordinaire

Pour mettre en correspondance un chier ordinaire avec la mmoire dun processus, utilisez lappel mmap ( Memory MAPped , Mmoire mappe, prononcez em-map ). Le premier argument est ladresse laquelle vous dsirez que Linux mette le chier en correspondance au sein de lespace dadressage de votre processus ; la valeur NULL permet Linux de choisir une adresse de dpart disponible. Le second argument est la longueur de lespace de correspondance en octets. Le troisime argument dnit la protection de lintervalle dadresses mis en correspondance. La protection consiste en un ou binaire entre PROT_READ, PROT_WRITE et PROT_EXEC, correspondant aux permissions de lecture, dcriture et dexcution, respectivement. Le quatrime argument est un drapeau spciant des options supplmentaires. Le cinquime argument est un descripteur de chier pointant vers le chier mettre en correspondance, ouvert en lecture. Le dernier argument est le dplacement, par rapport au dbut du chier, partir duquel commencer la mise en correspondance. Vous pouvez mapper tout ou partie du chier en mmoire en choisissant le dplacement et la longueur de faon approprie. Le drapeau est un ou binaire entre ces contraintes : MAP_FIXED Si vous utilisez ce drapeau, Linux utilise ladresse que vous demandez pour mapper le chier plutt que de la considrer comme une indication. Cette adresse doit tre aligne sur une page. MAP_PRIVATE Les critures en mmoire ne doivent pas tre rpercutes sur le chier mis en correspondance, mais sur une copie prive du chier. Aucun autre processus ne voit ces critures. Ce mode ne doit pas tre utilis avec MAP_SHARED. MAP_SHARED Les critures sont immdiatement rpercutes sur le chier mis en correspondance. Utilisez ce mode lorsque vous utilisez la mmoire mappe pour lIPC. Ce mode ne doit pas tre utilis avec MAP_PRIVATE. Si lappel se droule avec succs, la fonction renvoie un pointeur vers le dbut de la mmoire. Sil choue, la fonction renvoie MAP_FAILED.

5.3. MMOIRE MAPPE

95

5.3.2

Programmes Exemples

Examinons deux programmes pour illustrer lutilisation des rgions de mmoire mappe pour lire et crire dans des chiers. Le premier programme, le Listing 5.5, gnre un nombre alatoire et lcrit dans un chier mapp en mmoire. Le second programme, le Listing 5.6, lit le nombre, lache et le remplace par le double de sa valeur. Tous deux prennent en argument de ligne de commande le nom du chier mettre en correspondance avec la mmoire. Listing 5.5 (mmap-write.c) crit un Nombre Alatoire dans un Fichier Mapp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40

# include < stdlib .h > # include < stdio .h > # include < fcntl .h > # include < sys / mman .h > # include < sys / stat .h > # include < time .h > # include < unistd .h > # define FILE_LENGTH 0 x100 /* Renvoie un nombre alatoire compris dans l intervalle [ low , high ]. int random_range ( u n s i g n e d const low , u n s i g n e d const high ) { u n s i g n e d const range = high - low + 1; return low + ( int ) ((( double ) range ) * rand () / ( RAND_MAX + 1.0) ) ; } int main ( int argc , char * const argv []) { int fd ; void * file_memory ; /* Initialise le gnrateur de nombres alatoires . srand ( time ( NULL ) ) ; */

*/

/* Prpare un fichier suffisamment long pour contenir le nombre . */ fd = open ( argv [1] , O_RDWR | O_CREAT , S_IRUSR | S_IWUSR ) ; lseek ( fd , FILE_LENGTH +1 , SEEK_SET ) ; write ( fd , " " , 1) ; lseek ( fd , 0 , SEEK_SET ) ; /* Met en co rr es pon da nce le fichier et la mmoire . */ file_memory = mmap (0 , FILE_LENGTH , PROT_WRITE , MAP_SHARED , fd , 0) ; close ( fd ) ; /* Ecrit un entier alatoire dans la zone mise en co rr es pon da nce . */ sprintf (( char *) file_memory , " % d \ n " , random_range ( -100 , 100) ) ; /* Libre la mmoire ( facultatif car le programme se termine ) . */ munmap ( file_memory , FILE_LENGTH ) ; return 0; }

Le programme mmap-write ouvre le chier, le crant sil nexiste pas. Le second argument de open indique que le chier est ouvert en lecture et criture. Comme nous ne connaissons pas la taille du chier, nous utilisons lseek pour nous assurer quil est susamment grand pour stocker un entier puis nous nous replaons au dbut du chier. Le programme met en correspondance le chier et la mmoire puis ferme le chier car il nest plus utile. Il crit ensuite un entier alatoire dans la mmoire mappe, et donc dans le chier, puis

96

CHAPITRE 5. COMMUNICATION INTERPROCESSUS

libre la mmoire. Lappel munmap nest pas ncessaire car Linux supprimerait automatiquement la mise en correspondance la n du programme. Listing 5.6 (mmap-read.c) Lit un Entier Depuis un Fichier mis en correspondance avec la Mmoire et le Multiplie par Deux
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

# include < stdlib .h > # include < stdio .h > # include < fcntl .h > # include < sys / mman .h > # include < sys / stat .h > # include < unistd .h > # define FILE_LENGTH 0 x100 int main ( int argc , char * const argv []) { int fd ; void * file_memory ; int integer ; /* Ouvre le fichier . */ fd = open ( argv [1] , O_RDWR , S_IRUSR /* Met en co rr es pon da nce le fichier file_memory = mmap (0 , FILE_LENGTH , MAP_SHARED , fd , close ( fd ) ;

| S_IWUSR ) ; et la mmoire . */ PROT_READ | PROT_WRITE , 0) ;

/* Lit l entier , l affiche et le multiplie par deux . */ sscanf ( file_memory , " % d " , & integer ) ; printf ( " valeur : % d \ n " , integer ) ; sprintf (( char *) file_memory , " % d \ n " , 2 * integer ) ; /* Libre la mmoire ( facultatif car le programme se termine ) . */ munmap ( file_memory , FILE_LENGTH ) ; return 0; }

Le programme mmap-read lit le nombre partir du chier puis y crit son double. Tout dabord, il ouvre le chier et le met en correspondance en lecture/criture. Comme nous pouvons supposer que le chier est susamment grand pour stocker un entier non sign, nous navons pas besoin dutiliser lseek, comme dans le programme prcdent. Le programme lit la valeur partir de la mmoire en utilisant sscanf puis formate et crit le double de la valeur en utilisant sprintf. Voici un exemple de lexcution de ces programmes dexemple. Il utilise le chier /tmp/integer-file.
% ./mmap-write /tmp/integer-file % cat /tmp/integer-file 42 % ./mmap-read /tmp/integer-file valeur : 42 % cat /tmp/integer-file 84

Remarquez que le texte 42 a t crit dans le chier sur le disque sans jamais appeler write et t lu par la suite sans appeler read. Notez que ces programmes de dmonstration crivent et lisent lentier sous forme de chane (en utilisant sprintf et sscanf) dans un but dexemple uniquement il ny a aucune raison pour que le contenu dun chier mis en correspondance avec

5.3. MMOIRE MAPPE

97

la mmoire soit au format texte. Vous pouvez lire et crire de faon binaire dans un chier mis en correspondance avec la mmoire.

5.3.3

Accs Partag un Fichier

Des processus distincts peuvent communiquer en utilisant des rgions de la mmoire mises en correspondance avec le mme chier. Passez le drapeau MAP_SHARED an que toute criture dans une telle rgion soit immdiatement rpercute sur le disque et visible par les autres processus. Si vous nindiquez pas ce drapeau, Linux pourrait placer les donnes crites dans un tampon avant de les transfrer dans le chier. Une alternative est de forcer Linux intgrer les changements eectus en mmoire dans le chier en appelant msync. Ses deux premiers paramtres dnissent une rgion de la mmoire mise en correspondance avec un chier, comme pour munmap. Le troisime paramtre peut prendre les valeurs suivantes : MS_ASYNC La mise jour est planie mais pas ncessairement excute avant la n de la fonction. MS_SYNC La mise jour est immdiate ; lappel msync est bloquant jusqu ce quelle soit termine. MS_SYNC et MS_ASYNC ne peuvent tre utiliss simultanment. MS_INVALIDATE Toutes les autres mises en correspondance avec le chier sont invalides an de prendre en compte les modications. Par exemple, pour purger un chier partag mis en correspondance ladresse mem_addr et dune longueur de mem_length octets, eectuez cet appel :
msync ( mem_addr , mem_length , MS_SYNC | MS_INVALIDATE ) ;

Comme pour les segments de mmoire partage, les utilisateurs de rgions de mmoire mises en correspondance avec un chier doivent tablir et suivre un protocole an dviter les conditions de concurrence critique. Par exemple, un smaphore peut tre utilis pour viter que plus dun processus naccde la rgion de la mmoire en mme temps. Vous pouvez galement utiliser fcntl pour placer un verrou en lecture ou en criture sur le chier, comme le dcrit la Section 8.3, fcntl : Verrous et Autres Oprations sur les Fichiers , du Chapitre 8.

5.3.4

Mises en Correspondance Prives

Passer MAP_PRIVATE mmap cre une rgion en mode copie lcriture. Toute criture dans la rgion nest rpercute que dans la mmoire du processus ; les autres processus qui utilisent une mise en correspondance sur le mme chier ne voient pas les modications. Au lieu dcrire directement sur une page partage par tous les processus, le processus crit sur une copie prive de la page. Toutes les lectures et criture ultrieures du processus utilisent cette page.

5.3.5

Autres Utilisations de mmap

Lappel mmap peut tre utilis dans dautres buts que la communication interprocessus. Une utilisation courante est de le substituer read et write. Par exemple, plutt que de charger explicitement le contenu dun chier en mmoire, un programme peut mettre le chier en correspondance avec la mmoire et lanalyser via des lectures en mmoire. Pour certains

98

CHAPITRE 5. COMMUNICATION INTERPROCESSUS

programmes, cette faon de faire est plus pratique et peut galement tre plus rapide que des oprations dentres/sorties explicites sur le chier. Une technique puissante utilise par certains programmes consiste fabriquer des structures de donnes (des instances struct ordinaires, par exemple) dans un chier mis en correspondance avec la mmoire. Lors dune invocation ultrieure, le programme remet le chier en correspondance avec la mmoire et les structures de donnes retrouvent leur tat prcdent. Notez cependant que les pointeurs de ces structures de donnes seront invalides moins quils ne pointent vers des adresses au sein de la mme rgion de mmoire et que le chier soit bien mis en correspondance la mme adresse mmoire quinitialement. Une autre technique utile est de mettre le chier spcial /dev/zero en correspondance avec la mmoire. Ce chier, dcrit dans la Section 6.5.2, /dev/zero , du Chapitre 6, Priphriques , se comporte comme sil tait un chier de taille innie rempli doctets zro. Un programme ayant besoin dune source doctets zro peut appeler mmap pour le chier /dev/zero. Les critures sur /dev/zero sont ignores, donc la mmoire mise en correspondance peut tre utilise pour nimporte quelle opration. Les distributeurs de mmoire personnaliss utilisent souvent /dev/zero pour obtenir des portions de mmoire prinitialises.

5.4

Tubes

Un tube est un dispositif de communication qui permet une communication sens unique. Les donnes crites sur l extrmit dcriture du tube sont lues depuis l extrmit de lecture . Les tubes sont des dispositifs squentiels ; les donnes sont toujours lues dans lordre o elles ont t crites. Typiquement, un tube est utilis pour la communication entre deux threads dun mme processus ou entre processus pre et ls. Dans un shell, le symbole | cre un tube. Par exemple, cette commande provoque la cration par le shell de deux processus ls, lun pour ls et lautre pour less :
% ls | less

Le shell cre galement un tube connectant la sortie standard du processus ls avec lentre standard de less. Les noms des chiers lists par ls sont envoys less dans le mme ordre que sils taient envoys directement au terminal. La capacit dun tube est limite. Si le processus crivain crit plus vite que la vitesse laquelle le processus lecteur consomme les donnes, et si le tube ne peut pas contenir de donnes supplmentaires, le processus crivain est bloqu jusqu ce quil y ait nouveau de la place dans le tube. Si le lecteur essaie de lire mais quil ny a plus de donnes disponibles, il est bloqu jusqu ce que ce ne soit plus le cas. Ainsi, le tube synchronise automatiquement les deux processus.

5.4.1

Crer des Tubes

Pour crer un tube, appelez la fonction pipe. Passez lui un tableau de deux entiers. Lappel pipe stocke le descripteur de chier en lecture lindice zro et le descripteur de chier en criture lindice un. Par exemple, examinons ce code :

5.4. TUBES
int pipe_fds [2]; int read_fd ; int write_fd ; pipe ( pipe_fds ) ; read_fd = pipe_fds [0]; write_fd = pipe_fds [1];

99

Les donnes crites via le descripteur write_fd peuvent tre relues via read_fd.

5.4.2

Communication entre Processus Pre et Fils

Un appel pipe cre des descripteurs de chiers qui ne sont valide quau sein du processus appelant et de ses ls. Les descripteurs de chiers dun processus ne peuvent tre transmis des processus qui ne lui sont pas lis ; cependant, lorsquun processus appelle fork, les descripteurs de chiers sont copis dans le nouveau processus. Ainsi, les tubes ne peuvent connecter que des processus lis. Dans le programme du Listing 5.7, un fork cre un nouveau processus ls. Le ls hrite des descripteurs de chiers du tube. Le pre crit une chane dans le tube et le ls la lit. Le programme exemple convertit ces descripteurs de chiers en ux FILE* en utilisant fdopen. Comme nous utilisons des ux plutt que des descripteurs de chiers, nous pouvons utiliser des fonctions dentres/sorties de la bibliothque standard du C de plus haut niveau, comme printf et fgets. Listing 5.7 (pipe.c) Utiliser un Tube pour Communiquer avec un Processus Fils
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

# include < stdlib .h > # include < stdio .h > # include < unistd .h > /* crit COUNT fois entre chaque . */ MESSAGE vers STREAM , avec une pause d une seconde

void writer ( const char * message , int count , FILE * stream ) { for (; count > 0; -- count ) { /* crit le message vers le flux et purge immdiatement . */ fprintf ( stream , " % s \ n " , message ) ; fflush ( stream ) ; /* S arrte un instant . */ sleep (1) ; } } /* Lit des chanes alatoires depuis le flux aussi longtemps que possible . */ void reader ( FILE * stream ) { char buffer [1024]; /* Lit jusqu ce que l on atteigne la fin du flux . fgets lit jusqu ce qu une nouvelle ligne ou une fin de fichier survienne . */ while (! feof ( stream ) && ! ferror ( stream ) && fgets ( buffer , sizeof ( buffer ) , stream ) != NULL ) fputs ( buffer , stdout ) ;

100
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68

CHAPITRE 5. COMMUNICATION INTERPROCESSUS


} int main () { int fds [2]; pid_t pid ; /* Cre un tube . Les descripteurs de fichiers pour les deux bouts du tube sont placs dans fds . */ pipe ( fds ) ; /* Cre un processus fils . */ pid = fork () ; if ( pid == ( pid_t ) 0) { FILE * stream ; /* Nous sommes dans le processus fils . On ferme notre copie de l extrmit en criture du descripteur de fichiers . */ close ( fds [1]) ; /* Convertit le descripteur de fichier de lecture en objet FILE et lit partir de celui - ci . */ stream = fdopen ( fds [0] , " r " ) ; reader ( stream ) ; close ( fds [0]) ; } else { /* Nous sommes dans le processus parent . */ FILE * stream ; /* Ferme notre copie de l extrmit en lecture du descripteur de fichier . */ close ( fds [0]) ; /* Convertit le descripteur de fichier d criture en objet FILE et y crit des donnes . */ stream = fdopen ( fds [1] , " w " ) ; writer ( " Coucou . " , 5 , stream ) ; close ( fds [1]) ; } return 0; }

Au dbut de la fonction main, fds est dclar comme tant un tableau de deux entiers. Lappel pipe cre un tube et place les descripteurs en lecture et en criture dans ce tableau. Le programme cre alors un processus ls. Aprs avoir ferm lextrmit en lecture du tube, le processus pre commence crire des chanes dans le tube. Aprs avoir ferm lextrmit en criture du tube, le processus ls lit les chanes depuis le tube. Notez quaprs lcriture dans la fonction writer, le pre purge le tube en appelant fflush. Dans le cas contraire, les donnes pourraient ne pas tre envoyes dans le tube immdiatement. Lorsque vous invoquez la commande ls | less, deux divisions de processus ont lieu : une pour le processus ls ls et une pour le processus ls less. Ces deux processus hritent des descripteurs de chier du tube an quil puissent communiquer en lutilisant. Pour faire communiquer des processus sans lien, utilisez plutt des FIFO, comme le dcrit la Section 5.4.5, FIFO .

5.4. TUBES

101

5.4.3

Rediriger les Flux dEntre, de Sortie et dErreur Standards

Souvent, vous aurez besoin de crer un processus ls et de dnir lextrmit dun tube comme son entre ou sa sortie standard. Grce la fonction dup2, vous pouvez substituer un descripteur de chier un autre. Par exemple, pour rediriger lentre standard vers un descripteur de chier fd, utilisez ce code :
dup2 ( fd , STDIN_FILENO ) ;

La constante symbolique STDIN_FILENO reprsente le descripteur de chier de lentre standard, qui a la valeur 0. Lappel ferme lentre standard puis la rouvre comme une copie de fd de faon ce que les deux puissent tre utiliss indiremment. Les descripteurs de chiers substitus partagent la mme position dans le chier et le mme ensemble dindicateurs de statut de chier. Ainsi, les caractres lus partir de fd ne sont pas relus partir de lentre standard. Le programme du Listing 5.8 utilise dup2 pour envoyer des donne dun tube vers la commande sort2 . Une fois le tube cr, le programme se divise. Le processus parent envoie quelques chanes vers le tube. Le processus ls connecte le descripteur de chier en lecture du tube son entre standard en utilisant dup2. Il excute ensuite le programme sort. Listing 5.8 (dup2.c) Rediriger la Sortie dun Tube avec dup2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 2

# include # include # include # include

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

int main () { int fds [2]; pid_t pid ; /* Cre un tube . Les descripteurs de fichiers des deux extrmits du tube sont places dans fds . */ pipe ( fds ) ; /* Cre un processus fils . */ pid = fork () ; if ( pid == ( pid_t ) 0) { /* Nous sommes dans le processus fils . On ferme notre copie du descripteur de fichier en criture . */ close ( fds [1]) ; /* Connexion de l extrmit en lecture l entre standard . */ dup2 ( fds [0] , STDIN_FILENO ) ; /* Remplace le processus fils par le programme " sort ". */ execlp ( " sort " , " sort " , 0) ; } else { /* Processus pre . */ FILE * stream ; /* Ferme notre copie de l extrmit en lecture du descripteur . */ close ( fds [0]) ; /* Convertit le descripteur de fichier en criture en objet FILE , et y crit . */ stream = fdopen ( fds [1] , " w " ) ; fprintf ( stream , " C est un test .\ n " ) ;

sort lit des lignes de texte depuis lentre standard, les trie par ordre alphabtique et les ache sur la sortie standard.

102
34 35 36 37 38 39 40 41 42 43 44 45

CHAPITRE 5. COMMUNICATION INTERPROCESSUS


fprintf ( stream , " Coucou .\ n " ) ; fprintf ( stream , " Mon chien a des puces .\ n " ) ; fprintf ( stream , " Ce programme est excellent .\ n " ) ; fprintf ( stream , " Un poisson , deux poissons .\ n " ) ; fflush ( stream ) ; close ( fds [1]) ; /* Attend la fin du processus fils . */ waitpid ( pid , NULL , 0) ; } return 0; }

5.4.4

popen et pclose

Une utilisation courante des tubes est denvoyer ou de recevoir des donnes depuis un programme en cours dexcution dans un sous-processus. Les fonctions popen et pclose facilitent cette pratique en liminant le besoin dappeler pipe, fork, dup2, exec et fdopen. Comparez le Listing 5.9, qui utilise popen et pclose, lexemple prcdent (Listing 5.8). Listing 5.9 (popen.c) Exemple dUtilisation de popen
1 2 3 4 5 6 7 8 9 10 11 12 13

# include < stdio .h > # include < unistd .h > int main () { FILE * stream = popen ( " sort " , " w " ) ; fprintf ( stream , " C est un test .\ n " ) ; fprintf ( stream , " Coucou .\ n " ) ; fprintf ( stream , " Mon chien a des puces .\ n " ) ; fprintf ( stream , " Ce programme est excellent .\ n " ) ; fprintf ( stream , " Un poisson , deux poissons .\ n " ) ; return pclose ( stream ) ; }

Lappel popen cre un processus ls excutant la commande sort, ce qui remplace les appels pipe, fork, dup2 et execlp. Le second argument, "w", indique que ce processus dsire crire au processus ls. La valeur de retour de popen est lextrmit dun tube ; lautre extrmit est connecte lentre standard du processus ls. Une fois que le processus pre a termin dcrire, pclose ferme le ux du processus ls, attend la n du processus et renvoie son code de retour. Le premier argument de popen est excut comme sil sagissait dune commande shell, dans un processus excutant /bin/sh. Le shell recherche les programmes excuter en utilisant la variable denvironnement PATH de la faon habituelle. Si le deuxime argument est "r", la fonction renvoie le ux de sortie standard du processus ls an que le pre puisse lire la sortie. Si le second argument est "w", la fonction renvoie le ux dentre standard du processus ls an que le pre puisse envoyer des donnes. Si une erreur survient, popen renvoie un pointeur nul. Appelez pclose pour fermer un ux renvoyer par popen. Aprs avoir ferm le ux indiqu, pclose attend la n du processus ls.

5.4. TUBES

103

5.4.5

FIFO

Une le premier entr, premier sorti (rst-in, rst-out, FIFO) est un tube qui dispose dun nom dans le systme de chiers. Tout processus peut ouvrir ou fermer la FIFO ; les processus raccords aux extrmits du tube nont pas avoir de lien de parent. Les FIFO sont galement appels canaux nomms. Vous pouvez crer une FIFO via la commande mkfifo. Indiquez lemplacement o elle doit tre cre sur la ligne de commande. Par exemple, crez une FIFO dans /tmp/fifo en invoquant ces commandes :
% mkfifo /tmp/fifo % ls -l /tmp/fifo prw-rw-rw1 samuel users 0 Jan 16 14:04 /tmp/fifo

Le premier caractre ach par ls est p ce qui indique que le chier est en fait une FIFO (canal nomm, named pipe). Dans une fentre, lisez des donnes depuis la FIFO en invoquant cette commande :
% cat < /tmp/fifo

Dans une deuxime fentre, crivez dans la FIFO en invoquant cela :


% cat > /tmp/fifo

Puis, saisissez du texte. chaque fois que vous appuyez sur Entre, la ligne de texte est envoy dans la FIFO et apparat dans la premire fentre. Fermez la FIFO en appuyant sur Ctrl+D dans la seconde fentre. Supprimez la FIFO avec cette commande :
% rm /tmp/fifo

Crer une FIFO Pour crer une FIFO par programmation, utilisez la fonction mkfifo. Le premier argument est lemplacement o crer la FIFO ; le second paramtre spcie les permissions du propritaire du tube, de son groupe et des autres utilisateurs, comme le dcrit le Chapitre 10, Scurit , Section 10.3, Permissions du Systme de Fichiers . Comme un tube doit avoir un lecteur et un crivain, les permissions doivent comprendre des autorisations en lecture et en criture. Si le tube ne peut pas tre cr (par exemple, si un chier possdant le mme nom existe dj), mkfifo renvoie -1. Incluez <sys/types.h> et <sys/stat.h> si vous appelez mkfifo. Accder une FIFO Laccs une FIFO se fait de la mme faon que pour un chier ordinaire. Pour communiquer via une FIFO, un programme doit louvrir en criture. Il est possible dutiliser des fonction dE/S de bas niveau (open, write, read, close, etc. listes dans lAppendice B, E/S de Bas Niveau ) ou des fonctions dE/S de la bibliothque C (fopen, fprintf, fscanf, fclose, etc.). Par exemple, pour crire un tampon de donnes dans une FIFO en utilisant des routines de bas niveau, vous pourriez procder comme suit :

104

CHAPITRE 5. COMMUNICATION INTERPROCESSUS


int fd = open ( fifo_path , O_WRONLY ) ; write ( fd , data , data_length ) ; close ( fd ) ;

Pour lire une chane depuis la FIFO en utilisant les fonctions dE/S de la bibliothque C, vous pourriez utiliser ce code :
FILE * fifo = fopen ( fifo_path , " r " ) ; fscanf ( fifo , " % s " , buffer ) ; fclose ( fifo ) ;

Une FIFO peut avoir plusieurs lecteurs ou plusieurs crivains. Les octets de chaque crivain sont crits de faon atomique pour une taille infrieure PIPE_BUF (4Ko sous Linux). Les paquets dcrivains en accs simultans peuvent tre entrelacs. Les mmes rgles sappliquent des lectures concurrentes. Dirences avec les Canaux Nomms Windows Les tubes des systmes dexploitation Win32 sont trs similaires aux tubes Linux (consultez la documentation de la bibliothque Win32 pour plus de dtails techniques). Les principales diffrences concernent les canaux nomms, qui, sous Win32, fonctionnent plus comme des sockets. Les canaux nomms Win32 peuvent connecter des processus dordinateurs distincts connects via un rseau. Sous Linux, ce sont les sockets qui sont utiliss pour ce faire. De plus, Win32 permet de multiples connexions de lecteur crivain sur un mme canal nomm sans que les donnes ne soient entrelaces et les tubes peuvent tre utiliss pour une communication double sens3 .

5.5

Sockets

Un socket est un dispositif de communication bidirectionnel pouvant tre utilis pour communiquer avec un autre processus sur la mme machine ou avec un processus sexcutant sur dautres machines. Les sockets sont la seule forme de communication interprocessus dont nous traiterons dans ce chapitre qui permet la communication entre processus de direntes machines. Les programmes Internet comme Telnet, rlogin, FTP, talk et le World Wide Web utilisent des sockets. Par exemple, vous pouvez obtenir une page depuis un serveur Web en utilisant le programme Telnet car tous deux utilisent des sockets pour la communication via le rseau4 . Pour ouvrir une connexion vers un serveur Web dont ladresse est www.codesourcery.com, utilisez la commande telnet www.codesourcery.com 80. La constante magique 80 demande une connexion au serveur Web de www.codesourcery.com plutt qu un autre processus. Essayez dentrer GET / une fois la connexion tablie. Cela envoie un message au serveur Web travers le socket, celui-ci
Notez que seul Windows NT permet la cration de canaux nomms ; les programmes pour Windows 9x ne peuvent crer que des connexions client. 4 Habituellement, vous utilisez telnet pour vous connecter un serveur Telnet pour une identication distance. Mais vous pouvez galement utiliser telnet pour vous connecter un autre type de serveur et lui envoyer des commandes directement.
3

5.5. SOCKETS

105

rpond en envoyant la source HTML de la page daccueil puis en fermant la connexion par exemple :
% telnet www.codesourcery.com 80 Trying 206.168.99.1... Connected to merlin.codesourcery.com (206.168.99.1). Escape character is "^]". GET / <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> ...

5.5.1

Concepts relatifs aux Sockets

Lorsque vous crez un socket, vous devez indiquer trois paramtres : le style de communication, lespace de nommage et le protocole. Un style de communication contrle la faon dont le socket traite les donnes transmises et dnit le nombre dinterlocuteurs. Lorsque des donnes sont envoyes via le socket, elles sont dcoupes en morceaux appels paquets. Le style de communication dtermine comment sont grs ces paquets et comment ils sont envoys de lmetteur vers le destinataire. Le style connexion garantit la remise de tous les paquets dans leur ordre dmission. Si des paquets sont perdus ou mlangs cause de problmes dans le rseau, le destinataire demande automatiquement leur retransmission lmetteur. Un socket de type connexion ressemble un appel tlphonique : les adresses de lmetteur et du destinataire sont xes au dbut de la communication lorsque la connexion est tablie. Le style datagramme ne garantit pas la remise ou lordre darrive des paquets. Des paquets peuvent tre perdus ou mlangs cause de problmes dans le rseau. Chaque paquet doit tre associ sa destination et il ny a aucune garantie quant sa remise. Le systme ne garantit que le meilleur eort (best eort), des paquets peuvent donc tre perdus ou tre remis dans un ordre dirent de leur mission. Un socket de style datagramme se comporte plus comme une lettre postale. Lmetteur spcie ladresse du rcepteur pour chaque message. Lespace de nommage dun socket spcie comment les adresses de socket sont crites. Une adresse de socket identie lextrmit dune connexion par socket. Par exemple, les adresses de socket dans lespace de nommage local sont des noms de chiers ordinaires. Dans lespace de nommage Internet , une adresse de socket est compose de ladresse Internet (galement appele adresse IP) dun hte connect au rseau et dun numro de port. Le numro de port permet de faire la distinction entre plusieurs sockets sur le mme hte. Un protocole spcie comment les donnes sont transmises. Parmi ces protocoles, on peut citer TCP/IP ; les deux protocoles principaux utiliss pour Internet, le protocole rseau AppleTalk ; et le protocole de communication locale dUNIX. Toutes les combinaisons de styles, despace de nommage et de protocoles ne sont pas supportes.

106

CHAPITRE 5. COMMUNICATION INTERPROCESSUS

5.5.2

Appels Systme

Les sockets sont plus exibles que les techniques de communication cites prcdemment. Voici les appels systmes utiles lors de lutilisation de sockets : socket Cre un socket. close Dtruit un socket. connect Cre une connexion entre deux sockets. bind Associe un socket serveur une adresse. listen Congure un socket an quil accepte les connexions. accept Accepte une connexion et cre un nouveau socket pour celle-ci. Les sockets sont reprsents par des descripteurs de chiers. Crer et Dtruire des Sockets Les fonctions socket et close crent et dtruisent des sockets, respectivement. Lorsque vous crez un socket, spciez ses trois proprits : espace de nommage, style de communication et protocole. Pour le paramtre despace de nommage, utilisez les constantes commenant par PF_ (abrviation de protocol family , famille de protocoles). Par exemple, PF_LOCAL ou PF_UNIX spcient lespace de nommage local et PF_INET correspond lespace de nommage Internet. Pour le paramtre du style de communication, utilisez les constantes commenant par SOCK_. SOCK_STREAM demande un socket de style connexion, SOCK_DGRAM un socket de style datagramme. Le troisime paramtre, le protocole, spcie le mcanisme de bas niveau utilis pour transmettre et recevoir des donnes. Chaque protocole est valide pour une combinaison despace de nommage et de type de communication particulire. Comme il y a souvent un protocole adapt pour chaque paire, indiquer 0 slectionne gnralement le bon protocole. Si lappel socket se droule correctement, il renvoie un descripteur de chier pour le socket. Vous pouvez lire ou crire sur un socket en utilisant read, write, etc. comme avec les autres descripteurs de chiers. Lorsque vous en avez ni avec le socket, appelez close pour le supprimer. Appeler connect Pour crer une connexion entre deux sockets, le client appelle connect, en lui passant ladresse dun socket serveur auquel se connecter. Un client est un processus initiant une connexion et un serveur est un processus en attente de connexions. Le client appelle connect pour initier une connexion depuis un socket local, dont le descripteur est pass en premier argument, vers le socket serveur spci par le deuxime argument. Le troisime argument est la longueur, en octets, de la structure dadresse pointe par le second argument. Les formats dadresse de sockets dirent selon lespace de nommage du socket. Envoyer des Informations Toutes les techniques valides pour crire dans un descripteur de chier le sont galement pour crire dans un socket. Reportez-vous lAppendice B pour une prsentation des fonctions dE/S de bas niveau Linux et de certains des problmes ayant trait leur utilisation. La fonction send,

5.5. SOCKETS

107

qui est spcique aux descripteur de chiers socket, est une alternative write avec quelques choix supplmentaires ; reportez-vous sa page de manuel pour plus dinformations.

5.5.3

Serveurs

Le cycle de vie dun serveur consiste crer un socket de type connexion, le lier une adresse, appeler listen pour permettre au socket daccepter des connexions, appeler accept rgulirement pour accepter les connexions entrantes, puis fermer le socket. Les donnes ne sont pas lues et crites directement via le socket serveur ; au lieu de cela, chaque fois quun programme accepte une nouvelle connexion, Linux cre un socket spar utilis pour le transfert de donnes via cette connexion. Dans cette section, nous introduirons bind, listen et accept. Une adresse doit tre lie au socket serveur en utilisant bind an que les clients puissent le trouver. Son premier argument est le descripteur de chier du socket. Le second argument est un pointeur vers une structure dadresse de socket ; son format dpend de la famille dadresse du socket. Le troisime argument est la longueur de la structure dadresse en octets. Une fois quune adresse est lie un socket de type connexion, il doit invoquer listen pour indiquer quil agit en tant que serveur. Son premier argument est le descripteur de chier du socket. Le second spcie combien de connexions peuvent tre mise en le dattente. Si la le est pleine, les connexions supplmentaires seront refuses. Cela ne limite pas le nombre total de connexions quun serveur peut grer ; cela limite simplement le nombre de clients tentant de se connecter qui nont pas encore t accepts. Un serveur accepte une demande de connexion dun client en invoquant accept. Son premier argument est le descripteur de chier du socket. Le second pointe vers une structure dadresse de socket, qui sera renseigne avec ladresse de socket du client. Le troisime argument est la longueur, en octets, de la structure dadresse de socket. Le serveur peut utiliser ladresse du client pour dterminer sil dsire rellement communiquer avec le client. Lappel accept cre un nouveau socket pour communiquer avec le client et renvoie le descripteur de chier correspondant. Le socket serveur original continue accepter de nouvelles connexions de clients. Pour lire des donnes depuis un socket sans le supprimer de la le dattente, utilisez recv. Cette fonction prend les mmes arguments que read ainsi quun argument FLAGS supplmentaire. Passer la valeur MSG_PEEK permet de lire les donnes sans les supprimer de la le dattente.

5.5.4

Sockets Locaux

Les sockets mettant en relation des processus situs sur le mme ordinateur peuvent utiliser lespace de nommage local reprsent par les constantes PF_LOCAL et PF_UNIX. Ils sont appels sockets locaux ou sockets de domaine UNIX. Leur adresse de socket, un nom de chier, nest utilise que lors de la cration de connexions. Le nom du socket est spci dans une struct sockaddr_un. Vous devez positionner le champ sun_family AF_LOCAL, qui reprsente un espace de nommage local. Le champ sun_path spcie le nom de chier utiliser et peut faire au plus 108 octets de long. La longueur relle de la struct sockaddr_un doit tre calcule en utilisant la macro SUN_LEN. Tout nom de chier peut tre utilis, mais le processus doit avoir des autorisations dcriture sur le rpertoire, qui permettent lajout de chiers. Pour se connecter un socket, un processus doit avoir des droits en lecture sur le

108

CHAPITRE 5. COMMUNICATION INTERPROCESSUS

chier. Mme si dirents ordinateurs peuvent partager le mme systme de chier, seuls des processus sexcutant sur le mme ordinateur peuvent communiquer via les sockets de lespace de nommage local. Le seul protocole permis pour lespace de nommage local est 0. Comme il est stock dans un systme de chiers, un socket local est ach comme un chier. Par exemple, remarquez le s du dbut :
% ls -l /tmp/socket srwxrwx--x 1 user group 0 Nov 13 19:18 /tmp/socket

Appelez unlink pour supprimer un socket local lorsque vous ne lutilisez plus.

5.5.5

Un Exemple Utilisant les Sockets Locaux

Nous allons illustrer lutilisation des sockets au moyen de deux programmes. Le programme serveur, Listing 5.10, cre un socket dans lespace de nommage local et attend des connexions. Lorsquil reoit une connexion, il lit des messages au format texte depuis celle-ci et les ache jusqu ce quelle soit ferme. Si lun de ces messages est quit , le programme serveur supprime le socket et se termine. Le programme socket-server prend en argument de ligne de commande le chemin vers le socket. Listing 5.10 (socket-server.c) Serveur Utilisant un Socket Local
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33

# include # include # include # include # include # include

< stdio .h > < stdlib .h > < string .h > < sys / socket .h > < sys / un .h > < unistd .h >

/* Lit du texte depuis le socket et l affiche . Continue jusqu ce que le socket soit ferm . Renvoie une valeur diffrente de zro si le client a envoy un message " quit " , zro sinon . */ int server ( int client_socket ) { while (1) { int length ; char * text ; /* Commence par lire la longueur du message texte depuis le socket . Si read renvoie zro , le client a ferm la connexion . */ if ( read ( client_socket , & length , sizeof ( length ) ) == 0) return 0; /* Alloue un tampon pour contenir le texte . */ text = ( char *) malloc ( length ) ; /* Lit le texte et l affiche . */ read ( client_socket , text , length ) ; printf ( " % s \ n " , text ) ; /* Si le client a envoy le message " quit " , c est fini . if (! strcmp ( text , " quit " ) ) { /* Libre le tampon . */ free ( text ) ; return 1; }

*/

5.5. SOCKETS
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78

109

/* Libre le tampon . */ free ( text ) ; } } int main ( int argc , char * const argv []) { const char * const socket_name = argv [1]; int socket_fd ; struct sockaddr_un name ; int c l i e n t _ s e n t _ q u i t _ m e s s a g e ; /* Cre le socket . */ socket_fd = socket ( PF_LOCAL , SOCK_STREAM , 0) ; /* Indique qu il s agit d un serveur . */ name . sun_family = AF_LOCAL ; strcpy ( name . sun_path , socket_name ) ; bind ( socket_fd , ( struct sockaddr *) & name , SUN_LEN (& name ) ) ; /* Se met en attente de connexions . */ listen ( socket_fd , 5) ; /* Accepte les connexions de faon rpte , lance un server () pour traiter chaque client . Continue jusqu ce qu un client envoie un message " quit ". */ do { struct sockaddr_un client_name ; socklen_t c l i e nt _ n a m e _l e n ; int c l i e n t _ s o c k e t _ f d ; /* Accepte une connexion . */ client_socket_fd = accept ( socket_fd , ( struct sockaddr *) & client_name , & c l ie n t _ n a me _ l e n ) ; /* Traite la connexion . */ c l i e n t _ s e n t _ q u i t _ m e s s a g e = server ( c l i e n t _ s o c k e t _ f d ) ; /* Ferme notre extrmit . */ close ( c l i e n t _ s o c k e t _ f d ) ; } while (! c l i e n t _ s e n t _ q u i t _ m e s s a g e ) ; /* Supprime le fichier socket . */ close ( socket_fd ) ; unlink ( socket_name ) ; return 0; }

Le programme client, Listing 5.11, se connecte un socket local et envoie un message. Le chemin vers le socket et le message sont indiqus sur la ligne de commande. Listing 5.11 (socket-client.c) Client Utilisant un Socket Local
1 2 3 4 5 6 7 8 9 10

# include # include # include # include # include

< stdio .h > < string .h > < sys / socket .h > < sys / un .h > < unistd .h >

/* crit TEXT vers le socket indiqu par le descripteur SOCKET_FD . */ void write_text ( int socket_fd , const char * text ) {

110
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37

CHAPITRE 5. COMMUNICATION INTERPROCESSUS


/* crit le nombre d octets de la chane , y compris l octet nul de fin . */ int length = strlen ( text ) + 1; write ( socket_fd , & length , sizeof ( length ) ) ; /* crit la chane . */ write ( socket_fd , text , length ) ; } int main ( int argc , char * const argv []) { const char * const socket_name = argv [1]; const char * const message = argv [2]; int socket_fd ; struct sockaddr_un name ; /* Cre le socket . */ socket_fd = socket ( PF_LOCAL , SOCK_STREAM , 0) ; /* Stocke le nom du serveur dans l adresse du socket . */ name . sun_family = AF_LOCAL ; strcpy ( name . sun_path , socket_name ) ; /* Se connecte au socket . */ connect ( socket_fd , ( struct sockaddr *) & name , SUN_LEN (& name ) ) ; /* crit le texte de la ligne de commande vers le socket . */ write_text ( socket_fd , message ) ; close ( socket_fd ) ; return 0; }

Avant que le client nenvoie le message, il envoie sa longueur contenue dans la variable entire length. De mme, le serveur lit la longueur du texte en lisant une variable entire depuis le socket. Cela permet au serveur dallouer un tampon de taille adquate pour contenir le message avant de le lire. Pour tester cet exemple, dmarrez le programme serveur dans une fentre. Indiquez le chemin vers le socket par exemple, /tmp/socket.
% ./socket-server /tmp/socket

Dans une autre fentre, lancez plusieurs fois le client en spciant le mme socket ainsi que des messages envoyer au client :
% ./socket-client /tmp/socket "Coucou." % ./socket-client /tmp/socket "Ceci est un test."

Le programme serveur reoit et ache les messages. Pour fermer le serveur, envoyez le message quit depuis un client :
% ./socket-client /tmp/socket "quit"

Le programme serveur se termine alors.

5.5.6

Sockets Internet

Les sockets de domaine UNIX ne peuvent tre utiliss que pour communiquer entre deux processus sexcutant sur le mme ordinateur. Les sockets Internet, par contre, peuvent tre utiliss pour connecter des processus sexcutant sur des machines distinctes connectes par un rseau.

5.5. SOCKETS

111

Les sockets connectant des processus via Internet utilisent lespace de nommage Internet reprsent par PF_INET. Le protocole le plus courant est TCP/IP. LInternet Protocol (IP), un protocole de bas niveau transporte des paquets sur Internet, en les fragmentant et les rassemblant si ncessaire. Il ne garantit que le meilleur eort de remise, des paquets peuvent donc disparatre ou tre mlangs durant le transport. Tous les ordinateurs participants sont dnis en utilisant une adresse IP unique. Le Transmission Control Protocol (TCP), qui sappuie sur IP, fournit un transport able orient connexion. Il permet dtablir des connexions semblables aux connexions tlphoniques entre des ordinateurs et assure que les donnes sont remises de faon able et dans lordre.

Noms DNS Comme il est plus facile de se souvenir de noms que de numros, le Domain Name Service (DNS, Service de Nom de Domaine) associe un nom comme www.codesourcery.com ladresse IP dun ordinateur. Le DNS est implment par une hirarchie mondiale de serveurs de noms, mais vous navez pas besoin de comprendre les protocoles employs par le DNS pour utiliser des noms dhtes Internet dans vos programmes.

Les adresses de sockets Internet comprennent deux parties : un numro de machine et un numro de port. Ces informations sont stockes dans une variable de type struct sockaddr_in. Positionnez le champ sin_family AF_INET pour indiquer quil sagit dune adresse de lespace de nommage Internet. Le champ sin_addr stocke ladresse Internet de la machine cible sous forme dune adresse IP entire sur 32 bits. Un numro de port permet de faire la distinction entre plusieurs sockets dune machine donne. Comme des machines distinctes peuvent stocker les octets de valeurs multioctets dans des ordres dirents, utilisez htons pour reprsenter le numro de port dans lordre des octets dni par le rseau. Consultez la page de manuel de ip pour plus dinformations. Pour convertir des noms dhtes, des adresses en notation pointe (comme 10.0.0.1) ou des noms DNS (comme www.codesourcery.com) adresse IP sur 32 bits, vous pouvez utiliser gethostbyname. Cette fonction renvoie un pointeur vers une structure struct hostent ; le champ h_addr contient lIP de lhte. Reportez-vous au programme exemple du Listing 5.12. Le Listing 5.12 illustre lutilisation de sockets de domaine Internet. Le programme rapatrie la page daccueil du serveur Web dont le nom est pass sur la ligne de commande. Listing 5.12 (socket-inet.c) Lecture partir dun Serveur WWW
1 2 3 4 5 6 7 8 9

# include # include # include # include # include # include # include

< stdlib .h > < stdio .h > < netinet / in .h > < netdb .h > < sys / socket .h > < unistd .h > < string .h >

/* Affiche le contenu de la page d accueil du serveur correspondant

112
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60

CHAPITRE 5. COMMUNICATION INTERPROCESSUS


au socket . */ void get_home_page ( int socket_fd ) { char buffer [10000]; ssize_t n u m b e r _ c h a r a c t e r s _ r e a d ; /* Envoie la commande HTTP GET pour la page d accueil . */ sprintf ( buffer , " GET /\ n " ) ; write ( socket_fd , buffer , strlen ( buffer ) ) ; /* Lit partir du socket . L appel read peut ne pas renvoyer toutes les donnes en une seule fois , on continue de lire jusqu ce qu il n y ait plus rien . */ while (1) { n u m b e r _ c h a r a c t e r s _ r e a d = read ( socket_fd , buffer , 10000) ; if ( n u m b e r _ c h a r a c t e r s _ r e a d == 0) return ; /* Ecrit les donnes vers la sortie standard . */ fwrite ( buffer , sizeof ( char ) , number_characters_read , stdout ) ; } } int main ( int argc , char * const argv []) { int socket_fd ; struct sockaddr_in name ; struct hostent * hostinfo ; /* Cre le socket . */ socket_fd = socket ( PF_INET , SOCK_STREAM , 0) ; /* Place le nom du serveur dans l adresse du socket . */ name . sin_family = AF_INET ; /* Convertit la chane en adresse IP sur 32 bits . */ hostinfo = gethostbyname ( argv [1]) ; if ( hostinfo == NULL ) return 1; else name . sin_addr = *(( struct in_addr *) hostinfo - > h_addr ) ; /* Les serveurs Web utilisent le port 80. */ name . sin_port = htons (80) ; /* Se connecte au serveur Web . */ if ( connect ( socket_fd , ( struct sockaddr *) & name , sizeof ( struct sockaddr_in ) ) == -1) { perror ( " connect " ) ; return 1; } /* Rcupre la page d accueil du serveur . */ get_home_page ( socket_fd ) ; return 0; }

Ce programme lit le nom du serveur Web partir de la ligne de commande (pas lURL cest--dire sans le http :// ). Il appelle gethostbyname pour traduire le nom dhte en adresse IP numrique puis connecte un socket de type connexion (TCP) au port 80 de cet hte. Les serveurs Web parlent lHypertext Transport Protocol (HTTP), donc le programme met la commande HTTP GET et le serveur rpond en envoyant le texte correspondant la page daccueil.

5.5. SOCKETS Numros de Ports Standards

113

Par convention, les serveurs Web attendent des connexions sur le port 80. La plupart des services rseau Internet sont associs un numro de port standard. Par exemple, les serveurs Web scuriss utilisant SSL attendent les connexions sur le port 443 et les serveurs de mail (qui parlent SMTP) utilisent le port 25. Sur les systmes GNU/Linux, les associations entre les noms des services, les protocoles et les numros de port standards sont lists dans le chier /etc/services. La premire colonne est le nom du protocole ou du service. La seconde colonne indique le numro de port et le type de connexion : tcp pour le mode orient connexion et udp pour le mode datagramme. Si vous implmentez des services rseau personnaliss utilisant des sockets Internet, utilisez des numros de ports suprieurs 1024. Par exemple, pour rapatrier la page daccueil du site Web www.codesourcery.com, invoquez la commande suivante :
% ./socket-inet www.codesourcery.com <html> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> ...

5.5.7

Couples de Sockets

Comme nous lavons vu prcdemment, la fonction pipe cre deux descripteurs de chiers chacune tant lextrmit dun tube. Les tubes sont limits car les descripteurs de chiers doivent tre utiliss par des processus lis et car la communication est unidirectionnelle. La fonction socketpair cre deux descripteurs de chiers pour deux sockets connects sur le mme ordinateur. Ces descripteurs de chiers permettent une communication double sens entre des processus lis. Ses trois premiers paramtres sont les mmes que pour lappel socket : ils indiquent le domaine, le type de connexion et le protocole. Le dernier paramtre est un tableau de deux entiers, qui sera renseign avec les descripteurs de chiers des deux sockets, comme pour pipe. Lorsque vous appelez socketpair, vous devez utiliser PF_LOCAL comme domaine.

114

CHAPITRE 5. COMMUNICATION INTERPROCESSUS

Deuxime partie

Matriser Linux

115

Table des Matires


6 7 8 9 Priphriques Le Systme de Fichiers /proc Appels Systme Linux Code Assembleur en Ligne 119 135 153 173 181

10 Scurit

118

TABLE DES MATIRES

Chapitre 6

Priphriques
inux, comme la plupart des systmes dexploitation, interagit avec les priphriques matriels via des composants logiciels modulaires appels pilotes de priphriques. Un pilote masque les particularits des protocoles de communication utiliss par un dispositif matriel au systme dexploitation et lui permet dinteragir avec le priphrique par le biais dune interface standardise. Sous Linux, les pilotes de priphriques font partie du noyau et peuvent tre intgrs de faon statique celui-ci ou chargs la demande sous forme de modules. Les pilotes de priphriques sexcutent comme sils faisaient partie du noyau et ne sont pas accessibles directement aux processus utilisateur. Cependant, Linux propose un mcanisme ces processus pour communiquer avec un pilote et par l mme avec le dispositif matriel via des objets semblables aux chiers. Ces objets apparaissent dans le systme de chiers et des applications peuvent les ouvrir, les lire et y crire pratiquement comme sil sagissait de chiers normaux. Vos programmes peuvent donc communiquer avec des dispositifs matriels via des objets semblables aux chiers soit en utilisant les oprations dE/S de bas niveau de Linux (consultez lAppendice B, E/S de Bas Niveau ), soit les oprations de la bibliothque dE/S standard du C. Linux fournit galement plusieurs objets semblables des chiers qui communiquent directement avec le noyau plutt quavec des pilotes de priphriques. Ils ne sont pas lis des dispositifs matriels ; au lieu de cela, ils fournissent dirents types de comportements spcialiss qui peuvent tre utiles aux applications et aux programmes systmes.

Soyez Prudent Lorsque Vous Accdez aux Priphriques ! Les techniques prsentes dans ce chapitre fournissent un accs direct aux pilotes de priphriques sexcutant au sein du noyau Linux, et travers eux aux dispositifs matriels connects au systme. Utilisez ces techniques avec prudence car une mauvaise manipulation peut altrer ou endommager le systme GNU/Linux. Lisez notamment le cadre Danger des Priphriques Blocs .

119

120

CHAPITRE 6. PRIPHRIQUES

6.1

Types de Priphriques

Les chiers de priphriques ne sont pas des chiers ordinaires ils ne reprsentent pas des zones de donnes au sein dun systme de chiers sur disque. Au lieu de cela, les donnes lues ou crites sur un chier de priphrique sont transmises au pilote de priphrique correspondant et, par son intermdiaire, au matriel sous-jacent. Les chiers de priphriques se divisent en deux types : Un priphrique caractre reprsente un dispositif matriel qui lit ou crit en srie un ux doctets. Les ports srie et parallle, les lecteurs de cassettes, les terminaux et les cartes son sont des exemples de priphriques caractres. Un priphrique bloc reprsente un dispositif matriel qui lit ou crit des donnes sous forme de blocs de taille xe. Contrairement aux priphriques caractre, un priphrique bloc fournit un accs direct aux donnes stockes sur le priphrique. Un lecteur de disque est un exemple de priphrique bloc. Les programmes traditionnels nutiliseront jamais de priphriques blocs. Bien quun lecteur de disque soit reprsent comme un priphrique matriel, le contenu de chaque partition contient habituellement un systme de chiers mont sur larborescence racine de GNU/Linux. Seul le code du noyau qui implmente le systme de chiers a besoin daccder au priphrique bloc directement ; les programmes dapplication accdent au contenu du disque via des chiers et des rpertoires normaux. Nanmoins, les applications utilisent quelquefois les priphriques caractre. Nous traiterons de plusieurs dentre eux dans les sections suivantes.

Dangers des Priphriques Bloc Les priphriques bloc orent un accs direct aux donnes du lecteur de disque. Bien que la plupart des systmes GNU/Linux soient congurs pour interdire aux processus non root daccder directement ces priphriques, un processus root peut causer des dommages svres en changeant le contenu du disque. En crivant sur un priphrique bloc correspondant un disque, un programme peut modier ou dtruire les informations de contrle du systme de chier et mme la table des partitions dun disque et son secteur de dmarrage, rendant ainsi le lecteur, ou mme tout le systme, inutilisable. Accdez toujours ces priphriques avec la plus grande prudence.

6.2

Numros de Priphrique

Linux identie les priphriques au moyen de deux nombres : le numro de priphrique majeur et le numro de priphrique mineur. Le numro de priphrique majeur indique quel pilote correspond le priphrique. Les correspondances entre les numros de priphrique majeurs et les pilotes sont xes et dnies dans les sources du noyau Linux. Notez quun mme numro de priphrique majeur peut correspondre deux pilotes dirents, lun tant un priphrique

6.3. FICHIERS DE PRIPHRIQUES

121

caractre et lautre un priphrique bloc. Les numros de priphrique mineurs permettent de distinguer plusieurs priphriques ou composants contrls par le mme pilote. Par exemple, le priphrique de numro majeur 3 correspond au contrleur IDE primaire du systme. Un contrleur IDE peut tre connect deux priphriques (lecteur de disque, cassette ou CD-ROM) ; le priphrique matre a le numro mineur 0 et le priphrique esclave a le numro mineur 64. les partitions du priphrique matre (sil supporte les partitions) ont les numros 1, 2, 3, etc. Les partitions du priphrique esclave sont reprsentes par les numros de priphrique mineurs 65, 66, 67, etc. Les numros de priphrique majeurs sont rpertoris dans la documentation des sources du noyau Linux. Sur beaucoup de distributions GNU/Linux, ils sont dcrits dans le chier /usr/src/linux/Documentation/devices.txt. Le chier spcial /proc/devices dresse la liste des numros de priphrique majeurs correspondant aux pilotes de priphriques actuellement chargs dans le noyau (consultez le Chapitre 7, le Systme de Fichiers /proc pour plus dinformations sur les entres du systme de chiers /proc).

6.3

Fichiers de Priphriques

Un chier de priphrique ressemble beaucoup un chier classique. Vous pouvez le dplacer en utilisant la commande mv et le supprimer avec rm. Si vous essayez de copier un priphrique en utilisant cp, par contre, vous lirez des octets partir de celui-ci (sil le supporte) et les crirez vers un chier de destination. Si vous essayez dcraser un chier de priphrique, vous crirez des octets vers le priphrique concern. Vous pouvez crer un chier de priphrique en utilisant la commande mknod (saisissez man 1 mknod pour obtenir la page de manuel) ou lappel systme mknod (man 2 mknod pour la page de manuel). Crer un chier de priphrique nimplique pas automatiquement que le pilote ou le dispositif matriel soit prsent ou disponible ; le chier de priphrique est en quelque sort un portail pour communiquer avec le pilote, sil est prsent. Seul les processus superutilisateur peuvent crer des priphriques bloc et caractre via la commande ou lappel systme mknod. Pour crer un priphrique en utilisant la commande mknod, spciez le chemin du chier le reprsentant comme premier argument de la ligne de commande. Pour le second argument, passez b pour un priphrique bloc ou c pour un priphrique caractre. Fournissez les numros de priphrique majeur et mineur en troisime et quatrime argument, respectivement. Par exemple, la commande suivante cre un chier de priphrique caractre appel lp0 dans le rpertoire courant. Ce priphrique a le numro de priphrique majeur 6 et le numro mineur 0. Ces nombres correspondent au premier port parallle sur le systme Linux.
% mknod ./lp0 c 6 0

Souvenez-vous que seuls les processus du superutilisateur peuvent crer des priphriques bloc ou caractre, vous devez donc tre connect en tant que root pour invoquer cette commande avec succs. La commande ls ache les chiers de priphrique dune faon particulire. Si vous lappelez avec les options -l ou -o, le premier caractre de chaque ligne indique le type du chier. Rappelons que - (un tiret) indique un chier classique, alors que d indique un rpertoire.

122

CHAPITRE 6. PRIPHRIQUES

De mme, b dsigne un priphrique bloc et c un priphrique caractre. Pour ces deux derniers, ls ache les numros de priphrique majeur et mineur l o se trouve habituellement la taille pour les chiers ordinaires. Par exemple, nous pouvons acher le priphrique caractre que nous venons juste de crer :
% ls -l lp0 crw-r----- 1 root root 6, 0 Mar 7 17:03 lp0

Dans un programme, vous pouvez dterminer si un chier est un priphrique bloc ou caractre et donc obtenir ses numros de priphrique via stat. Consultez la Section B.2, stat , de lAppendice B, pour plus dinformations. Pour supprimer le chier, utilisez rm. Cela ne supprime pas le priphrique ou son pilote ; mais simplement le chier de priphrique du systme de chiers.
% rm ./lp0

6.3.1

Le Rpertoire /dev

Par convention, un systme GNU/Linux inclut un rpertoire /dev contenant tous les chiers de priphriques caractre ou bloc des priphriques dtects. Les entres de /dev ont des noms standardiss correspondants au numros de priphrique majeur et mineur. Par exemple, le priphrique matre connect au contrleur IDE primaire, qui dispose des numros de priphrique majeur et mineur 3 et 0, a le nom standard /dev/hda. Si ce priphrique gre les partitions, la premire, qui dispose du numro de priphrique mineur 1, a le nom standard /dev/hda1. Vous pouvez le vrier sur votre propre systme :
% ls -l /dev/hda /dev/hda1 brw-rw---1 root disk 3, 0 May 5 1998 /dev/hda brw-rw---1 root disk 3, 1 May 5 1998 /dev/hda1

De mme, /dev contient une entre pour le priphrique caractre quest le port parallle que nous avons utilis prcdemment :
% ls -l /dev/lp0 crw-rw---1 root daemon 6, 0 May 5 1998 /dev/lp0

Dans la plupart des cas, vous ne devriez pas utiliser mknod pour crer vos propres chiers de priphrique. Utilisez plutt les entres de /dev. Les programmes ne disposant pas des privilges superutilisateur nont pas dautre choix que de les utiliser puisquils ne peuvent pas crer leur propres entres. Typiquement, seuls les administrateurs systme et les dveloppeurs utilisant des priphriques spciques ont besoin de crer leurs propres chiers de priphrique. La plupart des distributions GNU/Linux proposent des utilitaires daide la cration de chiers de priphrique standards avec les noms corrects.

6.3.2

Accder des Priphriques en Ouvrant des Fichiers

Comment utiliser ces priphriques ? Dans le cas de priphriques caractre, cela peut tre relativement simple : ouvrez le priphrique comme sil sagissait dun chier classique et lisez ou crivez-y. Vous pouvez mme utiliser des commandes conues pour les chiers traditionnels,

6.4. PRIPHRIQUES MATRIELS

123

comme cat ou la syntaxe de redirection de votre shell pour envoyer ou lire des donnes partir du priphrique. Par exemple, si vous disposez dune imprimante connecte sur le premier port parallle de votre ordinateur, vous pouvez imprimer des chiers en les envoyant directement sur /dev/lp01 . Pour imprimer le contenu de document.txt, invoquez la commande suivante :
% cat document.txt > /dev/lp0

Vous devez disposer des permissions en criture sur le chier de priphrique pour que la commande nchoue pas ; sur beaucoup de systmes GNU/Linux, les permissions sont dnies de telle faon que seul root et le dmon dimpression systme (lpd) puissent crire dans ce chier. De plus, ce qui sort de votre imprimante dpend de la faon dont elle interprte les donnes que vous lui envoyez. Certaines imprimantes imprimeront les chiers texte plats que vous leur enverrez2 , dautres non. Les imprimantes PostScript interprteront et imprimeront les chiers PostScript que vous leur envoyez. Dans un programme, envoyer des donnes un priphrique est aussi simple. Par exemple, cet extrait de code utilise des fonctions dE/S standard de bas niveau pour envoyer le contenu dun tampon vers /dev/lp0.
int fd = open ( " / dev / lp0 " , O_WRONLY ) ; write ( fd , buffer , buffer_length ) ; close ( fd ) ;

6.4

Priphriques Matriels

Quelques priphriques bloc standards sont lists dans le Tableau 6.1. Les numros mineurs des priphriques similaires suivent un motif classique (par exemple, la seconde partition du premier priphrique SCSI est /dev/sda2). Il est parfois utile de savoir quel priphrique correspondent les noms de priphriques lorsque lon observe les systmes de chiers monts dans /proc/mounts (consultez la Section 7.5, Lecteurs et Systmes de Fichiers , du Chapitre 7, pour en savoir plus). Le Tableau 6.2 liste quelques priphriques caractre courants. Vous pouvez accder certains composants matriels via plus dun priphrique caractre ; souvent, des priphriques caractre distincts ont une smantique dirente. Par exemple, lorsque vous utilisez le lecteur de cassettes IDE /dev/ht0, Linux rembobine automatiquement la cassette lorsque vous fermez le descripteur de chier. Vous pouvez utiliser /dev/nht0 pour accder au mme lecteur de cassettes, la seule dirence est que Linux ne rembobine pas la cassette lors de la fermeture. Vous pourriez rencontrer des programmes utilisant /dev/cua0 et des dispositifs similaires ; il sagit danciennes interfaces vers les ports srie comme /dev/ttyS0. De temps autre, vous pourriez avoir besoin dcrire des donnes directement sur des priphriques caractre par exemple :
Les utilisateurs de Windows reconnatront l un priphrique similaire au chier magique LPT1 de Windows. Votre imprimante peut ncessiter lajout de retours chariot, code ASCII 13, la n de chaque ligne et lajout dun caractre de saut de page, code ASCII 12, la n de chaque page.
2 1

124

CHAPITRE 6. PRIPHRIQUES

Tab. 6.1 Listing Partiel des Priphriques Bloc Priphrique Nom Premier lecteur de disquettes /dev/fd0 Second lecteur de disquette /dev/fd1 Contrleur IDE primaire, matres /dev/hda Contrleur IDE primaire, matre, premire partition /dev/hda1 Contrleur IDE primaire, esclave /dev/hdb Contrleur IDE primaire, esclave, premire partition /dev/hdb1 Premier lecteur SCSI /dev/sda Premier lecteur SCSI, premire partition /dev/sda1 Second disque SCSI /dev/sdb Second disque SCSI, premire partition /dev/sdb1 Premier lecteur CD-ROM SCSI /dev/scd0 Second lecteur CD-ROM SCSI /dev/scd1

Courants N Majeur 2 2 3 3 3 3 8 8 8 8 11 11

N Mineur 0 1 0 1 64 65 0 1 16 17 0 1

Tab. 6.2 Listing Partiel des Priphriques Caractre Courants Priphrique Nom N Majeur Port parallle 0 /dev/fd0 ou /dev/par0 6 Port parallle 1 /dev/lp1 ou /dev/par1 6 Premier port srie /dev/ttyS0 4 Second port srie /dev/ttyS1 4 Lecteur de cassettes IDE /dev/ht0 37 Premier lecteur de cassettes SCSI /dev/st0 9 Second lecteur de cassettes SCSI /dev/st1 9 Console systme /dev/console 5 Premier terminal virtuel /dev/tty1 4 Second terminal virtuel /dev/tty2 4 Terminal du processus courant /dev/tty 5 Carte son /dev/audio 14

N Mineur 0 1 64 65 0 0 1 1 1 2 0 4

6.4. PRIPHRIQUES MATRIELS

125

Un programme terminal peut accder modem directement par le biais dun port srie. Les donnes crites ou lues partir de ces priphriques sont transmises par le modem un ordinateur distant. Un programme de sauvegarde sur cassette peut crire directement des donnes sur le priphrique de lecture de cassettes. Ce programme peut implmenter son propre format de compression et de vrication derreur. Un programme peut crire directement sur le premier terminal virtuel3 en crivant sur /dev/tty1. Les fentres de terminal sexcutant sous un environnement graphique ou les sessions distantes ne sont pas associes des terminaux virtuels mais des pseudos-terminaux. Consultez la Section 6.6, PTY , pour plus dinformations. Parfois, un programme peut avoir besoin daccder au terminal auquel il est associ. Par exemple, votre application peut avoir besoin de demander un mot de passe lutilisateur. Pour des raisons de scurit, vous ne voulez pas tenir compte des redirections dentre et de sortie standards et toujours lire le mot de passe partir du terminal, peut importe la faon dont lutilisateur appelle votre programme. Une faon de le faire est douvrir /dev/tty, qui correspond toujours au terminal associ au processus eectuant louverture. crivez linvite de mot de passe sur ce priphrique et lisez le mot de passe. En ignorant lentre et la sortie standards, vous vitez que lutilisateur nalimente votre programme avec un mot de passe stock dans un chier avec une syntaxe comme celle-ci :
% programme_sur < mon-motdepasse.txt

Si vous avez besoin dun mcanisme dauthentication dans votre programme, vous devriez vous tourner vers le dispositif PAM de GNU/Linux. Consultez la Section 10.5, Authentication des Utilisateurs , du Chapitre 10, Scurit , pour plus dinformations. Un programme peut diuser des sons via la carte son du systme en envoyant des donnes audio vers /dev/audio. Notez que les donnes audio doivent tre au format Sun (chiers portant habituellement lextension .au). Par exemple, beaucoup de distributions GNU/Linux fournissent le chier son classique /usr/share/sndconfig/sample.au. Si votre systme dispose de ce chier, essayez de le jouer grce la commande suivante :
% cat /usr/share/sndconfig/sample.au > /dev/audio

Si vous devez utiliser des sons dans votre programme, cependant, vous devriez utiliser lun des multiples bibliothques et services de gestion de sons disponibles pour GNU/Linux. Lenvironnement de bureau Gnome utilise lEnlightenment Sound Daemon (EsounD), disponible sur http://www.tux.org/~ricdude/EsounD.html. KDE utilise aRts, disponible sur http://space.twc.de/~stefan/kde/arts-mcop-doc/. Si vous utilisez lun de ces systmes de son au lieu dcrire directement sur /dev/audio, votre programme pourra tre utilis plus facilement avec dautres programmes utilisant la carte son de lordinateur.
Sur la plupart des systmes GNU/Linux, vous pouvez basculer vers le premier terminal en appuyant sur Ctrl+Alt+F1. Utilisez Ctrl+Alt+F2 pour le second terminal virtuel, etc.
3

126

CHAPITRE 6. PRIPHRIQUES

6.5

Priphriques Spciaux

Linux fournit galement divers priphriques caractre ne correspondant aucun priphrique matriel. Ces chiers ont tous le numro de priphrique majeur 1, qui est associ la mmoire du noyau Linux et non un pilote de priphrique.

6.5.1

/dev/null

Le chier /dev/null, le priphrique nul, est trs pratique. Il a deux utilisations ; vous tes probablement familiers avec la premire : Linux ignore toute donne crite vers /dev/null. Une astuce souvent utilise est de spcier /dev/null en tant que chier de sortie lorsque lon ne veut pas de sortie. Par exemple, pour lancer une commande et ignorer son achage standard (sans lacher ni lenvoyer vers un chier), redirigez la sortie standard vers /dev/null :
% commande_bavarde > /dev/null

Lire depuis /dev/null renvoie toujours une n de chier. Par exemple, si vous ouvrez un descripteur de chier correspondant /dev/null en utilisant open puis essayez dappeler read sur ce descripteur, aucun octet ne sera lu et read renverra 0. Si vous copiez /dev/null vers un autre chier, la destination sera un chier de taille nulle :
% cp /dev/null fichier_vide % ls -l fichier_vide -rw-rw---1 samuel samuel 0 Mar 8 00:27 fichier_vide

6.5.2

/dev/zero

Le chier de priphrique /dev/zero se comporte comme sil contenait une innit doctets 0. Quelle que soit la quantit de donnes que vous essayez de lire partir de /dev/zero, Linux gnrera susamment doctets nuls. Pour illustrer cela, excutons le programme de capture en hexadcimal prsent dans le Listing B.4, Lecture de Donnes , de lAppendice B. Ce programme ache le contenu dun chier au format hexadcimal.
% ./hexdump /dev/zero 0x000000 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x000010 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x000020 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ...

Appuyez sur Ctrl+C une fois convaincu quil continuera indniment. Mettre /dev/zero en correspondance avec la mmoire est une technique dallocation avance. Reportez-vous la Section 5.3.5, Autres Utilisations de nmap , du Chapitre 5, Communication Interprocessus pour plus dinformations, et consultez lencadr Obtenir de la Mmoire Aligne sur des Pages , de la Section 8.9, mprotect : Dnir des Permissions Mmoire , du Chapitre 8, Appels Systme Linux , pour un exemple.

6.5. PRIPHRIQUES SPCIAUX

127

6.5.3

/dev/full

Le chier /dev/full se comporte comme sil se trouvait sur un systme de chiers ne comportant plus despace libre. Une criture vers /dev/full choue et positionne errno ENOSPC, qui indique que le lecteur de destination est plein. Par exemple, vous pouvez tenter dcrire sur /dev/full en utilisant la commande cp :
% cp /etc/fstab /dev/full cp: criture de /dev/full: Aucun espace disponible sur le priphrique

Le chier /dev/full est essentiellement utile pour tester la faon dont se comporte votre programme sil tombe court despace disque lors de lcriture dun chier.

6.5.4

Dispositifs de Gnration de Nombres Alatoires

Les priphriques spciaux /dev/random et /dev/urandom donnent accs au dispositif de gnration de nombres alatoires intgr au noyau Linux. La plupart des fonctions logicielles charges de gnrer des nombres alatoires, comme la fonction rand de la bibliothque standard du C, gnrent en fait des nombres pseudo-alatoires. Bien que ces nombres aient certaines proprits des nombres alatoires, ils sont reproductibles : si vous relancez une srie avec la mme valeur dinitialisation, vous obtiendrez la mme squence de nombres pseudo-alatoires chaque fois. Cet inconvnient est invitable car les ordinateurs sont intrinsquement dterministes et prvisibles. Pour certaines applications, cependant, ce comportement nest pas souhaitable ; par exemple, il peut tre possible de casser un algorithme de chirement si lon connat la squence de nombres alatoires quil utilise. Lobtention de nombres alatoires plus stricts au sein de programmes informatiques ncessite une source externe dvnements alatoires. Le noyau Linux utilise une source dvnements alatoires particulirement bonne : vous ! En mesurant les carts temporels entre vos actions, comme lappui sur les touches et les mouvements de la souris, Linux est capable de gnrer un ux de nombres alatoires de grande qualit impossible prdire. Vous pouvez accder ce ux en lisant les chiers /dev/random et /dev/urandom. Les donnes que vous obtenez sont issues dun ux doctets gnr alatoirement. La dirence entre les deux priphriques nest visible que lorsque Linux a puis son stock de nombres alatoires. Si vous essayez de lire un nombre important doctets partir de /dev/random mais ne gnrez aucune action (vous nutilisez pas le clavier, ne bougez pas la souris ni neectuez aucune autre action de ce type), Linux bloque lopration de lecture. La gnration ne reprendra que lorsque vous eectuerez des actions. Par exemple, essayez dacher le contenu de /dev/random en utilisant la commande od4 . Chaque ligne ache 16 nombres alatoires.
% od -t 0000000 0000020 0000040 0000060 x1 2c d3 b3 05 /dev/random 9c 7a db 2e 6d 1e a7 91 b0 8d 94 21 a3 02 cb 22 79 05 57 0a 3d 2d f3 bc 65 4d 90 c9 36 c3 61 45 c2 a6 dd dd e3 de 26 a6 1b 54 ac 59 52 29 94 40 75 f4 c3 22 1e 46 b9 53 1a 04 3a d4

4 Nous utilisons od plutt que le programme hexdump du Listing B.4, mme sil font plus ou moins la mme chose, car hexdump se termine lorsquil ny a plus de donnes lire, alors que od attend des donnes supplmentaires. Loption -t x1 indique od dacher le contenu du chier en hexadcimal.

128

CHAPITRE 6. PRIPHRIQUES

Le nombre de lignes aches varie il peut y en avoir trs peu mais la sortie se mettra en pause ds que Linux puisera son stock de nombres alatoires. Essayez maintenant de dplacer votre souris ou de saisir quelque chose au clavier et vriez que de nouveaux nombres alatoires apparaissent. Pour en obtenir encore plus, vous pouvez laisser votre chat marcher sur le clavier. Une lecture partir de /dev/urandom, en revanche, ne bloque jamais. Si Linux tombe court de nombre alatoires, il utilise un algorithme de chirement pour gnrer des octets pseudo-alatoires partir de la dernire squence doctets alatoires. Bien que ces octets soient susamment alatoires pour la plupart des utilisations, ils ne satisfont pas autant de tests que ceux obtenus partir de /dev/random. Par exemple, si vous invoquez la commande suivante, les octets alatoires dleront en continu, jusqu ce que vous tuiez le programme avec Ctrl+C :
% od -t 0000000 0000020 0000040 ... x1 62 26 95 /dev/urandom 71 d6 3e af dd de 62 c0 42 78 bd 29 9c 69 49 3b 95 bc b9 6c 15 16 38 fd 7e 34 f0 ba ce c3 31 e5 2c 8d 8a dd f4 c4 3b 9b 44 2f 20 d1 54

Utiliser des nombres alatoires provenant de /dev/random dans un programme est une chose assez facile. Le Listing 6.1 prsente une fonction qui gnre un nombre alatoire en utilisant les octets lus partir de /dev/random. Souvenez-vous que la lecture est bloque jusqu ce quil y ait susamment dvnements alatoires pour la satisfaire ; vous pouvez utiliser /dev/urandom la place si vous accordez plus de priorit la rapidit dexcution et que vous pouvez vous contenter de nombres alatoires dune qualit moindre. Listing 6.1 (random_number.c) Fonction Gnrant un Nombre Alatoire partir de /dev/random
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

# include # include # include # include # include

< assert .h > < sys / stat .h > < sys / types .h > < fcntl .h > < unistd .h >

/* Renvoie un entier alatoire entre MIN et MAX inclus . La source utilise est / dev / random . */ int random_number ( int min , int max ) { /* Stocke un descripteur de fichier pointant vers / dev / random dans une variable static . De cette faon , nous n avons pas besoin d ouvrir le fichier chaque fois que la fonction est appele . */ static int dev_random_fd = -1; char * n e x t _ r a n d o m _ b y t e ; int bytes_to_read ; u n s i g n e d random_value ; /* S assure que MAX est plus grand que MIN . */ assert ( max > min ) ; /* S il s agit du premier appel de la fonction , ouvre un descripteur de fichier pointant vers / dev / random . */ if ( dev_random_fd == -1) { dev_random_fd = open ( " / dev / random " , O_RDONLY ) ; assert ( dev_random_fd != -1) ; } /* Lit suffisamment d octets alatoires pour remplir un entier . */ n e x t _ r a n d o m _ b y t e = ( char *) & random_value ;

6.5. PRIPHRIQUES SPCIAUX


28 29 30 31 32 33 34 35 36 37 38 39 40

129

bytes_to_read = sizeof ( random_value ) ; /* Boucle jusqu ce que l on ait assez d octets . Comme / dev / random est gnr partir d actions de l utilisateur , la lecture peut bloquer et ne renvoyer qu un seul octet la fois . */ do { int bytes_read ; bytes_read = read ( dev_random_fd , next_random_byte , bytes_to_read ) ; bytes_to_read -= bytes_read ; n e x t _ r a n d o m _ b y t e += bytes_read ; } while ( bytes_to_read > 0) ; /* Calcule un nombre alatoire dans l intervalle demand . */ return min + ( random_value % ( max - min + 1) ) ; }

6.5.5

Priphriques Loopback

Un priphrique loopback vous permet de simuler un priphrique bloc en utilisant un chier disque ordinaire. Imaginez un lecteur de disque pour lequel les donnes sont crites et lues partir dun chier appel image-disque plutt que depuis les pistes et secteurs dun disque physique rel ou dune des partition dun disque (bien sr, le chier image-disque doit se situer sur un disque physique, qui doit tre plus grand que le disque simul). Un priphrique loopback vous permet dutiliser un chier de cette faon. Les priphriques loopback sappellent /dev/loop0, /dev/loop1, etc. Chacun peut tre utilis pour simuler un priphrique bloc distinct. Notez que seul le superutilisateur peut paramtrer un priphrique loopback. Un tel priphrique peut tre utilis de la mme faon que nimporte quel autre priphrique bloc. En particulier, vous pouvez placer un systme de chiers sur le priphrique puis monter ce systme de chiers comme sil rsidait sur un disque ou une partition classique. Un tel systme de chiers, qui rside entirement au sein dun chier sur disque ordinaire, est appel systme de chiers virtuel. Pour construire un systme de chiers et le monter partir dun priphrique loopback, suivez ces tapes : 1. Crez un chier vide qui contiendra le systme de chiers virtuel. La taille du chier sera la taille du priphrique loopback une fois mont. Une faon pratique de construire un chier dune taille prdtermine est dutiliser la commande dd. Elle copie des blocs (de 512 octets chacun, par dfaut) dun chier vers un autre. Le chier /dev/zero convient parfaitement pour tre utilis comme source doctets nuls. Pour construire un chier de 10Mo appel image-disque, utilisez la commande suivante :
% dd if=/dev/zero of=/tmp/image-disque count=20480 20480+0 enregistrements lus. 20480+0 enregistrements crits. % ls -l /tmp/image-disque -rw-rw---1 root root 10485760 Mar 8 01:56 /tmp/image-disque

2. Le chier que vous venez de crer est rempli avec des octets 0. Avant de le monter, vous devez y placer un systme de chiers. Cette opration initialise diverses structures de contrle ncessaires lorganisation et au stockage de chiers et cre le rpertoire racine.

130

CHAPITRE 6. PRIPHRIQUES Vous pouvez placer nimporte quel type de systme de chiers sur votre image disque. Pour crer un systme de chiers ext3 (le type le plus courant pour les disques Linux), utilisez la commande mke2fs. Comme elle est habituellement excute sur des priphriques bloc, et non pas des chiers ordinaires, elle demande une conrmation :
/sbin/mke2fs -q -j /tmp/image-disque /tmp/image-disque nest pas un priphrique spcial bloc. Procder malgr tout? (y pour oui, n pour non) y

Loption -q supprime les informations rcapitulatives sur le systme de chiers nouvellement cr. Supprimez-la si vous tes curieux. Dsormais image-disque contient un nouveau systme de chiers comme sil sagissait dun disque de 10 Mo tout neuf. 3. Montez le systme de chiers en utilisant un priphrique loopback. Pour cela, utilisez la commande mount en spciant le chier de limage disque comme priphrique monter. Passez galement loption de montage loop=priphrique-loopback, en utilisant loption -o pour indiquer mount quel priphrique loopback utiliser. Par exemple, pour monter notre systme de chiers image-disque, utilisez ces commandes. Souvenez-vous, seul le superutilisateur peut utiliser un priphrique loopback. La premire commande cre un rpertoire, /tmp/virtual-fs, que nous allons utiliser comme point de montage pour le systme de chiers virtuel.
% mkdir /tmp/virtual-fs % mount -o loop=/dev/loop0 /tmp/image-disque /tmp/virtual-fs

Dsormais, limage disque est monte comme sil sagissait dun disque de 10Mo ordinaire.
% df -h /tmp/virtual-fs Sys. De fich. Tail. Occ. Disp. %Occ. Mont sur /tmp/image-disque 9.7M 13k 9.2M 0% /tmp/virtual-fs

Vous pouvez lutiliser comme nimporte quel autre disque :


% cd /tmp/virtual-fs % echo "Coucou !" > test.txt % ls -l total 13 drwxr-xr-x 2 root root -rw-rw---1 root root % cat test.txt Coucou !

12288 Mar 8 02:00 lost+found 14 Mar 8 02:12 test.txt

Notez que lost+found est un rpertoire automatiquement cr par mke2fs5 . Lorsque vous en avez ni, dmontez le systme de chiers virtuel.
% cd /tmp % umount /tmp/virtual-fs

Vous pouvez supprimer image-disque si vous le dsirez ou vous pouvez le monter plus tard pour accder aux chiers du systme de chiers virtuel. Vous pouvez galement le copier sur un autre ordinateur o vous pourrez le monter le systme de chiers que vous avez cr sera entirement intact.
Si le systme de chiers subit des dommage et que des donnes sont rcupres sans tre associes un chier, elles sont places dans lost+found.
5

6.6. PTY

131

Au lieu de crer un systme de chiers partir de rien, vous pouvez en copier un partir dun priphrique existant. Par exemple, vous pouvez crer limage du contenu dun CD-ROM simplement en le copiant partir dun lecteur de CD-ROM. Si vous disposez dun lecteur de CD-ROM IDE, utilisez le nom de priphrique correspondant, par exemple /dev/hda, dcrit prcdemment. Si vous disposez dun lecteur CD-ROM SCSI, le nom de priphrique sera du type /dev/scd0. Le lien symbolique /dev/cdrom peut galement exister sur votre systme, il pointe alors vers le priphrique appropri. Consultez le chier /etc/fstab pour dterminer quel priphrique correspond au lecteur de CD-ROM de votre ordinateur. Copiez simplement le priphrique vers un chier. Le rsultat sera une image disque complte du systme de chiers du CD-ROM situ dans le lecteur par exemple :
% cp /dev/cdrom /tmp/cdrom-image

Cette opration peut prendre plusieurs minutes selon le CD-ROM que vous copiez et la vitesse de votre lecteur. Le chier image rsultant sera relativement gros il fera la mme taille que le contenu du CD-ROM. Vous pouvez maintenant monter cette image sans disposer du disque original. Par exemple, pour le monter sur /mnt/cdrom, utilisez cette commande :
% mount -o loop=/dev/loop0 /tmp/cdrom-image /mnt/cdrom

Comme limage est situe sur le disque dur, les temps daccs seront bien infrieurs ceux du disque CD-ROM original. Notez que la plupart des CD-ROM utilisent le systme de chiers ISO-9660.

6.6

PTY

Si vous excutez la commande mount sans arguments de ligne de commande, ce qui liste les systmes de chiers monts sur votre systme, vous remarquerez une ligne ressemblant cela :
devpts on /dev/pts type devpts (rw,gid=5,mode=620)

Elle indique quun systme de chiers dun type particulier, devpts, est mont sur /dev/pts. Ce systme de chiers, qui nest pas associ avec un priphrique matriel, est un systme de chiers magique cr par le noyau Linux. Il est similaire au systme de chiers /proc ; consultez le Chapitre 7 pour plus dinformations sur son fonctionnement. Comme le rpertoire /dev, /dev/pts contient des entres correspondant des priphriques. Mais contrairement /dev, qui est un rpertoire classique, /dev/pts est un rpertoire spcial cr dynamiquement par le noyau Linux. Le contenu du rpertoire varie avec le temps et rete ltat du systme. Les chiers de /dev/pts correspondent des pseudo-terminaux (ou pseudo-TTY, ou PTY). Linux cre un PTY pour chaque nouvelle fentre de terminal que vous ouvrez et place lentre correspondante dans /dev/pts. Le priphrique PTY se comporte comme un terminal classique il accepte des entres depuis le clavier et ache les sorties du programme lui correspondant. Les PTY sont numrots et leur numro correspond au nom du chier correspondant dans /dev/pts.

132

CHAPITRE 6. PRIPHRIQUES

Vous pouvez acher le terminal associ un processus grce la commande ps. Indiquez tty comme lun des champ de format personnalis avec loption -o. Pour acher lidentiant de processus, le TTY et la ligne de commande de chaque processus partageant le mme terminal, invoquez ps -o pid,tty,cmd.

6.6.1

Exemple dutilisation des PTY

Par exemple, vous pouvez dterminer le PTY associ une fentre de terminal donne en invoquant cette commande au sein de la fentre :
% ps -o pid,tty,cmd PID TT CMD 28832 pts/4 bash 29287 pts/4 ps -o pid,tty,cmd

La fentre o est lance la commande sexcute au sein du PTY 4. Le PTY a un chier correspondant dans /dev/pts :
% ls -l /dev/pts/4 crw--w---1 samuel tty 136, 4 Mar 8 02:56 /dev/pts/4

Notez quil sagit dun priphrique caractre et son propritaire est celui du processus pour lequel il a t cr. Vous pouvez lire ou crire sur un priphrique PTY. Si vous lisez partir de celui-ci, vous intercepterez les saisies clavier destines au programme sexcutant au sein du PTY. Si vous essayez dy crire, les donnes apparatront dans la fentre correspondante. Essayez douvrir un nouveau terminal et dterminez son PTY en invoquant ps -o pid,tty,cmd. Depuis une autre fentre, crivez du texte sur ce priphrique. Par exemple, si le numro de PTY du nouveau terminal est 7, invoquez cette commande depuis une autre fentre :
% echo "Hello, other window!" > /dev/pts/7

La sortie apparat dans la fentre de terminal. Si vous la fermez, lentre numro 7 de /dev/pts disparat. Si vous invoquez ps pour dterminer le TTY depuis un terminal virtuel en mode texte (appuyez sur Ctrl+Alt+F1 pour basculer vers le premier terminal virtuel, par exemple), vous remarquerez quil sexcute au sein dun priphrique de terminal ordinaire et non pas un PTY :
% ps -o pid,tty,cmd PID TT CMD 29325 tty1 -bash 29353 tty1 ps -o pid,tty,cmd

6.7

ioctl

Lappel systme ioctl est une interface destine au contrle de dispositifs matriels. Le premier argument de ioctl est un descripteur de chier qui doit pointer sur le priphrique que vous voulez contrler. Le second argument est un code de requte indiquant lopration que vous souhaitez eectuer. Dirents codes de requtes sont disponibles pour chacun des priphriques.

6.7. IOCTL

133

Selon le code, il peut y avoir des arguments supplmentaires servant passer des donnes ioctl. La plupart des codes de requte disponibles pour les dirents priphriques sont lists sur la page de manuel de ioctl_list. Lutilisation de ioctl ncessite gnralement une connaissance approfondie du pilote de priphrique du matriel que vous souhaitez contrler. Il sagit dun sujet qui dpasse le cadre de ce livre. Cependant, nous prsentons un exemple vous donnant un aperu de la faon dont ioctl est utilis. Listing 6.2 (cdrom-eject.c) jecte un CD-ROM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

# include # include # include # include # include # include

< fcntl .h > < linux / cdrom .h > < sys / ioctl .h > < sys / stat .h > < sys / types .h > < unistd .h >

int main ( int argc , char * argv []) { /* Ouvre un descripteur de fichier vers le priphrique pass sur la ligne commande . */ int fd = open ( argv [1] , O_RDONLY ) ; /* jecte le CD - ROM . */ ioctl ( fd , CDROMEJECT ) ; /* Ferme le descripteur . */ close ( fd ) ; return 0; }

de

Le Listing 6.2 est un court programme qui jecte le disque prsent dans un lecteur de CDROM (si ce dernier le supporte). Il prend un argument en ligne de commande, le priphrique correspondant au lecteur de CD-ROM. Il ouvre un descripteur de chier pointant vers le priphrique et invoque ioctl avec le code de requte CDROMEJECT. Cette requte, dnie dans lentte <linux/cdrom.h>, indique au priphrique djecter le disque. Par exemple, si votre systme dispose dun lecteur de CD-ROM connect en tant que priphrique matre sur le second contrleur IDE, le priphrique correspondant est /dev/hdc. Pour jecter le disque du lecteur, invoquez cette commande :
% ./cdrom-eject /dev/hdc

134

CHAPITRE 6. PRIPHRIQUES

Chapitre 7

Le Systme de Fichiers /proc


ssayez dinvoquer la commande mount sans argument elle liste les systmes de chiers actuellement monts sur votre systme GNU/Linux. Vous apercevrez une ligne de ce type :
proc on /proc type proc (rw)

Il sagit du systme de chiers spcial /proc. Notez que le premier champ, proc, indique quil nest associ aucun priphrique matriel, comme un lecteur de disque. Au lieu de cela, /proc est une fentre sur le noyau Linux en cours dexcution. Les chiers de /proc ne correspondent pas des chiers rels sur un disque physique. Il sagit plutt dobjets magiques qui se comportent comme des chiers mais donnent accs des paramtres, des structures de donnes et des statistiques du noyau. Le contenu de ces chiers nest pas compos de blocs de donnes, comme celui des chiers ordinaires. Au lieu de cela, il est gnr la vole par le noyau Linux lorsque vous lisez le chier. Vous pouvez galement changer la conguration du noyau en cours dexcution en crivant dans certains chiers du systme de chiers /proc. tudions un exemple :
% ls -l /proc/version -r--r--r-1 root root 0 Jan 17 18:09 /proc/version

Notez que la taille du chier est zro ; comme le contenu du chier est gnr par le noyau, le concept de taille de chier na pas de sens. De plus, si vous essayez cette commande, vous remarquerez que la date de modication est la date courante. Quy a-t-il dans ce chier ? Le contenu de /proc/version est une chane dcrivant le numro de version du noyau Linux. Il correspond aux informations qui seraient renvoyes par lappel systme uname, dcrit dans le Chapitre 8, Appels Systme Linux , Section 8.15, uname , ainsi que des informations supplmentaires comme la version du compilateur utilis pour construire le noyau. Vous pouvez lire /proc/version comme nimporte quel autre chier. Par exemple, au moyen de cat :

% cat /proc/version Linux version 2.2.14-5.0 (root@porky.devel.redhat.com) (gcc version egcs-2.91.66 19990314/Linux (egcs-1.1.2 release)) #1

135

136

CHAPITRE 7. LE SYSTME DE FICHIERS /PROC

Les direntes entres du systme de chiers /proc sont dcrites dans la page de manuel de /proc (Section 5). Pour la consulter, utilisez la commande suivante :
% man 5 proc

Dans ce chapitre, nous dcrirons certaines fonctionnalits du systme de chiers /proc qui sont les plus susceptibles de servir des programmeurs et nous donnerons des exemples dutilisation. Quelques unes de ces fonctionnalits sont galement utiles pour le dbogage. Si vous tes intress par le fonctionnement exact de /proc, consultez le code source du noyau Linux situ dans /usr/src/linux/fs/proc.

7.1

Obtenir des Informations partir de /proc

La plupart des chiers de /proc donnent des informations formates pour pouvoir tre lues par des humains, cependant leur prsentation reste susamment simple pour quelles puissent tre analyses automatiquement. Par exemple, /proc/cpuinfo contient des informations sur le processeur (ou les processeurs dans le cas dune machine multiprocesseur). Son contenu est un tableau de valeurs, une par ligne, avec une description de la valeur et deux points avant chacune dentre elles. Par exemple, son contenu peut ressembler cela :
% cat /proc/cpuinfo processor :0 vendor_id : GenuineIntel cpu family :6 model :5 model name : Pentium II (Deschutes) stepping :2 cpu MHz : 400.913520 cache size : 512 KB fdiv_bug : no hlt_bug : no sep_bug : no f00f_bug : no coma_bug : no fpu : yes fpu_exception : yes cpuid level :2 wp : yes flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 mmx fxsr bogomips : 399.77

Nous dcrirons quoi correspondent certains de ces champs dans la Section 7.3.1, Informations sur le Processeur . Une mthode simple pour extraire une valeur de cette liste est de lire le chier dans un tampon et de lanalyser en mmoire au moyen de sscanf. Le Listing 7.1 montre une faon de faire. Le programme dclare une fonction, get_cpu_clock_speed, qui charge /proc/cpuinfo en mmoire et en extrait la vitesse dhorloge du premier processeur. Listing 7.1 (clock-speed.c) Exemple dutilisation de /proc/cpuinfo

7.2. RPERTOIRES DE PROCESSUS


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38

137

# include < stdio .h > # include < string .h > /* Renvoie la vitesse d horloge du processeur en MHZ , d aprs / proc / cpuinfo . Sur un systme multiprocesseur , renvoie la vitesse du premier . Renvoie zro en cas d erreur . */ float g e t _ c p u _ c l o c k _ s p e e d () { FILE * fp ; char buffer [1024]; size_t bytes_read ; char * match ; float clock_speed ; /* Charge le contenu de / proc / cpuinfo dans le tampon . */ fp = fopen ( " / proc / cpuinfo " , " r " ) ; bytes_read = fread ( buffer , 1 , sizeof ( buffer ) , fp ) ; fclose ( fp ) ; /* Traite le cas o la lecture choue ou le buffer est trop petit . */ if ( bytes_read == 0 || bytes_read == sizeof ( buffer ) ) return 0; /* Place un caractre nul la fin de la chane . */ buffer [ bytes_read ] = " \0 " ; /* Recherche la ligne commenant par " cpu MHz ". */ match = strstr ( buffer , " cpu MHz " ) ; if ( match == NULL ) return 0; /* Analyse la ligne pour extraire la vitesse d horloge . */ sscanf ( match , " cpu MHz : % f " , & clock_speed ) ; return clock_speed ; } int main () { printf ( " CPU clock speed : %4.0 f MHz \ n " , g e t _ c p u _ c l o c k _ s p e e d () ) ; return 0; }

Soyez conscient du fait que les noms, la signication et les formats de sortie des entres du systme de chiers /proc peuvent changer au l des rvisions du noyau Linux. Si vous les utilisez au sein dun programme, assurez-vous que le comportement du programme reste cohrent si une entre est absente ou formate dune faon inconnue.

7.2

Rpertoires de Processus

Le systme de chiers /proc contient un rpertoire par processus sexcutant sur le systme. Le nom de chacun de ces rpertoires est lidentiant du processus auquel il correspond1 . Ces rpertoires apparaissent et disparaissent dynamiquement lors du dmarrage et de larrt dun processus. Chaque dossier contient plusieurs entres donnant accs aux informations sur le processus en cours dexcution. Cest de ces rpertoires de processus que le systme de chiers /proc tire son nom.
Sur certains systmes UNIX, les identiants de processus sont aligns au moyen de zros. Ce nest pas le cas sous GNU/Linux.
1

138

CHAPITRE 7. LE SYSTME DE FICHIERS /PROC

Chaque rpertoire de processus contient ces chiers : cmdline contient la liste dargument du processus. Lentre cmdline est dcrite dans la Section 7.2.2, Liste dArguments dun Processus . cwd est un lien symbolique pointant vers le rpertoire de travail courant du processus (qui peut tre dni par exemple, via un appel chdir). environ contient lenvironnement du processus. Le chier environ est dcrit dans la Section 7.2.3, Environnement de Processus . exe est un lien symbolique pointant vers limage binaire excute par le processus. Lentre exe est dcrite dans la Section 7.2.5, Descripteurs de Fichiers dun Processus . maps contient des informations sur les chiers mis en correspondance avec lespace mmoire du processus. Consultez le Chapitre 5, Section 5.3, Mmoire Mappe pour plus de dtails sur le fonctionnement des chiers mis en correspondance avec la mmoire. Pour chaque chier mis en correspondance, maps ache lintervalle dadresses de lespace mmoire du processus avec lequel le chier est mis en correspondance, les permissions applicables ces adresses, le nom du chier ainsi que dautres informations. Le tableau maps ache pour chaque processus le chier binaire en cours dexcution, les bibliothques partages actuellement charges et les autres chiers que le processus a mis en correspondance avec sa mmoire. root est un lien symbolique vers le rpertoire racine du processus. Gnralement, il sagit dun lien vers /, le rpertoire racine du systme. Le rpertoire racine dun processus peut tre chang par le biais de lappel chroot ou de la commande chroot2 . stat contient des statistiques et des informations sur le statut du processus. Ce sont les mmes donnes que celles prsentes dans le chier status, mais au format numrique brut, sur une seule ligne. Ce format est dicile lire mais plus adapt au traitement automatique par des programmes. Si vous voulez utiliser le chier stat dans vos programmes, consultez la page de manuel de proc qui dcrit son contenu en invoquant man 5 proc. statm contient des informations sur la mmoire utilise par le processus. Le chier statm est dcrit dans la Section 7.2.6, Statistiques Mmoire de Processus . status contient des informations statistiques et statut sur le processus formate de faon tre lisibles par un humain. La Section 7.2.7, Statistiques sur les Processus prsente une description du chier status. Le chier cpu napparat que sur les noyaux Linux SMP. Il contient un rcapitulatif de la consommation en temps (utilisateur et systme) du processus, par processeur. Notez que pour des raisons de scurit, les permissions de certains chiers sont dnies de faon ce que seul lutilisateur propritaire du processus (ou le superutilisateur) puisse y accder.

7.2.1

/proc/self

Une entre particulire dans le systme de chiers /proc facilite son utilisation par un programme pour obtenir des informations sur le processus au sein duquel il sexcute. Lentre
2 Lappel et la commande chroot sortent du cadre de ce livre. Consultez la page de manuel de chroot dans la section 1 pour des informations sur la commande (man 1 chroot) ou la page de manuel de chroot dans la section 2 (man 2 chroot) pour plus dinformations sur lappel.

7.2. RPERTOIRES DE PROCESSUS

139

/proc/self est un lien symbolique vers le rpertoire de /proc correspondant au processus courant. La destination du lien /proc/self dpend du processus qui lutilise : chaque processus voit son propre rpertoire comme destination du lien. Par exemple, le programme du Listing 7.2 utilise la destination du lien /proc/self pour dterminer son identiant de processus (nous ne faisons cela que dans un but dillustration ; lappel de la fonction getpid, dcrite dans le Chapitre 3, Processus , Section 3.1.1, Identiants de Processus constitue une faon beaucoup plus simple darriver au mme rsultat). Ce programme utilise lappel systme readlink, prsent dans la Section 8.11, readlink : Lire des Liens Symboliques , pour extraire la cible du lien. Listing 7.2 (get-pid.c) Rcuprer son PID partir de /proc/self
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

# include < stdio .h > # include < sys / types .h > # include < unistd .h > /* Renvoie l identifiant de processus de l appelant , dtermin partir du lien symbolique / proc / self . */ pid_t g e t _ p i d _ f r o m _ p r o c _ s e l f () { char target [32]; int pid ; /* Lit la cible du lien symbolique . */ readlink ( " / proc / self " , target , sizeof ( target ) ) ; /* La cible est un rpertoire portant de nom du PID . */ sscanf ( target , " % d " , & pid ) ; return ( pid_t ) pid ; } int main () { printf ( " / proc / self renvoie l identifiant % d \ n " , ( int ) g e t _ p i d _ f r o m _ p r o c _ s e l f () ) ; printf ( " getpid () renvoie l identifiant % d \ n " , ( int ) getpid () ) ; return 0; }

7.2.2

Liste dArguments dun Processus

Le chier cmdline contient la liste darguments du processus (consultez le Chapitre 2, crire des Logiciels GNU/Linux de Qualit , Section 2.1.1, La Liste dArguments ). Les arguments sont prsents sous forme dune seule chane de caractres, les arguments tant spars par des NUL. La plupart des fonctions de traitement de chanes de caractres sattendent ce la chane en elle-mme soit termine par un NUL et ne greront pas les NUL contenus dans la chane correctement, vous aurez donc traiter le contenu de cette chane dune faon spciale.

Dans la Section 2.1.1, nous avons prsent un programme, Listing 2.1, qui achait sa liste darguments. En utilisant lentre cmdline du systme de chiers /proc, nous pouvons crer un programme qui ache la liste darguments dun autre processus. Le Listing 7.3 est un programme de ce type ; il ache la liste darguments du processus dont lidentiant est pass en paramtre.

140
NUL et NULL

CHAPITRE 7. LE SYSTME DE FICHIERS /PROC

NUL est le caractre dont la valeur dcimale est 0. Il est dirent de NULL, qui est un pointeur avec

une valeur de 0. En C, une chane de caractres est habituellement termine par un caractre NUL. Par exemple, la chane de caractres "Coucou !" occupe 9 octets car il y a un NUL implicite aprs le point dexclamation indiquant la n de la chane. NULL, par contre, est une valeur de pointeur dont vous pouvez tre sr quelle ne correspondra jamais une adresse mmoire relle dans votre programme. En C et en C++, NUL est reprsent par la constante de caractre 0, ou (char) 0. La dnition de NULL dire selon le systme dexploitation ; sous Linux, NULL est dni comme ((void*)0) en C et tout simplement 0 en C++.

Comme il peut y avoir plusieurs NUL dans le contenu de cmdline et non un seul en n de chane, nous ne pouvons pas dterminer la taille de la chane en utilisant strlen (qui se contente de compter le nombre de caractres jusqu ce quelle rencontre un NUL). Au lieu de cela, nous dterminons la longueur de cmdline grce read, qui renvoie le nombre doctets lus. Listing 7.3 (print-arg-list.c) Ache la Liste dArguments dun Processus
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36

# include # include # include # include # include # include

< fcntl .h > < stdio .h > < stdlib .h > < sys / stat .h > < sys / types .h > < unistd .h >

/* Affiche la liste d aguments , un par ligne , du processus dont l identifiant est pass en paramtre . */ void p r i n t _ p r o c e s s _ a r g _ l i s t ( pid_t pid ) { int fd ; char filename [24]; char arg_list [1024]; size_t length ; char * next_arg ; /* Gnre le nom du fichier cmdline pour le processus . */ snprintf ( filename , sizeof ( filename ) , " / proc /% d / cmdline " , ( int ) pid ) ; /* Lit le contenu du fichier . */ fd = open ( filename , O_RDONLY ) ; length = read ( fd , arg_list , sizeof ( arg_list ) ) ; close ( fd ) ; /* read n ajoute pas de NULL la fin du tempon , nous le faisons ici . */ arg_list [ length ] = \0 ; /* Boucle sur les arguments . Ceux - ci sont spars par des NUL . */ next_arg = arg_list ; while ( next_arg < arg_list + length ) { /* Affiche l argument . Chaque argument est termin par NUL , nous le traitons donc comme une chane classique . */ printf ( " % s \ n " , next_arg ) ; /* Avance l argument suivant . Puisque chaque argument est termin par NUL , strlen renvoie la longueur de l argument , pas celle de la liste . */

7.2. RPERTOIRES DE PROCESSUS


37 38 39 40 41 42 43 44 45 46

141

next_arg += strlen ( next_arg ) + 1; } } int main ( int argc , char * argv []) { pid_t pid = ( pid_t ) atoi ( argv [1]) ; p r i n t _ p r o c e s s _ a r g _ l i s t ( pid ) ; return 0; }

Par exemple, supposons que le processus 372 soit le dmon de journalisation systme, syslogd.
% ps 372 PID TTY 372 ? STAT S TIME COMMAND 0:00 syslogd -m 0

% ./print-arg-list 372 syslogd -m 0

Dans ce cas, syslogd a t invoqu avec les arguments -m 0.

7.2.3

Environnement de Processus

Le chier environ contient lenvironnement du processus (consultez la Section 2.1.6, LEnvironnement ). Comme pour cmdline, les direntes variables denvironnement sont spares par des NUL. Le format de chaque lment est le mme que celui utilis dans la variable environ, savoir VARIABLE=valeur. Le Listing 7.4 prsente une gnralisation du programme du Listing 2.3 de la Section 2.1.6. Cette version prend un identiant du processus sur la ligne de commande et ache son environnement en le lisant partir de /proc. Listing 7.4 (print-environment.c) Ache lEnvironnement dun Processus
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

# include # include # include # include # include # include

< fcntl .h > < stdio .h > < stdlib .h > < sys / stat .h > < sys / types .h > < unistd .h >

/* Affiche l environnement du processus dont l identifiant est pass en paramtre , une variable par ligne . */ void p r i n t _ p r o c e s s _ e n v i r o n m e n t ( pid_t pid ) { int fd ; char filename [24]; char environment [8192]; size_t length ; char * next_var ; /* Gnre le nom du fichier environ pour le processus . */ snprintf ( filename , sizeof ( filename ) , " / proc /% d / environ " , ( int ) pid ) ; /* Lit le contenu du fichier . */ fd = open ( filename , O_RDONLY ) ;

142
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45

CHAPITRE 7. LE SYSTME DE FICHIERS /PROC


length = read ( fd , environment , sizeof ( environment ) ) ; close ( fd ) ; /* read ne place pas de caractre NUL la fin du tampon . */ environment [ length ] = \0 ; /* Boucle sur les variables . Elles sont spares par des NUL . */ next_var = environment ; while ( next_var < environment + length ) { /* Affiche la variable . Elle est termine par un NUL , on la traite donc comme une chane ordinaire . */ printf ( " % s \ n " , next_var ) ; /* Passe la variable suivante . Puisque chaque variable est termine par un NUL , strlen calcule bien la taille de la prochaine variable , et non pas de toute la liste . */ next_var += strlen ( next_var ) + 1; } } int main ( int argc , char * argv []) { pid_t pid = ( pid_t ) atoi ( argv [1]) ; p r i n t _ p r o c e s s _ e n v i r o n m e n t ( pid ) ; return 0; }

7.2.4

Excutable de Processus

Lentre exe pointe vers le chier binaire excut par le processus. Dans la Section 2.1.1, nous avons expliqu que le nom de ce chier est habituellement pass comme premier lment de la liste dargument. Notez cependant quil sagit dune convention ; un programme peut tre invoqu avec nimporte quelle liste darguments. Utiliser lentre exe du systme de chiers /proc est une faon plus able de dterminer le chier binaire en cours dexcution. De mme, il est possible dextraire lemplacement absolu de lexcutable, partir du systme de chiers /proc. Pour beaucoup de programmes, les chiers auxiliaires se trouvent dans des rpertoires relatifs lexcutable, il est donc ncessaire de dterminer o se trouve rellement le chier binaire. La fonction get_executable_path du Listing 7.5 dtermine le chemin du chier binaire sexcutant au sein du processus appelant en examinant le lien symbolique /proc/self/exe. Listing 7.5 (get-exe-path.c) Obtient lEmplacement du Fichier Binaire en Cours dExcution
1 2 3 4 5 6 7 8 9 10 11 12 13 14

# include # include # include # include

< limits .h > < stdio .h > < string .h > < unistd .h >

/* Recherche l emplacement du fichier binaire en cours d excution . Ce chemin est plac dans BUFFER , de taille LEN . Renvoie le nombre de caractres dans le chemin ou -1 en cas d erreur . */ size_t g e t _ e x e c u t a b l e _ p a t h ( char * buffer , size_t len ) { char * path_end ; /* Lit la cible de / proc / self / exe . */ if ( readlink ( " / proc / self / exe " , buffer , len ) <= 0)

7.2. RPERTOIRES DE PROCESSUS


15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39

143

return -1; /* Recherche la dernire occurrence du caractre slash . */ path_end = strrchr ( buffer , " / " ) ; if ( path_end == NULL ) return -1; /* Se place sur le caractre suivant le dernier slash . */ ++ path_end ; /* Rcupre le rpertoire contenant le programme en tronquant le chemin aprs le dernier slash . */ * path_end = " \0 " ;

/* La longueur du chemin est le nombre de caractres jusqu au dernier slash . */ return ( size_t ) ( path_end - buffer ) ; } int main () { char path [ PATH_MAX ]; g e t _ e x e c u t a b l e _ p a t h ( path , sizeof ( path ) ) ; printf ( " ce programme se trouve dans le rpertoire % s \ n " , path ) ; return 0; }

7.2.5

Descripteurs de Fichiers dun Processus

Le rpertoire fd contient des sous-rpertoires correspondant aux descripteurs de chiers ouverts par un processus. Chaque entre est un lien symbolique vers le chier ou le priphrique dsign par le descripteur de chier. Vous pouvez lire ou crire sur ces liens symboliques ; cela revient crire ou lire depuis le chier ou le priphrique ouvert dans le processus cible. Les noms des entres du sous-rpertoire fd correspondent aux numros des descripteurs de chiers. Voici une astuce amusante que vous pouvez essayer avec les entres fd de /proc. Ouvrez une nouvelle fentre de terminal et rcuprez lidentiant de processus du processus shell en lanant ps.
% ps PID TTY TIME CMD 1261 pts/4 00:00:00 bash 2455 pts/4 00:00:00 ps

Dans ce cas, le shell (bash) sexcute au sein du processus 1261. Ouvrez maintenant une seconde fentre et jetez un oeil au contenu du sous-rpertoire fd pour ce processus.
% ls -l /proc/1261/fd total 0 lrwx-----1 samuel samuel 64 Jan 30 01:02 0 -> /dev/pts/4 lrwx-----1 samuel samuel 64 Jan 30 01:02 1 -> /dev/pts/4 lrwx-----1 samuel samuel 64 Jan 30 01:02 2 -> /dev/pts/4

(Il peut y avoir dautres lignes correspondant dautres descripteurs de chiers ouverts). Souvenezvous de ce que nous avons dit dans la Section 2.1.4, E/S Standards : les descripteurs de chiers 0, 1 et 2 sont initialiss pour pointer vers lentre, la sortie et la sortie derreurs standards, respectivement. Donc, en crivant dans le chier /proc/1261/fd/1 vous pouvez

144

CHAPITRE 7. LE SYSTME DE FICHIERS /PROC

crire sur le priphrique attach stdout pour le processus shell dans ce cas, un pseudo TTY correspondant la premire fentre. Dans la seconde fentre, essayez dcrire un message dans ce chier :
% echo "Coucou." >> /proc/1261/fd/1

Le texte apparat dans la premire fentre. Les descripteurs de chiers autres que lentre, la sortie et la sortie derreurs standards apparaissent dans le sous-rpertoire fd. Le Listing 7.6 prsente un programme qui se contente douvrir un descripteur pointant vers un chier pass sur la ligne de commande et de boucler indniment. Listing 7.6 (open-and-spin.c) Ouvre un Fichier en Lecture
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

# include # include # include # include # include

< fcntl .h > < stdio .h > < sys / stat .h > < sys / types .h > < unistd .h >

int main ( int argc , char * argv []) { const char * const filename = argv [1]; int fd = open ( filename , O_RDONLY ) ; printf ( " dans le processus %d , le descripteur % d pointe vers % s \ n " , ( int ) getpid () , ( int ) fd , filename ) ; while (1) ; return 0; }

Essayez de le lancer dans une fentre :


% ./open-and-spin /etc/fstab dans le processus 2570, le descripteur 3 pointe vers /etc/fstab

Dans une autre fentre, observez le contenu du sous-rpertoire fd correspondant ce processus.


% ls -l /proc/2570/fd total 0 lrwx-----1 samuel samuel lrwx------ 1 samuel samuel 64 lrwx------ 1 samuel samuel 64 lr-x------ 1 samuel samuel 64

64 Jan Jan 30 Jan 30 Jan 30

30 01:30 0 01:30 1 -> 01:30 2 -> 01:30 3 ->

-> /dev/pts/2 /dev/pts/2 /dev/pts/2 /etc/fstab

Notez que lentre du descripteur de chier 3 est lie au chier /etc/fstab vers lequel pointe le descripteur. Les descripteurs de chiers peuvent pointer vers des sockets ou des tubes (consultez le Chapitre 5 pour plus dinformations). Dans un tel cas, la cible du lien symbolique correspondant au descripteur de chier indiquera socket ou pipe au lieu de pointer vers un chier ou un priphrique classique.

7.2.6

Statistiques Mmoire de Processus

Lentre statm contient une liste de sept nombres, spars par des espaces. Chaque valeur correspond au nombre de pages mmoire utilises par le processus dans une catgorie donne. Les voici , dans lordre :

7.3. INFORMATIONS SUR LE MATRIEL

145

Taille totale du processus. Taille du processus rsident en mmoire. Mmoire partage avec dautres processus cest--dire les pages mises en correspondance avec lespace mmoire du processus et dau moins un autre (comme les bibliothques partages ou les pages en copie lcriture non modies). Taille du texte du processus cest--dire la taille du code charg. Taille des bibliothques partages charges pour le processus. Mmoire utilise pour la pile du processus. Le nombre de pages modies par le programme.

7.2.7

Statistiques sur les Processus

Lentre status contient un certain nombre dinformations propos du processus, formates de faon tre comprhensibles par un humain. Parmi ces informations on trouve lidentiant du processus et de son pre, les identiants dutilisateur et de groupe rel et eectif, lutilisation mmoire et des masques binaires indiquant quels signaux sont intercepts, ignors et bloqus.

7.3

Informations sur le Matriel

Plusieurs autres chiers du systme de chiers /proc donnent accs des informations sur le matriel. Bien quelles nintressent gnralement que les administrateurs systme, ces informations peuvent parfois tre utiles aux programmeurs. Nous prsenterons ici les plus utiles.

7.3.1

Informations sur le Processeur

Comme nous lavons dit prcdemment, /proc/cpuinfo contient des informations sur le ou les processeur(s) du systme. Le champ Processor indique le numro du processeur ; il est 0 sur un systme monoprocesseur. Les champs Vendor, CPU Family, Model et Stepping vous permettent de dterminer le modle et la rvision exacts du processeur. Plus utile, le champ Flags indique quels indicateurs sont positionns, ils indiquent les fonctionnalits disponibles pour processeur. Par exemple, mmx, indique que les instructions MMX3 sont disponibles. La plupart des informations renvoyes par /proc/cpuinfo sont obtenues partir de linstruction assembleur x86 cpuid. Il sagit dun mcanisme de bas niveau permettant dobtenir des informations sur le processeur. Pour mieux comprendre lachage de /proc/cpuinfo, consultez la documentation de linstruction cpuid dans le IA-32 Intel Architecture Software Developers Manual, Volume 2 : Instruction Set Reference dIntel (en anglais). Ce manuel est disponible sur http://developer.intel.com/design. Le dernier champ, bogomips, est une valeur propre Linux. Il sagit dun indicateur de la vitesse du processeur mesure au moyen dune boucle et qui est donc relativement mauvais.
3 Reportez-vous au IA-32 Intel Architecture Software Developers Manuel pour une documentation complte sur les instructions MMX, et consultez le Chapitre 9, Code Assembleur en Ligne , dans ce livre pour plus dinformations sur leur utilisation au sein de programmes GNU/Linux.

146

CHAPITRE 7. LE SYSTME DE FICHIERS /PROC

7.3.2

Informations sur les Priphriques

Le chier /proc/devices dresse la liste des numros de priphrique majeurs pour les priphriques caractres et bloc disponibles pour le systme. Consultez le Chapitre 6, Priphriques , pour plus dinformations sur les types de priphriques et leurs numros.

7.3.3

Informations sur le Bus PCI

Le chier /proc/pci donne un aperu des priphriques attachs au(x) bus PCI. Il sagit de cartes dextension PCI mais cela peut aussi inclure les priphriques intgrs la carte mre ainsi que les cartes graphiques AGP. Le listing inclut le type de priphrique ; son identiant et son constructeur ; son nom sil est disponible ; des informations sur ses capacits et sur les ressources PCI quil utilise.

7.3.4

nformations sur le Port Srie

Le chier /proc/tty/driver/serial contient des informations de conguration et des statistiques sur les ports srie. Ceux-ci sont numrots partir de 04 . Les informations de conguration sur les ports srie peuvent galement tre obtenues, ainsi que modies, via la commande setserial. Cependant, /proc/tty/driver/serial contient des statistiques supplmentaires sur le nombre dinterruptions reues par chaque port srie. Par exemple, cette ligne de /proc/tty/driver/serial pourrait dcrire le port srie 1 (qui serait COM2 sous Windows) :
1: uart:16550A port:2F8 irq:3 baud:9600 tx:11 rx:0

Elle indique que le port srie est gr par un USART (Universal Synchronous Asynchronous Receiver Transmitter) de type 16550, utilise le port 0x2f8 et lIRQ 3 pour la communication et tourne 9 600 bauds. Le port a reu 11 interruptions dmission et 0 interruption de rception. Consultez la Section 6.4, Priphriques Matriels pour des informations sur les priphriques srie.

7.4

Informations sur le Noyau

La plupart des entres de /proc donnent accs des informations sur la conguration et ltat du noyau en cours dexcution. Certaines de ces entres sont la racine de /proc ; dautres se trouvent sous /proc/sys/kernel.

7.4.1

Informations de Version

Le chier /proc/version contient une chane dcrivant la version du noyau et donnant des informations sur sa compilation. Elle comprend galement des informations sur la faon dont il a t compil : par qui, sur quelle machine, quand et avec quel compilateur par exemple :
Notez que sous DOS et Windows, les port srie sont numrots partir de 1, donc COM1 correspond au port srie 0 sous Linux.
4

7.4. INFORMATIONS SUR LE NOYAU


% cat /proc/version Linux version 2.2.14-5.0 (root@porky.devel.redhat.com) (gcc version egcs-2.91.66 19990314/Linux (egcs-1.1.2 release)) #1 Tue Mar 7 21:07:39 EST 2000

147

Cette commande indique que le systme sexcute sur une version 2.2.14 du noyau Linux, compil avec la version 1.1.2 de EGCS (EGC, lExperimental GNU Compiler System, tait un prcurseur du projet GCC actuel). Les informations les plus intressantes de cet achage, le nom de lOS et la version et la rvision du noyau, sont galement disponibles dans des chiers individuels de /proc. Il sagit de /proc/sys/kernel/ostype, /proc/sys/kernel/osrelease et /proc/sys/kernel/version, respectivement.
% cat /proc/sys/kernel/ostype Linux % cat /proc/sys/kernel/osrelease 2.2.14-5.0 % cat /proc/sys/kernel/version #1 Tue Mar 7 21:07:39 EST 2000

7.4.2

Noms dHte et de Domaine

Les chiers /proc/sys/kernel/hostname et /proc/sys/kernel/domainname contiennent les noms dhte et de domaine de lordinateur, respectivement. Ces informations sont les mmes que celles renvoyes par lappel systme uname, dcrit dans la Section 8.15.

7.4.3

Utilisation Mmoire

Le chier /proc/meminfo contient des informations sur lutilisation mmoire du systme. Les informations sont prsentes la fois pour la mmoire physique et pour lespace dchange (swap). Les trois premires lignes prsentent les totaux, en octets ; les lignes suivantes reprennent ces informations en kilooctets par exemple :
% cat /proc/meminfo total: used: free: shared: buffers: cached: Mem: 529694720 519610368 10084352 82612224 10977280 82108416 Swap: 271392768 44003328 227389440 MemTotal: 517280 kB MemFree: 9848 kB MemShared: 80676 kB Buffers: 10720 kB Cached: 80184 kB BigTotal: 0 kB BigFree: 0 kB SwapTotal: 265032 kB SwapFree: 222060 kB

Cet achage montre un total de 512 Mo de mmoire physique, dont environ 9 Mo sont libres et 258 Mo despace dchange (swap), dont 216 Mo sont libres. Dans la ligne correspondant la mmoire physique, trois autres valeurs sont prsentes : La colonne Shared ache la quantit de mmoire partage actuellement alloue sur le systme (consultez la Section 5.1, Mmoire Partage ).

148

CHAPITRE 7. LE SYSTME DE FICHIERS /PROC

La colonne Buers donne la mmoire alloue par Linux pour les tampons des priphriques bloc. Ces tampons sont utiliss par les pilotes de priphriques pour conserver les blocs de donnes lus ou crits sur le disque. La colonne Cached indique la mmoire alloue par Linux pour le cache de page. Cette mmoire est utilise pour mettre en cache les accs des chiers mis en correspondance avec la mmoire. Vous pouvez utiliser la commande free pour acher les mmes informations sur la mmoire.

7.5

Lecteurs et Systmes de Fichiers

Le systme de chiers /proc contient galement des informations sur les lecteurs de disques prsents sur le systme et les systmes de chiers monts qui y correspondent.

7.5.1

Systmes de Fichiers

Lentre /proc/filesystems dresse la liste de tous les types de systmes de chiers connus par le noyau. Notez quelle nest pas trs utile car elle nest pas complte : des systmes de chiers peuvent tre chargs et dchargs dynamiquement sous forme de modules noyau. Le contenu de /proc/filesystems ne rete que les types de systmes de chiers lis statiquement au noyau ou actuellement chargs. Dautres types de systmes de chiers peuvent tre disponibles sous forme de modules mais ne pas tre chargs.

7.5.2

Lecteurs et Partitions

Le systme de chiers /proc donne galement des informations sur les priphriques connects aux contrleurs IDE ou SCSI (si le systme en dispose). Sur un systme classique, le rpertoire /proc/ide peut contenir un ou deux sous-rpertoires, ide0 et ide1, relatifs aux contrleurs primaire et secondaire du systme5 . Ils contiennent dautres sous-rpertoires correspondants aux priphriques physiques connects ces contrleurs. Les rpertoires de chaque contrleur ou priphrique peuvent tre absents si Linux na dtect aucun priphrique connect. Les chemins absolus des quatre priphriques IDE possibles sont prsents dans le Tableau 7.1.

Tab. 7.1 Chemins Absolus des Quatre Priphriques IDE Possibles Contrleur Priphrique Rpertoire Primaire Matre /proc/ide/ide0/hda/ Primaire Esclave /proc/ide/ide0/hdb/ Secondaire Matre /proc/ide/ide1/hdc/ Secondaire Esclave /proc/ide/ide1/hdd/
Sil est correctement congur, le noyau Linux peut prendre en charge des contrleurs IDE supplmentaires. Ils sont numrots de faon squentielle partir de ide2.
5

7.5. LECTEURS ET SYSTMES DE FICHIERS

149

Consultez la Section 6.4, Priphriques Matriels , pour plus dinformations sur les noms des priphriques IDE. Chaque rpertoire de priphrique IDE contient plusieurs entres donnant accs des informations didentication et de conguration sur le priphrique. Voici les plus utiles : model contient la chane didentication du modle de priphrique. media donne le type de mdia lu par le priphrique. Les valeurs possibles sont disk, cdrom, tape (cassette), floppy (disquette) et UNKNOWN (inconnu). capacity indique la capacit du priphrique en blocs de 512 octets. Notez que pour les lecteurs de CD-ROM, cette valeur sera 231 1, et non pas la capacit du disque prsent dans le lecteur. La valeur de capacity reprsente la capacit total du disque physique ; la capacit des systmes de chiers contenus dans les partitions du disque sera plus petite. Par exemple, ces commandes vous montrent comment dterminer le type de mdia et le priphrique primaire connect au contrleur IDE secondaire. Dans ce cas, il sagit dun lecteur CD-ROM Toshiba.
% cat /proc/ide/ide1/hdc/media cdrom % cat /proc/ide/ide1/hdc/model TOSHIBA CD-ROM XM-6702B

Si des priphriques SCSI sont prsents, /proc/scsi/scsi contient la liste de leurs identiants. Par exemple, son contenu peut ressembler cela :
% cat /proc/scsi/scsi Attached devices: Host: scsi0 Channel: 00 Id: 00 Lun: 00 Vendor: QUANTUM Model: ATLAS_V__9_WLS Type: Direct-Access Host: scsi0 Channel: 00 Id: 04 Lun: 00 Vendor: QUANTUM Model: QM39100TD-SW Type: Direct-Access

Rev: 0230 ANSI SCSI revision: 03 Rev: N491 ANSI SCSI revision: 02

Cet ordinateur contient un contrleur SCSI simple canal (appel scsi0), auquel sont connects deux disques Quantum, avec les identiants de priphrique SCSI 0 et 4. Le chier /proc/partitions contient des informations sur les partitions des lecteurs de disque reconnus. Pour chaque partition, il dcrit les numros de priphrique majeur et mineur, le nombre de blocs de 1024 octets quelle contient et le nom du priphrique correspondant cette partition. Lentre /proc/sys/dev/cdrom/info donne diverses informations sur les fonctionnalits des lecteurs CD-ROM. Les informations nont pas besoin dexplication.
% cat /proc/sys/dev/cdrom/info CD-ROM information, Id: cdrom.c 2.56 1999/09/09 drive name: hdc drive speed: 48 drive # of slots: 0 Can close tray: 1 Can open tray: 1 Can lock tray: 1 Can change speed: 1 Can select disk: 0

150
Can read multisession: Can read MCN: 1 Reports media changed: Can play audio: 1 1 1

CHAPITRE 7. LE SYSTME DE FICHIERS /PROC

7.5.3

Points de Montage

Le chier /proc/mounts donne un aperu des systmes de chiers monts. Chaque ligne correspond un descripteur de montage et donne le priphrique mont, le point de montage et dautres informations. Notez que /proc/mounts contient les mmes informations que le chier traditionnel /etc/mtab, qui est automatiquement mis jour par la commande mount. Voici les dirents lments dun descripteur de point de montage : Le premier lment de la ligne est le priphrique mont (consultez le autorefchap :peripheriques). Le second lment est le point de montage, lemplacement dans le systme de chiers racine o le contenu du systme de chiers apparat. Pour le systme de chiers racine lui-mme, le point de montage est /. Pour les espaces dchange, le point de montage est not swap. Le troisime lment est le type de systme de chiers. Actuellement, la plupart des systmes GNU/Linux utilisent le systme de chiers ext2 (ou ext3) pour les lecteurs de disques, mais des disques DOS ou Windows peuvent tre monts et seront alors de type fat ou vfat. La plupart des CD-ROM sont au format iso9660. Consultez la page de manuel de la commande mount pour une liste des types de systmes de chiers. Les deux derniers lments des lignes de /proc/mounts sont toujours 0 et nont pas de signication. Consultez la page de manuel de fstab pour plus de dtail sur le format des descripteurs de point de montage6 . GNU/Linux dispose de fonction destines vous aider analyser ces descripteurs ; consultez la page de manuel de la fonction getmntent pour plus dinformations sur leur utilisation.

7.5.4

Verrous

La Section 8.3, fcntl : Verrous et Autres Oprations sur les Fichiers , dcrit lutilisation de lappel systme fcntl pour manipuler des verrous en lecture ou en criture sur les chiers. Le chier /proc/locks dcrit tous les verrous de chiers actuellement en place sur le systme. Chaque ligne correspond un verrou. Pour les verrous crs avec fcntl, les deux premires mentions de la ligne sont POSIX ADVISORY. La troisime est WRITE ou READ, selon le type de verrou. Le nombre qui suit les lidentiant du processus possdant le verrou. Les trois chires suivants, spars par deux-points, sont les numros de priphrique majeur et mineur du priphrique sur lequel se trouve le chier et le numro dinode, qui situe le chier dans le systme de chiers. Le reste de la ligne est constitu de valeurs utilises en interne par le noyau et qui ne sont gnralement daucune utilit.
6

Le chier /etc/fstab dcrit la conguration des points de montage statiques dun systme GNU/Linux.

7.6. STATISTIQUES SYSTME

151

Traduire le contenu de /proc/locks en informations utiles demande un petit travail dinvestigation. Vous pouvez observer lvolution de /proc/locks en excutant le programme du Listing 8.2 qui cre un verrou en lecture sur le chier /tmp/test-file.
% touch /tmp/test-file % ./lock-file /tmp/test-file fichier /tmp/test-file ouverture de /tmp/test-file verrouillage verrouill ; appuyez sur entre pour dverrouiller...

Dans une autre fentre, observez le contenu de /proc/locks.


% cat /proc/locks 1: POSIX ADVISORY WRITE 5467 08:05:181288 0 2147483647 d1b5f740 00000000 dfea7d40 00000000 00000000

Il peut y avoir des lignes supplmentaires si dautres programmes ont pos des verrous. Dans notre cas, 5467 est lidentiant de processus du programme lock-file. Utilisez ps pour observer ce quest en train dexcuter ce processus.
% ps 5467 PID TTY STAT TIME COMMAND 5467 pts/28 S 0:00 ./lock-file /tmp/test-file

Le chier verrouill, /tmp/test-file, se trouve sur le priphrique avec les numros majeur et mineur 8 et 5, respectivement. Ces nombres correspondent /dev/sda5.
% df /tmp Filesystem 1k-blocks Used Available Use% Mounted on /dev/sda5 8459764 5094292 2935736 63% / % ls -l /dev/sda5 brw-rw---1 root disk 8, 5 May 5 1998 /dev/sda5

Le chier /tmp/test-file se trouve au niveau de linode 181 288 sur ce priphrique.


% ls --inode /tmp/test-file 181288 /tmp/test-file

Consultez la Section 6.2, Numros de Priphrique pour plus dinformations sur ceux-ci.

7.6

Statistiques Systme

Deux entres de /proc contiennent des statistiques utiles sur le systme. Le chier /proc/loadavg donne des informations sur sa charge. Les trois premiers nombres reprsentent le nombre de tches actives sur le systme processus en cours dexcution avec une moyenne sur les 1, 5 et 15 dernires minutes. Le chire suivant indique le nombre courant de tches excutables processus programms pour tre excuts, loppos de ceux bloqus dans un appel systme et le nombre total de processus sur le systme. Le dernier champ correspond lidentiant du processus ayant eu la main le plus rcemment. Le chier /proc/uptime contient le temps coul depuis le dmarrage du systme, ainsi que la dure pendant laquelle le systme a t inactif. Ces deux valeurs sont donnes sous forme dcimale, en secondes.

152
% cat /proc/uptime 3248936.18 3072330.49

CHAPITRE 7. LE SYSTME DE FICHIERS /PROC

Le programme du Listing 7.7 extrait le temps depuis lequel le systme est dmarr ainsi que sa dure dinactivit et les ache de faon lisible. Listing 7.7 (print-uptime.c) Ache des Informations sur les Temps Systme
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

# include < stdio .h > /* Affiche une dure sur la sortie standard de faon lisible . TIME est la dure , en secondes , et LABEL est une lgende courte . */ void print_time ( char * label , long time ) { /* Constantes de conversion . */ const long minute = 60; const long hour = minute * 60; const long day = hour * 24; /* Affiche la dure . */ printf ( " % s : % ld jours , % ld :%02 ld :%02 ld \ n " , label , time / day , ( time % day ) / hour , ( time % hour ) / minute , time % minute ) ; } int main () { FILE * fp ; double uptime , idle_time ; /* Lit le temps coul depuis le dmarrage et le temps d inactivit partir de / proc / uptime . */ fp = fopen ( " / proc / uptime " , " r " ) ; fscanf ( fp , " % lf % lf \ n " , & uptime , & idle_time ) ; fclose ( fp ) ; /* L affiche . */ print_time ( " Temps coul depuis le dmarrage " , ( long ) uptime ) ; print_time ( " Temps d inactivit " , ( long ) idle_time ) ; return 0; }

La commande uptime et lappel systme sysinfo (dcrit dans la Section 8.14, sysinfo : Obtenir des Statistiques Systme ) permettent galement dobtenir le temps coul depuis le dmarrage. La commande uptime ache galement les moyennes de charge systme contenues dans /proc/loadavg.

Chapitre 8

Appels Systme Linux


usquici, nous avons prsent diverses fonctions que votre programme peut utiliser pour accomplir des actions relatives au systme, comme analyser des options de ligne de commande, manipuler des processus et mapper de la mmoire. Si vous y regardez de plus prs, vous remarquerez que ces fonctions se rpartissent en deux catgories, selon la faon dont elles sont implantes. Une fonction de bibliothque est une fonction ordinaire qui se trouve dans une bibliothque externe votre programme. La plupart des fonctions que nous avons prsent jusquici se trouvent dans la bibliothque standard du C, libc. Par exemple, getopt_long et mkstemp en font partie. Un appel une fonction de bibliothque est identique lappel de nimporte quelle autre fonction. Les arguments sont placs dans des registres du processeur ou sur la pile et lexcution est transfre au dbut de la fonction qui se trouve gnralement dans une bibliothque partage. Un appel systme est implant au sein du noyau Linux. Lorsquun programme eectue un appel systme, les arguments sont mis en forme et transfrs au noyau qui prend la main jusqu la n de lappel. Un appel systme nest pas identique un appel de fonction classique et une procdure spcique est ncessaire pour transfrer le contrle au noyau. Cependant, la bibliothque C GNU (limplmentation de la bibliothque standard du C fournie avec les systmes GNU/Linux) masque les appels systmes par des fonctions classiques an quils soient plus simples utiliser. Les fonctions dE/S de bas niveau comme open ou read font partie des appels systmes Linux. Les appels systme Linux constituent linterface de base entre les programmes et le noyau Linux. chaque appel correspond une opration ou une fonctionnalit de base. Certains appels sont trs puissants et inuent au niveau du systme. Par exemple, il est possible dteindre le systme ou dutiliser des ressources systme tout en interdisant leur accs aux autres utilisateurs. De tels appels ne sont utilisables que par des programmes sexcutant avec les privilges superutilisateur (lanc par root). Ils chouent si le programme ne dispose pas de ces droits. 153

154

CHAPITRE 8. APPELS SYSTME LINUX

Notez quune fonction de bibliothque peut son tour appeler une ou plusieurs fonctions de bibliothques ou appels systme. Linux propose prs de 300 appels systme. La liste des appels disponibles sur votre systme se trouve dans le chier /usr/include/asm/unistd.h. Certains ne sont destins qu tre utiliss en interne par le noyau et dautres ne servent que pour limplmentation de certaines bibliothques. Dans ce chapitre, nous vous prsenterons ceux qui nous semblent les plus susceptibles de vous servir. La plupart sont dclars dans le chier den-tte /usr/include/asm/unistd.h.

8.1

Utilisation de strace

Avant de commencer parler des appels systme, il est ncessaire de prsenter une commande qui peut vous en apprendre beaucoup sur les appels systme. La commande strace trace lexcution dun autre programme en dressant la liste des appels systme quil eectue et des signaux quil reoit. Pour observer lenchanement des appels systme et des signaux dun programme invoquez simplement strace, suivi du nom du programme et de ses arguments. Par exemple, pour observer les appels systmes eectus par la commande hostname1 , utilisez cette commande :
% strace hostname

Elle vous achera un certain nombre dinformations. Chaque ligne correspond un appel systme. Pour chaque appel, vous trouverez son nom suivi de ses arguments (ou de leur abrviation sils sont trop longs) et de sa valeur de retour. Lorsque cest possible, strace utilise des noms symboliques pour les arguments et la valeur de retour plutt que leur valeur numrique et ache les dirents champs des structures passes via un pointeur lappel systme. Notez que strace ne montre pas les appels de fonctions classiques. La premire ligne ache par strace hostname montre lappel systme execve qui invoque le programme hostname2 :
execve("/bin/hostname", ["hostname"], [/* 49 vars */]) = 0

Le premier argument est le nom du programme excuter ; le second sa liste darguments, constitue dun seul lment ; et le troisime lenvironnement du programme que strace nache pas par souci de concision. Les 30 lignes suivantes, approximativement, font partie du mcanisme qui charge la bibliothque standard du C partir dun chier de bibliothque partag. Les appels systmes eectivement utiliss par le programme pour fonctionner se trouvent vers la n de lachage. Lappel systme uname permet dobtenir le nom dhte du systme partir du noyau :
uname({sys="Linux", node="myhostname", ...}) = 0
1 Invoque sans aucune option, la commande hostname ache simplement le nom dhte de la machine sur la sortie standard. 2 Sous Linux, la famille de fonction exec est implmente via lappel systme execve.

8.2. ACCESS : TESTER LES PERMISSIONS DUN FICHIER

155

Remarquez que strace donne le nom des champs (sys et node) de la structure passe en argument. Cette structure est renseigne par lappel systme Linux place le nom du systme dexploitation dans le champ sys et le nom dhte du systme dans le champ node. Lappel systme uname est dtaill dans la Section 8.15, uname . Enn, lappel systme write ache les informations. Souvenez-vous que le descripteur de chier 1 correspond la sortie standard. Le troisime argument est le nombre de caractres crire et la valeur de retour est le nombre de caractres eectivement crits.
write(1, "myhostname\n", 11) = 11

Lachage peut paratre un peu confus lorsque vous excutez strace car la sortie du programme hostname est mlange avec celle de strace. Si le programme que vous analysez ache beaucoup dinformations, il est parfois plus pratique de rediriger la sortie de strace vers un chier. Pour cela, utilisez loption -o nom_de_fichier. La comprhension de tout ce quache strace ncessite une bonne connaissance du fonctionnement du noyau Linux et de lenvironnement dexcution ce qui prsente un intrt limit pour les programmeurs dapplication. Cependant, une comprhension de base est utile pour dboguer des problmes sournois et comprendre le fonctionnement dautres programmes.

8.2

access : Tester les Permissions dun Fichier

Lappel systme access dtermine si le processus appelant le droit daccder un chier. Il peut vrier toute combinaison des permissions de lecture, criture ou excution ainsi que tester lexistence dun chier. Lappel access prend deux argument. Le premier est le chemin daccs du chier tester. Le second un OU binaire entre R_OK, W_OK et X_OK, qui correspondent aux permissions en lecture, criture et excution. La valeur de retour est zro si le processus dispose de toutes les permissions passes en paramtre. Si le chier existe mais que le processus na pas les droits dessus, access renvoie -1 et positionne errno EACCES (ou EROFS si lon a test les droits en criture dun chier situ sur un systme de chiers en lecture seule). Si le second argument est F_OK, access vrie simplement lexistence du chier. Si le chier existe, la valeur de retour est 0 ; sinon, elle vaut -1 et errno est positionn ENOENT. errno peut galement tre positionn EACCES si lun des rpertoires du chemin est inaccessible. Le programme du Listing 8.1 utilise access pour vrier lexistence dun chier et dterminer ses permissions en lecture et en criture. Spciez le nom du chier vrier sur la ligne de commande. Listing 8.1 (check-access.c) Vrier les Droits dAccs un Fichier
1 2 3 4 5 6 7 8 9

# include < errno .h > # include < stdio .h > # include < unistd .h > int main ( int argc , char * argv []) { char * path = argv [1]; int rval ;

156
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38

CHAPITRE 8. APPELS SYSTME LINUX


/* Vrifie l existence du fichier . */ rval = access ( path , F_OK ) ; if ( rval == 0) printf ( " % s existe \ n " , path ) ; else { if ( errno == ENOENT ) printf ( " % s n existe pas \ n " , path ) ; else if ( errno == EACCES ) printf ( " % s n est pas accessible \ n " , path ) ; return 0; } /* Vrifie l accs en lecture . */ rval = access ( path , R_OK ) ; if ( rval == 0) printf ( " % s est accessible en lecture \ n " , path ) ; else printf ( " % s n est pas accessible en lecture ( accs refus ) \ n " , path ) ; /* Vrifie l accs en criture . */ rval = access ( path , W_OK ) ; if ( rval == 0) printf ( " % s est accessible en criture \ n " , path ) ; else if ( errno == EACCES ) printf ( " % s n est pas accessible en criture ( accs refus ) \ n " , path ) ; else if ( errno == EROFS ) printf ( " % s n est pas accessible en criture ( SF en lecture seule ) \ n " , path ) ; return 0; }

Par exemple, pour tester les permissions daccs un chier appel README situ sur un CDROM, invoquez le programme comme ceci :
% ./check-access /mnt/cdrom/README /mnt/cdrom/README existe /mnt/cdrom/README est accessible en lecture /mnt/cdrom/README nest pas accessible en criture (SF en lecture seule)

8.3

fcntl : Verrous et Oprations sur les Fichiers

Lappel systme fcntl est le point daccs de plusieurs oprations avances sur les descripteurs de chiers. Le premier argument de fcntl est un descripteur de chiers ouvert et le second est une valeur indiquant quelle opration doit tre eectue. Pour certaines dentre-elles, fcntl prend un argument supplmentaire. Nous dcrirons ici lune des oprations les plus utiles de fcntl : le verrouillage de chier. Consultez la page de manuel de fcntl pour plus dinformations sur les autres oprations. Lappel systme fcntl permet un programme de placer un verrou en lecture ou en criture sur un chier, dune faon similaire celle utilise pour les verrous mutex traits dans le Chapitre 4, Threads . Un verrou en lecture se place sur un descripteur de chier accessible en lecture et un verrou en criture sur un descripteur de chier accessible en criture. Plusieurs processus peuvent dtenir un verrou en lecture sur le mme chier au mme moment, mais un seul peut dtenir un verrou en criture et le mme chier ne peut pas tre verrouill la fois en lecture et en criture. Notez que le fait de placer un verrou nempche pas rellement les autres

8.3. FCNTL : VERROUS ET OPRATIONS SUR LES FICHIERS

157

processus douvrir le chier, dy lire des donnes ou dy crire, moins quil ne demandent eux aussi un verrou avec fcntl. Pour placer un verrou sur un chier, il faut tout dabord crer une variable struct flock et la remplir de zros. Positionnez le champ l_type de la structure F_RDLCK pour un verrou en lecture ou F_WRLCK pour un verrou en criture. Appelez ensuite fcntl en lui passant le descripteur du chier verrouiller, le code dopration F_SETLKW et un pointeur vers la variable struct flock. Si un autre processus dtient un verrou qui empche lacquisition du nouveau, lappel fcntl bloque jusqu ce que ce verrou soit relch. Le programme du Listing 8.2 ouvre en criture le chier dont le nom est pass en paramtre puis place un verrou en criture dessus. Le programme attend ensuite que lutilisateur appuie sur la touche Entre puis dverrouille et ferme le chier. Listing 8.2 (lock-le.c) Cre un Verrou en criture avec fcntl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33

# include # include # include # include

< fcntl .h > < stdio .h > < string .h > < unistd .h >

int main ( int argc , char * argv []) { char * file = argv [1]; int fd ; struct flock lock ; printf ( " ouverture de % s \ n " , file ) ; /* Ouvre un descripteur de fichier . */ fd = open ( file , O_WRONLY ) ; printf ( " verrouillage \ n " ) ; /* Initialise la structure flock . */ memset (& lock , 0 , sizeof ( lock ) ) ; lock . l_type = F_WRLCK ; /* Place un verrou en criture sur le fichier . */ fcntl ( fd , F_SETLKW , & lock ) ; printf ( " verrouill ; appuyez sur Entre pour dverrouiller ... " ) ; /* Attend l appui sur Entre . */ getchar () ; printf ( " dv er ro uil la ge \ n " ) ; /* Libre le verrou . */ lock . l_type = F_UNLCK ; fcntl ( fd , F_SETLKW , & lock ) ; close ( fd ) ; return 0; }

Compilez et lancez le programme sur un chier test par exemple, /tmp/fichier-test comme suit :
% cc -o lock-file lock-file.c % touch /tmp/fichier-test % ./lock-file /tmp/fichier-test ouverture de /tmp/fichier-test verrouillage verrouill ; appuyez sur Entre pour dverouiller...

158

CHAPITRE 8. APPELS SYSTME LINUX

Maintenant, dans une autre fentre, essayez de le lancer sur le mme chier :
% ./lock-file /tmp/fichier-test ouverture de /tmp/fichier-test verrouillage

Notez que la seconde instance est bloqu lors de la tentative de verrouillage du chier. Revenez dans la premire fentre et appuyez sur Entre :
Dverrouillage

Le programme sexcutant dans la seconde fentre acquiert immdiatement le verrou. Si vous prfrez que fcntl ne soit pas bloquant si le verrou ne peut pas tre obtenu, utilisez F_SETLK au lieu de F_SETLKW. Si le verrou ne peut pas tre acquis, fcntl renvoie -1 immdiatement. Linux propose une autre implmentation du verrouillage de chiers avec lappel flock. La fonction fcntl dispose dun avantage majeur : elle fonctionne sur les chiers se trouvant sur un systme de chiers NFS3 (si tant est que le serveur NFS soit relativement rcent et correctement congur). Ainsi, si vous disposez de deux machines qui ont toutes deux le mme systme de chiers mont via NFS, vous pouvez reproduire lexemple ci-dessus en utilisant deux machines direntes. Lancez lock-file sur une machine en lui passant un chier situ sur le systme de chiers NFS puis lancez le sur la seconde en lui passant le mme chier. NFS relance le second programme lorsque le verrou est relch par le premier.

8.4

fsync et fdatasync : Purge des Tampons Disque

Sur la plupart des systme dexploitation, lorsque vous crivez dans un chier, les donnes ne sont pas immdiatement crites sur le disque. Au lieu de cela, le systme dexploitation met en cache les donnes crites dans un tampon en mmoire, pour rduire le nombre dcritures disque requises et amliorer la ractivit du programme. Lorsque le tampon est plein ou quun vnement particulier survient (par exemple, au bout dun temps donn), le systme crit les donnes sur le disque. Linux fournit un systme de mise en cache de ce type. Normalement, il sagit dune bonne chose en termes de performances. Cependant, ce comportement peut rendre instables les programmes qui dpendent de lintgrit de donnes stockes sur le disque. Si le systme sarrte soudainement par exemple, en raison dun crash du noyau ou dune coupure de courant toute donne crite par le programme qui rside dans le cache en mmoire sans avoir t crite sur le disque est perdue. Par exemple, supposons que vous criviez un programme de gestion de transactions qui tient un chier journal. Ce dernier contient les enregistrements concernant toutes les transactions qui ont t traites an que si une panne systme survient, le statut des donnes impliques par les transactions puisse tre restaur. Il est bien sr important de prserver lintgrit du chier journal chaque fois quune transaction a lieu, son entre dans le journal doit tre envoye immdiatement sur le disque dur.
Network File System (NFS) est une technologie de partage de chiers courante, comparable aux partages et aux lecteurs rseau Windows.
3

8.5. GETRLIMIT ET SETRLIMIT : LIMITES DE RESSOURCES

159

Pour faciliter limplmentation de tels mcanismes, Linux propose lappel systme fsync. Celui-ci prend un seul argument, un descripteur de chier ouvert en criture, et envoie sur le disque toutes les donnes crites dans le chier. Lappel fsync ne se termine pas tant que les donnes nont pas t physiquement crites. La fonction du Listing 8.3 illustre lutilisation de fsync. Il crit un enregistrement dune ligne dans un chier journal. Listing 8.3 (write_journal_entry.c) crit et Synchronise un enregistrement
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

# include # include # include # include # include

< fcntl .h > < string .h > < sys / stat .h > < sys / types .h > < unistd .h >

const char * j o u r n a l _ f i l e n a m e = " journal . log " ; void w r i t e _ j o u r n a l _ e n t r y ( char * entry ) { int fd = open ( journal_filename , O_WRONLY | O_CREAT | O_APPEND , 0660) ; write ( fd , entry , strlen ( entry ) ) ; write ( fd , " \ n " , 1) ; fsync ( fd ) ; close ( fd ) ; }

Un autre appel systme, fdatasync, a la mme fonction. Cependant, alors que fsync garantit que la date de modication du chier sera mise jour, ce nest pas le cas de fdatasync ; ce dernier ne garantit que le fait que les donnes seront crites. Cela signie quen gnral, fdatasync peut sexcuter plus vite que fsync car il na quune seule criture eectuer au lieu de deux. Cependant, sur les version actuelles de Linux, ces deux appels systme font en fait la mme chose, mettant tous deux jour la date de modication du chier. Lappel systme fsync vous permet de forcer explicitement lcriture dun tampon. Vous pouvez galement ouvrir un chier en mode entres/sorties synchrones, ce qui signie que toutes les critures sont envoyes sur le disque immdiatement. Pour cela, passez loption O_SYNC lors de louverture du chier avec open.

8.5

getrlimit et setrlimit : Limites de Ressources

Les appels systme getrlimit et setrlimit permettent un processus de connatre et de dnir des limites sur les ressources systme quil peut consommer. Vous connaissez peut tre la commande shell ulimit, qui vous permet de restreindre la consommation de ressources des programmes que vous excutez4 ; ces appels systme permettent un programme de faire la mme chose par programmation. Pour chaque ressource il existe deux limites, la limite stricte et la limite souple. La limite souple ne doit jamais dpasser la limite dure. Typiquement, une application rduira la limite souple pour viter une monte en puissance de sa consommation de ressources.
4

Consultez la page de manuel de votre shell pour plus dinformations sur ulimit.

160

CHAPITRE 8. APPELS SYSTME LINUX

getrlimit et setrlimit prennent tous deux en argument un code spciant le type de limite de ressource et un pointeur vers une variable struct rlimit. Lappel getrlimit renseigne les champs de cette structure, tandis que setrlimit modie la limite selon son contenu. La structure rlimit a deux champs : rlim_cur qui est la limite souple et rlim_max qui est la limite stricte.

Voici une liste des limites de ressources pouvant tre modies les plus utiles, avec le code correspondant : RLIMIT_CPU Temps processeur maximum, en secondes, utilis par un programme. Il sagit du temps pendant lequel le programme utilise eectivement le processeur, qui nest pas forcment le temps dexcution du programme. Si le programme dpasse cette limite, il est interrompu par un signal SIGXCPU. RLIMIT_DATA Quantit maximale de mmoire quun programme peut allouer pour ses donnes. Toute allocation au-del de cette limite chouera. RLIMIT_NPROC Nombre maximum de processus ls pouvant tre excuts par lutilisateur. Si le processus appel fork et que trop de processus appartenant lutilisateur sont en cours dexcution, fork chouera. RLIMIT_NOFILE Nombre maximum de descripteurs de chiers que le processus peut ouvrir en mme temps. Consultez la page de manuel de setrlimit pour une liste complte des ressources systme. Le programme du Listing 8.4 illustre lutilisation de la limite de temps processeur consomm par un programme. Il dnit un temps processeur de une seconde puis entre dans une boucle innie. Linux tue le processus peu aprs, lorsquil dpasse la seconde de temps processeur. Listing 8.4 (limit-cpu.c) Dmonstration de la Limite de Temps Processeur
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

# include < sys / resource .h > # include < sys / time .h > # include < unistd .h > int main () { struct rlimit rl ; /* Rcupre la limite courante . */ getrlimit ( RLIMIT_CPU , & rl ) ; /* Dfinit une limite de temps processeur d une seconde . */ rl . rlim_cur = 1; setrlimit ( RLIMIT_CPU , & rl ) ; /* Occupe le programme . */ while (1) ; return 0; }

Lorsque le programme est termin par SIGXCPU, le shell ache un message interprtant le signal :
% ./limit_cpu Temps UCT limite expir

8.6. GETRUSAGE : STATISTIQUES SUR LES PROCESSUS

161

8.6

getrusage : Statistiques sur les Processus

Lappel systme getrusage obtient des statistiques sur un processus partir du noyau. Il peut tre utilis pour obtenir des statistiques pour le processus courant en passant RUSAGE_SELF comme premier argument ou pour les processus ls termins qui ont t crs par ce processus et ses ls en passant RUSAGE_CHILDREN. Le second argument de getrusage est un pointeur vers une variable de type struct rusage, qui est renseigne avec les statistiques. Voici quelques-uns des champs les plus intressant dune struct rusage : ru_utime Champ de type struct timeval contenant la quantit de temps utilisateur, en secondes, que le processus a utilis. Le temps utilisateur est le temps processeur pass excut le programme par opposition celui pass dans le noyau pour des appels systme. ru_stime Champ de type struct timeval contenant la quantit de temps systme, en seconde, que le processus a utilis. Le temps systme est le temps processeur pass excut des appels systme la demande du processus. ru_maxrss Quantit maximale de mmoire physique occupe par le processus au cours son excution. La page de manuel de getrusage liste tous les champs disponibles. Consultez la Section 8.7, gettimeofday : Heure Systme pour plus dinformations sur le type struct timeval. La fonction du Listing 8.5 ache les temps systme et utilisateur du processus en cours. Listing 8.5 (print-cpu-times.c) Ache les Temps Utilisateur et Processeur
1 2 3 4 5 6 7 8 9 10 11 12 13

# include # include # include # include

< stdio .h > < sys / resource .h > < sys / time .h > < unistd .h >

void pr in t_ cpu _t ime () { struct rusage usage ; getrusage ( RUSAGE_SELF , & usage ) ; printf ( " Temps processeur : % ld .%06 lds utilisateur , % ld .%06 lds systme \ n " , usage . ru_utime . tv_sec , usage . ru_utime . tv_usec , usage . ru_stime . tv_sec , usage . ru_stime . tv_usec ) ; }

8.7

gettimeofday : Heure Systme

Lappel systme gettimeofday renvoie lheure systme. Il prend un pointeur vers une variable de type struct timeval. Cette structure reprsente un temps, en secondes, spar en deux champs. Le champ tv_sec contient la partie entire du nombre de secondes et le champ tv_usec la fraction de microsecondes. La valeur de la struct timeval reprsente le nombre de secondes coul depuis le dbut de lepoch UNIX, cest--dire le premier janvier 1970 minuit UTC. Lappel gettimeofday prend galement un second argument qui doit tre NULL. Incluez <sys/ time.h> si vous utilisez cet appel systme. Le nombre de secondes depuis lepoch UNIX nest gnralement pas une faon trs pratique de reprsenter les dates. Les fonctions de la bibliothque standard localtime et strftime aident manipuler les valeurs renvoyes par gettimeofday. La fonction localtime prend un pointeur vers un nombre de secondes (le champ tv_sec de struct timeval) et renvoie un pointeur vers

162

CHAPITRE 8. APPELS SYSTME LINUX

un objet struct tm. Cette structure contient des champs plus utiles renseigns selon le fuseau horaire courant : tm_hour, tm_min, tm_sec Heure du jour en heures, minutes et secondes. tm_year, tm_mon, tm_day Anne, mois, jour. tm_wday Jour de la semaine. Zro reprsente le Dimanche. tm_yday Jour de lanne. tm_isdst Drapeau indiquant si lheure dt est en vigueur ou non. La fonction strftime permet de produire partir dun pointeur vers une struc tm une chane personnalise et formate reprsentant la date et lheure. Le format est spci dune faon similaire printf, sous forme dune chane contenant des codes qui indiquent les champs inclure. Par exemple, la chane de format
" %Y -% m -% d % H :% M :% S "

Correspond une date de la forme :


2006 -07 -15 21:00:42

Passez strftime un tampon pour recevoir la chane, la longueur du tampon, la chane de format et un pointeur vers une variable struct tm. Consultez la page de manuel de strftime pour une liste complte des codes qui peuvent tre utiliss dans la chane de format. Notez que ni localtime ni strftime ne prennent en compte la partie fractionnaire de lheure courante avec une prcision suprieure la seconde. Si vous voulez exploiter le champ tv_usec de la struct timeval, vous devrez le faire manuellement. Incluez <time.h> si vous appelez localtime ou strftime. La fonction du Listing 8.6 ache la date et lheure courante, avec une prcision de lordre de la milliseconde. Listing 8.6 (print-time.c) Ache la Date et lHeure
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

# include # include # include # include

< stdio .h > < sys / time .h > < time .h > < unistd .h >

void print_time () { struct timeval tv ; struct tm * ptm ; char time_string [40]; long milliseconds ; /* Rcupre l heure courante et la convertit en struct tm . */ gettimeofday (& tv , NULL ) ; ptm = localtime (& tv . tv_sec ) ; /* Formate la date et l heure la seconde prs . */ strftime ( time_string , sizeof ( time_string ) , " %Y -% m -% d % H :% M :% S " , ptm ) ; /* Calcule les millisecondes partir des microsecondes . */ milliseconds = tv . tv_usec / 1000; /* Affiche l heure de faon formate , en secondes , suivie d un point dcimal et des millisecondes . */ printf ( " % s .%03 ld \ n " , time_string , milliseconds ) ; }

8.8. LA FAMILLE MLOCK : VERROUILLAGE DE LA MMOIRE PHYSIQUE

163

8.8

La famille mlock : Verrouillage de la Mmoire Physique

La famille dappels systme mlock permet un programme de verrouiller tout ou partie de son espace dadressage en mmoire physique. Cela vite que Linux ne lenvoie vers lespace dchange, mme si le programme ny accde pas pendant quelques temps. Un programme pour lequel le temps est une ressource critique peut verrouiller la mmoire physique car le temps ncessaire au processus dchange peut tre trop long ou trop imprvisible. Un programme sensible au niveau de la scurit pourrait vouloir empcher lenvoi de donnes critiques vers un espace dchange partir duquel elles pourraient tre rcupres aprs la n du programme. Le verrouillage dune rgion de la mmoire consiste simplement appeler mlock en lui passant un pointer vers le dbut de la rgion ainsi que la longueur de la rgion. Linux divise la mmoire en pages et ne peut verrouiller que des pages dans leur intgralit ; chaque page qui contient une partie de la rgion de la mmoire passe mlock est verrouille. La fonction getpagesize renvoie la taille de page du systme qui est de 4Ko sous Linux x86. Par exemple pour allouer 32Mo despace dadressage et le verrouiller en RAM, vous utiliserez ce code :
const int alloc_size = 32 * 1024 * 1024; char * memory = malloc ( alloc_size ) ; mlock ( memory , alloc_size ) ;

Notez que le simple fait dallouer une page mmoire et de la verrouiller avec mlock ne rserve pas de mmoire physique pour le processus appelant car les pages peuvent tre en copie lcriture5 . Vous devriez donc crire une valeur quelconque sur chaque page :
size_t i ; size_t page_size = getpagesize () ; for ( i = 0; i < alloc_size ; i += page_size ) memory [ i ] = 0;

Le fait dcrire sur toutes les pages force Linux allouer une page mmoire unique, non partage, au processus pour chacune. Pour dverrouiller une rgion, appelez munlock, qui prend les mmes arguments que mlock. Si vous voulez que lintgralit de lespace dadressage de votre programme soit verrouill en mmoire physique, appelez mlockall. Cet appel systme ne prend quun seul argument : MCL_CURRENT verrouille toute la mmoire alloue mais les allocations suivantes ne sont pas verrouilles ; MCL_FUTURE verrouille toutes les pages qui sont alloues aprs lappel. Utilisez MCL_CURRENT|MCL_FUTURE pour verrouiller en mmoire la fois les allocation en cours et les futures allocations. Le verrouillage de grandes quantits de mmoire, en particulier en via mlockall, peut tre dangereux pour tout le systme Linux. Le verrouillage de mmoire sans discernement est un bon moyen de ralentir considrablement le systme au point de le faire sarrter car les autres processus en cours dexcution doivent sarranger avec des ressources en mmoire physique moindres et avec le fait dtre envoy et repris depuis lespace dchange. Si vous verrouillez
La copie lcriture (copy on write) signie que Linux ne fait une copie prive de la page que lorsque le processus crit une valeur lintrieur.
5

164

CHAPITRE 8. APPELS SYSTME LINUX

trop de mmoire, le systme sera totalement court de mmoire et Linux commencera tuer des processus. Pour cette raison, seuls les processus avec les privilges de superutilisateur peuvent verrouiller de la mmoire avec mlock ou mlockall. Si un processus ne disposant pas de ces privilges appel lune de ces fonctions, elle chouera en renvoyant -1 et en positionnant errno EPERM. Lappel munlockall dverrouille toute la mmoire verrouille par le processus courant, quelle ait t verrouille par mlock ou mlockall. Un moyen pratique de surveiller lutilisation mmoire de votre programme est dutiliser la commande top. La colonne VIRT indique la taille de lespace dadressage virtuel de chaque programme (cette taille inclut le code, les donnes et la pile dont certains peuvent tre envoys vers lespace dchange). La colonne RES (pour resident size) indique la quantit de mmoire physique eectivement occupe par le programme. La somme de toutes les valeurs prsentes dans la colonne RES pour tous les programmes en cours dexcution ne peut pas excder la taille de la mmoire physique de votre ordinateur et la somme de toutes les tailles despace dadressage est limite 2Go6 (pour les versions 32 bits de Linux). Incluez <sys/mman.h> si vous utilisez lun des appels systme mlock.

8.9

mprotect : Dnir des Permissions Mmoire

Dans la Section 5.3, Mmoire Mappe , nous avons montr comment utiliser lappel systme mmap pour mettre en correspondance un chier avec la mmoire. Souvenez vous que le troisime argument de mmap est un ou binaire entre les indicateurs de protection mmoire PROT_READ, PROT_WRITE et PROT_EXEC pour des permissions en lecture, criture ou excution, respectivement, ou PROT_NONE pour empcher laccs la mmoire. Si un programme tente deffectuer une opration sur un emplacement mmoire sur lequel il na pas les bonnes permissions, il se termine sur la rception dun signal SIGSEGV (erreur de segmentation). Une fois que la mmoire a t mappe, ces permissions peuvent tre modie par lappel systme mprotect. Les arguments de mprotect sont ladresse dune rgion mmoire, la taille de cette rgion et un jeu dindicateurs de protection. La rgion mmoire consiste en un ensemble de pages compltes : ladresse de la rgion doit tre aligne sur la taille de page systme et la longueur de la rgion doit tre un multiple de la taille de page. Les indicateurs de protection de ces pages sont remplacs par la valeur passe en paramtre.

Par exemple, supposons que votre programme alloue une page mmoire en mappant /dev/zero, comme dcrit dans la Section 5.3.5, Autres Utilisations de mmap . La mmoire est initialement en lecture/criture.
int fd = open ( " / dev / zero " , O_RDONLY ) ; char * memory = mmap ( NULL , page_size , PROT_READ | PROT_WRITE , MAP_PRIVATE , fd , 0) ; close ( fd ) ;
6

NdT Cette limite nest plus dactualit avec les sries 2.4 et 2.6.

8.9. MPROTECT : DFINIR DES PERMISSIONS MMOIRE Obtenir de la Mmoire Aligne sur une Page

165

Notez que les rgions mmoire renvoyes par malloc ne sont gnralement pas alignes sur des pages, mme si la taille de la mmoire est un multiple de la taille de page. Si vous voulez protger de la mmoire obtenue via malloc, vous devez allouer une rgion plus importante que celle dsire et trouver une sous-rgion qui soit aligne sur une page. Vous pouvez galement utiliser lappel systme mmap pour court-circuiter malloc et allouer de la mmoire aligne sur des pages directement partir du noyau Linux. Consultez la Section 5.3, Mmoire Mappe , pour plus de dtails. Plus loin, votre programme peut protger la mmoire en criture en appelant mprotect :
mprotect ( memory , page_size , PROT_READ ) ;

une technique avance pour surveiller les accs mmoire est de protger une rgion mmoire avec mmap ou mprotect puis de grer le signal SIGSEGV que Linux envoie au programme lorsquil tente daccder cette mmoire. Lexemple du Listing 8.7 illustre cette technique. Listing 8.7 (mprotect.c) Dtecter un Accs Mmoire en Utilisant mprotect
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37

# include # include # include # include # include # include # include # include

< fcntl .h > < signal .h > < stdio .h > < string .h > < sys / mman .h > < sys / stat .h > < sys / types .h > < unistd .h >

static int alloc_size ; static char * memory ; void segv_handler ( int signal_number ) { printf ( " accs mmoire !\ n " ) ; mprotect ( memory , alloc_size , PROT_READ | PROT_WRITE ) ; } int main () { int fd ; struct sigaction sa ; /* Installe segv_handler comme gestionnaire de signal SIGSEGV . memset (& sa , 0 , sizeof ( sa ) ) ; sa . sa_handler = & segv_handler ; sigaction ( SIGSEGV , & sa , NULL ) ; */

/* Alloue une page de mmoire en mappant / dev / zero . Mappe la mmoire en criture seule initialement . */ alloc_size = getpagesize () ; fd = open ( " / dev / zero " , O_RDONLY ) ; memory = mmap ( NULL , alloc_size , PROT_WRITE , MAP_PRIVATE , fd , 0) ; close ( fd ) ; /* crit sur la page pour en obtenir une copie prive . */ memory [0] = 0; /* Protge la mmoire en criture . */

166
38 39 40 41 42 43 44 45 46 47

CHAPITRE 8. APPELS SYSTME LINUX


mprotect ( memory , alloc_size , PROT_NONE ) ; /* crit dans la rgion qui vient d tre alloue . */ memory [0] = 1; /* Termin ; libre la mmoire . */ printf ( " Fini \ n " ) ; munmap ( memory , alloc_size ) ; return 0; }

Le programme eectue les oprations suivantes : 1. Il dclare un gestionnaire de signal pour SIGSEGV ; 2. Il alloue une page mmoire en mappant /dev/zero et en crivant une valeur dans la page obtenue pour en obtenir une copie prive. 3. Il protge la mmoire en appelant mprotect avec loption PROT_NONE ; 4. Lorsque le programme tente dcrire en mmoire, Linux envoie un SIGSEGV qui est pris en charge par segv_handler. Le gestionnaire de signal supprime la protection de la mmoire ce qui autorise laccs ; 5. Lorsque le gestionnaire de signal se termine, le contrle repasse main, o le programme libre la mmoire via munmap.

8.10

nanosleep : Pause en Haute Prcision

Lappel systme nanosleep est une version en haute prcision de lappel UNIX sleep. Au lieu de suspendre lexcution pendant un nombre entier de secondes, nanosleep prend comme argument un pointeur vers un objet de type struct timespec, qui peut indiquer un temps la nanoseconde prs. Cependant, en raison de dtails dimplmentation du noyau Linux, la prcision fournie par nanosleep nest que de 10 millisecondes cest toujours mieux que celle oerte par sleep. Cette prcision supplmentaire peut tre utile, par exemple, pour ordonnancer des oprations frquentes avec de faibles intervalles de temps entre elles. La structure struct timespec a deux champs : tv_sec, le nombre entier de secondes et tv_nsec, un nombre supplmentaire de nanosecondes. La valeur de tv_nsec doit tre infrieure 109 . Lappel nanosleep ore un autre avantage par rapport sleep. Comme pour sleep, larrive dun signal interrompt lexcution de nanosleep, qui positionne alors errno EINTR et renvoie 1. Cependant, nanosleep prend un second argument, un autre pointeur vers un objet struct timespec, qui, sil nest pas NULL, est renseign avec le temps de pause quil restait faire (cest--dire la dirence entre le temps de suspension demand le temps de suspension eectif). Cela facilite la reprise de lopration de suspension. La fonction du Listing 8.8 fournit une implmentation alternative de sleep. Contrairement lappel systme classique, cette fonction prend en paramtre une valeur en virgule ottante correspondant la dure en secondes pour laquelle il faut suspendre lexcution et reprend lopration de suspension si elle est interrompue.

8.11. READLINK : LECTURE DE LIENS SYMBOLIQUES Listing 8.8 (better-sleep.c) Fonction de Suspension Haute prcision
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

167

# include < errno .h > # include < time .h > int better_sleep ( double sleep_time ) { struct timespec tv ; /* Construit l objet timespec partir du nombre entier de seconde ... tv . tv_sec = ( time_t ) sleep_time ; /* ... et le reste en nanosecondes . */ tv . tv_nsec = ( long ) (( sleep_time - tv . tv_sec ) * 1 e +9) ; while (1) { /* Suspend l excution pour un temps spcifi par tv . Si l on est interrompu par un signal , le temps restant est replac dans tv . */ int rval = nanosleep (& tv , & tv ) ; if ( rval == 0) /* On a suspendu l excution pour le temps demand ; termin . */ return 0; else if ( errno == EINTR ) /* Interrompu par un signal . Ressaie . */ continue ; else /* Autre erreur , abandon . */ return rval ; } return 0; }

*/

8.11

readlink : Lecture de Liens Symboliques

Lappel systme readlink rcupre la cible dun lien symbolique. Il prend trois arguments : le chemin vers le lien symbolique, un tampon pour recevoir la cible du lien et sa longueur. Une particularit de readlink est quil ninsre pas de caractre NUL la n de la chane quil place dans le tampon. Cependant, il renvoie le nombre de caractres composant le chemin cible, placer soi-mme le caractre NUL est donc simple. Si le premier argument de readlink pointe vers un chier qui nest pas un lien symbolique, readlink positionne errno EINVAL et renvoie -1. Le petit programme du Listing 8.9 ache la cible du lien symbolique pass sur la ligne de commande. Listing 8.9 (print-symlink.c) Ache la Cible dun Lien Symbolique
1 2 3 4 5 6 7 8 9 10 11

# include < errno .h > # include < stdio .h > # include < unistd .h > int main ( int argc , char * argv []) { char target_path [256]; char * link_path = argv [1]; /* Tente de lire la cible du lien symbolique . */ int len = readlink ( link_path , target_path , sizeof ( target_path ) ) ;

168
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

CHAPITRE 8. APPELS SYSTME LINUX


if ( len == -1) { /* L appel a chou . */ if ( errno == EINVAL ) /* Il ne s agit pas d un lien symbolique ; en informe l utlisateur . */ fprintf ( stderr , " % s n est pas un lien symbolique \ n " , link_path ) ; else /* Autre problme ; affiche un message gnrique . */ perror ( " readlink " ) ; return 1; } else { /* Place un caractre NUL la fin de la cible . */ target_path [ len ] = \0 ; /* L affiche . */ printf ( " % s \ n " , target_path ) ; return 0; } }

Par exemple, voici comment crer un lien symbolique et utiliser print-symlink pour le lire :
% ln -s / usr / bin / wc my_link % ./ print - symlink my_link / usr / bin / wc

8.12

sendle : Transferts de Donnes Rapides

Lappel systme sendfile constitue un mcanisme ecace pour copier des donnes entre deux descripteurs de chier. Les descripteurs de chiers peuvent pointer vers un chier sur le disque, un socket ou tout autre dispositif. Typiquement, pour copier des donnes dun descripteur de chier vers un autre, un programme alloue un tampon de taille xe, y copie des donnes provenant dun des descripteurs, lcrit sur lautre et recommence jusqu ce que toutes les donnes aient t crites. Ce procd nest pas ecace que ce soit en termes de temps ou despace car il ncessite lutilisation de mmoire supplmentaire pour le tampon et ajoute une copie intermdiaire pour le remplir. En utilisant sendfile, le tampon intermdiaire peut tre supprim. Appelez sendfile en lui passant le descripteur de chier de destination, le descripteur source, un pointeur vers une variable de dplacement et le nombre doctets transfrer. La variable de dplacement contient le dplacement partir duquel lire le chier source (0 correspond au dbut du chier) et est mis jour avec la position au sein du chier lissue du transfert. La valeur de retour contient le nombre doctets transfrs. Incluez <sys/sendfile.h> dans votre programme sil utilise sendfile. Le programme du Listing 8.10 est une implmentation simple mais extrmement ecace de copie de chier. Lorsquil est invoqu avec deux noms de chiers sur la ligne de commande, il copie le contenu du premier dans le second. Il utilise fstat pour dterminer la taille, en octets, du chier source. Listing 8.10 (copy.c) Copie de Fichier avec sendle
1 2 3 4

# include # include # include # include

< fcntl .h > < stdlib .h > < stdio .h > < sys / sendfile .h >

8.13. SETITIMER : CRER DES TEMPORISATEURS


5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

169

# include < sys / stat .h > # include < sys / types .h > # include < unistd .h > int main ( int argc , char * argv []) { int read_fd ; int write_fd ; struct stat stat_buf ; off_t offset = 0; /* Ouvre le fichier source . */ read_fd = open ( argv [1] , O_RDONLY ) ; /* Evalue le fichier afin d obtenir sa taille . */ fstat ( read_fd , & stat_buf ) ; /* Ouvre le fichier de destination en criture avec les mmes permissions que le fichier source . */ write_fd = open ( argv [2] , O_WRONLY | O_CREAT , stat_buf . st_mode ) ; /* Transfre les octets d un fichier l autre . */ sendfile ( write_fd , read_fd , & offset , stat_buf . st_size ) ; /* Ferme tout . */ close ( read_fd ) ; close ( write_fd ) ; return 0; }

Lappel sendfile peut tre utilis dans de multiples occasions pour amliorer lecacit des copies. Un bon exemple est un serveur Web ou tout autre service rseau, qui envoie le contenu dun chier un client via le rseau. Typiquement, ce genre de programme reoit une requte depuis un socket connect la machine cliente. Le programme serveur ouvre le chier local transfrer et crit son contenu sur un socket rseau. Utiliser sendfile peut acclrer cette opration de faon signicative. Dautres facteurs ont une inuence sur lecacit du transfert, comme le paramtrage correct du socket. Cependant, ils sortent du cadre de ce livre.

8.13

setitimer : Crer des Temporisateurs

Lappel systme setitimer est une gnralisation de la fonction alarm. Il programme lenvoi dun signal au programme aprs coulement une priode de temps donne. Un programme peut crer trois types dirents de temporisateurs : Si le code temporisateur est ITIMER_REAL, le processus reoit un signal SIGALRM aprs que le temps spci sest coul ; Si le code temporisateur est ITIMER_VIRTUAL, le processus reoit un signal SIGVTALRM aprs stre excut pendant un temps donn. Le temps pendant lequel le programme ne sexcute pas (cest--dire lorsque le noyau ou un autre processus est en cours dexcution) nest pas pris en compte ; Si le code temporisateur est ITIMER_PROF, le processus reoit un signal SIGPROF lorsque le temps dexcution du processus ou des appels systme quil a invoqu atteint le temps spci. Le premier argument de setitimer est un code temporisateur, indiquant le type de temporisateur mettre en place. Le second argument est un pointeur vers un objet struct itimerval spciant

170

CHAPITRE 8. APPELS SYSTME LINUX

les nouveaux paramtres du temporisateur. Le troisime argument, sil nest pas NULL est un pointeur vers un autre objet struct itimerval qui reoit lancien paramtrage du temporisateur. Une variable struct itimerval est constitue de deux champs : it_value est un champ de type struct timeval qui contient le temps avant lexpiration suivante du temporisateur et lenvoi du signal. Sil vaut 0, le temporisateur est dsactiv ; it_interval est un autre champ de type struct timeval contenant la valeur avec laquelle sera rinitialis aprs son expiration. Sil vaut 0, le temporisateur sera dsactiv aprs expiration. Sil est dirent de zro, le temporisateur expirera de faon rptitive chaque coulement de lintervalle. Le type struct timeval est dcrit dans la Section 8.7, gettimeofday : Heure Systme . Le programme du Listing 8.11 illustre lutilisation de setitimer pour suivre le temps dexcution dun programme. Un temporisateur est congur pour expirer toutes les 250 millisecondes et envoyer un signal SIGVTALRM.

Listing 8.11 (timer.c) Exemple dUtilisation dun Temporisateur


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34

# include # include # include # include

< signal .h > < stdio .h > < string .h > < sys / time .h >

void timer_handler ( int signum ) { static int count = 0; printf ( " le temporisateur a expir % d fois .\ n " , ++ count ) ; } int main () { struct sigaction sa ; struct itimerval timer ; /* Installe timer_handler en tant que gestionnaire pour SIGVTALRM . */ memset (& sa , 0 , sizeof ( sa ) ) ; sa . sa_handler = & timer_handler ; sigaction ( SIGVTALRM , & sa , NULL ) ; /* Configure le temporisateur pour expirer aprs 250 ms ... */ timer . it_value . tv_sec = 0; timer . it_value . tv_usec = 250000; /* ... puis rgulirement toutes les 250 ms . */ timer . it_interval . tv_sec = 0; timer . it_interval . tv_usec = 250000; /* Dmarre un temporisateur virtuel . Il dcompte ds que le processus s excute . */ setitimer ( ITIMER_VIRTUAL , & timer , NULL ) ; /* Perd du temps . while (1) ; } */

8.14. SYSINFO : RCUPRATION DE STATISTIQUES SYSTME

171

8.14

sysinfo : Rcupration de Statistiques Systme

Lappel systme sysinfo renseigne une structure avec des statistiques sur le systme. Son seul argument est un pointeur vers une variable struct sysinfo. Voici quelques uns des champs les plus intressants de cette structure : uptime Temps coul depuis le dmarrage du systme, en secondes ; totalram Mmoire physique disponible au total ; freeram Mmoire physique libre ; procs Nombre de processus sexcutant sur le systme. Consultez la page de manuel de sysinfo pour une description complte du type struct sysinfo. Incluez <linux/kernel.h>, <linux/sys.h> et <sys/sysinfo.h> si vous utilisez sysinfo. Le programme du Listing 8.12 ache des statistiques sur le systme courant. Listing 8.12 (sysinfo.c) Ache des Statistiques Systme
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

# include # include # include # include

< linux / kernel .h > < linux / sys .h > < stdio .h > < sys / sysinfo .h >

int main () { /* Facteurs de conversion . */ const long minute = 60; const long hour = minute * 60; const long day = hour * 24; const double megabyte = 1024 * 1024; /* Rcupre les statistiques systme . */ struct sysinfo si ; sysinfo (& si ) ; /* Affiche un rsum des informations intressantes . */ printf ( " uptime systme : % ld jours , % ld :%02 ld :%02 ld \ n " , si . uptime / day , ( si . uptime % day ) / hour , ( si . uptime % hour ) / minute , si . uptime % minute ) ; printf ( " RAM totale : %5.1 f Mo \ n " , si . totalram / megabyte ) ; printf ( " RAM libre : %5.1 f Mo \ n " , si . freeram / megabyte ) ; printf ( " nb processus : % d \ n " , si . procs ) ; return 0; }

8.15

uname

Lappel systme uname renseigne une structure avec diverses informations systme, comme le nom de lordinateur, le nom de domaine et la version du systme dexploitation en cours dexcution. Ne passez quun seul argument uname : un pointeur vers un objet struct utsname . Incluez <sys/utsname.h> si vous utilisez uname. Lappel uname renseigne les champs suivants : sysname Nom du systme dexploitation (par exemple, Linux) ; release, version Numros de version majeure et mineure du noyau ; machine Informations sur la plateforme systme. Pour un Linux x86, ce sera i386 ou i686 selon le processeur ;

172

CHAPITRE 8. APPELS SYSTME LINUX

node Nom non quali de lordinateur ; __domainname Nom de domaine de lordinateur. Chacun de ces champs est une chane de caractres. Le petit programme du Listing 8.13 ache les numros de version du noyau et des informations sur le matriel. Listing 8.13 (print-uname.c) Ache le Numro de Version et des Infos Matrielles
1 2 3 4 5 6 7 8 9 10 11

# include < stdio .h > # include < sys / utsname .h > int main () { struct utsname u ; uname (& u ) ; printf ( " % s version % s .% s sur systme % s \ n " , u . sysname , u . release , u . version , u . machine ) ; return 0; }

Chapitre 9

Code Assembleur en Ligne


ujourdhui, peu de programmeurs utilisent le langage assembleur. Des langages de plus haut niveau comme le C ou le C++ sexcutent sur quasiment toutes les architecture et permettent une productivit plus importante lors de lcriture et de la maintenance du code. Parfois, les programmeurs ont besoin dutiliser des instructions assembleur dans leurs programmes, la GNU Compiler Collection permet aux programmeurs dajouter des instructions en langage assembleur dpendantes de larchitecture leurs programmes. Les instructions assembleur GCC en ligne ne doivent pas tre utilises de faon excessive. Les instructions en langage assembleur dpendant de larchitecture, aussi, des programmes utilisant des instructions x86 ne peuvent pas tre compils sur des PowerPC. Pour les utiliser, vous aurez besoin dun dispositif permettant de les traduire dans le jeu dinstruction de votre architecture. Cependant, les instructions assembleur vous permettent daccder au matriel directement et peuvent permettre de produire du code plus performant. Linstruction asm vous permet dinsrer des instructions assembleur dans des programmes C ou C++. Par exemple, linstruction
asm ( " fsin " : " = t " ( answer ) : " 0 " ( angle ) ) ;

est une faon spcique larchitecture x86 de coder cette instruction C1 :


answer = sin ( angle ) ;

Notez que contrairement aux instructions assembleur classiques, les constructions asm vous permettent de spcier des oprandes en utilisant la syntaxe du C. Pour en savoir plus sur le jeu dinstructions x86, que nous utiliserons dans ce chapitre, consultez http://developer.intel.com/design/pentiumii/manuals/ et http://www.x86-64.org/ documentation.
Lexpression sin (angle) est gnralement implmente par un appel la bibliothque math, mais si vous demandez une option -01 ou plus, GCC remplacera cet appel par une unique instruction assembleur fsin.
1

173

174

CHAPITRE 9. CODE ASSEMBLEUR EN LIGNE

9.1

Quand Utiliser du Code Assembleur

Bien quil ne faille pas abuser des constructions asm, elles permettent vos programmes daccder au matriel directement et peuvent produire des programmes qui sexcutent rapidement. Vous pouvez les utiliser lors de lcriture du code faisant partie du systme dexploitation qui a besoin dinteragir avec le matriel. Par exemple, /usr/include/asm/io.h contient des instructions assembleur pour accder aux ports dentre/sortie directement. Le chier source du noyau situ dans /usr/src/linux/arch/i386/kernel/process.s ore un autre exemple en utilisant hlt dans une boucle dinactivit. Consultez dautres chiers source du noyau Linux situs dans /usr/src/linux/arch/ et /usr/src/linux/drivers/. Les instructions assembleur peuvent galement acclrer la boucle de traitement interne de certains programmes. Par exemple, si la majorit du temps dexcution dun programme est consacr au calcul du sinus et du cosinus du mme angle, il est possible dutiliser linstruction x86 fsincos2 . Consultez par exemple /usr/include/bits/mathinline.h qui encapsule des squences assembleur au sein de macros an dacclrer le calcul de certaines fonctions transversales. Vous ne devriez utiliser des instructions assembleur en ligne pour acclrer lexcution quen dernier ressort. Les compilateurs actuels sont sophistiqus et connaissent les dtails des processeurs pour lesquels ils gnrent du code. Ainsi, ils peuvent souvent slectionner des squences de code qui paraissent contre-intuitives mais qui sexcutent en fait plus vite que dautres. moins que vous ne compreniez le jeu dinstructions et les proprits dordonnancement du processeur cible dans les dtails, vous feriez sans doute mieux de laisser les optimiseurs du compilateur gnrer du code assembleur votre place pour la plupart des oprations. De temps autre, une ou deux instructions assembleur peuvent remplacer plusieurs lignes de code dun langage de plus haut niveau. Par exemple, dterminer la position du bit non nul le plus signicatif dun entier en utilisant le C ncessite une boucle ou des calculs en virgule ottante. Beaucoup darchitecture, y compris le x86 disposent dune instruction assembleur (bsr) qui calcule cette position. Nous illustrerons son utilisation dans la Section 9.4, Exemple .

9.2

Assembleur en Ligne Simple

Nous allons maintenant prsenter la syntaxe des instructions assembleur asm avec un exemple dcalant une valeur de 8 bits vers la droite sur architecture x86 :
asm ( " shrl $8 , %0 " : " = r " ( answer ) : " r " ( operand ) : " cc " ) ;

Le mot-clef asm est suivi par une expression entre parenthses constitue de sections spares par deux-points. La premire section contient une instruction assembleur et ses oprandes, dans cet exemple, shrl dcale droite les bits du premier oprande. Celui-ci est reprsent par %0, le second est la constante immdiate $8. La deuxime section indique les sorties. Ici, la premire sortie de linstruction sera place dans la variable C answer, qui doit tre une lvalue. La chane "=r" contient un signe gal indiquant un oprande de sortie et un r indiquant que answer est stock dans un registre.
Les amliorations au niveau des algorithmes ou des structures de donnes sont souvent plus ecaces dans la rduction du temps dexcution dun programme que lutilisation dinstructions assembleur.
2

9.3. SYNTAXE ASSEMBLEUR AVANCE

175

La troisime section spcie les entres. La variable C operand donne la valeur dcaler. La chane "r" indique quelle est stocke dans un registre mais ne contient pas le signe gal car il sagit dun oprande dentre et non pas de sortie. La quatrime et dernire section indique que linstruction modie la valeur du registre de code condition cc.

9.2.1

Convertir un asm en Instructions Assembleur

Le traitement des constructions asm par GCC est trs simple. Il produit des instructions assembleur pour traiter les oprandes de lasm et replace la construction asm par linstruction que vous spciez. Il nanalyse pas du tout linstruction. Par exemple, GCC convertit cet extrait de code :
double foo , bar ; asm ( " mycool_asm %1 , %0 " : " = r " ( bar ) : " r " ( foo ) ) ;

en la squence dinstructions x86 suivante :


movl -8(% ebp ) ,% edx movl -4(% ebp ) ,% ecx # APP mycool_asm % edx , % edx # NO_APP movl % edx , -16(% ebp ) movl % ecx , -12(% ebp )

Souvenez-vous que foo et bar requirent chacun deux mots despace de stockage dans la pile sur une architecture x86 32 bits. Le registre ebp pointe vers les donnes sur la pile. Les deux premires instructions copient foo dans les registres EDX et ECX quutilise mycool_asm . Le compilateur a dcid dutiliser les mmes registres pour stocker la rponse, qui est copie dans bar par les deux dernires instructions. Il choisit les registres adquats, peut mme les rutiliser, et copie les oprandes partir et vers les emplacements appropris automatiquement.

9.3

Syntaxe Assembleur Avance

Dans les sous-sections qui suivent, nous dcrivons les rgles de syntaxe pour les constructions
asm. Leurs sections sont spares par deux-points. Nous utiliserons linstruction asm suivante qui calcule lexpression boolenne x > y :
asm ( " fucomip %% st (1) , %% st ; seta %% al " : " = a " ( result ) : " u " ( y ) , " t " ( x ) : " cc " , " st " ) ;

Tout dabord, fucomip compare ses deux oprandes x et y et stocke les valeurs indiquant le rsultat dans le registre de code condition. Puis, seta convertit ces valeurs en 0 ou 1.

9.3.1

Instructions Assembleur

La premire section contient les instructions assembleur, entre guillemets. La construction asm exemple contient deux instructions assembleur, fucomip et seta, spares par un pointvirgule. Si lassembleur nautorise pas les points-virgule, utilisez des caractres de nouvelle ligne

176

CHAPITRE 9. CODE ASSEMBLEUR EN LIGNE

(\n) pour sparer les instructions. Le compilateur ignore le contenu de cette premire section, except quil supprime un niveau de signes pourcent, %% devient donc %. La signication de %%st(1) et dautres termes similaires dpend de larchitecture. GCC se plaindra si vous spciez loption -traditional ou -ansi lors de la compilation dun programme contenant des constructions asm. Pour viter de telles erreurs, utilisez le mot cl alternatif __asm__ comme dans les chiers den-tte cits prcdemment.

9.3.2

Sorties

La seconde section spcie les oprandes de sortie des instructions en utilisant une syntaxe C. Chaque oprande est dcrit par une contrainte doprande sous forme de chane suivie dune expression C entre parenthses. Pour les oprandes de sortie, qui doivent tre des lvalues, la chane de contrainte doit commencer par un signe gal. Le compilateur vrie que lexpression C pour chaque oprande de sortie est eectivement une lvalue. Les lettres spciant les registres pour une architecture donne peuvent tre trouve dans le code source de GCC, dans la macro REG_CLASS_FROM_LETTER. Par exemple, le chier de conguration gcc/con ?g/i386/i386.h de GCC liste les lettres de registre pour larchitecture x863 . Le Tableau 9.1 en fait le rsum.

Lettre R q f t u a b c d x y A D S

Tab. 9.1 Lettres correspondant aux Registres sur lArchitecture Intel x86 Registres ventuellement Utiliss par GCC Registre gnral (EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP) Registre de donnes gnral (EAX, EBX, ECX, EDX) Registre virgule ottante Registre en virgule ottante suprieur Second registre en virgule ottante Registre EAX Registre EBX Registre ECX Registre EDX Registre SSE (Streaming SIMD Extension) Registres multimdias MMX Valeur de 8 octets forme partir de EAX et EDX Pointeur de destination pour les oprations sur les chanes (EDI) Pointeur source pour les oprations sur les chanes (ESI)

Lorsquune structure asm utilise plusieurs oprandes, elles doivent tre dcrites par une chane de contrainte et une expressions C et spares par des virgules, comme lillustre lexemple donn prcdemment. Vous pouvez spcier jusqu 10 oprandes, numrotes de %0 %9 dans les
Vous devrez avoir une certaine familiarit avec le fonctionnement de GCC pour comprendre le contenu de ce chier.
3

9.3. SYNTAXE ASSEMBLEUR AVANCE

177

sections dentre et de sortie. Sil ny a pas doprandes de sortie ou de registre aects, laissez la section de sortie vide ou signalez la avec un commentaire du type /* no outputs */.

9.3.3

Entres

La troisime section dcrit les entres des instructions assembleur. La chane de contrainte dun oprande dentre ne doit pas contenir de signe gal, ce qui indique une lvalue. Mis part cela, leur syntaxe est identique celle des oprandes de sortie. Pour indiquer quun registre est la fois lu et crit par la mme construction asm, utilisez lindice de loprande de sortie comme chane de contrainte dentre. Par exemple, pour indiquer quun registre dentre est le mme que le premier registre de sortie, utilisez 0. Spcier la mme expression C pour des oprandes dentre et de sortie ne garantit pas que les deux valeurs seront places dans le mme registre. La section dentre peut tre omise sil ny a pas doprandes dentre et que le section de dclaration des modications est vide.

9.3.4

Dclaration des Modications

Si une instruction modie les valeur dun ou plusieurs registres par eet de bord, spciez ces registres dans la quatrime section de la structure asm. Par exemple, linstruction fucomip modie le registre de code condition, qui est dsign par cc. Les registres modis sont dcrits dans des chanes individuelles spares par des virgules. Si linstruction est susceptible de modier un emplacement mmoire arbitraire, spciez memory. En utilisant les informations sur la modication des registres, le compilateur dtermine les valeurs qui doivent tre restaures aprs lexcution du bloc asm. Si vous ne renseignez pas ces informations correctement, GCC pourrait supposer que certains registres contiennent des valeurs qui ont en fait t crases, ce qui pourrait aecter le fonctionnement de votre programme.

9.3.5

Exemple

Larchitecture x86 dispose dinstructions qui dterminent les positions du bit non nul le plus signicatif ou le moins signicatif dans un mot. Le processeur peut excuter ces instructions de faon relativement ecace. Par contre, limplmentation de la mme opration en C ncessite une boucle et un dcalage binaire. Par exemple, linstruction assembleur bsrl calcule la position du bit le plus signicatif de son premier oprande et place cette position ( partir de 0 qui reprsente le bit le moins signicatif) dans le second. Pour placer la position du bit non nul le plus signicatif de number dans position, nous pourrions utiliser cette structure asm :
asm ( " bsrl %1 , %0 " : " = r " ( position ) : " r " ( number ) ) ;

Une faon dimplmenter la mme opration en C, est dutiliser une boucle de ce genre :
long i ; for ( i = ( number >> 1) , position = 0; i != 0; ++ position ) i > >= 1;

178

CHAPITRE 9. CODE ASSEMBLEUR EN LIGNE

Pour comparer les vitesses de ces deux versions, nous allons les placer dans une boucle qui calcule les positions pour de grands nombres. Le Listing 9.1 utilise limplmentation en C. Le programme boucle sur des entiers, en partant de 1 jusqu la valeur passe sur la ligne de commande. Pour chaque valeur de number, il calcule la position du bit non nul le plus signicatif. Le Listing 9.2 eectue les mmes oprations en utilisant une construction assembleur en ligne. Notez que dans les deux versions, nous plaons la position calcule une variable volatile result. Cela permet dviter que loptimiseur du compilateur ne supprime le calcul ; si le rsultat nest pas utilis ou stock en mmoire, loptimiseur limine le calcul en le considrant comme du code mort. Listing 9.1 (bit-pos-loop.c) Recherche dun Bit en Utilisant une Boucle
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

# include < stdio .h > # include < stdlib .h > int main ( int argc , char * argv []) { long max = atoi ( argv [1]) ; long number ; long i ; u n s i g n e d position ; v o l a t i l e u n s i g n e d result ; /* Rpte l opration pour un nombre important de valeurs . */ for ( number = 1; number <= max ; ++ number ) { /* Dcale le nombre vers la droite jusqu ce qu il vaille zro . Mmorise le nombre de dcalage qu il a fallu faire . */ for ( i = ( number >> 1) , position = 0; i != 0; ++ position ) i > >= 1; /* La position du nombre non nul le plus significatif est le nombre de dcalages qu il a fallu aprs le premier . */ result = position ; } return 0; }

Listing 9.2 (bit-pos-asm.c) Recherche dun Bit en Utilisant bsrl


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

# include < stdio .h > # include < stdlib .h > int main ( int argc , char * argv []) { long max = atoi ( argv [1]) ; long number ; u n s i g n e d position ; v o l a t i l e u n s i g n e d result ; /* Repeat the operation for a large number of values . */ for ( number = 1; number <= max ; ++ number ) { /* Compute the position of the most signi ? cant set bit using the bsrl assembly instruction . */ asm ( " bsrl %1 , %0 " : " = r " ( position ) : " r " ( number ) ) ; result = position ; } return 0; }

Compilons les deux versions avec les optimisations actives :

9.4. PROBLMES DOPTIMISATION


% cc -O2 -o bit-pos-loop bit-pos-loop.c % cc -O2 -o bit-pos-asm bit-pos-asm.c

179

prsent, lanons-les en utilisant la commande time pour mesurer le temps dexcution. Il est ncessaire de passer une valeur importante comme argument an de sassurer que chaque version mette un minimum de temps sexcuter.
% time ./bit-pos-loop 250000000 real 0m27.042s user 0m24.583s sys 0m0.135s % time ./bit-pos-asm 250000000 real 0m1.472s user 0m1.442s sys 0m0.013s

Voyez comme la version utilisant lassembleur sexcute beaucoup plus vite (vos propres rsultats peuvent varier).

9.4

Problmes dOptimisation

Loptimiseur de GCC tente de rordonner et de rcrire le code du programme pour minimiser le temps dexcution mme en prsence dexpressions asm. Si loptimiseur dtermine que les valeurs de sortie ne sont pas utilises, linstruction sera supprime moins que le mot cl volatile ne gure entre asm et ses arguments (par ailleurs, GCC ne dplacera pas une structure asm sans oprande de sortie en dehors dune boucle). Toute structure asm peut tre dplace de faon dicile prvoir, mme dun saut lautre. La seule faon de garantir lordre dun bloc assembleur est dinclure toutes les instructions dans la mme structure asm. Lutilisation de constructions asm peut limiter lecacit de loptimiseur car le compilateur ne connat pas leur smantique. GCC est forc de faire des suppositions qui peuvent interdire certaines optimisations. Caveat emptor !

9.5

Problmes de Maintenance et de Portabilit

Si vous dcidez dutiliser des constructions asm non-portables et dpendantes de larchitecture, les encapsuler dans des macros peut aider la maintenance et amliorer la portabilit. Placer toutes ces macros dans un chier et les documenter facilitera le portage de lapplication vers une autre architecture, quelque chose qui arrive souvent mme pour les programmes la va vite . De cette faon, le programmeur naura besoin de rcrire quun seul chier pour adapter le logiciel une autre architecture. Par exemple, la plupart des instructions asm du code source Linux sont regroupes dans les chiers den-tte situs sous /usr/src/linux/include/asm/ et /usr/src/linux/include/ asm-i386/ et les chiers source situs sous /usr/src/linux/arch/i386/ et /usr/src/linux/ drivers/.

180

CHAPITRE 9. CODE ASSEMBLEUR EN LIGNE

Chapitre 10

Scurit
ne grande partie de la puissance dun systme GNU/Linux vient de sa capacit grer plusieurs utilisateurs et de son bon support pour le rseau. Beaucoup de gens peuvent utiliser le systme en mme temps et se connecter au systme distance. Malheureusement, cette puissance prsente des risques, en particulier pour les systme connects Internet. Dans certaines circonstances, un hacker distant peut se connecter au systme et lire, modier ou supprimer des chiers stocks sur la machine. Ou bien, un utilisateur pourrait lire, modier ou supprimer les chier dun autre sans y tre autoris. Lorsque ce type dvnement se produit, la scurit du systme est dite compromise. Le noyau Linux fournit divers mcanismes an de sassurer que de telles situations ne se prsentent pas. Mais les applications classiques doivent elles aussi tre attentives viter les failles de scurit. Par exemple, imaginons que vous dveloppiez un logiciel de comptabilit. Bien que vous puissiez vouloir permettre tous les utilisateurs de remplir des notes de frais, il nest certainement pas souhaitables quils puissent tous les valider. De mme, les utilisateurs ne doivent pouvoir consulter que leur propre bulletin de paie et les managers ne connatre que les salaires des employs de leur dpartement. Pour forcer ce type de contrle, vous devez tre trs attentif. Il est tonnamment facile de faire une erreur permettant des utilisateurs de faire quelque chose quils ne devraient pas pouvoir faire. La meilleur approche est de demander de laide des experts en scurit. Toutefois, tout dveloppeur dapplication doit matriser quelques bases.

10.1

Utilisateurs et Groupes

Chaque utilisateur sous Linux est identi par un numro unique appel user ID, ou UID. Bien sr, lorsque vous vous connectez, vous utilisez un nom dutilisateur plutt que cet UID. Le systme eectue la conversion entre ce nom dutilisateur et lUID, et partir de ce moment, seul luser ID est pris en compte. Vous pouvez en fait faire correspondre plus dun nom dutilisateur au mme UID. En ce qui concerne le systme, seuls les UID comptent. Il ny a aucune faon de donner plus de droits un utilisateur qu un autre sils ont tous deux le mme UID. 181

182

CHAPITRE 10. SCURIT

Vous pouvez contrler laccs un chier ou toute autre ressource en lassociant un UID particulier. Dans ce cas, seul lutilisateur disposant de cet UID peut accder la ressource. Par exemple, vous pouvez crer un chier que vous serez le seul pouvoir lire ou un rpertoire o vous seul pourrez crer de nouveau chier. Cela sut dans la plupart des cas simples. Parfois, cependant, vous avez besoin de partager une ressource entre plusieurs utilisateurs. Par exemple, si vous tes un manager, vous pourriez vouloir crer un chier que tous les managers pourraient lire mais pas les employs ordinaires. Linux ne vous permet pas dassocier plusieurs UID au mme chier, vous ne pouvez donc pas crer une liste de tous les gens auxquels vous souhaitez donner accs au chier et les lier au chier. Par contre, vous pouvez crer un groupe. chaque groupe est associ un numro unique, appel group ID ou GID. Chaque groupe contient un ou plusieurs user ID. Un mme UID peut faire partie de plusieurs groupes mais un groupe ne peut pas contenir dautres groupes ; ils ne peuvent contenir que des utilisateurs. Comme les utilisateurs, les groupes ont des noms. Tout comme pour les noms dutilisateurs, le nom dun groupe na pas dimportance, le systme utilise toujours le GID en interne. Pour continuer avec notre exemple, vous pourriez crer un groupe managers et y placer les UID de tous les managers. Vous pourriez alors crer un chier qui peut tre lu par nimporte qui dans ce groupe mais pas par les gens qui y sont extrieurs. En gnral, vous ne pouvez associer quun seul groupe une ressource. Il ny a aucun moyen de spcier que les utilisateurs peuvent accder un chier sils font partie du groupe 7 ou du groupe 42, par exemple. Si vous tes curieux de connatre votre UID et les groupes auxquels vous appartenez, vous pouvez utiliser la commande id. Voici un exemple de la sortie de cette commande :
% id uid=501(mitchell) gid=501(mitchell) groups=501(mitchell),503(csl)

la premire partie indique que lUID de lutilisateur ayant invoqu la commande est 501. La commande dtermine le nom dutilisateur correspondant et lache entre parenthses. On voit ici que lutilisateur 501 est dans deux groupes : le groupe 501 (appel mitchell) et le groupe 503 (appel csl). Vous vous demandez certainement pourquoi le groupe 501 apparat deux fois : une premire fois dans le champ gid et une seconde dans le champ groups. Nous y reviendrons plus tard.

10.1.1

Le superutilisateur

Un des comptes utilisateur est trs spcial1 . Cet utilisateur a lUID 0 est a gnralement le nom dutilisateur root. On y fait parfois rfrence en parlant du compte superutilisateur. Lutilisateur root peut littralement tout faire : lire ou supprimer nimporte quel chier, ajouter de nouveaux utilisateurs, dsactiver laccs rseau, etc. Beaucoup doprations spciales ne peuvent tre ralises que par des processus sexcutant avec les privilges root cest--dire sexcutant sous le compte utilisateur root.
1 Le fait quil ny ait quun seul utilisateur spcial est lorigine du nom dUNIX donn par AT&T son systme dexploitation. Un systme dexploitation plus ancien qui disposait de plusieurs utilisateurs spciaux a t baptis MULTICS. GNU/Linux, bien sr, est compatible avec UNIX.

10.2. USER ET GROUP ID DE PROCESSUS

183

Le problme de cette conception est quun nombre important de programmes doivent tre excuts par root car beaucoup de programmes ont besoin daccder ces oprations spciales. Si lun de ses programmes ne se comporte pas correctement, les consquences peuvent tre dramatique. Il ny a aucun moyen de contenir un programme lorsquil est excut par root ; il peut faire nimporte quoi. Les programmes lancs par root doivent tre crit avec beaucoup de prcautions.

10.2

User et Group ID de Processus

Jusqu maintenant, nous navons parl que des commandes excutes par un utilisateur en particulier. Cela ne colle pas exactement la ralit car lordinateur ne sait jamais rellement quel utilisateur lexcute. Si ve dcouvre le nom dutilisateur et le mot de passe dAlice, ve peut se connecter en tant quAlice et le systme laisserait faire ve tout ce quAlice peut faire. Il ne connat que lUID en cours et non pas lutilisateur qui saisit les commandes. Si on ne peut pas faire conance Alice pour garder secret son mot de passe, alors rien de ce que vous pourrez faire en tant que programmeur dapplication ne pourra empcher ve daccder aux chiers dAlice. La responsabilit de la scurit du systme est partage entre le dveloppeur, les utilisateurs du systme et les administrateurs du systme. chaque processus est associ un UID et un GID. Lorsque vous invoquez une commande, elle lance un processus dont lUID et le GID sont les vtres. Lorsque nous disons quun utilisateur eectue une opration quelconque, nous voulons dire en ralit quun processus avec lUID correspondant eectue cette opration. Lorsque le processus excute un appel systme, le noyau dcide sil y est autoris. Pour cela, il examine les permissions associes aux ressources auxquelles le processus tente daccder et vrie lUID et le GID associs au processus tentant dexcuter lappel. Dsormais, vous savez ce que signie le champ gid de la sortie de la commande id. Il indique le GID du processus courant. Mme si lutilisateur 501 fait partie de plusieurs groupes, le processus courant ne peut avoir quun seul GID. Dans lexemple prsent prcdemment, le GID en cours est 501. Si vous devez manipuler des UID ou des GID dans votre programme (et vous aurez le faire si vous crivez des programmes concernant la scurit), vous devez utiliser les types uid_t et gid_t dnis dans len-tte <sys/types.h> mme si les UID et GID ne sont en fait que des entiers, vitez de faire des suppositions sur le nombre de bits utilis dans ces types ou deectuer des oprations arithmtiques en les utilisant. Traitez les comme un moyen obscur de manipuler les identiants de groupe et dutilisateur. Pour rcuprer lUID et le GID du processus courant, vous pouvez utiliser les fonctions geteuid et getegid dnies dans <unistd.h>. Ces fonctions ne prennent aucun paramtre et nchouent jamais, il ny a pas derreur contrler. Le Listing 10.1 prsente un simple programme qui fournit un sous-ensemble des fonctionnalits de la commande id : Listing 10.1 (simpleid.c) Ache les Identiants dUtilisateur et de Groupe
1 2 3

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

184
4 5 6 7 8 9 10

CHAPITRE 10. SCURIT


int main () { uid_t uid = geteuid () ; gid_t gid = getegid () ; printf ( " uid =% d gid =% d \ n " , ( int ) uid , ( int ) gid ) ; return 0; }

Lorsque ce programme est excut (par le mme utilisateur que celui qui a lanc le programme id dans lexemple prcdemment), la sortie est la suivante :
% ./simpleid uid=501 gid=501

10.3

Permissions du Systme de Fichiers

Un bon moyen de voir les utilisateurs et les groupes en actions est dtudier les permissions du systme de chiers. En examinant la faon dont le systme associe les permissions avec chaque chier et en observant comment le noyau vrie qui est autoris accder quels chiers, le concept didentiant utilisateur et de groupe devrait devenir clair. Chaque chier a exactement un utilisateur propritaire et un groupe propritaire. Lorsque vous crez un nouveau chier, il est dtenu par lutilisateur et le groupe du processus crateur2 . Les oprations de base sur les chiers, en ce qui concerne Linux, sont la lecture, lcriture et lexcution (Notez que la cration et la suppression ne sont pas considres comme des oprations sur le chier, elles sont considres comme des oprations sur le rpertoire contenant le chier. Nous en parlerons plus loin). Si vous ne pouvez pas lire un chier, Linux ne vous laissera pas en examiner le contenu. Si vous ne pouvez pas y crire, vous ne pouvez pas modier son contenu. Si vous ne disposez pas des droits dexcution sur un chier contenant le code dun programme, vous ne pouvez pas excuter ce programme. Linux vous permet dindiquer lesquelles de ces trois actions lire, crire et excuter peuvent tre ralises par lutilisateur propritaire, le groupe propritaire et toute autre personne. Par exemple, vous pouvez dire que lutilisateur propritaire aura tous les droits, que les membres du groupe propritaire pourront lire et excuter le chier (mais pas y crire) et que personne dautre ny a accs. Vous pouvez consulter ces bits de permission de faon interactive avec la commande ls en utilisant les options -l ou -o et par programmation via lappel systme stat. Vous pouvez dnir de faon interactive les bits de permission avec le programme chmod3 et par programmation avec lappel systme du mme nom. Pour examiner les permissions dun chier appel hello, utilisez ls -l hello. Voici un exemple de sortie :
% ls -l hello -rwxr-x--1 samuel csl 11734 Jan 22 16:29 hello

En fait, il existe de rares exceptions faisant intervenir les sticky bits, dcrits plus loin dans la Section 10.3.2, Sticky Bits . 3 On fait parfois rfrence aux bits de permission en utilisant le terme mode. Le nom de la commande chmod est un diminutif pour change mode .

10.3. PERMISSIONS DU SYSTME DE FICHIERS

185

Les indications samuel et csl signient que lutilisateur propritaire est samuel et que le groupe propritaire est csl. La chane de caractres a dbut de la ligne indique les permissions associes au chier. Le premier tiret indique que le chier est un chier classique. Il serait remplac par d pour un rpertoire ou dautres lettres dans le cas de chiers spciaux comme les priphriques (reportezvous au Chapitre 6, Priphriques ) ou les canaux nomms (voyez le Chapitre 5, Communication Interprocessus , Section 5.4, Tubes ). Les trois caractres suivants reprsentent les permissions de lutilisateur propritaire : ils indiquent que samuel peut lire, crire et excuter le chier. Les trois caractres daprs donnent les permissions des membres du groupe csl ; ses membres ne peuvent que lire et excuter le chier. Enn, les trois derniers caractres indiquent les permissions de toute autre personne ; ici, tout utilisateur ntant pas samuel et ne faisant pas partie du groupe csl ne peut rien faire avec le chier hello. Voyons comment cela fonctionne. Tout dabord, essayons daccder au chier en tant quutilisateur nobody, qui ne fait pas partie du groupe csl :
% id uid=99(nobody) gid=99(nobody) groups=99(nobody) % cat hello cat: hello: Permission denied % echo hi > hello sh: ./hello: Permission denied % ./hello sh: ./hello: Permission denied

Nous navons pas le droit de lire le chier, cest pourquoi cat choue ; nous ne pouvons pas crire dans le chier, cest pourquoi echo choue ; et nous navons pas le droit dexcuter le chier, cest pourquoi ./hello choue. Les choses sarrangent si nous accdons au chier en tant que mitchell, qui nest pas membre du groupe csl :
% id uid=501(mitchell) gid=501(mitchell) groups=501(mitchell),503(csl) % cat hello #!/bin/bash echo "Hello, world." % ./hello Hello, world. % echo hi > hello bash: ./hello: Permission denied

Nous pouvons acher le contenu du chier et nous pouvons lexcuter (il sagit dun simple script shell) mais nous ne pouvons toujours pas y crire. Si nous sommes identis en tant que propritaire (samuel), nous pouvons mme craser le chier :
% id uid=502(samuel) gid=502(samuel) groups=502(samuel),503(csl) % echo hi > hello % cat hello hi

Vous pouvez modier les permissions associes avec un chier si vous tes son propritaire (ou le superutilisateur). Par exemple, si vous voulez dsormais permettre tout le monde dexcuter le chier, vous pouvez faire ce qui suit :

186
% chmod o+x hello % ls -l hello -rwxr-x--x 1 samuel csl 3 Jan 22 16:38 hello

CHAPITRE 10. SCURIT

Notez quil y a maintenant un x la n de la premire chane de caractres. Loption o+x signie que vous voulez donner la permission dexcution tous les autres gens (ni le propritaire, ni les membres du groupe propritaire). Pour rvoquer les permissions en criture du groupe, vous utiliseriez g-w. Consultez la page de manuel de la section 1 sur chmod pour plus de dtails sur sa syntaxe :
% man 1 chmod

Dans un programme, vous pouvez utiliser lappel systme stat pour dterminer les permissions associes un chier. Cette fonction prend deux paramtres : le nom du chier sur lequel vous voulez des renseignements et ladresse dune structure de donnes renseigne avec des informations sur le chier. Consultez lAppendice B, E/S de Bas Niveau , Section B.2, stat , pour une prsentation des autres informations que vous pouvez obtenir via stat. Le Listing 10.2 montre un exemple dutilisation de stat pour dterminer les permissions associes un chier. Listing 10.2 (stat-perm.c) Dterminer si le Propritaire a les Droits dcriture
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

# include < stdio .h > # include < sys / stat .h > int main ( int argc , char * argv []) { const char * const filename = argv [1]; struct stat buf ; /* Rcupre les informations sur le fichier . */ stat ( filename , & buf ) ; /* Affiche un message si les permissions sont dfinies de faons ce que le propritaire puisse y crire . */ if ( buf . st_mode & S_IWUSR ) printf ( " Le propritaire peut crire dans % s .\ n " , filename ) ; return 0; }

Si vous excutez ce programme sur notre script hello, il indique :


% ./stat-perm hello Le propritaire peut crire dans hello.

La constante S_IWUSR correspond la permission en criture pour le propritaire. Il y a une constante pour chaque bit. Par exemple, S_IRGRP correspond la permission en lecture pour le groupe propritaire et S_IXOTH la permission en excution pour les utilisateurs qui ne sont ni propritaires, ni membre du groupe. Si vous stockez les permissions dans une variable, utilisez le typedef mode_t. Comme la plupart des appels systme, stat renverra -1 et positionnera errno sil ne peut pas obtenir les informations sur le chier. Vous pouvez utiliser la fonction chmod pour modier les bits de permission dun chier existant. Appelez chmod avec le nom du chier dont vous voulez changer les permission et les bits activer sous forme dun OU binaire entre les direntes constantes cites prcdemment.

10.3. PERMISSIONS DU SYSTME DE FICHIERS

187

Par exemple, lextrait suivant rend hello lisible et excutable par le propritaire mais dsactive toutes les autres permissions associes hello :
chmod ( " hello " , S_IRUSR | S_IXUSR ) ;

Le mme systme de bits de permission sapplique aux rpertoires mais ces bits ont des signications direntes. Si un utilisateur est autoris lire le rpertoire, alors il peut consulter la liste des chiers prsents dans ce rpertoire. Avec les droits en criture, il est possible dajouter ou de supprimer des chiers. Notez quun utilisateur peut supprimer des chiers dun rpertoire sil a les droits en criture sur celui-ci, mme sil na pas la permission de modier le chier quil supprime. Si un utilisateur a les droits dexcution sur un rpertoire, il a le droit dy entrer et daccder aux chiers quil contient. Sans droit dexcution sur un rpertoire, un utilisateur ne peut pas accder aux chiers quil contient, indpendamment des droits quil dtient sur les chiers eux-mmes. Pour conclure, observons comment le noyau dcide dautoriser ou non un processus accder un chier donn. Tout dabord, il dtermine si lutilisateur demandant laccs est le propritaire du chier, un membre du groupe propritaire ou quelquun dautre. La catgorie dans laquelle tombe lutilisateur est utilise pour dterminer quel ensemble de bits lecture/criture/excution est vri. Puis, le noyau contrle lopration eectue par rapport aux permissions accordes lutilisateur4 . Il y a une exception quil convient de signaler : les processus sexcutant en tant que root (avec luser ID 0) sont toujours autoriss accder nimporte quel chier, quelques soient les permissions qui y sont associes.

10.3.1

Faille de scurit : les Programmes non Excutables

Voici un premier exemple de situation ou la scurit se complique. Vous pourriez penser que si vous interdisez lexcution dun programme, personne ne pourra le lancer. Aprs tout, cest ce que sous-entend linterdiction dexcution. Mais un utilisateur ingnieux pourrait copier le programme, modier les permissions pour rendre la copie excutable et la lancer ! Si vous interdisez lexcution de programmes sans interdire aux utilisateurs de les copier, vous crez une faille de scurit un moyen pour les utilisateurs de faire des choses que vous naviez pas prvu.

10.3.2

Sticky Bits

En plus des permissions en lecture, criture et excution, il existe un bit magique appel sticky bit 5 . Ce bit ne concerne que les rpertoires. Un rpertoire pour lequel le sticky bit est actif ne vous autorise dtruire un chier que si vous en tes le propritaire. Comme nous lavons dit prcdemment, vous pouvez normalement supprimer un chier si vous avez un accs en criture sur le rpertoire qui le contient, mme si
Le noyau peut galement refuser laccs un chier si un rpertoire dans le chemin du chier est inaccessible. Par exemple, si un processus na pas le droit daccder au rpertoire /tmp/private, il ne pourra pas lire /tmp/private/data, mme si les permissions de ce dernier sont dnies de faon ly autoriser. 5 Ce terme est anachronique ; il remonte un temps o lactivation du sticky bit (bit collant ) permettait de conserver un programme en mmoire mme lorsquil avait termin de sexcuter. Les pages alloues pour le programme taient colles en mmoire. Les sticky bits sont galement parfois appels bits de rappel .
4

188

CHAPITRE 10. SCURIT

vous nen tes pas le propritaire. Lorsque le sticky bit est activ, vous devez toujours avoir les droits en criture sur le rpertoire, mais vous devez en plus tre le propritaire du chier que vous voulez supprimer. Seuls certains rpertoires sur un systme GNU/Linux ont le sticky bit positionn. Par exemple, le rpertoire /tmp, dans lequel tout utilisateur peut placer des chiers temporaires, en fait partie. Ce rpertoire est spciquement conu pour pouvoir tre utilis par tous les utilisateur, tout le monde doit donc y crire. Mais il nest pas souhaitable quun utilisateur puisse supprimer les chiers dun autre, le sticky bit est donc activ pour ce rpertoire. De cette faon, seul le propritaire (ou root, bien sr) peut supprimer le chier. Vous pouvez voir que le sticky bit est actif grce au t la n de la liste des permissions si vous lancez ls sur /tmp :
% ls -ld /tmp drwxrwxrwt 12 root root 2048 Jan 24 17:51 /tmp

La constante correspondante utiliser avec stat et chmod est S_ISVTX. Si votre programme cre des rpertoires qui se comportent comme /tmp, cest--dire que beaucoup de personne doivent y crire sans pouvoir supprimer les chier des autres, vous devez activer le sticky bit sur ce rpertoire. Vous pouvez le faire en utilisant la commande chmod de cette faon :
% chmod o+t rpertoire

Pour le faire par programmation, appelez chmod avec le drapeau de mode S_ISVTX. Par exemple, pour activer le sticky bit du rpertoire spci par dir_path et donner un accs complet tous les utilisateur, eectuez lappel suivant :
chmod ( dir_path , S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX ) ;

10.4

ID Rels et Eectifs

Jusqu maintenant, nous avons parl de lUID et du GID associs avec un processus comme sil ny en avait quun seul de chaque. Mais, en ralit, ce nest pas aussi simple. Chaque processus a en ralit deux user ID : luser ID eectif et luser ID rel (bien sr, il y a galement un group ID eectif et un group ID rel ; presque tout ce qui est vrai pour les user ID lest galement pour les group ID). La plupart du temps, le noyau ne se proccupe que du user ID eectif. Par exemple, si un processus tente douvrir un chier, le noyau vrie luser ID eectif pour dcider sil laisse le processus accder au chier. Les fonctions geteuid et getegid dcrites prcdemment renvoient luser ID et le group ID eectifs. Les fonctions analogues getuid et getgid renvoient luser ID et le group ID rels. Si le noyau ne soccupe que de luser ID eectif, il ne semble pas trs utile de faire la distinction entre user ID rel et eectif. Cependant, il y a un cas trs important dans lequel le user ID rel est pris en compte. Si vous voulez changer luser ID eectif dun processus en cours dexcution, le noyau vrie luser ID rel et luser ID eectif. Avant dobserver comment vous pouvez changer luser ID eectif dun processus, examinons pourquoi vous pourriez vouloir le faire en reprenant lexemple de notre application de comptabilit. Supposons quil y ait un processus serveur qui ait besoin de consulter tout chier prsent

10.4. ID RELS ET EFFECTIFS

189

sur le systme, peu importe qui lait cr. Un tel processus doit sexcuter en tant que root car lui seul est sr de pouvoir accder nimporte que chier. Mais supposons maintenant quune requte arrive de la part dun utilisateur particulier (disons mitchell) pour accder des chiers quelconques. Le processus serveur pourrait examiner avec attention les permissions associes avec les chiers concerns et essayer de dterminer si mitchell devrait tre autoris accder ces chiers. Mais cela signierait dupliquer tous les traitements que le noyau ferait de toutes faons. Rimplmenter cette logique serait complexe, sujet des erreurs et pnible. Une meilleure approche est simplement de modier temporairement luser ID eectif du processus pour ne plus quil soit celui de root mais celui de mitchell puis dessayer deectuer les oprations demandes. Si mitchell na pas le droit daccder aux donnes, le noyau interdira laccs au processus et renverra des informations adquates sur lerreur. Une fois que les oprations demandes par mitchell sont termines, le processus peut rcuprer son user ID eectif original qui est root. Les programmes qui authentient les utilisateurs lorsquils essaient de se connecter tirent eux aussi avantage de cette possibilit de modier les user ID. Ces programmes sexcutent en tant que root lorsque lutilisateur saisit un login et un mot de passe, le programme de connexion vrie le nom dutilisateur et le mot de passe dans la base de donnes du systme. Puis il change la fois ses user ID rel et eectif an de devenir cet utilisateur. Enn, le programme de connexion appelle exec pour lancer le shell de lutilisateur, ce qui permet lutilisateur davoir un environnement dans lequel les user ID rel et eectif sont les siens. La fonction utilise pour modier les user ID dune processus est setreuid (il y a, bien sr, une fonction setregid similaire). Cette fonction prend deux arguments. Le premier argument est luser ID rel dsir ; le second est luser ID eectif demand. Par exemple, voici comment vous changeriez les user ID rel et eectif :
setreuid ( geteuid () , getuid () ) ;

Bien sr, le noyau ne laisse pas nimporte quel processus changer son user ID. Si un processus pouvait modier son user ID eectif volont, alors, toute personne pourrait facilement prendre lidentit de nimporte quel autre utilisateur simplement en changeant luser ID eectif de lun de ses processus. Le systme laissera un processus disposant dun user ID eectif de 0 modier son user ID sa guise (encore une fois, remarquez la puissance dont dispose un processus sexcutant en tant que root ! Un processus dont luser ID eectif est 0 peut faire tout ce quil lui plat). Tout autre processus, par contre, ne peut faire que lune des actions suivantes : dnir son user ID eectif pour quil soit le mme que son user ID rel ; dnir son user ID rel pour quil soit le mme que son user ID eectif ; changer les deux identiants. La premire possibilit serait utilise par notre systme de comptabilit lorsquil a termin daccder au chiers en tant que mitchell et veut redevenir root. La seconde par un programme de connexion une fois quil a dni luser ID eectif pour quil soit celui de lutilisateur qui sest connect. Dnir luser ID rel permet de sassurer que lutilisateur ne pourra jamais redevenir root. Lchange des deux identiants est avant tout une fonctionnalit historique, les programmes modernes lutilisent rarement. Vous pouvez passer -1 la place de lun des deux arguments de setreuid si vous ne voulez pas modier luser ID correspondant. Il existe galement une fonction raccourci appele seteuid.

190

CHAPITRE 10. SCURIT

Cette fonction dnit luser ID eectif mais ne modie pas luser ID rel. Les deux instructions suivantes font toutes deux la mme chose :
seteuid ( id ) ; setreuid ( -1 , id ) ;

10.4.1

Programmes Setuid

En utilisant les techniques prsentes ci-dessus, vous tes en mesure de crer des processus root qui sexcutent sous une autre identit de faon temporaire puis redeviennent root. Vous pouvez galement faire abandonner tous ses privilges un processus root en rednissant ses user ID rel et eectif. Voici une nigme : un processus non root peut il devenir root ? Cela semble impossible, en utilisant les techniques prcdentes, mais voici une preuve que a lest :
% whoami mitchell % su Password: ... % whoami root

La commande whoami est similaire id, except quelle nache que luser ID eectif, pas les autres informations. La commande su vous permet de devenir le superutilisateur si vous connaissez le mot de passe root. Comment fonctionne su ? Comme nous savons que le shell initial sexcute avec des user ID rel et eectif qui sont ceux de mitchell, setreuid ne nous permettra pas de les changer. Lastuce est que le programme su est un programme setuid. Cela signie que lorsquil sexcute, son user ID eectif sera celui du propritaire du chier et non luser ID du processus qui eectue lappel exec (luser ID rel est cependant toujours dtermin par ce dernier). Pour crer un programme setuid, utilisez la commande chmod +s ou loption S_ISUID si vous appelez chmod par programmation6 . tudions le programme du Listing 10.3. Listing 10.3 (setuid-test.c) Programme de Dmonstration de setuid
1 2 3 4 5 6 7 8

# include < stdio .h > # include < unistd .h > int main () { printf ( " uid =% d euid =% d \ n " , ( int ) getuid () , ( int ) geteuid () ) ; return 0; }

Supposons maintenant que ce programme est en mode setuid et que root en est le propritaire. Dans ce cas, la sortie de ls -l ressemblerait cela :
-rwsrws--x 1 root root 11931 Jan 24 18:25 setuid-test
6 Bien sr, il existe la notion parallle de programme setgid. Lorsquun tel programme sexcute, son group ID eectif est celui du groupe propritaire du chier. La plupart des programmes setuid sont galement des programmes setgid.

10.5. AUTHENTIFIER LES UTILISATEURS

191

Le bit s indique que le chier nest pas seulement excutable (comme lindiquerait un x) mais quil est galement setuid et setgid. Lorsque nous utilisons ce programme, il ache quelque chose de ce genre :
% whoami mitchell % ./setuid-test uid=501 euid=0

Notez que luser ID eectif est 0 lorsque le programme sexcute. Vous pouvez utiliser la commande chmod avec les arguments u+s ou g+s pour activer les bits setuid et setgid sur un chier excutable, respectivement par exemple :
% ls -l program -rwxr-xr-x 1 samuel csl 0 Jan 30 23:38 program % chmod g+s program % ls -l program -rwxr-sr-x 1 samuel csl 0 Jan 30 23:38 program % chmod u+s program % ls -l program -rwsr-sr-x 1 samuel csl 0 Jan 30 23:38 program

Vous pouvez galement utiliser lappel chmod avec les indicateurs de mode S_ISUID et S_ISGID. su est capable de modier luser ID eectif par le biais de ce mcanisme. Il sexcute initialement avec un user ID eectif 0. Puis il vous demande un mot de passe. Si le mot de passe concorde avec celui de root, il positionne son user ID rel de sorte quil soit celui de root puis lance un nouveau shell. Dans le cas contraire, il se termine, vous renvoyant votre shell dutilisateur non privilgi. Observons les permissions du programme su :
% ls -l /bin/su -rwsr-xr-x 1 root root 14188 Mar 7 2000 /bin/su

Notez que root est le propritaire et que le bit setuid est actif. Remarquez que su ne change pas rellement luser ID du shell partir duquel il a t lanc. Au lieu de cela, il lance un nouveau shell avec le nouvel user ID. Le shell original est bloqu jusqu ce que le nouveau se termine.

10.5

Authentier les utilisateurs

Souvent, lorsque vous crez un programme setuid, vous souhaitez nen autoriser laccs qu certains utilisateurs. Par exemple, le programme su ne vous laisse devenir root que si vous disposez du mot de passe correspondant. Le programme vous oblige prouver que vous pouvez devenir root avant de vous permettre de faire quoi que ce soit. Ce mcanisme est appel authentication le programme su vrie que vous tes celui que vous prtendez tre. Si vous administrez un systme trs scuris, lauthentication des utilisateurs par le biais dun simple mot de passe ne vous satisfera probablement pas. Les utilisateurs ont tendence noter leur mot de passe et les personnes mal intentionnes les dcouvrir. Les utilisateurs

192

CHAPITRE 10. SCURIT

choisissent gnralement des mots de passe comme leur date de naissance, le nom de leur animal de compagnie, etc 7 . Les mot de passe ne sont nalement pas une bonne garantie de scurit. Par exemple, beaucoup dorganisations utilisent dsormais un systme de mots de passe usage unique gnrs par des cartes didentit lectronique que les utilisateurs gardent sur eux. Le mme mot de passe ne peut tre utilis deux fois et vous ne pouvez obtenir de mot de passe valide partir de la carte quen entrant un code didentication. Lattaquant doit donc obtenir la carte et le code pour accder au systme. Dans des complexes extrmement scuriss, les scan rtiniens et dautres types de systmes didentication biomtriques sont utiliss. Si vous crivez un programme qui doit authentier ses utilisateurs, vous devriez permettre ladministrateur systme de slectionner le moyen dauthentication quil souhaite utiliser. GNU/Linux propose une bibliothque trs utile pour vous faciliter la tche. Ce mcanisme, appel Pluggable Authentication Modules (Modules dAuthentication Enchable), ou PAM, facilite lcriture dapplication qui authentient leurs utilisateurs comme le dsire ladministrateur systme. Il est plus simple de comprendre le fonctionnement de PAM en tudiant une application PAM simple. Le Listing 10.4 illustre lutilisation de PAM. Listing 10.4 (pam.c) Exemple dUtilisation de PAM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

# include < security / pam_appl .h > # include < security / pam_misc .h > # include < stdio .h > int main () { pam_handle_t * pamh ; struct pam_conv pamc ; /* Initialise la conversation PAM . */ pamc . conv = & misc_conv ; pamc . appdata_ptr = NULL ; /* Dmarre une nouvelle session d a u t h e n t i f i c a t i o n . */ pam_start ( " su " , getenv ( " USER " ) , & pamc , & pamh ) ; /* Authentifie l utilisateur . */ if ( p a m _ a u t h e n t i c a t e ( pamh , 0) != PAM_SUCCESS ) fprintf ( stderr , " chec de l a u t h e n t i f i c a t i o n !\ n " ) ; else fprintf ( stderr , " A u t h e n t i f i c a t i o n OK .\ n " ) ; /* Fini . */ pam_end ( pamh , 0) ; return 0; }

Pour compiler ce programme, vous devez le lier deux bibliothques : libpam et une bibliothque utilitaire appele libpam_misc :
% gcc -o pam pam.c -lpam -lpam_misc

Ce programme commence par construire un objet de conversation PAM. Cet objet est utilis par la bibliothque PAM lorsquelle a besoin dobtenir des informations de la part des utilisateurs.
7 On a dcouvert que les administrateurs systme avaient tendance choisir le mot dieu pour mot de passe plutt que nimporte quel autre (pensez-en ce que vous voulez). Aussi, si vous avez besoin dun accs root sur une machine et que ladministrateur nest pas l, une inspiration divine pourra peut tre vous aider.

10.5. AUTHENTIFIER LES UTILISATEURS

193

La fonction misc_conv utilise dans cet exemple est une fonction standard utilisant le terminal pour les entres sorties. Vous pouvez crire votre propre fonction qui ache une bote de dialogue, utilise la parole pour les entres/sorties ou propose mme des mthodes dinteractions plus exotiques. Le programme appelle ensuite pam_start. Cette fonction initialise la bibliothque PAM. Le premier argument est un nom de service. Vous devez utiliser un nom qui identie de faon unique votre application. Par exemple, si votre application sappelle whizbang, vous devriez utiliser ce nom pour le service. Cependant, le programme ne fonctionnera probablement pas moins quun administrateur systme ne congure explicitement le systme pour fonctionner avec votre service. Revenons notre exemple, nous utilisons le service su, qui indique que notre programme authentie les utilisateurs de la mme faon que la commande su. Vous ne devriez pas utiliser cette technique dans un programme rel. Slectionnez un nom de service qui vous est propre et concevez vos scripts dinstallation pour aider ladministrateur congurer PAM correctement pour votre application. Le second argument est le nom de lutilisateur que vous voulez authentier. Dans cet exemple, nous utilisons la valeur de la variable denvironnement USER (en thorie, il sagit du nom dutilisateur correspondant luser ID eectif du processus courant, mais ce nest pas toujours le cas). Dans la plupart des programmes rels, vous acheriez une invite pour saisir le nom dutilisateur. Le troisime argument est la conversation PAM, que nous avons prsente prcdemment. Lappel pam_start renseigne le handle pass en quatrime argument. Passez ce handle aux appels ultrieurs aux fonctions de la bibliothque PAM. Ensuite, le programme appelle pam_authenticate. Le second argument vous permet de spcier diverses options ; la valeur 0 demande lutilisation des valeurs par dfaut. La valeur de retour de cette fonction indique le rsultat de lauthentication. Finalement, le programme appelle pam_end pour librer toutes les structures de donnes alloues. Supposons que le mot de passe pour lutilisateur courant soit password (un mot de passe extrmement faible). Alors, lexcution de ce programme avec le mot de passe correct produit le comportement attendu :
% ./pam Password: password Authentification OK.

Si vous excutez ce programme dans un terminal, le mot de passe napparatra probablement pas lorsque vous le saisirez : il est masqu pour viter quune autre personne ne puisse lapercevoir alors que vous lentrez. Par contre, si un hacker tente dutiliser un mauvais mot de passe, la bibliothque PAM signalera lchec correctement :
% ./pam Password: rat chec de lauthentification !

Les bases que nous avons prsent sont susantes pour la plupart des programmes simples. Une documentation complte sur le fonctionnement de PAM est disponible sous /usr/doc/pam sur la plupart des systmes GNU/Linux.

194

CHAPITRE 10. SCURIT

10.6

Autres failles de scurit

Bien que ce chapitre prsente quelques failles de scurit rpandues, vous ne devez en aucun cas compter sur ce livre pour couvrir toutes les failles possibles. Beaucoup on dj t trouvs et beaucoup plus attendent de ltre. Si vous essayez dcrire du code scuris, il ny a rellement pas dautre solution que de faire appel un expert pour un audit de code.

10.6.1

Dpassement de tampon

Pratiquement toutes les applications Internet majeures, y compris sendmail, finger, tal et dautres, ont a un moment donn t victimes de failles dites de dpassement de tampon. Si vous crivez du code destin tre excut en tant que root, vous devez absolument tre familier avec ce type de failles de scurit. Cela sapplique galement si vous crivez un programme qui utilise les mcanismes de communication interprocessus. Si vous crivez un programme qui lit des chiers (ou pourrait lire des chiers) vous devez l aussi connatre les concepts de cette faille. Ce dernier critre sapplique presque tous les programmes. Fondamentalement, si vous avez lintention dcrire des applications GNU/Linux, vous devez connatre ce type de failles. Lide sous-jacente dune attaque par dpassement de tampon est de faire excuter un programme du code quil ntait pas cens excuter. Le mode opratoire habituel est dcraser une partie de la pile du processus. La pile du programme contient, entre autres, ladresse mmoire laquelle le programme doit transfrer le contrle la n de la fonction en cours. Ainsi, si vous placez le code que vous voulez excuter quelque part en mmoire et que vous modiez ladresse de retour pour pointer cet emplacement, vous pouvez faire excuter nimporte quoi au programme. Lorsque le programme terminera la fonction en cours dexcution, il sautera vers le nouveau code et excutera ce quil contient, avec les privilges du processus en cours. Il est clair que si le programme sexcute en tant que root, ce serait un dsastre. Si le processus sexcute avec les privilges dun autre utilisateur, ce nest un dsastre que pour cet utilisateur et par consquent pour les utilisateurs dpendants de ses chiers. Si le programme sexcute en tant que dmon en attente de connexions rseau, la situation est encore pire. Un dmon sexcute habituellement en tant que root. Sil contient des bugs de type dpassement de tampon, nimporte quelle personne pouvant se connecter lordinateur excutant le dmon peut en prendre le contrle en envoyant une squence de donnes au dmon via le rseau. Un programme qui nutilise pas les communications rseau est plus sr car seuls les utilisateurs disposant dun compte sur la machine peuvent lattaquer. Les versions de finger, talk et sendmail concernes par ce type de bug partageaient toutes la mme faille. Toutes utilisaient un tampon de taille xe pour lire une chane, ce qui impliquait une limite suprieure constante pour la taille de la chane, mais permettaient quand mme aux clients denvoyer des chanes plus grandes que le tampon. Voici le genre de code quelles pouvaient contenir :
# include < stdio .h > int main () {

/* Personne de cens naurait plus de 32 caractres dans son nom dutilisateur. De plus, il me semble quUNIX ne permet que des

10.6. AUTRES FAILLES DE SCURIT


nom de 8 caractres. Il y a donc suffisamment de place. */
char username [32];

195

/* Demande le nom de lutilisateur. */


printf ( " Saisisez votre identifiant de connexion : " ) ;

/* Lit une ligne saisie par lutilisateur. */


gets ( username ) ;

/* Traitements divers... */
return 0; }

Lutilisation conjointe dun tampon de 32 caractres et de la fonction gets ouvre la porte un dpassement de tampon. La fonction gets lit la saisie de lutilisateur jusqu ce quun caractre de nouvelle ligne apparasse et stocke le rsultat dans le tampon username. Les commentaires dans le code sont corrects en ce sens que les utilisateurs ont gnralement des identiants courts, aucun utilisateur bien intentionn ne saisirait plus de 32 caractres. Mais vous crivez un logiciel scuris, vous devez adopter le point de vue dun attaquant. Dans ce cas, lattaquant pourrait dlibrment saisir un nom dutilisateur trs long. Les variables locales comme username sont stockes dans la pile, aussi, en dpassant les limites du tableau, il est possible de placer des octets arbitraires sur la pile au del de la zone rserve la variable username. Le nom dutilisateur dpasse alors le tampon et crase une partie de la pile, permettant une attaque telle que celle dcrite prcdemment. Heureusement, il est facile dviter les dpassements de tampon. Lorsque vous lisez des chanes, vous devriez toujours utiliser soit une fonction, comme getline, qui alloue dynamiquement susamment despace soit une fonction qui interrompt la lecture lorsque le tampon est plein. Voici un exemple dutilisation de getline :
char * username = getline ( NULL , 0 , stdin ) ;

Cet appel utilise automatiquement malloc pour allouer un tampon susamment grand pour contenir la ligne et vous le renvoie. Vous ne devez pas oublier dappeler free pour librer le tampon an dviter les fuites mmoire. Vous vous faciliterez la vie si vous utiliser le C++ ou un autre langage proposant des primitives simples pour lire les saisies utilisateur. En C++, par exemple, vous pouvre utiliser cette simple instruction :
string username ; getline ( cin , username ) ;

La chane username sera galement dsalloue automatiquement, vous navez pas besoin dappeler free8 . Bien sr, les dpassements de tampon peuvent survenir avec nimporte quel tableau dimensionn de faon statique, pas seulement avec les chanes de caractres. Si vous voulez produire un code scuris, vous ne devriez jamais crire dans une structure de donnes, sur la pile ou ailleurs, sans vrier que vous nallez pas dpasser ses limites.
8 Certains programmeurs pensent que le C++ est un langage horrible et compliqu. Leurs arguments sur lhritage multiple et dautres complication ont un certain mrite, mais il est plus simple dcrire du code vitant les dpassements de tampon et autres problmes similaires en C++ quen C.

196

CHAPITRE 10. SCURIT

10.6.2

Conditions de concurrence critique dans /tmp

Une autre problme trs rpandu converne la cration de chiers avec des noms prdictibles, typiquement dans le rpertoire /tmp. Supposons que votre programme prog, qui sexcute avec les droits root, cre toujours un chier temporaire appel /tmp/prog et y crive des informations vitales. Un utilisateur mal intentionn pourrait crer un lien symbolique sous /tmp/prog vers nimporte quel chier du systme. Lorsque votre programme tente de crer le chier, lappel systme open nchouera pas. Cependant, les donnes que vous crirez niront pas vers /tmp/prog mais seront crites dans le chier choisi par lattaquant. On dit de ce genre dattaque quelle exploite un condition de concurrence critique. Il y a une concurrence implicite entre vous et lattaquant. Celui qui arrive crer le chier en premier gagne. Cette attaque est gnralement utilise pour dtruire des lments importants du systme de chiers. En crant les liens appropris, lattaquant peut utiliser un programme sexcutant en tant que root croyant crire dans un chier temporaire pour craser un chier systme important. Par exemple, en crant un lien symbolique vers /etc/passwd, lattaquant peut eacer la base de donnes des mots de passe du systme. Il existe galement des moyens pour lattaquant dobtenir un accs root en utilisant cette technique. Une piste pour viter ce genre dattaque serait dutiliser un nom alatoire pour le chier. Par exemple, vous pourriez utiliser /dev/random pour injecter une partie alatoire dans le nom du chier. Cela complique bien sr la tche de lattaquant pour deviner le nom du chier, mais cela ne len empche pas. Il pourrait crer un nombre consquent de liens symboliques en utilisant beaucoup de nom potentiels. Mme sil doit essayer 10 000 fois avant dobtenir des conditions de concurrence critique, cette seule fois peut tre dsastreuse. Une autre approche est dutiliser loption O_EXCL lors de lappel open. Cette option provoque lchec de louverture si le chier existe dj. Malheureusement, si vous utilisez le Network File System (NFS), ou si un utilisateur de votre programme est susceptible dutiliser NFS, cette approche nest pas assez robuste car O_EXCL nest pas able sur un systme de chier NFS. Vous ne pouvez pas savoir avec certitude si votre code sera utilis sur un systme disposant de NFS, aussi, si vous tes paranoaque, ne vous reposez pas sur O_EXCL. Dans le Chapitre 2, crire des Logiciels GNU/Linux de Qualit, Section 2.1.7, Utilisation de Fichiers temporaires , nous avons prsent mkstemp. Malheureusement, sous Linux, mkstemp ouvre le chier avec loption O_EXCL aprs avoir dtermin un nom susamment dur deviner. En dautres termes, lutilisation de mkstemp nest pas sre si /tmp est mont via NFS9 . Lutilisation de mkstemp est donc mieux que rien mais nest pas totalement sre. Une approche qui fonctionne est dutiliser lstat sur le nouveau chier (lstat est prsent dans la Section B.2, stat ). La fonction lstat est similaire stat, except que si le chier est lien symbolique, lstat vous donne des informations sur ce lien et non sur le chier vers lequel il pointe. Si lstat vous indique que votre nouveau chier est un chier ordinaire, pas un lien symbolique, et que vous en tes le propritaire, alors tout devrait bien se passer. Le Listing 10.5 prsente une fonction tentant douvrir un chier dans /tmp de faon scurise. Les auteurs de ce livre ne lont pas fait audit de faon professionnelle et ne sont pas non plus
9

Bien sr, si vous tes administrateur systme, vous ne devriez pas monter un systme NFS sur /tmp.

10.6. AUTRES FAILLES DE SCURIT

197

des experts en scurit, il y a donc de grandes chances quelle ait une faiblesse. Nous ne vous recommandons pas son utilisation sans lavoir faite auditer, mais elle devrait vous convaincre que lcriture de code scuris est complexe. Pour vous dissuader encore plus, nous avons dlibrment dni linterface de faon ce quelle soit complexe utiliser dans un programme rel. La vrication derreurs tient une place importante dans lcriture de logiciels scuriss, nous avons donc inclus la logique de contrle derreurs dans cet exemple. Listing 10.5 (temp-le.c) Crer un Fichier Temporaire
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51

# include # include # include # include

< fcntl .h > < stdlib .h > < sys / stat .h > < unistd .h >

/* Renvoie le descripteur de fichier d un nouveau fichier temporaire . Le fichier pourra tre lu ou crit par l user ID effectif du processus courant et par personne d autre . Renvoie -1 si le fichier temporaire ne peut pas tre cr . */

int s e c u r e _ t e m p _ f i l e () { /* Ce descripteur de fichier pointe vers / dev / random et nous permet de disposer d une bonne source de nombres alatoires . */ static int random_fd = -1; /* Entier alatoire . */ u n s i g n e d int random ; /* Tampon utilis pour convertir random en chane de caractres . Le tampon une taille fixe , ce qui signifie que nous sommes p o t en t i e l l e me n t vulnrables un bug de dpassement de tampon si les entiers de la machine d excution tiennent sur un nombre * consquent * de bits . */ char filename [128]; /* Descripteur de fichier du nouveau fichier temporaire . */ int fd ; /* Informations sur le nouveau fichier . */ struct stat stat_buf ; /* Si nous n avons pas encore ouvert / dev / random nous le faisons maintenant . Cette faon de faire n est pas threadsafe . */ if ( random_fd == -1) { /* Ouvre / dev / random . Notez que nous supposons ici que / dev / random est effectivement une source de bits alatoire et non pas un fichier rempli de zros plac ici par l attaquant . */ random_fd = open ( " / dev / random " , O_RDONLY ) ; /* Abandonne si l on ne peut pas ouvrir / dev / random . */ if ( random_fd == -1) return -1; } /* Lit un entier partir de / dev / random . */ if ( read ( random_fd , & random , sizeof ( random ) ) != sizeof ( random ) ) return -1; /* Cre un fichier partir du nombre alatoire . */ sprintf ( filename , " / tmp /% u " , random ) ; /* Tente d ouvrir le fichier . */ fd = open ( filename , /* Nous utilisons O_EXECL ,

198
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77

CHAPITRE 10. SCURIT


mme si cela ne fonctionne pas avec NFS . */ O_RDWR | O_CREAT | O_EXCL , /* Personne ne doit pouvoir lire ou crire dans le fichier . */ S_IRUSR | S_IWUSR ) ; if ( fd == -1) return -1; /* Appelle lstat sur le fichier afin de s assurer qu il ne s agit pas d un lien symbolique . */ if ( lstat ( filename , & stat_buf ) == -1) return -1; /* Si le fichier n est pas un fichier traditionnel , quelqu un a tent de nous piger . */ if (! S_ISREG ( stat_buf . st_mode ) ) return -1; /* Si le fichier ne nous appartient pas , quelqu un d autre pourrait le supprimer , le lire ou le modifier alors que nous nous en servons . */ if ( stat_buf . st_uid != geteuid () || stat_buf . st_gid != getegid () ) return -1; /* Si il y a d autres bits de permissions actifs , quelque chose cloche . */ if (( stat_buf . st_mode & ~( S_IRUSR | S_IWUSR ) ) != 0) return -1; return fd ; }

Cette fonction appelle open pour crer le chier puis appelle lstat quelques lignes plus loin pour sassurer que le chier nest pas un lien symbolique. Si vous rchissez attentivement, vous raliserez quil semble y avoir une condition de concurrence critique dans ce cas. En eet, un attaquant pourrait supprimer le chier et le remplacer par un lien symbolique entre le moment o nous appelons open et celui o nous appelons lstat. Cela naurait pas dimpact direct sur cette fonction car nous avons dj un descripteur de chier ouvert pointant sur le nouveau chier, mais nous indiquerions une erreur lappelant. Cette attaque ne causerait pas de dommages directs mais rendrait impossible le fonctionnement de lappelant. Une telle attaque est dite dni de service (DoS, Denial of Service). Heureusement, le sticky bit vient notre aide. Comme le sticky bit est actif sur /tmp, personne dautre que nous ne peut supprimer les chiers de ce rpertoire. Bien sr, root peut toujours supprimer des chiers, mais si lattaquant dispose dj des privilges root, il ny a rien qui puisse protger votre programme. Si vous choisissez de supposer que ladministrateur systme est comptent, alors /tmp ne sera pas mont via NFS. Et si ladministrateur systme est susamment stupide pour monter /tmp en NFS, il y a de bonnes chances que le sticky bit ne soit pas actif non plus. Aussi, pour la plupart des utilisations, nous pensons quil est sr dutiliser mkstemp. Mais vous devez tre conscient de ces problmes et ne devez pas vous reposer sur le fonctionne de O_EXCL pour un fonctionnement correct si le rpertoire utilis nest pas /tmp pas plus que vous ne devez supposer que le sticky bit est actif ailleurs.

10.6.3

Utilisation de system ou popen

La troisime faille de scurit que tout programme devrait avoir en tte est lutilisation du shell pour excuter dautres programme. Prenons lexemple ctif dun serveur dictionnaire. Ce

10.6. AUTRES FAILLES DE SCURIT

199

programme est conu pour accepter les connexions venant dInternet. Chaque client envoie un mot et le serveur indique sil sagit dun mot anglais valide. Comme tout systme GNU/Linux dispose dune liste denviron 45000 mots anglais dans /usr/share/dict/word, une faon simple de crer ce serveur est dinvoquer le programme grep, comme ceci :
% grep -x word /usr/dict/words

Ici, word est le mot que souhaite valider lutilisateur. Le code de sortie de grep nous indiquera si le mot gure dans /usr/share/dict/words10 . Le Listing 10.6 vous montre comment vous pourriez coder la partie du serveur invoquant grep : Listing 10.6 (grep-dictionary.c) Cherche un Mot dans le Dictionnaire
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

# include < stdio .h > # include < stdlib .h > /* Renvoie une valeur diffrente de 0 si et seulement si WORD figure dans / usr / dict / words . */ int grep_for_word ( const char * word ) { size_t length ; char * buffer ; int exit_code ; /* Construit la chane " grep -x WORD / usr / dict / words ". Alloue la chane dynamiquement pour viter les dpassements de tampon . */ length = strlen ( " grep -x " ) + strlen ( word ) + strlen ( " / usr / dict / words " ) + 1; buffer = ( char *) malloc ( length ) ; sprintf ( buffer , " grep -x % s / usr / dict / words " , word ) ; /* Excute la commande . */ exit_code = system ( buffer ) ; /* Libre le tampon . */ free ( buffer ) ; /* Si grep a renvoy 0 , le mot tait prsent dans le dictionnaire . */ return exit_code == 0; }

Remarquez quen calculant le nombre de caractres dont nous avons besoin et en allouant le tampon dynamiquement, nous sommes srs dviter les dpassements de tampon. Malheureusement, lutilisation de la fonction system (dcrite dans le Chapitre 3, Processus, Section 3.2.1, Utiliser system) nest pas sr. Cette fonction invoque le shell systme standard pour lancer la commande puis renvoyer la valeur de sortie. Mais que se passe-t-il si un attaquant envoie un mot qui est fait la ligne suivante ou quelque chose du mme type ?
foo /dev/null; rm -rf /

Dans ce cas, le serveur excutera cette commande :


grep -x foo /dev/null; rm -rf / /usr/dict/words

Si vous ne connaissez pas grep, vous devriez consulter les pages de manuel. Cest un programme incoyablement utile.

10

200

CHAPITRE 10. SCURIT

Le problme est maintenant vident. Lutilisateur a transform une commande, linvocation de grep, en deux commandes car le shell traite le point virgule comme un sparateur de commandes. La premire commande est toujours linvocation inoensive de grep, mais la seconde supprime tous les chiers du systme ! Mme si le serveur ne sexcute pas en tant que root, tous les chiers qui peuvent tre supprims par lutilisateur sous lequel sexcute le serveur seront supprims. Le mme problme peut survenir avec popen (dcrit dans la Section 5.4.4, popen et pclose), qui cre un pipe entre le processus pre et le ls mais utilise quand mme le shell pour lancer la commande. Il y a deux faons dviter ces problmes. La premire est dutiliser les fonctions de la famille exec au lieu de system et popen. Cette solution contourne le problme car les caractres considrs comme spciaux par le shell (comme le point-virgule dans la commande prcdente) ne sont pas traits lorsquils apparaissent dans les arguments dun appel exec. Bien sr, vous abandonnez le ct pratique de system et popen. Lautre alternative est de valider la chane pour sassurer quelle nest pas dangereuse. Dans lexemple du serveur dictionnaire, vous devriez vous assurer que le mot ne contient que des caractres alphabtiques, en utilisant la fonction isalpha. Si elle ne contient pas dautres caractres, il ny a aucun moyen de piger le shell en lui faisant excuter une autre commande. Nimplmentez pas la vrication en recherchant des caractres dangereux ou inattendus ; il est toujours plus sr de rechercher explicitement les caractres dont vous savez quils sont srs que dessayer danticiper sur tous les caractres pouvant tre problmatiques.

Troisime partie

Annexes

201

Table des Matires


A Autres Outils de Dveloppement B E/S de Bas Niveau C Tableau des Signaux D Ressources en Ligne E Open Publication License Version 1.0 F Licence Publique Gnrale GNU 205 225 241 243 245 249

204

TABLE DES MATIRES

Annexe A

Autres Outils de Dveloppement


e dveloppement de programmes GNU/Linux rapides et ables en C ou en C++ ncessite plus que la comprhension en surface du fonctionnement du systme dexploitation et des appels systme. Dans cette annexe, nous prsenterons des outils de dveloppement permettant de dtecter les erreurs lexcution comme lutilisation incorrecte dune zone mmoire alloue dynamiquement et de dterminer quelles parties du programme monopolisent le plus de temps dexcution. Lanalyse du code source dun programme peut rvler certaines de ces informations ; en utilisant ces outils danalyse dynamique et en excutant eectivement le programme, vous pouvez en obtenir beaucoup plus.

A.1

Analyse Statique de Programmes

Certaines erreurs de programmation peuvent tre dtectes en utilisant des outils danalyse statique qui tudient le code source du programme. Si vous invoquez GCC avec loption -Wall ou -pedantic, le compilateur ache des avertissement sur les structures de programmation risques ou potentiellement fausses. En liminant de telles constructions, vous rduirez les risques de bugs et faciliterez la compilation de vos programmes sur dautres variantes de GNU/Linux et mme sur dautres systmes dexploitation. En utilisant diverses options, vous pouvez faire en sorte que GCC mette des avertissements sur un nombre impressionnant de structures de programmation douteuses. Loption -Wall active la plupart des vrications. Par exemple, le compilateur achera un avertissement sur un commentaire commenant au sein dun autre commentaire, sur un type de retour incorrect pour la fonction main ou pour une fonction non void qui ne dispose pas dinstruction return. Si vous utilisez loption -pedantic, GCC met des avertissements pour tout ce qui ne respecte pas les normes ANSI C et ISO C++. Par exemple, lutilisation de lextension GNU asm provoque lmission dun avertissement avec cette option. Un petit nombre dextensions GNU, comme lutilisation des mots cls alternatifs commenant par __ (deux tirets bas), ne dclencheront aucun message davertissement. Bien que la page info de GCC marque lutilisation de ces options comme dprcie, nous vous recommandons de les utiliser pour viter la plupart des 205

206

ANNEXE A. AUTRES OUTILS DE DVELOPPEMENT

extensions GNU du langage car ces extensions ont tendance changer au cours du temps et interagir de faon nfaste sur loptimisation du code. Listing A.1 (hello.c) Programme Coucou
1 2 3 4

main () { printf ( " Coucou .\ n " ) ; }

Compilez le programme "Coucou" du Listing A.1. Bien que GCC le compile sans rien dire, le code source nobit pas aux rgles ANSI C. Si vous activez les avertissement en compilant avec les options -Wall -pedantic, GCC indique trois constructions douteuses.
% gcc -Wall -pedantic hello.c hello.c:2: warning: return type defaults to "int" hello.c: In function "main": hello.c:3: warning: implicit declaration of function "printf" hello.c:4: warning: control reaches end of non-void function

Ces avertissements indiquent les problmes suivants : Le type de retour de main nest pas prcis ; La fonction printf est dclare de faon implicite car <stdio.h> nest pas inclus ; La fonction, implicitement dclare comme renvoyant un int, ne renvoie en fait aucune valeur. Lanalyse du code source dun programme ne permet pas de dtecter toutes les erreurs de programmation et les lacunes. Dans la section suivante, nous prsentons quatre outils aidant dtecter les erreurs commises dans lutilisation de mmoire alloue dynamiquement. Dans les sections suivantes, nous montrerons comment analyser le temps dexcution dun programme laide du proler gprof.

A.2

Dtection des Erreurs dAllocation Dynamique

Lorsque vous crivez un programme, vous ne savez gnralement pas quelle quantit de mmoire il ncessitera pour sexcuter. Par exemple, une ligne lue partir dun chier au moment de lexcution peut avoir nimporte quelle taille. Les programmes C et C++ utilisent malloc , free et leurs variantes pour allouer dynamiquement de la mmoire pendant lexcution du programme. Voici quelques rgles sur lutilisation de mmoire alloue dynamiquement : Le nombre dallocations (appels malloc) doit correspondre exactement au nombre de librations (appels free) ; Les lectures et critures doivent se faire dans lespace allou, pas au del ; La mmoire alloue ne doit pas tre utilise avant son allocation ou aprs sa libration. Comme lallocation et la libration dynamiques ont lieu durant lexcution, les analyses statiques ne peuvent que rarement dtecter les violations de ces rgles. Cest pourquoi il existe des programmes de vrication mmoire qui excutent le programme et collectent des informations pour dterminer si une telle violation a lieu. Voici un exemple de types de violations pouvant tre dtects : Lecture dun emplacement mmoire avant allocation ;

A.2. DTECTION DES ERREURS DALLOCATION DYNAMIQUE

207

criture dans un emplacement mmoire avant son allocation ; Lecture dune position se trouvant avant la zone alloue ; criture dune position se trouvant avant la zone alloue ; Lecture dune position se trouvant aprs la n de la zone alloue ; criture dune position se trouvant aprs la n de cette zone ; Lecture dun emplacement mmoire aprs sa libration ; criture dans un emplacement aprs sa libration ; chec de la libration de la mmoire ; Double libration de mmoire ; Libration de mmoire non alloue. Il est galement utile dtre averti lors dune demande dallocation de zro octet car il sagit certainement dune erreur de la part du programmeur. Le Tableau A.1 indique les fonctionnalits des quatre outils de diagnostic. Malheureusement, il nexiste pas doutil diagnostiquant toutes les erreurs dutilisation de la mmoire. De plus, aucun outil ne dtecte lcriture ou la lecture dune zone non alloue, toutefois, une telle opration dclencherait probablement une erreur de segmentation. Ces outils ne dtectent que les erreurs survenant durant lexcution du programme. Si vous excutez le programme avec des donnes qui ne ncessitent pas dallocation mmoire, les outils ne dtecteront aucun problme. Pour tester un programme de faon exhaustive, vous devez lexcuter en utilisant direntes donnes dentre pour vous assurer que tous les chemins possible au sein de votre programme sont parcourus. De plus, vous ne pouvez utiliser quun seul outil la fois, vous devez donc recommencer les mmes tests avec plusieurs outils pour vous assurer davoir le plus de vrications possibles.

Tab. A.1 Fonctionnalits de Dirents Outils de Vrication Mmoire (X : prise en charge totale, O : dtection occasionnelle) Comportement erron malloc mtrace ccmalloc Electric Fence Lecture avant allocation criture avant allocation Lecture avant la zone alloue X criture avant la zone allou O O X Lecture aprs la zone alloue X criture aprs la zone alloue X X Lecture aprs libration X criture aprs libration X chec de libration X X Double libration X X Libration de mmoire non alloue X X Allocation de taille nulle X X Dans les sections qui suivent, nous dcrirons comment utiliser la vrication fournie par malloc et mtrace, qui sont les deux plus simples, puis nous nous intresserons ccmalloc et Electric Fence.

208

ANNEXE A. AUTRES OUTILS DE DVELOPPEMENT

A.2.1

Programme de Test dAllocation et de Libration Mmoire

Nous utiliserons les programme malloc-use du Listing A.2 pour illustrer lallocation, la libration et lutilisation de la mmoire. Pour le lancer, passez-lui le nombre maximum de rgions mmoire allouer en premier argument. Par exemple, malloc-use 12 cre un tableau A avec 12 pointeurs de caractres qui ne pointent sur rien. Le programme accepte cinq commandes direntes : Pour allouer b octets sur lesquels pointe lentre A[i], saisissez a i b. Lindice i peut tre nimporte quel nombre non ngatif infrieur largument de ligne de commande ; Pour librer la mmoire se situant lindice i, entrez d i ; Pour lire le pme caractre de la mmoire alloue lindice i (comme avec A[i][p]), saisissez r i p. Ici, p peut avoir nimporte quelle valeur entire ; Pour crire un caractre la pme position de la mmoire alloue lindice i, entrez w i p; Lorsque vous avez termin, saisissez q. Nous prsenterons le code du programme plus loin dans la Section A.2.7 et illustrerons comment lutiliser.

A.2.2

Vrication par malloc

Les fonctions dallocation mmoire fournies avec la bibliothque C GNU peut dtecter lcriture en mmoire avant une zone alloue et la double libration. La dnition de la variable denvironnement MALLOC_CHECK_ la valeur 2 provoque larrt dun programme lorsquune telle erreur est dtecte (attention, la variable denvironnement se termine par un tiret bas). Il nest pas ncessaire de recompiler le programme. Voici un exemple de dtection dcriture un emplacement mmoire juste avant le dbut dune zone alloue :
% export MALLOC_CHECK_=2 % ./malloc-use 12 Please enter a command: a 0 10 Please enter a command: w 0 -1 Please enter a command: d 0 Aborted (core dumped)

la commande export active les vrications de malloc. La valeur 2 provoque larrt immdiat du programme lors de la dtection dune erreur. Lutilisation de la vrication par malloc est avantageuse car elle ne ncessite aucune recompilation, mais ltendue des vrications est assez limite. Fondamentalement, il sagit de sassurer que les structures de donnes utilises pour lallocation mmoire ne sont pas corrompues. Cest pourquoi il est possible de dtecter une double libration du mme emplacement mmoire. De plus, lcriture juste avant le dbut dune zone alloue peut tre dtecte facilement car le dispositif dallocation mmoire stocke la taille de chaque zone alloue juste avant celle-ci. Aussi, une criture juste avant la zone corrompt cette taille. Malheureusement, la vrication de cohrence ne peut avoir lieu que lorsque le programme appelle des routines dallocation, pas lorsquil accde la mmoire, aussi, un nombre important de lectures et dcritures peuvent

A.2. DTECTION DES ERREURS DALLOCATION DYNAMIQUE

209

avoir lieu avant quune erreur ne soit dtecte. Dans lexemple prcdent, lcriture illgale na t dtecte que lorsque la mmoire a t libre.

A.2.3

Recherche de Fuites Mmoire avec mtrace

Loutil mtrace aide diagnostiquer les erreurs les plus courantes lors de lallocation dynamique de mmoire : la non concordance entre le nombre dallocations et de librations. Lutilisation de mtrace se fait en quatre tapes, mtrace est fourni avec la bibliothque C GNU : 1. Modier le code source pour inclure <mcheck.h> et invoquer mtrace() ds le dbut du programme, au dbut de main. Lappel mtrace active la surveillance des allocations et librations mmoire ; 2. Indiquer le nom dun chier pour stocker les informations sur les allocations et librations mmoire :
% export MALLOC_TRACE=memory.log

3. Excuter le programme. Toutes les allocations et librations sont stockes dans le chier journal. 4. Utiliser la commande mtrace pour analyser les allocations et librations mmoire pour sassurer que leur nombre concorde.
% mtrace mon_programme $MALLOC_TRACE

Les messages mis par mtrace sont relativement simples comprendre. Par exemple, avec notre excution de malloc-use, ils seraient de ce type :
- 0000000000 Free 3 was never allocd malloc-use.c:39 Memory not freed: ----------------Address Size Caller 0x08049d48 0xc at malloc-use.c:30

Ces messages indiquent une libration de mmoire qui na jamais t alloue la ligne 39 de malloc-use.c et une zone de mmoire alloue la ligne 30 non libre. mtrace dtecte les erreurs grace lanalyse du chier spci par la variable denvironnement MALLOC_TRACE, celui-ci contient la liste de toutes les allocations et librations mmoire du programme. Lexcutable doit se terminer normalement pour que les donnes soient crites. La commande mtrace analyse le chier et dresse la liste des allocations et librations qui nont pas de rciproque.

A.2.4

Utiliser ccmalloc

La bibliothque ccmalloc dtecte les erreurs mmoire en remplaant les appels malloc et free avec des instructions traant leur utilisation. Si le programme se termine correctement, elle cre un rapport concernant les fuites mmoire et dautres erreurs. La bibliothque ccmalloc a t crite par Armin Bierce. Vous devrez probablement tlcharger et installer la bibliothque ccmalloc vous-mme. Elle est disponible sur http://www.inf.ethz.ch/personal/biere/projects/ccmalloc/, dcompressez les sources et lancez configure. Excutez make et make install, copiez le chier ccmalloc.cfg

210

ANNEXE A. AUTRES OUTILS DE DVELOPPEMENT

dans le rpertoire do vous lancerez le programme que vous voulez contrler et renommez la copie en .ccmalloc. Vous tes maintenant prt utiliser ccmalloc. Les chiers objets du programme doivent tre lis avec la bibliothque ccmalloc et la bibliothque de liaison dynamique. Ajoutez -lccmalloc et -ldl votre commande ddition de liens, par exemple :
% gcc -g -Wall -pedantic malloc-use.o -o ccmalloc-use -lccmalloc -ldl

Lancez votre programme pour crer un rapport. Par exemple, lexcution de notre programme malloc-use de faon ce quil ne libre pas une zone mmoire produit le rapport suivant :
% ./ccmalloc-use 12 file-name=a.out does not contain valid symbols trying to find executable in current directory ... using symbols from "ccmalloc-use" (to speed up this search specify "file ccmalloc-use" in the startup file ".ccmalloc") Please enter a command: a 0 12 Please enter a command: q. ---------------. |ccmalloc report| ======================================================== | total # of| allocated | deallocated | garbage | +-----------+-------------+-------------+--------------+ | bytes | 60 | 48 | 12 | +-----------+-------------+-------------+--------------+ |allocations| 2| 1| 1| +------------------------------------------------------+ | number of checks: 1 | | number of counts: 3 | | retrieving function names for addresses ... done. | | reading file info from gdb ... done. | | sorting by number of not reclaimed bytes ... done. | | number of call chains: 1 | | number of ignored call chains: 0 | | number of reported call chains: 1 | | number of internal call chains: 1 | | number of library call chains: 0 | ======================================================== | *100.0% = 12 Bytes of garbage allocated in 1 allocation | | | | 0x400389cb in <???> | | | | 0x08049198 in <main> | | at malloc-use.c:89 | | | | 0x08048fdc in <allocate> | | at malloc-use.c:30 | | | +-----> 0x08049647 in <malloc> | at src/wrapper.c:284 | +------------------------------------------------------

Les dernires lignes donnent la liste des appels de fonctions qui ont allou de la mmoire sans la librer.

A.2. DTECTION DES ERREURS DALLOCATION DYNAMIQUE

211

Pour utiliser ccmalloc an de diagnostiquer des criture avant le dbut ou aprs la n dune zone mmoire alloue, vous devrez modier le chier .ccmalloc dans le rpertoire du programme. Ce chier est lu au dmarrage du programme.

A.2.5

Electric Fence

crit par Bruce Perens, Electric Fence stoppe lexcution du programme la ligne exacte de la lecture ou de lcriture en dehors dune zone alloue. Il sagit du seul outil qui dtecte les lectures illgales. Il est inclus dans la plupart des distributions GNU/Linux, le code source est tout de mme disponible sur http://www.perens.com/FreeSoftware/. Comme pour ccmalloc, les chiers objets de votre programme doivent tre lis la bibliothque Electric Fence en ajoutant -lefence la commande ddition de liens, par exemple :
% gcc -g -Wall -pedantic malloc-use.o -o emalloc-use -lefence

Electric Fence vrie la validit des utilisations de la mmoire au cours de lexcution du programme. Une mauvaise utilisation provoque une erreur de segmentation :
% ./emalloc-use 12 Electric Fence 2.0.5 Copyright (C) 1987-1998 Bruce Perens. Please enter a command: a 0 12 Please enter a command: r 0 12 Segmentation fault

En utilisant un dbogueur, vous pouvez dterminer lemplacement de laction illgale. Par dfaut, Electric Fence ne diagnostique que les accs des emplacements situs aprs la zone alloue. Pour activer la dtection des accs des emplacements situs avant la zone alloue la place des accs des zones situes aprs, utilisez cette commande :
% export EF_PROTECT_BELOW=1

Pour dtecter les accs des emplacements mmoire librs, positionnez EF_PROTECT_FREE 1. Des fonctionnalits supplmentaires sont dcrites dans la page de manuel de libefence. Electric Fence peut diagnostiquer des accs illgaux en mobilisant au moins deux pages mmoire pour toute allocation. Il place la zone alloue la n de la premire page, ainsi, tout accs aprs la n de la zone alloue provoque une erreur de segmentation. Si vous positionnez EF_PROTECT_BELOW 1, il place la zone alloue au dbut de la seconde page. Comme chaque appel malloc utilise deux pages mmoire, Electric Fence peut consommer une quantit importante de mmoire. Nutilisez cette bibliothque qu des ns de dbogage.

A.2.6

Choisir Parmi les Dirents Outils de Dbogage Mmoire

Nous avons prsent quatre outils distincts, incompatibles destins diagnostiquer de mauvaises utilisations de mmoire alloue dynamiquement. Alors comment un programmeur GNU/Linux peut-il sassurer que la mmoire est utilise correctement ? Aucun outil ne garantit la dtection de toutes les erreurs, mais lutilisation de nimporte lequel dentre eux augmente la probabilit den dcouvrir. Pour faciliter la dtection derreurs concernant la mmoire alloue dynamiquement, dveloppez et testez le code lutilisant sparment du reste. Cela rduit le

212

ANNEXE A. AUTRES OUTILS DE DVELOPPEMENT

volume de code analyser lors de la recherche de telles erreurs. Si vous crivez vos programmes en C++, ddiez une classe la gestion de la mmoire dynamique. Si vous programmez en C, minimisez le nombre de fonctions utilisant lallocation et la libration. Lors des tests de ce code, assurez-vous quun seul outil sexcute la fois car ils sont incompatibles. Lorsque vous testez le programme, assurez-vous de varier la faon dont vous lutilisez an de tester toutes les portions de code. Lequel de ces outils devriez-vous utiliser ? Comme labsence dquilibre entre les allocations et les librations est lerreur la plus courante en matire de gestion dynamique de la mmoire, utilisez mtrace au dbut du processus de dveloppement. Ce programme est disponible pour tous les systme GNU/Linux et a t prouv. Aprs vous tre assur que les nombres dallocations et de librations sont identiques, utilisez Electric Fence pour dtecter les accs mmoire invalides. Vous liminerez ainsi presque toutes les erreurs mmoire. Lorsque vous utilisez Electric Fence, vous devez tre attentif ne pas eectuer trop dallocations et de librations car chaque allocation ncessite au moins deux pages mmoire. Lutilisation de ces deux outils vous permettra de dtecter la plupart des erreurs.

A.2.7

Code Source du Programme dAllocation Dynamique de Mmoire

Le Listing A.2 prsente le code source dun programme illustrant lallocation dynamique, la libration et lutilisation de mmoire. Consultez la Section A.2.1, Programme de Test dAllocation et de Libration Mmoire, pour une description de son utilisation. Listing A.2 (malloc-use.c) Dynamic Memory Allocation Checking Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

/* Utilisation des fonctions C d allocation mmoire . */ /* Invoquez le programme en utilisant un argument prcisant la taille du tableau . Ce tableau est compos de pointeurs sur des tableaux pouvant tre allous par la suite . Le programme accepte les commandes suivantes : o allouer de la mmoire : a < indice > < taille > o librer de la mmoire : d < indice > o lire un emplacement mmoire : r < indice > < position - dans - la - mmoire - alloue > o crire un emplacement : w < indice > < position - dans - la - mmoire - alloue > o quitter : q L utilisateur a la re sp on sabil it de respecter les rgles de l allocation dynamique de la mmoire ( ou non ) . */ # ifdef MTRACE # include < mcheck .h > # endif /* MTRACE */ # include < stdio .h > # include < stdlib .h > # include < assert .h > /* Alloue de la mmoire pour la taille spcifie , renvoie une valeur diffrentes de zro en cas de succs . */ void allocate ( char ** array , size_t size ) { * array = malloc ( size ) ; } /* Libre la mmoire . */ void deallocate ( char ** array ) { free (( void *) * array ) ; } /* Lit un emplacement mmoire . */

A.2. DTECTION DES ERREURS DALLOCATION DYNAMIQUE


31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92

213

void r e a d _ f r o m _ m e m o r y ( char * array , int position ) { char character = array [ position ]; } /* crit un emplacement mmoire . */ void w r i te _ t o _ m e mo r y ( char * array , int position ) { array [ position ] = a ; } int main ( int argc , char * argv []) { char ** array ; u n s i g n e d array_size ; char command [32]; u n s i g n e d array_index ; char co mm an d_l et ter ; int s i z e _ o r _ p o s i t i o n ; int error = 0; # ifdef MTRACE mtrace () ; # endif /* MTRACE */ if ( argc != 2) { fprintf ( stderr , " % s : taille - du - tableau \ n " , argv [0]) ; return 1; } array_size = strtoul ( argv [1] , 0 , 0) ; array = ( char **) calloc ( array_size , sizeof ( char *) ) ; assert ( array != 0) ; /* Effectue ce que l utilisateur demande . */ while (! error ) { printf ( " Entrez une commande : " ) ; co mm an d_l et ter = getchar () ; assert ( c omm an d_ let te r != EOF ) ; switch ( com ma nd _le tt er ) { case a : fgets ( command , sizeof ( command ) , stdin ) ; if ( sscanf ( command , " % u % i " , & array_index , & s i z e _ o r _ p o s i t i o n ) == 2 && array_index < array_size ) allocate (&( array [ array_index ]) , s i z e _ o r _ p o s i t i o n ) ; else error = 1; break ; case d : fgets ( command , sizeof ( command ) , stdin ) ; if ( sscanf ( command , " % u " , & array_index ) == 1 && array_index < array_size ) deallocate (&( array [ array_index ]) ) ; else error = 1; break ; case r : fgets ( command , sizeof ( command ) , stdin ) ; if ( sscanf ( command , " % u % i " , & array_index , & s i z e _ o r _ p o s i t i o n ) == 2 && array_index < array_size ) r e a d _ f r o m _ m e m o r y ( array [ array_index ] , s i z e _ o r _ p o s i t i o n ) ; else error = 1; break ; case w : fgets ( command , sizeof ( command ) , stdin ) ; if ( sscanf ( command , " % u % i " , & array_index , & s i z e _ o r _ p o s i t i o n ) == 2 && array_index < array_size )

214
93 94 95 96 97 98 99 100 101 102 103 104 105 106

ANNEXE A. AUTRES OUTILS DE DVELOPPEMENT


w r i te _ t o _ m e mo r y ( array [ array_index ] , s i z e _ o r _ p o s i t i o n ) ; else error = 1; break ; case q : free (( void *) array ) ; return 0; default : error = 1; } } free (( void *) array ) ; return 1; }

A.3

Prolage

Maintenant que votre programme est correct (esprons-le), nous allons voir comment amliorer ses performances. Avec laide du proleur gprof, vous pouvez dterminer les fonctions qui monopolisent le plus de temps dexcution. Cela peut vous aider dterminer les portions du programme optimiser ou rcrire pour quelles sexcutent plus rapidement. Cela peut galement vous aider trouver des erreurs. Par exemple, vous pourriez dtecter quune fonction est appele beaucoup plus souvent que vous ne le supposiez. Dans cette sections, nous dcrirons comment utiliser gprof. La rcriture de code pour acclerer son excution ncessite de la crativit et un certain soin dans le choix des algorithmes. Lobtention dinformations de prolage demande de suivre trois tapes : 1. Compiler et lier votre programme de faon activer le prolage ; 2. Excuter votre programme de faon gnrer les donnes de prolage ; 3. Utiliser gprof pour analyser et acher les donnes de prolage. Avant dillustrer ces direntes tapes, nous allons prsenter une programme susamment important pour quil soit possible de le proler.

A.3.1

Une Calculatrice Simplie

Pour illustrer le prolage, nous allons utiliser un programme faisant oce de calculatrice. Pour nous assurer que son excution prend susamment de temps, nous utiliserons des nombres monadiques pour les calculs, chose que nous ne ferions jamais dans un programme rel. Le code de ce programme est prsent la n de ce chapitre. Un nombre monadique (ou unaire) est reprsent par autant de symboles que la valeur quil reprsente. Par exemple, le nombre 1 est reprsent par "x", 2 par "xx" et 3 par "xxx". Au lieu dutiliser des x, notre programme reprsentera un nombre positif en utilisant une liste chane constitue dautant dlments que la valeur du nombre. Le chier number.c contient les fonctions pour crer le nombre 0, ajouter 1 un chire, soustraire 1 dun nombre et ajouter, soustraire et multiplier des nombres. Une autre fonction convertit une chaine reprsentant une nombre dcimal positif en un nombre unaire et enn, une dernire fonction permet de passer dun nombre unaire un int. Laddition est implante en eectuant des additions successives

A.3. PROFILAGE

215

du nombre un tandis que la soustraction utilise des soustractions rptitives du nombre 1. La multiplication utilise une rptition daddtions. Les prdicats even et odd renvoient le nombre unaire 1 si et seulement si leur oprande est paire ou impaire (respectivement) ; sinon, ils renvoient le nombre unaire reprsentant 0. Ces deux prdicats sappellent lun lautre, par exemple, un nombre est pair sil vaut zro ou si ce nombre moins un est impair. La calculatrice accepte des expression postxes1 sur une ligne et ache la valeur de chaque expression par exemple :
% ./calculator Veuillez saisir une expression postfixe : 2 3 + 5 Veuillez saisir une expression postfixe : 2 3 + 4 1

La calculatrice, dnie dans calculator.c, lit chaque expression et stocke les valeurs intermdiaires sur une pile de nombres unaires, dnie dans stack.c. La pile stocke ses nombres unaires dans une liste chane.

A.3.2

Collecter des Informations de Prolage

La premire tape dans le prolage dun programme est de marquer son excutable de faon ce quil collecte des informations de prolage. Pour cela, utilisez loption de compilation -pg lors de la compilation et de ldition de liens. Par exemple :
% % % % gcc gcc gcc gcc -pg -pg -pg -pg -c -o calculator.o calculator.c -c -o stack.o stack.c -c -o number.o number.c calculator.o stack.o number.o -o calculator

Cette squence de commandes active la collecte dinformations sur les appels de fonction et lhorodatage. Pour collecter des informations dutilisation ligne par ligne, utilisez loption de dbogage -g. Pour compter le nombre dexcutions de blocs de base, comme le nombre ditrations des boubles do, utilisez -a. La seconde tape est lexcution du programme. Pendant son excution, les donnes sont collectes dans un chier nomm gmon.out, uniquement pour les portions de code qui ont t traverses. Vous devez varier les entres du programme ou les commandes pour excuter les sections du code que vous souhaitez proler. Le programme doit se terminer normalement pour que le chier de donnes de prolage soient crites correctement.

A.3.3

Achage des Donnes de Prolage

partir du nom dun excutable, gprof analyse le chier gmon.out pour acher des informations sur le temps pass dans chaque fonction. Par exemple, examinons les donnes de prolage "brutes" pour le calcul de 1787 x 13 - 1918 en utilisant notre programme, elles sont fournies par la commande grpof ./calculator :
1 Dans la notation postxe, un oprateur binaire est plac aprs ses oprandes plutt quentre elles. Ainsi, pour multiplier 6 par 8, vous utiliseriez 6 8 x. Pour multiplier 6 et 8 puis ajouter 5 au rsultat, vous utiliseriez 6 8 x 5 +.

216

ANNEXE A. AUTRES OUTILS DE DVELOPPEMENT


Flat profile: Each sample counts as 0.01 seconds. % cumulative self self total time seconds seconds calls ms/call ms/call name 26.07 1.76 1.76 20795463 0.00 0.00 decrement_number 24.44 3.41 1.65 1787 0.92 1.72 add 19.85 4.75 1.34 62413059 0.00 0.00 zerop 15.11 5.77 1.02 1792 0.57 2.05 destroy_number 14.37 6.74 0.97 20795463 0.00 0.00 add_one 0.15 6.75 0.01 1788 0.01 0.01 copy_number 0.00 6.75 0.00 1792 0.00 0.00 make_zero 0.00 6.75 0.00 11 0.00 0.00 empty_stack

Le parcours de la fonction decrement_number et des sous fonctions quelle appelle occupe 26,07% du temps dexcution total du programme. Elle a t appel 20795463 fois. Chaque excution a ncessit 0,0 seconde autrement dit, un temps trop faible pour tre mesur. La fonction add a t invoque 1787 fois, certainement pour calculer le produit. Chaque appel a demand 0.92 secondes. La fonction copy_number a t invoque 1788 fois, tandis que son excution na ncessit que 0,15% du temps total dexcution. Parfois, les fonctions mcount et profil utilises pour le prolage apparaissent dans les donnes. En plus des donnes brutes de prolage, qui indique le temps pass dans chacune des fonctions, gprof produit des graphes dappel indiquant le temps pass dans chaque fonction et les fonctions quelle appelle dans le cadre dune chane dappels de fonctions :
index % time [1] 100.0 self children called name <spontaneous> 0.00 6.75 main [1] 0.00 6.75 2/2 apply_binary_function [2] 0.00 0.00 1/1792 destroy_number [4] 0.00 0.00 1/1 number_to_unsigned_int [10] 0.00 0.00 3/3 string_to_number [12] 0.00 0.00 3/5 push_stack [16] 0.00 0.00 1/1 create_stack [18] 0.00 0.00 1/11 empty_stack [14] 0.00 0.00 1/5 pop_stack [15] 0.00 0.00 1/1 clear_stack [17] ----------------------------------------------0.00 6.75 2/2 main [1] [2] 100.0 0.00 6.75 2 apply_binary_function [2] 0.00 6.74 1/1 product [3] 0.00 0.01 4/1792 destroy_number [4] 0.00 0.00 1/1 subtract [11] 0.00 0.00 4/11 empty_stack [14] 0.00 0.00 4/5 pop_stack [15] 0.00 0.00 2/5 push_stack [16] ----------------------------------------------0.00 6.74 1/1 apply_binary_function [2] [3] 99.8 0.00 6.74 1 product [3] 1.02 2.65 1787/1792 destroy_number [4] 1.65 1.43 1787/1787 add [5] 0.00 0.00 1788/62413059 zerop [7] 0.00 0.00 1/1792 make_zero [13]

Le premier cadre indique que lexcution de main a ncessit 100% des 6,75 secondes qua dur lexcution du programme. Elle a appel apply_binary_function deux fois, celle-ci ayant

A.3. PROFILAGE

217

t appel deux fois pendant toute la dure dexcution du programme. Lappelant de main tait <spontaneous>, ce qui signie que le proleur na pas t capable de le dterminer. Le premier cadre montre galement que string_to_number a appel push_stack trois fois sur les cinq fois o celle-ci a t appele. Le troisime cadre montre que lexcution de product et des fonctions quil appelle a ncessit 99,8% du temps dexcution total. Elle a t invoque une seule fois depuis apply_binary_function. Le graphe dappel indique le temps total dexcution dune fonction et de ses ls. Si le graphe dappel est un arbre, ce temps est simple calculer, mais les fonction rcursives doivent tre traites dune manire spciale. Par exemple, la fonction even appelle odd qui appelle even son tour. Chaque cycle dappel de ce genre bncie de son propre horodatage et est ach individuellement dans le graphe dappel. Considrons ces donnes venant du prolage de la squence visant dterminer si 1787 x 13 x 3 est pair :
----------------------------------------------0.00 0.02 1/1 main [1] [9] 0.1 0.00 0.02 1 apply_unary_function [9] 0.01 0.00 1/1 even <cycle 1> [13] 0.00 0.00 1/1806 destroy_number [5] 0.00 0.00 1/13 empty_stack [17] 0.00 0.00 1/6 pop_stack [18] 0.00 0.00 1/6 push_stack [19] ----------------------------------------------[10] 0.1 0.01 0.00 1+69693 <cycle 1 as a whole> [10] 0.00 0.00 34847 even <cycle 1> [13] ----------------------------------------------34847 even <cycle 1> [13] [11] 0.1 0.01 0.00 34847 odd <cycle 1> [11] 0.00 0.00 34847/186997954 zerop [7] 0.00 0.00 1/1806 make_zero [16] 34846 even <cycle 1> [13]

La valeur 1+69693 dans le cadre [10] indique que le cycle 1 a t appel une fois, tandis quau sein de ce cycle il y a eu 69693 appels de fonction. Le cycle a appel la fonction even. Lentre suivante montre que la fonction odd a t appele 34847 par even. Dans cette section, nous avons brivement prsent une partie des fonctionalits de gprof. Les pages info donnent des informations sur dautres fonctionalits utiles : Loption -s ache la somme des rsultats pour plusieurs excutions conscutives ; Loption -c permet didentier les ls qui auraient pu tre appels mais ne lont pas t ; Loption -l pour acher des informations de prolage ligne par ligne. Loption -A pour acher le code source annot avec les pourcentages de temps dexcution. Les pages info donnent galement plus dinformations sur la faon dinterprter les rsultats de lanalyse.

A.3.4

Comment gprof Collecte les Donnes

Lors du prolage dun excutable, chaque fois quune fonction est appele, le compteur qui y est associ est incrment. De plus, gprof interrompt rgulirement lexcution pour dterminer la fonction en cours. Ces exemples montrent comment le temps dexcution est dtermin. Comme les interruptions dhorloge de Linux surviennent toutes les 0,01 secondes,

218

ANNEXE A. AUTRES OUTILS DE DVELOPPEMENT

larrt de lexcution ne peut avoir lieu quau maximum toutes les 0,01 secondes. Ainsi, le prolage de programmes sexcutant trs rapidement ou appelant peu souvent des fonctions qui sexcutent rapidement pourrait tre imprcis. Pour viter cela, excutez le programme plus longtemps ou additionnez les donnes de prolage de plusieurs excutions. Reportez-vous la documentation concernant loption -s dans les pages info de groff pour plus dinformations.

A.3.5

Code Source de la Calculatrice

Le Listing A.3 prsente un programme qui calcule la valeur dexpressions postxes. Listing A.3 (calculator.c) Programme Principal de la calculatrice
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47

/* Effectue des calculs en utilisant des nombres unaires . */ /* Saisissez des expressions utilisant la notation postfixe sur une ligne , par exemple : 602 7 5 - 3 * + Les nombres positifs sont saisis en utilisant une notation dcimal . Les oprateurs "+" , " -" et "*" sont accepts . Les oprateurs unaires " even " et " odd " renvoient le nombre 1 si leur oprande est pair ou impair , re sp ec tiv em ent . Les diffrents lments doivent tre spars par des espaces . Les nombres ngatifs ne sont pas pris en charge . */ # include < stdio .h > # include < stdlib .h > # include < string .h > # include < ctype .h > # include " definitions . h " /* Applique la fonction binaire demande aux oprandes obtenues depuis la pile et place le rsultat sur la pile . Renvoie une valeur diffrente de zro si tout se passe bien . */ int a p p l y _ b i n a r y _ f u n c t i o n ( number (* function ) ( number , number ) , Stack * stack ) { number operand1 , operand2 ; if ( empty_stack (* stack ) ) return 0; operand2 = pop_stack ( stack ) ; if ( empty_stack (* stack ) ) return 0; operand1 = pop_stack ( stack ) ; push_stack ( stack , (* function ) ( operand1 , operand2 ) ) ; de st ro y_n um be r ( operand1 ) ; de st ro y_n um be r ( operand2 ) ; return 1; } /* Applique la fonction unaire demande aux oprandes obtenues depuis la pile et place le rsultat sur la pile . Renvoie une valeur diffrente de zro si tout se passe bien . */ int a p p l y _ u n a r y _ f u n c t i o n ( number (* function ) ( number ) , Stack * stack ) { number operand ; if ( empty_stack (* stack ) ) return 0; operand = pop_stack ( stack ) ; push_stack ( stack , (* function ) ( operand ) ) ; de st ro y_n um be r ( operand ) ; return 1; } int main () {

A.3. PROFILAGE
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85

219

char command_line [1000]; char * c o m m a n d _ t o _ p a r s e ; char * token ; Stack number_stack = create_stack () ; while (1) { printf ( " Veuillez saisir une opration postfixe :\ n " ) ; c o m m a n d _ t o _ p a r s e = fgets ( command_line , sizeof ( command_line ) , stdin ) ; if ( c o m m a n d _ t o _ p a r s e == NULL ) return 0; token = strtok ( command_to_parse , " \ t \ n " ) ; c o m m a n d _ t o _ p a r s e = 0; while ( token != 0) { if ( isdigit ( token [0]) ) push_stack (& number_stack , s t r i n g _ t o _ n u m b e r ( token ) ) ; else if ((( strcmp ( token , " + " ) == 0) && ! a p p l y _ b i n a r y _ f u n c t i o n (& add , & number_stack ) ) || (( strcmp ( token , " -" ) == 0) && ! a p p l y _ b i n a r y _ f u n c t i o n (& subtract , & number_stack ) ) || (( strcmp ( token , " * " ) == 0) && ! a p p l y _ b i n a r y _ f u n c t i o n (& product , & number_stack ) ) || (( strcmp ( token , " even " ) == 0) && ! a p p l y _ u n a r y _ f u n c t i o n (& even , & number_stack ) ) || (( strcmp ( token , " odd " ) == 0) && ! a p p l y _ u n a r y _ f u n c t i o n (& odd , & number_stack ) ) ) return 1; token = strtok ( command_to_parse , " \ t \ n " ) ; } if ( empty_stack ( number_stack ) ) return 1; else { number answer = pop_stack (& number_stack ) ; printf ( " % u \ n " , n u m b e r _ t o _ u n s i g n e d _ i n t ( answer ) ) ; de st ro y_n um ber ( answer ) ; clear_stack (& number_stack ) ; } } return 0; }

Les fonctions du Listing A.4 implante les nombres unaires en utilisant des listes chanes vides. Listing A.4 (number.c) Implantation dun Nombre Unaire
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

/* Oprations sur les nombres unaires . */ # include < assert .h > # include < stdlib .h > # include < limits .h > # include " definitions . h " /* Cre un nombre reprsentant zro . */ number make_zero () { return 0; } /* Renvoie une valeur diffrente de zro si le nombre reprsente un zro . int zerop ( number n ) { return n == 0; } /* Soustrait 1 un nombre positif . */ number d e c r e m e n t _ n u m b e r ( number n )

*/

220
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79

ANNEXE A. AUTRES OUTILS DE DVELOPPEMENT


{ number answer ; assert (! zerop ( n ) ) ; answer = n - > one_less_ ; free ( n ) ; return answer ; } /* Ajoute 1 un nombre . */ number add_one ( number n ) { number answer = malloc ( sizeof ( struct L i n k e d L i s t N u m b e r ) ) ; answer - > one_less_ = n ; return answer ; } /* Dtruit un nombre . */ void de st ro y_n um ber ( number n ) { while (! zerop ( n ) ) n = decrement_number (n); } /* Copie un nombre . Cette fonction n est ncessaire qu cause de l allocation mmoire . */ number copy_number ( number n ) { number answer = make_zero () ; while (! zerop ( n ) ) { answer = add_one ( answer ) ; n = n - > one_less_ ; } return answer ; } /* Additionne deux nombres . */ number add ( number n1 , number n2 ) { number answer = copy_number ( n2 ) ; number addend = n1 ; while (! zerop ( addend ) ) { answer = add_one ( answer ) ; addend = addend - > one_less_ ; } return answer ; } /* Soustrait un nombre d un autre . */ number subtract ( number n1 , number n2 ) { number answer = copy_number ( n1 ) ; number subtrahend = n2 ; while (! zerop ( subtrahend ) ) { assert (! zerop ( answer ) ) ; answer = d e c r e m e n t _ n u m b e r ( answer ) ; subtrahend = subtrahend - > one_less_ ; } return answer ; } /* Renvoie le produit de deux nombres . */ number product ( number n1 , number n2 ) { number answer = make_zero () ; number multiplicand = n1 ; while (! zerop ( multiplicand ) ) { number answer2 = add ( answer , n2 ) ; de st ro y_n um ber ( answer ) ;

A.3. PROFILAGE
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123

221

answer = answer2 ; multiplicand = multiplicand - > one_less_ ; } return answer ; } /* Renvoie une valeur diffrente de zro si un nombre est pair . */ number even ( number n ) { if ( zerop ( n ) ) return add_one ( make_zero () ) ; else return odd (n - > one_less_ ) ; } /* Renvoie une valeur diffrente de zro si un nombre est impair . */ number odd ( number n ) { if ( zerop ( n ) ) return make_zero () ; else return even (n - > one_less_ ) ; } /* Convertit une chane reprsentant un entier dcimal en un " number ". */ number s t r i n g _ t o _ n u m b e r ( char * char_number ) { number answer = make_zero () ; int num = strtoul ( char_number , ( char **) 0 , 0) ; while ( num != 0) { answer = add_one ( answer ) ; -- num ; } return answer ; } /* Convertit un " number " en " unsigned int ". */ u n s i g n e d n u m b e r _ t o _ u n s i g n e d _ i n t ( number n ) { u n s i g n e d answer = 0; while (! zerop ( n ) ) { n = n - > one_less_ ; ++ answer ; } return answer ; }

La fonction du Listing A.5 implante une pile de nombre unaires en utilisant une liste chane. Listing A.5 (stack.c) Pile de Nombres Unaires
1 2 3 4 5 6 7 8 9 10 11 12 13

/* Implante une pile de " number " s . */ # include < assert .h > # include < stdlib .h > # include " definitions . h " /* Cre une pile vide . */ Stack create_stack () { return 0; } /* Renvoie une valeur diffrente de zro si la pile est vide . int empty_stack ( Stack stack ) { return stack == 0;

*/

222
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43

ANNEXE A. AUTRES OUTILS DE DVELOPPEMENT


} /* Supprime le number situ au sommet d une pile non vide . choue si la pile est vide . */ number pop_stack ( Stack * stack ) { number answer ; Stack rest_of_stack ; assert (! empty_stack (* stack ) ) ; answer = (* stack ) -> element_ ; rest_of_stack = (* stack ) -> next_ ; free (* stack ) ; * stack = rest_of_stack ; return answer ; } /* Ajoute un number au dbut de la pile . */ void push_stack ( Stack * stack , number n ) { Stack new_stack = malloc ( sizeof ( struct StackElement ) ) ; new_stack - > element_ = n ; new_stack - > next_ = * stack ; * stack = new_stack ; } /* Supprime tous les lments de la pile . */ void clear_stack ( Stack * stack ) { while (! empty_stack (* stack ) ) { number top = pop_stack ( stack ) ; de st ro y_n um ber ( top ) ; } }

Le Listing A.6 contient les dclaration de la pile et des nombres. Listing A.6 (denitions.h) Fichier dEn-Tte pour number.c et stack.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

# ifndef DEFINITIONS_H # define DEFINITIONS_H 1 /* Implante un number en utilisant une liste chane . */ struct L i n k e d L i s t N u m b e r { struct L i n k e d L i s t N u m b e r * one_less_ ; }; typedef struct L i n k e d L i s t N u m b e r * number ; /* Implante une pile de numbers sous forme de liste chane . Utilise 0 pour reprsenter une pile vide . */ struct StackElement { number element_ ; struct StackElement * next_ ; }; typedef struct StackElement * Stack ; /* Oprations sur les piles de numbers . */ Stack create_stack () ; int empty_stack ( Stack stack ) ; number pop_stack ( Stack * stack ) ; void push_stack ( Stack * stack , number n ) ; void clear_stack ( Stack * stack ) ; /* Operations sur les numbers . */ number make_zero () ; void de st ro y_n um ber ( number n ) ; number add ( number n1 , number n2 ) ;

A.3. PROFILAGE
28 29 30 31 32 33 34

223

number subtract ( number n1 , number n2 ) ; number product ( number n1 , number n2 ) ; number even ( number n ) ; number odd ( number n ) ; number s t r i n g _ t o _ n u m b e r ( char * char_number ) ; u n s i g n e d n u m b e r _ t o _ u n s i g n e d _ i n t ( number n ) ; # endif /* DEFINITIONS_H */

224

ANNEXE A. AUTRES OUTILS DE DVELOPPEMENT

Annexe B

E/S de Bas Niveau


es programmeurs C sous GNU/Linux ont deux jeux de fonctions dentres/sorties leur disposition. La bibliothque C standard fournit des fonctions dE/S : printf, fopen, etc 1 . Le noyau Linux fournit un autre ensemble doprations dE/S qui oprent un niveau infrieur celui des fonctions de la bibliothque C. Comme ce livre est destin des gens connaissant dj le langage C, nous supposerons que vous avez dj rencontr et savez comment utiliser les fonctions dE/S de la bibliothque C. Il y a souvent de bonnes raisons dutiliser les fonctions dE/S de bas niveau de Linux. Elles sont pour la plupart des appels systmes au noyau2 et fournissent un accs direct aux possibilits sous-jacentes oertes par le systme aux applications. En fait, les fonctions dE/S de la bibliothque C standard sont implantes par dessus les appels systmes dE/S de bas niveau de Linux. Lutilisation de cette dernire est gnralement la faon la plus ecace deectuer des oprations dentre/sortie et est parfois galement plus pratique. Tout au long de ce livre, nous supposons que vous tes familier avec les appels dcrits dans cette annexe. Vous tes peut tre dj familiers avec eux car ils sont trs proches de ceux fournis avec dautres systme dexploitation de type unique (ainsi quavec la plateforme Win32). Si vous ntes pas coutumier de ces appels, cependant, continuez votre lecture ; le reste du livre nen sera que plus simple comprendre si vous commencez par prendre connaissance de ce chapitre.

B.1

Lire et crire des Donnes

La premire fonction dE/S que vous avez rencontr lorsque vous avez commenc apprendre le langage C tait certainement printf. Elle formate une chane de texte puis lache sur la sortie standard. La version gnrique, fprintf, peut acher le texte sur un autre ux que la sortie standard. Un us est reprsent par un pointeur FILE*. Vous obtenez un tel pointeur en ouvrant un chier avec fopen. Lorsque vous en avez ni, vous pouvez le fermer avec fclose. En plus de
La bibliothque C++ stndard fournit les ux dE/S (iostreams) qui proposent des fonctionnalits similaires. La bibliothque C standard est galement disponible avec le langage C++. 2 Consultez le Chapitre 8, Appels Systme Linux pour des explications concernant la dirence entre un appel systme et un appel de fonction traditionnelle.
1

225

226

ANNEXE B. E/S DE BAS NIVEAU

fprintf, vous pouvez utiliser dautres fonctions comme fputc, fputs ou fwrite pour crire des donnes dans un ux, ou fscanf, fgetc, fgets ou fread pour lire des donnes.

Avec les oprations dE/S de bas niveau de Linux, vous utilisez descripteur de chier au lieu dun pointeur FILE*. Un descripteur de chier est un entier qui fait rfrence une instance donne dun chier ouvert au sein dun processus. Il peut tre ouvert en lecture, en criture ou en lecture/criture. Un descripteur de chier ne fait pas forcment rfrence un chier ouvert ; il peut reprsenter une connexion vers un composant dun autre systme qui est capable denvoyer ou de recevoir des donnes. Par exemple, une connexion vers un dispositif matriel est reprsente par un descripteur de chier (voir Chapitre 6, Priphriques), tout comme lest un socket ouvert (voir Chapitre 5, Communication Interprocessus, Section 5.5, Sockets) ou lextrmit dun tube (voir Section 5.4, Tubes). Incluez les chiers den-tte <fcntl.h>, <sys/types.h>, <sys/stat.h> et <unistd.h> si vous utilisez lune des fonctions dE/S de bas niveau dcrites ici.

B.1.1

Ouvrir un Fichier

Pour ouvrir un chier et obtenir un descripteur de chier pouvant y accder, utilisez lappel
open. Il prend le chemin du chier ouvrir sous forme dune chane de caractres et des indicateurs spciant comment il doit ltre. Vous pouvez utiliser open pour crer un nouveau

chier ; pour cela, passez un troisime argument dcrivant les droits daccs appliquer au nouveau chier. Si le second argument est O_RDONLY, le chier est ouvert en lecture seul ; un erreur sera signale si vous essayez dy crire. De mme, O_WRONLY ouvre le descripteur de chier en criture seule. Passer O_RDWR cre un descripteur de chier pouvant tre utilis la fois en lecture et en criture. Notez que tous les chiers ne peuvent pas tre ouverts dans les trois modes. Par exemple, les permissions dun chier peuvent interdire un processus de louvrir en lecture ou en criture ; un chier sur un priphrique en lecture seule, comme un lecteur CD-ROM ne peut pas tre ouvert en lecture. Vous pouvez passer des options supplmentaires en utilisant un OU binaire de ces valeurs avec dautres indicateurs. Voici les valeurs les plus courantes : Passez O_TRUNC pour tronquer le chier ouvert, sil existait auparavant. Les donnes crites remplaceront le contenu du chier ; Passez O_APPEND pour ajouter les donnes au contenu dun chier existant. Elles sont crites la n du chier ; Passez O_CREAT pour crer un nouveau chier. Si le nom de chier que vous passez open correspond un chier inexistant, un nouveau chier sera cr, si tant est que le rpertoire le contenant existe et que le processus a les permissions ncessaires pour crer des chiers dans ce rpertoire. Si le chier existe dj, il est ouvert ; Passez O_EXCL et O_CREATE pour forcer la cration dun nouveau chier. Si le chier existe dj, lappel open chouera. Si vous appelez open en lui passant O_CREATE, fournissez un troisime argument indiquand les permissions applicable au nouveau chier. Consultez le Chapitre 10, Scurit, Section 10.3, Permissions du Systme de Fichiers, pour une description des bits de permission et de la faon de les utiliser.

B.1. LIRE ET CRIRE DES DONNES

227

Par exemple, le programme du Listing B.1 cre un nouveau chier avec le nom de chier indiqu sur la ligne de commande. Il utilise lindicateur O_EXCL avec open an de signaler une erreur si le chier existe dj. Le nouveau chier dispose des autorisations en lecture/criture pour lutilisateur et le groupe propritaires et est en lecture seule pour les autres utilisateurs (si votre umask est positionn une valeur dirente de zro, les permissions eectives pourraient tre plus restrictives).

Umasks Lorsque vous crez un nouveau chier avec open, certains des bits de permissions peuvent tre dsacrivs. Cela survient lorsque votre umask est dirent de zro. Lumask dun processus spcie les bits de permissions qui sont masqus lors de nimporte quelle cration de chier. Les permissions eectives sont obtenues en appliquant un ET binaire entre les permissions que vous passez open et le complment un du umask. Pour changer la valeur de votre umask partir dun shell, utilisez la commande umask et indiquez la valeur numrique du masque en notation octale. Pour changer le umask dun processus en cours dexcution, utilisez lappel umask en lui passant la valeur du masque utiliser pour les appels suivants. Par exemple, cette ligne :
umask (S_IRWXO | S_IWGRP);

dans un programme ou linvocation de cette commande : % umask 027 indiquent que les permissions en criture pour le groupe et toutes les permissions pour les autres seront toujours masque pour les crations de chiers.

Listing B.1 (create-le.c) Create a New File


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

# include < fcntl .h > # include < stdio .h > # include < sys / stat .h > # include < sys / types .h > # include < unistd .h > int main ( int argc , char * argv []) { /* Chemin vers le nouveau fichier . */ char * path = argv [1]; /* Permissions du nouveau fichier . */ mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH ; /* Cre le fichier . */ int fd = open ( path , O_WRONLY | O_EXCL | O_CREAT , mode ) ; if ( fd == -1) { /* Une erreur est survenue , affiche un message et quitte . */ perror ( " open " ) ; return 1; } return 0; }

Voici le programme en action :

228
% ./create-file testfile % ls -l testfile -rw-rw-r-1 samuel users 0 Feb 1 22:47 testfile % ./create-file testfile open: Le fichier existe

ANNEXE B. E/S DE BAS NIVEAU

Notez que le chier fait zro octet car le programme ny a crit aucune donne.

B.1.2

Fermer un chier

Lorsque vous en avez ni avec un descripteur de chier, fermez-le avec close. Dans certains cas, comme dans le cas du programme du Listing B.1 il nest pas ncessaire dappeler close explicitement car Linux ferme tous les descripteurs de chiers lorsquun processus se termine (cest--dire la n du programme). Bien sr, une fois que vous avez ferm un descripteur de chier, vous ne pouvez plus lutiliser. La fermeture dun descripteur de chier peut dclencher des actions spciques de la part de Linux, selon la nature du descripteur de chier. Par exemple, lorsque vous fermez un descripteur correspondant un socket rseau, Linux ferme la connexion entre les deux ordinateurs communicant via le socket. Linux limite le nombre de descripteurs de chiers quun processus peut maintenir ouverts en mme temps. Les descripteurs de chiers ouverts utilisent des ressources noyau, il est donc conseill de fermer les descripteurs de chiers ds que vous avez terminer de les utiliser. La limite classique est de 1024 descripteurs par processus. Vous pouvez lajuster avec lappel systme setrlimit ; consultez la Section 8.5, getrlimit et setrlimit: Limites de Ressources, pour plus dinformations.

B.1.3

crire des donnes

crire des donnes dans un chier se fait par le biais de lappel write. Passz lui un descripteur de chier, un pointeur vers un tampon de donnes et le nombre doctets crire. Les donnes crites nont pas besoin dtre une chaine de caractres ; write copie des octets quelconques depuis le tampon vers le descripteur de chier. Le programme du Listing B.2 crit lheure courante la n du chier pass sur la ligne de commande. Si le chier nexiste pas, il est cr. Ce programme utilise galement les fonctions time, localtime et asctime pour obtenir et formater lheure courante ; consultez leurs pages de manuel respectives pour plus dinformations. Listing B.2 (timestamp.c) Ajoute lHeure Courante un Fichier
1 2 3 4 5 6 7 8 9 10 11

# include < fcntl .h > # include < stdio .h > # include < string .h > # include < sys / stat .h > # include < sys / types .h > # include < time .h > # include < unistd .h > /* Renvoie une chaine reprsentant l heure courante . */ char * get_timestamp () { time_t now = time ( NULL ) ;

B.1. LIRE ET CRIRE DES DONNES


12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

229

return asctime ( localtime (& now ) ) ; } int main ( int argc , char * argv []) { /* Fichier auquel ajouter l horodatage . */ char * filename = argv [1]; /* Rcupre l heure courante . */ char * timestamp = get_timestamp () ; /* Ouvre le fichier en criture . S il existe , ajoute les donnes la fin , sinon , un nouveau fichier est cr . */ int fd = open ( filename , O_WRONLY | O_CREAT | O_APPEND , 0666) ; /* Calcule la longueur de la chane d horodatage . */ size_t length = strlen ( timestamp ) ; /* L crit dans le fichier . */ write ( fd , timestamp , length ) ; /* Fini . */ close ( fd ) ; return 0; }

Voici comment fonctionne ce programme :


% ./timestamp tsfile % cat tsfile Thu Feb 1 23:25:20 2001 % ./timestamp tsfile % cat tsfile Thu Feb 1 23:25:20 2001 Thu Feb 1 23:25:47 2001

Notez que la premire fois que nous invoquons timestamp, il cre le chier tsfile, alors que la seconde fois, les donnes sont ajoutes la n. Lappel write renvoie le nombre doctets eectivement crits ou -1 si une erreur survient. Pour certains types de descripteurs de chiers, le nombre doctets crits peut tre infrieur au nombre doctets demands. Dans ce cas, cest vous dappeler write encore une fois pour crire le reste des donnes. La fonction du Listing B.3 montre une faon de le faire. Notez que pour certaines applications, vous pourriez avoir eectuer des contrles supplmentaires avant la reprise de lcriture. Par exemple, si vous utilisez un socket rseau, il faudrait ajouter le code permettant de dtecter si la connexion a t ferme pendant lcriture et, si cest le cas, prendre les mesures adquates. Listing B.3 (write-all.c) crit tout le Contenu dun Tampon
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

/* crit l intgralit des COUNT octets de BUFFER vers le descripteur de fichier FD . Renvoie -1 en cas d erreur ou le nombre d octets crits . */ ssize_t write_all ( int fd , const void * buffer , size_t count ) { size_t left_to_write = count ; while ( left_to_write > 0) { size_t written = write ( fd , buffer , count ) ; if ( written == -1) /* Une erreur est survenue . Termin . */ return -1; else /* Mmorise le nombre d octets restant crire . */ left_to_write -= written ; }

230
16 17 18 19 20

ANNEXE B. E/S DE BAS NIVEAU


/* Nous ne devons pas avoir crit plus de COUNT octets ! */ assert ( left_to_write == 0) ; /* Le nombre d octets crits est exactement COUNT . */ return count ; }

B.1.4

Lecture de Donnes

Lappel permettant de lire des donnes est read. Tout comme write, il prend en arguments un descripteur de chier, un pointeur vers un tampon et un nombre doctets. Ce dernier indique le nombre doctets lire partir du descripteur. Lappel read renvoie -1 en cas derreur ou le nombre doctets eectivement lus. Il peut tre infrieur au nombre doctets demand, par exemple, sil ne reste pas susamment doctets lire dans le chier.

Lire des Fichiers Texte DOS/Windows Une fois que vous aurez lu ce livre, nous sommes convaincus que vous dciderez dcrire tous vos programmes pour GNU/Linux. Cependant, il pourrait vous arriver davoir lire des chiers texte gnrs par des programmes DOS ou Windows. Il est important danticiper une dirence majeure entre les deux plateformes dans la faon de structurer les chiers texte. Dans les chiers texte GNU/Linux, chaque ligne est spare de la suivante par un caractre de nouvelle ligne. Ce dernier est reprsent par la constante de caractre \n dont le code ASCII est 10. Sou Windows, par contre, les lignes sont spares par une squence de deux caractres : un retour chariot (le caractre \r, dont le code ASCII est 13), suivi dun caractre de nouvelle ligne. Certains diteurs de texte GNU/Linux achent un M la n de chaque ligne lorsquils achent le contenu dun chier texte Windows il sagit du caractre de retour chariot. Emacs ache les chiers texte Windows correctement mais les signale en achant (DOS) dans la barre de mode en bas du tampon. Certains diteurs Windows, comme Notepad, achent tout le texte des chiers GNU/Linux sur une seule ligne car ils ne trouvent pas le caractre de retour chariot la n de chaque ligne. Dautres programmes, que ce soit sous GNU/Linux ou Windows, peuvent signaler des erreurs tranges lorsque les chiers qui leur sont fournis en entre ne sont pas au bon format. Si votre programme lit des chiers texte gnrs par des programmes Windows, vous voudrez probablement remplacer la squence \r\n par un seul caractre de nouvelle ligne. De mme, si votre programme crit des chiers texte qui doivent tre lus par des programmes Windows, remplacez le caractre de nouvelle ligne par la squence \r\n. Vous devrez le faire, que vous utilisiez les appels dE/S de bas niveau prsents dans cette annexe ou les fonctions de la bibliothque C standard.

Le Listing B.4 prsente un programme utilisant lappel read. Il ache une image hexadcimale du contenu du chier pass sur la ligne de commande. Chaque ligne ache le dplacement dans le chier et les 16 octets suivants.

B.1. LIRE ET CRIRE DES DONNES Listing B.4 (hexdump.c) Ache lImage Hexadcimale dun Fichier
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32

231

# include < fcntl .h > # include < stdio .h > # include < sys / stat .h > # include < sys / types .h > # include < unistd .h > int main ( int argc , char * argv []) { u n s i g n e d char buffer [16]; size_t offset = 0; size_t bytes_read ; int i ; /* Ouvre le fichier en lecture . */ int fd = open ( argv [1] , O_RDONLY ) ; /* Lit le fichier morceau par morceau , jusqu ce que la lecture soit " trop courte " , c est - - dire que l on lise moins que ce que l on a demand , ce qui indique que la fin du fichier est atteinte . */ do { /* Lis la " ligne " suivante . */ bytes_read = read ( fd , buffer , sizeof ( buffer ) ) ; /* Affiche le dplacement dans le fichier , suivi des octets co rr es pon da nts . */ printf ( " 0 x %06 x : " , offset ) ; for ( i = 0; i < bytes_read ; ++ i ) printf ( " %02 x " , buffer [ i ]) ; printf ( " \ n " ) ; /* Conserve notre position dans le fichier . */ offset += bytes_read ; } while ( bytes_read == sizeof ( buffer ) ) ; /* Termin . */ close ( fd ) ; return 0; }

Voici hexdump en action. Il ache limage de son propre excutable :


% ./hexdump hexdump 0x000000 : 7f 45 4c 0x000010 : 02 00 03 0x000020 : e8 23 00 0x000030 : 1d 00 1a ... 46 00 00 00 01 01 00 06 01 00 00 00 01 00 00 00 00 00 00 00 00 c0 34 34 00 83 00 00 00 04 20 00 00 08 00 00 00 34 06 34 00 00 00 80 00 00 28 04 00 00 00 08

La sortie peut tre dirente chez vous, selon le compilateur utilis pour construire hexdump est les options de compilation.

B.1.5

Se Dplacer dans un Fichier

Un descripteur de chier connait sa position dans le chier. Lorsque vous y crivez ou que vous y lisez, sa position est modie selon le nombre doctets lus ou crits. Parfois, cependant, vous pouvez avoir besoin de vous dplacer dans un chier sans lire ou crire de donnes. Par exemple, vous pouvez vouloir crire au milieu dun chier sans en modier le dbut, ou vous pouvez avoir besoin de retourner au dbut dun chier et de le relire sans avoir le rouvrir. Lappel lseek vous permet de modier votre position dans un chier. Passez lui le descripteur deux chier et deux autres arguments indiquand la nouvelle position.

232

ANNEXE B. E/S DE BAS NIVEAU

Si le troisime argument est SEEK_SET, lseek interprte le second argument comme une position, en octets, depusi le dbut du chier ; Si le troisime argument est SEEK_CUR, lseek interprte le second argument comme un dplacement, positif ou ngatif, depuis la position courante ; Si le troisime argument est SEEK_END, lseek interprte le second argument comme un dplacement partir de la n du chier. Une valeur positive indique une position au-del de la n du chier. Lappel lseek renvoie la nouvelle position, sous forme dun dplacement par rapport au dbut du chier.Le type de ce dplacement est off_t. Si une erreur survient, lseek renvoie -1. Vous ne pouvez pas utiliser lseek avec certains types de descripteurs de chiers comme les sockets. Si vous voulez obtenir votre position dans un chier sans la modier, indiquez un dplacement de 0 par rapport votre position actuelle par exemple :
off_t position = lseek ( file_descriptor , 0 , SEEK_CUR ) ;

Linux autorise lutilisation de lseek pour spcier une position au-del de la n du chier. Normalement, si un descripteur de chier est positionn la n dun chier et que vous y crivez, Linux augmente automatiquement la taille du chier pour faire de la place pour les nouvelles donnes. Si vous indiquez une position au-del de la n du chier puis que vous y crivez, Linux commence par agrandir le chier de la taille du "trou" que vous avez cr en appelant lseek puis crit la n de celui-ci. Ce trou noccupe toutefois pas de place sur le disque ; Linux note simplement sa taille. Si vous essayez de lire le chier, il apparait comme sil tait rempli doctets nuls. En utilisant cette proprit de lseek, il est possible de crer des chier extrmement grands qui noccupent pratiquement aucun espace disque. Le programme lseek-huge du Listing B.5 le fait. Il prend en arguments de ligne de commande un nom de chier et sa taille, en mgaoctets. Le programme ouvre un nouveau chier, se place aprs la n de celui-ci en utilisant lseek puis crit un octet 0 avant de fermer le chier. Listing B.5 (lseek-huge.c) Crer de Gros Fichiers avec lseek
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

# include < fcntl .h > # include < stdlib .h > # include < sys / stat .h > # include < sys / types .h > # include < unistd .h > int main ( int argc , char * argv []) { int zero = 0; const int megabyte = 1024 * 1024; char * filename = argv [1]; size_t length = ( size_t ) atoi ( argv [2]) * megabyte ; /* Ouvre un nouveau fichier . */ int fd = open ( filename , O_WRONLY | O_CREAT | O_EXCL , 0666) ; /* Se place un octet avant la fin dsire du fichier . */ lseek ( fd , length - 1 , SEEK_SET ) ; /* crit un octet nul . */ write ( fd , & zero , 1) ; /* Termin . */ close ( fd ) ; return 0; }

B.2. STAT

233

Voici comment crer un chier dun gigaoctet (1024 Mo) en utilisant lseek-huge. Notez lespace libre avant et aprs lopration.
% df -h . Filesystem Tail. Occ. Disp. %Occ. Mont sur /dev/hda5 2.9G 2.1G 655M 76% / % ./lseek-huge grosfichier 1024 % ls -l grosfichier -rw-r----1 samuel samuel 1073741824 Feb 5 16:29 grosfichier % df -h . Filesystem Tail. Occ. Disp. %Occ. Mont sur /dev/hda5 2.9G 2.1G 655M 76% /

Aucun espace disque signicatif nest utilis en dpit de la taille consquente de grosfichier. Si nous ouvrons grosfichier et que nous y lisons des connes, il apparait comme tant rempli de 1 Go doctets nuls. Par exemple, nous pouvons inspecter son contenu avec le programme hexdump du Listing B.4.
% ./hexdump grosfichier 0x000000 : 00 00 00 00 0x000010 : 00 00 00 00 0x000020 : 00 00 00 00 0x000030 : 00 00 00 00 0x000040 : 00 00 00 00 0x000050 : 00 00 00 00 ... | head -10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

Si vous excutez cette commande vous-mme, vous devrez probabelement la tuer avec Ctrl+C plutt de la regarder achier 230 octets nuls. Notez que ces trous magiques dans les chiers sont une fonctionalit spciale du systme de chier ext2 qui est traditionnelement utilis pour les disques GNU/Linux. Si vous utilisez lseek-huge pour crer un chier sur un autre type de systme de chiers, comme fat ou vfat qui sont utiliss sur certaines partitions DOS et Windows, vous constaterez que le chier occupe eectivement autant despace que sa taille. Linux nautorise pas le positionnement avant le dbut du chier avec lseek.

B.2

stat

En utilisant open et read, vous pouvez extraire le contenu dun chier. Mais quen est-il des autres informations ? Par exemple, la commande ls -l ache des informations comme la taille, la date de dernire modication, les permissions ou le propritaire pour les chiers du rpertoire courant. Lappel stat rcupre ces informations pour un chier. Appelez stat en lui passant le chemin du chier sur lequel vous voulez des informations et un pointeur sur une variable de type struct stat. Si lappel stat se droule correctement, il revoie 0 et renseigne les champs de la structure avec des informations sur le chier ; sinon, il renvoie -1. Voici les champs les plus intressant dune struct stat : st_mode contient les permissions du chier. Ces permissions sont dcrites dans la Section 10.3, Permissions du Systme de Fichiers ;

234

ANNEXE B. E/S DE BAS NIVEAU

En plus des permissions clasiques, le champ st_mode encode le type de chier dans les bits de poids fort. Consultez les explication ci-dessous pour savoir comment le dcoder ; st_uid et st_gid contiennent les identiants de lutilisateur et du groupe auxquels le chier appartient, respectivement. Les identiants de groupe et dutilisateur sont dtaills dans la Section 10.1, Utilisateurs et Groupes ; st_size contient la taille du chier, en octets ; st_atime contient la date de dernier accs au chier (en lecture ou criture) ; st_mtime contient la date de dernire modication du chier. Un certain nombre de macros analysent la valeur du champ st_mode pour dterminer le type de chier sur lequel stat a t invoqu. Une macro vaut vrai si le chier est de ce type : S_ISBLK (mode) priphrique bloc priphrique caractre S_ISCHR (mode) S_ISDIR (mode) rpertoire S_ISFIFO (mode) fo (canal nomm) S_ISLNK (mode) lien symbolique S_ISREG (mode) chier classique S_ISSOCK (mode) socket Le champ st_dev contient les numros de priphrique majeur et mineur du dispositif matriel sur lequel le chier se situe. Les numros de priphriques sont traits dans le Chapitre 6. Le numro majeur de priphrique est dcal gauche de 8 bits, tandis que le numro mineur occupe les 8 bits de poids faible. Le champ st_ino contient le numro dinode du chier. Cela situe le chier sur le systme de chiers. Si vous appelez stat sur un lien symbolique, stat suit le lien et vous renvoie les informations sur le chier sur lequel il pointe, pas sur le lien symbolique. Cela signie que S_ISLNK ne sera jamais vrai pour une valeur renvoye par stat. Utilisez la fonction lstat si vous ne voulez pas suivre les liens symboliques ; cette fonction renvoie les informations sur le lien et non pas sur le chier cible. Si vous appelez lstat sur un chier qui nest pas un lien symbolique, elle a le mme comportement que stat. Appeler stat sur un lien invalide (un lien qui pointe vers un chier inexistant ou inaccessible) provoque une erreur, alors que lappel de lstat sur le mme chier na aucune incidence. Si vous disposez dj dun chier ouvert en lecture/criture, appelez fstat au lieu de stat. Il prend un descripteur de chier en premier argument la place du chemin vers le chier. Le Listing B.6 prsente une fonction qui alloue un tampon susamment long pour recevoir le contenu du chier et charge les donnes lintrieur. Elle utilise fstat pour dterminer la taille du tampon allouer et vrier que le chier est bien un chier classique. Listing B.6 (read-le.c) Charge un Fichier dans un Tampon
1 2 3 4 5 6 7 8 9 10

# include < fcntl .h > # include < stdio .h > # include < sys / stat .h > # include < sys / types .h > # include < unistd .h > /* Charge le contenu de FILENAME dans un tampon nouvellement allou . La taille du tampon est stocke dans * LENGTH . Renvoie le tampon , qui devra tre libr par l appelant . Renvoie NULL si FILENAME ne correspond pas un fichier rgulier . */ char * read_file ( const char * filename , size_t * length )

B.3. CRITURE ET LECTURE VECTORIELLES


11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33

235

{ int fd ; struct stat file_info ; char * buffer ; /* Ouvre le fichier . */ fd = open ( filename , O_RDONLY ) ; /* Rcupre les informations sur le fichier . */ fstat ( fd , & file_info ) ; * length = file_info . st_size ; /* S assure que le fichier est un fichier ordinaire . */ if (! S_ISREG ( file_info . st_mode ) ) { /* Ce n en est pas un , abandonne . */ close ( fd ) ; return NULL ; } /* Alloue un tampon suffisamment grand pour recevoir le contenu du fichier . */ buffer = ( char *) malloc (* length ) ; /* Charge le fichier dans le tampon . */ read ( fd , buffer , * length ) ; /* Termin . */ close ( fd ) ; return buffer ; }

B.3

criture et Lecture Vectorielles

Lappel write prend en arguments un pointeur vers le dbut dun tampon de donnes et la taille de ce tampon. Il crit le contenu dune rgion contige de mmoire vers un descripteur de chier. Cependant, un programme a souvent besoin dcrir plusieurs lments de donnes, chacun se trouvant un endroit dirent. Pour utiliser write, un tel programme devrait soit copier tous les objets dans une rgion contige, ce qui gaspillerait des cycles CPU et de la mmoire, soit eectuer de multiples appels write. Pour certaines applications, appeler write plusieurs fois peut tre inecace ou peu souhaitable. Par exemple, lors de lcriture vers un socket rseau, deux appels write peut provoquer lenvoi de deux paquets sur le rseau, alors que les mmes donnes auraient pu tre envoyes en une fois si un seul appel write avait t possible. Lappel writev vous permet denvoyer le contenu de plusieurs zones mmoire non-contiges vers un descripteur de chier en une seul opration. Cest ce que lon appelle lcriture vectorielle. La contrepartie de lutilisation de writev est que vous devez crer une structure de donnes indiquand le dbut et la taille de chaque rgion mmoire. Cette structure est un tableau dlments struct iovec. Chaque lment correspond un emplacement mmoire crire ; les champs iov_base et iov_len correspondent respectivement ladresse du dbut de la rgion et sa taille. Si vous connaissez lavance le nombre de zones mmoire crire, vous pouvez vous contenter de dclarer un tableau de struct iovec ; si le nombre de rgions peut varier, vous devez allouer le tableau dynamiquement. Appelez writev en lui passant le descripteur de chier vers lequel envoyer les donnes, le tableau de struct iovec et le nombre dlments contenus dans ce tableau. La valeur de retour correspond au nombre doctets crits.

236

ANNEXE B. E/S DE BAS NIVEAU

Le programme du Listing B.7 crit ses arguments de ligne de commande dans un chier en utilisant un unique appel writev. Le premier argument est le nom du chier dans lequel crire les arguments qui suivent. Le programme alloue un tableau de struct iovec qui fait le double du nombre darguments crire pour chaque argument, le texte de largument proprement dit est crit, suivit dun caractre de nouvelle ligne. Comme nous ne savons pas lavance le nombre darguments, le tableau est cr en utilisant malloc. Listing B.7 (write-args.c) crit la Liste dArguments dans un Fichier avec writev
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51

# include < fcntl .h > # include < stdlib .h > # include < sys / stat .h > # include < sys / types .h > # include < sys / uio .h > # include < unistd .h > int main ( int argc , char * argv []) { int fd ; struct iovec * vec ; struct iovec * vec_next ; int i ; /* Nous aurons besoin d un " tampon " contenant un caractre de nouvelle ligne . Nous utilisons une variable char normale pour cela . */ char newline = \ n ; /* Le premier argument est le nom du fichier de sortie . */ char * filename = argv [1]; /* Ignore les deux premiers lments de la liste d arguments . L lment l indice 0 est le nom du programme et celui l indice 1 est le nom du fichier de sortie . */ argc -= 2; argv += 2; /* Alloue un tableau d lments iovec . Nous aurons besoin de deux lements pour chaque argument , un pour le texte proprement dit et un pour la nouvelle ligne . */ vec = ( struct iovec *) malloc (2 * argc * sizeof ( struct iovec ) ) ; /* Boucle sur la liste d arguments afin de construire les lments iovec . */ vec_next = vec ; for ( i = 0; i < argc ; ++ i ) { /* Le premier lment est le texte de l argument . */ vec_next - > iov_base = argv [ i ]; vec_next - > iov_len = strlen ( argv [ i ]) ; ++ vec_next ; /* Le second lement est un caractre de nouvelle ligne . Il est possible de faire pointer plusieurs lments du tableau de struct iovec vers la mme rgion mmoire . */ vec_next - > iov_base = & newline ; vec_next - > iov_len = 1; ++ vec_next ; } /* crit les arguments dans le fichier . */ fd = open ( filename , O_WRONLY | O_CREAT ) ; writev ( fd , vec , 2 * argc ) ; close ( fd ) ; free ( vec ) ; return 0; }

B.4. LIEN AVEC LES FUNCTIONS DE/S STANDARDS DU C Voici un exemple dexcution de write-args.
% ./write-args fichiersortie "premier arg" "deuxime arg" "troisme arg" % cat outputfile premier arg deuxime arg troisime arg

237

Linux propose une fonction quivalente pour la lecture, readv qui permet de charger des donnes dans des zones mmoire non-contiges en une seule fois. Comme pour writev, un tableau dlments struct iovec indique les zones mmoire dans lesquelles charger les donnes partir du descripteur de chier.

B.4

Lien avec les Functions dE/S Standards du C

Nous avons voqu le fait que les fonctions dE/S standards du C sont implmentes comme une surcouche de ces fonctions dE/S de bas niveau. Parfois, cependant, il peut tre pratique dutiliser les fonctions de la bibliothque standard avec des descripteurs de chiers ou les fonctions de bas niveau sur un ux FILE*. GNU/Linux autorise ces deux pratiques. Si vous avez ouvert un chier en utilisant fopen, vous pouvez obtenir le descripteur de chier sous-jacent par le biais de la fonction fileno. Elle prend un paramtre FILE* et renvoie le descripteur de chier. Par exemple, pour ouvrir un chier avec lappel standard fopen mais y crire avec writev, vous pourriez utiliser une squencence telle que :
FILE * flux = fopen ( nomfichier , " w " ) ; int descripteur = fileno ( flux ) ; writev ( descripteur , tableau , t ai lle _t ab lea u ) ;

Notez que flux et descripteur correspondent tous deux au mme chier. Si vous appelez fclose comme ceci, vous ne pourrez plus crire dans descripteur :
fclose ( flux ) ;

De mme, si vous appelez close, vous ne pourrez plus crire dans flux :
close ( descripteur ) ;

Pour eectuer lopration inverse, obtenir un ux partir dun descripteur, utilisez la fonction fdopen. Elle produit un pointeur FILE* correspondant un descripteur de chier. La fonction fdopen prend en paramtres un descripteur de chier et une chane correspondant au mode dans lequel le ux doit tre ouvert. La syntaxe de largument de mode est la mme que pour le second argument de fopen et il doit tre compatbile avec le descripteur de chier. Par exemple, passez la chaine de mode r pour un descripteur de chier en lecture ou w pour un descripteur de chier en criture. Comme pour fileno, le ux et le descripteur de chier font rfrence au mme chier, ainsi, si vous en fermez lun des deux, vous ne pouvez plus utiliser lautre.

B.5

Autres Oprations sur les Fichiers

Un petit nombre doprations supplmentaires sur les chiers et rpertoires peut savrer utile :

238

ANNEXE B. E/S DE BAS NIVEAU getcwd renvoie le rpertoire de travail courant. Il prend deux argument, un tampon de char et sa longueur. Il copie le chemin du rpertoire de travail courant dans le tampon ; chdir change le rpertoire de travail courant pour quil corresponde au chemin pass en paramtre ; mkdir cre un nouveau rpertoire. Son premier argument est le chemin de celui-ci, le second les permissions y appliquer. Linterprtation des permissions est la mme que celle du troisime argument de open, elles sont aectes par lumask du processus ; rmdir supprime le rpertoire dont le chemin est pass en paramtre ; unlink supprime le chier dont le chemin est pass en paramtre. Cet appel peut galement tre utilis pour supprimer dautres objets du systme de chiers, comme les canaux nomms (rfrez-vous la Section 5.4.5, FIFO) ou les priphriques (voir le Chapitre 6) ; En fait, unlink ne supprime pas forcment le contenu du chier. Comme son nom lindique, il rompt le lien entre le chier et le rpertoire qui le contient. Le chier napparait plus dans le listing du rpertoire mais si un processus dtient un descripteur de chier ouvert sur ce chier, le contenu nest pas eac du disque. Cela narrive que lorsque quaucun processus ne dtient de descripteur de chier ouvert. Ainsi, si un priocessus ouvre un chier pour y crire ou y lire et quun second processus supprime le chier avec unlink et cre un nouveau chier avec le mme nom, le premier processus "voit" lancien contenu du chier et non pas le nouveau ( moins quil ne ferme le chier et le r-ouvre) ; rename renomme ou dplace un chier. Le premier argument correspond au chemin courant vers le chier, le second au nouveau chemin. Si les deux chemins sont dans des rpertoires dirents, rename dplace le chier, si tant est que le nouveau chemin est sur le mme systme de chiers que lancien. Vous pouvez utiliser rename pour dplacer des rpertoires ou dautres objets du systme de chiers.

B.6

Lire le Contenu dun Rpertoire

GNU/Linux dispose de fonctions pour lire le contenu des rpertoires. Bien quelles ne soient pas directement lies aux fonctions de bas niveau dcrites dans cet appendice, nous les prsentons car elles sont souvent utiles. Pour lire le contenu dun rpertoire, les tapes suivantes sont ncessaires : 1. Appelez opendir en lui passant le chemin du rpertoire que vous souhaitez explorer. opendir renvoie un descripteur DIR*, dont vous aurez besoin pour accder au contenu du rpertoire. Si une erreur survient, lappel renvoie NULL ; 2. Appelez readdir en lui passant le descripteur DIR* que vous a renvoy opendir. chaque appel, readdir renvoie un pointeur vers une instance de struct dirent correspondant lentre suivante dans le rpertoire. Lorsque vous atteignez la n du contenu du rpertoire, readdir renvoie NULL. La struct dirent que vous optenez via readdir dispose dun champ d_name qui contient le nom de lentre. 3. Appelez closedir en lui passant le descripteur DIR* la n du parcours. Incluez <sys/types.h> et <dirent.h> si vous utilisez ces fonctions dans votre programme.

B.6. LIRE LE CONTENU DUN RPERTOIRE

239

Notez que si vous avez besoin de trier les entres dans un ordre particulier, cest vous de le faire. Le programme du Listing B.8 ache le contenu dun rpertoire. Celui-ci peut tre spci sur la ligne de commande mais, si ce nest pas le cas, le rpertoire courant est utilis. Pour chaque entre, son type et son chemin est ach. La fonction get_file_type utilise lstat pour dterminer le type dune entre. Listing B.8 (listdir.c) Ache le Contenu dun Rpertoire
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51

# include < assert .h > # include < dirent .h > # include < stdio .h > # include < string .h > # include < sys / stat .h > # include < sys / types .h > # include < unistd .h > /* Renvoie une chaine qui dcrit le type du fichier PATH . */ const char * get_file_type ( const char * path ) { struct stat st ; lstat ( path , & st ) ; if ( S_ISLNK ( st . st_mode ) ) return " lien symbolique " ; else if ( S_ISDIR ( st . st_mode ) ) return " rpertoire " ; else if ( S_ISCHR ( st . st_mode ) ) return " priphrique caractre " ; else if ( S_ISBLK ( st . st_mode ) ) return " priphrique bloc " ; else if ( S_ISFIFO ( st . st_mode ) ) return " fifo " ; else if ( S_ISSOCK ( st . st_mode ) ) return " socket " ; else if ( S_ISREG ( st . st_mode ) ) return " fichier ordinaire " ; else /* Impossible . Toute entre doit tre de l un des type ci - dessus . */ assert (0) ; } int main ( int argc , char * argv []) { char * dir_path ; DIR * dir ; struct dirent * entry ; char entry_path [ PATH_MAX + 1]; size_t path_len ; if ( argc >= 2) /* Utilise le rpertoire spcifi sur la ligne de commande s il y a lieu . */ dir_path = argv [1]; else /* Sinon , utilise le rpertoire courant . */ dir_path = " . " ; /* Copie le chemin du rpertoire dans entry_path . */ strncpy ( entry_path , dir_path , sizeof ( entry_path ) ) ; path_len = strlen ( dir_path ) ; /* Si le rpertoire ne se termine pas par un slash , l ajoute . */ if ( entry_path [ path_len - 1] != / ) { entry_path [ path_len ] = / ; entry_path [ path_len + 1] = \0 ; ++ path_len ;

240
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70

ANNEXE B. E/S DE BAS NIVEAU


} /* Dmarre l affichage du contenu du rpertoire . */ dir = opendir ( dir_path ) ; /* Boucle sur les entres du rpertoire . */ while (( entry = readdir ( dir ) ) != NULL ) { const char * type ; /* Construit le chemin complet en concatnant le chemin du rpertoire et le nom de l entre . */ strncpy ( entry_path + path_len , entry - > d_name , sizeof ( entry_path ) - path_len ) ; /* Dtermine le type de l entre . */ type = get_file_type ( entry_path ) ; /* Affiche le type et l emplacement de l entre . */ printf ( " % -18 s : % s \ n " , type , entry_path ) ; } /* Fini . */ closedir ( dir ) ; return 0; }

Voici les premires lignes aches lors de lachage du contenu de /dev (elles peuvent tre direntes chez vous) :
% ./listdir /dev directory directory socket character device regular file fifo character device ... : : : : : : : /dev/. /dev/.. /dev/log /dev/null /dev/MAKEDEV /dev/initctl /dev/agpgart

Pour vrier les rsultats, vous pouvez utiliser la commande ls sur le mme rpertoire. Passez lindicateur -U pour demander ls de ne pas trier les entres et passez lindicateur -a pour inclure le rpertoire courant (.) et le rpertoire parent (..).
% ls -lUa /dev total 124 drwxr-xr-x 7 drwxr-xr-x 22 srw-rw-rw1 crw-rw-rw1 -rwxr-xr-x 1 prw------1 crw-rw-r-1 ...

root root root root root root root

root 36864 root 4096 root 0 root 1, 3 root 26689 root 0 root 10, 175

Feb Oct Dec May Mar Dec Feb

1 15:14 . 11 16:39 .. 18 01:31 log 5 1998 null 2 2000 MAKEDEV 11 18:37 initctl 3 2000 agpgart

Le premier caractre de chaque ligne ache par ls indique le type de lentre.

Annexe C

Tableau des Signaux Le Tableau C.1 prsente une partie des signaux Linux que vous avez le plus de
chances de rencontrer. Notez que certains signaux peuvent tre interprts de direntes faons selon lendroit o ils surviennent. Les noms des signaux prsents ici sont dnis sous forme de macros prprocesseur. Pour les utiliser dans votre programme utilisez <signal.h>. Les dnitions proprement dites se trouvent dans le chier /usr/include/sys/signum.h, qui est lui-mme inclus par <signal.h>. Pour une liste complte des signaux Linux, accompagns dune courte description et du comportement associ par dfaut leur rception, consultez la page de manuel de signal de la Section 7 par le biais de la commande suivante :
% man 7 signal

241

242

ANNEXE C. TABLEAU DES SIGNAUX

Tab. C.1 Signaux Linux Nom


SIGHUP

SIGINT SIGILL SIGABRT SIGFPE

SIGKILL SIGUSR1 SIGUSR2 SIGSEGV

SIGPIPE SIGALRM

SIGTERM SIGCHLD

SIGXCPU

SIGVTALRM

Description Linux envoie ce signal un processus lorsquil est dconnect dun terminal. La plupart des programmes Linux utilisent SIGHUP pour tout autre chose : indiquer un programme en cours dexcution quil doit recharger ses chiers de conguration. Ce signal est envoy lorsque lutilisateur tente darrter le programme en utilisant Ctrl+C. Reu par un processus lorsquil tente dexcuter une instruction illgale, cela peut indiquer une corruption de la pile du programme. Reu lors dun appel abort. Reu lorsque le processus excute une instruction en virgule ottante invalide. Selon la manire dont le CPU est congur, une opration en virgule ottante peut renvoyer une valeur nonnumrique spciale comme inf (inni) ou NaN (not a number) au lieu de lever un SIGFPE Ce signal termine un processus immdiatement et ne peut pas tre intercept. Rserv lusage par lapplication. Rserv lusage par lapplication. Le programme a eectu un accs invalide la mmoire. Il peut sagir dun accs une adresse invalide dans lespace dadressage du processus ou laccs peut tre interdit par les permissions appliques la mmoire. Librer un "pointeur sauvage" peut provoquer un SIGSEGV. Le programme a tent daccder un ux de donnes invalide, comme une connexion qui a t ferme par lautre protagoniste. Lappel systme alarm programme lenvoi de ce signal. Consultez la Section 8.13, setitimer: Crer des Temporisateurs dans le Chapitre 8, Appels Systme Linux, pour plus dinformations sur setitimer, une version gnrique de alarm. Ce signal demande un processus de se terminer. Il sagit du signal envoy par dfaut par la commande kill. Linux envoie ce signal un processus lorsquun processus ls se termine. Consultez la Section 3.4.4, Librer les Ressources des Fils de Faon Asynchrone du Chapitre 3, Processus. Linux envoie ce signal un processus lorsquil dpasse la limite de temps CPU qui lui a t alloue. Consultez la Section 8.5, getrlimit et setrlimit: Limites de Ressources, du Chapitre 8 pour plus dinformations sur les limites de temps CPU. La fonction setitimer programme lenvoi de ce signal. Consultez la Section 8.13, setitimer: Crer des Temporisateurs.

Annexe D

Ressources en Ligne
ette annexe dresse une liste de ressources prsentes sur Internet permettant den savoir plus sur la programmation sur le systme GNU/Linux.

D.1

Informations Gnrales

http://www.advancedlinuxprogramming.com est le site de ce livre. Vous pourrez y tlcharger lintgrale de ce livre et les sources des programmes, trouver des liens vers dautres ressources et obtenir plus dinformations sur la programmation GNU/Linux. Ces informations sont galement disponibles sur http://www.newriders.com ; http://www.tldp.org hberge le Linux Documentation Project. Ce site regroupe une grande varit de documents, des listes de FAQ, HOWTO et autres concernant les systmes et logiciels GNU/Linux ; http://www.advancedlinuxprogramming-fr.org est le site de la version franaise de ce livre, vous y trouverez un wiki reprenant son contenu amlior par les lecteurs.

D.2

Informations sur les Logiciels GNU/Linux

http://www.gnu.org est le site ociel du projet GNU. partir de ce site, vous pouvez tlcharger une quantit impressionnante dapplications libres sophistiques. On y trouve entre autre la bibliothque C GNU, qui fait partie de tout systme GNU/Linux et une part importante des fonctions prsentes dans ce livre. Le site du Projet GNU fournit galement des informations sur la faon de contribuer au dveloppement du systme GNU/Linux en crivant du code ou de la documentation, en utilisant des logiciels libres et en faisant passer le message des logiciels libres ; http://www.kernel.org est le site principal de distribution du code source du noyau Linux. Pour les questions les plus tordues et les plus techniques sur le fonctionnement de Linux, il sagit dune mine. Consultez galement le rpertoire Documentation pour plus dexplications sur le fonctionnement interne du noyau ; 243

244

ANNEXE D. RESSOURCES EN LIGNE http://www.linuxhq.com distribue galement des sources patch et informations sur le noyau Linux ; http://gcc.gnu.org hbere le projet de la GNU Compiler Collection (GCC). GCC est le principal compilateur utilis sur les systme GNU/Linux et inclut des compilateurs C, C++, lObjective C, Java, Chill, Fortran, etc. ; http://www.gnome.org et http://www.kde.org hbergent les deux environnements de bureau les plus populaires sous Linux, Gnome et KDE. Si vous prvoyez dcrire une application avec une interface graphique, vous devriez commencer par vous familiariser avec lun des deux (ou les deux).

D.3

Autres Sites

http://developer.intel.com fournit des informations sur larchitecture des processeurs Intel, y compris pour les x86 (IA32). Si vous dveloppez pour Linux sur x86 et que vous utilisez des instructions assembleur en ligne, les manuels techniques qui y sont disponibles peuvent vous tre trs utiles ; http://developer.amd.com/ fournit le mme genre dinformation sur les processeurs dAMD et leurs fonctionnalits spciques ; http://freshmeat.net recense des projets open source, gnralement pour GNU/Linux. Ce site est lun des meilleurs endroits pour se tenir au courant des nouvelles version de logiciels GNU/Linux, depuis le systme de base des applications plus obscures et spcialises ; http://www.linuxsecurity.com donne des informations, des techniques et des liens concernant des logiciels lis la scurit sous GNU/Linux. Ce site peut tre intressant pour les utilisateurs, les administrateurs systme et les dveloppeurs.

Annexe E

Open Publication License Version 1.0


Cette traduction de lOpen Publication License nest pas ocielle. Seule la version (en anglais) disponible sur http://www.opencontent.org/openpub/ fait foi. Lauteur ne pourra tre tenu responsable derreurs de traduction.

I.

Conditions Applicables aux Versions Modies ou Non

Les travaux sous Open Publication License peuvent tre reproduits et distribus, entirement ou partiellement, sous quelle que forme que ce soit, physique ou lectronique, la condition que les termes de cette licence soient respects et que cette licence ou une rfrence celle-ci (mentionnant les options xeces par les auteurs et/ou lditeur) soit attache la reproduction. La forme de lincorporation doit tre la suivante : Copyright c <anne> <auteur ou mandataire>. Ce document ne peut tre distribu que dans le respect des termes et conditions dnies par lOpen Poublication License, vX.Y ou ultrieure (la dernire version est disponible sur http://www.opencontent. org/openpub/). La rfrence doit tre immdiatement suivie de la mention des options exerces par les auteurs ou lditeur du document (voir Section VI., Options Possibles). La redistribution de documents sous Open Publication License est autorise. Toute publication sous une forme classique (papier) impose la citation de lditeur et de lauteur original. Les noms de lditeur et des auteurs doivent apparatre sur toutes les couvertures du livre. Sur les couvertures, le nom de lditeur original devrait tre aussi grand que le titre de louvrage et adopter une forme possessive vis vis du titre.

II.

Copyright

Le copyright dun travail sous Open Publication License est dtenu par ses auteurs ou mandataires. 245

246

ANNEXE E. OPEN PUBLICATION LICENSE VERSION 1.0

III.

Porte de cette Licence

Les termes de cette licence sappeliquent tous les travaux sous licence Open Publication sauf mention contraire explicite dans le document. La mise en commun de travaux sous licence Open Publication ou dun document sous licence Open Publication avec des travaux sous une autre licence sur le mme support ne provoque pas lapplication de la licence Open Publication aux autres travaux. Les documents rsultants doivent contenir une note stipulant linclusion de documents sous licence Open Publication et la mention de copyright adquate. Divisibilit . Si une partie de cette licence savre inapplicable en raison des lois en vigueur, le reste de la licence reste en vigueur ; Absence de Garantie . Les travaux sous licence Open Publication sont fournis "en ltat" sans aucune garantie daucune sort, explicite ou implicite, y compris, mais ne se limitant pas , les garanties de commercialisation ou dadaptation un but particulier ou une garantie de non violation de copyright.

IV.

Conditions Applicables aux Travaux Modis

Toute version modie des documents couverts par cette licence, y compris les traduction, les anthologies, les compilations et les reproductions partielles doivent rpondre aux conditions suivantes : 1. La version modie doit tre indique comme telle ; 2. La personne eectuant les modications doit tre identie et les modications dates ; 3. Des remerciements vis vis des auteurs auteurs et de lditeur originaux, sil y a lieu, doivent tre maintenus en accord avec les pratiques habituelles en matire de citations acadmiques ; 4. Lemplacement du document original doit tre clairement identi ; 5. Les noms des auteurs originaux ne doit pas tre utilis pour cautionner le document modi sans leur permission.

V.

Bonnes Pratiques

En plus des contraintes imposes par cette licence, il est demand et fortement recommand aux ditributeurs : 1. Si vous distribuez des travaux sous licence Open Publication sous forme papier ou numrique, prvenez les auteurs de vos intention au moins trente jours avant le gel de la maquette, an de donner aux auteur le temps de fournir des documents jour. Cette notication doit mentionner, sil y a lieu, les modications apportes au document ; 2. Toute modication substancielle (y compris les suppressions) doit tre clairement signale dans le document ou dcrite dans une note attache au document ;

VI.. OPTIONS POSSIBLES

247

3. Enn, bien que cela ne soit pas impos par cette licence, il est de bon ton dorir une copie gratuite de toute version papier ou numrique dun travail sous licence Open Publication ses auteurs.

VI.

Options Possibles

Les auteurs ou lditeur dun document sous licence Open Publication peut choisir dapporter des modications la licence sous forme doptions signales la suite de la rfrence ou de la copie de cette licence. Ces options font partie de la licence et doivent tre incluses avec celle-ci (ou avec ses rfrences) dans les travaux drivs. A. Interdire la distribution de version modies de faon substancielle sans lautorisation explicite des auteurs. Le terme "modications substancielles" est dnie comme tant une modication du contenu smantique du document et exclut les modications ne touchant que le formatage ou les corrections typographiques. Pour cela, ajoutez la mention "La distribution de versions de ce document modies de manire substancielle est interdite sans lautorisation explicite du dtenteur du copyright" la suite de la rfrence la licence ou de sa copie ; B. Interdire toute distribution des travaux, drivs ou non, dans leur intgralit ou non au format papier dans un but commercial est interdit sauf autorisation pralable du dtenteur du copyright. Pour appliquer cette option, ajoutez la mention "La distribution de ce document ou de travaux drivs sy rapportant sous forme papier est interdite sauf autorisation pralable du dtenteur du copyright" la suite de la rfrence la licence ou de sa copie.

VII.

Annexe la Licence Open Publication

(Cette section nest pas considr comme faisant partie de la licence). Les sources de travaux sous licence Open Publication sont disponibles sur la page daccueil dOpen Publication sur http://works.opencontent.org/. Les auteurs voulant proposer leurs propres licences appliques des travaux Open Publication peuvent le faire la condition que les termes ne soient pas plus restrictifs que ceux de la licence Open Publication. Si vous avez des questions concernant la Licence Open Publication, veuillez contacter David Wiley ou la Liste des Auteurs Open Publication par email ladresse opal@opencontent.org. Pour souscrire cette liste, envoyez un email contenant le mot "subscribe" ladresse opal-request@opencontent.org. Pour envoyer un email la liste, envoyez un email opal@opencontent.org ou rpondez simplement un email. Pour rsilier votre abonnement cette liste, envoyez un email contenant le mot "unsubscribe" ladresse opal-request@opencontent.org.

248

ANNEXE E. OPEN PUBLICATION LICENSE VERSION 1.0

Annexe F

Licence Publique Gnrale GNU


La version originale de cette licence est disponible sur http://www.gnu.org/copyleft/gpl. html, cette traduction est disponible sur http://fsffrance.org/gpl/gpl-fr.fr.html. Ceci est une traduction non ocielle de la GNU General Public License en franais. Elle na pas t publie par la Free Software Foundation, et ne dtermine pas les termes de distribution pour les logiciels qui utilisent la GNU GPL, seul le texte anglais original de la GNU GPL dterminent ces termes. Cependant, nous esprons que cette traduction aidera les francophones mieux comprendre la GNU GPL.

Prambule
Les licences de la plupart des logiciels sont conues pour vous enlever toute libert de les partager et de les modier. A contrario, la Licence Publique Gnrale est destine garantir votre libert de partager et de modier les logiciels libres, et assurer que ces logiciels soient libres pour tous leurs utilisateurs. La prsente Licence Publique Gnrale sapplique la plupart des logiciels de la Free Software Foundation, ainsi qu tout autre programme pour lequel ses auteurs sengagent lutiliser. (Certains autres logiciels de la Free Software Foundation sont couverts par la GNU Lesser General Public License la place.) Vous pouvez aussi lappliquer aux programmes qui sont les vtres. Quand nous parlons de logiciels libres, nous parlons de libert, non de prix. Nos licences publiques gnrales sont conues pour vous donner lassurance dtre libres de distribuer des copies des logiciels libres (et de facturer ce service, si vous le souhaitez), de recevoir le code source ou de pouvoir lobtenir si vous le souhaitez, de pouvoir modier les logiciels ou en utiliser des lments dans de nouveaux programmes libres et de savoir que vous pouvez le faire. Pour protger vos droits, il nous est ncessaire dimposer des limitations qui interdisent quiconque de vous refuser ces droits ou de vous demander dy renoncer. Certaines responsabilits vous incombent en raison de ces limitations si vous distribuez des copies de ces logiciels, ou si vous les modiez. Par exemple, si vous distribuez des copies dun tel programme, titre gratuit ou contre une rmunration, vous devez accorder aux destinataires tous les droits dont vous disposez. Vous 249

250

ANNEXE F. LICENCE PUBLIQUE GNRALE GNU

devez vous assurer queux aussi reoivent ou puissent disposer du code source. Et vous devez leur montrer les prsentes conditions an quils aient connaissance de leurs droits. Nous protgeons vos droits en deux tapes : (1) nous sommes titulaires des droits dauteur du logiciel, et (2) nous vous dlivrons cette licence, qui vous donne lautorisation lgale de copier, distribuer et/ou modier le logiciel. En outre, pour la protection de chaque auteur ainsi que la ntre, nous voulons nous assurer que chacun comprenne que ce logiciel libre ne fait lobjet daucune garantie. Si le logiciel est modi par quelquun dautre puis transmis des tiers, nous voulons que les destinataires soient mis au courant que ce quils ont reu nest pas le logiciel dorigine, de sorte que tout problme introduit par dautres ne puisse entacher la rputation de lauteur originel. En dnitive, un programme libre restera la merci des brevets de logiciels. Nous souhaitons viter le risque que les redistributeurs dun programme libre fassent des demandes individuelles de licence de brevet, ceci ayant pour eet de rendre le programme propritaire. Pour viter cela, nous tablissons clairement que toute licence de brevet doit tre concde de faon ce que lusage en soit libre pour tous ou bien quaucune licence ne soit concde. Les termes exacts et les conditions de copie, distribution et modication sont les suivants :

Conditions de copie, distribution et modication de la Licence Publique Gnrale GNU.


0. La prsente Licence sapplique tout programme ou tout autre ouvrage contenant un avis, appos par le titulaire des droits dauteur, stipulant quil peut tre distribu au titre des conditions de la prsente Licence Publique Gnrale. Ci-aprs, le "Programme" dsigne lun quelconque de ces programmes ou ouvrages, et un "ouvrage fond sur le Programme" dsigne soit le Programme, soit un ouvrage qui en drive au titre des lois sur le droit dauteur : en dautres termes, un ouvrage contenant le Programme ou une partie de ce dernier, soit lidentique, soit avec des modications et/ou traduit dans un autre langage. (Ci-aprs, le terme "modication" implique, sans sy rduire, le terme traduction) Chaque concessionaire sera dsign par "vous". Les activits autres que la copie, la distribution et la modication ne sont pas couvertes par la prsente Licence ; elles sont hors de son champ dapplication. Lopration consistant excuter le Programme nest soumise aucune limitation et les sorties du programme ne sont couvertes que si leur contenu constitue un ouvrage fond sur le Programme (indpendamment du fait quil ait t ralis par lexcution du Programme). La validit de ce qui prcde dpend de ce que fait le Programme. 1. Vous pouvez copier et distribuer des copies lidentique du code source du Programme tel que vous lavez reu, sur nimporte quel support, du moment que vous apposiez sur chaque copie, de manire ad hoc et parfaitement visible, lavis de droit dauteur adquat et une exonration de garantie ; que vous gardiez intacts tous les avis faisant rfrence la prsente Licence et labsence de toute garantie ; et que vous fournissiez tout destinataire du Programme autre que vous-mme un exemplaire de la prsente Licence en mme temps que le Programme. Vous pouvez faire payer lacte physique de transmission dune copie, et vous pouvez, votre discrtion, proposer une garantie contre rmunration.

251 2. Vous pouvez modier votre copie ou des copies du Programme ou nimporte quelle partie de celui-ci, crant ainsi un ouvrage fond sur le Programme, et copier et distribuer de telles modications ou ouvrage selon les termes de lArticle 1 ci-dessus, condition de vous conformer galement chacune des obligations suivantes : a) Vous devez munir les chiers modis davis bien visibles stipulants que vous avez modi ces chiers, ainsi que la date de chaque modication ; b) Vous devez prendre les dispositions ncessaires pour que tout ouvrage que vous distribuez ou publiez, et qui, en totalit ou en partie, contient ou est fond sur le Programme - ou une partie quelconque de ce dernier - soit concd comme un tout, titre gratuit, nimporte quel tiers, au titre des conditions de la prsente Licence. c) Si le programme modi lit habituellement des instructions de faon interactive lorsquon lexcute, vous devez, quand il commence son excution pour ladite utilisation interactive de la manire la plus usuelle, faire en sorte quil imprime ou ache une annonce comprenant un avis de droit dauteur ad hoc, et un avis stipulant quil ny a pas de garantie (ou bien indiquant que cest vous qui fournissez la garantie), et que les utilisateurs peuvent redistribuer le programme en respectant les prsentes obligations, et expliquant lutilisateur comment voir une copie de la prsente Licence. (Exception : si le Programme est lui-mme interactif mais nimprime pas habituellement une telle annonce, votre ouvrage fond sur le Programme nest pas oblig dimprimer une annonce). Ces obligations sappliquent louvrage modi pris comme un tout. Si des lments identiables de cet ouvrage ne sont pas fonds sur le Programme et peuvent raisonnablement tre considrs comme des ouvrages indpendants distincts en eux mmes, alors la prsente Licence et ses conditions ne sappliquent pas ces lments lorsque vous les distribuez en tant quouvrages distincts. Mais lorsque vous distribuez ces mmes lments comme partie dun tout, lequel constitue un ouvrage fond sur le Programme, la distribution de ce tout doit tre soumise aux conditions de la prsente Licence, et les autorisations quelle octroie aux autres concessionnaires stendent lensemble de louvrage et par consquent chaque et toute partie indirement de qui la crite. Par consquent, lobjet du prsent article nest pas de revendiquer des droits ou de contester vos droits sur un ouvrage entirement crit par vous ; son objet est plutt dexercer le droit de contrler la distribution douvrages drivs ou douvrages collectifs fonds sur le Programme. De plus, la simple proximit du Programme avec un autre ouvrage qui nest pas fond sur le Programme (ou un ouvrage fond sur le Programme) sur une partition dun espace de stockage ou un support de distribution ne place pas cet autre ouvrage dans le champ dapplication de la prsente Licence. 3. Vous pouvez copier et distribuer le Programme (ou un ouvrage fond sur lui, selon lArticle 2) sous forme de code objet ou dexcutable, selon les termes des Articles 1 et 2 ci-dessus, condition que vous accomplissiez lun des points suivants : a) Laccompagner de lintgralit du code source correspondant, sous une forme lisible par un ordinateur, lequel doit tre distribu au titre des termes des Articles 1 et 2 ci-dessus, sur un support habituellement utilis pour lchange de logiciels ; ou, b) Laccompagner dune proposition crite, valable pendant au moins trois ans, de fournir tout tiers, un tarif qui ne soit pas suprieur ce que vous cote lacte physique de

252

ANNEXE F. LICENCE PUBLIQUE GNRALE GNU

raliser une distribution source, une copie intgrale du code source correspondant sous une forme lisible par un ordinateur, qui sera distribue au titre des termes des Articles 1 et 2 ci-dessus, sur un support habituellement utilis pour lchange de logiciels ; ou, c) Laccompagner des informations reues par vous concernant la proposition de distribution du code source correspondant. (Cette solution nest autorise que dans le cas dune distribution non commerciale et seulement si vous avez reu le programme sous forme de code objet ou dexcutable accompagn dune telle proposition - en conformit avec le sous-Article b ci-dessus.) Le code source dun ouvrage dsigne la forme favorite pour travailler des modications de cet ouvrage. Pour un ouvrage excutable, le code source intgral dsigne la totalit du code source de la totalit des modules quil contient, ainsi que les ventuels chiers de dnition des interfaces qui y sont associs, ainsi que les scripts utiliss pour contrler la compilation et linstallation de lexcutable. Cependant, par exception spciale, le code source distribu nest pas cens inclure quoi que ce soit de normalement distribu (que ce soit sous forme source ou binaire) avec les composants principaux (compilateur, noyau, et autre) du systme dexploitation sur lequel lexcutable tourne, moins que ce composant lui-mme naccompagne lexcutable. Si distribuer un excutable ou un code objet consiste orir un accs permettant leur copie depuis un endroit particulier, alors lore dun accs quivalent pour copier le code source depuis le mme endroit compte comme une distribution du code source - mme si les tiers ne sont pas contraints de copier le source en mme temps que le code objet. 4. Vous ne pouvez copier, modier, concder en sous-licence, ou distribuer le Programme, sauf tel quexpressment prvu par la prsente Licence. Toute tentative de copier, modier, concder en sous-licence, ou distribuer le Programme dune autre manire est rpute non valable, et met immdiatement n vos droits au titre de la prsente Licence. Toutefois, les tiers ayant reu de vous des copies, ou des droits, au titre de la prsente Licence ne verront pas leurs autorisations rsilies aussi longtemps que ledits tiers se conforment pleinement elle. 5. Vous ntes pas oblig daccepter la prsente Licence tant donn que vous ne lavez pas signe. Cependant, rien dautre ne vous accorde lautorisation de modier ou distribuer le Programme ou les ouvrages fonds sur lui. Ces actions sont interdites par la loi si vous nacceptez pas la prsente Licence. En consquence, en modiant ou distribuant le Programme (ou un ouvrage quelconque fond sur le Programme), vous signiez votre acceptation de la prsente Licence en le faisant, et de toutes ses conditions concernant la copie, la distribution ou la modication du Programme ou douvrages fonds sur lui. 6. Chaque fois que vous redistribuez le Programme (ou nimporte quel ouvrage fond sur le Programme), une licence est automatiquement concde au destinataire par le concdant originel de la licence, lautorisant copier, distribuer ou modier le Programme, sous rserve des prsentes conditions. Vous ne pouvez imposer une quelconque limitation supplmentaire lexercice des droits octroys au titre des prsentes par le destinataire. Vous navez pas la responsabilit dimposer le respect de la prsente Licence des tiers. 7. Si, consquement une dcision de justice ou lallgation dune transgression de brevet ou pour toute autre raison (non limite un probleme de brevet), des obligations vous sont imposes (que ce soit par jugement, conciliation ou autre) qui contredisent les conditions de la prsente Licence, elles ne vous excusent pas des conditions de la prsente Licence. Si vous ne pouvez

253 distribuer de manire satisfaire simultanment vos obligations au titre de la prsente Licence et toute autre obligation pertinente, alors il en dcoule que vous ne pouvez pas du tout distribuer le Programme. Par exemple, si une licence de brevet ne permettait pas une redistribution sans redevance du Programme par tous ceux qui reoivent une copie directement ou indirectement par votre intermdiaire, alors la seule faon pour vous de satisfaire la fois la licence du brevet et la prsente Licence serait de vous abstenir totalement de toute distribution du Programme. Si une partie quelconque de cet article est tenue pour nulle ou inopposable dans une circonstance particulire quelconque, lintention est que le reste de larticle sapplique. La totalit de la section sappliquera dans toutes les autres circonstances. Cet article na pas pour but de vous induire transgresser un quelconque brevet ou dautres revendications un droit de proprit ou contester la validit de la moindre de ces revendications ; cet article a pour seul objectif de protger lintgrit du systme de distribution du logiciel libre, qui est mis en oeuvre par la pratique des licenses publiques. De nombreuses personnes ont fait de gnreuses contributions au large spectre de logiciels distribus par ce systme en se ant lapplication cohrente de ce systme ; il appartient chaque auteur/donateur de dcider si il ou elle veut distribuer du logiciel par lintermdiaire dun quelconque autre systme et un concessionaire ne peut imposer ce choix. Cet article a pour but de rendre totalement limpide ce que lon pense tre une consquence du reste de la prsente Licence. 8. Si la distribution et/ou lutilisation du Programme est limite dans certains pays que ce soit par des brevets ou par des interfaces soumises au droit dauteur, le titulaire originel des droits dauteur qui dcide de couvrir le Programme par la prsente Licence peut ajouter une limitation gographique de distribution explicite qui exclue ces pays an que la distribution soit permise seulement dans ou entre les pays qui ne sont pas ainsi exclus. Dans ce cas, la prsente Licence incorpore la limitation comme si elle tait crite dans le corps de la prsente Licence. 9. La Free Software Foundation peut, de temps autre, publier des versions rvises et/ou nouvelles de la Licence Publique Gnrale. De telles nouvelles versions seront similaires la prsente version dans lesprit mais pourront direr dans le dtail pour prendre en compte de nouvelles problmatiques ou inquitudes. Chaque version possde un numro de version la distinguant. Si le Programme prcise le numro de version de la prsente Licence qui sy applique et "une version ultrieure quelconque", vous avez le choix de suivre les conditions de la prsente version ou de toute autre version ultrieure publie par la Free Software Foundation. Si le Programme ne spcie aucun numro de version de la prsente Licence, vous pouvez choisir une version quelconque publie par la Free Software Foundation quelque moment que ce soit. 10. Si vous souhaitez incorporer des parties du Programme dans dautres programmes libres dont les conditions de distribution sont direntes, crivez lauteur pour lui en demander lautorisation. Pour les logiciels dont la Free Software Foundation est titulaire des droits dauteur, crivez la Free Software Foundation ; nous faisons parfois des exceptions dans ce sens. Notre dcision sera guide par le double objectif de prserver le statut libre de tous les drivs de nos logiciels libres et de promouvoir le partage et la rutilisation des logiciels en gnral.

254

ANNEXE F. LICENCE PUBLIQUE GNRALE GNU

ABSENCE DE GARANTIE
11. COMME LA LICENCE DU PROGRAMME EST CONCEDEE A TITRE GRATUIT, AUCUNE GARANTIE NE SAPPLIQUE AU PROGRAMME, DANS LES LIMITES AUTORISEES PAR LA LOI APPLICABLE. SAUF MENTION CONTRAIRE ECRITE, LES TITULAIRES DU DROIT DAUTEUR ET/OU LES AUTRES PARTIES FOURNISSENT LE PROGRAMME "EN LETAT", SANS AUCUNE GARANTIE DE QUELQUE NATURE QUE CE SOIT, EXPRESSE OU IMPLICITE, Y COMPRIS, MAIS SANS Y ETRE LIMITE, LES GARANTIES IMPLICITES DE COMMERCIABILITE ET DE LA CONFORMITE A UNE UTILISATION PARTICULIERE. VOUS ASSUMEZ LA TOTALITE DES RISQUES LIES A LA QUALITE ET AUX PERFORMANCES DU PROGRAMME. SI LE PROGRAMME SE REVELAIT DEFECTUEUX, LE COUT DE LENTRETIEN, DES REPARATIONS OU DES CORRECTIONS NECESSAIRES VOUS INCOMBENT INTEGRALEMENT. 12. EN AUCUN CAS, SAUF LORSQUE LA LOI APPLICABLE OU UNE CONVENTION ECRITE LEXIGE, UN TITULAIRE DE DROIT DAUTEUR QUEL QUIL SOIT, OU TOUTE PARTIE QUI POURRAIT MODIFIER ET/OU REDISTRIBUER LE PROGRAMME COMME PERMIS CI-DESSUS, NE POURRAIT ETRE TENU POUR RESPONSABLE A VOTRE EGARD DES DOMMAGES, INCLUANT LES DOMMAGES GENERIQUES, SPECIFIQUES, SECONDAIRES OU CONSECUTIFS, RESULTANT DE LUTILISATION OU DE LINCAPACITE DUTILISER LE PROGRAMME (Y COMPRIS, MAIS SANS Y ETRE LIMITE, LA PERTE DE DONNEES, OU LE FAIT QUE DES DONNEES SOIENT RENDUES IMPRECISES, OU LES PERTES EPROUVEES PAR VOUS OU PAR DES TIERS, OU LE FAIT QUE LE PROGRAMME ECHOUE A INTEROPERER AVEC UN AUTRE PROGRAMME QUEL QUIL SOIT) MEME SI LE DIT TITULAIRE DU DROIT DAUTEUR OU LE PARTIE CONCERNEE A ETE AVERTI DE LEVENTUALITE DE TELS DOMMAGES. FIN DES CONDITIONS

Comment appliquer ces conditions vos nouveaux programmes


Si vous dveloppez un nouveau programme, et si vous voulez quil soit de la plus grande utilit possible pour le public, le meilleur moyen dy parvenir est den faire un logiciel libre que chacun peut redistribuer et modier au titre des prsentes conditions. Pour ce faire, munissez le programme des avis qui suivent. Le plus sr est de les ajouter au dbut de chaque chier source pour vhiculer le plus ecacement possible labsence de toute garantie ; chaque chier devrait aussi contenir au moins la ligne "copyright" et une indication de lendroit o se trouve lavis complet. [Une ligne donnant le nom du programme et une courte ide de ce quil fait.] Copyright (C) [anne] [nom de lauteur] Ce programme est un logiciel libre ; vous pouvez le redistribuer et/ou le modier au titre des clauses de la Licence Publique Gnrale GNU, telle que publie par la Free Software Foundation ; soit la version 2 de la Licence, ou ( votre discrtion) une version ultrieure quelconque.

255 Ce programme est distribu dans lespoir quil sera utile, mais SANS AUCUNE GARANTIE ; sans mme une garantie implicite de COMMERCIABILITE ou DE CONFORMITE A UNE UTILISATION PARTICULIERE. Voir la Licence Publique Gnrale GNU pour plus de dtails. Vous devriez avoir reu un exemplaire de la Licence Publique Gnrale GNU avec ce programme ; si ce nest pas le cas, crivez la Free Software Foundation Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Ajoutez aussi des informations sur la manire de vous contacter par courrier lectronique et courrier postal. Si le programme est interactif, faites en sorte quil ache un court avis tel que celui-ci lorsquil dmarre en mode interactif : Gnomovision version 69, Copyright (C) anne nom de lauteur Gnomovision nest accompagn dABSOLUMENT AUCUNE GARANTIE ; pour plus de dtails tapez "show w". Ceci est un logiciel libre et vous tes invit le redistribuer en respectant certaines obligations ; pour plus de dtails tapez "show c". Les commandes hypothtiques "show w" et "show c" sont supposes montrer les parties ad hoc de la Licence Publique Gnrale. Bien entendu, les instructions que vous utilisez peuvent porter dautres noms que "show w" et "show c" ; elles peuvent mme tre des clics de souris ou des lments dun menu ou tout ce qui convient votre programme. Vous devriez aussi obtenir de votre employeur (si vous travaillez en tant que dveloppeur) ou de votre cole, si cest le cas, quil (ou elle) signe une "renonciation aux droits dauteur" concernant le programme, si ncessaire. Voici un exemple (changez les noms) : Yoyodyne, Inc., dclare par la prsente renoncer toute prtention sur les droits dauteur du programme "Gnomovision" (qui fait des avances aux compilateurs) crit par James Hacker. [signature de Ty Coon], 1er avril 1989 Ty Coon, Prsident du Vice La prsente Licence Publique Gnrale nautorise pas lincorporation de votre programme dans des programmes propritaires. Si votre programme est une bibliothque de sous-programmes, vous pouvez considrer plus utile dautoriser ldition de liens dapplications propritaires avec la bibliothque. Si cest ce que vous voulez faire, utilisez la GNU Lesser General Public License au lieu de la prsente Licence.