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_SETMASKaffectation du nouveau masque, récupération de la valeur de l'ancien masqueSIG_BLOCKunion de deux ensemblesnouvetancSIG_UNBLOCKsoustractionanc-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_handlerfonction de traitementsa_maskensemble de signaux supplémentaires à bloquer pendant le traitementsa_flagsdiffé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é.