Blog ENI : Toute la veille numérique !
🐠 -25€ dès 75€ 
+ 7 jours d'accès à la Bibliothèque Numérique ENI. Cliquez ici
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici
  1. Livres et vidéos
  2. Programmation système
  3. Les signaux
Extrait - Programmation système Maîtrisez les appels système Linux avec le langage C (Nouvelle édition)
Extraits du livre
Programmation système Maîtrisez les appels système Linux avec le langage C (Nouvelle édition) Revenir à la page d'achat du livre

Les signaux

Les principes

Les signaux sont un mécanisme géré par le noyau, ayant pour but d’informer un processus qu’un événement s’est produit. Cette notion est très comparable au principe des interruptions matérielles, on l’appelle parfois une interruption logicielle.

Linux a repris les signaux traditionnels issus d’Unix, mais en respectant la normalisation POSIX, pour remédier à des problèmes liés à leur conception originelle. Il implémente en réalité une forme de signaux plus évolués, dits signaux temps réel, dont le détail ne sera pas abordé dans cet ouvrage.

1. Qu’est-ce qu’un signal ?

Un signal est un indicateur, identifié par un numéro unique, géré par le noyau et destiné à un processus. Suivant les cas, ce dernier peut recevoir ou non cette information (un signal peut être ignoré par un processus, dans ce cas il ne lui est pas transmis). Seul un processus en état d’exécution peut effectivement recevoir un signal. Entre le moment où le signal est émis par le noyau et celui où il est reçu par le processus, le signal est en attente de réception, pendant (pending). 

On peut représenter l’ensemble des signaux pendants à destination d’un processus, sous forme d’un ensemble ordonné de bits. Chaque bit correspond à un numéro de signal. Chaque processus a une table de signaux pendants. Quand le noyau émet un signal vers un processus, il positionne à 1 le bit correspondant dans la table de signaux pendants de ce processus, s’il n’est pas déjà positionné à 1. Une fois le signal traité par le processus destinataire, le bit correspondant est remis à zéro.

Cette représentation, bien que schématique, permet d’illustrer quelques caractéristiques des signaux :

  • Il n’y a pas de file d’attente de signaux....

Les types de signaux

Les signaux sont toujours gérés par le noyau, mais ils peuvent être de différentes origines.

1. Signaux d’origine utilisateur

Un utilisateur peut envoyer un signal à un ou plusieurs processus, explicitement ou implicitement.

a. Signaux liés au clavier

Il peut générer un signal par certaines combinaisons de touches clavier, variables suivant la configuration du terminal (réel, virtuel ou fenêtre de terminal).

Par exemple CTRL/C au clavier provoque généralement l’envoi du signal INT vers tous les processus d’avant-plan associés au terminal.

Quand un utilisateur se déconnecte du terminal, un signal HUP est envoyé à tous les processus de la session associée à son terminal.

b. La commande kill

Un utilisateur peut également utiliser la commande interne du shell kill. Cette commande envoie le signal spécifié (TERM par défaut) au(x) processus spécifié(s).

Syntaxe

kill [options] PID [...] 

Options

-num|nomSignal

Numéro ou nom symbolique du signal à envoyer.

-l ou -L

Affiche la liste des signaux gérés par le système.

Arguments

Une liste d’identifiants de processus. Si un identifiant est négatif et différent de -1, le signal est envoyé à tous les processus dont c’est l’identifiant de groupe de processus. Si l’identifiant vaut -1, le signal est envoyé à tous les processus (sauf au processus de PID 1).

Description

Par défaut, la commande envoie le signal de terminaison TERM au(x) processus spécifié(s) en argument.

Un utilisateur non privilégié ne peut envoyer de signaux qu’à un processus ayant son compte utilisateur comme identifiant utilisateur effectif ou réel. Un utilisateur privilégié peut envoyer un signal à tous les processus, avec des restrictions concernant le processus de PID 1 (init ou systemd).

Le nom de la commande est trompeur. Elle n’a pas pour but de "tuer" les processus destinataires, mais plutôt de leur signifier un événement. Le nom vient du fait que le traitement par défaut associé à la plupart des signaux provoque la terminaison du processus destinataire.

2. Signaux émis à l’initiative du noyau...

Envoi d’un signal

Nous avons vu que les signaux peuvent être émis spontanément par le noyau, en réaction à certains événements (instruction illégale, demande d’accès à une adresse mémoire invalide ou interdite, terminaison de processus leaders, etc.), ou à la suite d’une action utilisateur (séquences de touches clavier particulières, déconnexion, commande shell kill).

Un processus peut également demander l’envoi d’un signal vers un ou plusieurs destinataires, y compris lui-même. Pour cela, il peut utiliser différents appels système, le principal étant l’appel kill().

1. Appel système kill()

Syntaxe

#include <sys/types.h> 
#include <signal.h> 
int kill(pid_t pid, int sig); 

Arguments

pid

Identifiant du ou des destinataires

sig

Numéro du signal à émettre

Valeur retournée

-1

Erreur, code erreur positionné dans la variable errno

0

Succès

Description

Un processus privilégié peut envoyer un signal à n’importe quel processus, avec des restrictions concernant le processus de PID 1 (voir plus loin).

Un processus non privilégié peut envoyer un signal à tout processus ayant le même identifiant utilisateur effectif ou réel que lui. Il peut également envoyer le signal SIGCONT (sortie de suspension) aux processus membres de sa session.

Un processus peut s’envoyer un signal à lui-même.

Cet appel système permet d’envoyer n’importe quel numéro de signal.

Il faut être très prudent avec le signal SIGKILL, numéro 9, qui ne peut être ni ignoré, ni bloqué, ni traité et qui entraîne la terminaison du processus destinataire (à l’exception du processus de PID 1).

L’argument pid détermine le ou les destinataires du signal :

> 0

Processus ayant cet identifiant de processus

<-1

Membres du groupe de processus ayant comme identifiant -pid

0

Membres du groupe du processus émetteur

-1

Tous les processus accessibles par le processus émetteur, sauf le processus de PID 1

L’argument sig contient le numéro du signal à émettre. S’il vaut zéro, le noyau n’émet aucun signal...

Traitement des signaux

Différents appels système permettent à un programme de modifier la gestion des signaux reçus.

L’appel système signal(), issu du système Unix d’origine, présente des limites et des différences d’implémentation entre systèmes de type Unix. Bien qu’existant dans Linux, cet appel système est en voie d’obsolescence. Il est donc conseillé d’utiliser plutôt son successeur, sigaction(), plus fiable et normalisé POSIX.

Nous présenterons les deux appels système, en commençant par signal(), que l’on peut rencontrer dans des programmes existants, et dont l’interface plus simple peut faciliter les premières expérimentations de traitement des signaux. 

1. Appel système signal()

Syntaxe

#include <signal.h> 
typedef void (*sighandler_t)(int); 
sighandler_t signal(int signum, sighandler_t handler); 

Arguments

signum

Numéro du signal à traiter

handler

Traitement du signal : SIG_IGN, SIG_DFL, adresse d’une fonction

Valeur retournée

SIG_ERR

Erreur, errno est positionnée

!= SIG_ERR

Traitement précédent du signal

Description

L’appel système permet de définir quelle action associer à un signal reçu. Il permet d’ignorer le signal (SIG_IGN), de le réassocier à son action par défaut (SIG_DFL), ou de l’associer à une fonction gestionnaire de signal, dont on passe l’adresse.

Certains signaux ne peuvent pas être traités ou ignorés (le signal SIGKILL, numéro 9, en particulier).

La fonction gestionnaire de signal est décrite par le type sighandler_t : elle attend un entier comme paramètre (le numéro du signal) et elle ne retourne rien.

Une fois cet appel système exécuté, l’action associée est mise en œuvre chaque fois que le processus courant est destinataire d’un signal du numéro spécifié.

Cet appel système a été amélioré dans les versions récentes de Linux, pour contourner certaines de ses limitations historiques, mais son emploi reste déconseillé.

a. Description d’un signal : psignal()

Syntaxe

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

Description

Cette...

Signaux et démons

Un daemon (souvent traduit par démon) est un programme s’exécutant en tâche de fond, indépendamment de tout terminal, avec généralement une durée de vie importante. Ces programmes sont souvent lancés automatiquement, pendant la phase de démarrage du système.

Le terme DAEMON, démon en anglais, a été "justifié" a posteriori par l’invention d’un rétroacronyme : Disk And Execution MONitor (moniteur de disque et d’exécution).

Ces caractéristiques imposent au programmeur de prendre des précautions par rapport aux signaux susceptibles de terminer prématurément un daemon.

Comme un daemon s’exécute le plus souvent du démarrage jusqu’à l’arrêt du système, il doit être résistant aux changements pouvant survenir dans son environnement. Il doit être aussi susceptible de modifier sa configuration sans nécessiter d’être arrêté et redémarré. De plus, il doit prendre garde à gérer correctement les ressources qu’il alloue pour éviter des boucles de consommation, conduisant à des dépassements de limites (fuites mémoire, nombre de fichiers ouverts excessif, processus enfants zombies, etc.).

Concernant plus particulièrement la gestion des signaux, un daemon doit être implémenté de façon à ne pas être soumis aux signaux liés à la gestion des sessions et des groupes de processus. Il doit également mettre en place des gestionnaires...