Skip to main content

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.

  1. Un signal est reposition à sa valeur par défaut au début de son traitement
  2. 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.
  3. 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 masque
  • SIG_BLOCK union de deux ensembles nouv et anc
  • SIG_UNBLOCK soustraction anc-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 traitement
  • sa_mask ensemble de signaux supplémentaires à bloquer pendant le traitement
  • sa_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é.