chapitre 17. Signaux - Département Informatique
Transcription
chapitre 17. Signaux - Département Informatique
UNSA UFR Sciences Université de Nice Sophia-Antipolis Département Informatique Licence d’informatique et Maîtrise EEA – Module L3I6 Cours Système et Réseaux Roger Rousseau CNRS-UNSA/I3S Version 4.1 Janvier 2006 17. SIGNAUX 8.0 Sommaire Chapitre 17 Communication par signaux suite(1) 6) Signification des signaux 7) Autres primitives de signalements : raise, abort 1) Communication synchrone vs asynchrone 8) Réveils avec horloge (timers) : setitimer, getitimer, alarm... 2) Concept de signal 3) Traitement d’un signal signal 4) Signalement explicite : kill 9) Mise en attente d’un processus : attente active, pause, sleep, nanosleep 5) Etats, description d’un signal 545 17. SIGNAUX 8.0 Sommaire 546 17. SIGNAUX 8.1 Communication synchrone vs asynchrone Communication synchrone vs asynchrone suite(2) 10) Primitives POSIX fiables : sigaddset, sigaction, sigprocmask, sigpending, sigsuspend Deux mécanismes fondamentaux de communication entre processus : ✱ synchrone, plus facile à programmer, ✱ asynchrone, plus délicate à programmer. 547 17. SIGNAUX 8.1 Communication synchrone vs asynchrone 548 17. SIGNAUX Communication synchrone 8.1 Communication synchrone vs asynchrone Communication synchrone : IPC suite(1) C’est le processus qui décide du moment où il souhaite communiquer, par un appel système. Les IPC (Interprocess Communication) sont des mécanismes de communication synchrone, plus faciles à utiliser. Dans les systèmes U NIX on dispose des IPC : => type boîte à lettres Cela peut conduire à une mise en attente si l’événement attendu ne s’est pas produit : (blocage plus ou moins long). Remarque : au niveau microscopique, il n’y a que des communications synchrones : par exemple, une interruption au niveau du cycle d’exécution des instructions machine. 549 ✱ sans partage de mémoire, dans une structure de type flot de caractères ou de messages : ✱ avec partage de mémoire : segment de mémoire partagée, sémaphores. 550 Communication asynchrone(événementielle) avec signaux IPC sans partage de mémoire entre processus d’une même machine : ✱ • tube (volatile, uni- ou bi-directionnel), Lorsque un événement arrive, le système force le processus à exécuter un code spécial : une routine, un traitant. • FIFO (idem, mais persistants), • file de messages, avec priorité (SVR4), entre processus sur le réseau : ✱ Le déroutement se produit (s’il est autorisé par le processus) à un moment arbitraire de l’exécution du processus. • socket : divers protocoles, messages, (BSD) Rappel: les “IPC système V” : file de messages, sémaphore, segments de mémoire partagée. => type appel téléphonique 551 17. SIGNAUX 8.1 Communication synchrone vs asynchrone Communication asynchrone le noyau et le matériel : ✱ les processus et le noyau : signal, les différentes parties d’un programme : ✱ 8.1 Communication synchrone vs asynchrone Communication asynchrone interruption, l’utilisateur et un serveur graphique : ✱ 17. SIGNAUX suite(1) Dans les systèmes, les communications asynchrones se retrouvent à plusieurs niveaux de dialogues entre : ✱ 552 événement, suite(2) La programmation événementielle est très délicate et source d’erreurs : ✱ mauvaise lisibilité des programmes (effet spaghetti), ✱ blocage, perte d’événements, conditions de vitesse, ✱ si ressources partagées, exclusion mutuelle nécessaire, exception, 553 17. SIGNAUX 8.1 Communication synchrone vs asynchrone 554 17. SIGNAUX 8.1 Communication synchrone vs asynchrone Sous-processus implicites Raison des difficultés : sous-processus implicites suite(1) Traitement d’un signal = scheduling implicite Rappel sur le scheduling des processus explicites signal s signal s Scheduler Scheduler P1 P1 P2 return return P3 traitant signal s 556 555 17. SIGNAUX 8.2 Concept de signal 17. SIGNAUX Concept de signal Liste alphabétique des signaux Sous U NIX, un signal est un événement émis à un processus ou à un groupe de processus bien identifiés qui : ✱ a nom (une constante entière) connu du système, ✱ a un traitant par défaut ou défini par une primitive système : • signal (simple, mais rustique), ou mieux par • sigaction (plus complet). ✱ 8.2 Concept de signal peut être masqué ou non, signal par signal, Selon un masque de signaux propre à chaque processus. 557 (cf. signification précise page ??) nom SIGABRT SIGALRM SIGBUS SIGCHLD SIGCONT SIGEMT SIGFPE SIGHUP SIGILL SIGINFO SIGINT nom complet abort alarm bus error child continue emulator trap floating point except. hangup illegal instruction information request interrupt catégorie terminaison réveil par timer erreur détectée par le matériel gestion des travaux (cf. chap. 11) gestion des travaux (cf. chap. 11) obsolète (PDP11) erreur détectée par le matériel terminaison erreur détectée par le matériel gestion des travaux (cf. chap. 11) terminaison 558 Liste alphabétique des signaux nom SIGIO SIGIOT SIGKILL SIGPIPE SIGPOLL SIGPROF SIGPWR SIGQUIT SIGSEGV SIGSTOP SIGSYS SIGTERM nom complet I/O termination I/O trap kill pipe (or socket) pollable event profiling power quit segmentation violation stop system call termination Liste alphabétique des signaux suite(1) catégorie événement lié aux périphériques obsolète (PDP11) terminaison événement lié aux périphériques événement lié aux périphériques réveil par timer événement lié aux périphériques terminaison erreur détectée par le matériel gestion des travaux (cf. chap. 11) erreur signalée par le système terminaison nom SIGTRAP SIGTSTP SIGTTIN SIGTTOU SIGURG SIGUSR1 SIGUSR2 SIGVTALRM SIGWINCH SIGXCPU SIGXFSZ nom complet trap instruction terminal stop terminal in error terminal out error urgent user-defined signal 1 user-defined signal 2 virtual time alarm window changed cpu time file size suite(2) catégorie obsolète (PDP11) gestion des travaux (cf. chap. 11) gestion des travaux (cf. chap. 11) gestion des travaux (cf. chap. 11) événement lié aux périphériques signal logique signal logique réveil par timer événement lié aux périphériques erreur signalée par le système erreur signalée par le système 559 17. SIGNAUX 8.3 Traitement d’un signal 560 17. SIGNAUX Traitement d’un signal 8.3 Traitement d’un signal Définition du traitement d’un signal mécanisme C- ANSI simplifié : signal (3) ✱ ✱ le traitement d’un signal peut consister : • à l’ignorer, • à utiliser un traitement par défaut, Syntaxe : #inlude <signal.h> void (* signal (int sig, void (* handlingr )(int)))(int); /* Ou plus lisiblement : */ • à appeler une procédure du processus appelée traitant (en anglais handler, ou “signal catching function”). typedef void (*handler_t)(int); handler_t signal (int sig, handler_t handling ); ✱ Retour : • OK : traitement précédent, • erreur : SIG_ERR 561 17. SIGNAUX 8.3 Traitement d’un signal primitive signal (3) ✱ 562 17. SIGNAUX 8.3 Traitement d’un signal Remarques sur signal (3) suite(1) Sémantique : • associe au signal sig le traitement handling : une constante prédéfinie dans signal.h ou nom d’une procédure du processus. ⊲ SIG_IGN : ignorer le signal ; ⊲ SIG_DFL : reprendre le traitement par défaut (cf. page ??). ⊲ procédure t : exécuter t lors du prochain signal sig, avec sig en argument. Cela permet d’associer la même procédure à plusieurs signaux. ✱ signal est un nom mal choisi : on ne signale rien. ✱ L’association peut être permanente ou valable une seule fois : • sous BSD, association permanente jusqu’au prochain appel à signal ; • sous U NIX v7 et L INUX, association éphémère, on reprend le traitement par défaut à chaque traitement du signal. Il faut donc réexécuter signal dans le traitant. 563 17. SIGNAUX 8.3 Traitement d’un signal Remarques sur signal (3) 564 17. SIGNAUX 8.3 Traitement d’un signal Exemple d’utilisation de signal (3) suite(1) ✱ signal(3) existait sous U NIX v7, est dans la norme C- ANSI, mais n’est pas fiable : on ne peut masquer ou démasquer les signaux en même temps, condition de vitesse ou pertes de signaux possibles. ✱ aujourd’hui signal(3) est implémentée par sigaction (cf. page ??). ✱ on peut cependant encore utiliser signal(3) dans les cas simples, assez fréquents. 565 stati void sig_usr (int signo){ /* traitant ommun pour USR1 et USR2 */ if (signo == SIGUSR1) printf("SIGUSR1 reçu\n"); else if (signo == SIGUSR2) printf("SIGUSR2 reçu\n"); else err_dump("signal inattendu %d\n", signo); return; } Remarque : L’impression par printf dans un traitant est déconseillée (primitive non réentrante). 566 Exemple d’utilisation de signal (3) Entrée dans un traitant suite(1) ... if (signal (SIGUSR1, sig_usr) == SIG_ERR) ERR; if (signal (SIGUSR2, sig_usr) == SIG_ERR) ERR; ✱ /* Travail du processus plus ou moins long... dès l’entrée dans le traitant, certains signaux doivent souvent être masqués (cf. système fiable vs non fiable, page ??), en particulier celui qui a été émis. * ou mise en attente : pause, sigsuspend, wait... * Ce code peut être interrompue par un signal : Le traitant peut alors : * USR1, USR2 => exécution du traitant et retour ✱ * INT, QUIT => terminaison par défaut */ ... connaître le signal (argument sig) et, sur option, d’autres informations (identité de l’émetteur..., cf. strut siginfo, page ??); 567 17. SIGNAUX 8.3 Traitement d’un signal Entrée dans un traitant ✱ 568 17. SIGNAUX 8.3 Traitement d’un signal Sortie d’un traitant suite(1) analyser la cause par d’autres moyens plus précis (appels systèmes, variable errno, variables globales...) et communiquer de manière synchrone avec d’autre processus, immédiatement ou plus tard s’il est pressé... ✱ masquer ou démasquer les signaux de son choix, ✱ changer le traitement associé à son signal : signal, sigaction. ✱ choisir un point de retour (cf. ci-après). À la fin de son traitement, un traitant de signal a trois possibilités de débranchement : 1) par exit(3) ou _exit(2) : abandon du travail ; 2) par return : reprise au point d’interruption (cas le plus fréquent) ; 3) par setjmp / longjmp (cf. chap. 5, page 285), ou leur variante sigsetjmp / siglongjmp (cf. page ??) : propagation de l’événement, par débranchement dans un gestionnaire spécialisé. 569 17. SIGNAUX 8.3 Traitement d’un signal Sortie d’un traitant 570 17. SIGNAUX 8.3 Traitement d’un signal Sortie d’un traitant par _exit (2) / exit (3) suite(1) Noyau du système Noyau du système t (s) + masquage s t (s) + masquage s return + démasquage s 1 main () { signal (s, t); manager(); 2 traitant t 1 void t (int s){ 2 2 traitant t main () { exécution du traitant pour le signal s void t (int s){ signal (s, t); exécution du traitant pour le signal s 2 exit (0) signal s } manager () { setjmp(e) } exit (1) return exit (0) 3 signal s } } } 3 exit (1) 3 sorties longjmp (e) possibles Espace mémoire du processus Espace mémoire du processus 571 17. SIGNAUX 8.3 Traitement d’un signal Sortie d’un traitant par return 572 17. SIGNAUX 8.3 Traitement d’un signal Sortie d’un traitant par return suite(1) Noyau du système La terminaison du traitant retourne dans le système qui réalise le débranchement au point d’interruption du signal. ✱ ordinaire : l’exécution se poursuit avec démasquage du signal émis. Cela peut provoquer un retour dans le traitant, si ce signal était en suspens. un appel système ... + démasquage s 1 Deux cas à distinguer, selon que l’instruction interrompue est: ✱ return t (s) + masquage s main () { signal (s, t); 2 traitant t void t (int s){ exécution du traitant pour le signal s 2 exit (0) signal s } 3 return } Espace mémoire du processus 573 574 Sortie d’un traitant par return Sortie d’un traitant à un endroit spécifique suite(2) Lorsqu’un appel système est interrompu, il est (au choix) : ✱ automatiquement relancé : l’appel se terminera comme s’il n’avait pas été interrompu. ✱ ou mis en échec avec CR == -1 et errno == EINTR. L’exécution se poursuit avec un démasquage du signal émis, sauf pour sigsuspend (cf. page ??) où le masque est rétabli comme il était au moment de l’appel à sigsuspend. Cela permet de se débrancher à un gestionnaire qui propage l’événement reçu à un module (ou classe) qui est “hiérarchiquement responsable” ✱ par setjmp et longjmp, sans modification du masque des signaux en cours, à la fin du traitant, ✱ (cf. page ?? pour une discussion complète sur ce phénomène). ou par les variantes sigsetjmp / siglongjmp , pour une restauration du masque des signaux dans l’état qu’il avait au moment de sigsetjmp. 575 576 17. SIGNAUX 8.3 Traitement d’un signal 17. SIGNAUX 8.4 Signalement explicite Envoi d’un signal par kill (2) Sortie d’un traitant à un endroit spécifique suite(1) Noyau du système ✱ t (s) + masquage s int kill (pid_t pid, int signo ); 1 main () { signal (s, t); 2 if (setjmp(e)==0){ travail ( ); exit (0) } else { traitement du problème exit (1); } travail ( ) { 2 signal s } return ; Syntaxe : #inlude <sys/types.h> #inlude <signal.h> ✱ Retour : • 0, si OK , • -1, si erreur : le(s) processus n’existe(nt) pas (plus) ✱ Sémantique : • émet le signal de nom signo au(x) processus spécifié(s) par pid. On définit signo par une constante SIGXXXX plutôt qu’un entier. traitant t void t (int s){ exécution du traitant pour le signal s longjmp (e, k) 3 } setjmp (env) longjmp(env, int) Espace mémoire du processus 578 577 17. SIGNAUX 8.4 Signalement explicite kill (2) ✱ 17. SIGNAUX 8.4 Signalement explicite Restrictions pour kill (2) suite(1) Selon pid, le signal est émis à : • pid > 0 : au processus de pid donné ; • pid == 0 : à tous les processus du groupe de l’émetteur ; ✱ Si le processus émetteur est non SU , son RUID ou EUID doit être le RUID ou SUID du processus receveur. ✱ L’émetteur peut envoyer un signal aux processus de la même session (à ses fils, même s’ils ont un EUID différent) ou à des processus d’une autre session, mais appartenant au même utilisateur. ✱ L’émetteur ne peut envoyer un signal à un processus d’un autre utilisateur. => on ne peut pas utiliser des signaux pour une architecture client-serveur ! • pid < 0 : à tous les processus du groupe du processus | pid | ; • pid == -1 : à tous les processus (sauf l’émetteur) du propriétaire de l’émetteur. Si l’émetteur est SU , c’est une diffusion (broadcasting) à tous les processus (sauf l’emetteur, e.g. shutdown). 579 17. SIGNAUX 8.4 Signalement explicite Restrictions pour kill 580 17. SIGNAUX 8.4 Signalement explicite Restrictions pour kill suite(1) suite(2) ✱ Si le processus émetteur est SU , il peut émettre un signal à n’importe quel autre processus. ✱ Le signal de numéro 0 n’envoit rien, mais sert à tester l’existence de processus. ✱ kill peut émettre n’importe quel signal, même ceux émis normalement par le matériel ou le système : utile pour les tests. ✱ ✱ SIGCONT ne peut être émis qu’à des processus de la même session. La commande kill(1) ne fait qu’appeler kill ; elle sert à envoyer n’importe quel signal à un ou plusieurs processus, généralement pour les tuer... (d’où ce nom mal choisi !). 581 582 États d’un signal États d’un signal suite(1) Pour un processus donné, chaque signal peut être dans les états suivants (non exclusifs) : ✱ associé à un traitement : par défaut ou choisi (par signal ou sigaction) ; ✱ masqué (ou bloqué) ou démasqué (débloqué) : traitable ou non par le processus ; ✱ émis (ou envoyé) à un processus : lorsque l’événement se manifeste ; cela est noté par le noyau dans le descripteur du processus ; ✱ en attente : émis, non masqué, pas encore traité ; ✱ en suspens (pending) : émis et masqué, donc pas encore traité. ✱ notifié : délivré à un traitant, en cours de traitement particulier, ✱ traité : ignoré, traité par défaut ou par un traitant particulier; 583 17. SIGNAUX 8.5 États d’un signal Descripteurs associés aux signaux 584 17. SIGNAUX Descripteurs associés aux signaux suite(1) champs divers du processus : Champs pour les signaux dans le descripteur d’un processus : ✱ 8.5 États d’un signal pid, ppid, group, files... descripteur de processus ... ITIMER_REAL ITIMER_VIRTUAL un tableau des signaux émis : (en attente ou en suspens) un tableau de bits ou de files de signaux; l’émission d’un signal i met son bit à 1, son traitement le remet à 0. ITIMER_PROF signaux émis masque table des traitements sighup sig int sigkill ✱ ✱ un masque de signaux : un ensemble des signaux masqués, e.g. un tableau de bits; sigalrm sigterm ... ... ... un tableau des traitements des signaux : e.g. des constantes ou des adresses des traitants. 585 17. SIGNAUX 8.5 États d’un signal 586 17. SIGNAUX 8.5 États d’un signal Notification d’un signal Émission d’un signal L’émetteur d’un signal (cf. page ??) peut être : ✱ le système lui-même, ✱ un autre processus par kill(2) ou la commande kill(1), ✱ le matériel, ✱ une horloge (timer), ✱ une touche du terminal de contrôle (TC , cf. chap. 11). La notification d’un signal à un processus (mise à jour de son descripteur) est toujours faite par le système, qui peut recevoir des interruptions du matériel ou des appels système (kill). Le système peut diffuser un signal à plusieurs processus (groupes) par une itération de son émission. 587 17. SIGNAUX 8.5 États d’un signal Cause d’un signal normale (changement de taille de fenêtre...) ✱ anormale (division par 0...) 17. SIGNAUX 8.5 États d’un signal Mémorisation des signaux La plupart des systèmes U NIX ne mémorisent au plus qu’une occurrence pour chaque numéro de signal en suspens : tableau de bits, pas un tableau de files ! (si un signal est émis plusieurs fois avant d’être traité, il ne sera notifié qu’une seule fois). La cause d’un signal peut être : ✱ 588 Cela explique les traitements par défaut (cf. page ??). Un usage fréquent des signaux est d’arrêter, stopper ou faire repartir un processus... Lorsqu’un processus a plusieurs signaux différents en attente, le choix d’un signal à traiter par le noyau est indéfini : pas de priorité entre les signaux. 589 590 Mémorisation des signaux Précautions à prendre dans les traitants de signaux suite(1) Lorsqu’un traitant est exécuté, il faut prendre garde à : Sauf si le système est très chargé, un signal ne reste pas longtemps en attente : il est notifié dès que possible par le céduleur. ✱ ce qu’il ne soit pas interrompu par le même numéro de signal envoyé immédiatement après, ou par un autre signal indésirable. ✱ ne pas appeler de fonctions non réentrantes (cf. plus loin) qui pourraient être actives au moment du déroutement dans le traitant : primitive système ou de bibliothèque, fonction propre au processus. ✱ à ne pas perdre de signaux : ignorer involontairement un signal émis. Un signal reste en suspens pour un processus, tant que ce processus existe et qu’il ne l’a pas démasqué ou ignoré. Un processus peut changer le traitement associé à des signaux en suspens : l’ignorer, revenir à son traitement par défaut, changer son traitement. 591 17. SIGNAUX 8.6 Signification des signaux 592 17. SIGNAUX Signification des signaux Signification des signaux Signaux de terminaison : Nom SIGKILL SIGTERM SIGINT action par déf. termine termine termine SIGABRT termine + core SIGQUIT termine + core 8.6 Signification des signaux suite(1) Signaux de terminaison : Effet terminaison immédiate et autoritaire demande de terminaison demande de terminaison par touche, e.g. trl demande de terminaison + core, cf. abort(3) demande de terminaison + core, e.g. trl \ Nom SIGHUP action par déf. Effet termine •déconnexion de la ligne d’accès : envoi au leader de session (option ioctl) ; •terminaison du leader de session : envoyé à ses fils ; •envoi à un démon (pas de TC ) : demande de relecture du ficher de configuration. 593 17. SIGNAUX 8.6 Signification des signaux Signification des signaux 594 17. SIGNAUX 8.6 Signification des signaux Signification des signaux suite(2) suite(3) Gestion des travaux (job system), cf. chap. 11 : Nom SIGSTOP SIGTSTP trl z Réveils par timer : cf. alarm, setitimer, getitimer : Nom SIGALRM SIGVTALRM SIGPROF action par déf. termine termine termine action par déf. Effet stop stop autoritaire stop demande stop par clavier, SIGCONT SIGCHLD Effet temps réel de la montre (µsec) temps cpu du processus (µsec) temps cpu pour profilage (µsec) SIGTTIN SIGTTOU SIGINFO continue/ignore redémarrage après stop ignore terminaison, stop ou redémarrage d’un fils stop tentative de lire le TC en background stop tentative d’écrire sur le TC en background ignore demande de statut par clavier (BSD), e.g. trl t 595 17. SIGNAUX 8.6 Signification des signaux Signification des signaux e.g. 596 17. SIGNAUX 8.6 Signification des signaux Signification des signaux suite(4) suite(5) Erreurs signalées par le système : Nom SIGSYS SIGXCPU SIGXFSZ Signaux logiques (utilisateur ou programmes) : action par déf. Effet termine + core argument erroné, appel système inconnu termine + core limite de temps dépassée cf. getrlimit termine + core limite de taille fichier dépassée cf. getrlimit Nom SIGUSR1 SIGUSR2 597 action par déf. Effet termine signal logique n◦1 termine signal logiciel n◦2 598 Signification des signaux Signification des signaux suite(6) suite(7) Événements liés aux périphériques : Nom SIGPIPE Erreurs détectées par le matériel : action par déf. Effet termine tentative d’écriture avec personne pour lire termine/ignore fin d’E/S asynchrone (BSD), cf. fcntl termine événement d’E/S sélectionnable (SVR4), cf. poll ignore condition urgente sur socket ignore changement taille fenêtre, cf. termio(4), win(4S) ignore coupure de courant électrique (SVR4) SIGIO SIGPOLL SIGURG SIGWINCH SIGPWR Nom SIGBUS SIGSEGV SIGFPE action par déf. termine + core termine + core termine + core SIGILL termine + core Effet indéterminée ( vient du PDP-11) adressage arithmétique entière ou flottante : div. par 0, sur- ou sousdébordement... instruction machine inconnue (possible en assembleur) 599 17. SIGNAUX 8.6 Signification des signaux Signification des signaux 600 17. SIGNAUX 8.6 Signification des signaux Signaux : émetteur et normes suite(8) Nom SIGABRT SIGALRM SIGBUS SIGCHLD SIGCONT SIGEMT SIGFPE SIGHUP SIGILL SIGINFO SIGINT SIGIO Signaux obsolètes (origine du PDP-11) : Nom SIGEMT SIGIOT SIGTRAP action par déf. termine + core termine + core termine + core Effet émulateur d’instructions (flottant...) trappe d’E/S instruction trappe Emetteur Présent dans norme d’origine C ANSI POSIX SVR4 BSD4.4 kill • • • • timer • • • hard • • syst • • • kill • • • hard • • hard • • • • TC • • • hard • • • • TC • TC • • • • hard • • 601 17. SIGNAUX 8.6 Signification des signaux Signaux : émetteur et normes Nom SIGIOT SIGKILL SIGPIPE SIGPOLL SIGPROF SIGPWR SIGQUIT SIGSEGV SIGSTOP SIGSYS SIGTERM 602 17. SIGNAUX 8.6 Signification des signaux Signaux : émetteur et normes suite(1) suite(2) Nom Emetteur Présent dans norme d’origine C ANSI POSIX SVR4 BSD4.4 SIGTRAP hard • • SIGTSTP TC • • • SIGTTIN TC • • • SIGTTOU TC • • • SIGURG hard • • SIGUSR1 kill • • • SIGUSR2 kill • • • SIGVTALRM timer • • SIGWINCH hard • • SIGXCPU syst • • SIGXFSZ syst • • Emetteur Présent dans norme d’origine C ANSI POSIX SVR4 BSD4.4 hard • • kill • • • syst • • • hard • timer • • hard • TC • • • hard • • • • kill • • • syst • • kill • • • • Dans ce cours, ne retenir que les signaux POSIX 603 17. SIGNAUX 8.6 Signification des signaux Connaître le nom des signaux : sys_siglist, psignal (3) ✱ 604 17. SIGNAUX 8.6 Signification des signaux Connaître le nom des signaux ✱ Syntaxe : #inlude <signal.h> extern har* sys_siglist [℄; extern int sys_nsig ; suite(1) Sémantique : • sys_siglist : tableau de noms de signaux, indexé par les numéros de signaux. Cela permet d’afficher des messages indiquant l’identité en clair des signaux reçus. • sys_nsig : nombre de signaux définis sur le système : 48 sur OSF1, 45 sur Solaris, 32 sur Linux... void psignal (int signo, onst har* msg ){ printf ( "%s: %s\n", mess, sys_siglist[signo℄); } • psignal : fonction analogue à perror . • Ces facilités ne sont pas dans la norme POSIX : cf. signal.h pour connaître ce qui est disponible. 605 606 Envoi d’un signal au processus lui-même : raise (3) ✱ Syntaxe : #inlude <sys/types.h> #inlude <signal.h> int raise (int signo); ✱ Retour : • 0, si OK , • -1, si erreur ✱ Sémantique : • émet le signal signo au processus lui-même. donc équivalent à kill (getpid(), signo); • primitive C- ANSI : MSDOS (monoprocessus)... Envoi du signal SIGABRT : abort (3) ✱ Syntaxe : #inlude <stdlib.h> void abort (void); ✱ Retour : pas de retour : terminaison, ✱ Sémantique : • émet le signal SIGABRT au processus lui-même. donc équivalent à : kill (getpid(), SIGABRT); • SIGABRT écrit un dump mémoire dans le fichier core pour les débogueurs comme adb, dbx, gdb... • un traitant de SIGABRT ne doit pas se terminer par return ; utiliser : exit(3), longjmp(3), siglongjmp(3) 607 17. SIGNAUX 8.8 Réveils avec horloge 608 17. SIGNAUX Réveils avec horloge (timer) 8.8 Réveils avec horloge Réveils avec horloge (timer) suite(1) ✱ ITIMER_REAL : correspond au temps réel externe (montre) et émet le signal SIGALRM. ✱ ITIMER_VIRTUAL : correspond au temps virtuel du processus seul (temps CPU) et émet le signal SIGVTALRM. ✱ Lorsqu’un compteur atteint la valeur 0 au bout d’un intervalle de temps, le système émet un signal propre à chaque compteur... ITIMER_PROF : correspond au temps virtuel complet (temps CPU du processus + des appels systèmes du processus) et émet le signal SIGPROF. Ce signal est utilisé, avec SIGVTALRM, principalement par les outils de profilage par échantillonnage temporel, qui estiment le temps passé dans les différentes parties d’un programme. 609 610 Chaque processus dispose de 3 horloges de réveil distinctes, implémentées par des compteurs qui se décrémentent avec une fréquence maximum de une µ-seconde (dans les premiers système, une seconde). 17. SIGNAUX 8.8 Réveils avec horloge Structure d’une horloge de réveil : struct itimerval ✱ 17. SIGNAUX 8.8 Réveils avec horloge Structure d’une horloge de réveil ✱ Syntaxe : #inlude <sys/time.h> strut itimerval { strut timeval it_interval ; /* fréquence */ strut timeval it_value ; /* valeur restante */ } strut timeval { long tv_se; /* secondes */ long tv_use; /* µ-secondes */ }; suite(1) Sémantique : • Chaque timer est une strut itimerval qui contient deux compteurs : ⊲ it_interval : l’intervalle de temps demandé lors du dernier setitimer (par défaut 0). ⊲ it_value : l’intervalle de temps restant avant la prochaine émission du signal. • Le timer décrémente it_value et émet un signal lorsque cette valeur atteint zéro. • La valeur de it_value est alors réinitialisée avec it_interval. 611 17. SIGNAUX 8.8 Réveils avec horloge Structure d’une horloge de réveil ✱ 612 17. SIGNAUX 8.8 Réveils avec horloge Structure d’une horloge de réveil suite(2) Sémantique : (suite) • Un timer est actif (génère des signaux) tant que son champ it_interval indique un temps non nul ; il est inactif (stoppé) sinon. • Les signaux ne sont jamais émis avant les délais demandés. Ils sont émis après le délai demandé + une constante de réflexe dépendant du système (de l’ordre de 10ms). 613 ✱ suite(3) Sémantique : (suite) • Lorsque le processus est actif et que le délai d’un timer expire : ⊲ le signal SIGVTALRM est émis immédiatement, ⊲ après un court délai dépendant de la charge du système pour les signaux SIGALRM et SIGPROF. 614 setitimer Armement d’une horloge de réveil : suite(1) setitimer (2) ✱ ✱ ✱ Syntaxe : #inlude <sys/time.h> int setitimer (int whih, onst strut itimerval* value, strut strut itimerval* value ); }; Sémantique : • which indique, par une constante symbolique ITIMER_REAL, ITIMER_VIRTUAL ou ITIMER_PROF, quel compteur est initialisé. • value indique l’intervalle de temps demandé (seul le champ it_interval est nécessaire); Retour : • 0, si OK , • -1, si erreur • ovalue, s’il est non NULL, reçoit la valeur courante du timer avant la réinitialisation. 615 17. SIGNAUX 8.8 Réveils avec horloge 616 17. SIGNAUX 8.8 Réveils avec horloge getitimer Consultation d’une horloge de réveil : suite(1) getitimer (2) ✱ ✱ Syntaxe : #inlude <sys/time.h> int getitimer (int whih, strut itimerval* value ); }; ✱ Sémantique : • which indique, par une constante symbolique ITIMER_REAL, ITIMER_VIRTUAL ou ITIMER_PROF, quel compteur est initialisé. • value récupère les deux compteurs du timer demandé, avec leurs valeurs actuelles : ⊲ it_interval : période actuelle d’émission du signal, Retour : • 0, si OK , • -1, si erreur ⊲ it_interval : durée restante avant le prochain signal, 0 si le timer est inactif. 617 17. SIGNAUX 8.8 Réveils avec horloge 618 17. SIGNAUX 8.8 Réveils avec horloge alarm (3) Armement simplifié de l’horloge de réveil : suite(1) alarm (3) ✱ Syntaxe : #inlude <unistd.h> unsigned int alarm ( unsigned int seonds ); ✱ Retour : • nb de sec. restantes, avant prochain SIGALRM., • 0, si l’horloge ITIMER_REAL est inactive. ✱ ✱ Sémantique : • Si seconds = 0, ITIMER_REAL est désactivé ; • alarm(3) est implémenté avec setitimer qui modifie ITIMER_REAL ; • alarm(3) existait dès U NIX v7 ; elle reste d’actualité (POSIX), par sa grande simplicité ; Sémantique : • Initialise ITIMER_REAL avec le temps en sec ; • Bien que la précision soit grossière (seconde), cette primitive reste réaliste pour les systèmes chargés. Remarque : la nouvelle primitive ualarm(3) tient compte de la vitesse des processeurs actuels (micro-secondes). 619 17. SIGNAUX 8.9 Mise en attente d’un processus 620 17. SIGNAUX Mise en attente d’un processus 8.9 Mise en attente d’un processus Mise en attente d’un processus Pour sa logique propre, un processus doit souvent, pour se synchroniser, attendre un événement externe : ✱ la terminaison d’un processus fils : wait, waitpid... ; ✱ une fin d’E/S : les primitives sont souvent blocantes, avec attente automatique de la fin ; suite(1) ✱ un intervalle de temps déterminé : nanosleep(3), sleep(3) ; ✱ un ou plusieurs événements quelconques signalés par un signal, par une attente volontaire : • active, infinie ou bornée, par l’exécution d’instructions inoffensives, il existe une possibilité d’E/S non bloccantes, asynchrones, qui en- • passive, par un appel système : pause(3), sigsuspend, voient un signal SIGIO à la fin de l’E/S. • passive bornée : sleep(3), nanosleep(3). Remarque : 621 622 Attente volontaire active, indéfinie Attente volontaire active indéfinie suite(1) Réalisée par une boucle « infinie », de durée indéfinie, sans effet sur le programme. Attente indéfinie : stati jmp_buf env; ... if (setjmp (env) == 0) { Inconvénients : /* Mise en attente active */ for ( ; ; ) ; ✱ très mauvaise utilisation du processeur de calcul, ✱ programmation compliquée. } /* reprise après un signal dont le traitant * se termine par longjmp(env) */ ... 623 17. SIGNAUX 8.9 Mise en attente d’un processus 624 17. SIGNAUX Attente volontaire active bornée 8.9 Mise en attente d’un processus Attente volontaire active bornée suite(1) Attente limitée correcte Attente limitée erronée : fenêtre de temps stati jmp_buf env; stati int i; ... if (setjmp (env)==0) {/* mise en attente ative */ for (i= 200000000 ; i > 0 ; i--) ; } stati int i; /* Mise en attente ative */ for (i= 200000000 ; i > 0 ; i--) ; /* reprise après un signal dont le traitant * se termine par i=0; /* reprise après un signal dont le traitant * => fenêtre de temps ! */ * se termine par longjmp(env) */ ... ... 625 17. SIGNAUX 8.9 Mise en attente d’un processus Attente volontaire active bornée programmation compliquée, ✱ durée limitée mais liée au processeur. 17. SIGNAUX 8.9 Mise en attente d’un processus Attente volontaire passive : pause (3) suite(2) Inconvénients : ✱ très mauvaise utilisation du processeur de calcul, ✱ 626 ✱ Syntaxe : #inlude <unistd.h> int pause (void); ✱ Retour : • Jamais OK, appel interrompu (cf. page ??), • -1, si erreur, avec errno == EINTR ✱ Sémantique : • Le noyau laisse le processus oisif (idle), tant qu’il ne reçoit pas un signal ; cela ne consomme pas de CPU pour le processus. 627 17. SIGNAUX 8.9 Mise en attente d’un processus pause (3) ✱ 628 17. SIGNAUX 8.9 Mise en attente d’un processus pause (3) suite(1) suite(2) Sémantique (suite) : • Le processeur peut être utilisé pour d’autres processus... • À la réception d’un signal, 2 cas possibles, selon son traitement : ⊲ traitant qui se termine par return : poursuite du traitement, après la primitive pause. CR == -1, errno == EINTR. ✱ Avantages : bonne utilisation du CPU, programmation facile, mais non sans dangers (cf. pages ??). ✱ Inconvénients : ne masque pas les signaux en même temps contrairement à sigsuspend (cf. page ??). ⊲ traitement par défaut qui termine ou traitant qui exécute exit : le processus se termine. 629 630 sleep (3) Attente volontaire passive bornée : sleep (3) ✱ ✱ Syntaxe : #inlude <unistd.h> unsigned int sleep (unsigned int seonds )) ; ✱ Retour : • 0 réveil par temps épuisé , • >0 réveil par signal, nb de sec. qui restent ✱ Sémantique : • attente comme pause, mais pour une durée limitée en secondes. suite(1) Sémantique (suite) : • réveil obtenu par le premier des deux : ⊲ le temps demandé est écoulé, ⊲ un signal a été émis au processus. Dans ce cas, il est traité comme pause, avec un retour juste après sleep (traitant qui retourne) ou une terminaison. • sleep(3) est implémentée avec alarm (cf. p. ??). Remarque : la nouvelle primitive usleep(3) tient compte de la vitesse des processeurs actuel (micro-secondes)... 631 17. SIGNAUX 8.9 Mise en attente d’un processus 632 17. SIGNAUX 8.9 Mise en attente d’un processus nanosleep (3) Attente volontaire précise : nanosleep (3) ✱ ✱ Syntaxe : #inlude <time.h> int nanosleep ( onst strut timespe * req, strut timespe * rem ); strut timespe { time_t tv_se; /* seonds */ long tv_nse; /* nanoseonds */ } ✱ suite(1) Sémantique : • Identique à sleep(3), sauf pour la précision qui descend très en dessous de la sec. : selon la précision de l’horloge, actuellement 1 à 10 ms, mais encore très loin de 1 ns ! • req est la limite de l’attente demandée, • Si rem 6= NULL, la structure pointée reçoit la durée restant à attendre au moment du réveil (0 si temps épuisé). Retour : • 0 : réveil par temps épuisé, • -1 et errno == EINTR : interruption par signal. -1 et errno == EINVAL : req.tv_se aberrant. 633 17. SIGNAUX 8.10 Problèmes avec les signaux 634 17. SIGNAUX 8.10 Problèmes avec les signaux Manque de fiabilité Manque de fiabilité des anciens systèmes Unix des anciens systèmes Unix suite(1) Pour corriger ce défaut, les systèmes BSD et SVR4 ont proposé des primitives aujourd’hui obsolètes : Pour être fiable, un traitant doit disposer d’opérations atomiques, donc avec certains signaux masqués. U NIX version 7 ne disposait que d’une version non fiable de la primitive signal, sans possibilité de masquage. 635 17. SIGNAUX 8.10 Problèmes avec les signaux Fiabilité des systèmes POSIX ✱ SVRX : sigset, sighold, sigrelse, sigignore, sigpause ✱ BSD4.X : sigvec, sigblock, sigsetmask, sigpause, De plus, les comportements de ces systèmes à l’entrée ou à la sortie d’un traitant varient : ✱ maintient de l’association du traitant, ✱ redémarrage automatique des appels système interrompus 636 17. SIGNAUX 8.10 Problèmes avec les signaux Fiabilité des systèmes POSIX suite(1) Aujourd’hui, les systèmes U NIX sont à la norme POSIX. Celle-ci a un mécanisme de signaux fiables, avec masquage possible par quatre primitives (cf. plus page ??) ✱ sigaction : pour associer un traitant à un signal, avec masquage atomique ; ✱ sigprocmask : pour connaître ou modifier le masque ; ✱ sigpending : pour connaître les signaux en suspens ; ✱ sigsuspend : pour mettre le processus en attente avec démasquage atomique. 637 La primitive signal est encore très utilisée à la place de sigaction, pour sa grande simplicité. Mais il faut s’en méfier car toutes les implémentations n’ont pas un comportement identique et fiable : vérifier que le traitant masquera au moins le signal d’appel. 638 Défauts de la primitive signal (3) Défauts de la primitive signal (3) Pour les versions de signal (U NIX v7, Linux...) qui ont une association (signal/traitant) valable une seule fois, il faut souvent rétablir l’association dans le traitant : suite(1) stati sig_int ( int signo ) { /* traitant */ /* Refaire l'assoiation pour prohain signal */ /* B : fenêtre de temps si signo non masqué ! */ signal (SIG_INT, sig_int); ... } ... signal (SIGINT, sig_int); /* A */ ... Remarque : L’appel à signal(3) dans le traitant établissait une fenêtre de temps en U NIX version 7 : le signal SIGINT pouvait être émis à nouveau entre les points A et B. => dans ce cas, le signal était perdu. 639 17. SIGNAUX 8.10 Problèmes avec les signaux 640 17. SIGNAUX Non atomicité avec pause Non atomicité du masquage des signaux avec pause suite(1) ... signal (SIGINT, sig_int); /* A */ ... while ( ! sig_int_flag ){ /* B : fenêtre de temps !! */ pause (); /* C */ ... La version suivante tente de mettre en attente un processus jusqu’à ce qu’un signal arrive : int sig_int_flag ; /* inrémenté par le traitant */ stati void sig_int(int signo){ sig_int_flag ++; } 8.10 Problèmes avec les signaux Le signal attendu peut être notifié au point B. S’il n’est émis qu’une fois, le processus restera bloqué en C. 641 17. SIGNAUX 8.10 Problèmes avec les signaux 642 17. SIGNAUX Non atomicité avec pause : 8.10 Problèmes avec les signaux sleep1, implémentation incorrecte de sleep (3) suite(1) sleep1, implémentation incorrecte de sleep (3) unsigned int sleep1 ( unsigned int nses) { if (signal (SIGALRM, sig_alrm) == SIG_ERR) return(nses); Soit la version naïve suivante de la primitive sleep(3) : alarm (nses); /* démarre le timer */ /* fenêtre de temps A */ void sig_alrm (int signo){ /* rien à faire, juste retourner pour réveiller la pause */ return; } pause (); /* attend le signal SIGALRM */ /* déative le timer et rend son temps restant */ return (alarm (0)); } Le signal SIGALRM peut être émis en A, en blocant le processus... 643 17. SIGNAUX 8.10 Problèmes avec les signaux Non atomicité avec pause : 644 17. SIGNAUX 8.10 Problèmes avec les signaux sleep2, implémentation incorrecte de sleep (3) suite(1) sleep2, implémentation incorrecte de sleep (3) Autre version incorrecte de sleep(3) avec setjmp : stati jmp_buf env_alrm; unsigned int sleep2 ( unsigned int nses ){ if (signal (SIGALRM, sig_alrm) == SIG_ERR) return(nses); if (setjmp (env_alrm) == 0) { alarm (nses); /* démarre le timer */ pause (); /* attend le signal SIGALRM */ } return (alarm (0)); /* déative le timer, rend le temps restant */ } 645 void sig_alrm (int signo){ longjmp (env_alrm, 1); } L’astuce est d’utiliser setjmp / longjmp pour garantir l’absence de blocage, quelles que soient les conditions de vitesse. C’est mieux, mais il y a encore un problème... 646 Exemple d’utilisation de sleep2 sleep2, implémentation incorrecte de sleep (3) : exemple d’utilisation de sleep2 suite(2) stati void sig_int (int signo){ int i; volatile int j; ... car le retour par longjmp dans sleep2 peut faire échouer un autre traitant interrompu par SIGALRM... ... unsigned int unslept; if (signal (SIGINT, sig_int) == SIG_ERR) ERR; unslept = sleep2(5); printf("temps restant: %u\n", unslept); ... suite(3) printf("\nsig_int starting\n"); for (i = 0; i < 2000000; i++) /* attente ative */ j += i * i; printf("fin de sig_int...\n"); /* A */ return; } Ici, la sortie en A est très incertaine... 647 17. SIGNAUX 8.10 Problèmes avec les signaux Schéma d’utilisation de sleep2 648 17. SIGNAUX 8.10 Problèmes avec les signaux Interruption d’un appel système par un signal suite(4) On distingue deux types d’appels systèmes : ✱ rapides ou ininterruptibles : ne peuvent se bloquer que pendant un temps borné et court. Exemple : read sur un fichier disque. ✱ lents ou interruptibles : peuvent être bloqués plusieurs secondes, voire plusieurs jours... Exemple : read sur un terminal, une ligne du réseau, demande d’action nécessitant une intervention humaine... figure à faire : figures/schema-utilisation-sleep2.eps 649 17. SIGNAUX 8.10 Problèmes avec les signaux Interruption d’un appel système 650 17. SIGNAUX 8.10 Problèmes avec les signaux Interruption d’un appel système suite(1) Lorsqu’un signal est émis à un processus bloqué sur un appel système lent, il peut être judicieux d’interrompre le blocage en : ✱ exécutant un traitant associé au signal, ✱ puis en faisant échouer l’appel système interrompu, ✱ puis en reprenant juste après son lancement, si le traitant termine par return (CR == -1, errno == EINTR) ; ✱ ou en terminant le processus, si le traitant termine par exit. suite(2) Programmation explicite de la reprise Cela oblige la programmation des appels systèmes lents à être plus compliquée pour traiter ces cas d’échecs : again: if ((n= read (term, buff, BSIZE)) <0 ) if (errno == EINTR) /* appel syst. interrompu */ goto again; else /* autres erreurs ...*/ ERR; 651 17. SIGNAUX 8.10 Problèmes avec les signaux Interruption d’un appel système 652 17. SIGNAUX 8.10 Problèmes avec les signaux Interruption d’un appel système suite(3) suite(4) Reprise automatique Pour simplifier cette programmation, POSIX permet une reprise automatique des appels systèmes lents, mais sur option à fournir pour chaque signal avec sigaction (cf. page ??). Appels système rapides : ininterruptibles (atomiques) Tous les appels dont la durée d’exécution est bornée, notamment les E/S avec un disque : read, write, open... (blocage éphémère) if ((n= read (term, buff, BSIZE)) <0 ) ERR; 653 654 Interruption d’un appel système Déblocage d’une primitive lente avec time out, sans masquage des signaux suite(5) Appels système lents : interruptibles ✱ read ou write sur un tube, un terminal, un appareil du réseau... Pour se prémunir d’un blocage de lecture du clavier : ... int n; har line[MAXLINE℄; if(signal (SIGALRM, talrm)==SIG_ERR) ERR; alarm (10); /* fenêtre de temps A */ if ( (n=read (0, line, MAXLINE)) < 0) ERR; alarm (0); write (STDOUT_FILENO, line, n); ... (en système V, cela correspond à des stream devices). ✱ open, lorsqu’il peut se bloquer : un modem par exemple. ✱ certains ioctl, comme rembobiner une bande magnétique en pause manuelle ; ✱ certaines primitives IPC, ✱ pause, sigsuspend, wait, waitpid, wait3 ou wait4 dont la vocation est justement de bloquer le processus pendant une durée indéterminée... 655 17. SIGNAUX 8.10 Problèmes avec les signaux 656 17. SIGNAUX Débloquer une primitive lente avec time out 8.10 Problèmes avec les signaux Problème de réentrance (primitive système ou non) suite(1) processus fonction f () { stati void talrm (int signo){ return; /* juste retourner pour débloquer le read */ } noyau 1er p ( ) primitive p( ) { s } Le signal SIGALRM peut être émis en A, n’empêchant pas le blocage éventuel. Si l’appel read est rédémarré automatiquement (cf. page ??) le signal SIGALRM est sans effet... traitant t (int s) { 1er p ( ) 2e p ( ) 2e p ( ) } return } return 2 appels qui se chevauchent 657 17. SIGNAUX 8.10 Problèmes avec les signaux 658 17. SIGNAUX Réentrance des primitives système 8.10 Problèmes avec les signaux Réentrance des primitives système suite(1) Si un traitant appelle une routine (procédure ou fonction du système, de bibliothèque ou propre au processus), celle-ci doit être réentrante : Les primitives non réentrantes sont : ✱ si cette primitive était en cours d’exécution au moment de l’émission du signal, ✱ et a été interrompue par l’activation du traitant, ✱ l’exécution de la primitive par le traitant (il réentre dans la primitive) doit donner un résultat correct, ✱ et lors de la fin du traitant, le retour dans la primitive doit aussi donner un résultat correct. ✱ interruptibles et utilisent la mémoire du processus : bss, data, heap. ✱ exemples : malloc ou free (ou celles qui les utilisent), et les primitives C- ANSI d’E/S (printf, getchar...). 659 17. SIGNAUX 8.10 Problèmes avec les signaux 660 17. SIGNAUX Appels système garantis réentrants : 8.10 Problèmes avec les signaux Appels système garantis réentrants suite(1) (appelables depuis un traitant de signal ) _exit abort access alarm cfgetispeed cfgetospeed cfsetispeed cfsetospeed chdir chmod chown fork fstat getegid geteuid getgid getgroups getpgrp getpid getppid getuid kill pipe read rename rmdir setgid setpgid setsid setuid sigaction sigaddset sigdelset close creat dup dup2 execle execve fcntl stat sysconf tcdrain tcflow tcflush tcgetattr tcgetpgrp tcsendbreak tcsetattr tcsetpgrp time link longjmp lseek mkdir mkfifo open pause sigemptyset sigfillset sigismember signal sigpending sigprocmask sleep times umask uname unlink utime wait write Ces primitives réentrantes sont : 661 ✱ soit ininterruptibles (rapides, comme les E/S sur un disque), ✱ soit n’utilisent pas la mémoire du processus pour leur résultat. 662 Ensemble de signaux : Ensemble de signaux suite(1) sigemptyset, sigfillset, sigaddset, sigdelset (3) ✱ ✱ ✱ Syntaxe : #inlude <signal.h> int sigemptyset (sigset_t* set ); int sigfillset (sigset_t* set ); int sigaddset (sigset_t* set, int signo ); int sigdelset (sigset_t* set, int signo ); Sémantique : • sigemptyset / sigemptyset : création d’un ensemble vide / plein. • sigaddset / sigdelset : ajout / retrait d’un signal à un ensemble. • Si l’ensemble à construire contient peu de signaux, on crée d’abord un ensemble vide, puis on lui ajoute les signaux voulus, un par un. Si l’ensemble contient beaucoup de signaux, on crée un ensemble plein, puis on ôte ce qu’il faut. Retour : • 0, si OK, • -1, si erreur 663 17. SIGNAUX 8.11 Primitives POSIX fiables 664 17. SIGNAUX Ensemble de signaux : 8.11 Primitives POSIX fiables Remarques sur les ensembles de signaux sigismember (3) ✱ ✱ ✱ Syntaxe : #inlude <signal.h> int sigismember (onst sigset_t* set, int signo ); Retour : • 1, si signo appartient à set, • 0, sinon Sémantique : • teste l’appartenance de signo dans set. ✱ Les structures de type sigset_t doivent être déclarées dans l’espace mémoire du processus. ✱ Leur réprésentation interne est inconnue : type abstrait de donnée, TAD (en anglais ADT). ✱ Les quatre primitives fournies sont rustiques, mais réentrantes si elles portent sur des ensembles différents. ✱ On peut définir des fonctions de bibliothèque plus faciles d’emploi (mais prendre garde à les laisser réentrantes). 665 17. SIGNAUX 8.11 Primitives POSIX fiables 666 17. SIGNAUX 8.11 Primitives POSIX fiables sigaction (2) Association d’un traitant à un signal : suite(1) sigaction (2) ✱ Syntaxe : #inlude <signal.h> int sigation ( int signo, onst strut sigation * at, strut sigation * oat ); strut sigation { /* spécification d’une action */ handler_t sa_handler; /* traitant */ ✱ Retour : 0, si OK, -1, si erreur ✱ Sémantique : • Associe le traitement sa_handler défini dans act au signal signo. • Si oact est non null, la structure pointée reçoit les paramètres act du précédent appel de sigaction pour signo. /* ou SIG_IGN, SIG_DFL */ sigset_t sa_mask; /* signaux à masquer */ int sa_flags; /* options, cf. tableau suivant */ } 667 17. SIGNAUX 8.11 Primitives POSIX fiables sigaction (2) ✱ 668 17. SIGNAUX 8.11 Primitives POSIX fiables Options de sigaction (2) suite(2) Sémantique (suite) : • Les signaux dans act.sa_mask sont ajoutés au masque du processus, avant l’appel au traitant. • Le retour du traitant restaure le masque de signaux, d’avant l’entrée dans le traitant, donc démasque le signal associé. • Le signal associé au traitant est implicitement masqué dans act.sa_mask. • L’association signal/traitant perdure jusqu’au prochain appel, contrairement à certaines versions de si- SA_NOCLDSTOP Pour signo == SIGCHLD, ne pas générer ce signal si un fils stoppe, seulement s’il se termine. SA_NOCLDWAIT Pour signo == SIGCHLD, ne pas créer de zombies avec les fils de l’appelant. Un appel à wait bloque alors le père jusqu’à ce que tous ses fils se terminent avec CR == -1 et errno == ECHILD. (Utile si le père n’est pas intéressé par les CR de ses fils) SA_RESTART Les appels système interrompus par ce signal sont automatiquement relancés. SA_ON_STACK Si une pile auxiliaire a été déclarée par sigaltstack, utiliser cette pile. gnal(3). 669 670 Options de sigaction Signature d’un traitant handler_t suite(1) ✱ SA_NODEFER Ne pas masquer le signal à l’entrée de son traitant, comme dans les vieilles versions de signal(3). SA_RESETHAND Ne pas maintenir l’association (signal, traitant) à l’entrée du traitant, comme dans les vieilles versions de signal(3). SA_SIGINFO Fournir des informations supplémentaires à l’entrée du traitant, dans une strut siginfo (cf. ci-après). Syntaxe (suite) : typedef void (* handler_t ) ( int, /* n◦du signal émis */ strut siginfo*, /* pour option SA_SIGINFO */ strut threadinfo* /* pour les threads */ ) ; 671 17. SIGNAUX 8.11 Primitives POSIX fiables 672 17. SIGNAUX 8.11 Primitives POSIX fiables Arguments d’un traitant : strut siginfo Arguments d’un traitant : siginfo suite(1) ✱ Syntaxe : strut siginfo { int si_signo; /* numéro du signal */ int si_errno; /* si 6= 0, valeur de errno */ int si_ode; /* info supplém., selon signal */ pid_t si_pid;/* pid du processus émetteur */ uid_t si_uid;/* RUID du processus émetteur */ ... ; /* autres champs... */ } ✱ Sémantique : • Avec l’option SA_SIGINFO de sigaction vue précédemment, les traitants de signaux peuvent avoir deux arguments supplémentaires : ⊲ un pointeur sur une strut siginfo qui donne plus d’informations sur les conditions de déroutement ; ⊲ des informations sur les threads du processus (non étudiés ici). 673 17. SIGNAUX 8.11 Primitives POSIX fiables 674 17. SIGNAUX sigprocmask Masquer, démasquer des signaux : sigprocmask (2) ✱ ✱ ✱ 8.11 Primitives POSIX fiables Syntaxe : #inlude <signal.h> int sigpromask ( int how, onst sigset_t* set, sigset_t* oset ); suite(1) Sémantique : • sigprocmask modifie ou consulte le masque des signaux du processus appelant (cf. page ??). • si set est non NULL, how indique comment affecter le masque courant avec l’ensemble set : SIG_BLOCK set est l’ensemble de signaux supplémentaires à masquer. SIG_UNBLOCK set est l’ensemble des signaux à démasquer SIG_SETMASK set définit intégralement le nouveau masque Retour : • 0, si OK, • -1, si erreur 675 17. SIGNAUX 8.11 Primitives POSIX fiables sigprocmask ✱ 676 17. SIGNAUX 8.11 Primitives POSIX fiables sigsetjmp (3) suite(2) Sémantique (suite) : • si set est NULL, le masque du processus est inchangé. how est alors sans signification, mais oset peut récupérer le masque en cours. • si oset est non NULL, le système renvoie dans la structure pointée la valeur du masque avant l’affectation éventuelle. 677 ✱ Syntaxe : #inlude <setjmp.h> int sigsetjmp (sigjmp_buf env, int savemask ); ✱ Retour : • 0, si définition d’un point de retour, • 6= 0, si retour d’un siglongjmp (la valeur rendue est celle émise dans val par siglongjmp) ✱ Sémantique : • comme setjmp ( cf. setjmp, p. 285 ), avec sauvegarde du masque courant des signaux dans la structure env. 678 siglongjmp (3) sigsetjmp , siglongjmp (3) ✱ ✱ Syntaxe : #inlude <setjmp.h> void siglongjmp (sigjmp_buf env, int val ); ✱ Sémantique : • comme longjmp ( cf. longjmp, p. 286 ), avec restauration du masque des signaux sauvegardé dans env. suite(1) Sémantique : • savemask indique : ⊲ (6= 0) s’il faut sauvegarder le masque, ⊲ (= 0) s’il ne faut pas le sauvegarder ; dans ce cas, sigsetjmp est identique à setjmp. • Ces deux primitives sont à utiliser de préférence à leurs équivalents setjmp et longjmp dans les traitants de signaux, de manière à maîtriser le masque de signaux à rétablir au point de retour. 679 17. SIGNAUX 8.11 Primitives POSIX fiables 680 17. SIGNAUX Exemple d’utilisation de sigsetjmp , siglongjmp 8.11 Primitives POSIX fiables Connaître les signaux en suspens : sigpending (2) ✱ Syntaxe : #inlude <signal.h> int sigpending (sigset_t* set ); ✱ Retour : • 0, si OK, • -1, si erreur ✱ Sémantique : • retourne l’ensemble des signaux en suspens : c’est-à-dire émis au processus, bloqués, en attente d’être notifiés. 681 17. SIGNAUX 8.11 Primitives POSIX fiables Exemple d’utilisation des primitives POSIX 682 17. SIGNAUX 8.11 Primitives POSIX fiables Exemple d’utilisation des primitives POSIX suite(1) ... sigset_t nmask, omask, pmask; sleep(5); /* SIGQUIT ii restera en suspens */ if (sigpending (&pmask) <0) ERR; if (sigismember (&pmask, SIGQUIT)) printf("\nSIGQUIT est en suspens\n"); /* Initialisations */ if(signal (SIGQUIT, sig_quit) == SIG_ERR) ERR; sigemptyset (&nmask); sigaddset (&nmask, SIGQUIT); /* Masquer SIGQUIT et sauvegarder le masque ourant */ if(sigpromask (SIG_BLOCK, &nmask, &omask)<0) ERR; /* Restaurer le masque d'origine, e qui débloquera SIGQUIT */ if(sigpromask (SIG_SETMASK, &omask, NULL)<0) ERR; printf("SIGQUIT débloqué\n"); sleep (5); /* SIGQUIT terminera ii ave un dump */ ... 683 17. SIGNAUX 8.11 Primitives POSIX fiables 684 17. SIGNAUX 8.11 Primitives POSIX fiables Exemple d’utilisation des primitives POSIX Mise en attente fiable suite(2) avec masquage de signaux : sigsuspend (2) stati void sig_quit(int signo){ printf("traitement de SIGQUIT\n"); /* non réentrant */ /* Reprise de l'assoiation par défaut */ if (signal (SIGQUIT, SIG_DFL)==SIG_ERR) ERR; return; ✱ Syntaxe : #inlude <signal.h> int sigsuspend ( onst sigset_t* set ); ✱ Retour : pas de succès, -1, avec errno == EINTR } 685 686 sigsuspend (2) sigsuspend (2) suite(1) ✱ ✱ Sémantique : • réalise de manière atomique (non interruptible) une attente et une définition du masque suite(2) lorsqu’un signal non masqué est émis, le système interrompt l’appel sigsuspend et le traite comme prévu : • l’ignore ou • le traite par défaut ( e.g. une terminaison), • ou active un traitant dont le retour reprend juste après l’appel sigsuspend interrompu, avec CR == -1 et errno == EINTR. Le masque des signaux est restauré à sa valeur à l’entrée de sigsuspend. /* début opération atomique */ sigpromask (SIG_SETMASK, set, 0); pause (); /* fin opération atomique */ 687 17. SIGNAUX 8.11 Primitives POSIX fiables 688 17. SIGNAUX 8.11 Primitives POSIX fiables Section critique avec pause (2) Exemple de section critique suite(1) avec attente non fiable avec pause sigset_t nmask, omask ; /* Initialisations */ if (signal(SIGINT, sig_int) == SIG_ERR) ERR; sigemptyset (&nmask); sigaddset(&nmask, SIGINT); /* Masquer SIGINT et sauvegarder le masque ourant */ if (sigpromask (SIG_BLOCK, &nmask, &omask)<0) ERR; /* setion ritique protégée de SIGINT */ /* (on ne veut pas être interrompu par SIGINT) */ ... /* Fin de SC : libérer tous les signaux et attendre ave pause */ /* (on attend SIGINT) */ if (sigpromask (SIG_SETMASK, &omask, NULL)<0) ERR; /* A : oops ! il y a une fenêtre de temps ii... */ pause (); /* ontinuer le traitement ... */ ... stati void sig_int (int signo) { return; /* juste pour débloquer une attente... */ } 689 17. SIGNAUX 8.11 Primitives POSIX fiables 690 17. SIGNAUX 8.11 Primitives POSIX fiables Section critique avec sigsuspend (2) Exemple de section critique suite(1) avec attente fiable avec sigsuspend (2) sigset_t nmask, omask, zmask; /* Initialisations */ if (signal (SIGINT, sig_int) == SIG_ERR) ERR; sigemptyset (&zmask); sigemptyset (&nmask); sigaddset(&nmask, SIGINT); /* A. masquer SIGINT et sauvegarder le masque ourant */ if (sigpromask (SIG_BLOCK, &nmask, &omask)<0) ERR; /* Setion ritique protégée de SIGINT */ ... /* B. fin de SC : libérer et attendre un signal quelonque */ if (sigsuspend(&zmask) != -1) ERR; /* C. retour du traitant via sigsuspend (appel systeme interrompu) * le masque en B est restauré, donc SIGINT est bloqué * => plus de fenêtre de temps !! (atomicité de sigsuspend) * D. la restauration du masque en A débloque SIGINT */ if(sigpromask (SIG_SETMASK, &omask, NULL)<0) ERR; /* E. ontinuer le traitement ... */ ... stati void sig_int(int signo){return;} /* débloque l'attente */ 691 17. SIGNAUX 8.12 Exemples de synthèse 692 17. SIGNAUX 8.12 Exemples de synthèse M. à j. d’une variable avec sigsuspend (2) Mise à jour d’une variable globale avec sigsuspend (2) suite(1) volatile int quitflag; /* mis à 1 par le traitant */ exemple(void){ sigset_t nmask, omask, zmask; /* Intialisations */ if (signal (SIGINT, sig_int) == SIG_ERR) ERR; if (signal (SIGQUIT, sig_int) == SIG_ERR) ERR; sigemptyset (&zmask); sigemptyset (&nmask); sigaddset (&nmask, SIGQUIT); /* A. masquer SIGQUIT et sauvegarde le masque ourant */ if(sigpromask(SIG_BLOCK, &nmask, &omask)<0) ERR; 693 /* B. Attendre jusqu’à SIGQUIT (tous signaux débloqués) * (n’importe quel signal débloque sigsuspend, d’où le while) */ while (quitflag == 0) sigsuspend (&zmask); /* C. retour du traitant via sigsuspend (appel systeme interrompu) * le masque en B est restauré, donc SIGQUIT est bloqué */ quitflag = 0; /* on peut don modifier quitflag ... */ /* D. la restauration du masque en A débloque SIGQUIT */ if(sigpromask (SIG_SETMASK, &omask, NULL)<0) ERR; /* E. ontinuer le traitement ... */ /* ... */ }/* exemple */ 694 Synchronisation avec des signaux M-à-j d’une variable avec sigsuspend (2) suite(2) Nous avons vu (cf. chap. 4, page ??) plusieurs exemples de synchronisation entre un processus père et son fils, en utilisant 5 primitives : stati void sig_int (int signo) { /* traitant ommun SIGINT & SIGQUIT */ if (signo == SIGINT) write (1, "\nSIGINT\n", 8); else if (signo == SIGQUIT) quitflag = 1; /* met à 1 pour boule prinipale */ return; } ✱ INIT_SYNCHRO, ✱ WAIT_PARENT , ✱ TELL_PARENT , ✱ WAIT_CHILD, ✱ TELL_CHILD. 695 17. SIGNAUX 8.12 Exemples de synthèse Synchronisation avec des signaux 696 17. SIGNAUX 8.12 Exemples de synthèse Synchronisation avec des signaux suite(1) pid_t pid; INIT_SYNCHRO (); if ( (pid = fork ()) < 0) ERR; else if (pid == 0) {/* - FILS - */ WAIT_PARENT (); /* attend le feu vert du père */ erit_ar_par_ar("message du fils\n"); } else { /* - PÈRE */ erit_ar_par_ar("message du père\n"); TELL_CHILD (pid); /* donne le feu vert au fils */ } suite(2) #define vert 0 #define rouge 1 /* Les variables globales sont dupliquées chez le père et le fils * Il y a bien deux feux distincts qui sont utilisés... */ stati volatile int feu = rouge; stati sigset_t nmask, omask, zmask; /* Cette version utilise 2 signaux SIGUSR1 et SIGUSR2 : * USR1: le père donne le feu vert au fils ; * USR2: le fils donne le feu vert au père. * remarque: en fait, un seul signal suffit ! */ 697 17. SIGNAUX 8.12 Exemples de synthèse Synchronisation avec des signaux 698 17. SIGNAUX 8.12 Exemples de synthèse Synchronisation avec des signaux suite(3) suite(4) void INIT_SYNCHRO (void) { /* initialisation de la synhronisation (avant fork) */ sigemptyset (&zmask); sigemptyset (&nmask); /* masque USR1 et USR2 */ sigaddset (&nmask, SIGUSR1); sigaddset (&nmask, SIGUSR2); stati void sig_usr ( int signo ) { /* traitant commun pour SIGUSR1 et SIGUSR2 * mais qui agit sur deux feux distincts */ feu = vert; return; /* bloquer SIGUSR1, SIGUSR2, sauvegarder masque ourant */ if(sigpromask (SIG_BLOCK, &nmask, &omask)<0) ERR; if(signal (SIGUSR1, sig_usr) == SIG_ERR) ERR; if(signal (SIGUSR2, sig_usr) == SIG_ERR) ERR; } } 699 17. SIGNAUX 8.12 Exemples de synthèse Synchronisation avec des signaux 700 17. SIGNAUX 8.12 Exemples de synthèse Synchronisation avec des signaux suite(5) suite(6) void WAIT_PARENT ( void ) { /* attente du fils jusqu’au feu vert du père : void TELL_CHILD ( pid_t pid ) { /* donne le feu vert au fils */ kill (pid, SIGUSR1); } * le feu manipulé est celui du fils ! */ while (feu == rouge) /* n'importe quel signal débloque sigsuspend, d'où le while */ sigsuspend (&zmask); /* démasque tous les signaux */ void TELL_PARENT ( pid_t pid ) { /* donne le feu vert au père */ kill (pid, SIGUSR2); } /* Le feu est passé au vert. USR1 et USR2 sont encore bloqués * (reprise de sigsuspend), donc sommes encore en section critique*/ feu = rouge; /* 'est le feu du fils */ /* remet le masque d'origine, e qui débloque USR1, USR2 */ if(sigpromask (SIG_SETMASK, &omask, NULL)<0) ERR; } 701 702 Synchronisation avec des signaux Implémentation fiable de sleep (3) suite(7) void WAIT_CHILD ( void ) { (Version simplifiée qui ne préserve pas le temps en cours dans le timer s’il est actif) /* Attente du père jusqu’au feu vert du fils : * code identique à WAIT_PARENT pour cette version... stati void sig_alrm ( void ) { /* rien à faire, juste débloquer une attente */ return; } * ... mais le feu manipulé est celui du père ! */ WAIT_PARENT(); } 703 17. SIGNAUX 8.12 Exemples de synthèse Implémentation fiable de sleep (3) 704 17. SIGNAUX 8.12 Exemples de synthèse Implémentation fiable de sleep (3) suite(1) unsigned int sleep(unsigned int nses) { strut sigation newat, oldat; sigset_t newmask, oldmask, suspmask; unsigned int unslept; /* assoiation ALRM <-> sig_alrm, sans signaux bloqués */ newat.sa_handler = sig_alrm; sigemptyset (&newat.sa_mask); newat.sa_flags = 0; sigation (SIGALRM, &newat, &oldat); suite(2) /* bloquer SIGALRM et sauvegarder masque ourant */ sigemptyset (&newmask); sigaddset (&newmask, SIGALRM); sigpromask (SIG_BLOCK, &newmask, &oldmask); /* Armer le timer de nsec secondes : * Cette version ne préserve pas l’activité éventuelle du timer ! */ alarm (nses); 705 17. SIGNAUX 8.12 Exemples de synthèse Implémentation fiable de sleep (3) 706 17. SIGNAUX 8.12 Exemples de synthèse Implémentation fiable de sleep (3) suite(3) suite(4) /* Un signal quelconque ou SIGALRM a été traité * SIGALRM est encore bloqué ; * Restaurer les conditions initiales: timer, traitants, signaux. */ /* désative le timer s'il était enore atif */ unslept = alarm (0); /* Traiter SIGALRM omme à l'entrée de sleep */ sigation (SIGALRM, &oldat, NULL); /* masquer SIGALRM omme il était à l'entrée de sleep */ sigpromask (SIG_SETMASK, &oldmask, NULL); return(unslept); /* sigsuspend avec le masque d’origine et SIGALRM non bloqué. * Ainsi soit SIGALRM, soit un autre signal débloquera l’attente */ suspmask = oldmask; sigdelset (&suspmask, SIGALRM); sigsuspend (&suspmask); } 707 17. SIGNAUX 8.12 Exemples de synthèse 708 17. SIGNAUX 8.12 Exemples de synthèse system (3) avec traitement des signaux Implémentation fiable de system (3) suite(1) avec traitement des signaux #inlude #inlude #inlude #inlude #inlude int system (onst har* mdstring) { /* Version omplète POSIX ave traitement des signaux */ pid_t pid; int status; strut sigation ignore, saveintr, savequit; sigset_t hldmask, savemask; <sys/types.h> <sys/wait.h> <errno.h> <signal.h> <unistd.h> /* demande si un shell existe : toujours vrai ave Unix */ if (mdstring == NULL) return(1); 709 710 system (3) avec traitement des signaux suite(2) /* ignore SIGINT and SIGQUIT */ ignore.sa_handler = SIG_IGN; sigemptyset(&ignore.sa_mask); ignore.sa_flags = 0; if(sigation (SIGINT, &ignore, &saveintr)<0) return(-1); if(sigation (SIGQUIT, &ignore, &savequit)<0) return(-1); /* bloquer SIGCHLD */ sigemptyset (&hldmask); sigaddset (&hldmask, SIGCHLD); if(sigpromask (SIG_BLOCK, &hldmask, &savemask)<0) return(-1); if ((pid = fork ())<0) { status = -1; } system (3) avec traitement des signaux else if (pid == 0) { /* - FILS */ /* restaurer traitements de signaux et masque d’origine * SIGQUIT ou SIGINT termineront le shell * La fin du shell enverra SIGCHLD au père, ce qui le débloquera. */ sigation(SIGINT, &saveintr, NULL); sigation(SIGQUIT, &savequit, NULL); sigpromask(SIG_SETMASK, &savemask, NULL); /* exéuter un shell ave la ommande en argument */ exel ("/bin/sh", "sh", "-", mdstring, (har *) 0); _exit (127); /* exe error */ }/* fin du fils */ 711 17. SIGNAUX 8.12 Exemples de synthèse system (3) avec traitement des signaux suite(4) else { /* PÈRE */ /* Attendre la fin du fils : * SIGINT, SIGQUIT ignorés ; * SIGCHLD débloquera l’attente. */ while (waitpid (pid, &status, 0) < 0) if (errno != EINTR) /* vraie erreur, pas une interruption de waitpid */ status = -1; break; suite(3) 712 17. SIGNAUX 8.12 Exemples de synthèse system (3) avec traitement des signaux suite(5) /* Restauration des traitants et masques des signaux */ if(sigation (SIGINT, &saveintr, NULL)<0) return(-1); if(sigation (SIGQUIT, &savequit, NULL)<0) return(-1); if(sigpromask (SIG_SETMASK, &savemask, NULL)<0) return(-1); return(status); }/* fin system */ } 713 714 Table des matières