Les signaux
Les signaux sont un mécanisme asynchrone de communication inter-processus. Intuitivement, ils sont comparables à des sonneries, les différentes sonneries indiquant des évènements différents. Les signaux sont envoyés à un ou plusieurs processus. Ce signal est en général associé à un évènement. Peu portables entre BSD et ATT, ils deviennent plus commodes à utiliser et portables avec la norme POSIX qui utilise la notion utile de vecteur de signaux et qui fournit un mécanisme de masquage automatique pendant les procédures de traitement (comme BSD). Un signal est envoyé à un processus en utilisant l'appel système
kill(int pid, int signal);
signal
est un numéro compris entre 1 et NSIG
(défini dans <signal.h>
) et
pid
est le numéro du processus. Le processus visé reçoit le signal sous forme
d'un drapeau positionné dans son bloc de contrôle. Le processus est interrompu
et réalise éventuellement un traitement de ce signal. On peut considérer les
signaux comme des interruptions logicielles, ils interrompent le flot normal
d'un processus mais ne sont pas traités de façon synchrone comme les
interruptions matérielles.
Provenance des signaux
Certains signaux peuvent être lancés à partir d'un terminal grâce aux caractères
spéciaux comme intr, quit
dont la frappe est transformée en l'envoi des
signaux SIGINT
et SIGQUIT
. D'autres sont dûes à des causes internes au
processus, par exemple : SIGEGV
qui est envoyé en cas d'erreur d'adressage,
SIGFPE
division par zéro (Floating Point Exception). Enfin certains sont dûes
à des évènements comme la déconnexion de la ligne (le terminal) utilisé : si le
processus leader d'un groupe de processus est déconnecté, il envoie à l'ensemble
des processus de son groupe le signal SIGHUP
.
Gestion interne des signaux
C'est dans le bloc de contrôle (BCP) de chaque processus que l'on trouve la
table de gestion des signaux. Cette table contient pour chaque signal défini sur
la machine, une structure sigvec
suivante :
{
bit pendant;
void(*traitement)(int);
}
en BSD et POSIX, on a un champ supplémentaire : bit masque;
Le drapeau
pendant
indique que le processus a reçu un signal, mais n'a pas encore eu
l'occasion de prendre en compte ce signal.
L'envoi de signaux : la primitive kill
Il y a NSIG signaux sur une machine, déclarés dans le fichier
/usr/include/signal.h
. La valeur de pid
indique le PID
du processus auquel
le signal est envoyé. Le paramètre sig
est interprété comme un signal si sig
est entre 0 et NSIG
, ou comme une demande d'information si sig=0
. Comme un
paramètre erroné sinon. La fonction raise(int signal)
est un raccourci pour
kill(getpid(),signal)
, le processus s'envoie à lui-même un signal.
La gestion simplifiée avec la fonction signal
La fonction signal
permet de spécifier ou de connaître le comportement du
processus à la réception d'un signal donné, il faut donner en paramètre à la
fonction la numéro du signal sig
que l'on veut détourner et la fonction de
traitement action à réaliser à la réception du signal.
Problèmes de la gestion de signaux ATT
Les phénomènes suivants sont décrits comme des problèmes mais la norme POSIX permet d'en conserver certains, mais fournit aussi les moyens de les éviter.
- Un signal est reposition à sa valeur par défaut au début de son traitement
- Certains appels systèmes peuvent être interrompus et dans ce cas la valeur de retour de l'appel système est -1. Il faudrait, pour réaliser correctement ,e modèle d'une interruption logicielle, relancer l'appel système en fin de traitement du signal.
- Si un signal est ignoré par un processus endormi, celui-ci sera réveillé par le système uniquement pour apprendre qu'il ignore le signal et doit donc être endormi de nouveau.
La norme POSIX
La norme POSIX ne définit pas le comportement d'interruption des appels systèmes, il faut le spécifier dans la structure de traitement du signal.
Les ensembles de signaux La norme POSIX introduit les ensembles de signaux :
ces ensembles de signaux permettent de dépasser la contrainte classique qui veut
que le nombre de signaux soit inférieur ou égal au nombre de bits des entiers de
la machine. D'autre part, des fonctions de manipulation de ces ensembles sont
fournies et permettent de définir simplement des masques. Ces ensembles de
signaux sont du type sigset_t
et sont manipulantes grâce aux fonctions
suivantes.
int sigemptyset(sigset_t *ens) //raz
int sigfillset(sigset_t *ens) // ens = {1,2, ..., NSIG}
int sigaddset(sigset_t *ens, int sig) // ens = ens + {sig}
int sigdelset(sigset_t *ens, int sig) // ens = ens - {sig}
Ces fonctions retournent -1 en cas d'échec et 0 sinon.
Le blocage des signaux
La fonction suivante permet de manipuler le masque des signaux du processus
#include <signal.h>
int sigprocmask(int op, const sigset_t *nouv, sigset_t *anc);
L'opération op :
SIG_SETMASK
affectation du nouveau masque, récupération de la valeur de l'ancien masqueSIG_BLOCK
union de deux ensemblesnouv
etanc
SIG_UNBLOCK
soustractionanc-nouv
On peut savoir si un signal est pendant
et donc bloqué
grâce à la fonction
int sigpending(sigset_t *ens);
qui retourne -1 en cas d'échec et 0 sinon et
l'ensemble des signaux pendants est stocké à l'adresse ens
.
sigaction
La structure sigaction
décrit le comportement utilisé pour le traitement d'un
signal
struct sigaction{
void (*sa_handler)();
sigset_t sa_mask;
int sa_flags;
}
sa_handler
fonction de traitementsa_mask
ensemble de signaux supplémentaires à bloquer pendant le traitementsa_flags
différentes options
Le position du comportement de réception d'un signal se fait par la primitive
sigaction
. L'installation d'une fonction de traitement du signal SIGCHLD
peut avoir pour effet d'envoyer un signal au processus, ceci dans le cas où le
processus a des fils zombie, c'est toujours le problème lié à ce signal qui n'a
pas le même comportement que les autres signaux. Un handler positionné par
sigaction
reste jusqu'à ce qu'un autre handler soit positionné, à la
différence des versions ATT où le handler par défaut est repositionné
automatiquement au début du traitement du signal.
#include <signal.h>
int sigaction(int sig,
const struct sigaction *paction,
struct sigaction *paction_precedente);
Cette fonction réalise soit une demande d'information. Si le pointeur paction
est null, on obtient la structure sigaction
courante. Sinon c'est une demande
de modification du comportement.
L'attente d'un signal
En plus de l'appel pause
, on trouve sous POSIX l'appel int sigsuspend(const sigset_t * ens);
qui permet de réaliser de façons atomique
les actions
suivantes :
- l'installation du masque de blocage défini par
ens
- mise en attente de la réception d'un signal non bloqué.