• Aucun résultat trouvé

Traitement rapide des signaux temps-réel

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

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.

Traitement rapide des signaux temps-réel

La norme Posix.1b donne accès à des possibilités de traitement rapide des signaux. Ceci ne concerne que les applications qui attendent passivement l'arrivée d'un signal pour agir.

Cette situation est assez courante lorsqu'on utilise les signaux comme une méthode pour implémenter un comportement multitâche au niveau applicatif.

Avec le traitement classique des signaux, nous utilisions quelque chose comme : sigfillset (& tous_signaux);

sigprocmask (SIG_BLOCK, & tous_signaux, NULL);

sigemptyset (& aucun_signal);

while( ! fin_programme)

sigsuspend (& aucun_signal);

Lorsqu'un signal arrive, le processus doit alors être activé par l'ordonnanceur, ensuite l'exécution est suspendue, le gestionnaire de signal est invoqué, puis le contrôle revient au fil courant d'exécution, qui termine la fonction sigsuspend( ). La boucle reprend alors.

Le problème est que l'appel du gestionnaire par le noyau nécessite un changement de contexte, tout comme le retour de ce gestionnaire. Ceci est beaucoup plus coûteux qu'un simple appel de fonction usuel.

Deux appels-système, définis dans la norme Posix.1b, ont donc fait leur apparition avec le noyau Linux 2.2. IL s'agit de sigwait_info( ) et de sig_timed_wait( ). Ce sont en quelque sorte des extensions de sigsuspendo. Ils permettent d'attendre l'arrivée d'un signal dans un ensemble précis. A la différence de sigsuspend( ), lorsqu'un signal arrive, son gestionnaire n'est pas invoqué. A la place, l'appel-système c ou sigtimedwait(

) se termine en renvoyant le numéro de signal reçu. Il n'est plus nécessaire d'effectuer des changements de contexte pour exécuter le gestionnaire, il suffit d'une gestion directement intégrée dans le fil du programme (la plupart du temps en utilisant une construction swi tch-case). Si le processus doit appeler le gestionnaire, il le fera simplement comme une fonction classique, avec toutes les possibilités habituelles d'optimisation par insertion du code en ligne.

Comme prévu, sigtimedwait( ) est une version temporisée de sigwaitinfo( ), qui échoue avec une erreur EAGAIN si aucun signal n'est arrivé pendant le délai imparti : int sigwaitinfo (const sigset_t * signaux_attendus, siginfo_t * info);

int sigtimedwait (const sigset_t * signaux_ attendus, siginfo_t * info, const struct timespec * delai);

De plus, ces fonctions offrent l'accès aux données supplémentaires disponibles avec les signaux temps-réel.

ATTENTION sigsuspend( ) prenait en argument l'ensemble des signaux bloqués, sigwaitinfo( ) comme sigtimedwait( ) réclament l'ensemble des signaux attendus.

La structure timespec utilisée pour programmer le délai offre les membres suivants :

Type Nom Signification

long tv_sec Nombre de secondes

long tv_nsec Nombre de nanosecondes

La valeur du champ tv_nsec doit être comprise entre 0 et 999.999.999, sinon le comporte-ment est indéfini.

Les appels-système sigwaitinfo( ) ou sigtimedwait( ) peuvent échouer avec l'erreur EINTR si un signal non attendu est arrivé et a été traité par un gestionnaire. Si leur second argument est NULL, aucune information n'est stockée.

Il est important de bloquer avec sigprocmask( ) les signaux qu'on attend avec sigwaitinfo( ) ou sigtimedwait( ), car cela assure qu'aucun signal impromptu n'arrivera juste avant ou après l'invocation de l'appel-système.

Notre premier exemple va consister à installer un gestionnaire normal pour un seul signal, SIGRTMIN+1, pour voir le comportement du système avec les signaux non attendus.

Ensuite, on bloque tous les signaux, puis on les attend tous, sauf SIGRTMIN+1 et SIGKILL. Nous expliquerons plus bas pourquoi traiter SIGKILL spécifiquement.

exemple_sigwaitinfo.c

#include <stdio.h>

#include <stdlib.h>

#include <signal.h>

#include <unistd.h>

void

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

fprintf (stderr, "gestionnaire : %d reçu \n", numero);

} int main (void) {

sigset_t ensemble;

int numero; struct sigaction action;

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

/* Installation gestionnaire pour SIGRTMIN+1 */

action . sa_sigaction = gestionnaire;

action . sa_flags = SA_SIGINFO;

sigemptyset (& action. sa_mask);

sigaction (SIGRTMIN + 1, & action, NULL);

/* Blocage de tous les signaux sauf SIGRTMIN+1 */

sigfillset (& ensemble);

sigdelset (& ensemble, SIGRTMIN + 1);

sigprocmask (SIG_BLOCK, & ensemble, NULL);

/* Attente de tous les signaux sauf RTMIN+1 et SIGKILL */

sigfillset (& ensemble);

sigdelset (& ensemble, SIGRTMIN + 1);

sigdelset (& ensemble, SIGKILL);

while (1) {

if ((numero = sigwaitinfo (& ensemble, NULL)) < 0) perror ("sigwaitinfo");

else

fprintf (stderr, "sigwaitinfo : %d reçu \n", numero);

}

return (0);

}

Nous ne traitons pas réellement les signaux reçus, nous contentant d'afficher leur numéro, mais nous pourrions très bien insérer une séquence switch-case au retour de sigwaitinfo( ). Il faut bien comprendre que le signal dont le numéro est renvoyé par sigwaitinfo( ) est

complètement éliminé de la liste des signaux en attente. La structure siginfo est également remplie lors de l'appel-système si des informations sont disponibles.

Voici un exemple d'exécution avec, en seconde colonne, les actions saisies depuis une autre console :

$ ./exemple_sigwaitinfo PID=1435

$ kill -HUP 1435 sigwaitinfo : 1

$ kill -TERM 1435 reçu sigwaitinfo : 15 reçu

$ kill -33 1435 gestionnaire : 33 reçu

sigwaitinfo: Appel système interrompu

$ kill -STOP 1435 sigwaitinfo : 19 reçu

$ kill -KILL 1435 Killed

$

Nous remarquons deux choses importantes :

• Lorsqu'un signal non attendu est reçu, il est traité normalement par son gestionnaire, et l'appel-système sigwaitinfo( ) est interrompu et échoue avec l'erreur EINTR.

• L'appel-système sigwaitinfo( ) peut recevoir le signal 19 (STOP) et en renvoyer tout simplment le numéro sans s'arrêter. Il s'agit d'un bogue dans les noyaux Linux jusqu'au 2.2.14. Le noyau «oublie» de supprimer SIGKILL et SIGSTOP de l'ensemble des signaux attendus.

À cause de ce second problème, il est important d'utiliser les codes suivants avant l'appel de sigwaitinfo( ):

sigdelset (& ensemble, SIGKILL) ; sigdelset (& ensemble, SIGSTOP) ;

Si on ne prend pas ces précautions, on risque d'obtenir un processus impossible à tuer sur les noyaux bogués. Dans l'exemple suivant, nous allons justement nous mettre dans cette situation. Par contre, nous utiliserons la temporisation de sigtimedwait( ) pour mettre fin au processus.

exemple_sigtimedwait.c #include <stdio.h>

#include <stdlib.h>

#include <signal.h>

#include <unistd.h>

int main (void) {

sigset_t ensemble;

int numero; struct timespec delai;

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

/* Blocage de tous les signaux */

/* Attente de tous les signaux pendant 10 secondes */

delai . tv_sec = 10;

delai . tv_nsec = 0;

sigfillset (& ensemble);

while (1) {

if ((numero = sigtimedwait (& ensemble, NULL, & delai)) < 0) { perror ("sigtimedwait");

break;

}

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

}

return (0);

}

L'exécution suivante, réalisée sur un noyau Linux 2.2.12, fait un peu froid dans le dos !

$ ./exemple_sigtimedwait PID=1452

$ kill -KILL 1452 sigtimedwait : 9 reçu

$ kill -STOP 1452 sigtimedwait : 19 reçu

$ kill -KILL 1452 sigtimedwait : 9 reçu

$ kill -STOP 1452 sigtimedwait : 19 reçu

sigtimedwait: Ressource temporairement non disponible

$

Le processus reste imperturbable devant les « kill -9 » en série 1... Lorsque le délai est écoulé, sigtimedwait( ) échoue avec l'erreur EAGAIN. dont le libellé est assez mal choisi.

Notons que si on remplit la structure timespec transmise à sigtimedwait( ) avec des valeurs nulles, l'appel-système revient immédiatement, en renvoyant le numéro d'un signal en attente ou -1, et une erreur EAGAIN si aucun n'est disponible. Cela permet de bloquer la délivrance asynchrone des signaux pendant des passages critiques d'un logiciel et de les recevoir uniquement quand les circonstances le permettent.

Conclusion

Nous avons examiné dans ce chapitre une extension importante des méthodes de traitement des signaux. Ces fonctionnalités temps-réel Posix. lb ajoutent une dimension considérable aux capacités de Linux à traiter des problèmes industriels ou scientifiques avec des délais critiques.

La norme Posix.1b, quand elle s'appelait encore Posix.4, a été étudiée en détail dans [GALLMEISTER 1995] Posix.4 Prograrming for the real world.

En fait lorsque j'ai présenté ce problème dans la liste de développement de Linux.

9

Sommeil des

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