Vous êtes sur la page 1sur 44

Programmation systme

Juliusz Chroboczek

septembre
Table des matires

Systmes dexploitation
. Accs au matriel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. Programmation sur matriel nu . . . . . . . . . . . . . . . . . . . . . . . .
.. HAL et moniteur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. Systme dexploitation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. Virtualisation et abstraction du matriel . . . . . . . . . . . . . . . . . . .
. Le systme Unix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. Fonctions stub . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. Le standard POSIX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Le systme de chiers
. Structure du systme de chiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. Entres/sorties de bas niveau . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. Tampons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. Tampon simple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. Tampon avec pointeur de dbut des donnes . . . . . . . . . . . . . . . . .
.. Tampon circulaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. La bibliothque stdio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. La structure FILE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. Entres/sorties de haut niveau . . . . . . . . . . . . . . . . . . . . . . . . .
.. Interaction entre stdio et le systme . . . . . . . . . . . . . . . . . . . . .
.. Entres/sorties formates . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. Le pointeur de position courante . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. Lappel systme lseek . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. Le mode ajout la n . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. Troncation et extension des chiers . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. Extension implicite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. Troncation et extension explicite . . . . . . . . . . . . . . . . . . . . . . . .
. I-nuds . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. Lecture des i-nuds . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. Modication des i-nuds . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. Liens et renommage de chiers . . . . . . . . . . . . . . . . . . . . . . . . .
.. Comptage des rfrences . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. Manipulation de rpertoires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. Rsolution de noms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. Lecture des rpertoires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


. Liens symboliques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Processus
. Lordonnanceur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. Appels systme bloquants . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. Contenu dun processus Unix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. La commande ps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. Appels systme daccs au processus . . . . . . . . . . . . . . . . . . . . . .
. Vie et mort des processus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. Cration de processus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. Mort dun processus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. Excution de programme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. Fonctions utilitaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. Parenthse : _start . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. Excution dun programme dans un nouveau processus . . . . . . . . . . . . . . .
.. Le double fork . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. Redirections et tubes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. Descripteurs standard . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. Redirections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. Tubes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Entres-sorties non-bloquantes
. Mode non-bloquant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. Attente active . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. Lappel systme select . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. La structure timeval . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. Ensembles de descripteurs . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. Lappel systme select . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. Exemples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. Bug . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Systmes dexploitation
. Accs au matriel
Dans un ordinateur, le processeur communique non seulement avec la mmoire principale,
qui contient le code excuter et les donnes sur lesquelles celui-ci opre, mais aussi avec des
priphriques de stockage de masse, ou mmoire secondaire, et des priphriques dentres sorties.
Le programmeur doit avoir accs des facilits qui lui permettent daccder ceux-ci.

.. Programmation sur matriel nu


Il est bien-sr possible daccder aux priphriques directement partir du code du programme
(le code utilisateur) ; on parle alors de programmation sur matriel nu. Cette approche prsente
plusieurs dsavantages :
le code dpend dune connaissance intime des dtails du matriel, et le changement dun
priphrique force la recriture dune quantit potentiellement grande de code ;
le code nest pas modulaire : laccs au matriel est mlang la logique du programme
elle-mme.
De nos jours, la programmation sur matriel nu nest plus praticable sur les ordinateurs clas-
siques, du fait de lexistence de matriel complexe, htrogne, et partag entre direntes appli-
cations (cest notamment le cas des disques). Elle se pratique encore dans les systmes dits em-
barqus, par exemple votre four micro-ondes. Cependant, elle est de plus en plus rare : mme
votre tlphone portable a un systme dexploitation.

.. HAL et moniteur
An dviter de rendre nos programmes dpendants des dtails du matriel et an de spa-
rer la logique du programme de laccs au matriel, il est naturel de mettre le code dit de bas
niveau (proche du matriel) dans une bibliothque spare. Une telle bibliothque est commun-
ment appele HAL, acronyme dvelopp, selon les auteurs, en Hardware Abstraction Layer ou en
Hardware Access Library. (De nos jours, la HAL est elle-mme modulaire elle est constitue de
lensemble des pilotes de priphrique (drivers).)

Moniteur Si la mme HAL est utilise par tous les programmes, elle est souvent rsidente, cest
dire charge une seule fois lors du lancement (boot)) du systme et prsente en mmoire tout
moment.
Un moniteur est compos dune HAL rsidente et dune interface utilisateur permettant no-
tamment de charger des programmes en mmoire et de les excuter, et parfois aussi de contrler


manuellement les priphriques (par exemple de renommer des chiers sur disque, ou de mani-
puler limprimante). Un exemple bien connu de moniteur est le systme MS-DOS.

.. Systme dexploitation
Un moniteur permet disoler le code utilisateur des dpendances matrielles ; cependant, luti-
lisation du moniteur nest pas obligatoire, et il reste possible de le contourner et daccder direc-
tement au matriel. En consquence, un moniteur nest pas susant pour sassurer quaucune
contrainte de scurit ou de modularit nest viole par le code utilisateur, ce qui est important
notamment sur une machine partage entre plusieurs utilisateurs.
Un systme dexploitation, ou noyau du systme dexploitation (operating system kernel), est une
couche situe entre le logiciel utilisateur et le matriel dont lutilisation est obligatoire pour tout
processus voulant accder au matriel.

Protection matrielle Ce qui prcde implique que le noyau doit empcher le code utilisateur
daccder directement au matriel, ce qui est gnralement implment en distinguant au niveau
matriel entre deux types de code : le code dit privilgi ou code noyau, qui a le droit daccder
au matriel, et le code dit non-privilgi ou code utilisateur, qui nen a pas le droit. Lorsquun
programme utilisateur essaie daccder (dlibrment, ou, comme cest le plus souvent le cas, par
erreur) au matriel ou des parties de la mmoire qui lui sont interdites, le matriel de protection
passe la main au noyau qui, typiquement, tue le processus coupable .

Appel systme Linvocation de code privilgi par du code non-privilgi se fait au moyen dun
appel systme. Un appel systme est semblable un appel de fonction, mais eectue en plus deux
changements de niveau de privilge : une transition vers le mode privilgi lors de lappel, et
une transition vers le mode non-privilgi lors du retour. De ce fait, un appel systme est lent,
typiquement entre et fois plus lent quun appel de fonction sur du matriel moderne.

.. Virtualisation et abstraction du matriel


La vision donne par le systme des priphriques nest pas compltement uniforme. Dans
certains cas, le systme se contente de vrier les privilges du processus avant de lui donner
accs au vrai matriel ; on parle alors de simple mdiation.
Dans dautres cas, le systme prsente au processus utilisateur des priphriques virtuels, qui se
comportent comme de vrais priphriques, mais ne correspondent pas directement la ralit ;
par exemple, il peuvent tre plus nombreux que les priphriques rels. Cest par exemple souvent
le cas de la mmoire et des consoles. On parle alors de virtualisation du matriel.
Enn, il arrive que le systme prsente des priphriques abstraits, de plus haut niveau que
les priphriques rels. Cest gnralement le cas des disques (qui sont prsents comme une
arborescence de chiers plutt quune suite de secteurs) et des interfaces rseau. On parle alors
dabstraction.

Unix, par exemple, envoie un signal SIGSEGV ou SIGBUS au processus.


. Le systme Unix
Unix, une famille de systmes dexploitation base sur le systme Unix des Bell Labs dAT&T, d-
velopp dans les annes , servira de base ce cours. La famille Unix a aujourdhui de nombreux
reprsentants, aussi bien libres (Linux, FreeBSD, NetBSD, Minix etc.) que moins libres (Solaris)
et propritaires (HP/UX, Mac OS X etc.).

.. Fonctions stub
Le mcanisme exact dappel systme dpend du matriel. Pour cette raison, chaque systme
Unix inclut dans la librairie C standard (libc) un certain nombre de fonctions dites stubs ou
wrappers, dont chacune a le rle dinvoquer un appel systme. Par exemple, lappel systme time
est invoqu par la fonction

int time(int *) ;

dclare dans le chier dentte <time.h>.


Le manuel Unix (accessible laide de la commande man) distingue entre les stubs dappels
systme, quil documente dans la section , et les vraies fonctions, quil documente dans la
section .

Convention dappel et variable errno La librairie standard dnit une variable globale ap-
pele errno qui sert communiquer les rsultats derreur entre les fonctions stub et le code
utilisateur. Lorsquun appel systme russit, une fonction stub retourne un entier positif ou nul ;
lorsquil choue, elle stocke le numro de lerreur dans la variable errno, et retourne une valeur
strictement ngative .
La variabe errno ainsi que des constantes symboliques pour les codes derreurs sont dcla-
res dans le chier dentte <errno.h>. Pour acher les messages derreurs, on peut utiliser les
fonctions strerror et perror (dclares dans <string.h> et <stdio.h> respectivement).

Excution dune fonction stub Une fonction stub eectue les actions suivantes :
stocker les arguments de lappel dans les bons registres ;
invoquer lappel systme ;
interprter la valeur de retour et, si besoin, positionner la variable errno.

.. Le standard POSIX
Au dbut des annes , Unix stait morcel en un certain nombre de systmes semblables
mais incompatibles. Lcriture dun programme portable entre les dirents dialectes dUnix tait
devenue un exercice inutilement excitant.

Il y a quelques exceptions cette convention.


Ce mcanisme, qui sert compenser labsence dexceptions en C, pose certains problmes pour lcriture de code
able en prsence de threads multiples.


Le standard IEEE , communment appel POSIX (Portable Operating System Interface), a
dni un ensemble de fonctions disponibles sur tous les systmes Unix. Tous les Unix modernes,
libres ou propritaires, implmentent ce standard.
Les interfaces POSIX restent en grande partie compatibles avec les interfaces Unix tradition-
nelles, mais remplacent les types concrets (tels que int) par des types dits opaques dont la d-
nition prcise nest pas spcie par POSIX, et est donc laisse la discrtion de limplmentation.
Par exemple, POSIX dnit lappel time comme

time_t time(time_t *) ;

o le choix du type concret pour lequel time_t est un alias est laiss limplmenteur. Typique-
ment, time_t est un alias pour int, dni par

typedef int time_t ;

mais rien nempche une implmentation de faire, par exemple,

typedef long int time_t ;

POSIX considre la dirence entre une fonction et un appel systme comme tant un dtail
de limplmentation, et ne direncie donc pas entre les sections et du manuel.
Depuis , chaque version du standard POSIX est identique une version de la Single Unix
Specication, qui est disponible en ligne ladresse http://www.unix.org/.


Le systme de chiers
Le disque dun ordinateur est une ressource partage : il est utilis par plusieurs programmes et,
sur un systme multi-utilisateur, par plusieurs utilisateurs. Les donnes sur le disque sont stockes
dans des chiers (le en anglais), des structures de donnes qui apparaissent aux programmes
comme des suites nies doctets. La structure de donnes qui organise les chiers sur le disque
sappelle le systme de chiers (le system).

. Structure du systme de chiers

bin home

jch
ls

.bashrc

Figure . : Vision abstraite du systme de chiers

Vision abstraite du systme de chiers Du point de vue du programmeur et de lutilisateur,


le systme de chiers apparat comme un arbre dont les feuilles sont les chiers (gure .). Un
nud interne de cet arbre sappelle un rpertoire (directory).
Un chier est identi par son chemin daccs (pathname), qui est constitu du chemin depuis
la racine dont les composantes sont spares par des slashes / . Par exemple, le chemin daccs
du chier en bas droite de la gure . est /home/jch/.bashrc.

Vision concrte du systme de chiers Concrtement (gure .), le systme de chiers est
constitu de deux types de structures de donnes : les i-nuds (i-nodes) et les donnes de chier.


i-noeud racine

entre de rpertoire

rpertoire

i-noeud

...

donnes de fichier

Figure . : Vision concrte du systme de chiers


Ces dernires peuvent tre soit des donnes de chier ordinaire, ou des rpertoires.
Intuitivement, li-nud cest le chier. Un i-nud est une structure de taille xe (quelques
centaines doctets) qui contient un certain nombre de mta-donnes propos dun chier son
type (chier ou rpertoire), sa taille, sa date daccs, son propritaire, etc. ainsi que susam-
ment dinformations pour retrouver le contenu du chier.
Le contenu du chier est (une structure de donnes qui code) une suite doctets. Dans le cas
dun chier ordinaire, ce contenu est interprt par lapplication, il na pas de signication pour le
systme. Dans le cas dun rpertoire, par contre, cest le systme qui linterprre comme une suite
dentres de rpertoire, dont chacune contient un nom de chier et une rfrence un i-nud.

Disque Mode noyau Mode utilisateur

i-noeud

Rpertoire Processus

Tables de descripteurs
i-noeud des processus

i-noeud
mmoire
Processus

Donnes

Table de fichiers ouverts

Figure . : Structures de donnes sur disque et en mmoire

Structures de donnes en mmoire Lorsque lutilisateur demande au systme de manipuler


un chier (en eectuant lappel systme open, voir ci-dessous), celui-ci charge li-nud corres-
pondant en mmoire ; il y a alors dans la mmoire du noyau un i-nud mmoire, une entre de la
table de chiers ouverts et une entre de la table de descripteurs de chier du processus.
Li-nud mmoire est une structure de donnes contenant le contenu de li-nud (disque)
ainsi que quelques donnes comptables supplmentaires (notamment le numro de priphrique
partir duquel cet i-nud a t charg).
Lentre de la table de chiers ouverts est une structure de donnes globale qui rfre li-nud
mmoire ; cette structure de donnes contient elle-mme quelques champs supplmentaires, no-
tamment le pointeur de position courante (voir partie . ci-dessous).


Lentre de la table de descripteurs de chiers est une structure qui rfre lentre de la table
de chiers ouverts. la dirence des deux structures de donnes ci-dessus, qui sont globales, il
sagit dune structure qui est locale au processus ayant ouvert le chier.
Ces structures de donnes vivent dans la mmoire du noyau ; le code utilisateur ne peut donc
pas y rfrer directement. Le code utilisateur indique une entre de la table de chiers travers
un petit entier, lindice dans la table de descripteurs de chiers, communment appel lui-mme
descripteur de chier.

. Entres/sorties de bas niveau


Sous Unix, les entres/sorties sur les chiers sont ralises par les appels systme open, close,
read, write.

open Lappel systme open est dni par


int open(char *pathname, int flags,... /* int mode */) ;

Son rle est de charger un i-nud en mmoire, crer une entre de la table de chiers qui y rfre,
et crer une entre dans la table de descripteurs de chiers du processus courant qui rfre cette
dernire. Il retourne le descripteur de chiers correspondant, ou - en cas derreur (et la variable
globale errno est alors positionne).
Le paramtre pathname est le nom du chier ouvrir. Le paramtre flags peut valoir
O_RDONLY, ouverture en lecture seulement ;
O_WRONLY, ouverture en criture seulement ;
O_RDWR, ouverture en lecture et criture.
Cette valeur peut en outre tre combine ( laide de la disjonction bit--bit | ) avec
O_CREAT, crer le chier sil nexiste pas ;
O_EXCL, chouer si le chier existe dj ;
O_TRUNC, tronquer le chier sil existe dj ;
O_APPEND, ouvrir le chier en mode ajout la n .
Le troisime paramtre, mode_t mode, nest prsent que si O_CREAT est dans flags. Il
spcie les permissions maximales du chier cr. On pourra le positionner 6668 (soit 0666
en C).

close Lappel systme close libre une entre de la table de descripteurs du processus cou-
rant, ce qui peut avoir pour eet de librer une entre de la table de chiers ouverts et un i-nud
en mmoire. Il est dni comme
int close(int fd) ;

Il retourne en cas de succs, et - en cas dchec . Cependant, la plupart du code Unix tradi-
tionnel ne vrie pas le rsultat retourn par close (ce qui cause parfois des problmes sur les
systmes de chiers monts travers le rseau).
Plus prcisment, cest la fonction stub correspondante qui est dnie comme-a.
Et la variable errno est alors positionne. Je ne le mentionne plus, dsormais.


read Lappel systme read est dni par

ssize_t read(int fd, void *buf, size_t count) ;

Sa fonction est de lire au plus count octets de donnes depuis le chier spci par le descripteur
de chiers fd et de les stocker ladresse buf.
Lappel read retourne le nombre doctets eectivement lus, la n du chier, ou - en cas
derreur.

write Lappel systme write est dni comme

ssize_t write(int fd, void *buf, size_t count) ;

Sa fonction est dcrire au plus count octets de donnes qui se trouvent ladresse buf dans le
chier spci par le descripteur de chiers fd.
Lappel write retourne le nombre doctets eectivement crits , ou - en cas derreur.

Exemple Le programme suivant eectue une copie de chiers. Comme nous le verrons, il est
extrmement inecace.
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv)


{
int fd1, fd2, rc ;
char buf ;

if(argc != 3) {
fprintf(stderr, "Syntaxe: %s f1 f2\n", argv[0]) ;
exit(1) ;
}

fd1 = open(argv[1], O_RDONLY) ;


if(fd1 < 0) {
perror("open(fd1)") ;
exit(1) ;
}

fd2 = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0666) ;


if(fd2 < 0) {

Une criture de moins de count octets sappelle une criture partielle. Une criture partielle nest normalement pas
possible sur un chier, mais elle est commune sur dautres types de descripteurs de chiers, par exemple les pipes
ou les sockets.


perror("open(fd2)") ;
exit(1) ;
}

while(1) {
rc = read(fd1, &buf, 1) ;
if(rc < 0) {
perror("read") ;
exit(1) ;
}
if(rc == 0)
break ;

rc = write(fd2, &buf, 1) ;
if(rc < 0) {
perror("write") ;
exit(1) ;
}
if(rc != 1) {
fprintf(stderr, "criture interrompue") ;
exit(1) ;
}
}

close(fd1) ;
close(fd2) ;
return 0 ;
}

Ce programme eectue deux appels systme pour chaque octet copi ce qui est tragique. Mon
portable est capable deectuer jusqu dappels systme par seconde ; ce programme
ne sera donc pas capable de copier plus d, Mo de donnes par seconde environ.

. Tampons
Pour rsoudre le problme de performances soulev ci-dessus, il sut deectuer des transferts
de plus dun octet pour chaque appel systme. Il faudra pour cela disposer dune zone de mmoire
pour stocker les donnes en cours de transfert. Une telle zone sappelle un tampon (buer en
anglais).

.. Tampon simple
Un tampon simple (g. .) est constitu dune zone de mmoire buf et dun entier buf_end
indiquant la quantit de donnes dans le tampon :

void *buf ;
int buf_end ;


buf buf_end

Figure . : Tampon simple

Les donnes valides dans le tampon se situent entre buf et buf + buf_len 1.
Un tampon simple permet de lire les donnes de faon incrmentale ; cependant, les donnes
doivent tre crites en une seule fois.

Exemple La version suivante du programme de copie utilise un tampon simple, et neectue


que deux appels systme tous les BUFFER_SIZE octets.
#define BUFFER_SIZE 4096
char buf[BUFFER_SIZE] ;
int buf_end ;
...
buf_end = 0 ;
while(1) {
rc = read(fd1, buf, BUFFER_SIZE) ;
if(rc < 0) { perror("read") ; exit(1) ; }
if(rc == 0) break ;
buf_end = rc ;
rc = write(fd2, buf, buf_end) ;
if(rc < 0) { perror("write") ; exit(1) ; }
if(rc != buf_end) {
fprintf(stderr, "criture interrompue") ;
exit(1) ;
}
buf_end = 0 ;
}
...

.. Tampon avec pointeur de dbut des donnes

buf buf_ptr buf_end

Figure . : Tampon avec pointeur de dbut des donnes

Un tampon avec pointeur de dbut des donnes (g. .) contient un entier supplmentaire
indiquant le dbut des donnes valides places dans le tampon :

void *buf ;


int buf_ptr ;
int buf_end ;

Les donnes valides se trouvent entre buf + buf_ptr et buf + buf_end 1.


Une telle structure permet de lire et dcrire les donnes de faon incrmentale ; cependant,
une fois quon a commenc crire les donnes, le tampon doit tre vid avant quon puisse
recommencer lire.

Exemple Le programme prcdent gre les lectures interrompues (read qui retourne moins
de BUFFER_SIZE octets), mais pas les critures interrompues (qui normalement ne peuvent pas
avoir lieu sur un chier). La version suivante utilise un tampon avec pointeur, et gre correctement
les critures interrompues :
#define BUFFER_SIZE 4096
char buf[BUFFER_SIZE] ;
int buf_ptr, buf_end ;
...
buf_ptr, buf_end = 0 ;
while(1) {
rc = read(fd1, buf, BUFFER_SIZE) ;
if(rc < 0) { perror("read") ; exit(1) ; }
if(rc == 0) break ;
buf_end = rc ;
while(buf_ptr < buf_end) {
rc = write(fd2, buf + buf_ptr, buf_end - buf_ptr) ;
if(rc < 0) { perror("write") ; exit(1) ; }
buf_ptr += rc ;
}
buf_ptr = buf_end = 0 ;
}
...

.. Tampon circulaire

buf buf_ptr buf_end buf buf_end buf_ptr

Figure . : Tampon circulaire

Avec un tampon avec pointeur, la variable buf_end ne revient au bord gauche du tampon que
lorsque le tampon est vide ; lorsque la n du tampon buf_end atteint le bord droit du tampon,
une lecture nest plus possible tant que le tampon nest pas vid.
Un tampon circulaire (g. .) consiste des mmes donnes quun tampon avec pointeur, mais
les deux pointeurs sont interprts modulo la taille du tampon. Lorsque buf_end > buf_ptr,


les donnes valides ne sont plus connexes, mais constitues des deux parties buf_ptr BUFFER_
SIZE et buf_end.
Il existe une ambigut dans cette structure de donnes : lorsque buf_end = buf_ptr, il nest
pas clair si le tampon est vide ou sil est plein. On peut direncier entre les deux conditions soit en
vitant de mettre plus de BUFFER_SIZE 1 octets dans le tampon, soit en utilisant une variable
boolenne supplmentaire.

. La bibliothque stdio
La bibliothque stdio a un double rle : elle encapsule la manipulation des tampons dans un
ensemble de fonctions pratiques utiliser, et combine les entres/sorties avec lanalyse lexicale et le
formatage. Des bibliothques analogues existent dans la plupart des langages de programmation,
par exemple la bibliothque java.io en Java.
Pour des raisons decacit, il est parfois souhaitable de contourner stdio et manipuler les
tampons soi-mme ; par exemple, les programmes ci-dessus utilisent un seul tampon avec deux
descripteurs de chiers ; avec stdio, il faudrait utiliser deux tampons, et donc gcher de la m-
moire et eectuer des copies supplmentaires.
Du fait de leur portabilit, les fonctions de la bibliothque stdio ont un certain nombre de
limitations. En particulier, comme certains systmes direncient entre chiers binaires et chiers
texte, stdio ne permet pas facilement de mlanger les donnes textuelles et les donnes binaires
au sein dun mme chier (cette limitation peut tre ignore sur les systmes POSIX).

.. La structure FILE
Les fonctions de stdio noprent pas sur des descripteurs de chiers, qui sont une notion
spcique Unix, mais sur des pointeurs sur une structure qui reprsente un ot, la structure
FILE. La dnition de FILE dpend du systme, mais sur un systme POSIX elle pourrait tre
dnie comme suit :
#define BUFSIZ 4096

#define FILE_ERR 1
#define FILE_EOF 2

typedef struct _FILE {


int fd ; /* descripteur de fichier */
int flags ; /* flags */
char *buffer /* tampon */
int buf_ptr ; /* position courante dans le tampon */
int buf_end ; /* fin des donnes du tampon */
} FILE ;
Le champ fd contient le descripteur de chier associ ce ot. Le champ buffer contient un
pointeur sur un tampon de taille BUFSIZ. Le champ buf_ptr indique la position courante dans
le tampon, et le champ buf_end indique la n du tampon.


Le champ flags indique ltat du ot. Il vaut normalement ; il peut valoir FILE_ERR si
une erreur a eu lieu sur ce ot, et FILE_EOF si la n du chier a t atteinte. Dans une vraie
implmentation de stdio, le champ flags contiendra aussi de linformation qui indique la
politique de gestion du tampon associ au ot (voir partie .. ci-dessous).
Une structure FILE est normalement cre par la fonction fopen :

FILE *fopen(const char *path, const char *mode)

Cette fonction ouvre le chier nomm par path et construit une structure FILE associe au
descripteur de chier rsultant.
Le paramtre mode indique le but de louverture de chier. Il sagit dune chane de caractres
dont le premier lment vaut r pour une ouverture en lecture, w pour une ouverture en
criture, et a pour une ouverture en mode ajout. Ce caractre peut tre suivi de + pour
une ouverture en lecture et criture simultanes, et b si le chier ouvert est un chier binaire
plutt quun chier texte (cette dernire information est ignore sur les systmes POSIX).
Une structure FILE est dtruite laide de la fonction fclose :

int fclose(FILE *file) ;

Cette fonction ferme le descripteur de chier, libre le tampon, puis libre la structure FILE elle-
mme.

.. Entres/sorties de haut niveau


Les entres/sorties stdio utilisent toujours un tampon ; cependant, plusieurs politiques di-
rentes sont possibles pour dcider quand ce tampon est vid, i.e. quand lappel systme eectuant
la sortie eective est eectu. Le tampon peut-tre vid seulement lorsquil est plein (cest ce qui
se passe lorsquun FILE est associ un chier), la n de chaque ligne (cest ce qui se passe
lorsquun FILE est associ un terminal), ou aprs chaque appel de fonction dentre/sortie de
haut niveau. La politique de gestion du tampon associ un FILE peut tre change laide de
la fonction setvbuf.
Les fonctions dentre/sortie fondamentales sont getc, qui retourne un caractre lu sur un ot,
et putc, qui crit un caractre sur un ot. La fonction fflush permet de vider le tampon.
En ignorant les complications lies aux chiers ouverts en entre et en sortie simultanment
ainsi que celles lies aux tampons par ligne , les fonctions getc et putc pourraient tre dnies
comme suit :

int getc(FILE *f)


{
if(f->buf_ptr >= f->read_end)
_filbuf(f) ;
if(f->flags & (FILE_ERR | FILE_EOF))
return -1 ;
return (unsigned char)fp->buf[fp->buf_ptr++] ;
}


int putc(char ch, FILE *f)
{
if(f->buf_ptr >= BUFSIZ)
_flshbuf(f) ;
if(f->flags & FILE_ERR)
return -1 ;
fp->buf[fp->buf_ptr++] = ch ;
return ch ;
}

Exercice: crivez les fonctions _filbuf et _flshbuf.


Comme stdio utilise des tampons, il est possible de faire toutes les entres/sorties avec ces
fonctions. Cependant, stdio fournit aussi des fonctions dentre/sortie par lots , fread et
fwrite, semblables read et write. Comme elles sont prvues pour fonctionner sur des sys-
tmes o les chiers ne sont pas forcment des suites doctets, ces fonctions oprent sur des ta-
bleaux dlments de taille quelconque plutt que sur des tableaux doctets :
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *file) ;
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *file) ;

Le paramtre nmemb indique le nombre dlments lire ou crire, tandis que size indique la
taille de chaque lment ; le nombre total doctets lus ou crits est donc nmemb * size.
Exercice: crivez les fonctions fread et fwrite, dabord en termes de getc et putc, ensuite en
manipulant directement les tampons.

.. Interaction entre stdio et le systme


Au dmarrage du programme, stdio cre trois ots associs aux descripteurs de chier , et
, et les aecte aux variables globales stdin, stdout et stderr respectivement.
La fonction fdopen, une extension POSIX stdio, permet de crer un FILE partir dun
descripteur de chier existant :
FILE *fdopen(int fd, const char *mode) ;

Cette fonction est notamment utile lorsque fd na pas obtenu laide dopen, mais par un autre
appel systme (par exemple pipe ou socket).
Inversement, la fonction fileno permet dobtenir le descripteur de chier associ un FILE :
int fileno(FILE *file) ;

Enn, la fonction fflush permet de vider le tampon associ un FILE :


int fflush(FILE *file) ;

Appele avec un pointeur nul, cette fonction vide les tampons de tous les FILE du systme.
Comme stdio utilise des tampons, il nest pas facile dutiliser simultanment les appels sys-
tme et les fonctions stdio sur le mme descripteur de chier. Dans le cas des sorties, il faut


utiliser la fonction fflush, qui vide le tampon associ un FILE, avant chaque appel systme
qui manipule le descripteur directement ; dans le cas des entres, il faut faire un appel fseek
chaque transition entre les appels systme et les fonctions stdio.

.. Entres/sorties formates
Pour prsenter des donnes lutilisateur, il faut les formater, cest dire en construire une re-
prsentation textuelle, une suite de caractres qui correspond une prsentation culturellement
accepte de ces donnes. Inversment, les donnes entres par lutilisateur doivent tre analyses.

Formatage et analyse La suite stdio contient la fonction snprintf qui eectue le formatage
des donnes :

int snprintf(char *buf, size_t size, const char *format, ...) ;

Cette fonction prend en paramtre un tampon buf de taille size, et y stocke une reprsentation
guide par format des paramtres optionnels qui suivent .
Si le formatage est russi, snprintf retourne le nombre de caractres stocks dans buf (sans
compter le \0 nal). Lorsque buf est trop court, le comportement dpend de la version : dans
les implmentations C, elle retourne -, dans les implmentations C, elle retourne la taille
du tampon dont elle aurait eu besoin. Cette dirence entre les versions doit tre gre par le
programmeur :

char *format_integer(int i)
{
char *buf ;
int n = 4, rc ;
while(1) {
buf = malloc(n) ;
if(buf == NULL) return NULL ;
rc = snprintf(buf, n, "%d", i) ;
if(rc >= 0 && rc < n) return buf ;
free(buf) ;
if(rc >= 0)
n = rc + 1 ;
else
n = 2 * n;
}
}

Inversement, la fonction sscanf eectue lanalyse :

int sscanf(const char *str, const char *format, ...) ;

La fonction semblable sprintf est viter, car elle peut dborder du tampon qui lui est pass.


Entres/sorties formates Les fonctions de formatage et danalyse et les fonctions dentres/sor-
ties sont combines en des fonctions dentres/sorties formates. Ainsi, la fonction fprintf
formate ses arguments avant de les crire sur un ot, et fscanf lit des donnes quelle analyse
ensuite. Les fonctions printf et scanf sont des abbrviations pour fprintf et fscanf dans
le cas o largument file vaut stdout et stdin respectivement.

. Le pointeur de position courante


Les lectures et les critures dans un chier sont par dfaut squentielles : les donnes sont lues
ou crites les unes la suite de lautre. Par exemple, lors de lexcution de la squence de code
rc = write(fd, "a", 1) ;
rc = write(fd, "b", 1) ;

loctet b est crit aprs loctet a .


La position dans un chier laquelle se fait la prochaine lecture ou criture est stocke dans le
noyau dans un champ de lentre de chier ouvert qui sappelle le pointeur de position courante
(gure .). Lors de lappel systme open, le pointeur de position courante est initialis , soit
le dbut du chier.

.. Lappel systme lseek


Le pointeur de position courante associ un descripteur de chier peut tre lu et modi
laide de lappel systme lseek :
off_t lseek(int fd, off_t offset, int whence) ;

Le paramtre fd est le descripteur de chier dont lentre de la table de chiers ouverts associe
doit tre modie. Le paramtre offset ( dplacement ) identie la nouvelle position, et le
paramtre whence ( partir do ) spcie linterprtation de ce dernier. Il peut avoir les valeurs
suivantes :
SEEK_SET : offset spcie un dcalage partir du dbut du chier ;
SEEK_CUR : offset spcie un dcalage partir de la position courante ;
SEEK_END : offset spcie un dcalage partir de la n du chier.
Lappel lseek retourne la nouvelle valeur du pointeur de position courante, ou - en cas derreur
(et alors errno est positionn).

Attention : le type off_t est dhabitude un synonyme de long, et pas de int ;


attention donc au type des variables.

Exemples Pour dplacer le pointeur de chier au dbut dun chier, on peut faire
lrc = lseek(fd, 0L, SEEK_SET) ;

Pour dplacer le pointeur de chier la n dun chier, on peut faire


size = lseek(fd, 0L, SEEK_END) ;


Si lappel russit, la variable size contient la taille du chier.
On peut dterminer la position courante laide de lappel

position = lseek(fd, 0L, SEEK_CUR) ;

.. Le mode ajout la n
Il est trs courant de vouloir ajouter des donnes la n dun chier. En premire approxima-
tion, ce nest pas dicile :

fd = open("/var/log/messages", O_WRONLY) ;
lrc = lseek(fd, 0L, SEEK_END) ;
rc = write(fd, ...) ; /* condition critique ! */
close(fd) ;

Malheureusement, une telle approche mne une condition critique (race condition) qui peut
causer une perte de donnes. Considrons en eet se qui peut se passer si deux processus veulent
simultanment ajouter des donnes la n du mme chier :

/* processus A */ /* processus B */
lseek(fd, 0L, SEEK_END) ;
lseek(fd, 0L, SEEK_END)
write(fd, "b", 1)
write(fd, "a", 1)

Supposons, pour xer les ides, que le chier a initialement une taille de octets. Le processus
A commence par se positionner la n du chier, soit la position . Supposons maintenant
quun changement de contexte intervient ce moment, le contrle passe au processus B, qui se
positionne la position , et y crit un octet b . Le contrle repasse ensuite au processus A,
qui est toujours positionn en ; il crit donc un octet a la position , et crase donc
les donnes crites par le processus B.
Une solution entirement gnrale au problme de laccs des ressources partages demande
que les processus A et B eectuent une synchronisation, par exemple en posant des verrous sur la
ressource commune ou en utilisant un troisime processus qui sert darbitre. Unix inclut cepen-
dant une solution simple pour le cas particulier de lcriture la n du chier.
Lorsque le drapeau O_APPEND est positionn dans le deuxime paramtre de open, le chier
est ouvert en mode criture la n (append mode). Le noyau repositionne alors le pointeur de
position courante la n du chier lors de toute opration dcriture eectue travers ce poin-
teur de chier. Lopration ayant entirement lieu dans le noyau, celui-ci sassure de lexcution
atomique du positionnement et de lcriture.

. Troncation et extension des chiers


Lorsquun processus crit la n du chier, la taille de ce chier augmente ; on dit alors que le
chier est tendu. Lopration inverse lextension sappelle la troncation.


.. Extension implicite
Toute criture au del de la n dun chier cause une extension de celui-ci. Par exemple, la
squence de code suivante provoque une extension dun octet :
lrc = lseek(fd, 0L, SEEK_END) ;
rc = write(fd, "a", 1) ;
Une extension plus importante peut tre ralise en se positionnant au-del de la n du chier
et en crivant des donnes ; les parties du chier qui nont jamais t crites sont alors automati-
quement remplies de par le noyau. Par exemple, la squence suivante ajoute la n du chier
Mo de zros suivi dun octet a :
lrc = lseek(fd, 1024 * 1024L, SEEK_END) ;
rc = write(fd, "a", 1) ;

Parenthse : chiers trous En fait, les donnes non crites ne sont pas forcment stockes sur
disque : le noyau note simplement que la plage est pleine de zros , et produira des zros lors
dune prochaine lecture. Ceci peut tre constat en consultant le champ st_blocks de li-nud,
par exemple laide de ls -s .

.. Troncation et extension explicite


Une troncation ou extension explicite peut se faire laide des appels systme truncate et
ftruncate :
int truncate(char *name, off_t length) ;
int ftruncate(int fd, off_t length) ;
Comme beaucoup dappels systme agissant sur les chiers, cet appel systme existe en deux va-
riantes : pour truncate, le chier tronquer ou tendre est spci laide de son nom name,
tandis que pour ftruncate, il est spci laide dun descripteur de chier fd. Le paramtre
length spcie la nouvelle taille du chier. Sil est infrieur la taille actuelle, le chier est tron-
qu, et des donnes sont perdues ; sil y est suprieur, le chier est tendu, et des zros sont crits
(mais voyez le commentaire ci-dessus sur les chiers trous).
Ces appels systme ne changent pas le pointeur de position courante, et retournent en cas de
succs, - en cas derreur (et alors errno bon, vous avez compris).

O_TRUNC Il est trs courant de vouloir tronquer un chier juste aprs lavoir ouvert. Pour
viter de faire un appel ftruncate aprs chaque ouverture, la troncation peut tre combine
cette dernire en positionnant le drapeau O_TRUNC dans le deuxime paramtre de open :
fd = open("alamakota", O_WRONLY | O_CREAT | O_TRUNC, 0666) ;
la dirence des drapeaux O_EXCL et O_APPEND, qui sont essentiels pour viter des situations
critiques dans certains cas, le drapeau O_TRUNC nest quune abrviation.
Traditionnellement, ces appels servent faire une troncation ; sur les Unix modernes, ils permettent aussi de faire
une extension.


. I-nuds
Li-nud est une des structures fondamentales du systme de chiers Unix ; conceptuellement,
li-nud cest le chier.

Structure dun i-nud sur disque Un i-nud sur disque contient, entre autres :
le type du chier quil reprsente (chier ordinaire ou rpertoire) ;
le nombre de liens sur li-nud ;
une indication du propritaire du chier ;
la taille des donnes du chier ;
les temps de dernier accs et de dernire modication des donnes du chier, et le temps
de dernire modication de li-nud.
une structure de donnes (typiquement un arbre) qui permet de retrouver les donnes du
chier.

Structure dun i-nud en mmoire Lorsquil est charg en mmoire, un i-nud contient en
outre les donnes ncessaires pour retrouver li-nud sur disque :
le numro du priphrique dont il provient ;
le numro di-nud sur le priphrique.

.. Lecture des i-nuds


Un i-nud contient un certain nombre de donnes qui peuvent intresser le programmeur.
Lappel systme stat et son cousin fstat permettent de les consulter. Ces appels systme ont
les prototypes suivants :

int stat(char *path, struct stat *buf) ;


int fstat(int fd, struct stat *buf) ;

Ils dirent par la faon de spcier li-nud consulter : stat y accde par un nom de chier,
tandis que fstat utilise un descripteur de chier . Ils remplissent le tampon buf avec les in-
formations de li-nud, puis retournent en cas de succs, - en cas dchec (et alors la variable
errno est positionne).

La structure stat
La structure stat retourne par stat et fstat contient au moins les champs suivants :

struct stat {
dev_t st_dev
ino_t st_ino
mode_t st_mode
nlink_t st_nlink
uid_t st_uid

Unix ne permet pas daccder directement un i-nud.


gid_t st_gid
off_t st_size
time_t st_atime
time_t st_mtime
time_t st_ctime
blkcnt_t st_blocks
};
Les types opaques ci-dessus (dev_t, ino_t etc.) sont souvent des synonymes de int, sauf
off_t, qui est normalement un synonyme de long.

Localisation de li-nud Les champs st_dev et st_ino contiennent, respectivement, le nu-


mro du priphrique o se trouve li-nud et le numro de li-nud. Ils ont la proprit qu un
moment donn la paire (st_dev, st_ino) identie li-nud de faon unique sur le systme,
ce qui permet par exemple de vrier que deux descripteurs de chier pointent sur le mme -
chier. Attention, un numro di-nud peut tre rutilis aprs quun chier a t supprim, ce
qui implique quil nest pas possible de se servir de ces champs pour identier des chiers des
moments dirents.

Comptage des rfrences Le champ st_nlinks contient le compte des rfrences li-nud.
Nous en parlons en plus de dtail la partie ...

Type et permissions Le champ st_mode contient bits qui servent identier le type de
chier et en dcrire les permissions daccs.
Les bits dordre bas de st_mode dcrivent les permissions.
Les bits dordre haut dcrivent le type de chier. Les valeurs possibles pour ces bits incluent
(en octal) :
ou S_IFREG : chier ordinaire ;
ou S_IFDIR : rpertoire ;
Outre les macros S_IF... ci-dessus, le chier dentte <sys/stat.h> dnit des macros
nommes S_ISREG et S_ISDIR qui permettent de tester ces valeurs.

Propritaire Les champs st_uid et st_gid dnissent le propritaire du chier. Il sagit nor-
malement des champs euid et egid du processus qui a cr li-nud.

Taille Le champ st_size contient la taille du chier, compte en octets.

Temps Les champs st_atime, st_mtime et st_ctime contiennent les temps du chier,
compts en secondes depuis lpoque (mme units que lappel systme time vu auparavant). Ils
ont la smantique suivante :
st_atime est le temps du dernier accs : il est mis jour chaque fois que des donnes du
chier sont lues (par exemple par lappel systme read) ;
Les mises jour du temps daccs posent de srieux problmes de performances sur certains matriels, et certains
systmes ne limplmentent pas ; il est donc dconseill de sen servir.


st_mtime est le temps de dernire modication : il est mis jour chaque fois que des
donnes sont crites dans le chier ;
st_ctime est le temps de dernire modication du i-nud : il est mis jour chaque
fois que le i-nud est modi (par exemple par les appels systme du paragraphe ..
ci-dessous). Ce champ est parfois appel temps de cration du chier.
Normalement, cest st_mtime qui est intressant ; lauteur de ces lignes avoue quil ne sest jamais
encore servi des deux autres temps.
Les fonctions localtime et strftime permettent de convertir le temps Unix en temps local
et de formater le rsultat.

Taille des donnes Le champ st_blocks indique la place occupe sur disque par les don-
nes. Lunit dans laquelle est exprime cette valeur nest pas dnie par POSIX (elle est de
octets sous la 7e dition, sous Unix BSD et sous Linux, mais elle vaut octets sur certains Unix
Systme V). Bien quil ne soit pas able sur les systmes de chiers modernes, ce champ permet
parfois de dtecter les chiers trous.

.. Modication des i-nuds


Un i-nud peut tre modi implicitement, par un appel systme qui manipule les donnes du
disque ou la structure de rpertoires, ou explicitement par un appel systme dont le rle principal
est de modier li-nud.

Modication implicite
Dhabitude, les champs dun i-nud sont modis implicitement, par des appels systme qui
modient le contenu du chier. Par exemple, toute extension ou troncation dun chier modie
les champs st_size et st_mtime dun chier, et toute criture modie st_mtime. De mme,
toute lecture modie st_atime. Toute cration ou suppression de liens modie st_nlinks.
Toute cration ou suppression de liens, et tout changement de propritaire ou de permissions
modie le champ st_ctime.

Modication explicite
Il est aussi possible de modier les champs dun i-nud explicitement laide dappels systme
ddis cela.

Changement de permissions Les permissions dun chier (les bits bas du mode de li-
nud) peuvent tre modis laide des deux appels systme suivants :

int chmod(char *path, int mode) ;


int fchmod(int fd, int mode) ;

Comme dans le cas de stat et fstat, chmod et fchmod dirent dans la faon dont est iden-
ti li-nud modier : chmod prend un nom de chier, fchmod un descripteur. Ces appels
retournent en cas de succs, - en cas derreur.


Changement de propritaire Le propritaire dun chier peut tre chang avec les appels sys-
tme suivants :

int chown(char *path, int uid, int gid) ;


int fchown(int fd, int uid, int gid) ;

Sur tous les systmes, lutilisateur root (euid = 0) a le droit de changer le propritaire de
nimporte quel chier. Sous Systme V, un utilisateur a en outre le droit de donner un chier
qui lui appartient un autre utilisateur ; sous Unix BSD et Linux, ceci nest pas autoris pour viter
de contourner le systme de quotas.
Dans certaines circonstances, un utilisateur a aussi le droit de changer le groupe dun chier
qui lui appartient.

Changement de temps Les temps dun chier peuvent tre changs laide de lappel systme
suivant :

int utime(char *filename, struct utimbuf *buf) ;

La structure utimbuf est dnie comme suit :

struct utimbuf {
int actime;
int modtime;
};

Le champ actime contient le nouveau temps de dernier accs, et le champ modtime contient le
nouveau temps de dernire modication. (Le temps de dernre modication de li-nud est mis
jour par cet appel.)

.. Liens et renommage de chiers


Comme nous lavons vu, un lien un i-nud est normalement cr par un appel open avec
le ag O_CREAT positionn.

Cration de liens supplmentaires


Lappel systme link cre un nouveau nom et un nouveau lien vers un i-nud existant. Cet
appel permet donc de faire apparatre le mme chier deux endroits de la hirarchie de chiers,
ou sous deux noms dirents.
Lappel link a le prototype suivant :

int link(char *old, char *new) ;

o oldpath est le nom de chier existant, et new le nom cr. Si new existe dj, lappel link
choue avec errno valant EEXIST.


Suppression de liens
Lappel unlink supprime un nom de chier et le lien associ ; sil sagissait du dernier lien
pointant sur un i-nud, le chier lui-mme (i-nud et donnes) est supprim aussi. Cet appel a
le prototype suivant :

int unlink(char *filename) ;

Renommage et dplacement de chiers


Sous la 7e dition, un chier tait renomm ou dplac en crant un nouveau lien puis en sup-
primant lancien :

int old_rename(char *old, char *new) {


int rc ;
unlink(new) ;
rc = link(old, new) ; if(rc < 0) return -1 ;
rc = unlink(old) ; if(rc < 0) return -1 ;
return 0 ;
}

Cette approche avait un dfaut srieux : comme old_rename est compos de plusieurs actions
dont chacune peut chouer, il est possible de se retrouver aprs une erreur (par exemple de per-
missions) avec un renommage partiellement eectu, par exemple o new nexiste plus tandis
que old na pas t renomm. De plus, comme link nest pas autoris sur les rpertoires pour
un utilisateur ordinaire, seul root pouvait renommer les rpertoires.
Aujourdhui, Unix implmente un appel systme rename :

int rename(char *old, char *new) ;

Cet appel systme ne peut pas chouer partiellement. la dirence de link, il supprime le
nouveau nom sil existait dj.

.. Comptage des rfrences


Lorsque le dernier lien vers un i-nud est supprim, li-nud lui-mme et le contenu du chier
sont librs, et lespace peut tre reutilis pour dautres chiers. Intuitivement, lappel systme
unlink sert supprimer les chiers.
Pour ce faire, le systme maintient un champ entier st_nlink dans chaque i-nud, son
compte de rfrences, qui est gal au nombre de liens existant vers cet i-nud. Ce champ vaut
initialement , il est incrment chaque appel link, et dcrment chaque appel unlink.
Lorsquil atteint , li-nud et son contenu sont supprims.
Le comptage de rfrences, qui est une des techniques classiques de gestion de la mmoire, ne
fonctionne pas en prsence de cycles dans le graphe de rfrences. Unix vite la prsence de cycles
dans le systme de chiers en interdisant la cration de liens vers des rpertoires.


Read aer delete La suppression dun i-nud est retarde lorsquil existe un processus qui la
ouvert, ou, en dautres termes, lorsquil en existe une copie en mmoire. Un processus peut donc
lire ou crire sur un chier vers lequel il nexiste plus aucun lien. Intuitivement, on peut lire un
chier aprs lavoir supprim.
Cette smantique dite read aer delete vite les conditions critiques entre suppression et lecture.
De plus, elle est pratique lors de la gestion de chiers temporaires, i.e. de chiers qui doivent tre
supprims lorsquun processus termine.

. Manipulation de rpertoires
Un rpertoire (directory en Anglais) est un chier dont le contenu est une suite dentres de
rpertoire, chaque entre tant compose dun nom et dun numro di-nud. Les rpertoires
sont identis par une valeur spcique du champ mode de leur i-nud, et le systme les traite
spcialement : il ne permet pas lutilisateur de les modier directement, et les consulte lors de
la rsolution de noms.

.. Rsolution de noms
La rsolution de noms est le processus qui permet de convertir un nom de chier en un i-nud.
La rsolution de noms est eectue lors de tout appel systme qui prend un nom de chier en
paramtre, par exemple open ou stat.
Chaque systme de chiers a un i-nud distingu, appel son i-nud racine, qui contient un
rpertoire, appel le rpertoire racine. La rsolution dun nom de chier absolu (un nom qui com-
mence par un slash / ) commence au rpertoire racine. Le systme recherche le premier com-
posant du nom de chier dans le rpertoire racine. Sil est trouv, et sil pointe sur un rpertoire,
le deuxime composant est recherch dans celui-ci. La rsolution se poursuit ainsi jusqu pui-
sement du nom de chier.
La rsolution dun nom relatif (un nom qui ne commence pas par un slash) se fait de faon
analogue, mais en commenant un i-nud qui dpend du processus le rpertoire courant
(voir paragraphe ..).

.. Lecture des rpertoires


Il est parfois ncessaire de lire explicitement le contenu dun nom de rpertoire. Par exemple,
le programme ls lit le contenu dun rpertoire et ache les noms de chier quil y trouve.

Les rpertoires en e dition


Sous Unix e dition, les noms de chier taient limits caractres. Un rpertoire tait
simplement une suite de structures de type dirent :

Le terme folder (dossier) a, ma connaissance, t introduit par Mac OS (classique). Microso la adopt avec la
sortie de Windows . Il nest normalement pas utilis sous Unix.


struct dirent {
unsigned short d_ino;
char d_name[14] ;
};

Un programme qui dsirait lire le contenu dun rpertoire procdait de la faon habituelle :
aprs un appel open, il lisait simplement le contenu du rpertoire laide de read ; chaque
suite de octets lus tait interprte comme une structure de type dirent.

Interface de haut niveau


Sous .BSD, la structure des rpertoires est devenue plus complique : un rpertoire pouvait
contenir des noms de chiers de longueur arbitraire, et il ntait plus possible de simplement
interprter un rpertoire comme une suite denregistrements de octets chacun. An dviter
au programmeur danalyser lui-mme une structure de donnes complexe, une interface de haut
niveau, analogue stdio, a t dnie.
Lincarnation moderne de cette interface sappelle dirent. Elle dnit deux structures de don-
nes : DIR (analogue FILE) est une structure de donnes opaque reprsentant un rpertoire ou-
vert. La structure dirent reprsente une entre de rpertoire ; elle contient au moins les champs
suivants :

struct dirent {
ino_t d_ino;
char d_name[] ;
};

Comme dans la version traditionnelle (e dition), le champ d_ino contient un numro di-
nud, et le champ d_name le nom dun chier. la dirence de celle-ci, cependant, la longueur
du champ d_name nest pas spcie il est termin par un \0, comme une chane normale en
C.
Les rpertoires sont manipuls laide de trois fonctions :

DIR *opendir(const char *name) ;


struct dirent *readdir(DIR *dir) ;
int closedir(DIR *dir) ;

La fonction opendir ouvre un rpertoire et retourne un pointeur sur une structure de type
DIR reprsentant le rpertoire ouvert.
La fonction readdir retourne un pointeur sur lentre de rpertoire suivante. Celui-ci peut
pointer sur les tampons internes au DIR manipul, ou mme sur un tampon statique ; son contenu
cesse donc dtre valable ds le prochain appel readdir, et il faut en faire une copie sil doit
tre utilis par la suite.
Enn, la fonction closedir ferme le rpertoire et dtruit la structure DIR.


. Liens symboliques
Les liens (dits durs ), tels quon les as vus au cours prcdent, sont un concept propre, lgant
et sans ambigut. Cependant, ils ont certaines limitations qui les rendent parfois peu pratiques :
il est impossible de faire un lien entre deux systmes de chiers distincts ;
il est impossible (en pratique) de faire un lien sur un rpertoire.
Les liens symboliques (.BSD) ont t dvelopps pour viter ces limitations. Il sagit dun
concept peu lgant et ayant une smantique souvent douteuse, mais fort utile en pratique.
la dirence dun lien (dur), un lien symbolique ne rfre pas un i-nud, mais un nom.
Lorsque le systme rencontre un lien symbolique lors de la rsolution dun nom de chier, il rem-
place le nom du lien par son contenu, et recommence la rsolution. Par exemple, si /usr/local
est un lien symbolique contenant /mnt/disk2/local, le nom de chier

/usr/local/src/hello.c

est remplac par

/mnt/disk2/local/src/hello.c

De mme, si /usr/share est un lien symbolique contenant ../lib, le nom de chier

/usr/share/doc

est remplac par

/usr/share/../lib/doc

Que se passe-t-il lorsquun lien symbolique contenant un nom relatif est dplac ?

Cration dun lien symbolique Un lien symbolique est cr par lappel systme symlink :

int symlink(const char *contents, const char *name) ;

Un appel symlink construit un lien symbolique nomm name dont le contenu est la chane
contenue dans contents.

Utilisation des liens symboliques Dans la plupart des cas, lutilisation des liens symboliques se
fait de faon transparente lorsquun appel systme eectue une rsolution de noms. Par exemple,
un appel open sur le nom dun lien symbolique sappliquera automatiquement la cible du lien.
Certains appels systme ne suivent pas les liens symboliques. Cest notamment le cas de un-
link, qui dtruit le lien, et pas sa cible. Lappel systme stat existe en deux variantes : stat,
qui suit les liens, et lstat, qui ne le fait pas et permet donc dobtenir des informations sur le lien
lui-mme.
On peut lire le contenu dun lien symbolique laide de lappel systme readlink :

ssize_t readlink(const char *path, char *buf, size_t bufsiz) ;

Moins horrible, toutefois, que les raccourcis utiliss sur certains autres systmes.


Processus
Un programme est un chier contenant une suite dinstructions excutables et les donnes as-
socies. Lorsque ce programme est excut, il est dabord charg en mmoire puis excut par le
processeur.
Un processus est une instance dun programme en train de sexcuter. Informellement, un pro-
cessus cest une zone mmoire et un ot de contrle. Plus prcisment, un processus consiste
de :
une zone de mmoire contenant le code excutable (le segment de texte), les variables glo-
bales (le segment de donnes), les variables locales et la chane dactivation des fonctions
(la pile) et les donnes alloues dynamiquement (le tas) ;
un contexte, cest dire lensemble des registres du processeur, notamment celui qui indique
la prochaine instrucution excuter et celui qui indique le sommet de la pile ;
une petite structure du noyau, contenant des informations supplmentaires propos du
processus, tels que son numro, son propritaire et son rpertoire courant.

Virtualisation de la mmoire Les adresses de mmoire manipules par le code utilisateur ne


correspondent pas forcment des adresses physiques : la mme adresse mmoire dans deux
processus peut correspondre deux locations physiques distinctes, et une adresse peut mme
ne pas correspondre de la mmoire du tout, mais tre simule par des donnes stockes sur
disque. La mmoire est donc virtualise, le systme prsentant lutilisateur une abstraction qui
ressemble de la mmoire physique, mais peut tre implmente de diverses manires.

. Lordonnanceur
Unix est un systme temps partag : plusieurs processus peuvent tre excutables un mo-
ment donn, le systme donnant lillusion dune excution simultane en passant rapidement
dun processus lautre . Il sagit l dun autre cas de virtualisation, cette fois-ci du processeur.
La partie du noyau qui choisit quel processus doit sexcuter un moment donn sappelle
lordonnanceur (scheduler). Lordonnanceur maintient les structures de donnes suivantes (-
gure .(a)) :
le processus en train de sexcuter (R) ;
une le de processus prts sexcuter (r) ;
un ensemble de processus en attente dun vnement (W) ;
un ensemble de processus morts, les zombies (Z).
Un processus peut donc avoir les tats suivants (Figure .(b)) :

Dans toute cette partie, nous faisons lhypothse simplicatrice dun systme un seul processeur.
En pratique, il sagit dun ensemble de les, une par vnement possible, ou mme dune structure plus complexe.


r R

r R

W Z W Z
(a) (b)

Figure . : Structures de donnes de lordonnanceur (a) et transitions des processus (b)

R, Running en train de sexcuter ;


r, runnable , prt sexcuter ;
W, Waiting , en attente dun vnement ;
Z, Zombie , mort.
Les transitions suivantes entre tats sont possibles :
r, cration dun processus ;
r R et R r, changement de contexte (context switch) dcid par lordonnanceur ;
R W, appel systme bloquant (voir ci-dessous) ;
W r, arrive dun vnement ;
R Z, mort dun processus.

.. Appels systme bloquants


Lorsquun processus est dans ltat en train de sexcuter R, il peut excuter des instructions
du processeur (par exemple pour eectuer des calculs), ou faire des appels systme. Parfois, le
noyau peut satisfaire lappel systme immdiatement par exemple, un appel time calcule
lheure et retourne immdiatement au code utilisateur.
Souvent, cependant, un appel systme demande une interaction prolonge avec le monde rel ;
un processus excutant un tel appel systme est mis en attente dun vnement (tat W), et ne
sera rveill (pass ltat prt sexcuter r) que lorsque lappel systme sera prt retourner.
Un tel appel systme est dit bloquant.
Considrons par exemple le cas dun appel systme read demandant des donnes stockes
sur le disque. Au moment ou le processus eectue lappel systme, le contrleur de disque est
programm pour lire les donnes, et le processus appelant est mis en attente de lvnement re-
qute de lecture termine . Quelques millisecondes plus tard (plusieurs millions de cycles du
processeur), lorsque la requte aura abouti, le processus sera rveill et remis dans ltat prt
sexcuter .


. Contenu dun processus Unix
En plus de la mmoire du processus et de son contexte, un processus Unix contient une com-
posante en mode noyau qui contient en particulier les donnes suivantes :
le numro du processus pid ;
le numro de son processus pre, ppid ;
le propritaire rel du processus, uid et gid, et le propritaire eectif , euid et egid ;
le rpertoire courant du processus ;
ltat du processus.

.. La commande ps
La commande ps l (BSD) ou ps -l (SYSV) permet de connatre une oppe de donnes
propos des processus. Par dfaut, elle nache que les processus appartenant lutilisateur qui
linvoque, et la variante BSD restreint de plus sa sortie aux processus intressants ; pour avoir
la liste de tous les processus du systme, il faut utiliser la commande ps algx (BSD) ou ps -el
(SYSV).
Cette commande ache en particulier :
le numro du processus et celui de son pre (colonnes pid et ppid) ;
le propritaire rel du processus (colonnes uid et gid) ;
ltat du processus (colonne stat), et, dans le cas dun processus bloqu, lvnement sur
lequel il est en attente (colonne wchan).

.. Appels systme daccs au processus


Identit du processus Le champ ppid organise lensemble des processus en une arborescence
souvent apple la hirarchie des processus. On dit que le processus p est le pre de q lorsque
ppid(q) = p ; q est alors un ls de p. Lorsquun processus meurt, ses enfants sont adopts
par le processus init , portant le numro .
Le numro du processus en train de sexcuter est accessible laide de lappel getpid ; le
numro de son pre laide de getppid :

pid_t getpid(void) ;
pid_t getppid(void) ;

Propritaire et privilges du processus Comme nous lavons vu dans la partie systme de -


chiers, sous Unix un principal de scurit (propritaire dun chier, dun processus ou dune autre
ressource) est reprsent par deux entiers : un numro dutilisateur et un numro de groupe.
Un processus maintient deux propritaires : un propritaire rel, qui reprsente lutilisateur qui
a cr ce processus, et un propritaire eectif, qui code les privilges dont dispose ce processus,
par exemple pour modier des chiers.
Le propritaire dun processus peut tre identi laide des appels systme suivants :

En fait, il y en a davantage voyez setresuid pour plus dinformations.


uid_t getuid(void) ;
gid_t getgid(void) ;
uid_t geteuid(void) ;
gid_t getegid(void) ;

Les appels systme setuid, setgid, seteuid et setegid permettent de changer de pro-
pritaire. Leur smantique est complexe, et ils ne sont pas portables : leur comportement dire
entre BSD et SYSV.

Le rpertoire courant Chaque processus contient une rfrence un i-nud distingu, le r-


pertoire courant (working directory) du processus, qui sert la rsolution des noms de chier re-
latifs (voir paragraphe ..). La valeur du rpertoire courant peut tre change laide des appels
systme chdir et fchdir :

int chdir(const char *path) ;


int fchdir(int fd) ;

Le rpertoire courant est consult chaque fois quun nom relatif est pass un appel systme
eectuant une rsolution de noms. Son contenu peut notamment tre lu en passant le nom "."
lappel systme stat ou la fonction opendir.

. Vie et mort des processus


Intuitivement, la cration dun processus saccompagne de lexcution dun programme ; ce-
pendant, sous Unix , la cration dun processus et lexcution dun programme sont deux op-
rations distinctes. La cration dun processus excutant un nouveau programme recquiert donc
lexcution de deux appels systme : fork et exec.

.. Cration de processus
Lappel systme fork
Un processus est cr avec lappel systme fork

pid_t fork() ;

En cas de susccs, un appel fork a pour eet de dupliquer le processus courant. Un appel
fork retourne deux fois : une fois dans le pre, o il retourne le pid du ls nouvellement cr,
et une fois dans le ls, o il retourne .
En cas dchec, fork ne retourne quune fois, avec une valeur de retour valant -.

Mais pas sous Windows ou MS-DOS.


Au contraire dun appel _exit, qui retourne fois.


Lappel systme wait
Lappel systme wait sert attendre la mort dun ls :

pid_t wait(int *status)

Cet appel le comportement suivant :


si le processus courant na aucun ls, il retourne - avec errno valant ECHILD ;
si le processus courant a au moins un ls zombie, un zombie est dtruit et son pid est re-
tourn par wait ;
si aucun des ls du processus courant nest un zombie, wait bloque en attendant la mort
dun ls.
Si status nest pas NULL, lappel wait y stocke la raison de la mort du ls. Cette valeur est
opaque, mais peut tre analyse laide des macros suivantes :
WIFEXITED(status) retourne vrai si le ls est mort de faon normale, i.e. du fait dun
appel _exit ;
WEXISTSTATUS(status), retourne le paramtre de _exit utilis par le ls ; cette ma-
cro nest valide que lorsque WIFEXITED(status) est vrai.

Lappel systme waitpid


Lappel systme waitpid est une version tendue de wait :

pid_t waitpid(pid_t pid, int *status, int flags) ;

Le paramtre pid indique le processus attendre ; lorsquil vaut -, waitpid attend la mort de
nimporte quel ls (comme wait). Le paramtre flags peut avoir les valeurs suivantes :
: dans ce cas waitpid attend la mort du ls (comme wait) ;
WNOHANG : dans ce cas waitpid rcupre le zombie si le processus est mort, mais retourne
immdiatement dans le cas contraire.
Lappel systme waitpid avec flags valant WNOHANG est un exemple de variante non-bloquante
dun appel systme bloquant, ce que nous tudierons de faon plus dtaille dans la partie .

.. Mort dun processus


Normalement, un processus meurt lorsquil invoque lappel systme _exit :

void _exit(int status) ;

Lorsquun processus eectue un appel _exec, il passe ltat zombie, dans lequel il nexcute
plus de code. Le zombie disparatra ds que sont pre fera un appel wait (voir ci-dessous).
La foncontion exit, que vous avez lhabitude dinvoquer, est quivalente fflush(NULL)
suivi dun appel _exit. Voyez aussi le paragraphe .. sur la relation entre exit et return.


. Excution de programme
Lexcution dun programme se fait laide de lappel systme execve :

int execve(const char *filename, char *const argv[],


char *const envp[]) ;

En cas de succs, execve remplace le processus courant par un processus qui excute le pro-
gramme contenu dans le chier filename avec les paramtres donns dans argv et avec un en-
vironnement gal celui contenu dans envp. Dans ce cas, execve ne retourne pas le contexte
dans lequel il a t appel a t dtruit (remplac par un contexte du programme filename), il
ny a donc pas o retourner.

.. Fonctions utilitaires
Lappel systme execve nest pas toujours pratique utiliser. La librairie standard contient un
certain nombre de wrappers autour dexecve. Parmi ceux-ci, les plus utiles sont execv, qui
duplique lenvironnement du pre, execvp, qui fait une recherche dans PATH lorsquil est pass
un chemin relatif, et execlp, qui prend ses arguments en ligne termins par un NULL (arguments
spread) :

int execv(const char *path, char *const argv[]) ;


int execvp(const char *file, char *const argv[]) ;
int execlp(const char *file, const char *arg, ...) ;

.. Parenthse : _start
Lorsquun programme est excut, la premire fonction qui sexcute est la fonction _start
de la bibliothque C. Cette fonction eectue les actions suivantes :
elle calcule les arguments de ligne de commande et lenvironnement, dune faon dpen-
dante du systme ;
elle stocke environ dans une variable globale ;
elle invoque main en lui passant argc et argv ;
si main retourne, elle invoque exit.
La fonction _start est la raison pour laquelle un retour normal de main (avec return)
provoque une terminaison du programme.

. Excution dun programme dans un nouveau processus


Pour excuter un programme dans un nouveau processus, il faut dabord crer un nouveau
processus (fork) puis excuter le processus dans le pre (execve). Il faut ensuite sarranger
pour excuter wait dans le pre.
Dans le cas dune excution synchrone, o le pre ne sexcute pas pendant lexcution du ls,
le schma typique est le suivant :
Vous voyez une bonne traduction ?


pid = fork() ;
if(pid < 0) {
/* Gestion des erreurs */
} else if(pid > 0) {
execlp(...) ;
/* Gestion des erreurs ? */
exit(1) ;
}
pid = wait(NULL) ;

.. Le double fork
Lors dune excution asynchrone, o le pre continue sexcuter durant lexcution du ls, il
faut sarranger pour que le pre appelle wait aprs la mort du ls an dliminer le zombie de
celui-ci. Une astuce souvent utilise consiste deshriter le ls en appelant fork deux fois de
suite, et en tuant le ls intermdiaire ; du coup, le petit-ls est adopt par le processus init :

pid = fork() ;
if(pid < 0) {
/* Gestion d'erreurs */
else if(pid == 0) {
/* Fils intermdiare */
pid = fork() ;
if(pid < 0) {
/* Gestion d'erreurs impossible */
} else if(pid == 0) {
/* Petit-fils */
execlp(...) ;
/* Gestion d'erreurs impossible */
exit(1) ;
}
exit(0) ;
}
/* Pre, attend la mort du fils intermdiare */
pid = wait(NULL) ;

Il existe une autre technique hors programme pour ce cours permettant dobtenir le mme
rsultat, qui consiste ignorer le signal SIGCHLD.

. Redirections et tubes
.. Descripteurs standard
Si tous les descripteurs de chiers sont identiques du point de vue du noyau, les descripteurs
de chier , et , dits descripteurs standard ont un rle conventionnel :


le descripteur de chier sappelle lentre standard, et il sert fournir lentre un proces-
sus qui ne lit qu un seul endroit ;
le descripteur de chier sappelle la sortie standard, et il sert de destination pour la sortie
dun processus qui ncrit qu un seul endroit ;
le descripteur de chier sappelle la sortie derreur standard, et il sert de destination pour
les messages derreur.
Comme tous les descripteurs de chiers, ces descripteurs standard sont hrits du pre. Norma-
lement, ils ont t associs au terminal par un parent, et permettent donc de lire et dcrire sur le
terminal.

.. Redirections
Plutt que de forcer tous les programmes implmenter la sortie sur le terminal, vers un chier,
vers limprimante etc., Unix permet de rediriger les descripteurs standard dun processus vers un
descripteur arbitraire, par exemple un chier ouvert auparavant ou un tube (paragraphe ..
ci-dessous).
Une redirection se fait laide de lappel dup2, qui copie un descripteur de chier :

int dup2(int oldfd, int newfd) ;

Un appel dup2 copie le descripteur oldfd en newfd, en fermant celui-ci auparavant sil tait
ouvert ; en cas de succs, il retourne newfd. Aprs un appel dup2, les entres oldfd et newfd
de la table de descripteurs de chiers pointent sur la mme entre de la table de chiers ouverts
le pointeur de position courante est donc partag (voir gure .).
Lappel systme dup est une version obsolte de dup2 :

int dup(int oldfd) ;

Un appel dup copie le descripteur de chier oldfd vers la premire entre libre de la table de
descripteurs de chier, et retourne le numro de cette dernire.

.. Tubes
Comme nous lavons vu en cours, la communication entre deux processus peut se faire travers
un chier. Une telle approche demande un mcanisme supplmentaire de synchronisation (par
exemple wait), et risque de laisser des chiers temporaires sur le disque.
Un tube consiste de deux descripteurs de chier, appels le bout criture et le bout lecture du
tube, connects travers un tampon. Une donne crite sur le bout criture est stocke dans le
tampon, et une lecture retourne les donnes contenues dans le tampon.
Un tube est cr laide de lappel systme pipe :

int pipe(int fd[2]) ;

Lappel systme pipe cre un nouveau tube. Son bout criture est stock en fd[1], et son bout
lecture est stock en fd[0]. La taille du tampon interne du tube nest pas spcie, mais POSIX
garantit quelle fait au moins octets.


Smantique des entres-sorties sur les tubes Les lectures sur les tubes ont la smantique sui-
vante :
un appel systme read peut retourner un nombre doctets strictement positif mais inf-
rieur celui demand mme si on na pas atteint une n de chier (on parle alors de lecture
partielle) ;
un appel systme read retourne (indication de n de chier) si lcrivain a ferm le tube
et celui-ci est vide ;
un appel systme read eectu sur un tube vide bloque.
Les critures ont la smantique suivante :
un appel systme write peut crire un nombre doctets strictement positif mais infrieur
celui demand (on parle alors dcriture partielle) ;
un appel systme write eectu sur un tube que le lecteur a ferm tue le processus avec
un signal SIGPIPE ;
un appel systme write eectu sur un tube plein bloque.
Cette smantique est plus relche que celle des entres-sorties sur disque, o les entres/sorties
ne bloquent jamais, o une lecture partielle nest possible qu la n du chier, et o une criture
partielle est impossible. En pratique, cela signie quon ne pourra pas utiliser un tampon simple
avec les tubes, il faudra utiliser un tampon avec pointeur de dbut ou un tampon circulaire (voir
partie .).

. Exemple
Le fragment de code suivant combine les plus importants des appels systme vus dans cette
partie :

int fd[2], rc ;
pid_t pid ;

rc = pipe(fd) ;
if(rc < 0) ...

pid = fork() ;
if(pid < 0) ...

if(pid == 0) {
close(fd[0]) ;
rc = dup2(fd[1], 1) ;
if(rc < 0) ...
execlp("whoami", "whoami", NULL) ;
...
} else {
char buf[101] ; /* Pourquoi 101 ? */

Ce quon peut viter en ignorant SIGPIPE ; dans ce cas, le write retourne - avec errno valant EPIPE.


int buf_end = 0 ;
close(fd[1]) ;
while(buf_end < 100) {
rc = read(fd[0], buf + buf_end, 100 - buf_end) ;
if(rc < 0) ...
if(rc == 0)
break ;
buf_end += rc ;
}

if(buf_end > 0 && buf[buf_end - 1] == '\n')


buf_end-- ;
buf[buf_end] = '\0';

printf("Je suis %s\n", buf) ;

wait(NULL) ; /* Pourquoi faire ? */


}


Entres-sorties non-bloquantes
Les appels systme read et write, lorsquils sont appliqus des tubes (ou des priphriques,
ou des sockets) peuvent bloquer pendant un temps indni. Cette smantique est souvent celle
qui convient, mais elle pose problme lorsquon veut appliquer des time-outs aux oprations
dentres-sorties, ou lire des donnes sur plusieurs descripteurs de chiers simultanment.
Un descripteur de chier peut tre mis en mode non-bloquant en passant le ag O_NONBLOCK
lappel systme open. Une opration de lecture ou criture sur un tel descripteur ne bloque
jamais ; si elle devait bloquer, elle retourne - avec errno valant EWOULDBLOCK ( cette opration
aurait bloqu ).

. Mode non-bloquant
Les ags dun descripteur obtenu laide de lappel systme open sont aects lors de louver-
ture. Souvent, cependant, les descripteurs sont obtenus laide dappels systme qui ne permettent
pas daecter les ags, notamment lappel systme pipe et, comme nous le verrons au deuxime
semestre, lappel systme socket.
Les ags dun descripteur existant peuvent tre rcuprs et aects laide de lappel systme
fcntl :

int fcntl(int fd, F_GETFL) ;


int fcntl(int fd, F_SETFL, int value) ;

La premire forme ci-dessus permet de rcuprer les ags ; en cas de russite, elle retourne les
ags. La deuxime aecte les ags la valeur passe en troisime paramtre.
Le ag O_NONBLOCK, qui nous intresse, peut donc tre positionn sur un descripteur de -
chier fd laide du fragment de code suivant :

rc = fcntl(fd, F_GETFL) ;
if(rc < 0)
...
rc = fcntl(fd, F_SETFL, rc | O_NONBLOCK) ;
if(rc < 0)
...

. Attente active
Sur un descripteur non-bloquant, il est possible de simuler une lecture bloquante laide dune
attente active, cest dire en vriant rptitivement si lopration peut russir sans bloquer :


while(1) {
rc = read(fd, buf, 512) ;
if(rc >= 0 || errno != EWOULDBLOCK)
break ;
}

Une attente active est plus exible quun appel systme bloquant. Par exemple, une attente avec
time-out simplmente simplement en interrompant lattente active au bout dun certain temps :

debut = time(NULL) ;
while(1) {
rc = read(fd, buf, 512) ;
if(rc >= 0 || errno != EWOULDBLOCK)
break ;
if(time(NULL) >= debut + 10)
break ;
}

De mme, une lecture sur deux descripteurs de chiers fd1 et fd2 peut se faire en faisant une
attente active simultanment sur fd1 et fd2 :

while(1) {
rc1 = read(fd1, buf, 512) ;
if(rc1 >= 0 || errno != EWOULDBLOCK)
break ;
rc2 = read(fd2, buf, 512) ;
if(rc2 >= 0 || errno != EWOULDBLOCK)
break ;
}

Comme son nom indique, lattente active utilise du temps de processeur pendant lattente ; de
ce fait, elle nest pas utilisable en pratique. Les mitigations qui consistent cder le processeur
pendant lattente ( laide de sched_yield ou usleep) ne rsolvent pas vraiment le problme.

. Lappel systme select


Lappel systme select permet deectuer lquivalent dune attente active sans avoir besoin
de boucler activement. Lappel select prend en paramtre des ensembles de descripteurs de
chiers (fd_set) et un time-out sous forme dune structure timeval.

.. La structure timeval
Nous avons dj vu le type time_t, qui reprsente un temps en secondes, soit relatif, soit ab-
solu (mesur depuis lpoque), et lappel systme time, qui retourne un temps absolu reprsent
comme un time_t.


BSD a introduit la structure timeval, qui reprsente un temps, absolu ou relatif, mesur en
secondes et micro-secondes :
struct timeval {
time_t tv_sec ;
int tv_usec ;
}
Le champ tv_usec reprsente les micro-secondes, et doit tre compris entre et .
Le temps absolu peut tre obtenu laide de lappel systme gettimeofday :
int gettimeofday(struct timeval *tv, struct timezone *tz) ;
Le paramtre tv pointe sur un tampon qui contiendra le temps courant aprs lappel. Le para-
mtre tz est obsolte, et doit valoir NULL.

.. Ensembles de descripteurs
Un ensemble de descripteurs est reprsent par le type opaque fd_set. Les oprations sur ce
type sont :
FD_ZERO(fd_set *set), qui aecte lenseble vide son paramtre ;
FD_SET(int fd, fd_set *set), qui ajoute llment fd son deuxime paramtre ;
FD_ISSET(int fd, fd_set *set), qui retourne vrai si fd est membre de son
deuxime paramtre.

.. Lappel systme select


Lappel systme select permet dattendre quun descripteur de chier parmi un ensemble soit
prt eectuer une opration dentres-sorties sans bloquer, ou quun intervalle de temps se soit
coul.
int select(int nfds,
fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
struct timeval *timeout) ;
Les paramtres readfds, writefds et exceptfds sont les ensembles de descripteurs sur les-
quels on attend, et timeout est un temps relatif qui borne le temps pendant lequel on attend. Le
paramtre nfds doit tre une borne exclusive des descripteurs de chiers contenus dans les trois
ensembles.
Un appel select bloque justqu ce quune des conditions suivantes soit vraie :
un des descripteurs contenus dans readfds est prt pour une lecture ; ou
un des descripteurs contenus dans writefds est prt pour une criture ; ou
un des descripteurs contenus dans exceptfds est dans une situation exceptionnelle (hors
programme pour ce cours) ; ou
un temps gal ou suprieur timeout sest coul.
Lappel select retourne dans ce dernier cas, et le nombre de descripteurs prts dans les autres
cas. Les descripteurs prts sont alors positionns dans les trois ensembles.
Dans tous les cas, la valeur de timeout est indnie (arbitraire) aprs lappel.


.. Exemples
Une lecture bloquante sur un descripteur se simule en bloquant dabord laide de select,
puis en eectuant une lecture :

fd_set readfds ;
FD_ZERO(&readfds) ;
FD_SET(fd, &readfds) ;
rc = select(fd + 1, &readfds, NULL, NULL, NULL) ;
if(FD_ISSET(fd, &readfds))
rc = read(fd, buf, 512) ;

Comme dans le cas dune attente active, il est facile dajouter un time-out :

struct timeval tv = {10, 0} ;


fd_set readfds ;
FD_ZERO(&readfds) ;
FD_SET(fd, &readfds) ;
rc = select(fd + 1, &readfds, NULL, NULL, &tv) ;
if(FD_ISSET(fd, &readfds))
rc = read(fd, buf, 512) ;

Il est tout aussi facile de gnraliser cette attente plusieurs descripteurs :

fd_set readfds ;
FD_ZERO(&readfds) ;
FD_SET(fd1, &readfds) ;
FD_SET(fd2, &readfds) ;
rc = select((fd1 >= fd2 ? fd1 : fd2) + 1,
&readfds, NULL, NULL, NULL) ;
if(FD_ISSET(fd, &readfds))
rc = read(fd, buf, 512) ;

.. Bug
La plupart des systmes Unix ont le bug suivant : un appel select peut parfois retourner un
descripteur de chier qui nest pas prt. De ce fait, il est ncessaire de mettre tous les descripteurs
de chier en mode non bloquant mme si on ne fait jamais faire une opration bloquante que sur
un descripteur qui a t retourn par select.