SYSTÈME D’EXPLOITATION I SIF-1015
Contenu du cours 6 Communication par tubes Tube nommé (FIFO) IPC (Inter Process Communication) Files de messages Mémoire Partagée Les signaux LECTURES: Chapitre 3 (OSC) Chapitres 10, 12, 13 (Mitchell) Chapitres 5, 10 et 11 (Card) Annexe C (Mitchell)
Communication par tubes Les tubes représentent un mécanisme de communication entre processus. La transmission des données entre processus s’effectue à travers un canal de communication: les données écrites à une extrémité sont lues à l’autre extrémité
Communication par tubes Le premier type de tubes est le tube anonyme, il est créé par un processus et la transmission des descripteurs associés ne se fait que par héritage vers ses descendants. Ce mécanisme est contraignant puisqu’il ne permet la commu-nication qu’entre processus ayant un ancêtre commun qui lui est le créateur du tube Les tubes nommés (FIFO) permettent d’éliminer cette contrainte puisqu’ils sont traités comme des fichiers en ce qui concerne les opérations d’ouverture, de fermeture, de lecture et d’écriture. Les FIFO ont donc une entrée dans le système de fichier. Un fichier de type FIFO peut être identifié par l’attribut p affiché par la commande shell ls -al
Communication par tubes Appels système Tubes anonymes: Créer par un appel système pipe() int pipe(int filedes[2]); filedes[0]: descripteur de lecture du tube filedes[1]: descripteur d’écriture du tube
Communication par tubes Exemple d’utilisation des pipes (1 processus) exemple pipe1.c dans le répertoire Chap12ProgLinux sur mil08 Exécution: > ./pipe1 Wrote 3 bytes Read 3 bytes : 123
Communication par tubes Exemple d’utilisation des pipes (2 processus) exemple pipe2.c dans le répertoire Chap12ProgLinux sur mil08
Communication par tubes Exemple d’utilisation des pipes (2 processus, utilisation de execl()) exemple pipe3.c dans le répertoire Chap12ProgLinux sur mil08
Communication par tubes Exemple d’utilisation des pipes (2 processus, utilisation de execl()) exemple pipe4.c dans le répertoire Chap12ProgLinux sur mil08 Exécution: > ./pipe3 980 - wrote 3 bytes 981 - read 3 bytes : 123
Communication par tubes Appels système FIFO: Peut être créée par la fonction libc mkfifo(). #include <sys/stat.h> int mkfifo(const char *path, mode_t mode); Cette fonction fait en réalité appel à l’appel système mknod() mknod(const char *path, mode_t mode, dev_t dev); Par exemple: mknod(path, mode | S_IFIFO, 0); Comme pour les fichiers, les FIFO permettent les I/O bloquantes (par défauts) ou non bloquantes (en spécifiant l’option O_NODELAY ou O_NONBLOCK lors de l’ouverture avec open())
Communication par tubes Utilisation des FIFO (Création d’un fichier FIFO, exemple fifo1.c dans le répertoire Chap12ProgLinux sur mil08)
Communication par tubes Utilisation des FIFO (Création d’un fichier FIFO: Entrée dans le répertoire /tmp)
Communication par tubes Utilisation des FIFO (Création d’un fichier FIFO: Entrée dans le répertoire /tmp)
Communication par tubes Utilisation des FIFO (Création d’un fichier FIFO) Fonctionnement: La fonction mkfifo() permet de créer un fichier spécial. Même si le mode d’ouverture de ce fichier est de 0777, cette valeur est modifiée par la valeur du masque utilisateur (variable système avec comme valeur typique: 0022) et ce comme dans le cas des fichiers normaux. Le fichier spécial (FIFO) est donc ouvert en mode 755 (RWX pour le propriétaire du processus, RX pour le groupe et les autres). Dans un programme si nous voulons modifier le masque utilisateur, nous utilisons l’appel système: #include <sys/stat.h> int umask(int mask) // nouvelle_permission = ancienne_permission & (~mask) // mask est le nouveau masque // umask() retourne la valeur de l’ancien masque
Communication par tubes Utilisation des FIFO (Création d’un fichier FIFO) Fonctionnement: Pour effacer un fichier spécial nous pouvons utiliser l’appel système: #include <unistd.h> int unlink( const char *pathname); // unlink() supprime le fichier dont le nom est pointé par pathname // unlink() retourne la valeur 0 si la suppression est réussi et –1 sinon
Communication par tubes Utilisation des FIFO (Ouverture d’un fichier FIFO) Voir exemple fifo2.c
Communication par tubes Utilisation des FIFO (Ouverture d’un fichier FIFO) Exécution: >./fifo2 O_RDONLY & [1] 152 Process 152 opening FIFO >./fifo2 O_WRONLY Process 153 opening FIFO Process 152 result 3 Process 153 result 3 Process 152 finished Process 153 finished La fonction access() vérifie si l’utilisateur a les permissions d’accès à une FIFO Permet aussi de vérifier si une FIFO existe
Communication par tubes Utilisation des FIFO (I/O dans un fichier FIFO: Écriture) Voir exemple fifo3.c
Communication par tubes Utilisation des FIFO (I/O dans un fichier FIFO: Écriture)
Communication par tubes Utilisation des FIFO (I/O dans un fichier FIFO: Lecture) Voir exemple fifo4.c Exécution: >./fifo3 & [1] 375 Process 375 opening FIFO O_WRONLY >./fifo4 Process 377 opening FIFO O_RDONLY Process 375 result 3 Process 377 result 3 Process 375 finished Process 377 finished, 10485760 bytes read
Communication par tubes Utilisation des FIFO (Serveur) Voir exemple server.c, client.h
Communication par tubes Utilisation des FIFO (Client) Voir exemple client.c
Communication par tubes La taille d’un tube est limitée à 4Ko (valeur de la constante PIPE_BUF dans le fichier <linux/limits.h> La création d’une FIFO correspond à la création d’un fichier et est détaillée dans le fichier source fs/fifo.c La lecture et l’écriture sont détaillées dans le fichier source fs/pipe.c Au niveau de l’entrée dans le système de fichier (i-nœud). L’i-nœud (inode) qui est décrit avec plus de détails dans le section sur les systèmes de fichiers LINUX est une structure décrivant les fichiers Pour un tube, un des champs de la structure i-nœud, i_pipe est mis à 1 pour identifier que le i-nœud correspond à un tube
Communication par tubes Le champ u du i-nœud est constitué de la structure pipe_inode_info décrite dans le fichier <linux/pipe_fs_i.h>
IPC (Inter Process Communication) Les IPC permettent d’échanger, de partager des données mais également de synchroniser des processus entre eux Les IPC sont constitués de trois mécanismes: Files de messages: Similaire à une boîte aux lettres. Une application peut déposer (si elle a les permissions nécessaires) un message (un nombre, une chaîne de caractères, ou le contenu d’une structure), d’autres applications peuvent lire ce message Mémoire partagée: Permet de mettre en commun une zone de mémoire entre plusieurs applications. Normalement, lorsqu’une zone de mémoire est allouée (ex: malloc()), elle est locale au processus donc aucun autre processus ne peut y accéder. Plusieurs processus peuvent par contre, accéder autant en lecture qu’en écriture à une zone de mémoire partagée
IPC (Inter Process Communication) Les IPC sont constitués de trois mécanismes: Sémaphores: Permet la synchronisation des processus. Permet alors à plusieurs processus qui fonctionnent en même temps d’accéder aux mêmes données A la différence des autres modes de communication (pipes) les IPC n’utilisent pas le système de fichiers. Un IPC n’est donc pas utilisé comme un fichier donc les appels open(), read() et write() deviennent inutilisables dans ce contexte. La seule manière de manipuler un IPC est de connaître sa clef d’identification. Cette clef d’identification permet d’avoir accès à la zone de mémoire où sont stockées les IPC.
IPC (Inter Process Communication) La gestion des clefs Pour créer ou accéder à un IPC il faut posséder un identificateur (clef) Cette clef est un nombre qui identifie l’IPC de façon unique au niveau du système Génération d’une clef: #include <sys/types.h> #include <sys/ipc.h> key_t ftok(char *pathname, char proj); pathname: Un nom de fichier quelconque proj: Facteur permettant de générer plusieurs clefs différentes pour un même fichier ftok() retourne un identificateur unique de l’IPC qui doit être utilisé pour gérer l’IPC
IPC (Inter Process Communication) La gestion des clefs ftok() retourne un identificateur unique de l’IPC qui doit être utilisé pour gérer l’IPC Clef est calculée par: clef = (st_ino & 0xFFFF) | ((st_dev & 0xFF) << 16) | (proj << 24) La clef découle d’une combinaison entre le numéro de l’i-nœud du fichier, le numéro du périphérique sur lequel se trouve le fichier et le paramètre proj, ce qui permet de générer une clef unique (voir exemple CreerClef.c p 383 Card)
IPC (Inter Process Communication) La gestion des clefs Chaque IPC possède une structure ipc_perm qui contient des informations relatives aux permissions
IPC (Inter Process Communication) Les appels système par catégorie: Création: msget() , semget() , shmget() Contrôle: msgctl() , semctl() , shmctl() Communication: msgsnd() , msgrcv() , semop() , shmop() Constantes importantes: IPC_PRIVATE: Nouvelle file de messages créée strictement pour un processus et ses descendants IPC_CREAT: Création d’un IPC si la clef n’est pas déjà utilisée IPC_EXCL: Création d’un objet si il n’existe pas IPC_NOWAIT: Pas d’attente (msgsnd() , msgrcv())
IPC (Inter Process Communication) Les files de messages Un processus dépose un ou plusieurs messages dans une boîte aux lettres. Un ou plusieurs autres processus peut alors lire les messages selon leur ordre d’arrivée, selon le type de messages désiré La structure msqid_ds (définit dans <sys/msg.h>) correspond à un objet de la file de messages. Cette structure permet la manipulation des objets créés.
IPC (Inter Process Communication) Les files de messages La structure msg stocke un message et son type:
IPC (Inter Process Communication) Les files de messages Représentation interne des files de messages
IPC (Inter Process Communication) Création et recherche des files de messages L’appel système msgget() permet: La création d’une nouvelle file de messages La recherche d’une file de messages existante (pouvant avoir été créée par une autre application) grâce à sa clef #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgget(key_t clef, int option); clef: clef ( découlant de ftok()) de la file de messages qui existe déjà ou celle que nous voulons créer Si la valeur IPC_PRIVATE est passée comme valeur de clef, une file est alors créée
IPC (Inter Process Communication) Si la valeur de clef est différente, deux possibilités peuvent survenir: Si la clef n’est pas déjà utilisée par une autre file de messages, il faut que le champ option soit affecté à IPC_CREAT. La file sera alors créée avec la clef passée en argument. Certains droits d’accès peuvent aussi être fixés par les options La clef est utilisée par une autre file de messages, il faut alors que IPC_CREAT ou IPC_EXCL aient été passés en paramètre. Par la suite, la file de messages peut être accédée en lecture ou en écriture si les permissions le permettent
IPC (Inter Process Communication) Écriture de messages L’appel msgsnd() permet de déposer un message dans une file de messages #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgsnd(int msqid, struct msgbuf *msgp, int msgtaille, int msgopt); msgqid: Identificateur de file de messages découlant de msgget() msgp: Pointeur sur les données constituant le message à déposer dans le file de messages msgtaille: Taille de l’objet déposé dans la file de messages msgopt: Si l’option est IPC_NOWAIT alors msgsnd() sera non bloquant
IPC (Inter Process Communication) Lecture de messages L’appel msgrcv() permet de lire un message dans une file de messages #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgrcv(int msqid, struct msgbuf *msgp, int msgsz, long msgtyp, int msgflg); msgqid: Identificateur de file de message découlant de msgget() msgp: Pointeur sur les données du message récupéré de la file de messages msgsz: Taille de l’objet retiré de la file de messages msgtyp: Type du message à lire msgflg: Options sur la lecture du message
IPC (Inter Process Communication) Lecture de messages Types de messages Si msgtyp == 0, lecture en mode FIFO (plus vieux message en premier) Si msgtyp est négatif, le premier message de la file avec un type <= |msgtyp| est extrait Si msgtyp est positif, le premier message de la file avec un type == msgtyp est extrait Options IPC_NOWAIT: cette option permet d’éviter l’attente active. Si une file est vide lors d’une lecture l’erreur ENOMSG est retournée. Si cette option n’est pas activée, l’appel est suspendu jusqu’à ce qu’un message du type recherché arrive dans la file de messages
IPC (Inter Process Communication) Utilisation des files de messages dans un contexte d’échange de fichiers (Voir CopieServeur.c et CopieClient.c, p. 391 Card)
IPC (Inter Process Communication) Utilisation des files de messages dans un contexte d’échange de caractères (Programme de réception)
IPC (Inter Process Communication) Utilisation des files de messages dans un contexte d’échange de caractères (Programme de réception)
IPC (Inter Process Communication) Utilisation des files de messages dans un contexte d’échange de caractères (Programme de transmission)
IPC (Inter Process Communication) Utilisation des files de messages dans un contexte d’échange de caractères (Programme de transmission)
IPC (Inter Process Communication) Utilisation des files de messages dans un contexte d’échange de caractères (Programmes de transmission et réception) Exécution: >./msg2 Enter some text: Bonjour les amis Enter some text: du cours SIF-1015 ! Enter some text: Comment allez-vous ce soir ? Enter some text: end >./msg1 You wrote: Bonjour les amis You wrote: du cours SIF-1015 ! You wrote: Comment allez-vous ce soir ? You wrote: end
IPC (Inter Process Communication) Mémoires partagées Dans un programme standard, une zone de mémoire allouée est propre au processus qui l’utilise. Aucun autre processus ne peut y accéder. Le principe de la mémoire partagée est de permettre à des processus de partager une partie de leur espace d’adressage Par contre, l’utilisation de zones de mémoire partagées requiert la mise en place de mécanismes de synchronisation d’accès La solution pour résoudre ce problème est d’utiliser les sémaphores
IPC (Inter Process Communication) Mémoires partagées
IPC (Inter Process Communication) Mémoires partagées La structure shmid_ds (définit dans <sys/shm.h>) correspond à un objet de la table de mémoire partagées
IPC (Inter Process Communication) Mémoires partagées Représentation interne des mémoires partagées
IPC (Inter Process Communication) Création et recherche d’une zone de mémoire partagée L’appel système shmget() permet: La création d’une nouvelle zone de mémoire partagée La recherche d’une zone mémoire partagée existante (pouvant avoir été créée par une autre application) grâce à sa clef #include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t clef, int taille, int option); taille: Représente la taille du segment de mémoire partagée désiré shmget() retourne un indice dans le vecteur shm_segs
IPC (Inter Process Communication) Attachement d’une zone de mémoire partagée L’appel shmat() permet de rattacher une mémoire partagée à un processus. Consiste en fait à attacher un zone mémoire à l’espace d’adressage virtuel du processus appelant #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> int *shmat(int msqid, const void *shmaddr, int option); shmaddr: Permet de spécifier l’adresse de la mémoire partagée. Si ce champ est null (0), le noyau tente de chercher une zone de mémoire libre (à préconiser) shmat() retourne un pointeur sur la mémoire partagée A chaque appel shmat(), les paramètres shm_atime, shm_lpid, shm_nattach de la structure shmid_ds sont mis à jour
IPC (Inter Process Communication) Détachement d’une zone de mémoire partagée L’appel shmdt() permet de détacher une mémoire partagée avec un processus #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> int *shmdt(const void *shmaddr); A chaque appel shmdt(), les paramètres shm_dtime, shm_lpid, shm_nattach de la structure shmid_ds sont mis à jour
IPC (Inter Process Communication) Interaction avec d’autres appels système fork(): Le processus fils hérite des segments de mémoire partagés qui sont attachés à son père Exemple d’utilisation de la mémoire partagée dans un contexte du problème des Écrivains et des Lecteurs (Voir Ecrivain.c et Lecteur.c, p. 410, Card)
Signaux Les signaux sont: des événements logiciels (comme des interruptions logicielles) générées par le noyau ou des processus acheminés du noyau ou d’un processus vers d’autres processus identifiés par des numéros d’identifications entiers contiennent comme information que leur ID et leur occurrence Num. Name Default Description 2 SIGINT Terminate Interrupt from keyboard (cntl-c) 9 SIGKILL Kill program (cannot override or ignore) 11 SIGSEGV Terminate & Dump Segmentation violation 14 SIGALRM Timer signal 17 SIGCHLD Ignore Child stopped or terminated
Envoyer des signaux Commande shell kill de UNIX linux> ./forks 8 Terminating Parent, PID = 6675 Running Child, PID = 6676 linux> ps PID TTY TIME CMD 6585 ttyp9 00:00:00 tcsh 6676 ttyp9 00:00:06 forks 6677 ttyp9 00:00:00 ps linux> /bin/kill –s 9 6676 6678 ttyp9 00:00:00 ps Commande shell kill de UNIX Envoit un signal à un processus Ex: /bin/kill –s 9 pid Envoit un SIGKILL à un processus Fonction kill Envoit un signal à un autre processus kill(pid, signal)
Example de la fonction kill void fork12() { pid_t pid[N]; int i; int child_status; for (i = 0; i < N; i++) if ((pid[i] = fork()) == 0) { /* Child: Infinite Loop */ while(1); } for (i = 0; i < N; i++) { printf("Killing process %d\n", pid[i]); kill(pid[i], SIGINT); pid_t wpid = wait(&child_status); if (WIFEXITED(child_status)) printf("Child %d terminated with exit status %d\n", wpid, WEXITSTATUS(child_status)); else printf("Child %d terminated abnormally\n", wpid); // attendre la terminaison de chaque processus Le kill force la terminaison des processus enfants
Gestion des signaux Chaque type de signaux a un comportement par défaut Généralement: terminate ou ignore Peut être modifié en déclarant une fonction de gestion de signal spécifique signal(sig, handler) Indique que le signal de type sig doit appeler la fonction handler handler retourne au point où l’exception est survenue void int_handler(int sig) { printf("Process %d received signal %d\n", getpid(), sig); exit(0); } void fork13() pid_t pid[N]; int i, child_status; signal(SIGINT, int_handler); . . .
Exemple de gestion de signal Les signaux ne sont pas mis dans une file d’attente Pour chaque type de signal, un seul bit indique si un signal est survenu ou non Donc un seul signal peut être en attente Même si plusieurs processus envoient un même signal int ccount = 0; void child_handler(int sig) { int child_status; pid_t pid = wait(&child_status); ccount--; printf("Received signal %d from process %d\n", sig, pid); } void fork14() pid_t pid[N]; int i, child_status; ccount = N; signal(SIGCHLD, child_handler); for (i = 0; i < N; i++) if ((pid[i] = fork()) == 0) { /* Child: Exit */ exit(0); while (ccount > 0) pause();/* Suspend until signal occurs */
Exemple de gestion de signal (signal1.c)
Comment gérer les signaux multiples Il faut vérifier toute les sources possibles de signaux Typiquement en bouclant sur une fonction wait() void child_handler2(int sig) { int child_status; pid_t pid; while ((pid = wait(&child_status)) > 0) { ccount--; printf("Received signal %d from process %d\n", sig, pid); } void fork15() . . . signal(SIGCHLD, child_handler2);
Exemple de gestion de signal (signal2.c)
Un programme réagissant à des événements externes (ctrl-c) #include <stdlib.h> #include <stdio.h> #include <signal.h> static void handler(int sig) { printf("You think hitting ctrl-c will stop the bomb?\n"); sleep(2); printf("Well..."); fflush(stdout); sleep(1); printf("OK\n"); exit(0); } main() { signal(SIGINT, handler); /* installs ctl-c handler */ while(1) {
Un programme réagissant à des événements internes #include <stdio.h> #include <signal.h> int beeps = 0; /* SIGALRM handler */ void handler(int sig) { printf("BEEP\n"); fflush(stdout); if (++beeps < 5) alarm(1); else { printf("BOOM!\n"); exit(0); } main() { signal(SIGALRM, handler); alarm(1); /* send SIGALRM in 1 second */ while (1) { /* handler returns here */ } bass> a.out BEEP BOOM! bass>