Vous êtes sur la page 1sur 301

Programmation Avance sous Linux

Mark Mitchell

Jeffrey Oldham

Traduction : Sbastien Le Ray

Alex Samuel

Programmation avance sous Linux


PREMIRE DITION : Juin 2001
c 2001 New Riders Publishing. Ce document peut tre distribu selon les
Copyright
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 chiffres lextrme droite est lanne dimpression
du livre ; le nombre un chiffre lextrme droite est le rang dimpression du livre. Par exemple,
le code 01-1 signifie 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 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 . . . . . . . .

1
.
.
.
.
.

5
5
7
10
12
14

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 . . . . . . . . . . . . . . . . . . . . . . . . . .

17
17
28
34

3 Processus
3.1 Introduction aux processus
3.2 Crer des processus . . . . .
3.3 Signaux . . . . . . . . . . .
3.4 Fin de processus . . . . . .

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

41
41
43
48
50

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 . . . . . . . .

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

55
56
62
64
68
80
82

5 Communication interprocessus
5.1 Mmoire partage . . . . . .
5.2 Smaphores de processus . . .
5.3 Mmoire mappe . . . . . . .
5.4 Tubes . . . . . . . . . . . . .
5.5 Sockets . . . . . . . . . . . .

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

85
. 86
. 90
. 93
. 98
. 104

.
.
.
.

.
.
.
.

.
.
.
.
.

.
.
.
.

.
.
.
.
.

.
.
.
.

.
.
.
.
.

.
.
.
.

.
.
.
.
.

.
.
.
.

.
.
.
.
.
iii

.
.
.
.

.
.
.
.
.

.
.
.
.

.
.
.
.
.

.
.
.
.

.
.
.
.
.

.
.
.
.
.

II

Matriser Linux

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 . . . . . . . . . . . .

115

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

119
120
120
121
123
126
131
132

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

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

135
136
137
145
146
148
151

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 : dfinir des permissions mmoire . . . . . . .
8.10 nanosleep : pause en haute prcision . . . . . . . . . .
8.11 readlink : lecture de liens symboliques . . . . . . . . .
8.12 sendfile : transferts de donnes rapides . . . . . . . . .
8.13 setitimer : crer des temporisateurs . . . . . . . . . . .
8.14 sysinfo : rcupration de statistiques systme . . . . .
8.15 uname . . . . . . . . . . . . . . . . . . . . . . . . . . .

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

153
154
155
156
158
159
161
161
163
164
166
167
168
169
171
171

.
.
.
.
.

173
174
174
175
179
179

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

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 . . . . . . .

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

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 Identifiant de groupe et utilisateur de processus
10.3 Permissions du systme de fichiers . . . . . . .
10.4 Identifiants rels et effectifs . . . . . . . . . . .
10.5 Authentifier les utilisateurs . . . . . . . . . . .
10.6 Autres failles de scurit . . . . . . . . . . . . .

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

181
181
183
184
188
191
194

11 Application GNU/Linux
11.1 Prsentation . . . . . .
11.2 Implantation . . . . .
11.3 Modules . . . . . . . .
11.4 Utilisation du serveur
11.5 Pour finir . . . . . . .

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

201
201
202
218
229
232

III

dIllustration
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

Annexes

233

A Autres outils de dveloppement


237
A.1 Analyse statique de programmes . . . . . . . . . . . . . . . . . . . . . . . . . . . 237
A.2 Dtection des erreurs dallocation dynamique . . . . . . . . . . . . . . . . . . . . 238
A.3 Profilage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246
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 fonctions dE/S standards du C
Autres oprations sur les fichiers . . . . . . .
Lire le contenu dun rpertoire . . . . . . . .

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

C Tableau des signaux

257
257
265
267
269
269
270
273

D Ressources en ligne
275
D.1 Informations gnrales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 275
D.2 Informations sur les logiciels GNU/Linux . . . . . . . . . . . . . . . . . . . . . . 275
D.3 Autres sites . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 276
E Licence Open Publication version 1.0
I.
Conditions applicables aux versions modifies ou non
II. Copyright . . . . . . . . . . . . . . . . . . . . . . . .
III. Porte de cette licence . . . . . . . . . . . . . . . . .
IV. Conditions applicables aux travaux modifis . . . . .
V. Bonnes pratiques . . . . . . . . . . . . . . . . . . . .
VI. Options possibles . . . . . . . . . . . . . . . . . . . .
v

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

277
277
277
278
278
278
279

VII. Annexe la licence Open Publication . . . . . . . . . . . . . . . . . . . . . . . . 279


F Licence Publique Gnrale GNU

281

vi

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.
Jeffrey 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 flux et combinatoires. Il travaille sur GCC et des logiciels de
calcul scientifique.
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. Jeffrey 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

vii

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 afin 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 Sheffield en 1974, en sciences thoriques. Lors de son premier cycle Sheffield, John a prouv de lintrt pour linformatique.
En 1986, il a obtenu un MSc au Cranfield 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..

viii

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 !

ix

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

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
confirms 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 effectue 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 fichiers et caetera.
Le noyau en lui-mme noffre pas de fonctionnalits utiles lutilisateur. Il ne peut mme
pas afficher une invite pour que celui-ci entre des commandes lmentaires. Il noffre aucun
moyen de grer ou dditer les fichiers, 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 fichiers, 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 spcifiquement 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 lAnnexe 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 signifie 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 fiables 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 spcification POSIX. Pour dvelopper des logiciels GNU/Linux, vous devez connatre les particularits du systme, ses limitations, les
possibilits supplmentaires quil offre 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 spcifiques 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 diffrents 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 diffrentes 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 fin de ce livre et lisez lAnnexe B, E/S de bas niveau , avant de
commencer le Chapitre 2, crire des logiciels GNU/Linux de qualit .
Ce livre noffre 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 deffectuer 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 fixe par exemple, printf("Hello, World !\bksl \n").
Les noms des commandes, des fichiers et des rpertoires sont galement en police chasse
fixe par exemple, cd /.
Lorsque nous montrons des interactions avec un shell de commandes, nous utiliserons
% comme invite shell (votre shell est probablement configur pour utiliser une invite
diffrente). 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
affichant Linux.
Le titre de chaque listing inclut un nom de fichier entre parenthses. Si vous saisissez le
listing, sauvegardez-le dans un fichier 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

Pour commencer

crire des logiciels GNU/Linux de qualit

17

Processus

41

Threads

55

Communication interprocessus

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 modifier 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 difficult propre au C++. Nous supposerons galement que vous savez comment effectuer les
oprations lmentaires avec le shell de commande Linux, comme crer des rpertoires et copier
des fichiers. 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 diffrents sont disponibles sous Linux, mais le plus populaire et celui offrant 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

CHAPITRE 1. POUR COMMENCER


propos dEmacs
Emacs est beaucoup plus quun simple diteur. Il sagit dun programme incroyablement puissant,
tel point que chez CodeSourcery, il est appel affectueusement 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 fichier source. Cliquez sur le menu File, slectionnez Open File puis saisissez
le nom du fichier que vous voulez ouvrir dans le minibuffer au bas de lcran1 . Si vous voulez
crer un fichier source C, utilisez un nom de fichier se terminant par .c ou .h. Si vous dsirez
crer un fichier C++, utilisez un nom de fichier se terminant par .cpp, .hpp, .cxx, .hxx, .C ou
.H. Lorsque le fichier est ouvert, vous pouvez taper comme vous le feriez dans un programme de
traitement de texte. Pour sauvegarder le fichier, slectionnez lentre Save Buffer 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 fichier et sortir dEmacs. Pour ouvrir un fichier, saisissez C-x C-f (C-x signifie de maintenir la
touche Control enfonce tout en appuyant sur la touche x). Pour sauvegarder un fichier, 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 efficace 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 offrir le mme type de fonctionnalit. Si vous ouvrez un
fichier 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 effectuer toutes sortes de tches
de formatage compliques. Si vous tes ambitieux, vous pouvez programmer Emacs pour effectuer
littralement tout formatage que vous pourriez imaginer. Des gens ont utilis ces fonctionnalits
pour implmenter des modifications 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 diffrents 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 fichier ~/.emacs et dy insrer
la chane suivante :
( global - font - lock - mode t )

Sauvegardez le fichier, sortez dEmacs et redmarrez-le. Ouvrez un fichier 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 fichier source C++
(reciprocal.cpp) et un fichier source C (main.c) comme dans le Listing 1.1. Ces deux fichiers
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

# 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.
3

CHAPITRE 1. POUR COMMENCER


2
3

# include < stdlib .h >


# include " reciprocal . hpp "

4
5
6
7

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;

9
10
11
12

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


1
2

# include < cassert >


# include " reciprocal . hpp "

3
4
5
6
7
8

double reciprocal ( int i ) {


// i doit tre diffrent de zro
assert ( i != 0) ;
return 1.0/ i ;
}

Il y a galement un fichier dentte appel reciprocal.hpp (voir Listing 1.3).


Listing 1.3 (reciprocal.hpp) Fichier dentte
1
2
3

# ifdef __cplusplus
extern " C " {
# endif

4
5

extern double reciprocal ( int i ) ;

6
7
8
9

# 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 fichier source C, utilisez loption -c.
Donc par exemple, cette commande compile le fichier source main.c :
% gcc -c main.c

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

Loption -c indique g++ de ne compiler le fichier que sous forme dun fichier objet ; sans cela,
g++ tenterait de lier le programme afin de produire un excutable. Une fois cette commande
saisie, vous obtenez un fichier 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 fichiers

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 fichiers dentte situs
un autre endroit, vous aurez besoin de loption -I. Par exemple, supposons que votre projet ait
un rpertoire appel src, pour les fichiers 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 dfinir 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 vrification en dfinissant la macro NDEBUG. Vous pourriez ajouter un #define explicite dans
reciprocal.cpp, mais cela ncessiterait de modifier la source elle-mme. Il est plus simple de
dfinir 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 afin 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 difficile
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

CHAPITRE 1. POUR COMMENCER


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

Loption -o donne le nom du fichier 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 dAuthentification Enfichable (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 prfixe lib et le suffixe .a.


Comme avec les fichiers 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 effectue 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 fichiers 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 spcifier
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 rflexion. 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 fichiers
objets. Les fichiers objets doivent tre recompils chaque fois que le fichier source correspondant
est modifi. Il y a encore une subtilit : une modification de reciprocal.hpp doit entraner la
recompilation des deux fichiers objets car les deux fichiers source incluent ce fichier dentte.
En plus des cibles videntes, il devrait toujours y avoir une cible clean. Cette cible supprime
tous les fichiers objets gnrs afin de pouvoir recommencer sur des bases saines. La rgle pour
cette cible utilise la commande rm pour supprimer les fichiers.
Vous pouvez fournir toutes ces informations make en les plaant dans un fichier 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 fichiers 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 fichiers objet puis les a lis. Si vous
modifiez 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 dfinir 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 fichiers objets et les excutables. Le dbogueur utilise ces informations pour savoir quelle
adresse correspond quelle ligne et dans quel fichier source, afficher 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)

1.4.2

13

Lancer GDB

Vous pouvez dmarrer gdb en saisissant :


% gdb reciprocal

Lorsque GDB dmarre, il affiche 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 file 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 fichier source main.c et quil affiche 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 confirme que le problme vient dun pointeur NULL pass atoi.
Vous pouvez placer un point darrt en utilisant la commande break :

14

CHAPITRE 1. POUR COMMENCER


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

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 fichier source appropri. Il est plus facile de se rendre compte de ce qui se passe en visualisant
le fichier 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.
6

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.

1.5. OBTENIR PLUS DINFORMATIONS

1.5.1

15

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 effectuer 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
configuration 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 effets de
bord et spcifie le fichier 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 afficher 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 profiler) 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 fichiers 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 fichier dentte correspondant pour vrifier 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 fichiers dentte situs dans les rpertoires /usr/include/bits,
/usr/include/asm et /usr/include/linux. Ainsi, le fichier /usr/include/bits/signum.h
dfinit les valeurs numriques des signaux (dcrits dans la Section 3.3, Signaux du Chapitre 3,
Processus ). Ces fichiers dentte constituent une bonne lecture pour les esprits curieux. Ne les
incluez pas directement dans vos programmes, cependant ; utilisez toujours les fichiers 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
C

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 offre 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 flux
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 afficher le contenu du rpertoire racine et les tailles de fichiers 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

# include < stdio .h >

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

2.1.2

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;
}

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 modifient le comportement du programme, alors que les autres arguments fournissent
des entres (par exemple, les noms des fichiers dentre).
1

NdT. flags 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 fichier appel
foo. Aprs les options, il peut y avoir dautres arguments de ligne de commande, typiquement
les fichiers ou les donnes dentre.
Par exemple, la commande ls -s / affiche le contenu du rpertoire racine. Loption -s
modifie le comportement par dfaut de ls en lui demandant dafficher 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 peu ennuyeuse). Cette fonction, getopt_long,
interprte la fois les options courtes et longues. Si vous utilisez cette fonction, incluez le fichier
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 fichiers 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
Affiche laide-mmoire et quitte
output nom fichier
Indique le nom du fichier de sortie
verbose
Affiche 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 spcifi 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 affiche un message derreur et renvoie
le caractre ? (un point dinterrogation). La plupart des programmes sinterrompent dans
ce cas, ventuellement aprs avoir affich 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 fini 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

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 . */

21

22

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;

71
72
73
74
75
76
77
78

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 flexibilit
dans la spcification 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 flux 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 flux 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 fichier tout en
laissant les erreurs safficher sur la console. La fonction fprintf peut tre utilise pour crire sur
stderr, par exemple :
% fprintf ( stderr , " Erreur : ... " ) ;

Ces trois flux sont galement accessibles via les commandes dE/S UNIX de bas niveau (read,
write, etc), par le biais des descripteurs de fichiers. Les descripteurs de fichiers 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 fichier ou un pipe. La syntaxe utiliser diffre 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 fichiers 2 (stderr) doit tre fusionn avec le
descripteur de fichiers 1 (stdout). Notez que 2>&1 doit tre plac aprs une redirection vers un
fichier (premier exemple) mais avant une redirection vers un pipe (second exemple).
Notez que stdout est bufferise. 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 bufferise ; les donnes crites sur stderr sont envoyes directement
vers la console3 .
Cela peut conduire des rsultats quelque peu surprenants. Par exemple, cette boucle
naffiche pas un point toutes les secondes ; au lieu de cela, les points sont placs dans le tampon,
et ils sont affichs par groupe lorsque le tampon est plein.
while (1) {
printf ( " . " ) ;
sleep (1) ;
}

Avec cette boucle, par contre, les points sont affichs 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 fin normale,
tandis quun code diffrent de zro signale quune erreur est survenue. Certains programmes
utilisent des codes de sortie diffrents de zro varis pour distinguer les diffrentes 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 affich 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 fichier spcifi sur la ligne de commande nexiste pas) et renvoie donc un code de
sortie diffrent 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
flux en plus dy envoyer un caractre de nouvelle ligne ; si vous ne voulez pas purger le flux (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 daffichage 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 modifier lenvironnement directement. Pour afficher
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, afin 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 dfinie
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 dfinie 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, affiche tout lenvironnement en bouclant sur le
tableau environ.
Listing 2.3 (print-env.c) Afficher 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 modifiez 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
configuration 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 spcifier 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 dfini 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 spcifier un serveur diffrent :


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

26

2.1.7

CHAPITRE 2. CRIRE DES LOGICIELS GNU/LINUX DE QUALIT

Utilisation de fichiers temporaires

Parfois, un programme a besoin de crer un fichier temporaire, pour stocker un gros volume
de donnes temporairement ou passer des informations un autre programme. Sur les systmes
GNU/Linux, les fichiers temporaires sont stocks dans le rpertoire /tmp. Lors de lutilisation
de fichiers 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 diffrents). Les copies devraient utiliser des noms de
fichiers temporaires diffrents afin dviter les collisions.
Les permissions du fichier devraient tre dfinies de faon viter quun utilisateur non
autoris puisse altrer la manire dont sexcute le programme en modifiant ou remplaant
le fichier temporaire.
Les noms des fichiers temporaires devraient tre gnrs de faon imprvisible de lextrieur ; autrement, un attaquant pourrait exploiter le dlai entre le test dexistence du nom
de fichier et louverture du nouveau fichier 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 fichier, savoir le passer un autre programme ou utiliser les fonctions dE/S
UNIX (open, write, etc.) ou les fonctions de flux dE/S de la bibliothque C (fopen, fprintf,
etc.).
Utilisation de mkstemp
La fonction mkstemp cre un nom de fichier temporaire partir dun modle de nom de fichier,
cre le fichier avec les permissions adquates afin que seul lutilisateur courant puisse y accder,
et ouvre le fichier en lecture/criture. Le modle de nom de fichier est une chane de caractres
se terminant par "XXXXXX" (six X majuscules) ; mkstemp remplace les X par des caractres afin
que le nom de fichier soit unique. La valeur de retour est un descripteur de fichier ; utilisez les
fonctions de la famille de write pour crire dans le fichier temporaire.
Les fichiers temporaires crs par mkstemp ne sont pas effacs automatiquement. Cest vous
de supprimer le fichier lorsque vous nen avez plus besoin (les programmeurs devraient tre
attentifs supprimer les fichiers temporaires ; dans le cas contraire, le systme de fichiers /tmp
pourrait se remplir, rendant le systme inutilisable). Si le fichier 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 fichier temporaire immdiatement. La fonction unlink supprime lentre
de rpertoire correspondant un fichier, mais comme le systme tient jour un dcompte du
nombre de rfrences sur chaque fichier, un fichier nest pas effac tant quil reste un descripteur
de fichier ouvert pour ce fichier. Comme Linux ferme les descripteurs de fichiers quand un
programme se termine, le fichier temporaire sera effac 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 fichier temporaire (afin que la
mmoire puisse tre libre ou rutilise) et sa relecture ultrieure.

2.1. INTERACTION AVEC LENVIRONNEMENT DEXCUTION

27

Listing 2.5 (temp_file.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

# 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 tmpfile
Si vous utilisez les fonctions dE/S de la bibliothque C et navez pas besoin de passer le
fichier temporaire un autre programme, vous pouvez utiliser la fonction tmpfile. Elle cre
et ouvre un fichier temporaire, et renvoie un pointeur de fichier. Le fichier temporaire a dj
t trait par unlink, comme dans lexemple prcdent, afin dtre supprim automatiquement
lorsque le pointeur sur le fichier 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 fichiers temporaires et des
noms de fichiers temporaires, par exemple, mktemp, tmpnam et tempnam. Nutilisez pas ces fonctions,
cependant, car elles souffrent des problmes de fiabilit 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 vrification derreur ou de rcupration sur erreur car cela risquerait dalourdir le code et de
masquer la fonctionnalit prsente. Cependant, lexemple final du Chapitre 11, Application
GNU/Linux dIllustration , 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 difficile 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 affich un message derreur contenant le nom du fichier, 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 vrifier 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 vrification 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 vrifications lexcution
comme celles induites par lutilisation de assert peuvent avoir un cot significatif en termes de
performances. Dans ce cas, vous pouvez compiler votre code en dfinissant la macro NDEBUG,
en utilisant loption -DNDEBUG sur la ligne de commande du compilateur. Lorsque NDEBUG est
dfinie, 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
fichiers 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 deffet de bord. En particulier,
vous ne devriez pas appeler de fonctions au sein dexpressions assert, y affecter des valeurs
des variables ou utiliser des oprateurs de modification 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 diffrente
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 vrification entrane une perte de performances trop
importante et dcider plus tard de recompiler avec la macro NDEBUG dfinie. Cela supprimerait
totalement lappel assert, lexpression ne serait donc jamais value et do_something ne serait
jamais appele. Voici un extrait de code effectuant la mme vrification, 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 affichant
un message derreur obscur, mme en rponse une entre invalide. Vous devriez cependant
toujours vrifier les saisies de lutilisateur et afficher des messages derreurs comprhensibles.
Nutilisez assert que pour des tests internes lors de lexcution.
Voici quelques exemples de bonne utilisation dassert :
Vrification 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

Vrification 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 dfini. 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 effectuer les oprations correspondant
aux sous-tches. On attend dune fonction qutant donn des entres prcises, elle produise une
sortie et des effets 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 effectuer des E/S ou dautres fins, 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 fichiers en mme
temps.
Linux peut bloquer un appel systme lorsquun programme tente deffectuer une opration
non permise. Par exemple, un programme pourrait tenter dcrire dans un fichier 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 fichier invalide un
appel systme ; ou un programme peut tenter douvrir un rpertoire comme un fichier
rgulier ou passer le nom dun fichier 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 diffrente 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 suffisante pour dterminer
si le programme doit continuer normalement, elle ne lest certainement pas pour une rcupration
fiable 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 fichier 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
flux 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 fichier ; si louverture choue, il affiche un message
derreur et quitte le programme. Notez que lappel de open renvoie un descripteur de fichier 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 dafficher 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 fichier 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 fichier core. Cela peut tre utile pour un dbogage
4

En ralit, pour des raisons disolement de threads, errno est implmente comme une macro, mais elle est
utilise comme une variable globale.

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 diffrente de zro car un fichier 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 fichier, des pointeurs sur des fichiers,
des fichiers 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 fichier dans un tampon. La fonction pourrait
passer par les tapes suivantes :
1. Allouer le tampon ;
2. Ouvrir le fichier ;
3. Lire le fichier dans le tampon ;
4. Fermer le fichier ;
5. Retourner le tampon.
Le Listing 2.6 montre une faon dcrire cette fonction.
Si le fichier 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 fichier.
Listing 2.6 (readfile.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

CHAPITRE 2. CRIRE DES LOGICIELS GNU/LINUX DE QUALIT


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

30
31
32
33

Linux libre la mmoire, ferme les fichiers et la plupart des autres ressources automatiquement lorsquun programme se termine, il nest donc pas ncessaire de librer les tampons et de
fermer les fichiers avant dappeler exit. Vous pourriez nanmoins devoir librer manuellement
dautres ressources partages, comme les fichiers 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
difficile 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 effectuer 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 fichiers objets
stocke dans un seul fichier objet (une archive est en gros quivalent un fichier .LIB sous
Windows). Lorsque vous fournissez une archive lditeur de liens, il recherche au sein de cette
archive les fichiers dont il a besoin, les extrait et les lie avec votre programme comme si vous
aviez fourni ces fichiers objets directement.
Vous pouvez crer une archive en utilisant la commande ar. Les fichiers archives utilisent
traditionnellement une extension .a plutt que lextension .o utilise par les fichiers 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 objet du Chapitre 1, Pour commencer .
5

Vous pouvez utiliser dautres options pour supprimer un fichier dune archive ou effectuer dautres oprations
sur larchive. Ces oprations sont rarement utilises mais sont documentes sur la page de manuel de ar.

2.3. CRIRE ET UTILISER DES BIBLIOTHQUES

35

Lorsque lditeur de liens dtecte une archive sur la ligne de commande, il y recherche les
dfinitions des symboles (fonctions ou variables) qui sont rfrencs dans les fichiers objets qui
ont dj t traits mais ne sont pas encore dfinis. Les fichiers objets qui dfinissent ces symboles
sont extraits de larchive et inclus dans lexcutable final. Comme lditeur de liens effectue une
recherche dans larchive lorsquil la rencontre sur la ligne de commande, il est habituel de placer
les archives la fin 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 fichier 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 dfinition 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 fichier
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 fichiers objets. Cependant, il y a
beaucoup de diffrences importantes. La diffrence la plus fondamentale est que lorsquune
bibliothque partage est lie un programme, lexcutable final 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 diffrence importante est quune bibliothque partage nest pas seulement une
collection de fichiers objets, parmi lesquels lditeur de liens choisit ceux qui sont ncessaires
pour satisfaire les rfrences non dfinies. Au lieu de cela, les fichiers objets qui composent la
bibliothque sont combins en un seul fichier objet afin 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 signifie code indpendant de la position. Les fonctions dune bibliothque partage peuvent
tre charges diffrentes adresses dans diffrents 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 fichiers 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 signifie objet
partag (shared object). Comme pour les archives statiques, le nom commence toujours par lib
pour indiquer que le fichier 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 spcifiiez 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

37

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

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 effectue 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 effectues
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 spcifis avant ceux passs via loption -L
lorsquil cre un excutable6 .

2.3.3

Bibliothques standards

Mme si vous ne spcifiez 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 spcifier 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++.
6

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 effet sous GNU/Linux.

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
fichiers 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 (tifftest.c) Utilisation de libtiff
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 fichier sous le nom de tifftest.c. Pour le compiler et le lier libtiff,


spcifiez -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
fichier /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 vrifier 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 spcifier 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 dfinis 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

2.3.5

39

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 bnficieront 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 affecte 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 afin quune mise jour des bibliothques sur le systme
hte naffecte 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 dfinitivement rflchir 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 dfinitive. Et demander vos utilisateurs de positionner LD_LIBRARY_PATH leur impose une
tape de configuration 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 prdfini. 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 dfinit 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 modificateur
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 modifie le nom de la fonction ce qui rsulterait en un nom
diffrent laspect sympathique qui encode des informations supplmentaires sur la fonction.
Un compilateur C ne modifie 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 afin 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> ; vrifiez la page de manuel de chaque fonction pour vous en assurer.

3.1

Introduction aux 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

Identifiants de processus

Chaque processus dun systme Linux est identifi par son identifiant de processus unique,
quelquefois appel pid (process ID). Les identifiants 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. Lidentifiant de processus
parent (parent process ID), ou ppid, est simplement lidentifiant du parent du processus.
Lorsque vous faites rfrence aux identifiants de processus au sein dun programme C ou
C++, utilisez toujours le typedef pid_t, dfini dans <sys/types.h>. Un programme peut obtenir
41

42

CHAPITRE 3. PROCESSUS

lidentifiant du processus au sein duquel il sexcute en utilisant lappel systme getpid() et


lidentifiant de son processus parent avec lappel systme getppid(). Par exemple, le programme
du Listing 3.1 affiche son identifiant de processus ainsi que celui de son parent.
Listing 3.1 (print-pid.c) Afficher lIdentifiant de Processus
1
2

# include < stdio .h >


# include < unistd .h >

3
4
5
6
7
8
9

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 identifiant de processus diffrent
est indiqu chaque fois car chaque invocation cre un nouveau processus. Cependant, si
vous linvoquez toujours depuis le mme shell, lidentifiant du processus parent (cest--dire
lidentifiant de processus du shell) est le mme.

3.1.2

Voir les processus actifs

La commande ps affiche 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 affiches.
Par dfaut, invoquer ps affiche 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 lidentifiant 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 dafficher tous processus en cours dexcution sur le systme. Loption
-o pid,ppid,command indique ps les informations afficher sur chaque processus dans ce
cas, lidentifiant de processus, lidentifiant 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 diffrer des vtres, selon les programmes en cours dexcution sur votre systme :

3.2. CRER DES PROCESSUS

43

Formats de Sortie ps
Avec loption -o de la commande ps, vous indiquez les informations sur les processus que vous
voulez voir safficher sous forme dune liste de valeurs spares par des virgules. Par exemple, ps
-o pid,user,start_time,command affiche lidentifiant 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 prdfinis.

% 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 lidentifiant du processus parent de la commande ps, 21727, est lidentifiant du
processus de bash, le shell depuis lequel jai invoqu ps. Lidentifiant du processus parent de
bash est 21725, lidentifiant 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. Spcifiez
simplement sur la ligne de commande lidentifiant 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 offre
une flexibilit, 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,
1

Vous pouvez galement utiliser la commande kill pour envoyer dautres signaux un processus. Reportezvous la Section 3.4, Fin de processus .

44

CHAPITRE 3. PROCESSUS

system cre un sous-processus dans lequel sexcute le shell Bourne standard (/bin/sh) et passe

la commande ce shell pour quil lexcute. Par exemple, le programme du Listing 3.2 invoque
la commande ls pour afficher 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 spcifique 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
diffrentes de GNU/Linux utilisent diffrentes version de bash. Invoquer un programme avec
les privilges root via la fonction system peut donner des rsultats diffrents 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 effectuant tout cela en une seule fois. Au lieu
de cela, Linux offre une fonction, fork, qui produit un processus fils 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 fils, est cre.
Le processus parent continue dexcuter le programme partir de lendroit o fork a t appel.
Le processus fils excute lui aussi le mme programme partir du mme endroit.
Quest-ce qui diffrencie les deux processus alors ? Tout dabord, le processus fils est un
nouveau processus et dispose donc dun nouvel identifiant de processus, distinct de celui de
lidentifiant de son processus parent. Un moyen pour un programme de savoir sil fait partie
du processus pre ou du processus fils est dappeler la mthode getpid. Cependant, la fonction
fork renvoie des valeurs diffrentes aux processus parent et enfant un processus rentre

3.2. CRER DES PROCESSUS

45

dans le fork et deux en ressortent avec des valeurs de retour diffrentes. La valeur de retour
dans le processus pre est lidentifiant du processus fils. La valeur de retour dans le processus
fils est zro. Comme aucun processus na lidentifiant zro, il est facile pour le programme de
dterminer sil sexcute au sein du processus pre ou du processus fils.
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 fils.
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

46

CHAPITRE 3. PROCESSUS

par le biais des paramtres argc et argv de main. Souvenez-vous que lorsquun programme est
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 deffectuer 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 fils.
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

# 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 ) ;

3.2. CRER DES PROCESSUS


printf ( " Fin du programme principal \ n " ) ;
return 0;

38
39
40

3.2.3

47

Ordonnancement de processus

Linux ordonnance le processus pre indpendamment du processus fils ; 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 finira 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 signifie 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 diffrente de zro, utilisez la
commande nice, en spcifiant 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 afin 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 signifie 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.
2

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.

48

3.3

CHAPITRE 3. PROCESSUS

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 diffrents, chacun ayant une
signification diffrente. 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 dfinis
dans /usr/include/bits/signum.h (vous ne devriez pas inclure ce fichier directement dans vos
programmes, utilisez plutt <signal.h>).
Lorsquun processus reoit un signal, il peut agir de diffrentes 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 spcifie 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 spcifiques.
Par exemple, SIGBUS (erreur de bus), SIGSEGV (erreur de segmentation) et SIGFPE (exception
de virgule flottante) peuvent tre envoys un programme essayant deffectuer une action non
autorise. Laction par dfaut pour ces trois signaux est de terminer le processus et de produire
un ficher 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 dfinis par lutilisateur sont rservs cet effet : SIGUSR1
et SIGUSR2. Le signal SIGHUP est galement parfois utilis dans ce but, habituellement pour
rveiller un programme inactif ou provoquer une relecture du fichier de configuration.
La fonction sigaction peut tre utilise pour paramtrer laction effectuer 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 effectuer 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 diffrence ? 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 deffectuer 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 effectuer 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
vrifie 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 difficile 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 laffectation dune valeur une variable globale peut tre dangereuse car elle peut
en fait tre effectue 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 affectations de valeur des variables de ce type sont effectues en une
seule instruction et ne peuvent donc pas tre interrompues. Sous Linux, sig_atomic_t est un int
ordinaire ; en fait, les affectations 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 fin du
processus. Dautres signaux sont utiliss pour terminer un processus explicitement. Le signal
SIGINT est envoy un processus lorsque lutilisateur tente dy mettre fin 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 fin au processus. En appelant la fonction abort, un processus
senvoie lui-mme le signal SIGABRT ce qui termine le processus et produit un fichier core. Le
signal de terminaison le plus puissant est SIGKILL qui met fin 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
identifiant de processus :
% kill -KILL pid}

Pour envoyer un signal depuis un programme, utilisez la fonction kill. Le premier paramtre
est lidentifiant 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
lidentifiant du processus fils, vous pouvez utiliser la fonction kill pour terminer un processus
fils 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 diffrent
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 affich 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 fichier spcifi sur la ligne de commande nexiste pas) et renvoie donc un code de
sortie diffrent de 0.
% ls /
bin
coda etc

lib

misc nfs proc

3.4. FIN DE PROCESSUS

51

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

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
signification 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 fin du programme principal. Cela est d au fait que le processus fils, 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 fin
dun ou plusieurs processus fils. Pour cela, il est possible dutiliser les appels systmes de la
famille de wait. Ces fonctions vous permettent dattendre la fin dun processus et permettent
au processus parent dobtenir des informations sur la faon dont sest termin son fils. Il y a
quatre appels systme diffrents 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 fils 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 fils 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 fils. Par exemple, la macro WEXITSTATUS
extrait le code de sortie du processus fils.
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 fils, dans lequel sexcute la
commande ls, se termine.
int main ()
{
int child_status ;

52

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

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 flexibles ou
apportent plus dinformations sur le processus fils se terminant. La fonction waitpid peut
tre utilise pour attendre la fin dun processus fils spcifique au lieu dattendre nimporte
quel processus fils. La fonction wait3 renvoie des statistiques sur lutilisation du processeur
par le processus fils se terminant et la fonction wait4 vous permet de spcifier des options
supplmentaires quant au processus dont on attend la fin.

3.4.3

Processus zombies

Si un processus fils se termine alors que son pre appelle la fonction wait, le processus fils
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 fils 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 fils zombies. La fonction wait le fait, il nest donc pas ncessaire de savoir si votre
processus fils est toujours en cours dexcution avant de lattendre. Supposons, par exemple,
quun programme cre un processus fils, fasse un certain nombre dautres oprations puis appelle
wait. Si le processus fils nest pas encore termin ce moment, le processus parent sera bloqu
dans lappel de wait jusqu ce que le processus fils se termine. Si le processus fils 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 fils est extrait, le processus fils est supprim et
lappel de wait se termine immdiatement.
Que se passe-t-il si le pre ne libre pas les ressources de ses fils ? Ils restent dans le systme,
sous la forme de processus zombies. Le programme du Listing 3.6 cre un processus fils qui se
termine immdiatement, puis sinterrompt pendant une minute, sans jamais librer les ressources
du processus fils.

3.4. FIN DE PROCESSUS

53

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

# 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 fichier 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 identifiants 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 affich. Il sagit du processus fils ; notez que lidentifiant de son pre
est celui du processus make-zombie principal. Le processus fils 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 fils, le programme init, qui sexcute toujours avec un identifiant 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 fils uniquement pour appeler exec, il est suffisant dappeler wait
immdiatement dans le processus parent, ce qui le bloquera jusqu ce que le processus fils se
termine. Mais souvent, vous voudrez que le processus pre continue de sexcuter alors quun ou
plusieurs fils sexcutent en parallle. Comment tre sr de librer les ressources occupes par
tous les processus fils qui se sont termins afin 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 fils zombies. Appeler wait de cette faon ne fonctionne pas
de manire optimale car si aucun fils 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 fils termin sil y en a un ou se terminera
sil ny en a pas. La valeur de retour de lappel est lidentifiant du processus fils stant termin
dans le premier cas, zro dans le second.
Une solution plus lgante est dindiquer au processus pre quand un processus fils 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 fils 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 fils est de grer SIGCHLD. Bien
sr, lorsque vous librez les ressources du processus fils, il est important de stocker son statut
de sortie si cette information est ncessaire, car une fois que les ressources du processus fils
ont t libres par wait, cette information nest plus disponible. Le Listing 3.7 montre quoi
ressemble un programme utilisant un gestionnaire pour SIGCHLD afin de librer les ressources de
ses processus fils.
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 fils 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 fine 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 diffrente du programme un
instant donn.
Nous avons vu comment un programme peut crer un processus fils. Celui-ci excute immdiatement le programme de son pre, la mmoire virtuelle, les descripteurs de fichiers, etc. de son
pre tant copis. Le processus fils peut modifier sa mmoire, fermer les descripteurs de fichiers
sans que cela affecte 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 fichiers et autres ressources. Si un thread modifie
la valeur dune variable, par exemple, lautre thread verra la valeur modifie. De mme, si un
thread ferme un descripteur de fichier, les autres threads ne peuvent plus lire ou crire dans ce
fichier. 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 fichier 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 identifiant de thread. Lorsque vous
manipulez les identifiants 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 lidentifiant 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 thread ;
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 affiche x de faon continue sur la sortie des
erreurs. Aprs lappel de pthread_create, le thread principal affiche des o indfiniment 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


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;

15
16
17
18
19
20
21

57

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 dfinir 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 diffrents
threads. Ils excutent alors tous les mmes traitements mais sur des donnes diffrentes.
Le programme du Listing 4.2 est similaire lexemple prcdent. Celui-ci cre deux nouveaux
threads, lun affiche des x et lautre des o. Au lieu de les afficher indfiniment, cependant, chaque
thread affiche 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 configur diffremment 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

CHAPITRE 4. THREADS
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

{
/* 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 fin des deux autres threads. Ce dont
nous avons besoin est une fonction similaire wait qui attende la fin dun thread au lieu de
celle dun processus. Cette fonction est pthread_join, qui prend deux arguments : lidentifiant
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 affichant des o et des x se soient eux-mme
termins, afin 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


/* 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;

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

59

Morale de lhistoire : assurez vous que toute donne que vous passez un thread par rfrence
nest pas libre, mme par un thread diffrent, moins que vous ne soyez sr que le thread
a fini 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 inefficace ; 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

CHAPITRE 4. THREADS
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

4.1.4

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;
}

Plus dinformations sur les identifiants de thread

De temps autre, il peut tre utile pour une portion de code de savoir quel thread lexcute.
La fonction pthread_self renvoie lidentifiant du thread depuis lequel elle a t appele. Cet
identifiant de thread peut tre compar un autre en utilisant la fonction pthread_equal.
Ces fonctions peuvent tre utiles pour dterminer si un identifiant 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 vrifier 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 finement 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 configurer le nouveau thread. Cependant, vous pouvez crer et personnaliser un
objet dattributs de threads pour spcifier 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. Modifiez 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 dfinir 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 deffectuer 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

4.2

CHAPITRE 4. THREADS

Annulation de thread

Dans des circonstances normales, un thread se termine soit en arrivant la fin de la fonction
de thread, soit en appelant la fonction pthread_exit. Cependant, il est possible pour un thread
de demander la fin dun autre thread. Cela sappelle annuler un thread.
Pour annuler un thread, appelez pthread_cancel, en lui passant lidentifiant 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 figure, 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 file
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
naffecte que le thread effectuant 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 effets 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 affecte le thread appelant. La dsactivation de lannulation se fait en
passant PTHREAD_CANCEL_DISABLE comme premier argument ; 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
fin 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 modification 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
20

# 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 ;
/* Fin de la section critique . */

64

CHAPITRE 4. THREADS
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;

21
22
23

Notez quil est important de restaurer lancien tat de lannulation la fin 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 signifie que si un thread modifie une valeur en mmoire (par exemple,
une variable globale), cette modification 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 diffrent 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 afin 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 modifier sa copie sans affecter 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 fins daudit, chaque thread doit avoir un fichier 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 fichier 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 fichier 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 fichier journal et stocke le pointeur de fichier sous cette cl. Plus tard, nimporte lequel
de ces thread peut appeler write_to_thread_log pour crire un message dans le fichier journal
propre au thread. Cette fonction obtient le pointeur de fichier du fichier 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

CHAPITRE 4. THREADS
32

{
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;

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

Remarquez que thread_function na pas besoin de fermer le fichier journal. En effet, lorsque
la cl du fichier a t cre, close_thread_log a t spcifie 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 fichier journal. Cette fonction
prend soin de fermer le fichier journal.
Gestionnaires de Libration de Ressources

Les fonctions de libration de ressources associes aux cls des donnes spcifiques 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 spcifier 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 spcifie 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 simplification, pthread_cleanup_pop prend un indicateur
int en argument ; sil est diffrent 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 diffrent 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 fin 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
offre 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

4.4

# 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 ;
}

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 afin 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 modifier 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 file dattente des tches est reprsente par une liste chane dobjets
struct job.
Aprs que chaque thread a fini une opration, il vrifie la file 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 finissent 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 vrifie galement job_queue, comme elle nest pas NULL, affecte 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 diffrentes, 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
vrifier 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 file de tches est de
nautoriser quun seul thread accder la file. Une fois que le thread commence observer la
file 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.
Limplantation 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 file de tches. Dsormais, celle-ci est protge
par un mutex. Avant daccder la file (que ce soit pour une lecture ou une criture), chaque
thread commence par verrouiller le mutex. Ce nest que lorsque la squence de vrification de la
file 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 dans la section comprise
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 file 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 file 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 file 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 offrent 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 daffil. 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 effectuer autant dappels pthread_mutex_unlock pour que le mutex soit
effectivement dverrouill et quun autre thread puisse y accder.
GNU/Linux dtectera et signalera un double verrouillage sur un mutex vrification 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
dfinissez 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 vrification 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 vrification 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 suffixe np, les mutexes rcursifs et vrification derreurs sont spcifiques
GNU/Linux et ne sont pas portables. Ainsi, il est gnralement dconseill de les utiliser
dans vos programmes (les mutexes vrification derreurs peuvent cependant tre utiles lors du
dbogage).

4.4.4

Vrification 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. Appel sur un mutex dverrouill, pthread_mutex_trylock verrouille le mutex comme le ferait pthread_mutex_lock et renvoie 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 affect. 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 file, 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
file 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 file de tches
se videra et les threads se termineront. Si de nouvelles tches sont mises dans la file 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 file 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 vrification ou la modification 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 diffrentes des smaphores.
Celle que nous dcrivons ici est limplantation 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 de 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 effectuer 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 file dattente de tches, nous pouvons utiliser un smaphore
pour compter le nombre de tches en attente dans la file. Le Listing 4.12 contrle la file avec un
smaphore. La fonction enqueue_job ajoute une nouvelle tche la file 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 diffrente 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

/* 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 ) ;

75

76

CHAPITRE 4. THREADS
69

Avant de prlever une tche en tte de file, chaque thread se mettra en attente sur le
smaphore. Si la valeur de celui-ci est zro, indiquant que la file est vide, le thread sera tout
simplement bloqu jusqu ce que le smaphore devienne positif, indiquant que la tche a t
ajoute la file.
La fonction enqueue_job ajoute une tche la file. Comme thread_function, elle doit verrouiller le mutex de la file avant de la modifier. Aprs avoir ajout la tche la file, 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 infinie, 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 vrifie que lindicateur est actif. Comme plusieurs
threads accdent lindicateur, il est protg par un mutex. Cette implmentation peut tre
correcte mais nest pas efficace. La fonction de thread utilisera du temps processeur, que lindicateur soit actif ou non, vrifier et revrifier 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 spcifier 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 modifier 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 efficace :
La boucle dans thread_function vrifie 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 vrifiera la condition nouveau.
Il y a un problme avec cette faon de faire : il y a une concurrence critique entre la vrification
de la valeur de lindicateur et la validation ou lattente sur la variable de condition. Supposons
que thread_function ait vrifi 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 effectue une action qui pourrait modifier 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 modifi) :
1. Verrouiller le mutex accompagnant la variable de condition.
2. Effectuer laction qui pourrait modifier la condition (dans notre exemple, activer lindicateur).
3. Valider ou effectuer 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 vrifier 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 dfinir 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 deffectuer une opration qui pourrait modifier 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 diffrence 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 effectuer. 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 afin dviter lapparition de telles situations car elles sont assez difficiles 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 diffrents, 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 suffisamment 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 fichiers ou des priphriques. Le problme survient lorsque plusieurs threads
tentent de verrouiller le mme ensemble de ressources dans des ordres diffrents. 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

Limplantation des threads POSIX sous GNU/Linux diffre de celle utilise sur un certain
nombre 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 affichent leurs identifiants
de processus respectifs, puis bouclent indfiniment.

4.5. IMPLMENTATION DES THREADS SOUS GNU/LINUX

81

Listing 4.15 (thread-pid.c) Affiche les Identifiants de Processus des Threads


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 < 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 afficher 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

Notifications 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 affiche lidentifiant 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 fils
excute un programme multithread, le processus pre conservera lidentifiant de processus du
thread principal du programme du processus fils et lutilisera pour envoyer des signaux son
fils. 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 dfini. Utilisez la fonction pthread_kill pour cela. Son premier paramtre
est lidentifiant 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 fils 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 spcifier les ressources partages entre lui et le nouveau processus. clone
ncessite galement que vous spcifiiez 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 difficile. 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 fils, au
contraire, peut excuter un programme diffrent 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 effectue que
lorsque la mmoire est modifie, donc ce cot est minime si le processus fils ne fait que
lire la mmoire.
Les threads devraient tre utiliss pour les programmes qui ont besoin dun paralllisme
finement 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 souffrent de bugs lis au paralllisme.

84

CHAPITRE 4. THREADS

Chapitre 5

Communication interprocessus
L

e Chapitre 3, Processus traitait de la cration de processus et montrait


comment il est possible dobtenir le code de sortie dun processus fils. 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 fils except via les arguments de ligne de commande et les variables
denvironnement, ni aucun moyen pour le processus fils 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 fils pendant son excution, ni nautorise une communication entre les processus en
dehors de la relation pre-fils.
Ce chapitre prsente des moyens de communication interprocessus qui dpassent ces limitations. Nous prsenterons diffrentes faons de communiquer entre pre et fils, 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 fichiers 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 prdfini.
La mmoire mappe est similaire la mmoire partage, except quelle est associe un
fichier.
Les tubes permettent une communication squentiel dun processus lautre.
Les files FIFO sont similaires aux tubes except que des processus sans lien peuvent
communiquer car le tube reoit un nom dans le systme de fichiers.
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 diffrent 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 fichiers 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 fils.

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 modifie la mmoire, tous les autres processus voient la
modification.

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 effectuer 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, afin 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 fini 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: verrouillage de 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 lidentifiant 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 spcifiant
la mme valeur de cl. Malheureusement, dautres processus pourraient avoir choisi la mme
valeur de cl fix, ce qui provoquerait un conflit. 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 effectivement 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 spcifiant une valeur de cl.
IPC_EXCL Cet indicateur, toujours utilis avec IPC_CREAT, provoque lchec de shmget si
la cl de segment spcifie 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 spcifier les permissions est dutiliser les
constantes dfinies 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 spcifient des permissions de lecture et criture
pour le propritaire du segment de mmoire partage et S_IROTH et S_IWOTH spcifient
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 identifiant de segment. Si le segment de mmoire


partage existe dj, les permissions daccs sont vrifies 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 lidentifiant 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 spcifie 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
fils crs par des appels fork hritent des segments partags attachs ; il peuvent les dtacher
sils le souhaitent.
Lorsque vous en avez fini 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 modifier. Le premier paramtre est
lidentifiant 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.
1

Ces bits de permissions sont les mmes que ceux utiliss pour les fichiers. Ils sont dcrits dans la Section 10.3,
Permissions du systme de fichiers .

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, afin dviter de dpasser la limite du nombre total de
segments de mmoire partage dfinie 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 effectuer 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

# 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 ) ;

32

/* Libre le segment de mmoire partage .


shmctl ( segment_id , IPC_RMID , 0) ;

33
34
35

return 0;

36
37

*/

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 dfinir 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 suffisant
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 identifiant densemble de smaphores.
Vous pouvez obtenir lidentifiant 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 afin de sassurer que le systme dexploitation ne tombe pas court de smaphores. Pour
cela, invoquez semctl avec lidentifiant 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). Lidentifiant dutilisateur effectif 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

# include < sys / ipc .h >


# include < sys / sem .h >
# include < sys / types .h >

4
5

/* Nous devons dfinir l union semun nous - mmes . */

6
7
8
9
10
11
12

union semun {
int val ;
struct semid_ds * buf ;
u n s i g n e d short int * array ;
struct seminfo * __buf ;
};

13
14

/* Obtient l identifiant d un smaphore binaire , l alloue si ncessaire . */

15
16
17
18
19

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 ) ;
}

20
21
22
23

/* Libre un smaphore binaire . Tous les utilisateurs doivent avoir fini de


s en servir . Renvoie -1 en cas d chec . */

24
25
26
27
28
29

5.2.2

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 ) ;
}

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

# include < sys / types .h >


# include < sys / ipc .h >
# include < sys / sem .h >

4
5

/* Nous devons dfinir l union semun nous - mmes . */

6
7
8
9
10
11
12

union semun {
int val ;
struct semid_ds * buf ;
u n s i g n e d short int * array ;
struct seminfo * __buf ;
};

13
14

/* Initialise un smaphore binaire avec une valeur de 1. */

15
16
17
18
19
20
21
22
23

5.2.3

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 ) ;
}

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 lidentifiant dun ensemble de smaphores. Son second paramtre est un tableau dlments
struct sembuf qui dfinit 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 effectue lopration.
sem_op est un entier spcifiant lopration accomplir.
Si sem_op est un entier positif, ce chiffre est ajout la valeur du smaphore immdiatement.
Si sem_op est un nombre ngatif, la valeur absolue de ce chiffre 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 effectues 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

93

Listing 5.4 (sem_pv.c) Attente et Rveil pour un Smaphore Binaire


1
2
3

# include < sys / types .h >


# include < sys / ipc .h >
# include < sys / sem .h >

4
5
6
7
8
9
10
11
12
13
14
15

/* 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 ;

16

return semop ( semid , operations , 1) ;

17
18

19
20
21
22
23
24
25
26
27
28
29
30

/* 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 ;

31

return semop ( semid , operations , 1) ;

32
33

Passer lindicateur SEM_UNDO permet de traiter le problme de la fin 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 afficher 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 lidentifiant
5790517, utilisez cette commande :
% ipcrm sem 5790517

5.3

Mmoire mappe

La mmoire mappe permet diffrents processus de communiquer via un fichier 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 diffrences
techniques. La mmoire mappe peut tre utilise pour la communication interprocessus ou
comme un moyen pratique daccder au contenu dun fichier.
La mmoire mappe cre une correspondance entre un fichier et la mmoire dun processus.
Linux divise le fichier en fragments de la taille dune page puis les copie dans des pages de
mmoire virtuelle afin quelles puissent tre disponibles au sein de lespace dadressage dun
processus. Donc le processus peut lire le contenu du fichier par le biais daccs mmoire classiques.
Cela permet un accs rapide aux fichiers.
Vous pouvez vous reprsenter la mmoire mappe comme lallocation dun tampon contenant
la totalit dun fichier, la lecture du fichier dans le tampon, puis (si le tampon est modifi)
lcriture de celui-ci dans le fichier. Linux gre les oprations de lecture et dcriture votre
place.
Il existe dautres utilisations des fichiers 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 fichier 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 fichier 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 dfinit 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 spcifiant des options supplmentaires. Le cinquime argument est un descripteur
de fichier pointant vers le fichier mettre en correspondance, ouvert en lecture. Le dernier
argument est le dplacement, par rapport au dbut du fichier, partir duquel commencer la mise
en correspondance. Vous pouvez mapper tout ou partie du fichier 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 fichier 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 fichier mis
en correspondance, mais sur une copie prive du fichier. 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 fichier 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

5.3.2

95

Programmes exemples

Examinons deux programmes pour illustrer lutilisation des rgions de mmoire mappe pour
lire et crire dans des fichiers. Le premier programme, le Listing 5.5, gnre un nombre alatoire
et lcrit dans un fichier mapp en mmoire. Le second programme, le Listing 5.6, lit le nombre,
laffiche et le remplace par le double de sa valeur. Tous deux prennent en argument de ligne de
commande le nom du fichier 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

# 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

9
10

/* Renvoie un nombre alatoire compris dans l intervalle [ low , high ].

*/

11
12
13
14
15
16

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) ) ;
}

17
18
19
20
21
22
23

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


{
int fd ;
void * file_memory ;
/* Initialise le gnrateur de nombres alatoires .
srand ( time ( NULL ) ) ;

*/

24

/* 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 ) ;

25
26
27
28

write ( fd , " " , 1) ;


lseek ( fd , 0 , SEEK_SET ) ;

29
30
31

/* 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;

32
33
34
35
36
37
38
39
40

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

96

CHAPITRE 5. COMMUNICATION INTERPROCESSUS

libre la mmoire. Lappel munmap nest pas ncessaire car Linux supprimerait automatiquement
la mise en correspondance la fin 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

# 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

8
9
10
11
12
13

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


{
int fd ;
void * file_memory ;
int integer ;

14

/* 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 ) ;

15
16
17
18
19
20

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

21

/* 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 ) ;

22
23
24
25
26
27
28

return 0;

29
30

Le programme mmap-read lit le nombre partir du fichier puis y crit son double. Tout
dabord, il ouvre le fichier et le met en correspondance en lecture/criture. Comme nous pouvons
supposer que le fichier est suffisamment 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. Il utilise le fichier /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 fichier 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 fichier 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 fichier 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 fichier. Passez le drapeau MAP_SHARED afin 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 fichier.
Une alternative est de forcer Linux intgrer les changements effectus en mmoire dans le
fichier en appelant msync. Ses deux premiers paramtres dfinissent une rgion de la mmoire
mise en correspondance avec un fichier, comme pour munmap. Le troisime paramtre peut prendre
les valeurs suivantes :
MS_ASYNC La mise jour est planifie mais pas ncessairement excute avant la fin 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 fichier sont invalides
afin de prendre en compte les modifications.
Par exemple, pour purger un fichier partag mis en correspondance ladresse mem_addr et dune
longueur de mem_length octets, effectuez 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 fichier doivent tablir et suivre un protocole afin 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 fichier, comme le dcrit la Section 8.3,
fcntl: Verrous et 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 fichier ne voient pas les modifications. 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 fichier en mmoire, un programme peut mettre le fichier
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 fichier.
Une technique puissante utilise par certains programmes consiste fabriquer des structures
de donnes (des instances struct ordinaires, par exemple) dans un fichier mis en correspondance
avec la mmoire. Lors dune invocation ultrieure, le programme remet le fichier 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 fichier soit bien mis
en correspondance la mme adresse mmoire quinitialement.
Une autre technique utile est de mettre le fichier spcial /dev/zero en correspondance avec la
mmoire. Ce fichier, dcrit dans la Section 6.5.2, /dev/zero , du Chapitre 6, Priphriques ,
se comporte comme sil tait un fichier de taille infinie rempli doctets zro. Un programme
ayant besoin dune source doctets zro peut appeler mmap pour le fichier /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 fils.
Dans un shell, le symbole | cre un tube. Par exemple, cette commande provoque la cration
par le shell de deux processus fils, 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 fichiers 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 fichier en lecture lindice zro et le descripteur de fichier en
criture lindice un. Par exemple, examinons ce code :

5.4. TUBES

99

int pipe_fds [2];


int read_fd ;
int write_fd ;
pipe ( pipe_fds ) ;
read_fd = pipe_fds [0];
write_fd = pipe_fds [1];

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 fichiers qui ne sont valide quau sein du processus
appelant et de ses fils. Les descripteurs de fichiers dun processus ne peuvent tre transmis des
processus qui ne lui sont pas lis ; cependant, lorsquun processus appelle fork, les descripteurs
de fichiers 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 fils. Le fils hrite
des descripteurs de fichiers du tube. Le pre crit une chane dans le tube et le fils la lit. Le
programme exemple convertit ces descripteurs de fichiers en flux FILE* en utilisant fdopen.
Comme nous utilisons des flux plutt que des descripteurs de fichiers, 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

# include < stdlib .h >


# include < stdio .h >
# include < unistd .h >

4
5
6

/* crit COUNT fois


entre chaque . */

MESSAGE vers STREAM , avec une pause d une seconde

7
8
9
10
11
12
13
14
15
16
17

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) ;
}
}

18
19
20

/* Lit des chanes alatoires depuis le flux


aussi longtemps que possible . */

21
22
23
24
25
26
27
28
29
30

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

CHAPITRE 5. COMMUNICATION INTERPROCESSUS


31

32
33
34
35
36

int main ()
{
int fds [2];
pid_t pid ;

37

/* 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]) ;
}

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

return 0;

67
68

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 fils. 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 fils 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 fils ls et une pour le processus fils less. Ces deux processus hritent
des descripteurs de fichier du tube afin 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

5.4.3

101

Rediriger les flux dentre, de sortie et derreur standards

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

La constante symbolique STDIN_FILENO reprsente le descripteur de fichier 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 indiffremment. Les descripteurs de fichiers substitus
partagent la mme position dans le fichier et le mme ensemble dindicateurs de statut de fichier.
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 fils connecte le descripteur de fichier 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

# include
# include
# include
# include

< stdio .h >


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

5
6
7
8
9

int main ()
{
int fds [2];
pid_t pid ;

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

/* 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 affiche sur la sortie
standard.

102

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) ;

34
35
36
37
38
39
40
41

42
43

return 0;

44
45

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

# include < stdio .h >


# include < unistd .h >

3
4
5
6
7
8
9
10
11
12
13

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 fils 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 fils. La valeur de retour de popen est lextrmit dun tube ; lautre extrmit est
connecte lentre standard du processus fils. Une fois que le processus pre a termin dcrire,
pclose ferme le flux du processus fils, attend la fin 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 flux de sortie standard du processus fils afin que le pre puisse lire la sortie. Si le second
argument est "w", la fonction renvoie le flux dentre standard du processus fils afin que le pre
puisse envoyer des donnes. Si une erreur survient, popen renvoie un pointeur nul.
Appelez pclose pour fermer un flux renvoyer par popen. Aprs avoir ferm le flux indiqu,
pclose attend la fin du processus fils.

5.4. TUBES

5.4.5

103

FIFO

Une file premier entr, premier sorti (first-in, first-out, FIFO) est un tube qui dispose dun
nom dans le systme de fichiers. 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 affich par ls est p ce qui indique que le fichier 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 spcifie les permissions du propritaire
du tube, de son groupe et des autres utilisateurs, comme le dcrit le Chapitre 10, Chapitre 10,
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 fichier 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 fichier 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 lAnnexe 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.
Diffrences 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 diffrentes 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
3

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 identification
distance. Mais vous pouvez galement utiliser telnet pour vous connecter un autre type de serveur et lui
envoyer des commandes directement.

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
dfinit 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 fixes 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 effort (best effort), des paquets peuvent donc tre perdus ou
tre remis dans un ordre diffrent de leur mission.
Un socket de style datagramme se comporte plus comme une lettre postale. Lmetteur
spcifie ladresse du rcepteur pour chaque message.
Lespace de nommage dun socket spcifie comment les adresses de socket sont crites. Une
adresse de socket identifie lextrmit dune connexion par socket. Par exemple, les adresses de
socket dans lespace de nommage local sont des noms de fichiers 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 spcifie 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

5.5.2

CHAPITRE 5. COMMUNICATION INTERPROCESSUS

Appels systme

Les sockets sont plus flexibles 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 Configure un socket afin quil accepte les connexions.
accept Accepte une connexion et cre un nouveau socket pour celle-ci.
Les sockets sont reprsents par des descripteurs de fichiers.
Crer et dtruire des sockets
Les fonctions socket et close crent et dtruisent des sockets, respectivement. Lorsque vous
crez un socket, spcifiez 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
spcifient 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, spcifie 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 fichier pour le socket. Vous pouvez lire ou
crire sur un socket en utilisant read, write, etc. comme avec les autres descripteurs de fichiers.
Lorsque vous en avez fini 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 spcifi 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
diffrent selon lespace de nommage du socket.
Envoyer des informations
Toutes les techniques valides pour crire dans un descripteur de fichier le sont galement pour
crire dans un socket. Reportez-vous lAnnexe 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 spcifique aux descripteur de fichiers 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 afin que les clients puissent le
trouver. Son premier argument est le descripteur de fichier 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 fichier du socket. Le
second spcifie combien de connexions peuvent tre mise en file dattente. Si la file 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 fichier 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 fichier
correspondant. Le socket serveur original continue accepter de nouvelles connexions de clients.
Pour lire des donnes depuis un socket sans le supprimer de la file 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 file 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 fichier, nest
utilise que lors de la cration de connexions.
Le nom du socket est spcifi 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 spcifie
le nom de fichier 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 fichier peut tre utilis,
mais le processus doit avoir des autorisations dcriture sur le rpertoire, qui permettent lajout
de fichiers. Pour se connecter un socket, un processus doit avoir des droits en lecture sur le

108

CHAPITRE 5. COMMUNICATION INTERPROCESSUS

fichier. Mme si diffrents ordinateurs peuvent partager le mme systme de fichier, 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 fichiers, un socket local est affich comme un fichier.
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 affiche
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

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

< stdio .h >


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

7
8
9
10

/* 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 . */

11
12
13
14
15
16

int server ( int client_socket )


{
while (1) {
int length ;
char * text ;

17
18
19
20
21
22
23

/* 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 ) ;

24
25
26
27
28
29
30
31
32
33

/* 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
/* Libre le tampon . */
free ( text ) ;

34
35

36
37

109

38
39
40
41
42
43
44

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 ;

45
46
47
48
49
50
51
52
53

/* 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) ;

54
55
56
57
58
59
60
61

/* 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 ;

62

/* 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 ) ;

63
64
65
66
67
68
69

}
while (! c l i e n t _ s e n t _ q u i t _ m e s s a g e ) ;

70
71
72

/* Supprime le fichier socket . */


close ( socket_fd ) ;
unlink ( socket_name ) ;

73
74
75
76

return 0;

77
78

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

# include
# include
# include
# include
# include

< stdio .h >


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

6
7

/* crit TEXT vers le socket indiqu par le descripteur SOCKET_FD . */

8
9
10

void write_text ( int socket_fd , const char * text )


{

110

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 ) ;

11
12
13
14
15
16
17

18
19
20
21
22
23
24

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 ;

25

/* 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;

26
27
28
29
30
31
32
33
34
35
36
37

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 spcifiant 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 affiche 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 effort de remise, des paquets
peuvent donc disparatre ou tre mlangs durant le transport. Tous les ordinateurs participants
sont dfinis en utilisant une adresse IP unique. Le Transmission Control Protocol (TCP), qui
sappuie sur IP, fournit un transport fiable orient connexion. Il permet dtablir des connexions
semblables aux connexions tlphoniques entre des ordinateurs et assure que les donnes sont
remises de faon fiable 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 diffrents, utilisez htons pour reprsenter le
numro de port dans lordre des octets dfini 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

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

< stdlib .h >


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

8
9
10

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


au socket . */

112

CHAPITRE 5. COMMUNICATION INTERPROCESSUS


11
12
13
14
15
16
17
18

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 ) ) ;

19

/* 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 ) ;
}

20
21
22
23
24
25
26
27
28
29
30

31
32
33
34
35
36

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


{
int socket_fd ;
struct sockaddr_in name ;
struct hostent * hostinfo ;

37

/* 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) ;

38
39
40
41
42
43
44
45
46
47
48
49
50

/* 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;

51
52
53
54
55
56
57
58
59
60

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.
Par exemple, pour rapatrier la page daccueil du site Web www.codesourcery.com, invoquez

5.5. SOCKETS

113

Numros de Ports Standards


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 fichier /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.

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 fichiers
chacune tant lextrmit dun tube. Les tubes sont limits car les descripteurs de fichiers
doivent tre utiliss par des processus lis et car la communication est unidirectionnelle. La
fonction socketpair cre deux descripteurs de fichiers pour deux sockets connects sur le mme
ordinateur. Ces descripteurs de fichiers 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 fichiers 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

Priphriques

119

Le systme de fichiers /proc

135

Appels systme Linux

153

Code assembleur en ligne

173

10 Scurit

181

11 Application GNU/Linux dIllustration

201

118

TABLE DES MATIRES

Chapitre 6

Priphriques
L

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 fichiers.
Ces objets apparaissent dans le systme de fichiers et des applications peuvent les ouvrir, les lire
et y crire pratiquement comme sil sagissait de fichiers normaux. Vos programmes peuvent donc
communiquer avec des dispositifs matriels via des objets semblables aux fichiers soit en utilisant
les oprations dE/S de bas niveau de Linux (consultez lAnnexe B, E/S de bas niveau ), soit
les oprations de la bibliothque dE/S standard du C.
Linux fournit galement plusieurs objets semblables des fichiers 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 diffrents 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

6.1

CHAPITRE 6. PRIPHRIQUES

Types de priphriques

Les fichiers de priphriques ne sont pas des fichiers ordinaires ils ne reprsentent pas des
zones de donnes au sein dun systme de fichiers sur disque. Au lieu de cela, les donnes lues ou
crites sur un fichier de priphrique sont transmises au pilote de priphrique correspondant
et, par son intermdiaire, au matriel sous-jacent. Les fichiers de priphriques se divisent en
deux types :
Un priphrique caractre reprsente un dispositif matriel qui lit ou crit en srie un flux
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 fixe. 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 fichiers mont sur larborescence racine de GNU/Linux. Seul le
code du noyau qui implmente le systme de fichiers a besoin daccder au priphrique bloc
directement ; les programmes dapplication accdent au contenu du disque via des fichiers 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 offrent un accs direct aux donnes du lecteur de disque. Bien que
la plupart des systmes GNU/Linux soient configurs 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 modifier ou dtruire les informations de contrle du systme de
fichier 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 identifie 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 fixes et dfinies dans les sources du noyau Linux. Notez quun mme numro
de priphrique majeur peut correspondre deux pilotes diffrents, 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 fichier
/usr/src/linux/Documentation/devices.txt. Le fichier 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 fichiers /proc).

6.3

Fichiers de priphriques

Un fichier de priphrique ressemble beaucoup un fichier 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 fichier de destination. Si vous essayez dcraser un fichier de priphrique, vous crirez
des octets vers le priphrique concern.
Vous pouvez crer un fichier 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 fichier de priphrique nimplique pas automatiquement que le pilote
ou le dispositif matriel soit prsent ou disponible ; le fichier 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, spcifiez le chemin du fichier
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 fichier 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 affiche les fichiers de priphrique dune faon particulire. Si vous lappelez
avec les options -l ou -o, le premier caractre de chaque ligne indique le type du fichier.
Rappelons que - (un tiret) indique un fichier 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 affiche les numros de priphrique majeur et mineur l o se trouve habituellement
la taille pour les fichiers ordinaires. Par exemple, nous pouvons afficher 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 fichier est un priphrique bloc ou caractre et
donc obtenir ses numros de priphrique via stat. Consultez la Section B.2, stat , Annexe B,
pour plus dinformations.
Pour supprimer le fichier, utilisez rm. Cela ne supprime pas le priphrique ou son pilote ;
mais simplement le fichier de priphrique du systme de fichiers.
% rm ./lp0

6.3.1

Le rpertoire /dev

Par convention, un systme GNU/Linux inclut un rpertoire /dev contenant tous les fichiers
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 vrifier 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 fichiers 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 spcifiques ont besoin de crer leurs propres fichiers de priphrique. La plupart des
distributions GNU/Linux proposent des utilitaires daide la cration de fichiers 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 fichier classique et lisez
ou crivez-y. Vous pouvez mme utiliser des commandes conues pour les fichiers 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 fichiers 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 fichier de priphrique pour que la
commande nchoue pas ; sur beaucoup de systmes GNU/Linux, les permissions sont dfinies de
telle faon que seul root et le dmon dimpression systme (lpd) puissent crire dans ce fichier.
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 fichiers texte plats que vous leur
enverrez2 , dautres non. Les imprimantes PostScript interprteront et imprimeront les fichiers
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 fichiers 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 diffrente. Par exemple, lorsque
vous utilisez le lecteur de cassettes IDE /dev/ht0, Linux rembobine automatiquement la cassette
lorsque vous fermez le descripteur de fichier. Vous pouvez utiliser /dev/nht0 pour accder au
mme lecteur de cassettes, la seule diffrence 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 :
1

Les utilisateurs de Windows reconnatront l un priphrique similaire au fichier magique LPT1 de Windows.
Votre imprimante peut ncessiter lajout de retours chariot, code ASCII 13, la fin de chaque ligne et lajout
dun caractre de saut de page, code ASCII 12, la fin de chaque page.
2

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

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
0
1
64
65
0
1
16
17
0
1

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 vrification 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 effectuant
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 fichier avec une syntaxe comme celle-ci :
% programme_sur < mon-motdepasse.txt

Si vous avez besoin dun mcanisme dauthentification dans votre programme, vous devriez
vous tourner vers le dispositif PAM de GNU/Linux. Consultez la Section 10.5, Authentifier les utilisateurs , du Chapitre 10, Scurit , pour plus dinformations.
Un programme peut diffuser 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 (fichiers
portant habituellement lextension .au).
Par exemple, beaucoup de distributions GNU/Linux fournissent le fichier son classique
/usr/share/sndconfig/sample.au. Si votre systme dispose de ce fichier, 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.
3

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.

126

CHAPITRE 6. PRIPHRIQUES

6.5

Priphriques spciaux

Linux fournit galement divers priphriques caractre ne correspondant aucun priphrique matriel. Ces fichiers 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 fichier /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
spcifier /dev/null en tant que fichier de sortie lorsque lon ne veut pas de sortie. Par
exemple, pour lancer une commande et ignorer son affichage standard (sans lafficher ni
lenvoyer vers un fichier), redirigez la sortie standard vers /dev/null :
% commande_bavarde > /dev/null

Lire depuis /dev/null renvoie toujours une fin de fichier. Par exemple, si vous ouvrez un
descripteur de fichier 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 fichier, la destination sera un fichier 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 fichier de priphrique /dev/zero se comporte comme sil contenait une infinit doctets
0. Quelle que soit la quantit de donnes que vous essayez de lire partir de /dev/zero, Linux
gnrera suffisamment doctets nuls.
Pour illustrer cela, excutons le programme de capture en hexadcimal prsent dans le
Listing B.4, Section B.1.4, Lecture de donnes , de lAnnexe B. Ce programme affiche le
contenu dun fichier 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 indfiniment.


Mettre /dev/zero en correspondance avec la mmoire est une technique dallocation avance.
Reportez-vous la Section 5.3.5, Autres utilisations de mmap , 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: dfinir des permissions mmoire , du
Chapitre 8, Appels systme Linux , pour un exemple.

6.5. PRIPHRIQUES SPCIAUX

6.5.3

127

/dev/full

Le fichier /dev/full se comporte comme sil se trouvait sur un systme de fichiers 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 fichier /dev/full est essentiellement utile pour tester la faon dont se comporte votre
programme sil tombe court despace disque lors de lcriture dun fichier.

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 chiffrement 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
flux de nombres alatoires de grande qualit impossible prdire. Vous pouvez accder ce flux
en lisant les fichiers /dev/random et /dev/urandom. Les donnes que vous obtenez sont issues
dun flux doctets gnr alatoirement.
La diffrence 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 neffectuez
aucune autre action de ce type), Linux bloque lopration de lecture. La gnration ne reprendra
que lorsque vous effectuerez des actions.
Par exemple, essayez dafficher le contenu de /dev/random en utilisant la commande od4 .
Chaque ligne affiche 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 dafficher le contenu du fichier en hexadcimal.

128

CHAPITRE 6. PRIPHRIQUES

Le nombre de lignes affiches 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 vrifiez 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 chiffrement pour gnrer des octets
pseudo-alatoires partir de la dernire squence doctets alatoires. Bien que ces octets soient
suffisamment 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 dfileront 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 suffisamment 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

# include
# include
# include
# include
# include

< assert .h >


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

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

/* 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


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) ) ;

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

6.5.5

129

Priphriques loopback

Un priphrique loopback vous permet de simuler un priphrique bloc en utilisant un fichier


disque ordinaire. Imaginez un lecteur de disque pour lequel les donnes sont crites et lues
partir dun fichier appel image-disque plutt que depuis les pistes et secteurs dun disque
physique rel ou dune des partition dun disque (bien sr, le fichier 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 fichier 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 fichiers sur le priphrique puis monter ce
systme de fichiers comme sil rsidait sur un disque ou une partition classique. Un tel systme
de fichiers, qui rside entirement au sein dun fichier sur disque ordinaire, est appel systme
de fichiers virtuel.
Pour construire un systme de fichiers et le monter partir dun priphrique loopback,
suivez ces tapes :
1. Crez un fichier vide qui contiendra le systme de fichiers virtuel. La taille du fichier sera
la taille du priphrique loopback une fois mont.
Une faon pratique de construire un fichier dune taille prdtermine est dutiliser la
commande dd. Elle copie des blocs (de 512 octets chacun, par dfaut) dun fichier vers un
autre. Le fichier /dev/zero convient parfaitement pour tre utilis comme source doctets
nuls.
Pour construire un fichier 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 fichier que vous venez de crer est rempli avec des octets 0. Avant de le monter,
vous devez y placer un systme de fichiers. Cette opration initialise diverses structures de
contrle ncessaires lorganisation et au stockage de fichiers et cre le rpertoire racine.

130

CHAPITRE 6. PRIPHRIQUES
Vous pouvez placer nimporte quel type de systme de fichiers sur votre image disque. Pour
crer un systme de fichiers 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 fichiers ordinaires, elle demande une confirmation :
/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 fichiers nouvellement cr. Supprimez-la si vous tes curieux.
Dsormais image-disque contient un nouveau systme de fichiers comme sil sagissait
dun disque de 10 Mo tout neuf.
3. Montez le systme de fichiers en utilisant un priphrique loopback. Pour cela, utilisez la
commande mount en spcifiant le fichier 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 fichiers 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 fichiers 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 fini, dmontez le systme de fichiers 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 fichiers du systme de fichiers virtuel. Vous pouvez galement le
copier sur un autre ordinateur o vous pourrez le monter le systme de fichiers que vous
avez cr sera entirement intact.
5

Si le systme de fichiers subit des dommage et que des donnes sont rcupres sans tre associes un fichier,
elles sont places dans lost+found.

6.6. PTY

131

Au lieu de crer un systme de fichiers 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 fichier /etc/fstab pour dterminer quel priphrique correspond au lecteur de CD-ROM de
votre ordinateur.
Copiez simplement le priphrique vers un fichier. Le rsultat sera une image disque complte
du systme de fichiers 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 fichier 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 fichiers
ISO-9660.

6.6

PTY

Si vous excutez la commande mount sans arguments de ligne de commande, ce qui liste les
systmes de fichiers 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 fichiers dun type particulier, devpts, est mont sur /dev/pts.
Ce systme de fichiers, qui nest pas associ avec un priphrique matriel, est un systme de
fichiers magique cr par le noyau Linux. Il est similaire au systme de fichiers /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 reflte
ltat du systme.
Les fichiers 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 affiche les sorties du programme lui correspondant. Les
PTY sont numrots et leur numro correspond au nom du fichier correspondant dans /dev/pts.

132

CHAPITRE 6. PRIPHRIQUES

Vous pouvez afficher le terminal associ un processus grce la commande ps. Indiquez
tty comme lun des champ de format personnalis avec loption -o. Pour afficher lidentifiant 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 fichier 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.
Ouvrez 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 fichier qui doit pointer sur le priphrique que
vous voulez contrler. Le second argument est un code de requte indiquant lopration que vous
souhaitez effectuer. Diffrents 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 diffrents 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

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

< fcntl .h >


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

7
8
9
10
11
12
13

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 ) ;

de

14

/* Ferme le descripteur . */
close ( fd ) ;
return 0;

15
16
17
18

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 fichier pointant vers le priphrique et invoque ioctl avec le code de requte CDROMEJECT. Cette requte, dfinie 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


E

ssayez dinvoquer la commande mount sans argument elle liste les systmes de
fichiers 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 fichiers 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 fichiers de /proc ne correspondent
pas des fichiers rels sur un disque physique. Il sagit plutt dobjets magiques qui se comportent
comme des fichiers mais donnent accs des paramtres, des structures de donnes et des
statistiques du noyau. Le contenu de ces fichiers nest pas compos de blocs de donnes,
comme celui des fichiers ordinaires. Au lieu de cela, il est gnr la vole par le noyau Linux
lorsque vous lisez le fichier. Vous pouvez galement changer la configuration du noyau en cours
dexcution en crivant dans certains fichiers du systme de fichiers /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 fichier est zro ; comme le contenu du fichier est gnr par le noyau,
le concept de taille de fichier na pas de sens. De plus, si vous essayez cette commande, vous
remarquerez que la date de modification est la date courante.
Quy a-t-il dans ce fichier ? 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 fichier. 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 diffrentes entres du systme de fichiers /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 fichiers /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 fichiers de /proc donnent des informations formates pour pouvoir tre lues
par des humains, cependant leur prsentation reste suffisamment 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 fichier 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

137

# include < stdio .h >


# include < string .h >

3
4
5
6

/* 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 . */

7
8
9
10
11
12
13
14

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 ;

15

/* 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 ;

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

33
34
35
36
37
38

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 signification et les formats de sortie des entres du
systme de fichiers /proc peuvent changer au fil 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 fichiers /proc contient un rpertoire par processus sexcutant sur le systme.
Le nom de chacun de ces rpertoires est lidentifiant 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 fichiers
/proc tire son nom.
1

Sur certains systmes UNIX, les identifiants de processus sont aligns au moyen de zros. Ce nest pas le cas
sous GNU/Linux.

138

CHAPITRE 7. LE SYSTME DE FICHIERS /PROC

Chaque rpertoire de processus contient ces fichiers :


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 dfini par exemple, via un appel chdir).
environ contient lenvironnement du processus. Le fichier 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 fichiers 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 fichiers mis en correspondance avec la mmoire. Pour chaque
fichier mis en correspondance, maps affiche lintervalle dadresses de lespace mmoire du
processus avec lequel le fichier est mis en correspondance, les permissions applicables ces
adresses, le nom du fichier ainsi que dautres informations.
Le tableau maps affiche pour chaque processus le fichier binaire en cours dexcution, les
bibliothques partages actuellement charges et les autres fichiers 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 fichier status, mais au format numrique
brut, sur une seule ligne. Ce format est difficile lire mais plus adapt au traitement
automatique par des programmes.
Si vous voulez utiliser le fichier 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 fichier 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 fichier status.
Le fichier 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 fichiers sont dfinies 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 fichiers /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 identifiant 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, Identifiants 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: lecture
de liens symboliques , pour extraire la cible du lien.
Listing 7.2 (get-pid.c) Rcuprer son PID partir de /proc/self
1
2
3

# include < stdio .h >


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

4
5
6

/* Renvoie l identifiant de processus de l appelant , dtermin partir du


lien symbolique / proc / self . */

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

7.2.2

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;
}

Liste darguments dun processus

Le fichier 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 affichait sa liste
darguments. En utilisant lentre cmdline du systme de fichiers /proc, nous pouvons crer un
programme qui affiche la liste darguments dun autre processus. Le Listing 7.3 est un programme
de ce type ; il affiche la liste darguments du processus dont lidentifiant est pass en paramtre.
Comme il peut y avoir plusieurs NUL dans le contenu de cmdline et non un seul en fin de chane,

140

CHAPITRE 7. LE SYSTME DE FICHIERS /PROC


NUL et NULL
NUL est le caractre dont la valeur dcimale est 0. Il est diffrent 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 fin 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 dfinition
de NULL diffre selon le systme dexploitation ; sous Linux, NULL est dfini comme ((void*)0)
en C et tout simplement 0 en C++.

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) Affiche la Liste dArguments dun Processus
1
2
3
4
5
6

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

< fcntl .h >


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

7
8
9

/* Affiche la liste d aguments , un par ligne , du processus dont l identifiant


est pass en paramtre . */

10
11
12
13
14
15
16
17

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 ;

18
19
20
21
22
23
24
25
26

/* 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 ;

27
28
29
30
31
32
33
34
35
36
37

/* 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 . */
next_arg += strlen ( next_arg ) + 1;

7.2. RPERTOIRES DE PROCESSUS


}

38
39

141

40
41
42
43
44
45
46

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;
}

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 fichier environ contient lenvironnement du processus (consultez la Section 2.1.6, Lenvironnement ). Comme pour cmdline, les diffrentes 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 identifiant du processus sur la ligne de commande et affiche son environnement en le lisant partir de /proc.
Listing 7.4 (print-environment.c) Affiche lEnvironnement dun Processus
1
2
3
4
5
6

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

< fcntl .h >


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

7
8
9

/* Affiche l environnement du processus dont l identifiant est pass en


paramtre , une variable par ligne . */

10
11
12
13
14
15
16
17

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 ;

18
19
20
21
22
23

/* 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 ) ;
length = read ( fd , environment , sizeof ( environment ) ) ;

142

CHAPITRE 7. LE SYSTME DE FICHIERS /PROC


24
25
26

close ( fd ) ;
/* read ne place pas de caractre NUL la fin du tampon . */
environment [ length ] = \0 ;

27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

7.2.4

/* 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;
}

Excutable de processus

Lentre exe pointe vers le fichier binaire excut par le processus. Dans la Section 2.1.1,
nous avons expliqu que le nom de ce fichier 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 fichiers
/proc est une faon plus fiable de dterminer le fichier binaire en cours dexcution.
De mme, il est possible dextraire lemplacement absolu de lexcutable, partir du systme
de fichiers /proc. Pour beaucoup de programmes, les fichiers auxiliaires se trouvent dans des
rpertoires relatifs lexcutable, il est donc ncessaire de dterminer o se trouve rellement le fichier binaire. La fonction get_executable_path du Listing 7.5 dtermine le chemin
du fichier 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

# include
# include
# include
# include

< limits .h >


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

5
6
7
8

/* 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 . */

9
10
11
12
13
14
15

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)
return -1;

7.2. RPERTOIRES DE PROCESSUS

143

/* 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 " ;

16
17
18
19
20
21
22
23
24
25
26
27

/* La longueur du chemin est le nombre de caractres


jusqu au dernier slash . */
return ( size_t ) ( path_end - buffer ) ;

28
29
30
31

32
33
34
35
36
37
38
39

7.2.5

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;
}

Descripteurs de fichiers dun processus

Le rpertoire fd contient des sous-rpertoires correspondant aux descripteurs de fichiers


ouverts par un processus. Chaque entre est un lien symbolique vers le fichier ou le priphrique
dsign par le descripteur de fichier. Vous pouvez lire ou crire sur ces liens symboliques ; cela
revient crire ou lire depuis le fichier ou le priphrique ouvert dans le processus cible. Les
noms des entres du sous-rpertoire fd correspondent aux numros des descripteurs de fichiers.
Voici une astuce amusante que vous pouvez essayer avec les entres fd de /proc. Ouvrez une
nouvelle fentre de terminal et rcuprez lidentifiant 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 si dautres fichiers sont ouverts). Souvenez-vous de ce que nous
avons dit dans la Section 2.1.4, E/S standards : les descripteurs de fichiers 0, 1 et 2 sont
initialiss pour pointer vers lentre, la sortie et la sortie derreurs standards, respectivement.
Donc, en crivant dans le fichier /proc/1261/fd/1 vous pouvez crire sur le priphrique attach

144

CHAPITRE 7. LE SYSTME DE FICHIERS /PROC

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 fichier :
% echo "Coucou." >> /proc/1261/fd/1

Le texte apparat dans la premire fentre.


Les descripteurs de fichiers 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 fichier pass sur la ligne de commande et de boucler
indfiniment.
Listing 7.6 (open-and-spin.c) Ouvre un Fichier en Lecture
1
2
3
4
5

# include
# include
# include
# include
# include

< fcntl .h >


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

6
7
8
9
10
11
12
13
14
15

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 fichier 3 est lie au fichier /etc/fstab vers lequel pointe le
descripteur.
Les descripteurs de fichiers 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 fichier indiquera socket ou pipe au lieu de pointer vers un fichier 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 modifies).
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 modifies 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 lidentifiant
du processus et de son pre, les identifiants dutilisateur et de groupe rel et effectif, lutilisation
mmoire et des masques binaires indiquant quels signaux sont intercepts, ignors et bloqus.

7.3

Informations sur le matriel

Plusieurs autres fichiers du systme de fichiers /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 laffichage 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 fichier /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 fichier /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 identifiant
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 fichier /proc/tty/driver/serial contient des informations de configuration et des statistiques sur les ports srie. Ceux-ci sont numrots partir de 04 . Les informations de configuration
sur les ports srie peuvent galement tre obtenues, ainsi que modifies, 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 configuration 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 fichier /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 :
4

Notez que sous DOS et Windows, les port srie sont numrots partir de 1, donc COM1 correspond au port
srie 0 sous Linux.

7.4. INFORMATIONS SUR LE NOYAU

147

% 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

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 affichage, le nom de lOS et la version et la
rvision du noyau, sont galement disponibles dans des fichiers 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 fichiers /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 fichier /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 affichage 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 affiche 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 Buffers 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 fichiers mis en correspondance
avec la mmoire.
Vous pouvez utiliser la commande free pour afficher les mmes informations sur la mmoire.

7.5

Lecteurs et systmes de fichiers

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

7.5.1

Systmes de fichiers

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

7.5.2

Lecteurs et partitions

Le systme de fichiers /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/
5

Sil est correctement configur, le noyau Linux peut prendre en charge des contrleurs IDE supplmentaires.
Ils sont numrots de faon squentielle partir de ide2.

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 didentification et de configuration sur le priphrique. Voici les plus utiles :
model contient la chane didentification 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 fichiers 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 identifiants.
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 identifiants de priphrique SCSI 0 et 4.
Le fichier /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

CHAPITRE 7. LE SYSTME DE FICHIERS /PROC


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

7.5.3

1
1

Points de montage

Le fichier /proc/mounts donne un aperu des systmes de fichiers 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 fichier
traditionnel /etc/mtab, qui est automatiquement mis jour par la commande mount.
Voici les diffrents 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 fichiers racine
o le contenu du systme de fichiers apparat. Pour le systme de fichiers 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 fichiers. Actuellement, la plupart des
systmes GNU/Linux utilisent le systme de fichiers 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 fichiers.
Les deux derniers lments des lignes de /proc/mounts sont toujours 0 et nont pas de
signification.
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 oprations sur les fichiers , dcrit lutilisation de lappel
systme fcntl pour manipuler des verrous en lecture ou en criture sur les fichiers. Le fichier
/proc/locks dcrit tous les verrous de fichiers actuellement en place sur le systme. Chaque
ligne correspond un verrou.
Pour les verrous crs avec fcntl, la ligne commence par POSIX ADVISORY. Le troisime
champ vaut WRITE ou READ, selon le type de verrou. Le nombre qui suit correspond lidentifiant
du processus possdant le verrou. Les trois chiffres suivants, spars par deux-points, sont les
numros majeur et mineur du priphrique sur lequel se trouve le fichier et le numro dinode,
qui situe le fichier dans le systme de fichiers. Le reste de la ligne est constitu de valeurs utilises
en interne par le noyau et qui ne sont gnralement daucune utilit.
6

Le fichier /etc/fstab dcrit la configuration 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 fichier /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 lidentifiant 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 fichier 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 fichier /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. /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 chiffre 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 lidentifiant du
processus ayant eu la main le plus rcemment.
Le fichier /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

CHAPITRE 7. LE SYSTME DE FICHIERS /PROC


% cat /proc/uptime
3248936.18 3072330.49

Le programme du Listing 7.7 extrait le temps depuis lequel le systme est dmarr ainsi que sa
dure dinactivit et les affiche de faon lisible.
Listing 7.7 (print-uptime.c) Affiche des Informations sur les Temps Systme
1

# include < stdio .h >

2
3
4

/* Affiche une dure sur la sortie standard de faon lisible . TIME est la
dure , en secondes , et LABEL est une lgende courte . */

5
6
7
8
9
10
11
12
13
14
15

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 ) ;
}

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

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: rcupration de statistiques systme ) permettent galement dobtenir le temps coul depuis le
dmarrage. La commande uptime affiche galement les moyennes de charge systme contenues
dans /proc/loadavg.

Chapitre 8

Appels systme Linux


J

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 effectue
un appel systme, les arguments sont mis en forme et transfrs au noyau qui prend
la main jusqu la fin de lappel. Un appel systme nest pas identique un appel de
fonction classique et une procdure spcifique 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 afin 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 influent 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 fichier /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 fichier 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 effectue 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 effectus par la commande hostname1 , utilisez cette commande :
% strace hostname

Elle vous affichera 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 affiche
les diffrents champs des structures passes via un pointeur lappel systme. Notez que strace
ne montre pas les appels de fonctions classiques.
La premire ligne affiche 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 naffiche
pas par souci de concision. Les 30 lignes suivantes, approximativement, font partie du mcanisme
qui charge la bibliothque standard du C partir dun fichier de bibliothque partag.
Les appels systmes effectivement utiliss par le programme pour fonctionner se trouvent
vers la fin de laffichage. 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 affiche 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 .
Enfin, lappel systme write affiche les informations. Souvenez-vous que le descripteur de
fichier 1 correspond la sortie standard. Le troisime argument est le nombre de caractres
crire et la valeur de retour est le nombre de caractres effectivement crits.
write(1, "myhostname\n", 11) = 11

Laffichage 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 affiche beaucoup dinformations, il est parfois plus pratique de rediriger la sortie de strace vers un fichier. Pour cela, utilisez loption -o nom_de_fichier.
La comprhension de tout ce quaffiche 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.

access : Tester les permissions dun fichier

8.2

Lappel systme access dtermine si le processus appelant le droit daccder un fichier. Il


peut vrifier toute combinaison des permissions de lecture, criture ou excution ainsi que tester
lexistence dun fichier.
Lappel access prend deux argument. Le premier est le chemin daccs du fichier 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 fichier 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 fichier
situ sur un systme de fichiers en lecture seule).
Si le second argument est F_OK, access vrifie simplement lexistence du fichier. Si le fichier
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 vrifier lexistence dun fichier et dterminer
ses permissions en lecture et en criture. Spcifiez le nom du fichier vrifier sur la ligne de
commande.
Listing 8.1 (check-access.c) Vrifier les Droits dAccs un Fichier
1
2
3

# include < errno .h >


# include < stdio .h >
# include < unistd .h >

4
5
6
7
8
9

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


{
char * path = argv [1];
int rval ;

156

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;
}

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

/* 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 ) ;

22
23
24
25
26
27
28

/* 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;

29
30
31
32
33
34
35
36
37
38

Par exemple, pour tester les permissions daccs un fichier 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 fichiers. Le premier argument de fcntl est un descripteur de fichiers ouvert et le second
est une valeur indiquant quelle opration doit tre effectue. Pour certaines dentre-elles, fcntl
prend un argument supplmentaire. Nous dcrirons ici lune des oprations les plus utiles de
fcntl : le verrouillage de fichier. 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 fichier, 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 fichier accessible
en lecture et un verrou en criture sur un descripteur de fichier accessible en criture. Plusieurs
processus peuvent dtenir un verrou en lecture sur le mme fichier au mme moment, mais un
seul peut dtenir un verrou en criture et le mme fichier 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 fichier, dy lire des donnes ou dy crire, moins quil ne demandent eux
aussi un verrou avec fcntl.
Pour placer un verrou sur un fichier, 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 fichier 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 fichier 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 fichier.
Listing 8.2 (lock-file.c) Cre un Verrou en criture avec fcntl
1
2
3
4

# include
# include
# include
# include

< fcntl .h >


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

5
6
7
8
9
10

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


{
char * file = argv [1];
int fd ;
struct flock lock ;

11

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 ) ;

12
13
14
15
16
17
18
19
20
21

printf ( " verrouill ; appuyez sur Entre pour dverrouiller ... " ) ;
/* Attend l appui sur Entre . */
getchar () ;

22
23
24
25

printf ( " dv er ro uil la ge \ n " ) ;


/* Libre le verrou . */
lock . l_type = F_UNLCK ;
fcntl ( fd , F_SETLKW , & lock ) ;

26
27
28
29
30

close ( fd ) ;
return 0;

31
32
33

Compilez et lancez le programme sur un fichier 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 fichier :
% ./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 fichier. 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 fichiers avec lappel flock. La
fonction fcntl dispose dun avantage majeur : elle fonctionne sur les fichiers se trouvant sur un
systme de fichiers NFS3 (si tant est que le serveur NFS soit relativement rcent et correctement
configur). Ainsi, si vous disposez de deux machines qui ont toutes deux le mme systme de
fichiers mont via NFS, vous pouvez reproduire lexemple ci-dessus en utilisant deux machines
diffrentes. Lancez lock-file sur une machine en lui passant un fichier situ sur le systme de
fichiers NFS puis lancez le sur la seconde en lui passant le mme fichier. 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 fichier, 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 fichier journal. Ce dernier contient les enregistrements concernant toutes les transactions qui
ont t traites afin 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 fichier
journal chaque fois quune transaction a lieu, son entre dans le journal doit tre envoye
immdiatement sur le disque dur.
3

Network File System (NFS) est une technologie de partage de fichiers courante, comparable aux partages et
aux lecteurs rseau Windows.

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 fichier ouvert en criture, et envoie sur le
disque toutes les donnes crites dans le fichier. 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 fichier journal.
Listing 8.3 (write_journal_entry.c) crit et Synchronise un enregistrement
1
2
3
4
5

# include
# include
# include
# include
# include

< fcntl .h >


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

6
7

const char * j o u r n a l _ f i l e n a m e = " journal . log " ;

8
9
10
11
12
13
14
15
16

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 modification du fichier sera mise jour, ce nest pas le cas de fdatasync ; ce dernier
ne garantit que le fait que les donnes seront crites. Cela signifie quen gnral, fdatasync peut
sexcuter plus vite que fsync car il na quune seule criture effectuer 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 modification du fichier.
Lappel systme fsync vous permet de forcer explicitement lcriture dun tampon. Vous
pouvez galement ouvrir un fichier en mode entres/sorties synchrones, ce qui signifie que toutes
les critures sont envoyes sur le disque immdiatement. Pour cela, passez loption O_SYNC lors
de louverture du fichier avec open.

8.5

getrlimit et setrlimit : limites de ressources

Les appels systme getrlimit et setrlimit permettent un processus de connatre et de


dfinir 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 spcifiant 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 modifie 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 modifies 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 effectivement 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 fils 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 fichiers 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 dfinit un temps processeur de une seconde puis entre dans une boucle
infinie. 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

# include < sys / resource .h >


# include < sys / time .h >
# include < unistd .h >

4
5
6
7

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) ;

9
10
11
12
13
14
15
16

return 0;

17
18

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

8.6. GETRUSAGE : STATISTIQUES SUR LES PROCESSUS

161

getrusage : statistiques sur les processus

8.6

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 fils termins qui ont t crs par ce processus
et ses fils 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 affiche les temps systme et utilisateur du processus en cours.
Listing 8.5 (print-cpu-times.c) Affiche les Temps Utilisateur et Processeur
1
2
3
4

# include
# include
# include
# include

< stdio .h >


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

5
6
7
8
9
10
11
12
13

8.7

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 ) ;
}

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 spcifi 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 affiche la date et lheure courante, avec une prcision de lordre
de la milliseconde.
Listing 8.6 (print-time.c) Affiche la Date et lHeure
1
2
3
4

# include
# include
# include
# include

< stdio .h >


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

5
6
7
8
9
10
11

void print_time ()
{
struct timeval tv ;
struct tm * ptm ;
char time_string [40];
long milliseconds ;

12

/* 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 ) ;

13
14
15
16
17
18
19
20
21
22
23

8.8. LA FAMILLE MLOCK : VERROUILLAGE DE LA MMOIRE PHYSIQUE

8.8

163

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 fin
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
5

La copie lcriture (copy on write) signifie que Linux ne fait une copie prive de la page que lorsque le
processus crit une valeur lintrieur.

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 effectivement 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 : dfinir des permissions mmoire

Dans la Section 5.3, Mmoire mappe , nous avons montr comment utiliser lappel
systme mmap pour mettre en correspondance un fichier 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 modifie 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 mappe /dev/zero pour allouer une page
mmoire, 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 ) ;

Plus loin, votre programme peut protger la mmoire en criture en appelant mprotect :
mprotect ( memory , page_size , PROT_READ ) ;
6

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

8.9. MPROTECT : DFINIR DES PERMISSIONS MMOIRE

165

Obtenir de la Mmoire Aligne sur une Page


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.

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

# 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 >

9
10
11

static int alloc_size ;


static char * memory ;

12
13
14
15
16
17

void segv_handler ( int signal_number )


{
printf ( " accs mmoire !\ n " ) ;
mprotect ( memory , alloc_size , PROT_READ | PROT_WRITE ) ;
}

18
19
20
21
22

int main ()
{
int fd ;
struct sigaction sa ;

23
24
25
26
27

/* Installe segv_handler comme gestionnaire de signal SIGSEGV .


memset (& sa , 0 , sizeof ( sa ) ) ;
sa . sa_handler = & segv_handler ;
sigaction ( SIGSEGV , & sa , NULL ) ;

*/

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

/* 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 . */
mprotect ( memory , alloc_size , PROT_NONE ) ;

166

CHAPITRE 8. APPELS SYSTME LINUX


/* crit dans la rgion qui vient d tre alloue . */
memory [0] = 1;

40
41
42

/* Termin ; libre la mmoire . */


printf ( " Fini \ n " ) ;
munmap ( memory , alloc_size ) ;
return 0;

43
44
45
46
47

Le programme effectue 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
offerte 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 offre 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 diffrence entre le temps de suspension demand le temps de suspension effectif).
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 flottante
correspondant la dure en secondes pour laquelle il faut suspendre lexcution et reprend
lopration de suspension si elle est interrompue.
Listing 8.8 (better-sleep.c) Fonction de Suspension Haute prcision

8.11. READLINK : LECTURE DE LIENS SYMBOLIQUES


1
2

167

# include < errno .h >


# include < time .h >

3
4
5
6
7
8
9
10

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) ;

*/

11

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;

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

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 fin 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 fichier qui nest pas un lien symbolique,
readlink positionne errno EINVAL et renvoie -1.
Le petit programme du Listing 8.9 affiche la cible du lien symbolique pass sur la ligne de
commande.
Listing 8.9 (print-symlink.c) Affiche la Cible dun Lien Symbolique
1
2
3

# include < errno .h >


# include < stdio .h >
# include < unistd .h >

4
5
6
7
8

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


{
char target_path [256];
char * link_path = argv [1];

9
10
11
12

/* Tente de lire la cible du lien symbolique . */


int len = readlink ( link_path , target_path , sizeof ( target_path ) ) ;
if ( len == -1) {

168

CHAPITRE 8. APPELS SYSTME LINUX


/* 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;

13
14
15
16
17
18
19
20

}
else {
/* Place un caractre NUL la fin de la cible . */
target_path [ len ] = \0 ;
/* L affiche . */
printf ( " % s \ n " , target_path ) ;
return 0;
}

21
22
23
24
25
26
27
28
29

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

sendfile : transferts de donnes rapides

Lappel systme sendfile constitue un mcanisme efficace pour copier des donnes entre
deux descripteurs de fichier. Les descripteurs de fichiers peuvent pointer vers un fichier sur le
disque, un socket ou tout autre dispositif.
Typiquement, pour copier des donnes dun descripteur de fichier vers un autre, un programme alloue un tampon de taille fixe, 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 efficace 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 fichier 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 fichier source (0 correspond au dbut du fichier) et est mis
jour avec la position au sein du fichier 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 efficace de
copie de fichier. Lorsquil est invoqu avec deux noms de fichiers sur la ligne de commande, il
copie le contenu du premier dans le second. Il utilise fstat pour dterminer la taille, en octets,
du fichier source.
Listing 8.10 (copy.c) Copie de Fichier avec sendfile
1
2
3
4
5

# include
# include
# include
# include
# include

< fcntl .h >


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

8.13. SETITIMER : CRER DES TEMPORISATEURS


6
7

169

# include < sys / types .h >


# include < unistd .h >

8
9
10
11
12
13
14

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


{
int read_fd ;
int write_fd ;
struct stat stat_buf ;
off_t offset = 0;

15

/* 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 ) ;

16
17
18
19
20
21
22
23
24
25
26
27
28

return 0;

29
30

Lappel sendfile peut tre utilis dans de multiples occasions pour amliorer lefficacit des
copies. Un bon exemple est un serveur Web ou tout autre service rseau, qui envoie le contenu
dun fichier 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 fichier local
transfrer et crit son contenu sur un socket rseau. Utiliser sendfile peut acclrer cette
opration de faon significative. Dautres facteurs ont une influence sur lefficacit 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 diffrents de temporisateurs :
Si le code temporisateur est ITIMER_REAL, le processus reoit un signal SIGALRM aprs que
le temps spcifi 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
spcifi.
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 spcifiant

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 diffrent 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 configur pour expirer toutes les 250 millisecondes et
envoyer un signal SIGVTALRM.

Listing 8.11 (timer.c) Exemple dUtilisation dun Temporisateur


1
2
3
4

# include
# include
# include
# include

< signal .h >


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

5
6
7
8
9
10

void timer_handler ( int signum )


{
static int count = 0;
printf ( " le temporisateur a expir % d fois .\ n " , ++ count ) ;
}

11
12
13
14
15

int main ()
{
struct sigaction sa ;
struct itimerval timer ;

16

/* Installe timer_handler en tant que gestionnaire pour SIGVTALRM . */


memset (& sa , 0 , sizeof ( sa ) ) ;
sa . sa_handler = & timer_handler ;
sigaction ( SIGVTALRM , & sa , NULL ) ;

17
18
19
20
21

/* 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 ) ;

22
23
24
25
26
27
28
29
30
31

/* Perd du temps .
while (1) ;

32
33
34

*/

8.14. SYSINFO : RCUPRATION DE STATISTIQUES SYSTME

8.14

171

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 affiche des statistiques sur le systme courant.
Listing 8.12 (sysinfo.c) Affiche des Statistiques Systme
1
2
3
4

# include
# include
# include
# include

< linux / kernel .h >


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

5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

8.15

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;
}

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 qualifi de lordinateur ;


__domainname Nom de domaine de lordinateur.
Chacun de ces champs est une chane de caractres.
Le petit programme du Listing 8.13 affiche les numros de version du noyau et des informations sur le matriel.
Listing 8.13 (print-uname.c) Affiche le Numro de Version et des Infos Matrielles
1
2

# include < stdio .h >


# include < sys / utsname .h >

3
4
5
6
7
8
9
10
11

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


A

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 dpendent 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 spcifique larchitecture x86 de coder cette instruction C1 :


answer = sin ( angle ) ;

Notez que contrairement aux instructions assembleur classiques, les constructions asm vous
permettent de spcifier 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.
1

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.

173

174

9.1

CHAPITRE 9. CODE ASSEMBLEUR EN LIGNE

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 fichier source
du noyau situ dans /usr/src/linux/arch/i386/kernel/process.s offre un autre exemple
en utilisant hlt dans une boucle dinactivit. Consultez dautres fichiers 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 afin 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
significatif dun entier en utilisant le C ncessite une boucle ou des calculs en virgule flottante.
Beaucoup darchitectures, y compris le x86 disposent dune instruction assembleur (bsr) qui
calcule cette position. Nous illustrerons son utilisation dans la Section 9.3.5, 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.
2

Les amliorations au niveau des algorithmes ou des structures de donnes sont souvent plus efficaces dans la
rduction du temps dexcution dun programme que lutilisation dinstructions assembleur.

9.3. SYNTAXE ASSEMBLEUR AVANCE

175

La troisime section spcifie 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 modifie 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 spcifiez. 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 signification de
%%st(1) et dautres termes similaires dpend de larchitecture.
GCC se plaindra si vous spcifiez 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 fichiers den-tte cits prcdemment.

9.3.2

Sorties

La seconde section spcifie 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 vrifie que lexpression C
pour chaque oprande de sortie est effectivement une lvalue.
Les lettres spcifiant 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 fichier de configuration gcc/config/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 flottante
Registre en virgule flottante suprieur
Second registre en virgule flottante
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 spcifier jusqu 10 oprandes, numrotes de %0 %9 dans les
3

Vous devrez avoir une certaine familiarit avec le fonctionnement de GCC pour comprendre le contenu de ce
fichier.

9.3. SYNTAXE ASSEMBLEUR AVANCE

177

sections dentre et de sortie. Sil ny a pas doprandes de sortie ou de registre affects, 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. Spcifier 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 modifications est vide.

9.3.4

Dclaration des modifications

Si une instruction modifie les valeur dun ou plusieurs registres par effet de bord, spcifiez
ces registres dans la quatrime section de la structure asm. Par exemple, linstruction fucomip
modifie le registre de code condition, qui est dsign par cc. Les registres modifis sont dcrits
dans des chanes individuelles spares par des virgules. Si linstruction est susceptible de
modifier un emplacement mmoire arbitraire, spcifiez memory. En utilisant les informations
sur la modification 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 affecter le fonctionnement de votre programme.

9.3.5

Exemple

Larchitecture x86 dispose dinstructions qui dterminent les positions du bit non nul le plus
significatif ou le moins significatif dans un mot. Le processeur peut excuter ces instructions de
faon relativement efficace. 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 significatif de son
premier oprande et place cette position ( partir de 0 qui reprsente le bit le moins significatif)
dans le second. Pour placer la position du bit non nul le plus significatif 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 significatif.
Le Listing 9.2 effectue 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

# include < stdio .h >


# include < stdlib .h >

3
4
5
6
7
8
9
10

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 ;

11

/* 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 ;
}

12
13
14
15
16
17
18
19
20
21
22

return 0;

23
24

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 ;
/* Rpte l opration pour un nombre important de valeurs . */
for ( number = 1; number <= max ; ++ number ) {
/* Calcule la position du bit non nul le plus significatif
en utilisant l instruction assembleur bsrl . */
asm ( " bsrl %1 , %0 " : " = r " ( position ) : " r " ( number ) ) ;
result = position ;
}
return 0;
}

Compilons les deux versions avec les optimisations actives :

9.4. PROBLMES DOPTIMISATION

179

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


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

prsent, lanons-les en utilisant la commande time pour mesurer le temps dexcution. Il est
ncessaire de passer une valeur importante comme argument afin 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 figure 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 difficile 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 lefficacit 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 fichier 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 fichier pour
adapter le logiciel une autre architecture.
Par exemple, la plupart des instructions asm du code source Linux sont regroupes dans
les fichiers den-tte situs sous /usr/src/linux/include/asm/ et /usr/src/linux/include/
asm-i386/ et les fichiers source situs sous /usr/src/linux/arch/i386/ et /usr/src/linux/
drivers/.

180

CHAPITRE 9. CODE ASSEMBLEUR EN LIGNE

Chapitre 10

Scurit
U

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, modifier ou
supprimer des fichiers stocks sur la machine. Ou bien, un utilisateur pourrait lire, modifier ou
supprimer les fichier 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 afin 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 identifi 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 effectue 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 fichier 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 fichier que vous serez le seul pouvoir lire ou un rpertoire o
vous seul pourrez crer de nouveau fichier. Cela suffit 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 fichier que tous les managers
pourraient lire mais pas les employs ordinaires. Linux ne vous permet pas dassocier plusieurs
UID au mme fichier, vous ne pouvez donc pas crer une liste de tous les gens auxquels vous
souhaitez donner accs au fichier et les lier au fichier.
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 fichier 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 spcifier que les utilisateurs peuvent
accder un fichier 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 laffiche 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 fichier, 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. IDENTIFIANT DE GROUPE ET UTILISATEUR 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

Identifiant de groupe et utilisateur 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 confiance 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
fichiers 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
effectue une opration quelconque, nous voulons dire en ralit quun processus avec lUID
correspondant effectue 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 vrifie lUID et le GID associs au processus tentant dexcuter
lappel.
Dsormais, vous savez ce que signifie 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 dfinis 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 deffectuer
des oprations arithmtiques en les utilisant. Traitez les comme un moyen obscur de manipuler
les identifiants de groupe et dutilisateur.
Pour rcuprer lUID et le GID du processus courant, vous pouvez utiliser les fonctions
geteuid et getegid dfinies 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) Affiche les Identifiants dUtilisateur et de Groupe
1
2
3

# include < sys / types .h >


# include < unistd .h >
# include < stdio .h >

184

CHAPITRE 10. SCURIT


4
5
6
7
8
9
10

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 fichiers. En examinant la faon dont le systme associe les permissions avec chaque
fichier et en observant comment le noyau vrifie qui est autoris accder quels fichiers, le
concept didentifiant utilisateur et de groupe devrait devenir clair.
Chaque fichier a exactement un utilisateur propritaire et un groupe propritaire. Lorsque
vous crez un nouveau fichier, il est dtenu par lutilisateur et le groupe du processus crateur2 .
Les oprations de base sur les fichiers, 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 fichier, elles sont considres comme des oprations sur le rpertoire contenant le fichier.
Nous en parlerons plus loin). Si vous ne pouvez pas lire un fichier, Linux ne vous laissera pas en
examiner le contenu. Si vous ne pouvez pas y crire, vous ne pouvez pas modifier son contenu.
Si vous ne disposez pas des droits dexcution sur un fichier 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 fichier (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 dfinir
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 fichier 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
2

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 signifient 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 fichier. Le
premier tiret indique que le fichier est un fichier classique. Il serait remplac par d pour un
rpertoire ou dautres lettres dans le cas de fichiers 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 fichier. Les trois caractres daprs donnent les permissions des membres du groupe csl ; ses
membres ne peuvent que lire et excuter le fichier. Enfin, 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 fichier hello.
Voyons comment cela fonctionne. Tout dabord, essayons daccder au fichier 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 fichier, cest pourquoi cat choue ; nous ne pouvons pas crire
dans le fichier, cest pourquoi echo choue ; et nous navons pas le droit dexcuter le fichier,
cest pourquoi ./hello choue.
Les choses sarrangent si nous accdons au fichier 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 afficher le contenu du fichier et nous pouvons lexcuter (il sagit dun simple
script shell) mais nous ne pouvons toujours pas y crire.
Si nous sommes identifis en tant que propritaire (samuel), nous pouvons mme craser le
fichier :
% id
uid=502(samuel) gid=502(samuel) groups=502(samuel),503(csl)
% echo hi > hello
% cat hello
hi

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

186

CHAPITRE 10. SCURIT


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

Notez quil y a maintenant un x la fin de la premire chane de caractres. Loption o+x signifie
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 fichier. Cette fonction prend deux paramtres : le nom du fichier sur lequel
vous voulez des renseignements et ladresse dune structure de donnes renseigne avec des
informations sur le fichier. Consultez lAnnexe 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 fichier.
Listing 10.2 (stat-perm.c) Dterminer si le Propritaire a les Droits dcriture
1
2

# include < stdio .h >


# include < sys / stat .h >

3
4
5
6
7

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;

9
10
11
12
13
14
15
16

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 fichier.
Vous pouvez utiliser la fonction chmod pour modifier les bits de permission dun fichier
existant. Appelez chmod avec le nom du fichier dont vous voulez changer les permission et les
bits activer sous forme dun OU binaire entre les diffrentes constantes cites prcdemment.
Par exemple, lextrait suivant rend hello lisible et excutable par le propritaire mais dsactive
toutes les autres permissions associes hello :

10.3. PERMISSIONS DU SYSTME DE FICHIERS

187

chmod ( " hello " , S_IRUSR | S_IXUSR ) ;

Le mme systme de bits de permission sapplique aux rpertoires mais ces bits ont des significations diffrentes. Si un utilisateur est autoris lire le rpertoire, alors il peut consulter la
liste des fichiers prsents dans ce rpertoire. Avec les droits en criture, il est possible dajouter
ou de supprimer des fichiers. Notez quun utilisateur peut supprimer des fichiers dun rpertoire
sil a les droits en criture sur celui-ci, mme sil na pas la permission de modifier le fichier
quil supprime. Si un utilisateur a les droits dexcution sur un rpertoire, il a le droit dy entrer
et daccder aux fichiers quil contient. Sans droit dexcution sur un rpertoire, un utilisateur
ne peut pas accder aux fichiers quil contient, indpendamment des droits quil dtient sur les
fichiers eux-mmes.
Pour conclure, observons comment le noyau dcide dautoriser ou non un processus accder
un fichier donn. Tout dabord, il dtermine si lutilisateur demandant laccs est le propritaire
du fichier, 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 vrifi. Puis, le noyau contrle lopration effectue 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 fichier, 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, modifier 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 fichier que si
vous en tes le propritaire. Comme nous lavons dit prcdemment, vous pouvez normalement
supprimer un fichier si vous avez un accs en criture sur le rpertoire qui le contient, mme si
vous nen tes pas le propritaire. Lorsque le sticky bit est activ, vous devez toujours avoir les
4

Le noyau peut galement refuser laccs un fichier si un rpertoire dans le chemin du fichier 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 dfinies 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 .

188

CHAPITRE 10. SCURIT

droits en criture sur le rpertoire, mais vous devez en plus tre le propritaire du fichier que
vous voulez supprimer.
Sur un systme GNU/Linux seuls certains rpertoires ont le sticky bit actif. Par exemple, le
rpertoire /tmp, dans lequel tout utilisateur peut placer des fichiers temporaires, en fait partie.
Ce rpertoire est spcifiquement 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 fichiers dun autre, le sticky bit est donc activ pour ce rpertoire. De cette faon, seul le
propritaire (ou root, bien sr) peut supprimer le fichier.
Vous pouvez voir que le sticky bit est actif grce au t la fin 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 fichier 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 spcifi par dir_path et donner un accs complet tous
les utilisateur, effectuez lappel suivant :
chmod ( dir_path , S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX ) ;

10.4

Identifiants rels et effectifs

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 effectif et luser ID rel (bien sr, il
y a galement un group ID effectif 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 effectif. Par exemple, si un processus tente douvrir un fichier, le noyau vrifie luser
ID effectif pour dcider sil laisse le processus accder au fichier.
Les fonctions geteuid et getegid dcrites prcdemment renvoient luser ID et le group ID
effectifs. Les fonctions analogues getuid et getgid renvoient luser ID et le group ID rels.
Si le noyau ne soccupe que de luser ID effectif, il ne semble pas trs utile de faire la
distinction entre user ID rel et effectif. Cependant, il y a un cas trs important dans lequel le
user ID rel est pris en compte. Si vous voulez changer luser ID effectif dun processus en cours
dexcution, le noyau vrifie luser ID rel et luser ID effectif.
Avant dobserver comment vous pouvez changer luser ID effectif 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 fichier prsent

10.4. IDENTIFIANTS 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 fichier. Mais supposons maintenant quune
requte arrive de la part dun utilisateur particulier (disons mitchell) pour accder des fichiers
quelconques. Le processus serveur pourrait examiner avec attention les permissions associes
avec les fichiers concerns et essayer de dterminer si mitchell devrait tre autoris accder
ces fichiers. Mais cela signifierait 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 modifier temporairement luser ID effectif du
processus pour ne plus quil soit celui de root mais celui de mitchell puis dessayer deffectuer
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 effectif
original qui est root.
Les programmes qui authentifient les utilisateurs lorsquils essaient de se connecter tirent eux
aussi avantage de cette possibilit de modifier les user ID. Ces programmes sexcutent en tant
que root lorsque lutilisateur saisit un login et un mot de passe, le programme de connexion
vrifie 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 effectif afin de devenir cet utilisateur. Enfin, 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 effectif sont les siens.
La fonction utilise pour modifier 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 effectif demand. Par exemple, voici comment
vous changeriez les user ID rel et effectif :
setreuid ( geteuid () , getuid () ) ;

Bien sr, le noyau ne laisse pas nimporte quel processus changer son user ID. Si un processus
pouvait modifier son user ID effectif volont, alors, toute personne pourrait facilement prendre
lidentit de nimporte quel autre utilisateur simplement en changeant luser ID effectif de lun de
ses processus. Le systme laissera un processus disposant dun user ID effectif de 0 modifier 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 effectif est 0 peut faire tout ce quil lui plat).
Tout autre processus, par contre, ne peut faire que lune des actions suivantes :
dfinir son user ID effectif pour quil soit le mme que son user ID rel ;
dfinir son user ID rel pour quil soit le mme que son user ID effectif ;
changer les deux identifiants.
La premire possibilit serait utilise par notre systme de comptabilit lorsquil a termin
daccder au fichiers en tant que mitchell et veut redevenir root. La seconde par un programme
de connexion une fois quil a dfini luser ID effectif pour quil soit celui de lutilisateur qui sest
connect. Dfinir luser ID rel permet de sassurer que lutilisateur ne pourra jamais redevenir
root. Lchange des deux identifiants 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 modifier luser ID correspondant. Il existe galement une fonction raccourci appele seteuid.

190

CHAPITRE 10. SCURIT

Cette fonction dfinit luser ID effectif mais ne modifie 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 redfinissant ses
user ID rel et effectif.
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 naffiche que luser ID effectif, 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 effectif qui sont ceux de mitchell, setreuid ne nous permettra pas de les changer.
Lastuce est que le programme su est un programme setuid. Cela signifie que lorsquil
sexcute, son user ID effectif sera celui du propritaire du fichier et non luser ID du processus
qui effectue 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

# include < stdio .h >


# include < unistd .h >

3
4
5
6
7
8

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 effectif est celui du groupe propritaire du fichier. La plupart des programmes setuid sont galement des
programmes setgid.

10.5. AUTHENTIFIER LES UTILISATEURS

191

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

Notez que luser ID effectif 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 fichier 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 modifier luser ID effectif par le biais de ce mcanisme. Il sexcute
initialement avec un user ID effectif 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

Authentifier 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
authentification le programme su vrifie que vous tes celui que vous prtendez tre.
Si vous administrez un systme trs scuris, lauthentification 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 finalement 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 didentification. 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 didentification biomtriques sont utiliss.
Si vous crivez un programme qui doit authentifier ses utilisateurs, vous devriez permettre
ladministrateur systme de slectionner le moyen dauthentification quil souhaite utiliser.
GNU/Linux propose une bibliothque trs utile pour vous faciliter la tche. Ce mcanisme,
appel Pluggable Authentication Modules (Modules dAuthentification Enfichable), ou PAM, facilite lcriture dapplication qui authentifient 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

# include < security / pam_appl .h >


# include < security / pam_misc .h >
# include < stdio .h >

4
5
6
7
8

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;

10
11
12
13
14
15
16
17
18
19
20
21
22
23

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 affiche 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 identifie 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 configure explicitement le systme pour fonctionner
avec votre service. Revenons notre exemple, nous utilisons le service su, qui indique que notre
programme authentifie 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 configurer PAM
correctement pour votre application.
Le second argument est le nom de lutilisateur que vous voulez authentifier. Dans cet exemple,
nous utilisons la valeur de la variable denvironnement USER (en thorie, il sagit du nom dutilisateur correspondant luser ID effectif du processus courant, mais ce nest pas toujours le cas).
Dans la plupart des programmes rels, vous afficheriez 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 spcifier diverses options ; la valeur 0 demande lutilisation des valeurs par dfaut. La valeur de
retour de cette fonction indique le rsultat de lauthentification.
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 suffisantes 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

10.6

CHAPITRE 10. SCURIT

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 fichiers (ou pourrait lire des fichiers) 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 fin de la fonction en cours. Ainsi, si
vous placez le code que vous voulez excuter quelque part en mmoire et que vous modifiez
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 fichiers.
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 fixe 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

195

nom de 8 caractres. Il y a donc suffisamment de place. */


char username [32];

/* 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 identifiants 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 suffisamment 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 suffisamment grand pour
contenir la ligne et vous le renvoie. Vous ne devez pas oublier dappeler free pour librer le
tampon afin 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 vrifier 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 fichiers avec des noms prdictibles,
typiquement dans le rpertoire /tmp. Supposons que votre programme prog, qui sexcute avec
les droits root, cre toujours un fichier 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 fichier du systme. Lorsque votre programme tente de crer le fichier, lappel
systme open nchouera pas. Cependant, les donnes que vous crirez niront pas vers /tmp/prog
mais seront crites dans le fichier 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 fichier en premier
gagne.
Cette attaque est gnralement utilise pour dtruire des lments importants du systme
de fichiers. En crant les liens appropris, lattaquant peut utiliser un programme sexcutant en
tant que root croyant crire dans un fichier temporaire pour craser un fichier systme important.
Par exemple, en crant un lien symbolique vers /etc/passwd, lattaquant peut effacer 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 fichier. Par
exemple, vous pourriez utiliser /dev/random pour injecter une partie alatoire dans le nom du
fichier. Cela complique bien sr la tche de lattaquant pour deviner le nom du fichier, 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 fichier 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 fiable sur un systme de fichier 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 fichier avec loption O_EXCL aprs avoir dtermin un nom suffisamment 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 fichier (lstat est prsent
dans la Section B.2, stat ). La fonction lstat est similaire stat, except que si le fichier est
lien symbolique, lstat vous donne des informations sur ce lien et non sur le fichier vers lequel
il pointe. Si lstat vous indique que votre nouveau fichier est un fichier 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 fichier 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 dfini linterface de faon ce quelle soit complexe utiliser dans un programme
rel. La vrification 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-file.c) Crer un Fichier Temporaire
1
2
3
4

# include
# include
# include
# include

< fcntl .h >


< stdlib .h >
< sys / stat .h >
< unistd .h >

5
6
7
8

/* 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 .

9
10

Renvoie -1 si le fichier temporaire ne peut pas tre cr .

*/

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

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 ;

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

/* 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;
}

41
42
43
44
45
46
47

/* 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 ) ;

48
49
50
51

/* Tente d ouvrir le fichier . */


fd = open ( filename ,
/* Nous utilisons O_EXECL ,

198

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;

52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

return fd ;

76
77

Cette fonction appelle open pour crer le fichier puis appelle lstat quelques lignes plus loin
pour sassurer que le fichier nest pas un lien symbolique. Si vous rflchissez attentivement, vous
raliserez quil semble y avoir une condition de concurrence critique dans ce cas. En effet, un
attaquant pourrait supprimer le fichier 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 fichier ouvert pointant sur le nouveau fichier,
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 fichiers de ce rpertoire. Bien sr, root peut
toujours supprimer des fichiers, 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 suffisamment 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 fictif 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 figure 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

# include < stdio .h >


# include < stdlib .h >

3
4
5

/* Renvoie une valeur diffrente de 0 si et seulement si WORD


figure dans / usr / dict / words . */

6
7
8
9
10
11

int grep_for_word ( const char * word )


{
size_t length ;
char * buffer ;
int exit_code ;

12
13
14
15
16
17
18

/* 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 ) ;

19

/* 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;

20
21
22
23
24
25
26

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
10

Si vous ne connaissez pas grep, vous devriez consulter les pages de manuel. Cest un programme incoyablement
utile.

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 inoffensive de grep, mais la seconde supprime
tous les fichiers du systme ! Mme si le serveur ne sexcute pas en tant que root, tous les fichiers
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 fils 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 vrification 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.

Chapitre 11

Application GNU/Linux
dIllustration
Ce chapitre est celui ou tout simbrique. Nous allons spcifier et implanter un
programme GNU/Linux complet qui fait appel la plupart des techniques dcrites dans ce livre.
Ce programme fournit des informations sur le systme sur lequel il sexcute par le biais dune
interface Web.
Lapplication est une application pratique de certaines des mthodes que nous avons dcrites
dans le cadre de la programmation GNU/Linux et illustres dans des programmes plus courts.
Celui-ci se rapproche plus du "monde rel", contrairement la plupart des listings des chapitres
prcdents. Il peut vous servir de tremplin pour crire vos propres logiciels GNU/Linux.

11.1

Prsentation

Ce programme fait partie dun systme de surveillance pour un systme GNU/Linux en cours
dxcution. Voici ses fonctionalits :
Il incorpore un serveur Web minimal. Les clients locaux ou distants accdent aux informations en demandant des pages au server via le protocole HTTP ;
Il nutilise pas de pages HTML statique mais les gnre la vole par le biais de modules,
chacun deux produisant une page informant sur un aspect de ltat du systme ;
Les modules ne sont pas lis statiquement avec lexcutable serveur. Ils sont chargs
dynamiquement partir de bibliothques partages. Des modules peuvent tre ajouts,
supprims ou remplacs alors que le serveur est en cours dexcution ;
Chaque connexion est traite dans un processus fils. Cela permet au serveur de rester
ractif mme lorsque certaines requtes ncessitent un temps de traitement important et
cela protge le serveur contre dventuels problmes dans les modules ;
Le serveur ne ncessite pas les privilges superutilisateur pour sexcuter (tant quil nutilise pas un port privilgi). Cependant, cela limite les informations pouvant tre collectes.
Nous prsentons quatre modules illustrant la faon dont ils peuvent tre crits. Ils exploitent
plus en profondeur certaines techniques de collecte dinformations prsentes prcdemment dans
201

202

CHAPITRE 11. APPLICATION GNU/LINUX DILLUSTRATION

ce livre. Le module time utilise lappel systme gettimeofday, le module issue exploite les E/S
de bas niveau et lappel systme sendfile, diskfree illustre lutilisation de fork, exec et dup2
en lanant une commande dans un processus fils, enfin, le module processes utilise le systme
de fichiers /proc et divers appels systme.

11.1.1

Limitations

Ce programme dispose dun nombre important de fonctionalit que vous pouvez trouver dans
une application relle, comme lanalyse de la ligne de commande et la vrification derreurs.
Mais, dans le mme temps, nous avons simplifier les choses afin damliorer la lisibilit et nous
concentrer sur les sujets spcifiques GNU/Linux traits tout au long de ce livre. Gardez en
tte les limitations suivantes lors de votre lecture du code :
Nous nessayons pas dimplanter le protocole HTTP dans son intgralit. Au contraire,
nous en implmentons juste assez pour que le serveur puisse interagir avec des clients
Web. Un programme rel fournirait une implantation plus aboutie ou sinterfacerait avec
lun des nombreux serveurs Web1 disponibles ;
De mme, nous ne visons pas une compatibilit complte avec les spcifications HTML
(consultez http://www.w3.org/MarkUppourplusdinformations. Nous gnrons un HTML
simple pris en charge par les navigateurs les plus rpandus ;
Le serveur nest pas conu pour maximiser les performances ou minimiser lutilisation
des ressources. En particulier, nous avons intentionnellement omis une partie du code
de configuration du rseau auquel vous pourriez vous attendre dans un serveur Web. Ce
sujet sort du cadre de ce livre. Consultez une des nombreuses excellentes rfrences sur
le dveloppement dapplication rseau, comme Unix Network Programming, Volume 1 :
Networking APIx Sockets and XTI de W. Richard Stevens (Prentice Hall, 1997) pour
plus dinformations.
Nous ne tentons pas de rguler lutilisation des ressources (nombre de processus, consommation mmoire, etc.) par le serveur ou ses modules. Beaucoup dimplantations de serveurs
Web rpondent aux connexions en utilisant un nombre fixe de processus plutt que den
crer un nouveau chaque connexion ;
Le serveur charge la bibliothque partage correspondant un module chaque fois quune
requte y faisant appel est reue puis le dcharge immdiatement une fois quelle a t
traite. Une implantation plus efficace utiliserait certainement un cache pour les modules
chargs.

11.2

Implantation

Except lorsquils sont trs concis, les programmes crits en C demande une organisation
soigneuse afin dassurer la modularit et la maintenabilit du code. Ce programme est divis en
quatre fichiers source principaux.
1

Le serveur Web open source le plus populaire sous GNU/Linux est le serveur Apache, disponible sur http:
//www.apache.org

11.2. IMPLANTATION

203

HTTP
LHypertext Transport Protocol (HTTP) est utilis pour la communication entre clients et
serveurs Web. Le client se connecte au serveur via un port connu (gnralement le port 80, mais
il peut sagir de nimporte quel port). Les requtes et les en-ttes HTTP sont au format texte.
Une fois connect, le client envoie une requte au serveur. Une requte classique est GET /page
HTTP/1.0. La mthode GET indique que le client demande au serveur lenvoi dune page. Le
second lment est le chemin de la page sur le serveur et le troisime est constitu du protocole
et de sa version. Les lignes suivantes sont constitues den-ttes, dont le format est similaire
celui des en-ttes mail, qui contiennent des informations supplmentaires sur le client. Len-tte
se termine par une ligne vide.
Le serveur renvoie une rponse indiquant le rsultat du traitement de la requte. Une rponse
classique est HTTP/1.0 200 OK. Le premier lment est la version du protocol et les deux suivants
indiquant le rsultat ; dans ce cas, 200 signifie que la requte a t correctement traite. Les
lignes suivantes contiennent des en-ttes, l encore similaires aux en-ttes mail. Len-tte se
termine par une ligne blanche. Le serveur peut envoyer des donnes quelconques pour satisfaire
la requte.
Typiquement, le serveur rpond la demande dune page en envoyant sa source HTML. Dans
ce cas, les en-tte de rponse inclueront Content-type: text/html indiquant que le rsultat est
au format HTML. La source HTML du document suit immdiatement len-tte.
Consultez la spcification HTTP disponible sur http://www.w3.org/Protocols/ pour plus
dinformations.

204

CHAPITRE 11. APPLICATION GNU/LINUX DILLUSTRATION

Chaque fichier exporte des fonctions ou des variables qui peuvent accder depuis dautres
endroits du programme. Pour des raisons de simplicit, toutes les fonctions et variables exportes
sont dfinies dans un unique fichier den-tte, server.h (cf. Listing 11.1), qui est inclus par les
autres fichiers. Les fonctions destines ntre utilises que dans une seule unit de compilation
sont dclares comme static et ne sont donc pas mentionnes dans server.h.
Listing 11.1 (server.h) Dclarations des Fonctions et Variables
1
2

# ifndef SERVER_H
# define SERVER_H

3
4
5

# include < netinet / in .h >


# include < sys / types .h >

6
7

/* ** Symboles definis dans common . c .

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

8
9
10

/* Nom du programme . */
extern const char * program_name ;

11
12
13

/* Mode verbeux si diffrent de zro .


extern int verbose ;

*/

14
15
16

/* Identique malloc , sauf que le programme se termine en cas d chec .


extern void * xmalloc ( size_t size ) ;

*/

17
18
19

/* Identique realloc , sauf que le programme se termine en cas d chec .


extern void * xrealloc ( void * ptr , size_t size ) ;

*/

20
21
22

/* Identique strdup , sauf que le programme se termine en cas d chec .


extern char * xstrdup ( const char * s ) ;

*/

23
24
25
26

/* Affiche un message d erreur suite l appel de OPERATION , utilise


la valeur de errno et termine le programme . */
extern void system_error ( const char * operation ) ;

27
28
29
30

/* Affiche un message d erreur lors d un chec dont la cause est CAUSE ,


le MESSAGE descriptif est affich et le programme termin . */
extern void error ( const char * cause , const char * message ) ;

31
32
33
34
35

/* Renvoie le rpertoire contenant l excutable du programme .


La valeur de retour est un tampon mmoir que l appelant doit
librer . Cette fonction appelle abort en cas d chec . */
extern char * g e t _ s e l f _ e x e c u t a b l e _ d i r e c t o r y () ;

36
37
38

/* ** Symboles dfinis dans module . c

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

39
40
41
42
43
44
45
46
47
48

/* Instance de module charg . */


struct server_module {
/* Rfrence sur la bibliothque partage correspondant au module . */
void * handle ;
/* Nom du module . */
const char * name ;
/* Fonction gnrant les rsultats au format HTML pour le module . */
void (* g e n e r a t e _ f u n c t i o n ) ( int ) ;
};

49
50
51
52

/* Rprtoire partir duquel sont chargs les modules .


extern char * module_dir ;

*/

11.2. IMPLANTATION
53
54
55
56

205

/* Tente de charger un module dont le nom est MODULE_PATH . Si un module


existe cet emplacement , charge le module et renvoie une structure
server_module le reprsentant . Sinon , renvoie NULL . */
extern struct server_module * module_open ( const char * module_path ) ;

57
58
59

/* Dcharge un module et libre l objet MODULE . */


extern void module_close ( struct server_module * module ) ;

60
61
62

/* ** Symboles definis dans server . c .

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

63
64
65

/* Lance le serveur sur l adresse LOCAL_ADDRESS et le port PORT . */


extern void server_run ( struct in_addr local_address , uint16_t port ) ;

66
67

11.2.1

# endif

/* SERVER_H */

Fonctions gnriques

common.c (cf. Listing 11.2 contient des fonctions gnriques utilises dans tous le programme.
Listing 11.2 (common.c) Fonctions Gnriques
1
2
3
4
5

# include
# include
# include
# include
# include

< errno .h >


< stdio .h >
< stdlib .h >
< string .h >
< unistd .h >

6
7

# include " server . h "

8
9

const char * program_name ;

10
11

int verbose ;

12
13
14
15
16
17
18
19
20
21

void * xmalloc ( size_t size )


{
void * ptr = malloc ( size ) ;
/* Termine le programme si l allocation choue . */
if ( ptr == NULL )
abort () ;
else
return ptr ;
}

22
23
24
25
26
27
28
29
30
31

void * xrealloc ( void * ptr , size_t size )


{
ptr = realloc ( ptr , size ) ;
/* Termine le programme si l allocation choue . */
if ( ptr == NULL )
abort () ;
else
return ptr ;
}

32
33
34
35
36
37
38

char * xstrdup ( const char * s )


{
char * copy = strdup ( s ) ;
/* Termine le programme si l allocation choue . */
if ( copy == NULL )
abort () ;

206

CHAPITRE 11. APPLICATION GNU/LINUX DILLUSTRATION


else
return copy ;

39
40
41

42
43
44
45
46
47

void system_error ( const char * operation )


{
/* Gnre le message d erreur correspondant errno .
error ( operation , strerror ( errno ) ) ;
}

*/

48
49
50
51
52
53
54
55

void error ( const char * cause , const char * message )


{
/* Affiche un message d erreur sur stderr . */
fprintf ( stderr , " % s : error : (% s ) % s \ n " , program_name , cause , message ) ;
/* Termine le programme . */
exit (1) ;
}

56
57
58
59
60
61
62
63

char * g e t _ s e l f _ e x e c u t a b l e _ d i r e c t o r y ()
{
int rval ;
char link_target [1024];
char * last_slash ;
size_t result_length ;
char * result ;

64

/* Lit la cible du lien symbolique / proc / self / exe . */


rval = readlink ( " / proc / self / exe " , link_target , sizeof ( link_target ) ) ;
if ( rval == -1)
/* L appel readlink a chou , termin . */
abort () ;
else
/* Ajoute un octet nul la fin de la chane . */
link_target [ rval ] = \0 ;
/* Nous voulons supprimer le nom du fichier excutable afin d obtenir
le nom du rpertoire qui le contient . On recherche le slash le plus droite .
*/
last_slash = strrchr ( link_target , / ) ;
if ( last_slash == NULL || last_slash == link_target )
/* Quelque chose d inattendu s est produit . */
abort () ;
/* Alloue un tampon pour accueillir le chemin obtenu . */
result_length = last_slash - link_target ;
result = ( char *) xmalloc ( result_length + 1) ;
/* Copie le rsultat . */
strncpy ( result , link_target , result_length ) ;
result [ result_length ] = \0 ;
return result ;

65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86

Vous pouvez utiliser ces fonctions dans dautres programmes ; le contenu de ce fichier peut
tre inclus dans une bibliothque constituant une base de code pour un certain nombre de
projets :
xmalloc, xrealloc et xstrdup sont des versions avec vrification derreur des fonctions
malloc, realloc et strdup, respectivement. Contrairement aux fonctions standard qui renvoient un pointeur NULL lorsque lallocation choue, ces fonctions terminent immdiatement
le programme lorsquil ny a pas assez de mmoire disponible.
La dtection prcoce de lchec des allocations mmoire est une bonne ide. Si ce nest pas

11.2. IMPLANTATION

207

fait, les allocations ayant chou introduisent des pointeurs NULL des endroits inattendus.
Comme les checs dallocation ne sont pas simples reproduire, il peut tre difficile de
rparer de tels problmes. Les checs dallocaltion sont gnralement catastrophiques,
aussi, larrt du programme est souvent une raction approprie ;
La fonction error sert signaler une erreur fatale. Elle affiche un message sur stderr et
termine le programme. Pour les erreurs causes par des appels systmes ou des fonctions
de bibliothques, system_error gnre une partie du message derreur partir de la valeur
de errno (reportez-vous la Section 2.2.3, Codes derreur des appels systme , du
Chapitre 2, crire des logiciels GNU/Linux de qualit ) ;
get_self_executable_directory dtermine le rpertoire contenant le fichier excutable du
processus courant. Ce chemin peut tre utilis pour localiser dautres composants du programmes installs au mme endroit. Cette fonction utilise le lien symbolique /proc/self/exe
du systme de fichiers /proc (cf. Section 7.2.1, namerefsec :procself du Chapitre 7, Le
systme de fichiers /proc ).
common.c dfinit galement deux variables globales trs utiles :
La valeur de program_name est le nom du programme en cours dexcution, dtermin
partir de la liste darguments (cf. Section 2.1.1, La liste darguments , dans le Chapitre 2). Lorsque le programme est invoqu partir dun shell, il sagit du chemin et du
nom de programme saisis par lutilisateur ;
La variable verbose est diffrente de zro si le programme sexcute en mode verbeux.
Dans ce cas, diverses portions du programme affichent des messages sur stdout.

11.2.2

Chargement de modules serveur

module.c (cf. Listing 11.3) fournit limplmentation des modules serveurs pouvant tre
chargs dynamiquement. Un module serveur charg est reprsent par une instance de struct
server_module dont la dfinition se trouve dans server.h.
Listing 11.3 (module.c) Chargement et Dchargement de Module Serveur
1
2
3
4

# include
# include
# include
# include

< dlfcn .h >


< stdlib .h >
< stdio .h >
< string .h >

5
6

# include " server . h "

7
8

char * module_dir ;

9
10
11
12
13
14
15

struct server_module * module_open ( const char * module_name )


{
char * module_path ;
void * handle ;
void (* m o d u l e_ g e n e r a te ) ( int ) ;
struct server_module * module ;

16
17
18
19
20
21

/* Dtermine le chemin absolu de la bibliothque partage correspondant


au module que nous essayons de charger . */
module_path =
( char *) xmalloc ( strlen ( module_dir ) + strlen ( module_name ) + 2) ;
sprintf ( module_path , " % s /% s " , module_dir , module_name ) ;

208

CHAPITRE 11. APPLICATION GNU/LINUX DILLUSTRATION


22

/* Tente d ouvrir la bibliothque partage MODULE_PATH . */


handle = dlopen ( module_path , RTLD_NOW ) ;
free ( module_path ) ;
if ( handle == NULL ) {
/* chec ; le chemin n existe pas ou il ne s agit pas
d une bibliothque partage . */
return NULL ;
}

23
24
25
26
27
28
29
30
31

/* Charge le symbole m o d ul e _ g e n e ra t e depuis la bibliothque partage . */


m o d ul e _ g e n e ra t e = ( void (*) ( int ) ) dlsym ( handle , " m o d u le _ g e n e ra t e " ) ;
/* S assure que le symbole existe . */
if ( m o d ul e _ g e n e ra t e == NULL ) {
/* Le symbole n a pas t trouv . Bien que l on soit en prsence d une
bibliothque partage , il ne s agit probablement pas d un module
serveur . Ferme et indique l chec de l opration . */
dlclose ( handle ) ;
return NULL ;
}
/* Alloue et initialise un objet server_module . */
module = ( struct server_module *) xmalloc ( sizeof ( struct server_module ) ) ;
module - > handle = handle ;
module - > name = xstrdup ( module_name ) ;
module - > g e n e r a t e _ f u n c t i o n = m o d u le _ g e n e ra t e ;
/* Le renvoie , indiquant que tout s est droul correctement . */
return module ;

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

void module_close ( struct server_module * module )


{
/* Ferme la bibliothque partage . */
dlclose ( module - > handle ) ;
/* Libre la mmoire alloue pour le nom du module . */
free (( char *) module - > name ) ;
/* Libre la mmoire alloue pour le module . */
free ( module ) ;
}

Chaque module est un fichier de bibliothque partage (reportez-vous la Section 2.3.2,


Bibliothques partages du Chapitre 2) et doit dfinir et exporter une fonction appele
module_generate. Cette fonction gnre une page HTML et lcrit vers un descripteur de fichiers
correspondant un socket client.
module.c contient deux fonctions :
module_open tente de charger un module serveur dont le nom est connu. Le nom se termine
normalement par lextension .so car les modules sont implants sous forme de bibliothques partages. La fonction ouvre la bibliothque partage avec dlopen et recherche
un symbole nomm module_generate partir de cette bibliothque en utilisant dlsym
(consultez la Section 2.3.6, Chargement et dchargement dynamiques , du Chapitre 2).
Si la bibliothque ne peut pas tre ouverte ou si module_generate nest pas export par
celle-ci, lappel choue et module_open renvoie un pointeur NULL. Si tout ce passe bien, elle
alloue et renvoie un objet module ;
module_close ferme la bibliothque partage correspondant au module serveur et libre
lobjet struct server_module.

11.2. IMPLANTATION

209

module.c dfinit galement une variable globale module_dir. Il sagit du chemin du rpertoire dans lequel module_open recherche les bibliothques partages correspondant aux modules
serveur.

11.2.3

Le serveur

server.c (cf. Listing 11.4) est limplantation du serveur HTTP minimal.


Listing 11.4 (server.c) Implantation du Serveur
1
2
3
4
5
6
7
8
9
10
11

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

< arpa / inet .h >


< assert .h >
< errno .h >
< netinet / in .h >
< signal .h >
< stdio .h >
< string .h >
< sys / types .h >
< sys / socket .h >
< sys / wait .h >
< unistd .h >

12
13

# include " server . h "

14
15

/* En - tte et rponse HTTP dans le cas d une requte traite avec succs . */

16
17
18
19
20

static char * ok_response =


" HTTP /1.0 200 OK \ n "
" Content - type : text / html \ n "
"\n";

21
22
23

/* Rponse , en - tte et corps de la rponse HTTP indiquant que nous n avons


pas compris la requte . */

24
25
26
27
28
29
30
31
32
33
34

static char * b a d _ r e q u e s t _ r e s p o n s e =
" HTTP /1.0 400 Bad Request \ n "
" Content - type : text / html \ n "
"\n"
" < html >\ n "
" < body >\ n "
" <h1 > Bad Request </ h1 >\ n "
" <p > This server did not understand your request . </p >\ n "
" </ body >\ n "
" </ html >\ n " ;

35
36
37

/* Rponse , en - tte et modle de corps indiquant que le document


demand n a pas t trouv . */

38
39
40
41
42
43
44
45
46
47
48

static char * n o t _ f o u n d _ r e s p o n s e _ t e m p l a t e =
" HTTP /1.0 404 Not Found \ n "
" Content - type : text / html \ n "
"\n"
" < html >\ n "
" < body >\ n "
" <h1 > Not Found </ h1 >\ n "
" <p > The requested URL % s was not found on this server . </p >\ n "
" </ body >\ n "
" </ html >\ n " ;

49
50

/* Rponse , en - tte et modle de corps indiquant que la mthode

210

CHAPITRE 11. APPLICATION GNU/LINUX DILLUSTRATION


n a pas t comprise . */

51
52
53
54
55
56
57
58
59
60
61
62

static char * b a d _ m e t h o d _ r e s p o n s e _ t e m p l a t e =
" HTTP /1.0 501 Method Not Implemented \ n "
" Content - type : text / html \ n "
"\n"
" < html >\ n "
" < body >\ n "
" <h1 > Method Not Implemented </ h1 >\ n "
" <p > The method % s is not implemented by this server . </p >\ n "
" </ body >\ n "
" </ html >\ n " ;

63
64
65

/* Gestionnaire de SIGCHLD , libre les ressources occupes par les


processus fils qui se sont termins . */

66
67
68
69
70
71

static void c l e a n _ u p _ c h i l d _ p r o c e s s ( int signal_number )


{
int status ;
wait (& status ) ;
}

72
73
74
75
76
77

/* Traite une requte HTTP " GET " pour PAGE et envoie les rsultats
vers le descripteur de fichier CONNECTION_FD . */
static void handle_get ( int connection_fd , const char * page )
{
struct server_module * module = NULL ;

78
79
80
81
82
83

/* S assure que la page demande dbute avec un slash et n en


contient pas d autre - nous ne prenons pas en charge les
sous - rpertoires . */
if (* page == / && strchr ( page + 1 , / ) == NULL ) {
char m o d u l e _ f i l e _ n a m e [64];

84

/* Le nom de la page a l air correct . Nous construisons le nom du


module en ajoutant ". so " au nom de la page . */
snprintf ( module_file_name , sizeof ( m o d u l e _ f i l e _ n a m e ) ,
" % s . so " , page + 1) ;
/* Tente d ouvrir le module . */
module = module_open ( m o d u l e _ f i l e _ n a m e ) ;

85
86
87
88
89
90
91

92
93
94
95
96
97

if ( module == NULL ) {
/* Soit la page demande tait malforme , soit nous n avons pas pu
ouvrir le module demand . Quoi qu il en soit , nous renvoyons
une rponse HTTP 404 , Introuvable . */
char response [1024];

98
99
100
101
102
103
104
105

/* Gnre le message de rponse . */


snprintf ( response , sizeof ( response ) , not_ found_ respo nse_te mplat e , page ) ;
/* L envoie au client . */
write ( connection_fd , response , strlen ( response ) ) ;
}
else {
/* Le module demand a t correctement charg .

*/

106
107
108
109
110
111
112

/* Envoie la rponse HTTP indiquant que tout s est bien pass


ainsi que l en - tte HTTP pour une page HTML . */
write ( connection_fd , ok_response , strlen ( ok_response ) ) ;
/* Invoque le module , il gnrera la sortie HTML et l enverra
au descripteur de fichier client . */
(* module - > g e n e r a t e _ f u n c t i o n ) ( connection_fd ) ;

11.2. IMPLANTATION
/* Nous en avons termin avec le module . */
module_close ( module ) ;

113
114

115
116

211

117
118

/* Prend en charge une connexion client sur le descripteur de fichier


CONNECTION_FD . */

119
120
121
122
123

static void h a n d l e _ c o n n e c t i o n ( int connection_fd )


{
char buffer [256];
ssize_t bytes_read ;

124
125
126
127
128
129
130

/* Lis les donnes envoyes par le client . */


bytes_read = read ( connection_fd , buffer , sizeof ( buffer ) - 1) ;
if ( bytes_read > 0) {
char method [ sizeof ( buffer ) ];
char url [ sizeof ( buffer ) ];
char protocol [ sizeof ( buffer ) ];

131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164

/* Les donnes ont t lues correctement . Ajoute un octet nul la


fin du tampon pour pouvoir l utiliser avec les fonctions de manipulation
de chane classiques . */
buffer [ bytes_read ] = \0 ;
/* La premire ligne qu envoie le client correspond la requte HTTP compose
d une mthode , de la page demande et de la version du protocole . */
sscanf ( buffer , " % s % s % s " , method , url , protocol ) ;
/* Le client peut envoyer divers en - ttes d information la suite de la
requte .
Pour notre implantation du protocole HTTP , nous n en tenons pas compte .
Cependant , nous devons lire toutes les donnes envoyes par le client .
Nous continuons donc lire les donnes jusqu la fin de l en - tte
qui se termine par une ligne blanche . Le protocole HTTP dfinit
la combinaison CR / LF comme dlimiteur de ligne . */
while ( strstr ( buffer , " \ r \ n \ r \ n " ) == NULL )
bytes_read = read ( connection_fd , buffer , sizeof ( buffer ) ) ;
/* S assure que la dernire lecture n a pas chou . Si c est le cas ,
il y a un problme avec la connexion , abandonne . */
if ( bytes_read == -1) {
close ( connection_fd ) ;
return ;
}
/* Vrifie le champ protocole . Nous ne comprenons que les versions 1.0
et 1.1. */
if ( strcmp ( protocol , " HTTP /1.0 " ) && strcmp ( protocol , " HTTP /1.1 " ) ) {
/* Nous ne comprenons pas ce protocole . Renvoie une indication
de requte incorrecte . */
write ( connection_fd , bad_request_response ,
sizeof ( b a d _ r e q u e s t _ r e s p o n s e ) ) ;
}
else if ( strcmp ( method , " GET " ) ) {
/* Ce serveur n implante que la mthode GET . Le client en a spcifi une
autre .
Indique un chec . */
char response [1024];

165
166
167
168
169
170
171

snprintf ( response , sizeof ( response ) ,


ba d_ me tho d_ res po ns e_t em pl ate , method ) ;
write ( connection_fd , response , strlen ( response ) ) ;
}
else
/* La requte est valide , nous la traitons .

*/

212

CHAPITRE 11. APPLICATION GNU/LINUX DILLUSTRATION


handle_get ( connection_fd , url ) ;
}
else if ( bytes_read == 0)
/* Le client a ferm la connexion avant d envoyer des donnes .
Rien faire . */
;
else
/* L appel read a chou . */
system_error ( " read " ) ;

172
173
174
175
176
177
178
179
180
181

182
183
184
185
186
187
188
189
190
191
192
193

void server_run ( struct in_addr local_address , uint16_t port )


{
struct sockaddr_in so ck et_ ad dr ess ;
int rval ;
struct sigaction sig ch ld _ac ti on ;
int server_socket ;
/* Dfinit un gestionnaire pour SIGCHLD qui libre les ressources des
processus fils termins . */
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 ) ;

194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212

/* Cre un socket TCP . */


server_socket = socket ( PF_INET , SOCK_STREAM , 0) ;
if ( server_socket == -1)
system_error ( " socket " ) ;
/* Construit une structure socket address pour recevoir l adresse
locale sur laquelle nous voulons attendre les connexions . */
memset (& socket_address , 0 , sizeof ( soc ke t_ add re ss ) ) ;
so ck et _ad dr ess . sin_family = AF_INET ;
so ck et _ad dr ess . sin_port = port ;
so ck et _ad dr ess . sin_addr = local_address ;
/* Lie le socket cette adresse . */
rval = bind ( server_socket , & socket_address , sizeof ( soc ke t_ add re ss ) ) ;
if ( rval != 0)
system_error ( " bind " ) ;
/* Demande au socket d accepter les connexions . */
rval = listen ( server_socket , 10) ;
if ( rval != 0)
system_error ( " listen " ) ;

213
214
215
216
217

if ( verbose ) {
/* En mode verbeux , affiche l adresse locale et le numro du port
sur lesquels nous coutons . */
socklen_t add re ss _le ng th ;

218

/* Dtermine l adresse locale du socket . */


ad dr es s_l en gth = sizeof ( soc ke t_ add re ss ) ;
rval = getsockname ( server_socket , & socket_address , & ad dr es s_l en gt h ) ;
assert ( rval == 0) ;
/* Affiche un message . Le numro du port doit tre converti partir de
l ordre des octets rseau ( big endian ) vers l ordre des octets de l hte .
*/
printf ( " server listening on % s :% d \ n " ,
inet_ntoa ( s ock et _addr ess . sin_addr ) ,
( int ) ntohs ( soc ket_ addre ss . sin_port ) ) ;

219
220
221
222
223
224
225
226
227
228

229
230
231
232

/* Boucle indfiniment , en attente de connexions .


while (1) {
struct sockaddr_in re mo te_ ad dre ss ;

*/

11.2. IMPLANTATION

213

socklen_t add re ss _le ng th ;


int connection ;
pid_t child_pid ;

233
234
235
236

/* Accepte une connexion . Cet appel est bloquant jusqu ce qu une


connexion arrive . */
ad dr es s_l en gt h = sizeof ( rem ot e_ add re ss ) ;
connection = accept ( server_socket , & remote_address , & ad dr ess _l en gth ) ;
if ( connection == -1) {
/* L appel accept a chou . */
if ( errno == EINTR )
/* L appel a t interrompu par un signal . Recommence . */
continue ;
else
/* Quelque chose est arriv . */
system_error ( " accept " ) ;
}

237
238
239
240
241
242
243
244
245
246
247
248
249
250

/* Nous avons reu une connexion . Affiche un message si nous sommes


en mode verbeux . */
if ( verbose ) {
socklen_t add re ss _le ng th ;

251
252
253
254
255

/* Rcupre l addresse distante de la connexion . */


ad dr es s_l en gth = sizeof ( soc ke t_ add re ss ) ;
rval = getpeername ( connection , & socket_address , & a dd re ss_ le ng th ) ;
assert ( rval == 0) ;
/* Affiche un message . */
printf ( " connection accepted from % s \ n " ,
inet_ntoa ( s ock et _ad dr es s . sin_addr ) ) ;

256
257
258
259
260
261
262

263
264

/* Cre un processus fils pour prendre en charge la connexion . */


child_pid = fork () ;
if ( child_pid == 0) {
/* Nous sommes dans le processus fils . Il n a pas besoin de stdin ou stdout ,
nous les fermons donc . */
close ( STDIN_FILENO ) ;
close ( STDOUT_FILENO ) ;
/* De mme , le socket d coute est inutile pour le processus fils . */
close ( server_socket ) ;
/* Traite une requte pour la connexion . Nous avons notre propre copie
du descripteur de socket . */
h a n d l e _ c o n n e c t i o n ( connection ) ;
/* Termin , nous fermons le socket de connexion et terminons le
processus fils . */
close ( connection ) ;
exit (0) ;
}
else if ( child_pid > 0) {
/* Nous sommes dans le processus parent . Le processus fils gre la connexion
,
nous n avons donc pas besoin de notre copie du descripteur de socket .
Nous la
fermons puis nous reprenons la boucle pour accepter une autre connexion .
*/
close ( connection ) ;
}
else
/* L appel fork a chou . */
system_error ( " fork " ) ;

265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291

214

CHAPITRE 11. APPLICATION GNU/LINUX DILLUSTRATION

292

Voici les fonctions dfinies dans server.c :


server_run est le point dentre du serveur. Cette fonction dmarre le serveur, accepte des
connexions et ne se termine pas tant quune erreur srieuse ne survient pas. Le serveur
utilise un socket serveur TCP (consultez la Section 5.5.3, Serveurs , du Chapitre 5,
Communication interprocessus ) ;
Le premier argument de server_run correspond ladresse locale sur laquelle les connexions
sont acceptes. Une machine sous GNU/Linux peut avoir plusieurs adresses rseau, chacune pouvant tre lie une interface rseau diffrente2 . Pour obliger le serveur naccepter
les connexions qu partir dune certaine interface, spcifiez son adresse rseau. Passez
ladresse locale INADDR_ANY pour accepter les connexions depuis nimporte quelle adresse
locale.
Le second argument de server_run est le numro de port sur lequel accepter les connexions.
Si le port est dj en cours dutilisation ou sil sagit dun port privilgi et que le serveur ne
sexcute pas avec les privilges superutilisateur, le dmarrage choue. La valeur spciale 0
demande Linux de slectionner automatiquement un port inutilis. Consultez la page de
manuel inet pour plus dinformations sur les adresses Internet et les numros de ports.
Le serveur traite chaque connexion dans un processus fils cr par le biais de fork (cf.
Section 3.2.2, Utiliser fork et exec dans le Chapitre 3, Processus ). Le processus
principal (parent) continue accepter des connexions tandis que celles dj acceptes sont
traites. Le processus fils appelle handle_connection puis ferme le socket et se termine.
handle_connection traite une connexion client en utilisant le descripteur de socket qui lui
est pass en argument. Cette fonction lit les donnes partir du socket et tente de les
interprter en tant que demande de page HTTP.
Le serveur ne prend en charge que les versions 1.0 et 1.1 du protocole HTTP. Lorsque le
protocole ou la version est incorrect, il rpond en envoyant le code HTTP 400 et le message
bad_request_response. Le serveur ne comprend que la mthode HTTP GET. Si le client
utilise une autre mthode, le code rponse HTTP 500 lui est renvoy ainsi que le message
bad_method_response_template ;
Si le client envoie une requte GET valide, handle_connection appelle handle_get pour la
traiter. Cette fonction tente de charger un module serveur dont le nom est driv de la page
demande. Par exemple, si le client demande une page information, elle tente de charger
le module information.so. Si le module ne peut pas tre charg, handle_get renvoie au
client le code rponse HTTP 404 et le message not_found_response_template.
Si le client demande une page laquelle correspond un module, handle_get renvoie un code
rponse 200, signifiant que la requte a t correctement traite, puis invoque la fonction
module_generate du module. Celle-ci gnre le HTML de la page WEb et lenvoie au client
Web.
server_run installe clean_up_child_process en tant que gestionnaire de signal pour SIGCHLD.
Cette fonction se contente de librer les ressources des processus fils termins (cf. Sec2

Votre ordinateur peut ter configur pour disposer dinterfaces telles que eth0, une carte Ethernet, lo, le
rseau de bouclage ou ppp0, une connexion rseau distance.

11.2. IMPLANTATION

215

tion 3.4.4, Librer les ressources des fils de faon asynchrone du Chapitre 3).

11.2.4

Le programme principal

main.c (cf. Listing 11.5) dfinit la fonction main du programme serveur. Il lui incombe
danalyser les options de la ligne de commande, de dtecter et signaler les erreurs et de configurer
et lancer le serveur.
Listing 11.5 (main.c) Programme principal du serveur et analyse de la ligne de
commande
1
2
3
4
5
6
7
8

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

< assert .h >


< getopt .h >
< netdb .h >
< stdio .h >
< stdlib .h >
< string .h >
< sys / stat .h >
< unistd .h >

9
10

# include " server . h "

11
12

/* Description des options longues pour getopt_long . */

13
14
15
16
17
18
19
20

static const struct option long_options [] = {


{ " address " ,
1 , NULL , a } ,
{ " help " ,
0 , NULL , h } ,
{ " module - dir " ,
1 , NULL , m } ,
{ " port " ,
1 , NULL , p } ,
{ " verbose " ,
0 , NULL , v } ,
};

21
22

/* Description des options courtes pour getopt_long . */

23
24

static const char * const short_options = " a : hm : p : v " ;

25
26

/* Usage summary text . */

27
28
29
30
31
32
33
34
35
36

static const char * const us ag e_tem plate =


" Usage : % s [ options ]\ n "
" -a , -- address ADDR
Bind to local address ( by default , bind \ n "
"
to all local addresses ) .\ n "
" -h , -- help
Print this information .\ n "
" -m , -- module - dir DIR Load modules from specified directory \ n "
"
( by default , use executable directory ) .\ n "
" -p , -- port PORT
Bind to specified port .\ n "
" -v , -- verbose
Print verbose messages .\ n " ;

37
38
39
40

/* Affiche des informations sur l utilisation . Si IS_ERROR est diffrent de zro ,


crit sur stderr et utilise un code de sortie d erreur , sinon utilise un code
de sortie normale . Ne se termine jamais . */

41
42
43
44
45
46

static void print_usage ( int is_error )


{
fprintf ( is_error ? stderr : stdout , usage_template , program_name ) ;
exit ( is_error ? 1 : 0) ;
}

47
48

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

216

CHAPITRE 11. APPLICATION GNU/LINUX DILLUSTRATION


49
50
51
52

{
struct in_addr local_address ;
uint16_t port ;
int next_option ;

53
54
55

/* Stocke le nom du programme qui sera utilis dans les messages d erreur . */
program_name = argv [0];

56
57
58
59
60
61
62
63
64
65

/* Dfinit les valeurs par dfaut des options . Le serveur coute sur toutes les
adresse et rcupre a u t om a t i q u e me n t un numro de port inutilis . */
local_address . s_addr = INADDR_ANY ;
port = 0;
/* N affiche pas les messages verbeux . */
verbose = 0;
/* Charge les modules depuis le rpertoire contenant l excutable . */
module_dir = g e t _ s e l f _ e x e c u t a b l e _ d i r e c t o r y () ;
assert ( module_dir != NULL ) ;

66
67
68
69
70
71
72
73
74
75

/* Analyse les options . */


do {
next_option =
getopt_long ( argc , argv , short_options , long_options , NULL ) ;
switch ( next_option ) {
case a :
/* L utilisateur a spcifi -a ou -- address . */
{
struct hostent * l o c a l _h o s t _ n am e ;

76
77
78
79
80
81
82
83
84
85
86
87

/* Recherche le nom d hte demand par l utilisateur . */


l o c al _ h o s t _ na m e = gethostbyname ( optarg ) ;
if ( l o c al _ h o s t _ na m e == NULL || local_host_name - > h_length == 0)
/* Impossible de le rsoudre . */
error ( optarg , " invalid host name " ) ;
else
/* Nom d hte OK , nous l utilisons . */
local_address . s_addr =
*(( int *) ( local_host_name - > h_addr_list [0]) ) ;
}
break ;

88
89
90
91

case h :
/* L utilisateur a pass -h ou -- help .
print_usage (0) ;

*/

92
93
94
95
96

case m :
/* L utilisater a pass -m ou -- module - dir .
{
struct stat dir_info ;

*/

97
98
99
100
101
102
103
104
105
106
107
108
109
110

/* Vrifie que le rpertoire existe ... */


if ( access ( optarg , F_OK ) != 0)
error ( optarg , " module directory does not exist " ) ;
/* ... qu il est accessible ... */
if ( access ( optarg , R_OK | X_OK ) != 0)
error ( optarg , " module directory is not accessible " ) ;
/* ... et qu il s agit d un rpertoire . */
if ( stat ( optarg , & dir_info ) != 0 || ! S_ISDIR ( dir_info . st_mode ) )
error ( optarg , " not a directory " ) ;
/* Tout semble correct , nous l utilisons . */
module_dir = strdup ( optarg ) ;
}
break ;

11.2. IMPLANTATION

217

111

case p :
/* L utilisateur a pass -p ou -- port .
{
long value ;
char * end ;

112
113
114
115
116

*/

117

value = strtol ( optarg , & end , 10) ;


if (* end != \0 )
/* Prsence de caractres non numriques dans le numro de port . */
print_usage (1) ;
/* Le numro de port doit tre converti l ordre des octets du rseau
( big endian ) . */
port = ( uint16_t ) htons ( value ) ;

118
119
120
121
122
123
124

}
break ;

125
126
127

case v :
/* L utilisateur a spcifi -v ou -- verbose . */
verbose = 1;
break ;

128
129
130
131
132

case ? :
/* L utilisateur a pass une option inconnue .
print_usage (1) ;

133
134
135

*/

136

case -1:
/* Nous en avons termin avec les options .
break ;
default :
abort () ;
}
} while ( next_option != -1) ;

137
138
139
140
141
142
143

*/

144

/* Ce programme ne prend aucun argument su pp l men ta ire . Affiche une erreur


si l utilisateur en a pass . */
if ( optind != argc )
print_usage (1) ;

145
146
147
148
149

/* Affiche le rpertoire des modules , si nous sommes en mode verbeux . */


if ( verbose )
printf ( " modules will be loaded from % s \ n " , module_dir ) ;
/* Lance le serveur . */
server_run ( local_address , port ) ;
return 0;

150
151
152
153
154
155
156

main.c dfinit les fonctions suivantes :


main invoque getopt_long (cf. la Section 2.1.3, Utiliser getopt_long du Chapitre 2)
afin danalyser les options de ligne de commande. Ces options sont disponibles au format
long ou court, comme dfini par le tableau long_options pour les premires et la chane
short_options pour les secondes.
La valeur par dfaut pour le port dcoute est 0 et ladresse locale est INADDR_ANY.
Ces valeurs peuvent tre redfinies au moyen des options port (-p) et address (-a),
respectivement. Si lutilisateur spcifie une adresse, main appelle la fonction gethostbyname
pour la convertir en adresse Internet numrique3 .
3

gethostbyname effectue une rsolution DNS si ncessaire.

218

CHAPITRE 11. APPLICATION GNU/LINUX DILLUSTRATION


La valeur par dfaut pour le rpertoire partir duquel charger les modules serveur est le rpertoire qui contient lexcutable du serveur, dtermin par get_self_executable_directory
. Lutilisateur peut modifier cette valeur grce loption module-dir (-m) ; main sassure
que le rpertoire indiqu est bien accessible.
Par dfaut, les messages verbeux ne sont pas affichs. Lutilisateur peut les activer au
moyen de loption verbose (-v) ;
Si lutilisateur spcifie loption help (-h) ou passe des options invalides, main invoque
print_usage qui affiche des explications sur lutilisation du programme et le termine.

11.3

Modules

Nous vous proposons quatre modules pour prsenter le type de fonctionnalits que vous
pourriez implanter en utilisant ce serveur. La cration de votre propre module serveur consiste
simplement dfinir une fonction module_generate pour renvoyer le texte adquat au format
HTML.

11.3.1

Afficher lheure

Le module time.so (cf. Listing 11.6 gnre une page affichant simplement lheure locale du
serveur. La fonction module_generate de ce module appelle gettimeofday pour obtenir lheure
courante (reportez-vous la Section 8.7, gettimeofday: heure systme du Chapitre 8, Appels
systme Linux ) et utilise localtime et strftime pour gnrer une reprsentation textuelle de
celle-ci. Cette reprsentation est ensuite injecte dans le modle HTML page_template.
Listing 11.6 (time.c) Module serveur affichant lheure courante
1
2
3
4

# include
# include
# include
# include

< assert .h >


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

5
6

# include " server . h "

7
8

/* Modle de la page gnre par le module . */

9
10
11
12
13
14
15
16
17
18

static char * page_template =


" < html >\ n "
" < head >\ n "
" < meta http - equiv =\" refresh \" content =\"5\" >\ n "
" </ head >\ n "
" < body >\ n "
" <p > The current time is % s . </p >\ n "
" </ body >\ n "
" </ html >\ n " ;

19
20
21
22
23
24
25
26

void m o d ul e _ g e n e ra t e ( int fd )
{
struct timeval tv ;
struct tm * ptm ;
char time_string [40];
FILE * fp ;

11.3. MODULES

219

/* Rcupre l heure courante et la convertit en struct tm . */


gettimeofday (& tv , NULL ) ;
ptm = localtime (& tv . tv_sec ) ;

27
28
29
30

/* Formate l heure avec une prcision la seconde . */


strftime ( time_string , sizeof ( time_string ) , " % H :% M :% S " , ptm ) ;

31
32
33

/* Cre un flux correspondant au descripteur de fichier du


socket client . */
fp = fdopen ( fd , " w " ) ;
assert ( fp != NULL ) ;
/* Gnre la sortie HTML . */
fprintf ( fp , page_template , time_string ) ;
/* Termin ; purge le flux . */
fflush ( fp ) ;

34
35
36
37
38
39
40
41
42

Ce module utilise les fonctions dE/S de la bibliothque C standard pour des raisons pratiques. Lappel fdopen gnre un pointeur sur un flux (FILE*) correspondant au socket client (cf.
Annexe B, E/S de bas niveau ; Section B.4, Lien avec les fonctions dE/S standards du
C ). Le module envoie des donnes au descripteur en utilisant fprintf et le purge au moyen de
fflush pour viter une perte des donnes mises dans le tampon lorsque le socket est germ.
La page HTML renvoye par le module time.so inclue lement <meta> qui demande au client
de recharger la page toutes les 5 secondes. De cette faon, le client reste lheure.

11.3.2

Afficher la distribution utilise

Le module issue.so (Listing 11.7 affiche des informations sur la distribution sur laquelle
tourne le serveur. Ces informations sont habituellement stockes dans le fichier /etc/issue. Le
module envoie le contenu de ce fichier encapsul dans un lment <pre>.
Listing 11.7 (issue.c) Module permettant dafficher des informations sur la
distribution GNU/Linux
1
2
3
4
5
6

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

< fcntl .h >


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

7
8

# include " server . h "

9
10

/* Source HTML du dbut de la page que nous gnrons . */

11
12
13
14
15

static char * page_start =


" < html >\ n "
" < body >\ n "
" < pre >\ n " ;

16
17

/* Source HTML de la fin de la page que nous gnrons . */

18
19
20
21
22
23

static char * page_end =


" </ pre >\ n "
" </ body >\ n "
" </ html >\ n " ;

220

CHAPITRE 11. APPLICATION GNU/LINUX DILLUSTRATION


24
25

/* Source HTML de la page indiquant qu il y a eu un problme l ouverture de


/ etc / issue . */

26
27
28
29
30
31
32

static char * error_page =


" < html >\ n "
" < body >\ n "
" <p > Error : Could not open / etc / issue . </ p >\ n "
" </ body >\ n "
" </ html >\ n " ;

33
34

/* Source HTML indiquant une erreur .

*/

35
36

static char * error_message = " Error reading / etc / issue . " ;

37
38
39
40
41
42

void m o d ul e _ g e n e ra t e ( int fd )
{
int input_fd ;
struct stat file_info ;
int rval ;

43

/* Ouvre / etc / issue . */


input_fd = open ( " / etc / issue " , O_RDONLY ) ;
if ( input_fd == -1)
system_error ( " open " ) ;
/* Rcupre des informations sur le fichier . */
rval = fstat ( input_fd , & file_info ) ;

44
45
46
47
48
49
50

if ( rval == -1)
/* Soit nous n avons pas pu ouvrir le fichier , soit ne n avons pas pu y lire .
*/
write ( fd , error_page , strlen ( error_page ) ) ;
else {
int rval ;
off_t offset = 0;

51
52
53
54
55
56
57

/* crit le dbut de la page . */


write ( fd , page_start , strlen ( page_start ) ) ;
/* Copie le contenu de / proc / issue vers le socket client . */
rval = sendfile ( fd , input_fd , & offset , file_info . st_size ) ;
if ( rval == -1)
/* Quelque chose s est mal pass lors de l envoi du contenu de / etc / issue .
Envoie un message d erreur . */
write ( fd , error_message , strlen ( error_message ) ) ;
/* Fin de la page . */
write ( fd , page_end , strlen ( page_end ) ) ;

58
59
60
61
62
63
64
65
66
67

68
69

close ( input_fd ) ;

70
71

Le module commence par essayer douvrir /etc/issue. Si ce fichier ne peut pas tre ouvert,
le module envoie une page derreur au client. Sinon, il envoie le dbut de la page HTML
contenu dans page_start. Puis le contenu de /etc/issue est transmis au moyen de sendfile
(cf. Chapitre 8, Section 8.12, sendfile: transferts de donnes rapides ). Enfin, il envoie la fin
de la page HTML contenue dans page_end.
Vous pouvez facilement adapter ce module pour envoyer le contenu dun autre fichier. Si celuici contient une page HTML complte, supprimez la partie qui envoie page_start et page_end.
Vous pourriez galement adapter le serveur principal pour quil puisse servir des fichier statiques,

11.3. MODULES

221

comme les serveurs Web traditionnels. Lutilisation de sendfile fournit un degr defficacit
supplmentaire.

11.3.3

Afficher lespace libre

Le modules diskfree.so (Listing 11.8) gnre une page affichant des informations sur
lespace disque libre sur les systmes de fichiers montes sur le serveur. Ces informations sont
simplement le rsultat de linvocaton de la commande df -h. Tout comme issue.so, le module
encapsule les informations dans une balise <pre>.
Listing 11.8 (diskfree.c) Module affichant des informations sur lespace disque
libre
1
2
3
4
5

# include
# include
# include
# include
# include

< stdlib .h >


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

6
7

# include " server . h "

8
9

/* Source HTML du dbut de la page gnre . */

10
11
12
13
14

static char * page_start =


" < html >\ n "
" < body >\ n "
" <pre >\ n " ;

15
16

/* Source HTML de la fin de la page gnre . */

17
18
19
20
21

static char * page_end =


" </ pre >\ n "
" </ body >\ n "
" </ html >\ n " ;

22
23
24
25
26

void m o d ul e _ g e n e ra t e ( int fd )
{
pid_t child_pid ;
int rval ;

27
28
29
30
31
32
33
34
35

/* crit le dbut de la page . */


write ( fd , page_start , strlen ( page_start ) ) ;
/* Cre un processus fils . */
child_pid = fork () ;
if ( child_pid == 0) {
/* Nous sommes le processus fils . */
/* Cre la liste d arguments pour l invocation de df .
char * argv [] = { " / bin / df " , " -h " , NULL };

*/

36
37
38
39
40
41
42
43
44
45

/* Duplique stdout et stderr afin d envoyer les donnes au socket client . */


rval = dup2 ( fd , STDOUT_FILENO ) ;
if ( rval == -1)
system_error ( " dup2 " ) ;
rval = dup2 ( fd , STDERR_FILENO ) ;
if ( rval == -1)
system_error ( " dup2 " ) ;
/* Lance df pour afficher l espace libre sur les systmes de fichiers monts .
*/
execv ( argv [0] , argv ) ;

222

CHAPITRE 11. APPLICATION GNU/LINUX DILLUSTRATION


/* Un appel execv ne se termine jamais moins d une erreur . */
system_error ( " execv " ) ;

46
47

}
else if ( child_pid > 0) {
/* Nous sommes dans le processus parent , nous
attendons la fin du processus fils . */
rval = waitpid ( child_pid , NULL , 0) ;
if ( rval == -1)
system_error ( " waitpid " ) ;
}
else
/* L appel fork a chou . */
system_error ( " fork " ) ;
/* crit la fin de la page . */
write ( fd , page_end , strlen ( page_end ) ) ;

48
49
50
51
52
53
54
55
56
57
58
59
60
61

Alors que issue.so envoie le contenu dun fichier en utilisant sendfile, ce module doit
invoquer une commande et rediriger sa sortie vers le client. Pour cela, il suit les tapes suivantes :
1. Tout dabord, le module cre un processus fils au moyen de fork (cf. Chapitre 3, Section 3.2.2, Utiliser fork et exec ) ;
2. Le processus fils copie le descripteur de fichier du socket vers les descripteurs STDOUT_FILENO
et STDERR_FILENO qui correspondernt la sortie standard et la sortie des erreurs standard
(cf. Chapitre 2, Section 2.1.4, E/S standards ). Les descripteurs sont copis en utilisant
lappel dup2 (cf. Chapitre 5, Section 5.4.3, Rediriger les flux dentre, de sortie et derreur
standards ). Tout affichage ultrieur depuis le processus vers un de ces flux est envoy au
socket client ;
3. Le processus fils invoque la commande df avec loption -h en apelant execv (cf. Chapitre 3,
Section 3.2.2, Utiliser fork et exec ) ;
4. Le processus parent attend la fin du processus fils en appelant waitpid (cf. Chapitre 3,
Section 3.4.2, Les appels systme wait ).
Vous pouvez facilement adapter ce module pour quil invoque une commande diffrente et
redirige sa sortie vers le client.

11.3.4

Afficher la liste des processus en cours dexcution

Le module processes.so (Listing 11.9) est un module plus riche. Il gnre une page contenant un tableau prsentant les processus en cours dexcution sur le serveur. Chaque processus
apparat dans une ligne du tableau affichant son PID, le nom de lexcutable, lutilisateur et le
groupe propritaires ainsi que la taille de lempreinte mmoire rsidente.
Listing 11.9 (processes.c) Module listant les processus
1
2
3
4
5
6
7
8

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

< assert .h >


< dirent .h >
< fcntl .h >
< grp .h >
< pwd .h >
< stdio .h >
< stdlib .h >
< string .h >

11.3. MODULES
9
10
11
12

# include
# include
# include
# include

223

< sys / stat .h >


< sys / types .h >
< sys / uio .h >
< unistd .h >

13
14

# include " server . h "

15
16
17
18

/* Place l identifiant du groupe et de l utilisateur propritaire du processus PID


dans * UID et * GID , re sp ec tiv ement . Renvoie 0 en cas de succs , une valeur
diffrente
de 0 sinon . */

19
20
21
22
23
24

static int get_uid_gid ( pid_t pid , uid_t * uid , gid_t * gid )


{
char dir_name [64];
struct stat dir_info ;
int rval ;

25

/* Gnre le nom du rpertoire du processus dans / proc . */


snprintf ( dir_name , sizeof ( dir_name ) , " / proc /% d " , ( int ) pid ) ;
/* Rcupre des informations sur le rpertoire . */
rval = stat ( dir_name , & dir_info ) ;
if ( rval != 0)
/* Rpertoire introuvable ; ce processus n existe peut tre plus . */
return 1;
/* S assure qu il s agit d un rpertoire ; si ce n est pas le cas
il s agit d une erreur . */
assert ( S_ISDIR ( dir_info . st_mode ) ) ;

26
27
28
29
30
31
32
33
34
35
36

/* Rcupre les identifiants dont nous avons besoin .


* uid = dir_info . st_uid ;
* gid = dir_info . st_gid ;
return 0;

37
38
39
40
41

*/

42
43
44
45

/* Renvoie le nom de l utilisateur UID . La valeur de retour est un tampon


que l appelant doit librer avec free . UID doit tre un identifiant utilisateur
valide . */

46
47
48
49

static char * get_user_name ( uid_t uid )


{
struct passwd * entry ;

50

entry = getpwuid ( uid ) ;


if ( entry == NULL )
system_error ( " getpwuid " ) ;
return xstrdup ( entry - > pw_name ) ;

51
52
53
54
55

56
57
58
59

/* Renvoie le nom du groupe GID . La valeur de retour est un tampon que


l appelant doit librer avec free . GID doit tre un identifiant de groupe
valide . */

60
61
62
63

static char * ge t_ gr oup _n am e ( gid_t gid )


{
struct group * entry ;

64

entry = getgrgid ( gid ) ;


if ( entry == NULL )
system_error ( " getgrgid " ) ;
return xstrdup ( entry - > gr_name ) ;

65
66
67
68
69

224

CHAPITRE 11. APPLICATION GNU/LINUX DILLUSTRATION


70
71
72
73

/* Renvoie le nom du programme s excutant dans le processus PID ou NULL en cas


d erreur . La valeur de retour est un tampon que l appelant doit librer avec
free . */

74
75
76
77
78
79
80
81
82
83

static char * g e t _ p r o g r a m _ n a m e ( pid_t pid )


{
char file_name [64];
char status_info [256];
int fd ;
int rval ;
char * open_paren ;
char * close_paren ;
char * result ;

84

/* Gnre le nom du fichier " stat " dans le rpertoire du processus sous / proc
et l ouvre . */
snprintf ( file_name , sizeof ( file_name ) , " / proc /% d / stat " , ( int ) pid ) ;
fd = open ( file_name , O_RDONLY ) ;
if ( fd == -1)
/* Impossible d ouvrir le fichier stat pour ce processus . Peut tre qu il
n existe plus . */
return NULL ;
/* Lit le contenu . */
rval = read ( fd , status_info , sizeof ( status_info ) - 1) ;
close ( fd ) ;
if ( rval <= 0)
/* Impossible lire pour une raison quelconque . chec . */
return NULL ;
/* Ajout un caractre nul la fin du contenu du fichier . */
status_info [ rval ] = \0 ;

85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101

/* Le nom du programme est le second lment dans le fichier et est


encadr par des parenthses . Recherche les positions des parenthses
dans le fichier . */
open_paren = strchr ( status_info , ( ) ;
close_paren = strchr ( status_info , ) ) ;
if ( open_paren == NULL
|| close_paren == NULL
|| close_paren < open_paren )
/* Impossible de les trouver ; chec . */
return NULL ;
/* Allou de la mmoire pour le rsultat . */
result = ( char *) xmalloc ( close_paren - open_paren ) ;
/* Copie le nom du programme dans le rsultat . */
strncpy ( result , open_paren + 1 , close_paren - open_paren - 1) ;
/* strncpy n ajoute pas d octet nul la fin du rsultat , nous le faisons . */
result [ close_paren - open_paren - 1] = \0 ;
/* Termin . */
return result ;

102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120

121
122
123

/* Renvoie la taille de l empreinte mmoire rsidente ( RSS ) en kilooctets , du


processus PID . Renvoie -1 en cas d chec . */

124
125
126
127
128
129
130
131

static int get_rss ( pid_t pid )


{
char file_name [64];
int fd ;
char mem_info [128];
int rval ;
int rss ;

11.3. MODULES

225

132

/* Gnre le nom de l entre " statm " du processus dans le rpertoire / proc . */
snprintf ( file_name , sizeof ( file_name ) , " / proc /% d / statm " , ( int ) pid ) ;
/* L ouvre . */
fd = open ( file_name , O_RDONLY ) ;
if ( fd == -1)
/* Impossible d ouvrir le fichier " statm " pour ce processus . Peut tre qu il
n existe plus . */
return -1;
/* Lit le contenu du fichier . */
rval = read ( fd , mem_info , sizeof ( mem_info ) - 1) ;
close ( fd ) ;
if ( rval <= 0)
/* Impossible de lire le fichier ; chec . */
return -1;
/* Ajoute un octet nul . */
mem_info [ rval ] = \0 ;
/* Extrait la RSS . Il s agit du second lment . */
rval = sscanf ( mem_info , " %* d % d " , & rss ) ;
if ( rval != 1)
/* Le contenu de statm est format d une faon inconnue . */
return -1;

133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154

/* Les valeurs de statm sont en nombre de pages systme .


Convertit le RSS en kilooctets . */
return rss * getpagesize () / 1024;

155
156
157
158

159
160
161
162

/* Gnre une ligne de tableau HTML pour le processus PID . Renvoie un


pointeur vers un tampon que l appelant doit librer avec free ou
NULL si une erreur survient . */

163
164
165
166
167
168
169
170
171
172
173
174

static char * f o r m a t _ p r o c e s s _ i n f o ( pid_t pid )


{
int rval ;
uid_t uid ;
gid_t gid ;
char * user_name ;
char * group_name ;
int rss ;
char * program_name ;
size_t result_length ;
char * result ;

175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190

/* Rcupre les UID et GID du processus . */


rval = get_uid_gid ( pid , & uid , & gid ) ;
if ( rval != 0)
return NULL ;
/* Obtient la RSS du processus . */
rss = get_rss ( pid ) ;
if ( rss == -1)
return NULL ;
/* Rcupre le nom du programme du processus . */
program_name = g e t _ p r o g r a m _ n a m e ( pid ) ;
if ( program_name == NULL )
return NULL ;
/* Convertit les UID et GID en noms symboliques .
user_name = get_user_name ( uid ) ;
group_name = g et_ gr ou p_n am e ( gid ) ;

*/

191
192
193

/* Calcule la taille de la chane ncessaire pour stocker le rsultat


et alloue la mmoire co rr es pon da nte . */

226

CHAPITRE 11. APPLICATION GNU/LINUX DILLUSTRATION


result_length = strlen ( program_name )
+ strlen ( user_name ) + strlen ( group_name ) + 128;
result = ( char *) xmalloc ( result_length ) ;
/* Formate le rsultat . */
snprintf ( result , result_length ,
" <tr > < td align =\" right \" >% d </ td > < td > < tt >% s </ tt > </ td > < td >% s </ td > "
" <td >% s </ td > < td align =\" right \" >% d </ td > </ tr >\ n " ,
( int ) pid , program_name , user_name , group_name , rss ) ;
/* Libration des ressources . */
free ( program_name ) ;
free ( user_name ) ;
free ( group_name ) ;
/* Termin . */
return result ;

194
195
196
197
198
199
200
201
202
203
204
205
206
207
208

209
210

/* Source HTML du dbut de la page de listing . */

211
212
213
214
215
216
217
218
219
220
221
222
223
224
225

static char * page_start =


" < html >\ n "
" < body >\ n "
" < table cellpadding =\"4\" cellspacing =\"0\" border =\"1\" >\ n "
"
< thead >\ n "
"
<tr >\ n "
"
<th > PID </ th >\ n "
"
<th > Program </ th >\ n "
"
<th > User </ th >\ n "
"
<th > Group </ th >\ n "
"
<th > RSS & nbsp ;( KB ) </ th >\ n "
"
</ tr >\ n "
"
</ thead >\ n "
"
< tbody >\ n " ;

226
227

/* Source HTML de la fin de la page . */

228
229
230
231
232
233

static char * page_end =


"
</ tbody >\ n "
" </ table >\ n "
" </ body >\ n "
" </ html >\ n " ;

234
235
236
237
238

void m o d ul e _ g e n e ra t e ( int fd )
{
size_t i ;
DIR * proc_listing ;

239
240
241
242

/* Cre un tableau d iovec . Nous le renseignerons avec les


tampons qui seront assembls pour gnrer la sortie , en adaptant
sa taille de faon dynamique . */

243
244
245
246
247
248
249
250

/* Nombre d lments utiliss dans le tableau . */


size_t vec_length = 0;
/* Taille du tableau . */
size_t vec_size = 16;
/* Tableau d iovec . */
struct iovec * vec =
( struct iovec *) xmalloc ( vec_size * sizeof ( struct iovec ) ) ;

251
252
253
254
255

/* Le premier buffer est la source HTML du dbut de la page . */


vec [ vec_length ]. iov_base = page_start ;
vec [ vec_length ]. iov_len = strlen ( page_start ) ;
++ vec_length ;

11.3. MODULES

227

256
257
258
259
260

/* Commence le listing du rpertoire / proc .


proc_listing = opendir ( " / proc " ) ;
if ( proc_listing == NULL )
system_error ( " opendir " ) ;

*/

261
262
263
264
265
266
267

/* Parcourt les entres de / proc .


while (1) {
struct dirent * proc_entry ;
const char * name ;
pid_t pid ;
char * process_info ;

*/

268

/* Rcupre l entre suivante . */


proc_entry = readdir ( proc_listing ) ;
if ( proc_entry == NULL )
/* Nous sommes la fin du rpertoire . */
break ;
/* Si cette entre n est pas compose uniquement de chiffre , il ne
s agit pas d un rpertoire de processus . Nous le laissons de ct . */
name = proc_entry - > d_name ;
if ( strspn ( name , " 0123456789 " ) != strlen ( name ) )
continue ;
/* Le nom de l entre est l identifiant du processus . */
pid = ( pid_t ) atoi ( name ) ;
/* Gnre une ligne de tableau HTML dcrivant ce processus . */
process_info = f o r m a t _ p r o c e s s _ i n f o ( pid ) ;
if ( process_info == NULL )
/* Quelque chose s est mal pass . Le processus peut s tre termin
pendant que nous l analysions . Affiche une ligne d erreur . */
process_info = " <tr > < td colspan =\"5\" > ERROR </ td > </ tr > " ;

269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287

/* S assure que le tableau iovec est suffisamment long pour accueillir


le tampon ( plus un car nous aurons besoin d un lment su pp l men ta ire
la fin du listing ) . Si ce n est pas le cas , double sa taille . */
if ( vec_length == vec_size - 1) {
vec_size *= 2;
vec = xrealloc ( vec , vec_size * sizeof ( struct iovec ) ) ;
}
/* Stocke le tampon en tant qu lment suivant dans le tableau . */
vec [ vec_length ]. iov_base = process_info ;
vec [ vec_length ]. iov_len = strlen ( process_info ) ;
++ vec_length ;

288
289
290
291
292
293
294
295
296
297
298
299

300
301
302

/* Ferme le rpertoire la fin du listing .


closedir ( proc_listing ) ;

*/

303
304
305
306
307

/* Ajout un dernier tampon avec le HTML de fin de page .


vec [ vec_length ]. iov_base = page_end ;
vec [ vec_length ]. iov_len = strlen ( page_end ) ;
++ vec_length ;

*/

308
309
310

/* Envoie toute la page au descripteur client en une seule fois .


writev ( fd , vec , vec_length ) ;

*/

311
312
313
314
315
316

/* Libre les diffrents tampons . Le premier et le dernier contiennent des


donnes
statique et ne doivent pas tre librs . */
for ( i = 1; i < vec_length - 1; ++ i )
free ( vec [ i ]. iov_base ) ;
/* Libre la mmoire occupe par le tableau d iovec . */

228

CHAPITRE 11. APPLICATION GNU/LINUX DILLUSTRATION


free ( vec ) ;

317
318

La rcolte dinformations sur un processus et leur formatage en HTML sont diviss en


plusieurs oprations plus simples :
get_uid_gid extrait les identifiants de lutilisateur et du groupe propritaires dun processus. Pour cela, la fonction utilise stat (cf. Annexe B, Section B.2, stat ) sur le
rpertoire de /proc correspondant au processus (cf. Chapitre 7, Section 7.2, Rpertoires
de processus ). Lutilisateur et le groupe propritaires de ce rpertoires sont identiques
aux propritaires du processus ;
get_user_name renvoie le nom dutilisateur correspondant un UID. Cette fonction se
contente dappeler la fonction de la bibliothque C getpwuid, qui consulte le fichier /etc/passwd
du systme et renvoie une copie du rsultat. get_group_name renvoie le nom du groupe
correspondant unGID. Il utilise lappel getrgid.
get_program_name renvopie le nom du programme sexcutant dans un processus donn.
Ces informations sont obtenues partir de lentre stat dans le rpertoire du processus
sous /proc (cf. Chapitre 7, Section 7.2, Rpertoires de processus ). Nous utilisons cette
entre plutt que le lien symbolique exe (cf. Chapitre 7, Section 7.2.4, Excutable de
processus ) ou lentre cmdline (cf. Chapitre 7, Section 7.2.2, Liste darguments dun
processus ) car ils sont inaccessibles si le processus serveur nappartient pas au mme
utilisateur que le celui qui est examin. De plus, la lecture de stat ne force pas Linux
remonter le processus examin en mmoire sil avait t dcharg de la mmoire ;
get_rss renvoie la taille de lempreinte mmoire rsidente dun processus. Cette information est donne par le second lment du contenu de lentre statm dans le rpertoire /proc
pour le processus (cf. Chapitre 7, Section 7.2.6, Statistiques mmoire de processus ) ;
format_process_info gnre une chane contenant les lments HTML placer dans une
ligne du tableau reprsentant un processus. Aprs lappel aux fonctions prsentes cidessus pour obtenir les informations ncessaires, elle alloue un tampon et gnre le HTML
en utilisant snprintf ;
module_generate gnre la page HTML proprement dite, y compris le tableau. La sortie est
compose dune chane contenant le dbut de la page et du tableau (page_start), dune
chane pour chaque ligne du tableau (gnre par format_process_info) et une chane
contenant la fin du tableau et de la page (page_end).
module_generate dtermine les PID des processus en cours dexcution sur le systme en
lisant le contenu de /proc. Elle utilise opendir et readdir (cf. Annexe B, Section B.6,
Lire le contenu dun rpertoire ) pour parcourir le rpertoire. Elle analyse son contenu
la recherche dentres composes uniquement de chiffres ; qui sont supposes tre des
entres correspondant des processus.
Un nombre potentiellement important de chanes doivent tre envoyes vers le socket client
une pour le dbut et une pour la fin de la page, ainsi quune par processus. Si chaque
chane devait tre crite avec un appel distinct write cela gnrerait une surcharge rseau
inutile car chaque chane devrait tre envoye dans un paquet distinct.
Pour optimiser lencapsulation des donnes dans des paquets, nous utilisons un unique
appel writev (cf. Annexe B, Section B.3, criture et lecture vectorielles ). Pour cela,

11.4. UTILISATION DU SERVEUR

229

nous devons construire un tableau vec dobjets struct iovec. Cependant, comme nous
ne connaissons pas lavance le nombre de processus, nous devons commencer avec un
tableau trs petit que nous tendons au fur et mesure de lajout de processus. La variable
vec_length contient le nombre dlments utiliss dans vec tandis que vec_size contient
la taille relle de vec. Lorsque vec_length est en passe de devenir plus grand que vec_size
, nous doublons la taille de vec par le viais de xrealloc. Lorsque nous avons termin
lcriture des donnes, nous devons librer toutes les chanes alloues dynamiquement sur
lesquelles pointent les diffrentes entres de vec puis librer vec proprement dit.

11.4

Utilisation du serveur

Si nous avions lintention de distribuer les sources du programme, de les maintenir et de


les porter sur dautres plateformes, nous utiliserions probablement GNU Automake et GNU
Autoconf ou un systme semblable dautomatisation de la configuration. De tels outils dpassent
du cadre de ce livre ; pour plus dinformations, consultez GNU Autoconf, Automake, and Libtool 4
(de Vaughan, Elliston, Tromey et Taylor, chez New Riders, 2000).

11.4.1

Le fichier Makefile

Au lieu dutiliser Autoconf ou un outil voisin, nous fournissons un fichier Makefile compatible avec GNU Make5 afin quil soit simple de compiler et lier le serveur et ses modules. Le
Makefile est prsent dans le Listing 11.10. Consultez la page info de GNU Make pour plus de
dtails sur la syntaxe de ce type de fichiers.
Listing 11.10 (Makefile) Fichier de confiuguration GNU Make pour lexemple de
serveur
1

# ## Configuration .

####################################################

2
3
4
5
6
7
8
9
10

# Options par dfaut du compilateur C .


CFLAGS
= - Wall -g
# Fichiers sources C pour le serveur .
SOURCES
= server . c module . c common . c main . c
# Fichiers objets co rr es pon da nts .
OBJECTS
= $ ( SOURCES :. c =. o )
# Fichiers de bibliothques partages des modules .
MODULES
= diskfree . so issue . so processes . so time . so

11
12

# ## Rgles .

###########################################################

13
14
15
16

# Les cibles phony ne correspondent pas des fichiers construire ; il


# s agit de noms pour des cibles conceptuelles .
. PHONY :
all clean

17
18
19

# Cible par dfaut : construit tout .


all :
server $ ( MODULES )

20
21
22
4
5

# Nettoie les objets construits .


clean :

NdT. en anglais
GNU Make est gnralement install sur les systmes GNU/Linux.

230

CHAPITRE 11. APPLICATION GNU/LINUX DILLUSTRATION


rm -f $ ( OBJECTS ) $ ( MODULES ) server

23
24
25
26
27
28
29
30

# Programme serveur principal . Fait l dition de liens avec les options


# -Wl , - export - dynamic afin que les modules puissent utiliser des symboles
# dfinis par le programme . Utilise libdl qui contient les appels ncessaires
# au chargement dynamique .
server :
$ ( OBJECTS )
$ ( CC ) $ ( CFLAGS ) -Wl , - export - dynamic -o $@ $ ^ - ldl

31
32
33
34
35

# Tous les fichiers objets du serveur dpendent de server . h . Les fichiers


# objets sont construits partir des fichiers sources grce une rgle par
# dfaut .
$ ( OBJECTS ) :
server . h

36
37
38
39
40
41
42

# Rgle pour construire les bibliothques partages des modules partir des
# fichiers sources co rr es pon da nts . Compile avec - fPIC et gnre un fichier
# objet partag .
$ ( MODULES ) : \
%. so :
%. c server . h
$ ( CC ) $ ( CFLAGS ) - fPIC - shared -o $@ $ <

Le Makefile dfinit les cibles suivantes :


all (la cible par dfaut si vous invoquez make sans arguments car il sagit de la premire
cible dans le Makefile) comprend lexcutable du serveur et tous les modules. Les modules
sont dfinis par la variable MODULES ;
clean supprime tout lment construit par le Makefile ;
server cre lexcutable du serveur. Les fichiers sources lists dans la variables SOURCES
sont compils et lis ;
la dernire ligne est un modle gnrique afin de compiler les fichiers objets partags des
modules du serveur partir des fichiers sources correspondants.
Notez que les fichiers sources des modules serveur sont compils en utilisant loption -fPIC
car il sagit de bibliothques partages (cf. Chapitre 2, Section 2.3.2, Bibliothques partages ).
Remarquez galement que lexcutable du serveur est li avec les options de compilation -Wl
et -export-dynamic. Grce cela, GCC passe loption -export-dynamic lditeur de liens qui
cre un fichier excutable qui exporte ses symboles comme le fait une bibliothque partage. Cela
permet aux modules, qui sont chargs dynamiquement sous forme de bibliothques partages, de
faire rfrence des fonctions de common.c qui sont lies statiquement lexcutable du serveur.

11.4.2

Construire le serveur

La construction du programme est relativement simple. Depuis le rpertoire o se situent les


sources, invoquez simplement make :
% make
cc -Wall
cc -Wall
cc -Wall
cc -Wall
cc -Wall
cc -Wall
cc -Wall
cc -Wall
cc -Wall

-g
-g
-g
-g
-g
-g
-g
-g
-g

-c -o server.o server.c
-c -o module.o module.c
-c -o common.o common.c
-c -o main.o main.c
-Wl,-export-dynamic -o server server.o module.o common.o main.o -ldl
-fPIC -shared -o diskfree.so diskfree.c
-fPIC -shared -o issue.so issue.c
-fPIC -shared -o processes.so processes.c
-fPIC -shared -o time.so time.c

11.4. UTILISATION DU SERVEUR

231

Cela lance la construction du serveur et des bibliothques partages des modules.


% ls -l server *.so
-rwxr-xr-x
1 samuel
-rwxr-xr-x
1 samuel
-rwxr-xr-x
1 samuel
-rwxr-xr-x
1 samuel
-rwxr-xr-x
1 samuel

11.4.3

samuel
samuel
samuel
samuel
samuel

25769
31184
41579
71758
13980

Mar
Mar
Mar
Mar
Mar

11
11
11
11
11

01:15
01:15
01:15
01:15
01:15

diskfree.so
issue.so
processes.so
server
time.so

Lancer le serveur

Pour lancer le serveur, invoquez simplement lexcutable server.


Si vous ne spcifiez pas de port au moyen de loption port (-p) ; Linux en choisira un pour
vous ; dans ce cas, passez loption verbose (-v) afin que le serveur laffiche.
Si vous ne demandez pas dadresse particulire au moyen de address (-a), le serveur
sexcute sur toutes vos adresses rseau. Si votre ordinateur est reli un rseau, cela signifie que
dautres personnes pourront y accder, si tant est quils connaissent le port utiliser et une page
demander. Pour des raisons de scurit, il est conseill dutiliser ladresse localhost jusqu
ce que vous soyez sr que le serveur fonctionne correctement et ne rvle pas dinformations
confidentielles. Lutilisation de cette adresse oblige le serveur utiliser le priphrique rseau
de bouclage (appel lo) seuls les programmes sexcutant sur le mme ordinateur peuvent sy
connecter. Si vous spcifiez une adresse diffrente, elle doit correspondre votre ordinateur :
% ./server --address localhost --port 4000

Le serveur est dsormais oprationnel. Ouvrez un navigateur et essayer de vous y connecter en


utilisant le bon numro de port. Demandez une page correspondant lun des modules. Par
exemple, pour invoquer le module diskfree.so, utilisez cette URL :
http://localhost:4000/diskfree

Au lieu de 4000, utilisez le numro de port que vous avez spcifiez (ou le port choisi pour vous
par Linux). Appuyez sur Ctrl+C pour tuer le serveur lorsque vous en avez fini.
Si vous ne spcifiez pas localhost comme adresse pour le serveur, vous pouvez galement
vous y connecter partir dun autre ordinateur en utilisant le nom du vtre dans lURL par
exemple :
http://host.domain.com:4000/diskfree

Si vous passez loption verbose (-v), le serveur affiche des informations au dmarrage et affiche
ladresse IP de chaque client se connectant. Si vous vous connecter via localhost, ladresse sera
toujours 127.0.0.1.
Si vous tentez dcrire vos propres modules, vous pourriez les placer dans un rpertoire
diffrent de celui contenant le serveur. Dans ce cas, indiquez ce rpertoire au moyen de loption
module-dir (-m). Le serveur recherchera les modules dans ce rpertoire.
Si vous oubliez la syntaxe des options en ligne de commande, invoquez server avec loption
help (-h) :

232

CHAPITRE 11. APPLICATION GNU/LINUX DILLUSTRATION


% ./server --help
Usage: ./server [ options ]
-a, --address ADDR
Bind to local address (by default, bind
to all local addresses).
-h, --help
Print this information.
-m, --module-dir DIR
Load modules from specified directory
(by default, use executable directory).
-p, --port PORT
Bind to specified port.
-v, --verbose
Print verbose messages.

11.5

Pour finir

Si vous aviez rellement lintention de publier ce programme, vous devriez crire de la documentation. Beaucoup de gens ne se rendent pas compte que lcriture dune bonne documentation
est aussi difficile et prend autant de temps et est aussi important que lcriture de bons
logiciels. Cependant, la documentation des logiciels pourrait faire lobjet dun livre entier, nous
vous laisserons donc avec quelques rfrences vous permettant den apprendre plus sur la faon
de documenter un logiciel GNU/Linux.
Vous aurez probablement besoin dcrire une page de manuel pour le programme server, par
exemple. Cest le premier endroit o la plupart des utilisateurs vont rechercher des informations
sur un programme. Les pages de manuel sont formates en utilisant un systme classique sous
UNIX, troff. Pour consulter la page de manuel de troff, qui dcrit le format des fichiers troff,
utilisez la commande suivante :
% man troff

Pour en savoir plus sur lorganisation des pages de manuel sous GNU/Linux, consultez la page
de manuel de la commande man via :
% man man

Vous pouvez galement crire des pages info pour le serveur et les modules, en utilisant le
systme Info GNU. Naturellement, le systme Info est document au format Info ; pour consulter
la documentation, invoquez :
% info info

Beaucoup de programmes GNU/Linux proposent galement une documentation texte ou HTML.


Bonne programmation sous GNU/Linux !

Troisime partie

Annexes

233

Table des Matires


A Autres outils de dveloppement

237

B E/S de bas niveau

257

C Tableau des signaux

273

D Ressources en ligne

275

E Licence Open Publication version 1.0

277

281

Licence Publique Gnrale GNU

236

TABLE DES MATIRES

Annexe A

Autres outils de dveloppement


L

e dveloppement de programmes GNU/Linux rapides et fiables 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 effectivement 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 affiche 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 vrifications. Par exemple, le compilateur affichera 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
237

238

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 profiler 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 fichier 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 vrification 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

239

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 fin de la zone alloue ;
criture dune position se trouvant aprs la fin 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 diffrentes 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 vrifications possibles.

Tab. A.1 Fonctionnalits de Diffrents Outils de Vrification 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 vrification fournie par
malloc et mtrace, qui sont les deux plus simples, puis nous nous intresserons ccmalloc et
Electric Fence.

240

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
diffrentes :
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

Vrification 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 dfinition 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 vrifications de malloc. La valeur 2 provoque larrt immdiat
du programme lors de la dtection dune erreur.
Lutilisation de la vrification par malloc est avantageuse car elle ne ncessite aucune recompilation, mais ltendue des vrifications 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 vrification
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

241

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. Modifier 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 fichier 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 fichier
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 fichier spcifi 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 fichier 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. Les
sources sont disponibles sur http://www.inf.ethz.ch/personal/biere/projects/ccmalloc/.

242

ANNEXE A. AUTRES OUTILS DE DVELOPPEMENT

Une fois celles-ci dcompresses, lancez configure. Excutez make et make install, copiez
le fichier ccmalloc.cfg 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 fichiers 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

243

Pour utiliser ccmalloc afin de diagnostiquer des criture avant le dbut ou aprs la fin
dune zone mmoire alloue, vous devrez modifier le fichier .ccmalloc dans le rpertoire du
programme. Ce fichier 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 fichiers 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 vrifie 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 fin de la premire page, ainsi, tout
accs aprs la fin 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 fins de dbogage.

A.2.6

Choisir parmi les diffrents 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

244

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 afin 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 effectuer 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

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 )

245

246

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;
}

93
94
95
96
97
98
99
100
101
102

}
free (( void *) array ) ;
return 1;

103
104
105
106

A.3

Profilage

Maintenant que votre programme est correct (esprons-le), nous allons voir comment amliorer ses performances. Avec laide du profileur 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 profilage demande de suivre trois tapes :
1. Compiler et lier votre programme de faon activer le profilage ;
2. Excuter votre programme de faon gnrer les donnes de profilage ;
3. Utiliser gprof pour analyser et afficher les donnes de profilage.
Avant dillustrer ces diffrentes tapes, nous allons prsenter une programme suffisamment
important pour quil soit possible de le profiler.

A.3.1

Une calculatrice simplifie

Pour illustrer le profilage, nous allons utiliser un programme faisant office de calculatrice.
Pour nous assurer que son excution prend suffisamment 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 fin 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 fichier number.c contient
les fonctions pour crer le nombre 0, ajouter 1 un chiffre, 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 enfin, une dernire fonction permet de passer
dun nombre unaire un int. Laddition est implante en effectuant des additions successives

A.3. PROFILAGE

247

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 postfixes1 sur une ligne et affiche 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, dfinie dans calculator.c, lit chaque expression et stocke les valeurs intermdiaires sur une pile de nombres unaires, dfinie dans stack.c. La pile stocke ses nombres
unaires dans une liste chane.

A.3.2

Collecter des informations de profilage

La premire tape dans le profilage dun programme est de marquer son excutable de faon
ce quil collecte des informations de profilage. 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 fichier 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 profiler. Le programme doit se terminer normalement pour
que le fichier de donnes de profilage soient crites correctement.

A.3.3

Affichage des donnes de profilage

partir du nom dun excutable, gprof analyse le fichier gmon.out pour afficher des
informations sur le temps pass dans chaque fonction. Par exemple, examinons les donnes
de profilage "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 postfixe, 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 +.

248

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 profilage apparaissent dans les donnes.
En plus des donnes brutes de profilage, 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

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]
[1]

100.0

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

249

t appel deux fois pendant toute la dure dexcution du programme. Lappelant de main tait
<spontaneous>, ce qui signifie que le profileur 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 fils. 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 bnficie de son propre horodatage et est affich
individuellement dans le graphe dappel. Considrons ces donnes venant du profilage 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 affiche la somme des rsultats pour plusieurs excutions conscutives ;
Loption -c permet didentifier les fils qui auraient pu tre appels mais ne lont pas t ;
Loption -l pour afficher des informations de profilage ligne par ligne.
Loption -A pour afficher 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 profilage 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,

250

ANNEXE A. AUTRES OUTILS DE DVELOPPEMENT

larrt de lexcution ne peut avoir lieu quau maximum toutes les 0,01 secondes. Ainsi, le
profilage 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 profilage 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 postfixes.


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
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;

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

251

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 )

*/

252

ANNEXE A. AUTRES OUTILS DE DVELOPPEMENT


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

{
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

253

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;

*/

254

ANNEXE A. AUTRES OUTILS DE DVELOPPEMENT


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

}
/* 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 (definitions.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

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 */

255

256

ANNEXE A. AUTRES OUTILS DE DVELOPPEMENT

Annexe B

E/S de bas niveau


L

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 offertes 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 efficace deffectuer 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 laffiche sur la sortie
standard. La version gnrique, fprintf, peut afficher le texte sur un autre flux que la sortie
standard. Un flus est reprsent par un pointeur FILE*. Vous obtenez un tel pointeur en ouvrant
un fichier avec fopen. Lorsque vous en avez fini, vous pouvez le fermer avec fclose. En plus de
1

La bibliothque C++ standard fournit les flux 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 diffrence entre un
appel systme et un appel de fonction traditionnelle.

257

258

ANNEXE B. E/S DE BAS NIVEAU

fprintf, vous pouvez utiliser dautres fonctions comme fputc, fputs ou fwrite pour crire des
donnes dans un flux, ou fscanf, fgetc, fgets ou fread pour lire des donnes.

Avec les oprations dE/S de bas niveau de Linux, vous utilisez descripteur de fichier au
lieu dun pointeur FILE*. Un descripteur de fichier est un entier qui fait rfrence une instance
donne dun fichier ouvert au sein dun processus. Il peut tre ouvert en lecture, en criture
ou en lecture/criture. Un descripteur de fichier ne fait pas forcment rfrence un fichier
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 fichier (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 fichiers 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 fichier et obtenir un descripteur de fichier pouvant y accder, utilisez lappel
open. Il prend le chemin du fichier ouvrir sous forme dune chane de caractres et des
indicateurs spcifiant comment il doit ltre. Vous pouvez utiliser open pour crer un nouveau

fichier ; pour cela, passez un troisime argument dcrivant les droits daccs appliquer au
nouveau fichier.
Si le second argument est O_RDONLY, le fichier est ouvert en lecture seul ; un erreur sera
signale si vous essayez dy crire. De mme, O_WRONLY ouvre le descripteur de fichier en criture
seule. Passer O_RDWR cre un descripteur de fichier pouvant tre utilis la fois en lecture et
en criture. Notez que tous les fichiers ne peuvent pas tre ouverts dans les trois modes. Par
exemple, les permissions dun fichier peuvent interdire un processus de louvrir en lecture ou
en criture ; un fichier 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 fichier ouvert, sil existait auparavant. Les donnes crites
remplaceront le contenu du fichier ;
Passez O_APPEND pour ajouter les donnes au contenu dun fichier existant. Elles sont
crites la fin du fichier ;
Passez O_CREAT pour crer un nouveau fichier. Si le nom de fichier que vous passez open
correspond un fichier inexistant, un nouveau fichier sera cr, si tant est que le rpertoire
le contenant existe et que le processus a les permissions ncessaires pour crer des fichiers
dans ce rpertoire. Si le fichier existe dj, il est ouvert ;
Passez O_EXCL et O_CREATE pour forcer la cration dun nouveau fichier. Si le fichier existe
dj, lappel open chouera.
Si vous appelez open en lui passant O_CREATE, fournissez un troisime argument indiquand les
permissions applicable au nouveau fichier. 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

259

Par exemple, le programme du Listing B.1 cre un nouveau fichier avec le nom de fichier
indiqu sur la ligne de commande. Il utilise lindicateur O_EXCL avec open afin de signaler une
erreur si le fichier existe dj. Le nouveau fichier 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 diffrente de zro, les permissions effectives pourraient
tre plus restrictives).

Umasks
Lorsque vous crez un nouveau fichier avec open, certains des bits de permissions peuvent tre
dsacrivs. Cela survient lorsque votre umask est diffrent de zro. Lumask dun processus
spcifie les bits de permissions qui sont masqus lors de nimporte quelle cration de fichier. Les
permissions effectives 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 fichiers.

Listing B.1 (create-file.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 :

260

ANNEXE B. E/S DE BAS NIVEAU


% ./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

Notez que le fichier fait zro octet car le programme ny a crit aucune donne.

B.1.2

Fermer un fichier

Lorsque vous en avez fini avec un descripteur de fichier, 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 fichiers lorsquun processus se termine
(cest--dire la fin du programme). Bien sr, une fois que vous avez ferm un descripteur de
fichier, vous ne pouvez plus lutiliser.
La fermeture dun descripteur de fichier peut dclencher des actions spcifiques de la part
de Linux, selon la nature du descripteur de fichier. 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 fichiers quun processus peut maintenir ouverts
en mme temps. Les descripteurs de fichiers ouverts utilisent des ressources noyau, il est donc
conseill de fermer les descripteurs de fichiers 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 fichier se fait par le biais de lappel write. Passz lui un descripteur
de fichier, 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 fichier.
Le programme du Listing B.2 crit lheure courante la fin du fichier pass sur la ligne de
commande. Si le fichier 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

261

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 fichier tsfile, alors que
la seconde fois, les donnes sont ajoutes la fin.
Lappel write renvoie le nombre doctets effectivement crits ou -1 si une erreur survient.
Pour certains types de descripteurs de fichiers, 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 effectuer 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 ;
}

262

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 ;

16
17
18
19
20

B.1.4

Lecture de donnes

Lappel permettant de lire des donnes est read. Tout comme write, il prend en arguments
un descripteur de fichier, 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 effectivement lus. Il peut tre infrieur au nombre doctets demand, par
exemple, sil ne reste pas suffisamment doctets lire dans le fichier.
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 fichiers
texte gnrs par des programmes DOS ou Windows. Il est important danticiper une diffrence
majeure entre les deux plateformes dans la faon de structurer les fichiers texte.
Dans les fichiers 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 affichent un M la fin de chaque ligne lorsquils affichent
le contenu dun fichier texte Windows il sagit du caractre de retour chariot. Emacs affiche
les fichiers texte Windows correctement mais les signale en affichant (DOS) dans la barre de
mode en bas du tampon. Certains diteurs Windows, comme Notepad, affichent tout le texte
des fichiers GNU/Linux sur une seule ligne car ils ne trouvent pas le caractre de retour chariot
la fin de chaque ligne. Dautres programmes, que ce soit sous GNU/Linux ou Windows, peuvent
signaler des erreurs tranges lorsque les fichiers qui leur sont fournis en entre ne sont pas au
bon format.
Si votre programme lit des fichiers 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 fichiers 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 affiche une image hexadcimale du contenu du fichier pass sur la ligne de commande. Chaque ligne affiche le dplacement
dans le fichier et les 16 octets suivants.

B.1. LIRE ET CRIRE DES DONNES

263

Listing B.4 (hexdump.c) Affichage de 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

# 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 affiche 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 diffrente 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 fichier connait sa position dans le fichier. Lorsque vous y crivez ou que
vous y lisez, sa position est modifie selon le nombre doctets lus ou crits. Parfois, cependant,
vous pouvez avoir besoin de vous dplacer dans un fichier sans lire ou crire de donnes. Par
exemple, vous pouvez vouloir crire au milieu dun fichier sans en modifier le dbut, ou vous
pouvez avoir besoin de retourner au dbut dun fichier et de le relire sans avoir le rouvrir.
Lappel lseek vous permet de modifier votre position dans un fichier. Passez lui le descripteur
deux fichier et deux autres arguments indiquand la nouvelle position.

264

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 fichier ;
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 fin du fichier. Une valeur positive indique une position au-del
de la fin du fichier.
Lappel lseek renvoie la nouvelle position, sous forme dun dplacement par rapport au dbut
du fichier.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 fichiers comme les sockets.
Si vous voulez obtenir votre position dans un fichier sans la modifier, 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 spcifier une position au-del de la fin du fichier.
Normalement, si un descripteur de fichier est positionn la fin dun fichier et que vous y crivez,
Linux augmente automatiquement la taille du fichier pour faire de la place pour les nouvelles
donnes. Si vous indiquez une position au-del de la fin du fichier puis que vous y crivez, Linux
commence par agrandir le fichier de la taille du "trou" que vous avez cr en appelant lseek
puis crit la fin de celui-ci. Ce trou noccupe toutefois pas de place sur le disque ; Linux note
simplement sa taille. Si vous essayez de lire le fichier, il apparait comme sil tait rempli doctets
nuls.
En utilisant cette proprit de lseek, il est possible de crer des fichier 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 fichier et sa taille, en mgaoctets.
Le programme ouvre un nouveau fichier, se place aprs la fin de celui-ci en utilisant lseek puis
crit un octet 0 avant de fermer le fichier.
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

265

Voici comment crer un fichier 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 significatif 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 affichier 230 octets nuls.
Notez que ces trous magiques dans les fichiers sont une fonctionalit spciale du systme
de fichier ext2 qui est traditionnelement utilis pour les disques GNU/Linux. Si vous utilisez
lseek-huge pour crer un fichier sur un autre type de systme de fichiers, comme fat ou vfat
qui sont utiliss sur certaines partitions DOS et Windows, vous constaterez que le fichier occupe
effectivement autant despace que sa taille.
Linux nautorise pas le positionnement avant le dbut du fichier avec lseek.

B.2

stat

En utilisant open et read, vous pouvez extraire le contenu dun fichier. Mais quen est-il des
autres informations ? Par exemple, la commande ls -l affiche des informations comme la taille,
la date de dernire modification, les permissions ou le propritaire pour les fichiers du rpertoire
courant.
Lappel stat rcupre ces informations pour un fichier. Appelez stat en lui passant le chemin
du fichier 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 fichier ; sinon, il renvoie -1.
Voici les champs les plus intressant dune struct stat :
st_mode contient les permissions du fichier. Ces permissions sont dcrites dans la Section 10.3, Permissions du systme de fichiers ;

266

ANNEXE B. E/S DE BAS NIVEAU

En plus des permissions clasiques, le champ st_mode encode le type de fichier dans les bits
de poids fort. Consultez les explication ci-dessous pour savoir comment le dcoder ;
st_uid et st_gid contiennent les identifiants de lutilisateur et du groupe auxquels le fichier
appartient, respectivement. Les identifiants de groupe et dutilisateur sont dtaills dans
la Section 10.1, Utilisateurs et groupes ;
st_size contient la taille du fichier, en octets ;
st_atime contient la date de dernier accs au fichier (en lecture ou criture) ;
st_mtime contient la date de dernire modification du fichier.
Un certain nombre de macros analysent la valeur du champ st_mode pour dterminer le type
de fichier sur lequel stat a t invoqu. Une macro vaut vrai si le fichier est de ce type :
S_ISBLK (mode)
priphrique bloc
priphrique caractre
S_ISCHR (mode)
S_ISDIR (mode)
rpertoire
S_ISFIFO (mode) fifo (canal nomm)
S_ISLNK (mode)
lien symbolique
S_ISREG (mode)
fichier classique
S_ISSOCK (mode) socket
Le champ st_dev contient les numros de priphrique majeur et mineur du dispositif matriel
sur lequel le fichier 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 fichier. Cela
situe le fichier sur le systme de fichiers.
Si vous appelez stat sur un lien symbolique, stat suit le lien et vous renvoie les informations
sur le fichier sur lequel il pointe, pas sur le lien symbolique. Cela signifie 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
fichier cible. Si vous appelez lstat sur un fichier 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 fichier
inexistant ou inaccessible) provoque une erreur, alors que lappel de lstat sur le mme fichier
na aucune incidence.
Si vous disposez dj dun fichier ouvert en lecture/criture, appelez fstat au lieu de stat.
Il prend un descripteur de fichier en premier argument la place du chemin vers le fichier.
Le Listing B.6 prsente une fonction qui alloue un tampon suffisamment long pour recevoir
le contenu du fichier et charge les donnes lintrieur. Elle utilise fstat pour dterminer la
taille du tampon allouer et vrifier que le fichier est bien un fichier classique.
Listing B.6 (read-file.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

{
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 ;

12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

B.3

267

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 fichier. Cependant, un programme a souvent besoin dcrir plusieurs lments de donnes,
chacun se trouvant un endroit diffrent. 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 effectuer de multiples appels write.
Pour certaines applications, appeler write plusieurs fois peut tre inefficace 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 fichier 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 fichier 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.

268

ANNEXE B. E/S DE BAS NIVEAU

Le programme du Listing B.7 crit ses arguments de ligne de commande dans un fichier en
utilisant un unique appel writev. Le premier argument est le nom du fichier 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

# 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;

23

/* 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 ) ) ;

24
25
26
27
28

/* 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 ;
}

29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

/* crit les arguments dans le fichier . */


fd = open ( filename , O_WRONLY | O_CREAT ) ;
writev ( fd , vec , 2 * argc ) ;
close ( fd ) ;

44
45
46
47
48

free ( vec ) ;
return 0;

49
50
51

B.4. LIEN AVEC LES FONCTIONS DE/S STANDARDS DU C

269

Voici un exemple dexcution de write-args.


% ./write-args fichiersortie "premier arg" "deuxime arg" "troisme arg"
% cat outputfile
premier arg
deuxime arg
troisime arg

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 fichier.

B.4

Lien avec les fonctions 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 fichiers ou les
fonctions de bas niveau sur un flux FILE*. GNU/Linux autorise ces deux pratiques.
Si vous avez ouvert un fichier en utilisant fopen, vous pouvez obtenir le descripteur de fichier
sous-jacent par le biais de la fonction fileno. Elle prend un paramtre FILE* et renvoie le
descripteur de fichier. Par exemple, pour ouvrir un fichier 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 fichier. 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 effectuer lopration inverse, obtenir un flux partir dun descripteur, utilisez la fonction
fdopen. Elle produit un pointeur FILE* correspondant un descripteur de fichier. La fonction
fdopen prend en paramtres un descripteur de fichier et une chane correspondant au mode dans
lequel le flux 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 fichier. Par exemple, passez la
chaine de mode r pour un descripteur de fichier en lecture ou w pour un descripteur de fichier en
criture. Comme pour fileno, le flux et le descripteur de fichier font rfrence au mme fichier,
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 fichiers et rpertoires peut savrer
utile :

270

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 affectes par lumask du processus ;
rmdir supprime le rpertoire dont le chemin est pass en paramtre ;
unlink supprime le fichier dont le chemin est pass en paramtre. Cet appel peut galement
tre utilis pour supprimer dautres objets du systme de fichiers, 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 fichier. Comme son nom lindique,
il rompt le lien entre le fichier et le rpertoire qui le contient. Le fichier napparait plus
dans le listing du rpertoire mais si un processus dtient un descripteur de fichier ouvert
sur ce fichier, le contenu nest pas effac du disque. Cela narrive que lorsque quaucun
processus ne dtient de descripteur de fichier ouvert. Ainsi, si un priocessus ouvre un fichier
pour y crire ou y lire et quun second processus supprime le fichier avec unlink et cre un
nouveau fichier avec le mme nom, le premier processus "voit" lancien contenu du fichier
et non pas le nouveau ( moins quil ne ferme le fichier et le r-ouvre) ;
rename renomme ou dplace un fichier. Le premier argument correspond au chemin courant
vers le fichier, le second au nouveau chemin. Si les deux chemins sont dans des rpertoires
diffrents, rename dplace le fichier, si tant est que le nouveau chemin est sur le mme
systme de fichiers que lancien. Vous pouvez utiliser rename pour dplacer des rpertoires
ou dautres objets du systme de fichiers.

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 fin 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 fin du parcours.
Incluez <sys/types.h> et <dirent.h> si vous utilisez ces fonctions dans votre programme.

B.6. LIRE LE CONTENU DUN RPERTOIRE

271

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 affiche le contenu dun rpertoire. Celui-ci peut tre spcifi
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 affich. La fonction get_file_type utilise lstat pour
dterminer le type dune entre.
Listing B.8 (listdir.c) Affiche 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 ;

272

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;

52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

Voici les premires lignes affiches lors de laffichage du contenu de /dev (elles peuvent tre
diffrentes 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 vrifier 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 affiche 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 diffrentes faons
selon lendroit o ils surviennent.
Les noms des signaux prsents ici sont dfinis sous forme de macros prprocesseur. Pour les
utiliser dans votre programme utilisez <signal.h>. Les dfinitions proprement dites se trouvent
dans le fichier /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

273

274

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 fichiers de configuration.
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
flottante invalide. Selon la manire dont le CPU est configur,
une opration en virgule flottante peut renvoyer une valeur nonnumrique spciale comme inf (infini) 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 effectu 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 flux 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 fils 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
C

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 officiel 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 ;
275

276

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 AMD
et leurs fonctionnalits spcifiques ;
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 recense 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

Licence Open Publication version 1.0


Cette traduction de lOpen Publication License nest pas officielle. 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 modifies 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 :
c <anne> <auteur ou mandataire>. Ce document ne peut tre distribu
Copyright
que dans le respect des termes et conditions dfinies 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.
277

278

III.

ANNEXE E. LICENCE OPEN PUBLICATION VERSION 1.0

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 modifis

Toute version modifie 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 modifie doit tre indique comme telle ;
2. La personne effectuant les modifications doit tre identifie et les modifications 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 identifi ;
5. Les noms des auteurs originaux ne doit pas tre utilis pour cautionner le document modifi
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, afin de donner aux auteur le temps de fournir des documents jour. Cette
notification doit mentionner, sil y a lieu, les modifications apportes au document ;
2. Toute modification substancielle (y compris les suppressions) doit tre clairement signale
dans le document ou dcrite dans une note attache au document ;

VI.. OPTIONS POSSIBLES

279

3. Enfin, bien que cela ne soit pas impos par cette licence, il est de bon ton doffrir 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 modifications 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 modifies de faon substancielle sans lautorisation
explicite des auteurs. Le terme "modifications substancielles" est dfinie comme tant une
modification du contenu smantique du document et exclut les modifications ne touchant
que le formatage ou les corrections typographiques.
Pour cela, ajoutez la mention "La distribution de versions de ce document modifies 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.

280

ANNEXE E. LICENCE OPEN PUBLICATION 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 officielle 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 modifier. A contrario, la Licence Publique Gnrale est destine garantir
votre libert de partager et de modifier 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 modifier 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 modifiez.
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
281

282

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 afin 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 modifier 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
modifi 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 dfinitive, 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 effet 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 modification sont les suivants :

Conditions de copie, distribution et modification 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 modifications et/ou traduit dans un autre langage. (Ci-aprs, le terme "modification"
implique, sans sy rduire, le terme traduction) Chaque concessionaire sera dsign par "vous".
Les activits autres que la copie, la distribution et la modification 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.

283
2. Vous pouvez modifier 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
modifications ou ouvrage selon les termes de lArticle 1 ci-dessus, condition de vous conformer
galement chacune des obligations suivantes :
a) Vous devez munir les fichiers modifis davis bien visibles stipulants que vous avez
modifi ces fichiers, ainsi que la date de chaque modification ;
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 modifi 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 affiche 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 modifi pris comme un tout. Si des lments identifiables
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 indiffrement 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

284

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 modifications 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 fichiers de dfinition
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 offrir un accs permettant leur copie
depuis un endroit particulier, alors loffre 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, modifier, concder en sous-licence, ou distribuer le Programme, sauf
tel quexpressment prvu par la prsente Licence. Toute tentative de copier, modifier, concder
en sous-licence, ou distribuer le Programme dune autre manire est rpute non valable, et met
immdiatement fin 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 modifier 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 modifiant ou distribuant le Programme
(ou un ouvrage quelconque fond sur le Programme), vous signifiez votre acceptation de la
prsente Licence en le faisant, et de toutes ses conditions concernant la copie, la distribution ou
la modification 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 modifier 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

285
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 fiant
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 afin 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 diffrer 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 spcifie 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 diffrentes, 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.

286

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 modifier 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 fichier source pour vhiculer le plus efficacement possible labsence de toute
garantie ; chaque fichier 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 modifier 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.

287
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 affiche 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, 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.

Vous aimerez peut-être aussi