152 lines
5.2 KiB
Markdown
152 lines
5.2 KiB
Markdown
## Cours 4 : les signaux
|
||
|
||
Les interruptions logicielles ou signaux sont utilisées par le système d’exploitation pour aviser les processus utilisateurs de l’' 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 com- portement 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. A 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.
|
||
|
||
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.
|
||
|
||
|
||
### Envoie d'un signal
|
||
|
||
```c
|
||
#include <sys/types.h>
|
||
#include <signal.h>
|
||
int kill(pid_t pid, int signal);
|
||
```
|
||
|
||
Renvoie $-1$ en cas d'erreur, $0$ sinon. Pour ```pid``` on peut avoir :
|
||
- si ```pid```$> 0$, le processus ```pid```
|
||
- si ```pid```$= 0$, le groupe de l'émetteur
|
||
- si ```pid```$= -1$, tous les processus (seul ```root``` peut faire ça)
|
||
- si ```pid```$< -1$, au groupe ```gid``` = valeur absolue (```pid```)
|
||
|
||
|
||
#### Exemple
|
||
|
||
```c
|
||
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <signal.h>
|
||
#include <unistd.h>
|
||
|
||
void sighandler (int signum);
|
||
|
||
|
||
int main(int argc, char *argv[]) {
|
||
|
||
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 EXIT_SUCCESS;
|
||
}
|
||
|
||
void sighandler (int signum) {
|
||
printf("Masquage du signal SIGTERM\n");
|
||
}
|
||
```
|
||
|
||
Avec pour la fonction ```signal```:
|
||
|
||
```c
|
||
#include <signal.h>
|
||
void (*signal(int sig, void (*func)(int)))(int);
|
||
```
|
||
|
||
or in the equivalent but easier to read typedef'd version:
|
||
|
||
```c
|
||
typedef void (*sig_t) (int);
|
||
|
||
sig_t signal(int sig, sig_t func);
|
||
```
|
||
qui permet de modifier le comportement du processus à la réception d'un signal (sauf ```SIGKILL``` et ```SIGSTOP```).
|
||
|
||
**Remarque** par défaut la commande ```kill``` du shell envoie le signal 15 (```SIGTERM```) au processus en paramètre.
|
||
|
||
|
||
### SIGUSR1 et SIGUSR2
|
||
|
||
```c
|
||
#include <signal.h>
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <unistd.h>
|
||
#include <sys/wait.h>
|
||
|
||
#define FOREVER for(;;)
|
||
|
||
static void action (int sig);
|
||
|
||
int main(int argc, char *argv[]) {
|
||
|
||
int i, pid, etat;
|
||
sig_t s1, s2;
|
||
|
||
s1 = signal(SIGUSR1, action);
|
||
s2 = signal(SIGUSR2, action);
|
||
if (( s1 == SIG_ERR) || (s2 == SIG_ERR)) {
|
||
perror("Erreur attachement signal");
|
||
exit(1);
|
||
}
|
||
|
||
if ((pid = fork()) == 0) {
|
||
pause();
|
||
kill(getppid(), SIGUSR1);
|
||
sleep(10);
|
||
}
|
||
else {
|
||
sleep(2);
|
||
kill(pid, SIGUSR2);
|
||
pause();
|
||
printf("Parent : Demande terminaison du fils\n");
|
||
fflush(stdout);
|
||
kill(pid, SIGTERM);
|
||
wait(&etat);
|
||
printf("Parent : fils terminé\n");
|
||
fflush(stdout);
|
||
}
|
||
return EXIT_SUCCESS;
|
||
}
|
||
|
||
static void action(int sig) {
|
||
switch (sig) {
|
||
case SIGUSR1 :
|
||
printf("Parent : signal SIGUSR1 recu\n");
|
||
fflush(stdout);
|
||
break;
|
||
case SIGUSR2 :
|
||
printf("Fils : signal SIGUSR2 recu\n");
|
||
fflush(stdout);
|
||
break;
|
||
default :
|
||
break;
|
||
}
|
||
}
|
||
```
|
||
|
||
On peut utiliser les fonctions ```pause``` et ```sleep``` pour *faire attendre les processus* (attente d'un signal de façon non active). Ces fonctions sont interrompus à la réception d'un signal.
|
||
|
||
#### Exercices
|
||
|
||
1- Ecrire un programme qui cré un fils. Celui-ci modifie son comportement sur ```SIGQUIT``` et ```SIGINT``` en les ignorants (on affiche qu'on ne les traite pas). Le fils s'endort au moins 3 fois (pour traiter le réveil des 3 signaux ```SIGQUIT```, ```SIGINT``` et ```SIGKILL```).
|
||
|
||
2- Ecrire un programme qui crée un processus qui se met à travailler :
|
||
- si pendant le travail il reçoit un message ```SIGUSR2``` alors il se met à sauvegarder ces données (dans un fichier) puis il signale à son père qu'il a terminé. Son père le tue définitivement.
|
||
- si il a le temps de finir son travail, alors il signale à son père qu'il a terminé. Son père lui demande d'engager une sauvegarde des données (dans un fichier). Puis le tue une fois la sauvegarde terminée.
|
||
|
||
3- Etude de cas : est ce que le handler de signaux d'un processus est hérité par son fils?
|
||
|