• Aucun résultat trouvé

Gestion des threads au niveau noyau

10 Communication centralisée inter- inter-processus

10.1 Les tubes (pipes)

10.1.1 Les tubes anonymes

10 Communication centralisée

DESCRIPTION

pipe() crée une paire de descripteurs de fichiers, pointant sur un inoeud de tube, et les place dans un

tableau filedes. filedes[0] est utilisé pour la lecture, et filedes[1] pour l’écriture.

VALEUR RENVOYÉE

En cas de réussite, zéro est renvoyé, sinon -1 est renvoyé et errno contient le code d’erreur.

ERREURS

EFAULT : filedes est invalide.

EMFILE : Trop de descripteurs de fichiers sont utilisés par le processus.

ENFILE : La limite du nombre total de fichiers ouverts sur le système a été atteinte.

>>

La fermeture d’un tube se fait comme la fermeture d’un fichier, à l’aide de la fonction close() :

<<

SYNOPSIS

#include <unistd.h>

int close(int fd);

DESCRIPTION

close() ferme le descripteur fd, de manière à ce qu'il ne référence plus aucun fichier, et puisse être réutilisé.

Si fd est la dernière copie d'un descripteur de fichier donné, les ressources qui lui sont associées sont libérées.

Par exemple tous les verrouillages sont supprimés et si le descripteur etait la dernière référence sur un fichier supprimé avec unlink(2), le fichier est effectivement effacé.

VALEUR RENVOYEE

close renvoie 0 s'il réussit, ou -1 en cas d'échec, auquel cas errno contient le code d'erreur.

ERREURS

EBADF : Le descripteur de fichier fd est invalide

>>

La lecture et l’écrite dans un tube se fait à l’aide des fonctions read() et write() :

<<

SYNOPSIS

#include <unistd.h>

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

DESCRIPTION

read() lit jusqu’à count octets depuis le descripteur de fichier fd dans le tampon pointé par buf.

Si count vaut zéro, read() renvoie zéro et n’a pas d’autres effets. Si count est supérieur à SSIZE_MAX, le résultat est indéfini.

VALEUR RENVOYÉE

read() renvoie -1 s’il échoue, auquel cas errno contient le code d’erreur, et la position de la tête de lecture est indéfinie. Sinon, read() renvoie le nombre d’octets lus (0 en fin de fichier), et avance la tête de lecture de ce nombre. Le fait que le nombre renvoyé soit plus petit que le nombre demandé n’est pas une erreur.

Ceci se produit à la fin du fichier, ou si on lit depuis un tube ou un terminal, ou encore si read() a été interrompu par un signal.

ERREURS

EAGAIN : On utilise une lecture non bloquante (attribut O_NONBLOCK du descripteur de fichier) et aucune donnée n’était disponible.

EBADF : fd n’est pas un descripteur de fichier valide ou n’est pas ouvert en lecture.

EFAULT : buf pointe en dehors de l’espace d’adressage accessible.

EINTR : read() a été interrompu par un signal avant d’avoir eu le temps de lire quoi que ce soit.

EINVAL : Le descripteur fd correspond à un objet sur lequel il est impossible de lire. Ou bien le fichier a été ouvert avec le drapeau O_DIRECT, et l’adresse de buf, la valeur de count ou la position actuelle de la tête de lecture ne sont pas alignées

correctement.

EIO : Erreur d’entrée-sortie. Ceci arrive si un processus est dans un groupe en arrière-plan et tente de lire depuis le terminal. Il reçoit un signal SIGTTIN mais il l’ignore ou le bloque. Ceci se produit également si une erreur d’entrée-sortie bas-niveau s’est produite pendant la lecture d’un disque ou d’une bande.

EISDIR : fd est un répertoire.

D’autres erreurs peuvent se produire, suivant le type d’objet

associé à fd. POSIX permet à un read() interrompu par un signal de renvoyer soit le nombre d’octets lus à ce point, soit -1, et de placer errno à EINTR.

--- SYNOPSIS

#include <unistd.h>

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

DESCRIPTION

write() écrit jusqu’à count octets dans le fichier associé au descripteur fd depuis le tampon pointé par buf. La norme POSIX réclame qu’une lecture avec read() effectuée après le retour d’une écriture avec write(), renvoie les nouvelles données. Notez que tous les systèmes de fichiers ne sont pas compatibles avec POSIX.

VALEUR RENVOYÉE

write() renvoie le nombre d’octets écrits (0 signifiant aucune écriture), ou -1 s’il échoue, auquel cas errno contient le code d’erreur. Si count vaut zéro, et si le

descripteur est associé à un fichier normal, 0 sera renvoyé sans effets de bord. Pour un fichier spécial, les résultats ne sont pas portables.

ERREURS

EAGAIN : L’écriture est non-bloquante (attribut

O_NONBLOCK du descripteur), et l’opération devrait bloquer.

EBADF : fd n’est pas un descripteur de fichier valide, ou n’est pas ouvert en écriture.

EFAULT : buf pointe en dehors de l’espace d’adressage accessible.

EFBIG : Tentative d’écrire sur un fichier dont la taille dépasse un maximum dépendant de l’implémentation ou du processus, ou d’écrire à une position qui

dépasse le maximum autorisé.

EINTR : L’appel système a été interrompu par un signal avant d’avoir pu écrire quoi que ce soit.

EINVAL : fd correspond à un objet sur lequel il est

impossible d’écrire. Ou bien le fichier a été ouvert avec l’attribut O_DIRECT, et soit l’adresse de buf, soit la valeur de count, soit la position actuelle dans le fichier ne sont pas alignées correctement.

EIO : Une erreur d’entrée-sortie bas niveau s’est produite durant la modification de l’inoeud.

ENOSPC : Le périphérique correspondant à fd n’a plus de place disponible.

EPIPE : fd est connecté à un tube (pipe) ou une socket dont l’autre extrémité est fermée. Quand ceci se produit, le processus écrivain reçoit un

signal SIGPIPE. Ainsi la valeur de retour de write n’est vue que si le programme intercepte, bloque ou ignore ce signal.

D’autres erreurs peuvent se produire suivant le type d’objet associé à fd.

>>

Exemple d’utilisation d’un tube anonyme :

<<

#include <sys/wait.h>

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <string.h>

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

int pfd[2];

pid_t cpid;

char buf;

if (argc != 2) {

fprintf(stderr, "Vous devez fournir un paramètre\n");

exit(-1) ; }

if (pipe(pfd) == -1) {

fprintf(stderr, "Erreur avec pipe()\n");

exit(-1);

}

cpid = fork();

if (cpid == -1) {

fprintf(stderr, "Erreur avec fork()\n");

exit(-1);

}

if (cpid == 0)

{ /* Le fils lit dans le tube */

/* Fermeture du descripteur en écriture inutilisé : */

close(pfd[1]);

while (read(pfd[0], &buf, 1) > 0) write(STDOUT_FILENO, &buf, 1);

write(STDOUT_FILENO, "\n", 1);

close(pfd[0]);

exit(0);

} else

{ /* Le père écrit argv[1] dans le tube */

/* Fermeture du descripteur en lecture inutilisé */

close(pfd[0]);

write(pfd[1], argv[1], strlen(argv[1]));

close(pfd[1]); /* Le lecteur verra EOF */

wait(NULL); /* Attente du fils */

exit(EXIT_SUCCESS);

}

return(0);

}

>>

Les pipes anonymes sont utilisé au niveau de l’interpréteur de commandes (le shell) avec la syntaxe « | » (barre verticale, appelée « pipe », faite avec AltGr+6 sur un clavier AZERTY).

Pour comprendre le mécanisme du pipe du shell Unix/Linux, il faut savoir que chaque programme lancé depuis le shell possède trois descripteurs ouverts par défaut :

stdin : c’est le descripteur de fichier d’entrée standard. Sauf action explicite, il s’agit du clavier. Dans ce cas, une fin de fichier peut être envoyée à l’aide de la combinaison de touches CTRL+Z (code ASCII 26) ;

stdout : c’est le descripteur de fichier de sortie standard. Sauf action explicite, écrire sur ce descripteur envoie le texte à l’écran. Par exemple, c’est sur ce descripteur que sont envoyées les chaînes de caractères passées à la fonction printf() ;

stderr : c’est le descripteur de fichier où sont envoyés les messages d’erreur (par exemple, ceux affichés par perror()).

Par convention, stdin est le premier fichier ouvert. Il a donc comme numéro de descripteur 0. Ensuite, stdout vaut 1, et stderr vaut 2. A noter que pour éviter tout problème si un jour cette convention venait à changer, et pour écrire du code portable, mieux vaut plutôt utiliser les constantes STDIN_FILENO, STDOUT_FILENO, et STDERR_FILENO.

Sous shell, rediriger stdin se fait avec le symbole « < ». Rediriger stdout se fait avec le symbole « > ». Rediriger stderr se fait avec la suite de caractères « 2>&1 » (sans espace, sinon, le symbole « & » sera interprété autrement par le shell).

Exemple : la commande shell « wc –l » (sans autre paramètre) compte le nombre de lignes dans stdin. La commande « ls » liste les fichiers présents dans le répertoire courant. Ainsi, la commande :

ls | wc -l

va rediriger stdout de ls pour et fournir ce flux en entrée à stdin de wc. Le résultat indiquera le nombre de fichiers contenus dans le répertoire courant.

Si nous voulions simuler ce même mécanisme dans un de nos programmes, il est nécessaire d’utiliser la fonction dup(), qui duplique un descripteur :

<<

SYNOPSIS

#include <unistd.h>

int dup(int oldfd);

DESCRIPTION

dup() crée une copie du descripteur de fichier oldfd.

Après un appel réussi à dup(), l’ancien et le nouveau descripteurs peuvent être utilisés de manière

interchangeable. Ils référencent la même description de fichier ouvert (voir open(2)) et ainsi partagent les pointeurs de position et les drapeaux. Par exemple, si le pointeur de position est modifié en utilisant lseek(2) sur l’un des descripteurs, la position est également changée pour l’autre.

Les deux descripteurs ne partagent toutefois pas l’attribut close_on_exec. L’attribut close_on_exec (FD_CLOEXEC ; voir fcntl(2)) de la copie est désactivé, ce qui signifie qu’il ne sera pas fermé lors d’un exec().

dup() utilise le plus petit numéro inutilisé pour le nouveau descripteur.

VALEUR RENVOYÉE

dup()renvoit le nouveau descripteur, ou -1 s’il échoue, auquel cas errno contient le code d’erreur.

ERREURS

EBADF : oldfd n’est pas un descripteur valide.

EMFILE : Le processus dispose déjà du nombre maximum de descripteurs de fichiers autorisés simultanément, et tente d’en ouvrir un nouveau.

>>

Ce qui est important, c’est que dup() utilise le plus petit numéro disponible. Ainsi, si nous fermons le fichier d’entrée standard stdin (avec un « close(STDIN_FILENO) »), puis un

« dup(descripteur_de_pipe_en_lecture) », le numéro de descripteur retrourné par dup() a toutes les chances d’être 0 (le numéro de descripteur de stdin).

Un bon exercice : selon ce principe, écrire un programme C qui ouvre un pipe anonyme, puis lance un fils avec fork(). Le père ferme stdout, puis lui ré-attribue grâce à la fonction dump() le descripteur en écriture du pipe, et lance (avec la fonction execlp()) une commande « ls ». De son coté, le fils ferme stdin, puis attribue (à l’aide de dump()) le descripteur en écriture du pipe à stdin, avant de lancer la commande « wc –l » avec la fonction execlp().