• Aucun résultat trouvé

Émission d'un signal temps-réel

Dans le document Programmation système en C sous (Page 96-99)

Émission d'un signal temps-réel

Bien sûr, si on désire transmettre des données supplémentaires au gestionnaire de signal, il ne suffit plus d'employer la fonction kill( ) habituelle. Il existe un nouvel appel-système, nommé sigqueue( ), défini par Posix.1b :

int sigqueue (pid_t pid, int numero, const union sigval valeur)

Les deux premiers arguments sont équivalents à ceux de kill( ), mais le troisième correspond au membre si_sigval de la structure siginfo transmise au gestionnaire de signal.

Il n'y a aucun moyen dans le gestionnaire de déterminer si l'argument de type union sigval a été rempli, lors de l'invocation de sigqueue( ) avec une valeur entière (champ sigval_int) ou un pointeur (champ sigval_ptr). Il est donc nécessaire que l'application reste cohérente entre l'envoi du signal et sa réception. Lorsque le signal est transmis entre deux processus distincts, on ne peut bien sûr passer de pointeurs que sur une zone de mémoire partagée.

Récapitulons les principaux champs de la structure siginfo reçue par le gestionnaire de signal :

Nom membre Type Posix.1b Signification

si_signo int • Numéro du signal, redondant avec le premier argument de l'appel du gestionnaire.

si_code int • Voir ci-dessous.

si_value.sigval_int int • Entier de l'union passée en dernier argument de sigqueue( ) .

si_value.sigval_ptr void

* • Pointeur de l'union passée en dernier argument de sigqueue( ). Ne doit pas être employé simultanément avec le membre précédent.

si_errno int Valeur de la variable globale errno lors du déclenchement du gestionnaire. Permet de rétablir

membre Type Posix.1b Signification

si_pid pid_t PID du processus fils s'étant terminé si le signal est SIG-CHLD. PID de l'émetteur si le signal est temps-réel.

si_uid uid_t UID réel de l'émetteur d'un signal temps-réel ou celui du processus fils terminé si le signal est SIGCHLD.

si_status int Code de retour du processus fils terminé, uniquement avec le signal SIGCHLD.

La signification du champ si_code varie suivant le type de signal. Pour les signaux temps-réel ou pour la plupart des signaux classiques, si_code indique l'origine du signal :

Valeur Provenance du signal Posix.1b

SI_KERNEL Signal émis par le noyau

SI_USER Appel-système kill( ) ou raise() •

SI_QUEUE Appel-système sigqueue( ) •

SI_ASYNCIO Terminaison d'une entrée-sortie asynchrone • SI_MESGQ Changement d'état d'une file de message temps-réel (non

implémenté sous Linux)

• SI_SIGIO Changement d'état sur un descripteur d'entrée-sortie

asynchrone

SI_TIMER Expiration d'une temporisation temps-réel (non

implémentée sous Linux) •

Pour un certain nombre de signaux classiques, Linux fournit également des données (principalement utiles au débogage) dans le champ si_code, si le gestionnaire est installé en utilisant SA_SIGINFO dans l'argument sa_flags de sigaction :

Signal SIGBUS

BUS_ADRALN Erreur d'alignement d'adresse.

BUS_ADRERR Adresse physique invalide.

BUS_OBJERR Erreur d'adressage matériel.

Signal SIGCHLD CLD_CONTINUED Un fils arrêté a redémarré.

CLD_DUMPED Un fils s'est terminé anormalement.

CLD_EXITED Un fils vient de se terminer normalement.

CLD_KILLED Un fils a été tué par un signal.

CLD_STCPPED Un fils a été arrêté.

CLD_TRAPPED Un fils a atteint un point d'arrêt.

Signal SIGFPE

FPE_FLTDIV Division en virgule flottante par zéro.

FPE_FLTINV Opération en virgule flottante invalide.

FPE_FLTOVF Débordement supérieur lors d'une opération en virgule flottante.

FPE_FLTRES Résultat faux lors d'une opération en virgule flottante.

FPE_FLTSUB élévation à une puissance invalide.

FPE_FLTUND Débordement inférieur lors d'une opération en virgule flottante.

FPE_INTDIV Division entière par zéro.

FPE_INTOVF Débordement de valeur entière.

Signal SIGILL ILL_BADSTK Erreur de pile.

ILL_COPROC Erreur d'un coprocesseur.

ILL_ILLADR Mode d'adressage illégal.

ILL_ILLOPC Code d'opération illégal.

ILL_ILLOPN Opérande illégale.

ILL_ILLTRP Point d'arrêt illégal.

ILL_PRVOPC Code d'opération privilégié.

ILL_PRVREG Accès à un registre privilégié.

Signal SIGPOLL POLL_ERR Erreur d'entrée-sortie.

POLL_HUP Déconnexion du correspondant.

POLL_IN Données prêtes à être lues.

POLL_MSG Message disponible en entrée.

POLL_OUT Zone de sortie disponible.

POLL_PRI Entrées disponibles à haute priorité.

Signal SIGSEGV

SEGV_ACCERR Accès interdit à la projection mémoire.

SEGV_MAPERR Adresse sans projection mémoire.

Signal SIGTRAP TRAP_BRKPT Point d'arrêt de débogage.

TRAP_TRACE Point d'arrêt de profilage.

ATTENTION Tous ces codes sont spécifiques à Linux et ne doivent pas être employés dans une application portable. En outre, ils sont tous déclarés dans les fichiers d'en-tête de Linux, mais ils ne sont pas tous réellement renvoyés par le noyau.

À la lecture du premier tableau, concernant les champs si_code généraux, nous remarquons plusieurs choses :

• Il est possible d'envoyer un signal temps-réel avec l'appel-système kill( ).

Simplement, les informations supplémentaires ne seront pas disponibles. Leur valeur dans ce cas n'est pas précisée par Posix.1b, mais sous Linux, le champ de type sigval correspondant est mis à zéro. Il est donc possible d'employer les signaux temps-réel en remplacement pur et simple de SIGUSR1 et SIGUSR2 dans une application déjà existante, en profitant de l'empilement des signaux, mais en restant conscient du problème que nous avons évoqué, concernant la priorité de délivrance.

• Il existe un certain nombre de sources de signaux temps-réel possibles, en supplément de la programmation manuelle avec sigqueue( ) ou kill( ). Plusieurs fonctionnalités introduites par la norme Posix.1b permettent en effet à l'application de programmer un travail et de recevoir un signal lorsqu'il est accompli. C'est le cas, par exemple, des files de messages utilisant les fonctions mq_open( ), mq_close( ), mq_notify( ), ou encore des temporisations programmées avec timer_create( ), timer_delete( ) et timer_settime( ).

Malheureusement, ces fonctionnalités temps-réel ne sont pas implémentées sous Linux et ne nous concernent donc pas pour le moment. Par contre, les entrées-sorties asynchrones permettent de programmer un message à recevoir quand l'opération désirée est terminée.

Ces fonctions seront étudiées dans le chapitre 30.

Nous allons commencer par créer un programme servant de frontal à sigqueue( ), comme l'utilitaire système /bin/kill pouvait nous servir à invoquer l'appel-système kill( ) depuis la ligne de commande.

exemple_sigqueue.c : #include <signal.h>

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

void

syntaxe (const char * nom) {

fprintf (stderr, "syntaxe %s signal pid...\n", nom);

exit (1);

} int

main (int argc, char * argv []) {

int i;

int numero;

pid_t pid;

union sigval valeur;

if (argc == 1) syntaxe(argv [0];

i = 1;

if (argc == 2) {

if (sscanf (argv [i], "%d", & numero) != 1) syntaxe(argv [0]);

i++;

}

if ((numero < 0) || (numero > NSIG - 1)) syntaxe(argv [0]);

valeur . sival_int = 0;

for (; i < argc; i ++) {

if (sscanf (argv [i], "%d", & pid) != 1) syntaxe(argv [0]);

if (sigqueue (pid, numero, valeur) < 0) { fprintf (stderr, "%u ", pid);

perror ("");

} }

return (0);

}

À présent, nous allons créer un programme qui installe un gestionnaire de type temps-réel pour tous les signaux — même les signaux classiques — pour afficher le champ si_code de leur argument de type siginfo.

exemple_siginfo.c

#include <signal.h>

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

void

gestionnaire (int numero, struct siginfo * info, void * inutilise) {

fprintf (stderr, "Reçu %d\n", numero);

fprintf (stderr, " si_code = %d\n", info -> si_code);

} int main (void) {

int i;

struct sigaction action;

char chaine [5];

action . sa_sigaction = gestionnaire;

action . sa_flags = SA_SIGINFO;

sigemptyset (& action . sa_mask);

fprintf (stderr, "PID=%u\n", getpid( ));

for (i = 1; i < NSIG; i++)

if (sigaction (i, & action, NULL) < 0)

fprintf (stderr, "%d non intercepté \n", i);

while (1)

fgets (chaine, 5, stdin);

return (0);

}

Finalement, nous lançons le programme exemple_siginfo, puis nous lui envoyons des signaux depuis une autre console (représentée en seconde colonne), en utilisant tantôt /bin/kill , tantôt exemple_sigqueue.

$ ./exemple_siginfo PID=1069

9 non intercepté 19 non intercepté $ kill -33 1069 Reçu 33

si_code = 0

$ ./exemple_sigqueue 33 1069 Reçu 33

si_code = -1

$ kill -TERM 1069 Reçu 33

si_code = 0

$ kill -KILL 1069 killed

$

Le champ si_code correspond bien à 0 (valeur de SI_USER) ou à -1 (valeur de SI_QUEUE) suivant le cas.

ATTENTION Si on utilise l'appel-système alarm( )pour déclencher SIGALRM, le champ si_code est rempli avec la valeur SI_USER et pas avec SI_TIMER, qui est réservée aux temporisations temps-réel.

Notre second exemple va mettre en évidence à la fois l'empilement des signaux temps-réel et leur respect d'une priorité. Notre programme va en effet bloquer tous les signaux, s'en envoyer une certaine quantité, et voir dans quel ordre ils arrivent. La valeur sigval associée aux signaux permettra de les reconnaître.

exemple_sigqueue_1.c #include <signal.h>

#include <stdio.h>

#include <unistd.h>

int signaux_arrives [10];

int valeur_arrivee [10];

int nb_signaux = 0;

void

gestionnaire_signal_temps_reel (int numero,

siginfo_t * info, void * inutile) {

signaux_arrives [nb_signaux] = numero - SIGRTMIN;

valeur_arrivee [nb_signaux] = info -> si_value . sival_int;

nb_signaux ++;

}

void

envoie_signal_temps_reel (int numero, int valeur) {

union sigval valeur_sig;

fprintf (stdout, "Envoi signal SIRTMIN+%d, valeur %d\n", numero, valeur);

valeur_sig . sival_int = valeur;

if (sigqueue (getpid( ) , numero + SIGRTMIN, valeur_sig) < 0) { perror ("sigqueue");

exit (1);

} } int main (void) {

struct sigaction action;

sigset_t ensemble;

int i;

fprintf (stdout, "Installation gestionnaires de signaux \n");

action . sa_sigaction = gestionnaire_signaltemps reel;

sigemptyset (& action . sa_mask);

action . sa_flags = SA_SIGINFO;

if ((sigaction (SIGRTMIN + 1, & action, NULL) < 0) || (sigaction (SIGRTMIN + 2, & action, NULL) < 0) || (sigaction (SIGRTMIN + 3, & action, NULL) < 0)) { perror ("sigaction");

exit (1);

}

fprintf (stdout, "Blocage de tous les signaux \n");

sigfillset(& ensemble);

sigprocmask (SIG_BLOCK, & ensemble, NULL);

envoie_signal_tempsreel (1, 0);

envoie_signal_temps_reel (2, 1);

envoie_signal_temps_reel (3, 2);

envoie_signal_temps_reel (1, 3);

envoie_signal_temps_reel (2, 4);

envoie_signal_temps_reel (3, 5);

envoie_signal_temps_reel (3, 6);

envoie_signal_temps_reel (2, 7);

envoie_signal_temps_reel (1, 8);

envoie_signal_temps_reel (3, 9);

fprintf (stdout, "Déblocage de tous les signaux \n");

sigfillset(& ensemble);

sigprocmask (SIG_UNBLOCK, & ensemble, NULL);

fprintf (stdout, "Affichage des résultats \n");

for (i = 0; i < nb_signaux; i++)

fprintf (stdout, "Signal SIGRTMIN+%d, valeur %d\n", signaux_arrives [i], valeur_arrivee [i]);

fprintf (stdout, "Fin du programme \n");

return (0);

}

Notre gestionnaire stocke les signaux arrivant dans une table qui est affichée par la suite, pour éviter les problèmes de concurrence sur l'accès au flux stdout.

$ ./exemple_sigqueue_1

Installation gestionnaires de signaux Blocage de tous les signaux

Envoi signal SIRTMIN+1, valeur 0 Envoi signal SIRTMIN+2, valeur 1 Envoi signal SIRTMIN+3, valeur 2 Envoi signal SIRTMIN+1, valeur 3 Envoi signal SIRTMIN+2, valeur 4 Envoi signal SIRTMIN+3, valeur 5 Envoi signal SIRTMIN+3, valeur 6 Envoi signal SIRTMIN+2, valeur 7 Envoi signal SIRTMIN+1, valeur 8 Envoi signal SIRTMIN+3, valeur 9 Déblocage de tous les signaux Affichage des résultats Signal SIGRTMIN+1, valeur 0 Signal SIGRTMIN+1, valeur 3 Signal SIGRTMIN+1, valeur 8 Signal SIGRTMIN+2, valeur 1 Signal SIGRTMIN+2, valeur 4 Signal SIGRTMIN+2, valeur 7 Signal SIGRTMIN+3, valeur 2 Signal SIGRTMIN+3, valeur 5 Signal SIGRTMIN+3, valeur 6 Signal SIGRTMIN+3, valeur 9 Fin du programme

$

Nous remarquons bien que les signaux sont délivrés suivant leur priorité : tous les SIRTMIN+1 en premier, suivis des SIGRTMIN+2, puis des SIGRTMIN+3. De même, au sein de chaque classe, les occurrences des signaux sont bien empilées et délivrées dans l'ordre chronologique d'émission.

Dans le document Programmation système en C sous (Page 96-99)