• Aucun résultat trouvé

COURS Communication interprocessus (IPC : les signaux)

N/A
N/A
Protected

Academic year: 2022

Partager "COURS Communication interprocessus (IPC : les signaux)"

Copied!
8
0
0

Texte intégral

(1)

Extrait du référentiel : BTS Systèmes Numériques option A (Informatique et Réseaux) Niveau(x) S6. Systèmes d’exploitation

S6.2 S.E. multitâche professionnelles S4. Développement logiciel

S4.9 Programmation événementielle

Communications interprocessus (IPC) Gestion des événements, signaux et interruptions

2

3

Objectifs du cours :

Ce cours traitera essentiellement les points suivants : - Variables et fichiers communs

- Les signaux

Les processus coopèrent souvent pour traiter un même problème. Ces processus s'exécutent en parallèle sur un même ordinateur (monoprocesseur ou multiprocesseurs) ou bien sur des

ordinateurs différents. Ils doivent alors s'échanger des informations (communication interprocessus).

Il existe plusieurs moyens de communication interprocessus. Nous pouvons citer, entre autres, les variables communes, les fichiers communs, les signaux, les messages et les tubes de

communication.

VARIABLES ET FICHIERS COMMUNS

Les processus partagent un ensemble de données communes qui sont soit en mémoire (variables ou segments de données) soit sur disque (fichiers). Chaque processus peut accéder en lecture ou en écriture à cet ensemble de données appelé espace de données commun. Des problèmes peuvent se poser lorsque plusieurs processus essayent d'accéder en même temps à un espace commun. Si deux processus ou plus lisent ou écrivent des données partagées, le résultat dépend de l'ordonnancement des processus.

C'est ce qu'on appelle des accès concurrents.

Il est nécessaire de contrôler les accès concurrents à des objets communs.

(2)

LES SIGNAUX

Les interruptions logicielles ou signaux sont utilisées par le système d'exploitation pour aviser les processus utilisateurs de l'occurrence d'un événement important. De nombreuses erreurs détectées comme l'exécution d'une instruction non autorisée (une division par 0, par exemple) ou l'emploi d'une adresse non valide, sont converties en signaux qui sont envoyés au processus fautif. Ce mécanisme permet à un processus de réagir à cet événement sans être obligé d'en tester en permanence l'arrivée.

Les processus peuvent indiquer au système ce qui doit se passer à la réception d'un signal. On peut ainsi ignorer le signal, ou bien le prendre en compte, ou encore laisser le système

d'exploitation appliquer le comportement par défaut, qui en général consiste à tuer le processus.

Certains signaux ne peuvent être ni ignorés, ni capturés. Si un processus choisit de prendre en compte les signaux qu'il reçoit, il doit alors spécifier la procédure de gestion de signal. Quand un signal arrive, la procédure associée est exécutée. À la fin de l'exécution de la procédure, le processus s'exécute à partir de l'instruction qui suit celle durant laquelle l'interruption a eu lieu.

En résumé, les signaux sont utilisés pour établir une communication minimale entre processus et faire la gestion des erreurs.

Ainsi, un processus peut :

ignorer le signal ;

appeler une routine de traitement fournie par le noyau. Cette procédure provoque normalement la mort du processus. Dans certains cas, elle provoque la création d'un fichier core, qui contient le contexte du processus avant de recevoir le signal. Ces fichiers peuvent être examinés à l'aide d'un débogueur ;

appeler une procédure spécifique créée par le programmeur (tous les signaux ne permettent pas ce type d'action).

Tous les signaux ont une routine de service, ou une action par défaut. Cette action peut être du type :

 A : terminaison du processus

 B : ignorer le signal

 C : créer un fichier core

 D : stopper le processus

 E : la procédure de service ne peut pas être modifiée

 F : le signal ne peut pas être ignoré

Chaque signal est identifié au moyen d'un numéro entier positif. Le tableau ci-dessous décrit les signaux les plus importants sur Linux et UNIX.

(3)

SERVICES DE SIGNAUX

Dans le cas du système Unix/Linux, un processus utilisateur peut également envoyer un signal à un autre processus. Les deux processus appartiennent au même propriétaire, ou bien le

processus émetteur du signal est le super-utilisateur. Les services Posix de gestion des signaux utilisent la bibliothèque <signal.h>.

L'envoi d'un signal peut se faire avec l'appel système kill() :

#include <sys/types.h>

#include <signal.h>

int kill(pid t pid, int signal);

kill() envoie le signal « numéro signal » à un processus identifié par son pid. En cas d'erreur il retourne -1, et 0 autrement.

Exemple 1 : Le programme ci-dessous montre l'utilisation du masquage des signaux.

#include <stdio.h>

#include <signal.h>

#include <unistd.h>

#include <stdlib.h>

void sighandler (int signum);

int main (void) {

char buffer[256] ;

if (signal (SIGTERM,&sighandler)==SIG_ERR) {

printf ("Ne peut pas manipuler le signal \n ");

exit(1);

}

while(1) {

fgets (buffer,sizeof(buffer),stdin);

printf ("Input : %s",buffer);

}

return 0 ; }

void sighandler(int signum) {

printf (" Masquage du signal SIGTERM\n ");

}

signaux1.c

Nous allons réaliser l'exécution de signaux1.c en utilisant deux terminaux, afin de montrer le masquage de signaux et d'essayer de tuer le processus.

(4)

D'abord, exécution sur un terminal :

Affichage console terminal 1 Avec un autre terminal, on peut essayer de tuer le processus 2855.

On dispose pour cela de la commande kill. Si on lui passe en argument seulement le PID du processus, elle enverra le signal SIGTERM à ce processus.

Extrait de l’affichage console terminal 2 On peut également utiliser la commande : kill - SIGKILL 2855 (à tester également : SIGSTOP puis SIGCONT)

Exemple 2 : Le programme ci-dessous illustre l'utilisation des signaux SIGUSR1 et SIGUSR2.

#include <signal.h>

#include <iostream.h>

#include <unistd.h>

#include <wait.h>

static void action (int sig);

int main(void) {

int pid,etat;

// Specification de l’action du signal if (signal(SIGUSR1,action)==SIG_ERR)

std::cout <<"Erreur de traitement du code de l’action"<<std::endl;

if (signal(SIGUSR2,action)==SIG_ERR)

std::cout << "Erreur de traitement du code de l’action" <<std::endl;

if ((pid=fork())==0) {

kill(getppid(),SIGUSR1) ; // Envoyer signal au parent for (;;)

pause();

// Mise en attente d’un signal }

else {

kill(pid,SIGUSR2);

// Envoyer un signal a l’enfant

(5)

std::cout << "Parent : terminaison du fils" <<std::endl;

pause();

kill(pid,SIGTERM);

// Signal de terminaison a l ’enfant wait (&etat);

// attendre la fin de l’enfant

std::cout << " Parent : fils termine " << std::endl;

} }

// Fonction definissant le code de

// l’ action a la reception d’ un signal static void action(int sig)

{

switch(sig) {

case SIGUSR1:

std::cout << "Parent : signal SIGUSR1 recu" << std::endl;

break;

case SIGUSR2 :

std::cout << "Fils : signal SIGUSR2 recu" << std::endl;

break;

default:break;

} }

Signaux2.cpp Parent : terminaison du fils

Fils : signal SIGUSR2 reçu Parent : signal SIGUSR1 reçu Parent : fils terminé

Une sortie console « possible » du fichier : signaux2.cpp

Cependant, si l'on essai à nouveau une autre exécution, la sortie sera probablement tout à fait différente :

Parent : terminaison du fils Parent : fils terminé

Une autre sortie console « possible » du fichier : signaux2.cpp

Pourquoi ce comportement « aléatoire » et comment le résoudre ?

Les appels système sleep() et pause() peuvent être utiles lorsqu'on travaille avec des signaux.

#include <unistd.h>

int pause(void);

int sleep(unsigned int seconds);

Un processus peut se mettre en veille et attendre l'arrivée d'un signal particulier pour exécuter le gestionnaire, ou bien l'émetteur utilise sleep() pour se synchroniser avec le récepteur.

(6)

pause() permet que le processus passe à l'état suspendu. Il va quitter cet état au moment de recevoir un signal. Après avoir reçu le signal, le processus recevra un traitement adéquat associé au signal et continuera son exécution à l'instruction qui suit pause(). Il retourne toujours la

valeur -1.

Cet appel fournit une alternative à l'attente active. C'est-à-dire qu'au lieu d'exécuter une boucle inutile en attente d'un signal (gaspillage du CPU), le processus bloquera jusqu'à ce qu'un signal lui soit transmis.

Exemple 3 : Le programme ci-dessous effectue un « ping-pong » des signaux entre un processus et son fils. Le signal SIGUSR1 est envoyé par le fils au père et SIGUSR2 est envoyé du père au fils. Le premier signal est envoyé par le père.

#include <stdio.h>

#include <sys/wait.h>

#include <sys/types.h>

#include <unistd.h>

#include <signal.h>

#include <stdlib.h>

void handlerSignal();

void sendSignal (int pid,int sig);

int Proc1,cptr,limite;

main (int argc , char **argv) {

if (argv[1]==0) {

printf ("format : ping n\n");

exit (1);

}

limite=atoi(argv[1]);

cptr=0;

Proc1=fork();

switch (Proc1) {

case 0 :

signal(SIGUSR2,handlerSignal);

printf(" Fils => gestionnaire install,PID=%d\n",getpid());

pause();

while (cptr<limite-1) {

cptr++;

sendSignal(getppid(),SIGUSR1);

pause();

}

cptr++;

sendSignal(getppid(),SIGUSR1);

exit(0);

break;

default:

signal(SIGUSR1,handlerSignal);

printf(" Pere => gestionnaire installe,PID=%d\n",getpid());

cptr++;

(7)

sendSignal(Proc1,SIGUSR2);

pause();

while (cptr<limite) {

cptr++;

sendSignal(Proc1,SIGUSR2);

pause();

} break;

} }

void handlerSignal(int sig) {

printf( "\ t\t[%d] gestionnaire %d => signal capte\n",getpid(),sig);

fflush(stdout);

}

void sendSignal (int pid,int sig) {

sleep(1);

if (kill(pid,sig)==-1) {

printf("ERREUR kill PID=%d\n",pid);

fflush (stdout);

exit(1);

}

printf("#%d [%d] signal %d envoye a %d\n",cptr,getpid(),sig,pid);

}

Fichier : ping.c

Père => signal envoyé, PID=4825 Fils => signal envoyé, PID=4826

#1 [4825] signal 12 envoyé à 4826

[4826] gestionnaire 12 => signal capté

#1 [4826] signal 10 envoyé à 4825

[4825] gestionnaire 10 => signal capté

#2 [4825] signal 12 envoyé à 4826

[4826] gestionnaire 12 => signal capté

#2 [4826] signal 10 envoyé à 4825

[4825] gestionnaire 10 => signal capté

Exemple de sortie console du fichier : ping.c

À RETENIR

PROGRAMMATION SYSTÈME : LES SIGNAUX

Un mécanisme de synchronisation de processus : les signaux (ou les évènements) permettent de communiquer entre processus : réveiller, arrêter ou avertir un processus d'un événement.

Un signal peut être imaginé comme une sorte d'impulsion qui oblige le processus cible à prendre immédiatement une mesure spécifique.

(8)

Le rôle des signaux est de permettre aux processus de communiquer. Ils peuvent par exemple : - réveiller un processus ;

- arrêter un processus ;

- avertir un processus d'un événement.

À la réception d'un signal, le processus peut : - l’ignorer ;

- déclencher l'action prévue par défaut sur le système ;

- déclencher un traitement spécial appelé « handler » (gestionnaire du signal).

Les différents signaux et les prototypes des fonctions qui les manipulent sont définis dans le fichier

<signal.h>.

L'attente d'un signal démontre tout l'intérêt du multitâche. En effet, au lieu de consommer des ressources CPU inutilement en testant la présence d'un événement dans une boucle while, on place le processus en sommeil et le processeur est mis alors à la disposition d'autres tâches.

La primitive pause() endort le processus appelant et le place dans l'attente d'un signal

d'interruption (quelconque). Le problème qui se pose est d'encadrer correctement l'appel pause. Il faudra aussi regarder du côté des appels sigsuspend() et sigwait().

Trois autres fonctions permettent d'endormir un processus : sleep, usleep, nanosleep.

Un processus peut bloquer à volonté un ensemble de signaux, à l'exception de SIGKILL et de SIGSTOP.

Cette opération se fait par l'appel système sigprocmask(). Cette fonction permet aussi bien de bloquer ou de débloquer des signaux.

L'utilité principale d'un blocage des signaux est la protection des portions critiques de code. Il est à noter que la délivrance des signaux s'effectue avant le retour de la fonction sigprocmask.

Un processus peut consulter la liste des signaux bloqués sans en demander la délivrance immédiate avec sigpending().

LA COMMUNICATION INTERPROCESSUS Multi-tâches :

Les processus ne sont pas isolés dans l'ordinateur puisqu'ils concourent tous à l'exploitation de travaux de l'utilisateur et puisqu'ils sont en compétition pour le partage de ressources qui sont limitées. Ceci implique un besoin des processus de communiquer.

Multi-programmation :

L'idée (paralléliser des traitements) est de substituer un seul programme purement séquentiel en plusieurs pièces (processus ou tâches) pouvant logiquement se dérouler simultanément et en partageant des données. Ceci implique un besoin des processus de communiquer.

Références

Documents relatifs

La priorité peut être ajustée avec nice dans l'intervalle 20 (le plus prioritaire) à 19 (le moins prioritaire) A noter tout de fois que seul le super utilisateur (root) peut

On va lancer la commande sleep en arrière plan, puis on va la visualiser afin de la repasser en premier plan puis la remettre en arrière plan... Exemple : Ci dessous, on voit

les outils permettant aux processus de s'échanger des données les outils permettant de synchroniser les processus, notamment pour gérer le principe de section critique.. les

• Round Robin (tourniquet) : lorsqu'une tâche sous ordonnancement « Round Robin » est activée, on lui accorde un délai au bout duquel elle sera préemptée pour laisser passer

Les tubes sans nom sont, en général, utilisés pour la communication entre un processus père et ses processus fils, avec un d'entre eux qui écrit sur le tube, appelé processus

– État des signaux (ignoré, action par défaut, trappé) – Masque des signaux bloqués.  Héritage de l'état et du masque lors d'un

 Arborescence de processus avec un rapport père - fils entre processus créateur et processus crée. Processus pid 15 Processus

• Tout processus connaissant la référence d’un tube nommé peut l’ouvrir avec la primitive « open ».. • Les fichiers correspondants à des tubes sont identifiés