SE 4 Redirections Ipc
SE 4 Redirections Ipc
SE 4 Redirections Ipc
Communication inter-processus
Thomas Ropars
thomas.ropars@univ-grenoble-alpes.fr
http://tropars.github.io/
2024
1
Références
2
Contenu de ce cours
3
Agenda
Communication inter-processus
Les tubes
Les signaux
4
Rappel du cours précédent
Descripteur de fichiers
• Chaque fichier ouvert est représenté par un descripteur de
fichier
• Tout processus a 3 descripteurs de fichiers ouverts par défaut:
▶ 0: L’entrée standard
▶ 1: La sortie standard
▶ 2: La sortie d’erreur
5
Représentation des fichiers ouverts
6
Commentaires sur la figure précédente
Des structures de données partagées entre tous les processus:
Open file table
• Chaque entrée représente une connexion à un fichier
▶ La position dans le fichier est associé à la connexion
• Plusieurs processus peuvent partager la même connexion
▶ Exemple: Héritage d’un descripteur de fichier avec fork()
• Un fichier peut être ouvert plusieurs fois
▶ Plusieurs connexions vers le même fichier
Vnode table
• Chaque entrée représente un fichier ouvert
• Contient des informations sur le fichier:
▶ Droits d’accès associés
▶ Taille
▶ Position sur le support physique
▶ Etc.
7
Ce qu’il se passe lors d’un appel à fork()
Avant
8
Ce qu’il se passe lors d’un appel à fork()
Après
10
Dup2()
Appel de dup2(4,1)
• L’écriture sur la sortie standard est maintenant redirigée vers
le fichier correspondant au descripteur de fichiers 4
11
Étapes pour mettre en oeuvre une redirection
ls > file.txt
• 1) Appel à fork() pour créer un nouveau processus
• 2) Dans le fils, appel à open() pour ouvrir le fichier vers
lequel rediriger la sortie standard
12
Étapes pour mettre en oeuvre une redirection
ls > file.txt
• 3) Appel à dup2() pour mettre en place la redirection
• 4) Appel à exec() pour charger le programme ls
13
Agenda
Communication inter-processus
Les tubes
Les signaux
14
Communication inter-processus
15
Communication inter-processus
• Les Sockets
▶ Permettent la communication sur le réseau
16
Agenda
Communication inter-processus
Les tubes
Les signaux
17
Introduction
Les principes
• Un tube est un tampon qui sert de moyen de communication
entre un processus producteur et un processus consommateur
▶ Le tampon a une capacité finie
▶ Il est unidirectionel (sous Linux)
• Les données sont transmises dans l’ordre FIFO (First In First
Out)
▶ Le consommateur lit les données dans l’ordre dans lequel elles
ont été produites
18
Manipuler les flots d’entrée-sortie (primitives)
Les tubes et les flots peuvent être manipulés au niveau des appels système.
Créer un tube : la primitive pipe() crée un tube, dont l’entrée et la sortie sont
associées à des descripteurs choisis par le système
int fd[2]; pipe(fd);
Si la primitive réussit, elle crée un tube, renvoie 0, et met à jour le tableau fd :
fd[0] = desc. sortie du tube, fd[1] = desc. entrée du tube.
Si elle échoue, elle renvoie –1
Par exemple, un père peut communiquer avec un fils à travers un tube.
père fils père fils
19
Utilisation
Communication entre un processus et son fils
Étapes principales
1. Appel à pipe(fd) pour créer un tube
▶ fd[1] correspond à l’entrée du tube (accès en écriture)
▶ fd[0] correspond à la sortie du tube (accès en lecture)
2. Création du processus fils avec fork()
▶ Le nouveau processus a une copie identique de la mémoire et
des descripteurs de fichiers
▶ Il a donc aussi accès au tube
3. Fermer les descripteurs de fichier inutiles
▶ Typiquement, un seul processus accède en lecture et un seul
accède un écriture
▶ Sinon, risque de blocage (voir slides suivants)
4. Communiquer en utilisant read() et write()
20
Programmation d’un tube père -> fils
#include "csapp.h"
#define BUFSIZE 10 bufin : "empty" bufin : "empty"
int main(void) {
char bufin[BUFSIZE] = "empty";
bufout : "hello" bufout : "hello"
char bufout[BUFSIZE] = "hello";
int bytesin, bytesout; pid_t childpid; père fils
int fd[2]; fd[0] fd[1]
pipe(fd); fd[1] fd[0]
bytesin = strlen(bufin);
childpid = Fork();
if (childpid != 0) { /* père */ tube
Close(fd[0]); bytesout = write(fd[1], bufout, strlen(bufout)+1);
printf("[%d]: wrote %d bytes\n", getpid(), bytesout); après Fork()
} else { /* fils */
Close(fd[1]); bytesin = read(fd[0], bufin, BUFSIZE);
printf("[%d]: read %d bytes, my bufin is {%s} \n », bufin : "empty" bufin : "hello"
getpid(), bytesin, bufin);
} bufout : "hello" bufout : "hello"
exit(0);
} write read
père fils
fd[1]
fd[0]
<unix>./parentwritepipe
[29196]:wrote 6 bytes
[29197]: read 6 bytes, my bufin is {hello}
<unix> tube
V.Marangozova-Martin L3 SR 23
21
Un producteur-consommateur
man 7 pipe
22
Un producteur-consommateur
man 7 pipe
Sémantique de l’opération de lecture (read())
• Lit le nombre d’octets demandé depuis le tube
• Bloque si le tampon est vide (ou si moins d’octets que
demandé sont disponibles)
▶ L’opération read() ne retourne pas
▶ Jusqu’à ce que des données soient disponibles sur le tube
24
Tubes nommés (FIFOs)
• Un tube ne peut être utilisé qu’entre un processus et ses
descendants (ou entre descendants d’un même processus).
▶ Ses extrémités ne sont désignées que par des descripteurs, qui
ne peuvent se transmettre qu’entre père et fils.
• Pour faire communiquer deux processus quelconques, on
utilise des tubes nommés, ou FIFOs.
Communication inter-processus
Les tubes
Les signaux
25
Signaux
▶ Un signal est un événement asynchrone destiné à un (ou plusieurs)
processus. Un signal peut être émis par un processus ou par le
système d’exploitation.
V.Marangozova-Martin SR L3 INFO 8
27
Fonctionnement des signaux
processus p
V.Marangozova-Martin SR L3 INFO 9
28
Quelques exemples de signaux
V.Marangozova-Martin SR L3 INFO 10
29
États d’un signal
Qu’est-ce qui empêche que le signal soit immédiatement traité dès qu’il est
reçu ?
• Le signal peut être bloqué, ou masqué (c’est à dire retardé) par le
destinataire. Il est délivré dès qu’il est débloqué
• En particulier, un signal est bloqué pendant l’exécution du traitant d’un
signal du même type ; il reste bloqué tant que ce traitant n’est pas
terminé
Point important : il ne peut exister qu’un seul signal pendant d’un type
donné (il n’y a qu’un bit par signal pour indiquer les signaux de ce type qui
sont pendants). S’il arrive un autre signal du même type, il est perdu.
V.Marangozova-Martin SR L3 INFO 11
30
Envoi d'un signal
V.Marangozova-Martin SR L3 INFO 12
31
Redéfinir le traitant associé à un signal
2 appels systèmes possibles
#include <signal.h>
typedef void (∗sighandler t)(int);
32
Exemple 1 : traitement d’une interruption du
clavier
void handler(int sig) { /∗ nouveau traitant ∗/
printf(”signal SIGINT recu !\n”);
exit(0);
}
int main() {
signal(SIGINT, handler); /∗ installe le traitant ∗/
pause () ; /∗ attend un signal∗/
exit(0);
}
int main() {
int reponse;
signal(SIGALRM, handler); /∗ installe le traitant ∗/
printf(”Entrez un nombre avant 5s :”);
alarm(5);
scanf(”%d”, &reponse);
alarm(0); printf(”bien recu !”);
exit (0),
}
34
Exemple 2 : utilisation de la temporisation
Illustration
35
Exemple 3: synchronisation père-fils
Application
• Cas d’un processus qui crée un grand nombre de fils
▶ Ex: Un shell (qui crée un processus pour traiter chaque
commande)
• Ce processus doit prendre en compte leur fin dès que possible
pour éviter une accumulation de processus zombis
▶ Solution: Attente de processus dans le traitant de SIGCHLD
36
Exemple 3: synchronisation père-fils
int main() {
Signal(SIGCHLD, handler); /∗ installe le traitant ∗/
... <creation d’un certain nombre de fils, sur demande> ...
exit (0),
}
37
Gestion des masques de signaux
• Le masque de signaux d’un processus est automatiquement
modifié lors de l’exécution d’un traitant
• Le masque définit les signaux qui sont bloqués (masqués)
pendant l’exécution du traitant
▶ Au moins le signal en cours de traitement (c’est le seul par
défaut)
▶ Les signaux ignorés dépendent des consignes spécifiées au
moment de la définition des traitants
38
Gestion des masques de signaux
int main() {
sigset t s; // structure de donnees opaque permettant de
// designer un ou plusieurs types de signaux
...
Sigemptyset(&s); //s <− ensemble vide
Sigaddset(&s, SIGINT); // ajout de SIGINT dans l’ensemble s
Sigprocmask(SIG BLOCK, &s, NULL);
// a ce stade, le masquage de SIGINT est effectif
// SIG BLOCK: ajout a l’ensemble des signaux masques
...
Sigprocmask(SIG UNBLOCK, &s, NULL);
// a ce stade, SIGINT est a nouveau démasqué
...
}
39
Terminaux, sessions et groupes sous Unix
• Pour bien comprendre le fonctionnement de certains signaux,
il faut avoir une idée des notions de session et de groupes
• En quelques mots:
▶ Une session est associée à un terminal
• Donc au login d’un usager du système au moyen d’un shell.
• Le processus qui exécute ce shell est le leader de la session.
▶ Dans une session, on peut avoir plusieurs groupes de processus
correspondant à divers travaux en cours.
• Il existe au plus un groupe interactif (foreground, ou
premier plan) avec lequel l’usager interagit via le terminal.
• Il peut aussi exister plusieurs groupes d’arrière-plan, qui
s’exécutent en travail de fond (par exemple processus lancés
avec &, appelés jobs).
• Seuls les processus du groupe interactif peuvent lire au
terminal
• les signaux SIGINT (frappe de control-C) et SIGTSTP (frappe
de control-Z) s’adressent au groupe interactif
40
Terminaux, sessions et groupes sous Unix (2)
loop.c
int main() {
printf"processus %d, groupe %d\n", getpid(), getpgrp());
while(1) ;
} <unix> loop & loop & ps <unix>bg %1
[1] loop &
processus 10468, groupe 10468 <unix>fg %2
[1] 10468 loop
processus 10469, groupe 10469 => frappe de control-C
[2] 10469 <unix>ps
PID TTY TIME CMD PID TTY TIME CMD
5691 pts/0 00:00:00 tcsh 5691 pts/0 00:00:00 tcsh
10468 pts/0 00:00:00 loop 10468 pts/0 00:02:53 loop
10469 pts/0 00:00:00 loop 10474 pts/0 00:00:00 ps
10470 pts/0 00:00:00 ps <unix> => frappe de control-C
<unix>fg %1 <unix>ps
loop PID TTY TIME CMD
=>frappe de control-Z 5691 pts/0 00:00:00 tcsh
Suspended 10468 pts/0 00:02:57 loop
<unix>jobs 10474 pts/0 00:00:00 ps
[1] + Suspended loop <unix>
[2] - Running loop
V.Marangozova-Martin SR L3 INFO 20
41
États d’un travail
42
Vie des travaux
pid=4231
pgid=4231
shell
commande
commande & commande &
pid=5802
pgid=5802
V.Marangozova-Martin SR L3 INFO 22
43
Des références en plus
44