Outils personnels
Vous êtes ici : Accueil C & C++ Programmation système Les signaux Les signaux

Les signaux

Par Eric Salice - Dernière modification 18/04/2008 22:40
Contributeurs : Benjamin Poulain (Ikipou)

Les signaux sont un mécanisme de communication unidirectionnel entre le noyau et les processus ou entre processus.

L'arrivée d'un signal interrompt le processus destinataire et déclenche une action qui peut être

  • l'arrêt brutal du processus par le système avec création éventuelle d'un fichier contenant l'image mémoire des données du processus (core dump) utilisable à des fins de débugage
  • le passage à l'état stoppé du processus
  • la reprise de l'exécution du processus s'il était à l'état stoppé
  • l'exécution d'une fonction du processus
  • aucune action, on dit alors que le processus ignore le signal.

L'action réalisée dépendra du type du signal et de la configuration de la gestion des signaux mise en place par le processus.

Un signal ne véhicule pratiquement pas d'information. Une des informations qu'il contient est son type. Sous Linux, il existe 64 types de signaux numérotés de 1 à 64 et divisés en deux catégories, les signaux classiques ou standards et les signaux temps réels.

La spécificité des signaux est qu'ils peuvent survenir à n'importe quel moment de l'exécution d'un processus et interrompre celle-ci pour, par exemple, exécuter une fonction attachée au signal. Cette caractéristique facilite la programmation car il ne faut pas prévoir de code pour vérifier à intervalle régulier si un signal n'est pas arrivé. Il faut toutefois être prudent lorsqu'on utilise les signaux car une fonction invoquée par l'utilisation d'un signal peut modifier une variable utilisée par la fonction interrompue par le signal avec les risques d'inconsistance de données que cela peut impliquer. On ne peut pas utiliser les mécanismes de synchronisation vus pour les threads pour prévenir ce genre de problème car il n'y a ici qu'un fil d'exécution concerné.

Les signaux classiques

Les signaux classiques sont numérotés de 1 à 31. Il est conseillé de ne pas utiliser directement les n° de signaux car ceux-ci peuvent varier d'un système à l'autre mais des constantes symboliques disponibles en incluant le fichier d'en-tête <signal.h>. Les signaux classiques font partie de la norme C Ansi qu'a repris SUSv3. Il s'agit pour la majorité d'entre-eux de signaux envoyés par le noyau aux processus dans des circonstances particulières qui seront détaillées ci-après. Toutefois rien n'interdit à un processus d'envoyer un tel type à un autre processus.

Le tableau ci-dessous reprend quelques signaux classiques importants, leur description et l'action par défaut qui leur correspond ((T: terminaison du processus, D: terminaison avec core dump, A: arrêt, R: reprise, I: signal ignoré).

Signal Description Action
SIGTERM Terminaison du processus T
SIGKILL Terminaison irrévocable T
SIGINT Terminaison à partir du clavier (généralement ctrl-c) T
SIGQUIT Terminaison clavier (ctrl-\ en général) D
SIGSTOP Arrêt temporaire du processus (passage à l'état stoppé) A
SIGSTP Arrêt temporaire du processus (passage à l'état stoppé) à partir du clavier (généralement ctrl-z) A
SIGCONT Reprise de l'exécution R
SIGILL Instruction illégale D
SIGSEGV Référence mémoire invalide D
SIGFPE Erreur "mathématique" D
SIGUSR1
SIGUSR2
Disponibles pour le programmeur T
SIGCHLD Fils stoppé ou terminé I
SIGHUP Déconnexion du terminal (sert à recharger le fichier de configuration dans beaucoup de programmes
T

La fonction

#include <signal.h>

void psignal (int sig, const char *s);

Affiche sur la sortie d'erreur standard la chaîne s suivie du caractère ':' suivi d'une espace et d'un texte décrivant le signal de n° sig. Les textes décrivant les signaux sont disponibles dans le tableau de chaînes de caractères sys_siglist[] déclaré dans <signal.h>.

Quand plusieurs signaux classiques d'un même type sont envoyés à un processus sans qu'il puisse les prendre en compte si, par exemple, il est en attente d'un processeur pendant que les signaux lui sont envoyés ou s'il bloque ce type de signaux comme nous verrons comment le faire plus loin, le noyau ne conserve qu'un exemplaire des signaux envoyés et ne délivrera donc qu'un seul signal au processus quand celui-ci sera en mesure de les recevoir.

Les signaux temps-réel

Les signaux temps-réel ont été introduits par la norme Posix. 1b. et repris dans SUSv3. Ils sont numérotés de 32 à 64 mais on utilise plutôt les constantes symboliques SIGRTMIN et SIGRTMAX pour désigner le plus petit n° correspondant à un signal temps-réel et le plus grand. On utilisera donc l'expression SIGRTMIN + n pour désigner le nème signal temps-réel et rendre les applications portables sur des systèmes qui utilisent d'autres numéros pour ces signaux.

Les signaux temps-réel n'ont pas de signification précise et sont toujours envoyés par des processus. Ils constituent donc en quelque sorte une extension des signaux SIGUSR1 et SIGUSR2.

Contrairement aux signaux classiques, tous les signaux temps-réel envoyés à un processus sont conservés par le noyau jusqu'à ce qu'ils puissent être délivrés au processus.

Si plusieurs signaux temps-réel sont en attente d'être délivrés à un processus, lorsque le processus pourra les traiter, ils lui seront délivrés en ordre croissant du n° de signal. Il y a donc une notion de priorité pour les signaux temps-réel qui n'existe pas pour les signaux classiques, SUSv3 ne précisant pas l'ordre de délivrance de ceux-ci, ni l'ordre de délivrance des signaux classiques par rapport aux signaux temps-réel.

L'envoi d'un signal.

La fonction kill().

La fonction kill() permet d'envoyer un signal à un processus.

#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);

pid est le pid du processus destinataire du signal et sig le n° du signal envoyé.

Cette fonction renvoie 0 si l'appel réussit et -1 en cas d'échec, auquel cas la variable errno contient le code d'erreur qui peut être:

  • EINVAL: le n° du signal n'est pas valide.
  • ESRCH: le processus indiqué par pid n'existe pas.
  • EPERM: le processus appelant n'a pas la permission d'envoyer un signal au processus destinataire car ils n'appartiennent pas au même propriétaire. Un processus tournant sous l'identité root peut envoyer un signal à n'importe quel autre processus.

Remarques.

  • Le paramètre sig peut prendre la valeur 0. Dans ce cas aucun signal n'est envoyé mais les conditions d'erreur sont vérifiées et donc, si l'appel réussit, cela indique que le processus cible existe et que l'on peut lui envoyer un signal.
  • Il est possible d'indiquer une valeur négative ou nulle pour le paramètre pid. Ceci permet d'envoyer un signal à des ensembles de processus mais nous ne détaillerons pas cette fonctionnalité ici.

La fonction raise() permet à un processus de s'envoyer un signal.

#include <signal.h>

int raise (int sig);

L'appel raise(sig) est équivalent à kill(getpid(), sig).

Exemple.

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

int main()
{
pid_t pid_fils;
int info_fin;

switch ( pid_fils = (long)fork() ){
case -1:
perror("Erreur lors de fork");
return EXIT_FAILURE;
case 0:
while ( 1 ){
printf("Le processus fils est vivant\n");
sleep(1);
}
default:
sleep(5);
printf("Le processus père envoie le signal SIGKILL à son fils\n");
if ( kill(pid_fils, SIGKILL) ){
perror("Erreur lors de kill");
return EXIT_FAILURE;
}
if ( waitpid(pid_fils, &info_fin, 0) != -1 )
if ( WIFSIGNALED(info_fin) )
printf("Le processus s'est terminé à cause du signal %s\n",
sys_siglist[WTERMSIG(info_fin)]);
}
return EXIT_SUCCESS;
}

L'exécution de ce programme produit le résultat suivant:

Le processus fils est vivant
Le processus fils est vivant
Le processus fils est vivant
Le processus fils est vivant
Le processus fils est vivant
Le processus père envoie le signal SIGKILL à son fils
Le processus s'est terminé à cause du signal Killed

Notez au passage l'utilisation des macros WIFSIGNALED et WTERMSIG pour déterminer à partir des informations de terminaison du fils, s'il s'est terminé à cause d'un signal et récupérer le n° du signal qui a causé sa terminaison.

La fonction sigqueue().

La fonction sigqueue() permet d'envoyer un signal accompagné d'une donnée à un processus. Cette donnée peut être un entier ou un pointeur.

#include <signal.h>

int sigqueue(pid_t pid, int sig, const union sigval valeur);

Les deux premiers arguments correspondent à ceux de kill() le troisième permet de spécifier la donnée associée au signal. Il s'agit d'une union définie de la façon suivante:

#include <signal.h>

union sigval {
int sival_int;
void *sival_ptr;
};

Il n'y a pas moyen d'indiquer si on a rempli le membre entier ou le membre pointeur de cet union. Si les processus émetteur et destinataire sont différents, cela n'a pas de sens de transmettre un pointeur qui n'aurait aucune signification dans l'espace d'adressage du processus destinataire. Si les deux processus sont les mêmes, il faut concevoir l'application de telle sorte qu'à la réception de la donnée, le processus puisse déterminer quand il s'agit d'un entier ou d'un pointeur si on utilise les deux possibilités.

Si l'appel réussit, la fonction retourne 0. Sinon elle retourne -1. Les valeur de errno sont les mêmes que pour kill() avec en plus EAGAIN si le nombre maximum de signaux en file d'attente pour le processus est atteint (_POSIX_SIGQUEUE_MAX).

Le blocage d'un signal.

Un processus peut demander au noyau de ne pas lui délivrer temporairement certains signaux pour ne pas être interrompu dans une opération "critique". On dit alors que le processus bloque des signaux. Le noyau mémorise les signaux envoyés au processus mais ne les délivre pas tant qu'ils ne sont pas débloqués. Les signaux sont donc mis en attente. Pour les signaux classiques, le noyau ne conserve qu'un exemplaire, pour un type donné, des signaux qui sont envoyés pendant que les signaux de ce type sont bloqués, tandis que pour les signaux temps-réels, tous les exemplaires sont conservés. La fonction qui bloque mais aussi débloque des signaux permet de le faire pour plusieurs type de signaux en un seul appel. Elle manipule des objets du type sigset_t défini dans signal.h qui représentent des ensembles de signaux. L'ensemble des signaux bloqués est appelé masque des signaux.

Les ensembles de signaux se définissent à l'aide des fonctions suivantes.

#include <signal.h>

int sigemptyset (sigset_t *set);
int sigfillset (sigset_t *set);
int sigaddset (sigset_t *set, int signum);
int sigdelset (sigset_t *set, int signum);
int sigismember (const sigset_t *set, int signum);

La fonction sigemptyset() vide l'ensemble set. La fonction sigfillset() remplit set avec tous les signaux. La fonction sigaddset() ajoute à l'ensemble set le signal signum. La fonction sigdelset() enlève de l'ensemble set le signal signum. La fonction sigismember() teste l'appartenance du signal signum à l'ensemble set.

Les fonctions sigemptyset() et sigfillset() retournent 0. sigaddset et sigdelset() retournent 0 si l'appel réussit, -1 sinon en fixant errno à la valeur EINVALsi signum n'est pas un n° de signal valide. sigismember() retourne 1 si signum se trouve dans l'ensemble, 0 s'il ne s'y trouve pas et -1 en cas d'erreur en fixant errno à la valeur EINVALsi signum n'est pas un n° de signal valide.

La fonction qui permet de fixer le masque des signaux est la fonction sigprocmask().

#include <signal.h>

int sigprocmask (int how, const sigset_t * set, sigset_t * oldset);

Le paramètre how détermine l'action effectuée et peut prendre les valeurs

  • SIG_BLOCK: les signaux de l'ensemble set sont ajoutés au masque courant.
  • SIG_UNBLOCK: les signaux de l'ensemble set sont retirés du masque courant.
  • SIG_SETMASK: l'ensemble de signaux set devient le masque courant.

Le paramètre oldset permet de récupérer le masque actif avant l'invocation de la fonction pour pouvoir éventuellement le restaurer ultérieurement. Si on ne souhaite pas le récupérer, on peut passer un pointeur NULL. Si on souhaite récupérer le masque courant sans le modifier, on peut passer un pointeur NULL pour le paramètre set et récupérer le masque courant dans oldset auquel cas, la valeur de how n'est pas prise en compte.

Si l'appel réussit, la fonction retourne 0. En cas d'échec, elle retourne -1 en fixant errno à la valeur EINVAL si la valeur de how ne se trouve pas dans la liste donnée ci-dessus.

Les signaux SIGSTOP et SIGKILL ne peuvent être bloqués.

Exemple.

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

int main()
{
pid_t pid_fils;
int info_fin;

switch ( pid_fils = (long)fork() )
{
case -1:
perror("Erreur lors de fork");
return EXIT_FAILURE;
case 0:
{
sigset_t masque;
int i;

sigemptyset(&masque);
sigaddset(&masque, SIGUSR1);

sigprocmask(SIG_SETMASK, &masque, NULL);

for ( i = 0; i < 5; ++i )
{
printf("Le processus fils est vivant\n");
sleep(1);
}

printf("Le processus fils débloque SIGUSR1\n");
sigprocmask(SIG_UNBLOCK, &masque, NULL);
sleep(1);
}
default:
sleep(1);
printf("Le processus père envoie le signal SIGUSR1 à son fils\n");
if ( kill(pid_fils, SIGUSR1) )
{
perror("Erreur lors de kill");
return EXIT_FAILURE;
}
if ( waitpid(pid_fils, &info_fin, 0) != -1 )
if ( WIFSIGNALED(info_fin) )
printf("Le processus s'est terminé à cause du signal %s\n",
sys_siglist[WTERMSIG(info_fin)]);
}
return EXIT_SUCCESS;
}

L'exécution de ce programme peut produire le résultat suivant:

Le processus fils est vivant
Le processus fils est vivant
Le processus père envoie le signal SIGUSR1 à son fils
Le processus fils est vivant
Le processus fils est vivant
Le processus fils est vivant
Le processus fils débloque SIGUSR1
Le processus s'est terminé à cause du signal User defined signal 1

La fonction sigpending() permet d'obtenir l'ensemble des signaux en attente.

#include <signal.h>

int sigpending (sigset_t * set);

Elle retourne 0 quand l'appel réussit et -1 en cas d'échec.

Le traitement des signaux.

Un des intérêts des signaux est qu'ils permettent de déclencher l'exécution d'une fonction du processus qui les reçoit. Une telle fonction est appelée un gestionnaire de signal.

Il existe deux façons d'installer un gestionnaire de signal que nous allons détailler ici.

La fonction signal().

Définie par la norme Ansi C et reprise dans SUSv3, la fonction signal() permet d'installer un gestionnaire de signaux en une seule instruction.

Son prototype est le suivant:

#include <signal.h>

void (*signal(int signum, void (*func)(int)))(int);

Pour le rendre plus lisible, on peut introduire le type

typedef void (*sighandler_t)(int);

Qui permet d'écrire le prototype de la façon suivante.

sighandler_t signal(int signum, sighandler_t handler);

La fonction signal() est donc une fonction qui prend comme paramètres un entier et une fonction void à un paramètre entier, et retourne une fonction du même type.

signum est le signal pour lequel on installe un gestionnaire et handler est ce gestionnaire. La valeur de retour est le gestionnaire présent au moment de l'appel (dont on peut ainsi conserver la valeur pour la restaurer ultérieurement), si l'appel réussit, et la valeur SIG_ERR si l'appel échoue en fixant la valeur de errno à EINVAL si sig n'est pas un n° de signal valide ou si on tente d'ignorer ou d'installer un gestionnaire pour les signaux SIGSTOP et SIGKILL.

handler peut être une fonction du programme mais peut aussi prendre les valeurs définies par les constantes symboliques SIG_IGN ou SIG_DFL qui permettent respectivement d'ignorer le signal et de restaurer le traitement par défaut. Les signaux ignorés sont perdus contrairement aux signaux bloqués qui sont mis en attente par le noyau. Si on ignore un signal bloqué, ses éventuels exemplaires mis en attente sont effacés.

Lorqu'un processus reçoit un signal pour lequel un gestionnaire qui est une fonction du programme a été installé, la fonction est exécutée et reçoit comme paramètre le n° du signal reçu.

La GlibC comme d'autres systèmes bloque le signal qui a déclenché le gestionnaire pendant l'exécution de celui-ci. Sur d'autres systèmes, au contraire, le signal n'est pas bloqué et de plus, le comportement par défaut est réinstallé juste avant l'exécution du gestionnaire. Ces deux comportements ne violent pas la norme Ansi C qui laisse totale liberté à l'implémentation concernant cet aspect du traitement des signaux.

La restauration du gestionnaire par défaut avant le déclenchement d'un gestionnaire peut entraîner la perte d'un signal ou l'arrêt du programme pendant l'exécution du gestionnaire si un signal du même type que celui qui a déclenché le gestionnaire survient pendant son exécution. Bloquer le signal qui a déclenché le gestionnaire et réinstaller ce gestionnaire pour ce signal tout au début du gestionnaire n'est pas une façon sûre d'éviter la perte d'un signal ou l'arrêt du processus car un deuxième exemplaire du signal peut arriver avant qu'il n'ait été bloqué.

L'option choisie par la Glibc apparaît donc plus sûre mais elle n'est pas portable du fait d'un comportement différent présent sur d'autres systèmes.

Il est possible d'obtenir avec la Glibc le comportement inverse de son comportement par défaut en définissant la macro _XOPEN_SOURCE avant l'inclusion du fichier <signal.h> ou en utilisant la fonction

#include <signal.h>

sighandler_t sysv_signal (int sig, sighandler_t action);

pour installer un gestionnaire de signaux.

Exemple

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>


void gestionnaire(int sig)
{
printf("Réception du signal %s\n", sys_siglist[sig]);
}

int main()
{
pid_t pid_fils;


signal(SIGUSR1, gestionnaire);

switch ( pid_fils = (long)fork() ){
case -1:
perror("Erreur lors de fork");
return EXIT_FAILURE;
case 0:
while ( 1 ){
printf("Le processus fils est vivant\n");
sleep(1);
}
default:
sleep(3);
printf("Le processus père envoie le signal SIGUSR1 à son fils\n");
if ( kill(pid_fils, SIGUSR1) ){
perror("Erreur lors de kill");
return EXIT_FAILURE;
}
sleep(3);
printf("Le processus père envoie le signal SIGKILL à son fils\n");
kill(pid_fils, SIGKILL);
wait(NULL);
}
return EXIT_SUCCESS;
}

Ce programme peut produire le résultat suivant:

Le processus fils est vivant
Le processus fils est vivant
Le processus fils est vivant
Le processus père envoie le signal SIGUSR1 à son fils
Réception du signal User defined signal 1
Le processus fils est vivant
Le processus fils est vivant
Le processus fils est vivant
Le processus père envoie le signal SIGKILL à son fils

Observons au passage que le gestionnaire pour le signal SIGUSR1 a été "hérité" par le fils. Les fils héritent de la configuration du traitement et du blocage des signaux de leur père. Lors d'un appel à une fonction de la famille exec() le masque des signaux est conservé et les signaux ignorés le restent. Les signaux pour lesquels un gestionnaire a été installé retrouvent leur comportement par défaut, l'adresse du gestionnaire n'ayant plus de sens quand un nouveau programme est chargé.

La fonction sigaction().

Pour remédier au problème de portabilité de la fonction signal() et pour fournir un mécanisme sûr d'interception de signaux, SUSv3 propose une autre fonction pour installer un gestionnaire de signaux: la fonction sigaction().

#include <signal.h>

int sigaction (int signum, const struct sigaction * act, struct sigaction *oldact);

signum est le signal pour lequel on installe un gestionnaire. La structure stuct sigaction act permet de fournir le gestionnaire de signal et le masque. oldact est une structure du même type qui permet de récupérer la configuration de la gestion du signal avant l'installation par la fonction d'une nouvelle configuration.

La structure stuct sigaction contient au moins les champs suivants:

#include <signal.h>

struct sigaction {
...
void (* sa_handler)(int);
void (* sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
...
};

Le membre sa_handler est le gestionnaire de signal équivalent à celui de la fonction signal().

sa_sigaction permet également de spécifier un gestionnaire qui reçoit comme paramètre non seulement le n° du signal mais également des informations sur celui-ci et la donnée qui l'accompagne s'il a été envoyé par la fonction sigqueue().

sa_mask permet d'indiquer un ensemble de signaux qui seront ajoutés au masque pendant l'exécution du gestionnaire. Le signal qui a déclenché le gestionnaire est d'office ajouté au masque.

sa_flags permet de fixer certaines options du comportement du gestionnaire. Sa valeur peut être 0 ou une combinaison à l'aide d'un ou (|) des constantes symboliques

  • SA_ONESHOT: Le gestionnaire par défaut est rétabli avant l'appel du gestionnaire.
  • SA_NOMASK ou SA_NODEFER: Le signal ayant déclenché le gestionnaire n'est pas masqué pendant l'exécution de celui-ci.
  • SA_SIGINFO: La fonction pointée par le membre sa_sigaction() est invoquée à la place de sa_handler().
  • SA_RESTART: Si le signal a interrompu un appel-système bloquant, celui-ci reprend au retour de l'exécution du gestionnaire au lieu de s'arrêter en retournant parfois, selon l' appel-système, une erreur et en fixant la valeur de errno à EINTR.

La fonction pointée par sa_sigaction reçoit comme paramètres le n° du signal et une structure de type siginfo_t contenant entre-autres les membres

int si_errno;
int si_code;
pid_t si_pid;
sigval_t si_sigval

si_errno contient la valeur de errno au moment de l'invocation du gestionnaire et permet de restaurer cette valeur à la sortie de celui-ci.

si_code indique la provenance du signal, sa valeur peut-être entre-autres

  • SI_USER: Le signal a été envoyé par la fonction kill() (ou raise()) (sous Linux SI_TKILL si le signal provient du processus lui-même).
  • SI_KERNEL: Le signal provient du noyau.
  • SI_QUEUE: Le signal a été envoyé par la fonction sigqueue().

si_pid est le pid du processus qui a envoyé le signal si le signal a été émis par un processus.

si_sigval permet de récupérer l'entier ou le pointeur spécifié lors de l'envoi du signal par sigqueue.

Le dernier paramètre de la fonction pointée par sa_sigaction n'est pas utilisé sous Linux.

sa_handler et sa_sigaction peuvent être membres d'une union. Il n'est donc pas conseillé d'affecter une valeur à chacun d'eux et d'activer ou non l'option correspondant à SA_SIGINFO pour fixer le choix du gestionnaire. Chaque modification de sa_flag relative à cette option doit donc s'accompagner de l'affectation d'une valeur à sa_handler ou sa_sigaction selon qu'on a utilisé ou non SA_SIGINFO.

La fonction sigaction() retourne 0 si l'appel réussit et -1 s'il échoue auquel cas la valeur de errno est fixée à EINVAL si le n° du signal n'est pas valide ou égal à SIGKILL ou SIGSTOP.

Exemple

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

void gestionnaire(int sig, siginfo_t *info, void *inutile)
{
char *origine;

printf("Reception du signal n° %d ", sig);
switch ( info->si_code ){
case SI_USER:
printf("envoyé au moyen de kill() ou raise() par le processus "
"%ld\n", info->si_pid);
break;
case SI_QUEUE:
printf("envoyé au moyen de sigqueue() par le processus "
"%ld avec la valeur %d\n", (long)info->si_pid,
info->si_value.sival_int);
break;
default:
printf("\n");
break;
}
}


int main()
{
pid_t pid_fils;

printf("Pid du père: %ld\n", (long)getpid());

switch ( pid_fils = (long)fork() ){
case -1:
perror("Erreur lors de fork");
return EXIT_FAILURE;
case 0:
{
sigset_t masque;
struct sigaction action;

sigemptyset(&masque);
sigaddset(&masque, SIGUSR1);
sigaddset(&masque, SIGUSR2);

sigprocmask(SIG_BLOCK, &masque, NULL);

action.sa_sigaction = gestionnaire;
action.sa_mask = masque;
action.sa_flags = SA_SIGINFO;

sigaction(SIGUSR1, &action, NULL);
sigaction(SIGUSR2, &action, NULL);

sleep(2);

sigprocmask(SIG_UNBLOCK, &masque, NULL);

while( 1 );
}
default:
{
union sigval val;

sleep(1);

printf("Envoi de 2x SIGUSR1 et 2x SIGUSR2 par kill()\n");
kill(pid_fils, SIGUSR1);
kill(pid_fils, SIGUSR1);
kill(pid_fils, SIGUSR2);
kill(pid_fils, SIGUSR2);

val.sival_int = 123;

printf("Envoi de 2x SIGUSR1 et 2x SIGUSR2 par sigqueue()\n");
sigqueue(pid_fils, SIGUSR1, val);
sigqueue(pid_fils, SIGUSR1, val);
sigqueue(pid_fils, SIGUSR2, val);
sigqueue(pid_fils, SIGUSR2, val);

sleep(2);

kill(pid_fils, SIGKILL);
wait(NULL);
}
}
return EXIT_SUCCESS;
}

Dans ce programme, le processus père crée un fils auquel il envoie deux fois les signaux SIGUSR1 et SIGRTMIN en utilisant la fonction kill() puis, à nouveau deux fois ces mêmes signaux en utilisant sigqueue().

Dans le processus fils, ces signaux sont d'abord bloqués. On installe ensuite, en utilisant la fonction sigaction(), un gestionnaire pour les traiter qui affiche l'origine du signal et éventuellement la donnée qui lui est attachée. Puis on débloque ces signaux après une courte pause (sleep()) qui laisse le temps au processus père d'envoyer les signaux.

L'exécution du programme produit le résultat suivant:

Pid du père: 2182
Envoi de 2x SIGUSR1 et 2x SIGRTMIN par kill()
Envoi de 2x SIGUSR1 et 2x SIGRTMIN par sigqueue()
Reception du signal n° 10 envoyé au moyen de kill() ou raise() par le processus 2182
Reception du signal n° 33 envoyé au moyen de kill() ou raise() par le processus 2182
Reception du signal n° 33 envoyé au moyen de kill() ou raise() par le processus 2182
Reception du signal n° 33 envoyé au moyen de sigqueue() par le processus 2182 avec la valeur 123
Reception du signal n° 33 envoyé au moyen de sigqueue() par le processus 2182 avec la valeur 123

On observe que sur les 4 signaux SIGUSR1 envoyés par le processus père, un seul a été délivré au processus fils. Par contre, les 4 signaux temps-réels ont bien été reçus.

La fonction alarm().

La fonction

#include <unistd.h>

unsigned int alarm(unsigned int nb_sec);

permet à un processus de recevoir un signal SIGALARM après un nombre de secondes indiqué par son paramètre nb_sec.

Un second appel à alarm() "annule" l'appel précédent en définissant la nouvelle. On ne peut donc définir qu'une alarme à la fois.

La valeur de retour est le nombre de secondes restantes avant l'émission du signal SIGALARM demandé par l'appel à alarm() précédent (et annulé par l'appel courant) ou 0 s'il n'y avait pas d'alarme en cours.

L'appel alarm(0) permet d'annuler une demande d'alarme sans en initier une nouvelle.

L'attente d'un signal.

La fonction
#include <unistd.h>

int pause(void);

endort le processus qui l'invoque jusqu'à l'arrivée d'un signal. Elle retourne toujours -1.

Cette fonction n'est pas sûre car si le signal attendu a lieu juste avant l'appel de la fonction, le processus restera bloqué sur l'appel à pause() comme l'illustre l'exemple suivant.

sigprocmask(…); //déblocage des signaux attendus.
<- le signal arrive entre sigprocmask() et pause().
pause(); //pause() manque le signal et le processus est bloqué.

La fonction

#include <signal.h>

int sigsuspend(const sigset_t *mask);

est plus fiable que pause() dans la mesure où elle permet de d'endormir le processus jusqu'à l'arrivée d'un signal tout en fixant un masque jusqu'au réveil du processus, ces deux opérations ne pouvant être interrompue par l'arrivée d'un signal.

Lorsqu'on attend des signaux avec sigsuspend(), on crée donc un ensemble de signaux qui contient tous les signaux sauf ceux attendus et on passe cet ensemble comme paramètre à suspend().

Actions sur le document