TP5 - Programmation Système C
TP5 - Programmation Système C
TP5 - Programmation Système C
303
15.1
<sys/types.h>
<stdio.h>
<stdlib.h>
<string.h>
<unistd.h>
#define LECTURE 0
#define ECRITURE 1
int main(int argc, char *argv[]) {
int tuyau[2], nb, i;
char donnees[10];
if (pipe(tuyau) == -1) { /* creation du pipe */
perror("Erreur dans pipe()");
exit(EXIT_FAILURE);
}
switch (fork()) { /* les deux processus partagent le pipe */
case -1 :
/* erreur */
perror("Erreur dans fork()");
exit(EXIT_FAILURE);
case 0 :
/* processus fils, lecteur */
close(tuyau[ECRITURE]); /* on ferme le cote ecriture */
/* on peut alors lire dans le pipe */
nb = read(tuyau[LECTURE], donnees, sizeof(donnees));
for (i = 0; i < nb; i++) {
putchar(donnees[i]);
}
putchar(\n);
close(tuyau[LECTURE]);
exit(EXIT_SUCCESS);
default :
/* processus pere, ecrivain */
close(tuyau[LECTURE]); /* on ferme le cote lecture */
strncpy(donnees, "bonjour", sizeof(donnees));
305
Puisquun tuyau sutilise comme un fichier, il serait agrable de pouvoir utiliser les
fonctions dentres / sorties standard (fprintf(), fscanf()...) au lieu de read() et
write(), qui sont beaucoup moins pratiques. Pour cela, il faut transformer les descripteurs de fichiers en pointeurs de type FILE *, comme ceux renvoys par fopen().
La fonction fdopen() permet de le faire. Elle prend en argument le descripteur
de fichier transformer et le mode daccs au fichier ("r" pour la lecture et "w" pour
lcriture) et renvoie le pointeur de type FILE * permettant dutiliser les fonctions
dentre/sortie standard.
Lexemple suivant illustre lutilisation de la fonction fdopen() :
Listing 15.2 Dialogue pre / fils
#include
#include
#include
#include
<sys/types.h>
<stdio.h>
<stdlib.h>
<unistd.h>
#define LECTURE 0
#define ECRITURE 1
int main (int argc, char *argv[]) {
int tuyau[2];
char str[100];
FILE *mon_tuyau ;
if ( pipe(tuyau) == -1 ) {
perror("Erreur dans pipe()");
exit(EXIT_FAILURE);
}
switch (fork()) {
case -1 :
/* erreur */
perror("Erreur dans fork()");
exit(EXIT_FAILURE);
case 0 :
/* processus fils, lecteur */
close(tuyau[ECRITURE]);
/* ouvre un descripteur de flot FILE * a partir */
/* du descripteur de fichier UNIX */
mon_tuyau = fdopen(tuyau[LECTURE], "r");
if (mon_tuyau == NULL) {
perror("Erreur dans fdopen()");
exit(EXIT_FAILURE);
}
/* mon_tuyau est un FILE * accessible en lecture */
fgets(str, sizeof(str), mon_tuyau);
printf("Mon pere a ecrit : %s\n", str);
/* il faut faire fclose(mon_tuyau) ou a la rigueur */
/* close(tuyau[LECTURE]) mais surtout pas les deux */
306
Il faut cependant garder lesprit que les fonctions dentres / sorties standard
utilisent une zone de mmoire tampon lors de leurs oprations de lecture ou dcriture.
Lutilisation de cette zone tampon permet doptimiser, en les regroupant, les accs au
disque avec des fichiers classiques mais elle peut se rvler particulirement gnante
avec un tuyau. Dans ce cas, la fonction fflush() peut se rvler trs utile puisquelle
permet dcrire la zone tampon dans le tuyau sans attendre quelle soit remplie.
Il est noter que lappel fflush() tait inutile dans lexemple prcdent en
raison de son appel implicite lors de la fermeture du tuyau par fclose().
Lexemple suivant montre lutilisation de la fonction fflush() :
Listing 15.3 Forcer lcriture dans le tuyau
#include
#include
#include
#include
<sys/types.h>
<stdio.h>
<stdlib.h>
<unistd.h>
#define LECTURE 0
#define ECRITURE 1
int main(int argc, char *argv[]) {
int tuyau[2];
char str[100];
FILE *mon_tuyau;
if ( pipe(tuyau) == -1 ) {
perror("Erreur dans pipe()");
exit(EXIT_FAILURE);
}
switch (fork()) {
case -1 :
/* erreur */
perror("Erreur dans fork()");
exit(EXIT_FAILURE);
case 0 :
/* processus fils, lecteur */
close(tuyau[ECRITURE]);
mon_tuyau = fdopen(tuyau[LECTURE], "r");
if (mon_tuyau == NULL) {
perror("Erreur dans fdopen()");
exit(EXIT_FAILURE);
}
fgets(str, sizeof(str), mon_tuyau);
printf("[fils] Mon pere a ecrit : %s\n", str);
307
Une technique couramment employe avec les tuyaux est de relier la sortie standard
dune commande lentre standard dune autre, comme quand on tape
ls | wc -l
Le problme, cest que la commande ls est conue pour afficher lcran et pas
dans un tuyau. De mme pour wc qui est conue pour lire au clavier.
Pour rsoudre ce problme, Unixfournit une mthode lgante. Les fonctions dup()
et dup2() permettent de dupliquer le descripteur de fichier pass en argument :
#include <unistd.h>
int dup(int descripteur);
int dup2(int descripteur, int copie);
308
<sys/types.h>
<stdio.h>
<stdlib.h>
<string.h>
<unistd.h>
#define LECTURE 0
#define ECRITURE 1
int main(int argc, char *argv[]) {
int tuyau[2];
if (pipe(tuyau) == -1) {
perror("Erreur dans pipe()");
exit(EXIT_FAILURE);
}
switch (fork()) {
case -1 :
/* erreur */
perror("Erreur dans fork()");
exit(EXIT_FAILURE);
case 0 :
/* processus fils, ls , ecrivain */
close(tuyau[LECTURE]);
/* dup2 va brancher le cote ecriture du tuyau */
/* comme sortie standard du processus courant */
if (dup2(tuyau[ECRITURE], STDOUT_FILENO) == -1) {
perror("Erreur dans dup2()");
}
/* on ferme le descripteur qui reste pour */
/* << eviter les fuites >> ! */
close(tuyau[ECRITURE]);
/* ls en ecrivant sur stdout envoie en fait dans le */
/* tuyau sans le savoir */
if (execlp("ls", "ls", NULL) == -1) {
perror("Erreur dans execlp()");
exit(EXIT_FAILURE);
}
default :
/* processus pere, wc , lecteur */
close(tuyau[ECRITURE]);
/* dup2 va brancher le cote lecture du tuyau */
/* comme entree standard du processus courant */
if (dup2(tuyau[LECTURE], STDIN_FILENO) == -1) {
perror("Erreur dans dup2()");
309
<sys/types.h>
<stdio.h>
<stdlib.h>
<unistd.h>
#define LECTURE 0
#define ECRITURE 1
int main(int argc, char *argv[]) {
int tuyau[2], i;
char car;
if (pipe(tuyau) == -1) {
perror("Erreur dans pipe()");
3. moins quil nait indiqu au systme, grce loption O_NONBLOCK de la fonction fcntl(), de lui
renvoyer une erreur au lieu de le suspendre.
310
Le signal SIGPIPE
Lorsquun processus crit dans un tuyau qui na plus de lecteurs (parce que les
processus qui lisaient le tuyau sont termins ou ont ferm le ct lecture du tuyau), ce
processus reoit le signal SIGPIPE. Comme le comportement par dfaut de ce signal est
de terminer le processus, il peut tre intressant de le grer afin, par exemple, davertir
lutilisateur puis de quitter proprement le programme. Les lecteurs curieux pourront
approfondir lutilisation des signaux en consultant les pages de manuel des fonctions
suivantes :
sigaction() ;
sigemptyset() ;
sigsuspend() ;
...
Lquipe enseignante se tient leur disposition pour des complments dinformation.
Autres moyens de communication entre processus
Comme on vient de le voir, linconvnient principal des tuyaux est de ne fonctionner quavec des processus issus dun anctre commun. Pourtant, les mcanismes de
communication mis en jeu dans le noyau sont gnraux et, en fait, seul lhritage des
descripteurs de fichiers aprs un fork() impose cette restriction.
Pour sen affranchir, il faut que les processus dsirant communiquer puissent
dsigner le tuyau quils souhaitent utiliser. Ceci se fait grce au systme de fichiers.
Un tuyau nomm est donc un fichier :
menthe22> mkfifo fifo
menthe22> ls -l fifo
prw-r--r-- 1 in201
in201
Il sagit cependant dun fichier dun type particulier, comme le montre le p dans
laffichage de ls.
Une fois cr, un tuyau nomm sutilise trs facilement :
menthe22> echo coucou > fifo &
[1] 25312
menthe22> cat fifo
[1] + done
echo coucou > fifo
coucou
Si lon fait abstraction des affichages parasites du shell, le tuyau nomm se comporte
tout fait comme on sy attend. Bien que la faon de lutiliser puisse se rvler
trompeuse, un tuyau nomm transfre bien ses donnes dun processus lautre en
mmoire, sans les stocker sur disque. La seule intervention du systme de fichiers
consiste permettre laccs au tuyau par lintermdiaire de son nom.
Il faut noter que :
Un processus tentant dcrire dans un tuyau nomm ne possdant pas de lecteurs
sera suspendu jusqu ce quun processus ouvre le tuyau nomm en lecture. Cest
312
15.2. Exercices
pourquoi, dans lexemple, echo a t lanc en tche de fond, afin de pouvoir
rcuprer la main dans le shell et dutiliser cat. On peut tudier ce comportement
en travaillant dans deux fentres diffrentes.
De mme, un processus tentant de lire dans un tuyau nomm ne possdant pas
dcrivains se verra suspendu jusqu ce quun processus ouvre le tuyau nomm
en criture. On peut tudier ce comportement en reprenant lexemple mais en
lanant dabord cat puis echo.
En particulier, ceci signifie quun processus ne peut pas utiliser un tuyau nomm
pour stocker des donnes afin de les mettre la disposition dun autre processus une
fois le premier processus termin. On retrouve le mme phnomne de synchronisation
quavec les tuyaux classiques.
En C, un tuyau nomm se cre au moyen de la fonction mkfifo() :
#include <sys/stat.h>
int retour;
retour = mkfifo("fifo", 0644);
if ( retour == -1 ) {
/* erreur : le tuyau nomme na pas pu etre cree */
}
Il doit ensuite tre ouvert (grce open() ou fopen()) et sutilise au moyen des
fonctions dentres / sorties classiques.
15.2
Exercices
Question 1
crire un programme tuyau1 qui met en place un tuyau et cre un processus fils.
Le processus fils lira des lignes de caractres du clavier, les enverra au processus pre
par lintermdiaire du tuyau et ce dernier les affichera lcran.
Le processus fils se terminera lorsquon tapera Control-D (qui fera renvoyer NULL
fgets()), aprs avoir ferm le tuyau. Le processus fils se terminera de la mme
faon.
Cet exercice permet de mettre en vidence la synchronisation de deux processus au
moyen dun tuyau.
Fonctions utiliser :
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
int pipe(int tuyau[2])
pid_t fork()
FILE *fdopen(int fichier, char *mode)
char *fgets(char *chaine, size_t taille, FILE *fichier)
Question 2
On se propose de raliser un programme mettant en communication deux commandes par lintermdiaire dun tuyau, comme si lon tapait dans un shell
313
ls | wc -l
Question 3
Reprenez le programme myshell du TP prcdent et modifiez-le afin quil reconnaisse aussi les commandes de la forme
ls -l | sort +4 -n
Fonctions utiliser :
#include <sys/types.h>
#include <string.h>
#include <unistd.h>
char *strtok(char *chaine, char *separateurs)
int pipe(int tuyau[2])
pid_t fork()
int dup2(int descripteur, int copie)
int execlp(const char *fichier, const char *arg, ...)
La fonction strtok() vous sera utile pour analyser la ligne tape au clavier.
Si vous avez le temps, essayez de modifier le programme pour quil reconnaisse un
enchanement quelconque de commandes :
cat toto | grep tata | wc -l
314
15.3. Corrigs
15.3
Corrigs
#include
#include
#include
#include
<sys/types.h>
<stdio.h>
<stdlib.h>
<unistd.h>
#define LECTURE 0
#define ECRITURE 1
int main(int argc, char *argv[]) {
int tuyau[2];
FILE *mon_tuyau;
char ligne[80];
if (pipe(tuyau) == -1) {
perror("Erreur dans pipe()");
exit(EXIT_FAILURE);
}
switch (fork()) {
case -1 :
/* erreur */
perror("Erreur dans fork()");
_
exit(EXIT FAILURE);
case 0 :
/* processus fils, ecrivain */
close(tuyau[LECTURE]); /* on ferme le cote lecture */
/* ouvre un descripteur de flot FILE * a partir */
/* du descripteur de fichier UNIX */
mon_tuyau = fdopen(tuyau[ECRITURE], "w");
if (mon_tuyau == NULL) {
perror("Erreur dans fdopen()");
exit(EXIT_FAILURE);
}
printf("Je suis le fils, tapez des phrases svp\n");
/* on lit chaque ligne au clavier */
/* mon_tuyau est un FILE * accessible en ecriture */
while (fgets(ligne, sizeof(ligne), stdin) != NULL) {
/* on ecrit la ligne dans le tuyau */
fprintf(mon_tuyau, "%s", ligne);
fflush(mon_tuyau);
}
/* il faut faire fclose(mon_tuyau) ou a la rigueur */
/* close(tuyau[LECTURE]) mais surtout pas les deux */
fclose(mon_tuyau);
exit(EXIT_SUCCESS);
default :
/* processus pere, lecteur */
close(tuyau[ECRITURE]); /* on ferme le cote ecriture */
_
mon tuyau = fdopen(tuyau[LECTURE], "r");
if (mon_tuyau == NULL) {
perror("Erreur dans fdopen()");
exit(EXIT_FAILURE);
}
/* on lit chaque ligne depuis le tuyau */
/* mon_tuyau est un FILE * accessible en lecture */
while (fgets(ligne, sizeof(ligne), mon_tuyau)) {
/* et on affiche la ligne a lecran */
/* la ligne contient deja un \n a la fin */
printf(">>> %s", ligne);
}
fclose(mon_tuyau);
315
<sys/types.h>
<stdio.h>
<stdlib.h>
<unistd.h>
#define LECTURE 0
#define ECRITURE 1
int main(int argc, char *argv[]) {
int tuyau[2];
if (pipe(tuyau) == -1) {
perror("Erreur dans pipe().");
exit(EXIT_FAILURE);
}
switch (fork()) {
case -1 :
/* erreur */
perror("Erreur dans fork().");
_
exit(EXIT FAILURE);
case 0 :
/* processus fils */
close(tuyau[LECTURE]);
/* dup2 va brancher le cote ecriture du tuyau */
/* comme sortie standard du processus courant */
dup2(tuyau[ECRITURE],STDOUT_FILENO);
/* on ferme le descripteur qui reste */
close(tuyau[ECRITURE]);
if (execlp("ls", "ls", "-l", NULL) == -1) {
perror("Erreur dans execlp()");
exit(EXIT_FAILURE);
}
default :
/* processus pere */
close(tuyau[ECRITURE]);
/* dup2 va brancher le cote lecture du tuyau */
/* comme entree standard du processus courant */
dup2(tuyau[LECTURE], STDIN_FILENO);
/* on ferme le descripteur qui reste */
close(tuyau[LECTURE]);
if (execlp("sort", "sort", "+4", "-n", NULL) == -1) {
perror("Erreur dans execlp()");
exit(EXIT_FAILURE);
}
}
exit(EXIT_SUCCESS);
}
<sys/types.h>
<stdio.h>
<stdlib.h>
<string.h>
<unistd.h>
<sys/wait.h>
316
15.3. Corrigs
#define LECTURE 0
#define ECRITURE 1
int main(int argc, char *argv[]) {
char ligne[80], *arg1[10], *arg2[10], *tmp;
int i, tuyau[2];
printf("myshell> ");
/* lecture de lentree standard ligne par ligne */
while (fgets(ligne, sizeof(ligne), stdin) != NULL) {
/* fgets lit egalement le caractere de fin de ligne */
if (strcmp(ligne, "exit\n") == 0) {
exit(EXIT_SUCCESS);
}
/* on decoupe la ligne en sarretant des que */
/* lon trouve un | : cela donne la premiere commande */
for (tmp = strtok(ligne, " \t\n"), i = 0;
tmp != NULL && strcmp(tmp , "|") != 0;
tmp = strtok(NULL, " \t\n"), i++) {
arg1[i] = tmp;
}
arg1[i] = NULL;
/* on decoupe la suite si necessaire pour obtenir la */
/* deuxieme commande */
arg2[0] = NULL;
if (tmp != NULL && strcmp(tmp, "|") == 0) {
for (tmp = strtok(NULL, " \t\n"), i = 0;
tmp != NULL;
tmp = strtok(NULL, " \t\n"), i++) {
arg2[i] = tmp;
}
arg2[i] = NULL;
}
if (arg2[0] != NULL) { /* il y a une deuxieme commande */
pipe(tuyau);
}
switch (fork()) {
case -1 :
/* erreur */
perror("Erreur dans fork()");
_
exit(EXIT FAILURE);
case 0 :
/* processus fils */
if (arg2[0] != NULL) { /* tuyau */
close(tuyau[LECTURE]);
dup2(tuyau[ECRITURE], STDOUT_FILENO);
close(tuyau[ECRITURE]);
}
execvp(arg1[0], arg1);
/* on narrivera jamais ici, sauf en cas derreur */
perror("Erreur dans execvp()");
exit(EXIT_FAILURE);
default :
/* processus pere */
if (arg2[0] != NULL) {
/* on recree un autre fils pour la deuxieme commande */
switch (fork()) {
case -1 :
/* erreur */
perror("Erreur dans fork()");
_
exit(EXIT FAILURE);
case 0 :
/* processus fils */
close(tuyau[ECRITURE]);
dup2(tuyau[LECTURE], STDIN_FILENO);
317
<sys/types.h>
<signal.h>
<stdio.h>
<stdlib.h>
<string.h>
<unistd.h>
<sys/wait.h>
#define LECTURE 0
#define ECRITURE 1
/* le type liste chainee darguments */
typedef struct argument {
char *arg;
struct argument *suivant;
} *t_argument;
/* le type commande, liste doublement chainee */
typedef struct commande {
int nombre;
t_argument argument;
struct commande *suivant;
struct commande *precedent;
} *t_commande;
int main(int argc ,char *argv[]) {
char ligne[80];
t_commande commande;
commande = (t_commande)malloc(sizeof(struct commande));
printf("myshell> ");
while (fgets(ligne, sizeof(ligne), stdin) != NULL) {
char *tmp;
t_commande com;
t_argument arg, precedent;
int tuyau[2], ecriture;
pid_t pid, dernier;
if (strcmp(ligne ,"exit\n") == 0) {
exit(EXIT_SUCCESS);
}
318
15.3. Corrigs
/* premier maillon */
com = commande;
com->argument = NULL ;
com->nombre
= 0 ;
com->suivant
= NULL ;
com->precedent = NULL ;
precedent = NULL ;
do {
/* remplissage des arguments dune commande */
/* avec allocation des maillons de la liste darguments */
for (tmp = strtok((commande->argument == NULL)?ligne:NULL,
" \t\n");
tmp != NULL && strcmp(tmp, "|") != 0;
tmp = strtok(NULL, " \t\n")) {
arg = (t_argument)malloc(sizeof(struct argument));
if (arg == NULL) {
perror("Erreur dans malloc()");
exit(EXIT_FAILURE);
}
/* chainage de la liste darguments */
if (precedent != NULL) {
precedent->suivant = arg;
precedent = arg;
} else {
com->argument = arg;
precedent = arg;
}
arg->arg = tmp;
arg->suivant = NULL;
com->nombre++;
}
if (tmp != NULL) { /* vrai uniquement si on a un | */
/* allocation dune nouvelle commande */
com->suivant = (t_commande)malloc(sizeof(struct commande));
if (com->suivant == NULL) {
perror("Erreur dans malloc()");
exit(EXIT_FAILURE);
}
/* chainage double du nouveau maillon */
com->suivant->precedent = com;
com = com->suivant;
com->argument = NULL;
com->nombre = 0;
com->suivant = NULL;
precedent = NULL;
}
} while (tmp != NULL); /* do */
/* on cree les processus en remontant la ligne de commande */
for (; com != NULL; com = com->precedent) {
int i;
char **args;
/* creation du tuyau */
if (com->precedent != NULL) {
if (pipe(tuyau) == -1) {
perror("Erreur dans pipe()");
exit(EXIT_FAILURE);
}
}
319
320
15.3. Corrigs
if (com != NULL) {
free(com->suivant); /* on libere la commande */
}
}
printf("myshell> ");
}
free(commande);
exit(EXIT_SUCCESS);
}
<sys/types.h>
<signal.h>
<stdio.h>
<stdlib.h>
<string.h>
<unistd.h>
<ctype.h>
<sys/wait.h>
#define LECTURE 0
#define ECRITURE 1
/* nombre maximal darguments dune commande */
#define MAXARG 10
/* compte le nombre darguments de la commande */
/* la plus a droite de la ligne, sarrete au | */
char *recherche_pipe(char *str, int *narg) {
int l,i;
int n = 1;
l = strlen(str);
for (i = l-1; str[i] != | && i > 0; i--) {
if (isspace(str[i-1]) && !isspace(str[i])) n++;
}
*narg = n;
if (str[i] == |) {
str[i] = \0;
return(str+i+1);
}
return(str);
}
/* Decoupage de la ligne en place (sans recopie) */
void decoupe_commande(char *ligne, char *arg[]) {
int i=0;
/* on avance jusquau premier argument */
while (isspace(*ligne)) ligne++;
/* on traite les arguments tant que ce nest */
/* pas la fin de la ligne */
while (*ligne != \0 && *ligne != \n) {
arg[i++]=ligne;
/* on avance jusquau prochain espace */
while (!isspace(*ligne) && *ligne!=\0)
ligne++;
/* on remplace les espaces par \0 ce qui marque */
/* la fin du parametre precedent */
321
}
arg[i]=NULL;
}
322
15.3. Corrigs
/* decoupe cette commande en tableau arg[] */
decoupe_commande(basecommande, arg);
/* lance la commande, en notant le pid de la derniere */
if (letuyau == -1) {
pid = execute_commande(arg, &letuyau);
} else {
execute_commande(arg, &letuyau);
}
}
/* le premier processus de la ligne na pas de stdin */
close(letuyau);
waitpid(pid, NULL, 0);
printf("myshell> ");
}
exit(EXIT_SUCCESS);
}
323
15.4
Corrections dtailles
Nous allons dans un premier temps revenir trs rapidement sur les fichiers afin que
les diffrentes fonctions que nous utiliserons pour manipuler les tuyaux soient bien
prsentes dans les esprits des lecteurs.
Les fichiers sous Unix
Les fichiers reprsentent en fait des ensembles de donnes manipules par lutilisateur. Un fichier nest donc rien dautre quun rceptacle donnes. Le systme nous
fournit diffrents moyens de manipuler ces rceptacles, car avant de pouvoir obtenir les
donnes il faut avant tout pouvoir se saisir du bon rceptacle. Entre lutilisateur et le
fichier, linterface la plus simple est le descripteur de fichier, cest--dire un entier : il
sagit du numro de la ligne de la table des fichiers ouverts faisant rfrence au fichier
souhait. Ce descripteur est obtenu trs simplement par lappel systme open().
Un descripteur de fichier (point dentre dans la table des fichiers ouverts) ne permet
que des manipulations simples sur les fichiers : la lecture et lcriture se feront par
entit atomique, donc des octets, via les deux fonctions systme read() et write().
Afin de pouvoir manipuler des entits plus structures (des entiers, des mots, des
double, etc.) il nous faut utiliser une autre interface que le descripteur entier comme
le montre la figure 15.1.
Linterface mise en place par le descripteur complexe FILE permet, outre une
manipulation plus simple des entits lire ou crire, une temporisation des accs au
fichier. Ainsi au lieu dcrire octet par octet dans le fichier, le systme placera dans un
premier temps les diffrentes donnes dans une zone tampon. Quand la ncessit sen
fera sentir, la zone tampon sera vide. Ceci vous explique pourquoi certaines critures
ne se font jamais lorsquun programme commet une erreur et sarrte, mme si lerreur
est intervenu aprs lappel fprintf().
Nous pouvons aisment passer du descripteur entier au descripteur complexe en
324
Lorsque nous utilisons la fonction fdopen(), nous nouvrons pas le fichier une
seconde fois, nous nous contentons simplement dobtenir une nouvelle faon de manipuler les donnes qui y sont stockes. Donc, lorsque nous fermons le fichier, nous
devons utiliser soit la fonction systme close(), soit la fonction de la bibliothque
standard fclose(), mais certainement pas les deux !
Quel lien avec les tuyaux ? Et bien les tuyaux sont des fichiers un peu particuliers
donc mieux vaut savoir comment manipuler les fichiers ! Il est important de garder
lesprit quun descripteur de fichier est obtenu en demandant une ouverture de fichier
avec un mode : lecture ou criture, il en sera de mme avec les tuyaux.
Les tuyaux
Un tuyau est une zone dchange de donnes entre deux ou plusieurs processus.
Cette zone est cependant assez restrictive quant aux changes autoriss, ceux-ci sont
unidirectionnels et destructifs :
la communication ne peut se fait que dun groupe de processus (les crivains)
vers dautres (les lecteurs). Une fois quun processus a choisit sa nature (crivain
ou lecteur), impossible de revenir en arrire.
toute donne lue par un lecteur nest plus disponible pour les autres lecteurs.
Comme le nom le suggre assez bien, un tuyau doit possder deux descripteurs de
fichier, lun permettant dcrire, et lautre permettant de lire. La figure 15.2 dcrit de
manire schmatique un tuyau et les deux descripteurs attachs.
On subodore donc que la fonction permettant de crer un tel objet doit soit renvoyer
un tableau de deux entiers, soit prendre en paramtre un tableau de deux entiers. Mais
comme nous savons que les fonctions systmes retournent en gnral un code derreur,
la bonne solution doit tre la seconde :
int pipe(int filedes[2]);
Il existe un moyen simple pour se souvenir quid des deux descripteurs est celui utilis
en lecture (du coup lautre sera utilis en criture). Le priphrique servant lire nest
rien dautre que le clavier, soit de manire un peu plus gnrale stdin, dont le numro
est 0. Le descripteur utilis en lecture est donc fildes[0].
Nous allons commencer par utiliser un tuyau au sein dun seul processus, ce qui ne
servira pas grand chose sinon fixer les esprits sur les diffrents moyens mis notre
disposition pour lire et crire dans un tuyau.
325
F IGURE 15.2 Un tuyau permet de ranger (dans lordre darrive selon les critures)
des donnes en mmoire pour les rcuprer. Il est donc fourni avec deux descripteurs
de fichier, lun servant crire et lautre lire.
326
La cration du tuyau est trs simple, lappel systme pipe() nous renvoie un code
derreur si le tuyau na pas pu tre cr. Si tout se passe bien nous obtenons nos deux
descripteurs entiers dans le tableau tuyau[] pass en paramtre.
Dans un premier temps nous allons simplement crire 10 octets dans le tuyau
par lintermdiaire du descripteur dcriture. Nous utilisons la fonction write(),
intermdiaire dcriture incontournable pour les descripteurs de fichier entiers. Puis
nous lisons, mais sur le ct lecture du tuyau, laide de la fonction read(). On lit
exactement le bon nombre de caractres (ou moins la rigueur, mais surtout pas plus).
Afin de faire appel des fonctions manipulant des donnes un peu plus volues
que les octets, nous demandons une reprsentation plus complexe des deux descripteurs,
et pour ce faire nous utilisons la fonction fdopen() qui nous renvoie un descripteur
complexe (FILE *) partir dun descripteur entier.
Nous pouvons alors crire et lire des donnes structures, comme le montre la
suite du programme, avec toutefois un bmol important, lutilisation de la fonction
fflush(), car nous le savons les descripteurs complexes utilisent des tampons et ne
placent les donnes dans leur fichier destination immdiatement.
Les tuyaux nomms
<unistd.h>
<stdlib.h>
<stdio.h>
<sys/types.h>
<sys/stat.h>
Ensuite le programme de lecture qui prend un argument afin de pouvoir tre identifi
car nous allons le lancer dans 2 fentres diffrentes :
Listing 15.13 Le programme de lecture complmentaire au prcdent
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char **argv)
{
FILE *fp;
char phrase[256];
if (argc < 2) {
fprintf(stderr,"usage: %s <un nom>\n",argv[0]);
exit(EXIT_FAILURE);
}
if ((fp = fopen("ma_fifo","r")) == NULL) {
perror("P2: Impossible douvrir ma_fifo");
exit(EXIT_FAILURE);
}
while(fgets(phrase,255,fp)!=NULL) {
printf("%s: \"%s\"\n",argv[1],phrase);
}
fclose(fp);
exit(EXIT_SUCCESS);
}
Nous voyons tout dabord que le programme p1 reste bloqu tant quil reste des
lecteurs. Ensuite nous remarquons que le programme p2 lanc dans deux fentres
diffrentes, lit alternativement les diffrentes critures ce qui reflte bien la notion de
lecture destructive.
Il est important de garder lesprit que les donnes changes ne sont jamais crites
sur le disque. Le fichier ma_fifo ne sert qu donner un nom, mais tout les changes
restent confins la mmoire.
Nous allons maintenant passer aux tuyaux permettant diffrents processus issus
dun mme pre de partager des donnes.
Tuyaux et processus
Nous savons que lappel fork() permet de conserver les descripteurs de fichier
ouverts lors de la duplication. Nous allons tirer partie de cela pour crer un processus
fils qui va pouvoir dialoguer avec son pre.
Il est fondamental de bien comprendre que lappel fork() vient dupliquer les
diffrents descripteurs et quune fois la duplication ralise, le tuyau possde deux
points dentre et deux points de sortie comme le montre la figure 15.3.
Nous allons mettre en uvre cela en rpondant la question 15.2. Notre programme
va sarticuler autour dune fonction qui lira des caractres sur le clavier (lentre
standard), une fonction qui les crira dans le tuyau et une fonction qui lira dans le
tuyau.
Dans un premier temps nous crons le tuyau, car il est impratif quil existe
avant la duplication pour tre partag. la suite de la duplication, nous scinderons le
programme en deux, la partie fils ralisera lenvoi des donnes dans le tuyau, la
partie pre les lira et les affichera lcran.
Listing 15.14 Utilisation de la duplication
#include
#include
#include
#include
<unistd.h>
<stdlib.h>
<stdio.h>
<string.h>
#define LECTURE 0
329
F IGURE 15.3 Un fois le tuyau cr, la duplication va offrir deux points dentre
et deux points de sortie sur le tuyau puisque les descripteurs sont partags entre le
processus pre et le processus fils. Nous avons donc un tuyau possdant deux lecteurs
et deux crivains, il faut imprativement faire quelque chose !
#define ECRITURE 1
330
F IGURE 15.4 Une fois le tuyau cr et la duplication effective, on ferme lun des
lecteurs et lun des crivains. Le tuyau ne possde plus quune entre (le fils) et une
sortie (le pre).
Tuyaux et recouvrement
Une des utilisations possibles est de pouvoir faire communiquer des programmes
entre eux, nous lavons vu avec les tuyaux nomms. Peut-on aboutir la mme chose
sans ces tuyaux nomms ? Avec des programmes que nous concevons nous-mmes et
que nous pouvons intgrer dans un dveloppement, la chose est strictement identique
ce que nous venons de faire dans le programme prcdent. Par contre, comment
procder lexcution de cette commande assez simple : ls -l | wc -l ? Il faut
brancher la sortie standard de la commande ls sur lentre standard de la commande
wc. Pour ce faire nous allons utiliser une nouvelle commande : dup2().
Cette commande cre une copie dun descripteur de fichier, et en paraphrasant le
manuel, aprs un appel russi cette commande, le descripteur et sa copie peuvent tre
utiliss de manire interchangeable. La solution est donc l : on cre un tuyau et on
duplique son entre (le ct crivain) sur la sortie standard de la premire commande
(ls -l). Puis on duplique sa sortie (le ct lecteur) sur lentre standard de la deuxime
commande (wc -l). Ainsi, la premire commande crit dans le tuyau et la deuxime
commande lit dans le tuyau. La figure 15.5 dcrit ce cheminement.
Nous allons donc pouvoir programmer laide de fork() et dup2() lexercice
de recouvrement. Nous avons deux commandes et nous allons utiliser fork() pour
obtenir deux processus. Lequel du pre ou du fils doit se charger de la premire
commande ? Il faudrait que le pre se termine aprs le fils, donc le pre doit attendre
des informations du fils, ce qui implique ncessairement que le pre soit en charge de
la dernire commande et le fils de la premire. La figure 15.6 dcrit la chronologie des
vnements.
332
333
<unistd.h>
<stdlib.h>
<stdio.h>
<sys/types.h>
<sys/wait.h>
#define LECTURE 0
#define ECRITURE 1
334
Linterprte de commandes
<sys/types.h>
<signal.h>
<stdio.h>
<stdlib.h>
<string.h>
<unistd.h>
<sys/wait.h>
<string.h>
#define LECTURE 0
#define ECRITURE 1
#define MAXCHAR_LIGNE 80
#define MAXARG 10
/* Structure de liste chainee pour les arguments */
/* dune commande */
typedef struct argument {
char *arg;
/* largument (non recopie)
*/
struct argument *suivant; /* ladresse du maillon suivant */
} Argument;
/* Structure de liste chainee pour les commandes */
typedef struct commande {
int numero;
/* le numero de la commande (4fun)
*/
int argc;
/* le nombre darguments de la commande*/
Argument *argv;
/* la liste chainee des arguments
*/
struct commande *suivante; /* ladresse du maillon suivant
*/
} Commande;
/* Cette fonction ajoute un argument (char *) a la liste
* chainee "l" des arguments.
* Si la liste est vide (l==NULL) la fonction retourne
* ladresse du nouveau maillon, sinon la fonction rajoute
* largument a la fin de la liste en se rappelant elle meme
* La liste construite a partir de :
* l=NULL;
* l = add_argument(l,arg1);
* l = add_argument(l,arg2);
* l = add_argument(l,arg3);
*
* sera donc : l(arg1) --> l(arg2) --> l(arg3) --> NULL
*/
Argument *add_argument(Argument *l, char *argvalue)
{
335
336
337
338
339
340