• Aucun résultat trouvé

Liste des signaux sous Linux

Dans le document Programmation système en C sous Linux (Page 145-156)

Certains signaux sont révélateurs de conditions d’erreur. Ils sont en général délivrés immédia-tement après la détection du dysfonctionnement. On ne peut donc pas vraiment parler de comportement asynchrone. Par contre, tous les signaux indiquant une interaction avec l’envi-ronnement (action sur le terminal, connexion réseau…) ont une nature fortement asynchrone.

Ils peuvent se produire à tout moment du programme.

Signaux SIGABRT et SIGIOT

Il s’agit de deux synonymes sous Linux. SIGIOT est le nom historique sous Système V, mais SIGABRT est préférable car il est défini par les normes Ansi C et SUSv3. SIGABRT est déclenché lorsqu’on appelle la fonction abort() de la bibliothèque C. Le comportement par défaut est de terminer le programme en créant un fichier core. La routine abort() sert, comme nous l’avons vu dans le chapitre précédent, à indiquer la fin anormale d’un programme (détection d’un bogue interne, par exemple).

Il est possible d’ignorer le signal SIGABRT (si on le reçoit depuis un autre processus), mais il faut savoir que la fonction abort() restitue le comportement par défaut avant d’envoyer SIGABRT vers le processus appelant lui-même.

La routine abort() de la bibliothèque C est complexe, car la norme SUSv3 réclame plusieurs fonctionnalités assez difficiles à concilier :

• Si le processus ignore SIGABRT, abort() doit réinitialiser le comportement par défaut avant d’envoyer ce signal.

• Si le processus capture le signal SIGABRT et si son gestionnaire redonne ensuite la main à la routine abort(), celle-ci doit vider tous les flux d’entrée-sortie en utilisant fflush() et les fermer avant de terminer le programme.

• Si le processus capture le signal SIGABRT et s’il ne rend pas la main à abort(), celle-ci ne doit pas toucher aux flux d’entrée-sortie. Pour ne pas revenir, le gestionnaire peut soit sortir

directement du programme en appelant exit() ou _exit(), soit effectuer un saut non local siglongjmp() vers une autre routine.

Rajoutons à tout ceci que la routine abort() de la bibliothèque C doit traiter le cas des programmeurs distraits qui invoquent abort() depuis le gestionnaire de signaux SIGABRT lui-même, ainsi que l’appel simultané depuis plusieurs threads. La complexité de cette tâche est toutefois résolue par la routine abort(), dont on peut examiner l’implémentation dans les sources de la bibliothèque GlibC.

Nous parlerons souvent des sauts non locaux au cours de ce chapitre. Nous les détaillerons lorsque nous étudierons la manière de terminer un gestionnaire de signaux. Pour le moment, on peut considérer qu’il s’agit d’une sorte de « goto » permettant de sauter d’une fonction au sein d’une autre, en nettoyant également la pile.

Signaux SIGALRM, SIGVTALRM et SIGPROF

SIGALRM (attention à l’orthographe…) est un signal engendré par le noyau à l’expiration du délai programmé grâce à l’appel-système alarm(). Nous l’étudierons plus en détail ultérieure-ment car il est souvent utilisé pour programmer une limite de temporisation pour des appels-système bloquants (comme une lecture depuis une connexion réseau). On programme un délai maximal avant d’invoquer l’appel-système susceptible de rester coincé, et l’arrivée du signal SIGALRM l’interrompt, avec un code d’erreur EINTR dans errno.

SIGALRM est également utilisé pour la programmation de temporisations avec setitimer(). Cet appel-système sert à fournir un suivi temporel de l’activité d’un processus. Il y a trois types de temporisations : la première fonctionne en temps réel et déclenche SIGALRM à son expiration, la deuxième ne fonctionne que lorsque le processus s’exécute et déclenche SIGVTALRM, et la troisième décompte le temps cumulé d’exécution du code du processus et celui du code du noyau exécuté lors des appels-système, puis déclenche SIGPROF. L’utilisation conjointe des deux dernières temporisations permet un suivi de l’activité du processus.

Il est donc formellement déconseillé d’utiliser simultanément les fonctions de comptabilité de setitimer() et la programmation de alarm().

Notons de surcroît que l’implémentation de la fonction sleep() de la bibliothèque GlibC utilise également le signal SIGALRM. Cette fonction prend bien garde de ne pas interférer avec les éventuelles autres routines d’alarme du processus, sauf si le gestionnaire de SIGALRM installé par l’utilisateur se termine par un saut non local ; dans ce cas, elle échoue. Il peut arriver que, sur d’autres systèmes, la routine sleep() soit moins prévenante et qu’elle inter-fère avec alarm(). Pour une bonne portabilité d’un programme, il vaut donc mieux éviter d’utiliser les deux conjointement.

SIGALRM est défini par SUSv3. SIGVTALRM et SIGPROF également mais sous forme d’extension X/Open (pas toujours disponibles dans tous les Unix). Par défaut, ces trois signaux terminent le processus en cours.

Signaux SIGBUS et SIGSEGV

Ces deux signaux indiquent respectivement une erreur d’alignement des adresses sur le bus et une violation de la segmentation. Ils n’ont pas la même signification mais sont généralement dus au même type de bogue : l’emploi d’un pointeur mal initialisé.

Le signal SIGBUS est dépendant de l’architecture matérielle car il représente en fait une réfé-rence à une adresse mémoire invalide (par exemple un mauvais alignement de mots de deux ou quatre octets sur des adresses paires ou multiples de quatre).

Le signal SIGSEGV correspond à une adresse correcte mais pointant en dehors de l’espace d’adressage affecté au processus.

Ces deux signaux arrêtent par défaut le processus en créant un fichier core. Dans un cas comme dans l’autre, il est mal vu d’ignorer ces types de signaux, qui sont révélateurs de bogues. SUSv3 souligne d’ailleurs que le comportement d’un programme ignorant SIGSEGV est indéfini. À la rigueur, on peut capturer le signal, afficher un message à l’utilisateur et reprendre l’exécution dans un contexte propre par un saut non local longjmp(), comme nous le décrivons ci-dessous pour SIGILL.

Signaux SIGCHLD et SIGCLD

Le signal SIGCHLD est émis par le noyau vers un processus dont un fils vient de se terminer ou d’être stoppé. Ce processus peut alors soit ignorer le signal (ce qui est le comportement par défaut, mais qui est déconseillé par SUSv3), soit le capturer pour invoquer l’appel-système wait() qui précisera le PID du fils concerné et le code de terminaison s’il s’est fini.

Le signal SIGCLD est un synonyme de SIGCHLD sous Linux. Il s’agit d’une variante historique sur Système V. Le fonctionnement de SIGCLD était différent de celui de SIGCHLD et était parti-culièrement discutable. Les nouvelles applications doivent donc uniquement considérer SIGCHLD, qui est défini par SUSv3.

Par défaut, SIGCHLD est ignoré, mais ce comportement est légèrement différent de celui qui est adopté si on demande explicitement d’ignorer ce signal. Lorsqu’un processus fils se termine, et tant que son père n’a pas exécuté un appel wait(), il devient zombie si SIGCHLD est capturé par un gestionnaire ou s’il est traité par défaut. Par contre, le processus fils disparaît sans rester à l’état zombie si SIGCHLD est volontairement ignoré. SUSv3 déconseille toutefois qu’un processus ignore SIGCHLD s’il est destiné à avoir des descendants. Un gestionnaire de signaux minimal permettra d’éliminer facilement les zombies.

Signaux SIGFPE et SIGSTKFLT

SIGFPE correspond théoriquement à une « Floating Point Exception », mais il n’est en fait nullement limité aux erreurs de calcul en virgule flottante. Il inclut par exemple l’erreur de division entière par zéro. Il peut également se produire si le système ne possède pas de copro-cesseur arithmétique, si le noyau a été compilé sans l’option pour l’émuler, et si le processus exécute des instructions mathématiques spécifiques.

SIGFPE est défini par Ansi C et SUSv3 ; par défaut, ce signal arrête le processus en créant un fichier core. Une application devra donc le laisser tel quel durant la phase de débogage afin de déterminer toutes les conditions dans lesquelles il se produit (en analysant post-mortem le fichier core). Théoriquement, un programme suffisamment défensif ne devrait laisser passer aucune condition susceptible de déclencher un signal SIGFPE, quelles que soient les données saisies par l’utilisateur…

On notera toutefois que le débogage peut parfois être malaisé en raison du retard du signal provenant du coprocesseur. Le signal n’est pas nécessairement délivré immédiatement après l’exécution de la condition d’erreur, mais peut survenir après plusieurs instructions.

SIGSTKFLT est un signal indiquant une erreur de pile, condition qui semble laisser les concep-teurs de Linux particulièrement perplexes, comme en témoigne le commentaire dans /usr/

include/signum.h :

#define SIGSTKFLT 16 /* ??? */

Ce signal, non conforme SUSv3, arrête par défaut le processus. On n’en trouve pas trace dans les sources du noyau, aussi est-il conseillé de ne pas s’en préoccuper.

Signal SIGHUP

Ce signal correspond habituellement à la déconnexion (Hang Up) du terminal de contrôle du processus. Le noyau envoie SIGHUP, suivi du signal SIGCONT que nous verrons plus bas, au processus leader de la session associée au terminal. Ce processus peut d’ailleurs se trouver en arrière-plan. La gestion de ce signal se trouve dans /usr/src/linux/drivers/char/tty_io.c. SIGHUP est aussi envoyé, suivi de SIGCONT, à tous les processus d’un groupe qui devient orphelin. Rappelons qu’un processus crée un groupe en utilisant setpgid(), l’identifiant du groupe étant égal au PID du processus leader. Ses descendants futurs appartiendront automa-tiquement à ce nouveau groupe, à moins qu’ils ne créent le leur. Lorsque le processus leader se termine, le groupe est dit orphelin. À ce moment, le noyau envoie SIGHUP, suivi de SIGCONT si le groupe contient des processus arrêtés.

Enfin, il est courant d’envoyer manuellement le signal SIGHUP (en utilisant la commande /bin/

kill) à certains processus démons, pour leur demander de se réinitialiser. Comme un démon n’a pas de terminal de contrôle, ce signal n’a pas de signification directe. Il est alors souvent utilisé pour demander au démon de relire ses fichiers de configuration (par exemple pour xinetd, named, sendmail…). En outre ces démons ferment puis rouvrent leurs fichiers de journalisation (log) à la réception de ce signal ce qui permet de réaliser une rotation de ces fichiers.

SIGHUP est décrit par SUSv3 et, par défaut, il termine le processus cible. On notera que la commande nohup permet de lancer un programme en l’immunisant contre SIGHUP. En fait, nohup n’est pas un programme C mais un script shell utilisant la fonctionnalité TRAP de l’inter-préteur de commandes. Ce programme permet de lancer une application en arrière-plan, en redirigeant sa sortie vers le fichier nohup.out, et de se déconnecter en toute tranquillité.

Signal SIGILL

Ce signal est émis lorsque le processeur détecte une instruction assembleur illégale. Le noyau est prévenu par l’intermédiaire d’une interruption matérielle, et il envoie un signal SIGILL au processus fautif. Ceci ne doit jamais se produire dans un programme normal, compilé pour la bonne architecture matérielle. Mais il se peut toutefois que le fichier exécutable soit corrompu, ou qu’une erreur d’entrée-sortie ait eu lieu lors du chargement du programme et que le segment de code contienne effectivement un code d’instruction invalide.

Un autre problème possible peut être causé par les systèmes à base de 386 ne disposant pas de coprocesseur arithmétique. Le noyau utilise alors un émulateur (wm-FPU-emu) qui peut déclencher le signal SIGILL dans certains cas rares d’instructions mathématiques inconnues du 80486 de base.

En fait, l’occurrence la plus courante du signal SIGILL est révélatrice d’un bogue de déborde-ment de pile. Par exemple, un tableau de caractères déclaré en variable locale (et donc alloué dans la pile) peut avoir été débordé par une instruction de copie de chaîne sans limite de lon-gueur, comme strcpy(). La pile peut très bien être corrompue, et l’exécution du programme est totalement perturbée, avec une large confusion entre code et données.

Le comportement par défaut est d’arrêter le processus et de créer un fichier d’image mémoire core. Il s’agit d’un signal décrit dans la norme Ansi C. Il n’est d’aucune utilité d’ignorer ce signal. Un programme intelligemment conçu peut, en cas d’arrivée de SIGILL, adopter l’un des deux comportements suivants (dans le gestionnaire de signaux) :

• S’arrêter en utilisant exit(), pour éviter de laisser traîner un fichier core qui ne sera d’aucune aide à l’utilisateur, après avoir éventuellement affiché un message signalant la présence de bogue à l’auteur.

• Utiliser une instruction de saut non local longjmp(), que nous verrons un peu plus loin.

Cette instruction permet de reprendre le programme à un point bien défini, avec un contexte propre. Il est toutefois préférable d’indiquer à l’utilisateur que l’exécution a été reprise à partir d’une condition anormale.

Signal SIGINT

Ce signal est émis vers tous les processus du groupe en avant-plan lors de la frappe d’une touche particulière du terminal : la touche d’interruption. Sur les claviers de PC sous Linux, ainsi que dans les terminaux Xterm, il s’agit habituellement de Contrôle-C. L’affectation de la commande d’interruption à la touche choisie peut être modifiée par l’intermédiaire de la commande stty, que nous détaillerons ultérieurement en étudiant les terminaux. L’affecta-tion courante est indiquée au début de la deuxième ligne en invoquant stty –all :

$ stty --all

speed 9600 baud; rows 24; columns 80; line = 0;

intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>;

[...]

$

SIGINT est défini par la norme Ansi C et SUSv3, et arrête par défaut le processus en cours.

Signaux SIGIO et SIGPOLL

Ces deux signaux sont synonymes sous Linux. SIGPOLL est le nom historique sous Système V, SIGIO celui de BSD.

SIGIO est envoyé lorsqu’un descripteur de fichier change d’état et permet la lecture ou l’écri-ture. Il s’agit généralement de descripteurs associés à des tubes, des sockets de connexion réseau, ou un terminal. La mise en place d’un gestionnaire de signaux pour SIGIO nécessite également la configuration du descripteur de fichiers pour accepter un fonctionnement asynchrone. Nous y reviendrons dans le chapitre 30, plus précisément dans les paragraphes consacrés aux entrées-sorties asynchrones.

L’utilisation de SIGIO permet à un programme d’accéder à un traitement d’entrée-sortie tota-lement asynchrone par rapport au déroutota-lement du programme principal. Les données arrivant pourront servir par exemple à la mise à jour de variables globales consultées régulièrement dans le cours du programme normal. Il est alors important, lors de la lecture ou de l’écriture

de ces variables globales par le programme normal (en dehors du gestionnaire de signaux), de bien bloquer le signal SIGIO durant les modifications, comme nous apprendrons à le faire dans le reste de ce chapitre.

Un problème posé par les anciennes implémentations de SIGIO était l’impossibilité de déter-miner automatiquement quel descripteur a déclenché le signal, dans le cas où plusieurs sont utilisés en mode asynchrone. Il fallait alors tenter systématiquement des lectures ou écritures non bloquantes sur chaque descripteur, en vérifiant celles qui échouent et celles qui réussis-sent. Ce défaut est corrigé depuis Linux 2.4.

Signal SIGKILL

SIGKILL est l’un des deux seuls signaux (avec SIGSTOP) qui ne puisse ni être capturé ni être ignoré, ni même être temporairement bloqué par un processus. À sa réception, tout processus est immédiatement arrêté. C’est une garantie pour s’assurer qu’on pourra toujours reprendre la main sur un programme particulièrement rétif. Le seul processus qui ne puisse pas recevoir SIGKILL est init, le processus de PID 1.

SIGKILL est traditionnellement associé à la valeur 9, d’où la célèbre ligne de commande kill -9 xxx, équivalente à kill -KILL xxxx. On notera que l’utilisation de SIGKILL doit être considérée comme un dernier recours, le processus ne pouvant se terminer proprement. On préfèrera essayer par exemple SIGTERM auparavant, puis SIGQUIT éventuellement.

Notons qu’un processus zombie n’est pas affecté par SIGKILL (ni par aucun autre signal d’ailleurs). Si un processus est stoppé, il sera relancé avant d’être terminé par SIGKILL. Le noyau lui-même n’envoie que rarement ce signal. C’est le cas lorsqu’un processus a dépassé sa limite de temps d’exécution, ou lors d’un problème grave de manque de place mémoire. La bibliothèque GlibC n’utilise ce signal que pour tuer un processus qu’elle vient de créer lors d’un popen() et à qui elle n’arrive pas à allouer de flux de données.

Signal SIGPIPE

Ce signal est émis par le noyau lorsqu’un processus tente d’écrire dans un tube qui n’a pas de lecteur. Ce cas peut aussi se produire lorsqu’on tente d’envoyer des données dans une socket TCP/IP (traitée dans le chapitre sur la programmation réseau) dont le correspondant s’est déconnecté.

Le signal SIGPIPE (défini dans SUSv3) arrête par défaut le processus qui le reçoit. Toute appli-cation qui établit une connexion réseau doit donc soit intercepter le signal, soit (ce qui est préférable) l’ignorer. Dans ce dernier cas en effet, les appels système comme write() renver-ront une erreur EPIPE dans errno. On peut donc gérer les erreurs au coup par coup dès le retour de la fonction. Nous reviendrons sur ce problème dans les chapitres traitant des communications entre processus.

Signal SIGQUIT

Comme SIGINT, ce signal est émis par le pilote de terminal lors de la frappe d’une touche particulière : la touche QUIT. Celle-ci est affectée généralement à Contrôle-\ (ce qui nécessite sur les claviers français la séquence triple Ctrl-AltGr-\). SIGQUIT termine par défaut les processus qui ne l’ignorent pas et ne le capturent pas. SIGQUIT engendre en plus un fichier d’image mémoire (core).

Signaux SIGSTOP, SIGCONT, et SIGTSTP

SIGSTOP est le deuxième signal ne pouvant être ni capturé ni ignoré, comme SIGKILL. Il a toutefois un effet nettement moins dramatique que ce dernier, puisqu’il ne s’agit que d’arrêter temporairement le processus visé. Celui-ci passe à l’état stoppé.

Le signal SIGCONT a l’effet inverse : il permet de relancer un processus stoppé. Si le processus n’est pas stoppé ce signal n’a pas d’effet. Le redémarrage a toujours lieu, même si SIGCONT est capturé par un gestionnaire de signaux de l’utilisateur ou s’il est ignoré.

SIGTSTP est un signal ayant le même effet que SIGSTOP, mais il peut être capturé ou ignoré, et il est émis par le terminal de contrôle vers le processus en avant-plan. Lors d’un stty --all, la touche affectée à ce signal est indiquée par susp= et non par stop= qui correspond à un arrêt temporaire de l’affichage sur le terminal. Dans la plupart des cas, il s’agit de la touche Contrôle-Z.

Il est rare qu’une application ait besoin de capturer les signaux SIGTSTP et SIGCONT, mais cela peut arriver si elle doit gérer le terminal de manière particulière (par exemple en affichant des menus déroulants), et si elle désire effacer l’écran lorsqu’on la stoppe et le redessiner lorsqu’elle redémarre.

Dans la plupart des cas, on ne s’occupera pas du comportement de ces signaux.

Signal SIGTERM

Ce signal est une demande « gentille » de terminaison d’un processus. Il peut être ignoré ou capturé pour terminer proprement le programme en ayant effectué toutes les tâches de nettoyage nécessaires. Traditionnellement numéroté 15, ce signal est celui qui est envoyé par défaut par la commande /bin/kill.

SIGTERM est défini par Ansi C et SUSv3. Par défaut, il termine le processus concerné. En prin-cipe, une application bien conçue devrait installer systématiquement un gestionnaire pour SIGTERM et SIGINT afin d’assurer une fin « propre » au programme lorsque l’utilisateur a besoin de l’arrêter rapidement.

Signal SIGTRAP

Ce signal est émis par le noyau lorsque le processus a atteint un point d’arrêt. SIGTRAP est utilisé par les débogueurs comme gdb. Il n’a pas d’intérêt pour une application classique. Le

Ce signal est émis par le noyau lorsque le processus a atteint un point d’arrêt. SIGTRAP est utilisé par les débogueurs comme gdb. Il n’a pas d’intérêt pour une application classique. Le

Dans le document Programmation système en C sous Linux (Page 145-156)