Dans l'environnement Linux, la communication entre processus et le noyau repose souvent sur les signaux. Pour un développeur système, maîtriser les mécanismes de réception et de blocage de ces notifications est essentiel. Cet article explore l'utilisation des fonctions signal et sigaction, ainsi que la gestion des masques de signaux.
L'interface basique : la fonction signal
La fonction signal est la méthode la plus simple pour intercepter une interruption. Elle associe un numéro de signal à une fonction de rappel (callback). Bien que facile à implémenter, elle présente des limitations en termes de portabilité et de contrôle fin.
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void declencher_alerte(int sig_num) {
printf("\nSignal [%d] intercepté avec succès.\n", sig_num);
}
int main() {
// Association du signal SIGINT (Ctrl+C) à notre gestionnaire
signal(SIGINT, declencher_alerte);
while(1) {
printf("Processus en cours d'exécution (PID: %d)...\n", getpid());
sleep(2);
}
return 0;
}
Il est important de noter que certains signaux, tels que SIGKILL et SIGSTOP, ne peuvent être ni interceptés ni ignorés. Par défaut, signal bloque le signal en cours de traitement pour éviter une récursion infinie, mais il ne gère pas nativement le blocage d'autres signaux simultanés.
Contrôle avancé avec sigaction
Pour des applications robustes, on privilégie sigaction. Cette structure offre un contrôle précis sur le comportement du processus lors de la réception d'un signal. Sa signature est la suivante :
int sigaction(int sig, const struct sigaction *act, struct sigaction *oact);
La structure sigaction contient notamment :
sa_handler: Pointeur vers la fonction de gestion.sa_mask: Ensemble de signaux à bloquer pendant l'exécution du gestionnaire.sa_flags: Options modifiant le comportement (ex:SA_RESTART,SA_NODEFER).
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void gestionnaire_robuste(int s) {
printf("Début du traitement du signal %d...\n", s);
for(int i = 0; i < 3; i++) {
printf("Opération critique en cours... %d/3\n", i + 1);
sleep(1);
}
printf("Fin du traitement.\n");
}
int main() {
struct sigaction configuration;
configuration.sa_handler = gestionnaire_robuste;
sigemptyset(&configuration.sa_mask);
// On bloque SIGQUIT pendant que l'on traite SIGINT
sigaddset(&configuration.sa_mask, SIGQUIT);
configuration.sa_flags = 0;
if (sigaction(SIGINT, &configuration, NULL) == -1) {
perror("Erreur lors de sigaction");
}
while(1) {
pause(); // Attente d'un signal
}
return 0;
}
Le paramètre sa_mask permet d'éviter l'entrelacement de signaux critiques. Si SIGQUIT survient pendant le traitement de SIGINT, il sera mis en attente et délivré immédiatmeent après la fin de la fonction gestionnaire_robuste.
Le masquage et la persistance des signaux
Le masquage consiste à retarder la délivrance d'un signal sans l'ignorer. Un signal envoyé à un processus qui le bloque restera "en attente" (pending) jusqu'à ce que le masque soit levé.
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void info_signal(int n) {
printf("Signal %d reçu après déblocage.\n", n);
}
int main() {
sigset_t nouveau_masque, ancien_masque;
signal(SIGUSR1, info_signal);
sigemptyset(&nouveau_masque);
sigaddset(&nouveau_masque, SIGUSR1);
// Blocage de SIGUSR1
printf("Blocage de SIGUSR1 pour 5 secondes. Envoyez 'kill -USR1 %d'\n", getpid());
sigprocmask(SIG_BLOCK, &nouveau_masque, &ancien_masque);
sleep(5);
printf("Déblocage imminent...\n");
// Restauration du masque original (déblocage)
sigprocmask(SIG_SETMASK, &ancien_masque, NULL);
sleep(2);
return 0;
}
Si plusieurs signaux identiques (non temps-réel) sont envoyés pendant la période de blocage, le système n'en délivre généralement qu'un seul lors du déblocage. Ce comportement démontre que les signaux standards ne sont pas accumulés dans une file d'attente infinie, mais mémorisés par un simple bit d'état dans le noyau.