Objectif du cours : comprendre les parties avancées des signaux Linux, au-delà des bases comme kill(), signal(), sigaction(), sigset_t et sigprocmask().
1. Rappel rapide : un signal, c’est quoi ?
Un signal est une notification envoyée à un processus ou à un thread.
Il peut servir à :
- interrompre un programme ;
- demander un arrêt propre ;
- notifier qu’un enfant a terminé ;
- prévenir qu’un timer a expiré ;
- signaler une faute CPU ou mémoire ;
- réveiller un processus bloqué ;
- transmettre une petite information avec les signaux temps réel.
Code: Select all
SIGINT // Ctrl+C
SIGTERM // demande d'arrêt propre
SIGKILL // arrêt forcé, impossible à intercepter
SIGSTOP // pause forcée, impossible à intercepter
SIGCONT // reprise après stop
SIGSEGV // segmentation fault
SIGFPE // erreur arithmétique
SIGILL // instruction illégale
SIGBUS // erreur mémoire / bus
SIGCHLD // enfant terminé ou changé d'état
SIGUSR1 // signal utilisateur 1
SIGUSR2 // signal utilisateur 2
Code: Select all
kill(pid, SIGTERM);
2. Les 3 dispositions possibles d’un signal
Quand un signal arrive, Linux peut appliquer une disposition.
1. Action par défaut
Chaque signal possède une action par défaut.
Exemples :
- SIGTERM termine le processus ;
- SIGSEGV termine le processus et peut produire un core dump ;
- SIGCHLD est souvent ignoré par défaut ;
- SIGSTOP stoppe le processus ;
- SIGCONT reprend le processus.
Exemple :
Code: Select all
signal(SIGINT, SIG_IGN);
Mais certains signaux ne peuvent pas être ignorés :
Code: Select all
SIGKILL
SIGSTOP
Un handler est une fonction appelée quand le signal arrive.
Exemple simple :
Code: Select all
#include <signal.h>
#include <unistd.h>
void handler(int sig)
{
write(1, "signal recu\n", 12);
}
int main(void)
{
struct sigaction sa = {0};
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
while (1)
pause();
return 0;
}
L’ancienne fonction signal() existe encore, mais elle a eu des comportements historiques différents selon les systèmes UNIX.
Sur d’anciens systèmes, après l’appel du handler, le comportement pouvait revenir à SIG_DFL. Il fallait donc parfois réinstaller le handler dans le handler lui-même.
Aujourd’hui, sous Linux/glibc, signal() est plus stable, mais pour du code système propre, on préfère sigaction().
À retenir :
Code: Select all
signal() = ancien, simple, moins précis
sigaction() = moderne, robuste, contrôlable
- la fonction handler ;
- les signaux bloqués pendant le handler ;
- les options comme SA_RESTART ou SA_SIGINFO ;
- l’ancien comportement du signal.
Code: Select all
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
};
Un core dump est un fichier contenant l’état mémoire d’un programme au moment du crash.
Cela permet de debugger après coup avec gdb.
Signaux pouvant produire un core dump :
Code: Select all
SIGSEGV
SIGABRT
SIGFPE
SIGILL
SIGBUS
Code: Select all
int *p = NULL;
*p = 123; // SIGSEGV
Code: Select all
ulimit -c unlimited
Code: Select all
gdb ./programme core
Code: Select all
bt
info registers
frame 0
list
5. Signaux de stop et de reprise
Certains signaux servent à suspendre ou reprendre un processus.
Code: Select all
SIGSTOP // stop forcé, impossible à intercepter
SIGTSTP // Ctrl+Z, peut être intercepté
SIGCONT // reprise du processus
Code: Select all
kill -STOP 1234
kill -CONT 1234
Code: Select all
SIGSTOP = pause forcée, non interceptable
SIGTSTP = pause demandée par le terminal, interceptable
SIGCONT = reprise
Exceptions importantes :
- SIGKILL peut tuer un processus stoppé ;
- SIGCONT peut reprendre un processus stoppé.
Signal synchrone
Un signal synchrone est causé directement par l’instruction exécutée.
Exemples :
Code: Select all
SIGSEGV // accès mémoire invalide
SIGFPE // erreur arithmétique
SIGILL // instruction invalide
SIGBUS // erreur mémoire ou mapping incorrect
Code: Select all
int *p = NULL;
*p = 10; // SIGSEGV causé par cette instruction
Signal asynchrone
Un signal asynchrone arrive depuis l’extérieur ou depuis un événement indépendant.
Exemples :
Code: Select all
SIGINT // Ctrl+C
SIGTERM // kill
SIGALRM // timer
SIGCHLD // enfant terminé
SIGUSR1 // signal utilisateur
SIGUSR2
Code: Select all
synchrone = causé par l’instruction actuelle
asynchrone = arrive depuis l’extérieur
Certains signaux sont générés par une exception CPU ou une erreur mémoire.
Exemples :
- SIGSEGV : accès à une adresse mémoire interdite ;
- SIGBUS : erreur d’accès mémoire plus bas niveau, mapping invalide, alignement sur certaines architectures ;
- SIGILL : instruction CPU invalide ;
- SIGFPE : erreur arithmétique, division entière par zéro, exception flottante selon configuration.
Code: Select all
int a = 1;
int b = 0;
int c = a / b; // SIGFPE possible
Un handler de SIGSEGV ne “répare” pas magiquement le programme.
Si le handler retourne normalement, le programme peut reprendre sur la même instruction fautive, donc recrasher immédiatement.
Mauvais exemple :
Code: Select all
void handler(int sig)
{
// ne corrige rien
}
À retenir :
Code: Select all
SIGSEGV/SIGBUS/SIGILL/SIGFPE = souvent état grave du programme
Quand plusieurs signaux sont pending, l’ordre de livraison peut être subtil.
Pour les signaux standards, il ne faut pas construire un protocole qui dépend fortement de l’ordre exact.
Point très important :
Les signaux standards ne sont pas une vraie file d’attente.
Si le même signal standard est envoyé plusieurs fois pendant qu’il est bloqué, le noyau peut retenir seulement :
Code: Select all
SIGUSR1 est pending
Code: Select all
SIGUSR1 est arrivé 10 fois
Code: Select all
kill -USR1 pid
kill -USR1 pid
kill -USR1 pid
À retenir :
Code: Select all
signal standard bloqué 10 fois envoyé = souvent 1 seule livraison
Un signal peut interrompre un appel système bloquant.
Exemples d’appels pouvant être concernés :
Code: Select all
read()
write()
accept()
recv()
send()
wait()
sleep()
nanosleep()
poll()
select()
epoll_wait()
Code: Select all
ssize_t n = read(fd, buffer, sizeof(buffer));
if (n == -1 && errno == EINTR) {
// l'appel a été interrompu par un signal
}
Exemple robuste :
Code: Select all
ssize_t safe_read(int fd, void *buf, size_t size)
{
ssize_t ret;
do {
ret = read(fd, buf, size);
} while (ret == -1 && errno == EINTR);
return ret;
}
Code: Select all
sa.sa_flags = SA_RESTART;
Mais attention :
- tous les appels ne sont pas forcément redémarrés ;
- certaines fonctions retournent quand même EINTR ;
- il faut encore savoir gérer EINTR dans du code sérieux.
Code: Select all
SA_RESTART aide, mais ne remplace pas une vraie gestion de EINTR
Un handler doit rester minuscule.
Dans un handler, il ne faut pas appeler n’importe quelle fonction.
À éviter :
Code: Select all
printf()
malloc()
free()
pthread_mutex_lock()
std::cout
fopen()
fprintf()
exit()
Parce que beaucoup de fonctions ne sont pas async-signal-safe.
Elles peuvent utiliser des locks internes, de la mémoire dynamique ou un état global déjà interrompu par le signal.
Pattern propre :
Code: Select all
#include <signal.h>
#include <unistd.h>
volatile sig_atomic_t running = 1;
void handler(int sig)
{
running = 0;
}
int main(void)
{
struct sigaction sa = {0};
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sigaction(SIGINT, &sa, NULL);
while (running) {
// travail
}
return 0;
}
Code: Select all
dans un handler :
- modifier un sig_atomic_t
- écrire avec write()
- faire le minimum
Dans struct sigaction, le champ sa_mask permet de dire quels signaux doivent être bloqués pendant l’exécution du handler.
Exemple :
Code: Select all
struct sigaction sa = {0};
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, SIGTERM);
sigaction(SIGINT, &sa, NULL);
Code: Select all
quand le handler de SIGINT tourne,
SIGTERM est temporairement bloqué
- éviter qu’un handler soit interrompu par certains signaux ;
- éviter des problèmes de réentrance ;
- protéger une zone courte du handler.
Les signaux temps réel ont été ajoutés à POSIX pour corriger certaines limites des signaux standards.
Ils vont de :
Code: Select all
SIGRTMIN
Code: Select all
SIGRTMAX
Code: Select all
SIGRTMIN
SIGRTMIN + 1
SIGRTMIN + 2
- ils peuvent être mis en file ;
- plusieurs occurrences peuvent être conservées ;
- ils peuvent transporter une donnée ;
- leur ordre est mieux défini.
Code: Select all
signal standard envoyé 10 fois pendant blocage
=> peut être livré 1 fois
signal temps réel envoyé 10 fois pendant blocage
=> peut être livré 10 fois
Il existe une limite au nombre de signaux temps réel pouvant être en file. Si la limite est atteinte, sigqueue() peut échouer.
13. sigqueue() : envoyer signal + donnée
kill() envoie seulement un signal :
Code: Select all
kill(pid, SIGUSR1);
Prototype :
Code: Select all
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
Code: Select all
union sigval {
int sival_int;
void *sival_ptr;
};
Code: Select all
#include <signal.h>
#include <unistd.h>
int main(void)
{
pid_t pid = 1234;
union sigval val;
val.sival_int = 42;
sigqueue(pid, SIGRTMIN, val);
return 0;
}
Code: Select all
kill() = signal simple
sigqueue() = signal + petite donnée
Pour recevoir les informations envoyées avec sigqueue(), il faut utiliser un handler avancé.
Handler simple :
Code: Select all
void handler(int sig)
{
}
Code: Select all
void handler(int sig, siginfo_t *info, void *context)
{
}
Code: Select all
struct sigaction sa = {0};
sa.sa_sigaction = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO;
sigaction(SIGRTMIN, &sa, NULL);
Code: Select all
info->si_pid // PID de l'émetteur
info->si_uid // UID de l'émetteur
info->si_code // origine du signal
info->si_value // donnée envoyée par sigqueue()
info->si_value.sival_int
info->si_value.sival_ptr
Même avec SA_SIGINFO, un handler reste un handler. Il faut éviter printf(), malloc(), etc. Pour traiter proprement l’information, on peut utiliser sigwaitinfo() ou signalfd().
15. sigsuspend() : attendre un signal sans race condition
Problème classique :
Code: Select all
sigprocmask(SIG_UNBLOCK, &set, NULL);
pause();
sigsuspend() corrige ce problème.
Prototype :
Code: Select all
int sigsuspend(const sigset_t *mask);
- remplacer temporairement le masque de signaux ;
- dormir jusqu’à recevoir un signal ;
- laisser le handler s’exécuter ;
- restaurer l’ancien masque.
Code: Select all
#define _POSIX_C_SOURCE 200809L
#include <signal.h>
#include <unistd.h>
volatile sig_atomic_t got_sigint = 0;
void handler(int sig)
{
got_sigint = 1;
}
int main(void)
{
sigset_t block_mask;
sigset_t old_mask;
struct sigaction sa = {0};
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sigaction(SIGINT, &sa, NULL);
sigemptyset(&block_mask);
sigaddset(&block_mask, SIGINT);
sigprocmask(SIG_BLOCK, &block_mask, &old_mask);
while (!got_sigint) {
sigsuspend(&old_mask);
}
sigprocmask(SIG_SETMASK, &old_mask, NULL);
return 0;
}
Code: Select all
sigsuspend() = attendre un signal proprement avec un handler
sigwaitinfo() permet d’attendre un signal de manière synchrone.
Cela veut dire :
Code: Select all
au lieu que Linux appelle un handler,
ton code attend et récupère le signal comme un retour de fonction
Code: Select all
#include <signal.h>
int sigwaitinfo(const sigset_t *set, siginfo_t *info);
- préparer un sigset_t ;
- bloquer les signaux qu’on veut attendre ;
- appeler sigwaitinfo().
Code: Select all
#define _POSIX_C_SOURCE 200809L
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
int main(void)
{
sigset_t set;
siginfo_t info;
printf("PID: %d\n", getpid());
sigemptyset(&set);
sigaddset(&set, SIGUSR1);
sigaddset(&set, SIGTERM);
if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) {
perror("sigprocmask");
return 1;
}
for (;;) {
int sig = sigwaitinfo(&set, &info);
if (sig == -1) {
if (errno == EINTR)
continue;
perror("sigwaitinfo");
return 1;
}
printf("signal recu: %d\n", sig);
printf("pid emetteur: %ld\n", (long)info.si_pid);
printf("uid emetteur: %ld\n", (long)info.si_uid);
if (sig == SIGTERM)
break;
}
return 0;
}
Code: Select all
gcc waitinfo.c -o waitinfo
./waitinfo
kill -USR1 <pid>
kill -TERM <pid>
Les signaux attendus par sigwaitinfo() doivent être bloqués avant.
Sinon, le signal peut être traité par son comportement normal ou par un handler avant que sigwaitinfo() ne le récupère.
À retenir :
Code: Select all
sigwaitinfo() = attendre et consommer un signal bloqué
sigwaitinfo() peut récupérer la donnée envoyée par sigqueue().
Récepteur :
Code: Select all
#define _POSIX_C_SOURCE 200809L
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
int main(void)
{
sigset_t set;
siginfo_t info;
printf("PID recepteur: %d\n", getpid());
sigemptyset(&set);
sigaddset(&set, SIGRTMIN);
sigprocmask(SIG_BLOCK, &set, NULL);
for (;;) {
int sig = sigwaitinfo(&set, &info);
if (sig == -1) {
if (errno == EINTR)
continue;
perror("sigwaitinfo");
return 1;
}
printf("signal: %d\n", sig);
printf("envoye par PID: %ld\n", (long)info.si_pid);
printf("valeur: %d\n", info.si_value.sival_int);
}
return 0;
}
Code: Select all
#define _POSIX_C_SOURCE 200809L
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv)
{
if (argc != 3) {
fprintf(stderr, "usage: %s <pid> <valeur>\n", argv[0]);
return 1;
}
pid_t pid = (pid_t)strtol(argv[1], NULL, 10);
union sigval val;
val.sival_int = atoi(argv[2]);
if (sigqueue(pid, SIGRTMIN, val) == -1) {
perror("sigqueue");
return 1;
}
return 0;
}
Code: Select all
gcc receiver.c -o receiver
gcc sender.c -o sender
Code: Select all
./receiver
./sender <pid> 100
./sender <pid> 200
./sender <pid> 300
18. sigtimedwait() : attendre avec timeout
sigtimedwait() est comme sigwaitinfo(), mais avec un temps maximum d’attente.
Prototype :
Code: Select all
int sigtimedwait(const sigset_t *set,
siginfo_t *info,
const struct timespec *timeout);
Code: Select all
#define _POSIX_C_SOURCE 200809L
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
int main(void)
{
sigset_t set;
siginfo_t info;
struct timespec timeout;
sigemptyset(&set);
sigaddset(&set, SIGUSR1);
sigprocmask(SIG_BLOCK, &set, NULL);
timeout.tv_sec = 5;
timeout.tv_nsec = 0;
printf("attente de SIGUSR1 pendant 5 secondes...\n");
int sig = sigtimedwait(&set, &info, &timeout);
if (sig == -1) {
if (errno == EAGAIN) {
printf("timeout: aucun signal recu\n");
return 0;
}
perror("sigtimedwait");
return 1;
}
printf("signal recu: %d\n", sig);
return 0;
}
Code: Select all
sigwaitinfo() = attente infinie
sigtimedwait() = attente avec timeout
sigaction() + handler
Code: Select all
le signal arrive
le code courant est interrompu
Linux appelle le handler
le code reprend après
Inconvénient : handler dangereux si on fait trop de choses dedans.
sigsuspend()
Code: Select all
le programme dort
un signal arrive
le handler s'exécute
sigsuspend() retourne
Inconvénient : nécessite encore un handler.
sigwaitinfo()
Code: Select all
le signal est bloqué
le programme attend avec sigwaitinfo()
sigwaitinfo() retourne le signal et ses infos
pas besoin de handler
Résumé :
Code: Select all
sigaction() = réaction asynchrone
sigsuspend() = dormir jusqu'à un signal + handler
sigwaitinfo() = attendre/consommer un signal sans handler
signalfd() est une API Linux non standard POSIX.
Elle transforme des signaux en événements lisibles sur un descripteur de fichier.
Prototype :
Code: Select all
#include <sys/signalfd.h>
int signalfd(int fd, const sigset_t *mask, int flags);
Code: Select all
signal -> signalfd -> read()
Code: Select all
read(sfd, &fdsi, sizeof(fdsi));
Code: Select all
ssi_signo // numéro du signal
ssi_pid // PID de l'émetteur
ssi_uid // UID de l'émetteur
ssi_int // valeur entière envoyée par sigqueue()
ssi_ptr // pointeur envoyé par sigqueue()
Code: Select all
#define _GNU_SOURCE
#include <sys/signalfd.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
sigset_t mask;
int sfd;
struct signalfd_siginfo fdsi;
printf("PID: %d\n", getpid());
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGTERM);
sigaddset(&mask, SIGUSR1);
if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) {
perror("sigprocmask");
return 1;
}
sfd = signalfd(-1, &mask, 0);
if (sfd == -1) {
perror("signalfd");
return 1;
}
for (;;) {
ssize_t s = read(sfd, &fdsi, sizeof(fdsi));
if (s != sizeof(fdsi)) {
perror("read");
return 1;
}
printf("signal recu via fd: %u\n", fdsi.ssi_signo);
printf("pid emetteur: %u\n", fdsi.ssi_pid);
printf("uid emetteur: %u\n", fdsi.ssi_uid);
if (fdsi.ssi_signo == SIGTERM)
break;
if (fdsi.ssi_signo == SIGINT)
break;
}
close(sfd);
return 0;
}
Code: Select all
gcc signalfd_demo.c -o signalfd_demo
./signalfd_demo
kill -USR1 <pid>
kill -TERM <pid>
Code: Select all
signalfd() = signaux lisibles avec read()
signalfd() devient très intéressant dans un serveur.
Sans signalfd :
Code: Select all
sockets gérées dans epoll
signaux gérés par handlers
Code: Select all
sockets + signaux dans la même boucle epoll
Code: Select all
epoll_wait()
-> socket serveur lisible
-> socket client lisible
-> signalfd lisible
Code: Select all
#define _GNU_SOURCE
#include <sys/signalfd.h>
#include <sys/epoll.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
sigset_t mask;
int sfd;
int epfd;
struct epoll_event ev;
struct epoll_event events[8];
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGTERM);
if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) {
perror("sigprocmask");
return 1;
}
sfd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC);
if (sfd == -1) {
perror("signalfd");
return 1;
}
epfd = epoll_create1(EPOLL_CLOEXEC);
if (epfd == -1) {
perror("epoll_create1");
return 1;
}
ev.events = EPOLLIN;
ev.data.fd = sfd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, sfd, &ev) == -1) {
perror("epoll_ctl");
return 1;
}
printf("PID: %d\n", getpid());
printf("Ctrl+C ou kill -TERM pour quitter proprement\n");
for (;;) {
int n = epoll_wait(epfd, events, 8, -1);
if (n == -1) {
perror("epoll_wait");
return 1;
}
for (int i = 0; i < n; i++) {
if (events[i].data.fd == sfd) {
struct signalfd_siginfo fdsi;
ssize_t r = read(sfd, &fdsi, sizeof(fdsi));
if (r != sizeof(fdsi))
continue;
printf("signal recu: %u\n", fdsi.ssi_signo);
if (fdsi.ssi_signo == SIGINT || fdsi.ssi_signo == SIGTERM) {
printf("arret propre\n");
close(sfd);
close(epfd);
return 0;
}
}
}
}
}
Code: Select all
signalfd + epoll = très propre pour serveur Linux
Les signaux peuvent être vus comme une forme d’IPC, mais très limitée.
IPC signifie inter-process communication.
Les signaux permettent à un processus de notifier un autre processus.
Exemples :
Code: Select all
kill(pid, SIGTERM); // demande d'arrêt
kill(pid, SIGHUP); // recharge config possible
sigqueue(pid, SIGRTMIN, val); // notification + petite valeur
- Les signaux standards ne sont pas vraiment queueés.
- Les handlers sont asynchrones.
- On ne peut transporter qu’une petite donnée avec sigqueue().
- Ce n’est pas adapté aux gros messages.
- Ce n’est pas adapté aux protocoles complexes.
- L’ordre peut devenir subtil.
Code: Select all
signal = notification
pipe/socket/shared memory = vraie communication
- arrêt propre ;
- rechargement de configuration ;
- réveil d’un processus ;
- notification simple ;
- timer ;
- gestion d’enfant avec SIGCHLD.
- envoyer des structures complètes ;
- faire un protocole réseau ;
- envoyer beaucoup d’événements ;
- remplacer une socket ;
- transférer un fichier ;
- faire une messagerie.
Le chapitre mentionne aussi d’anciennes APIs.
System V :
Code: Select all
sigset()
sighold()
sigrelse()
sigignore()
sigpause()
Code: Select all
sigvec()
sigblock()
sigsetmask()
siggetmask()
Équivalents modernes :
Code: Select all
signal(), sigset() -> sigaction()
sighold(), sigblock() -> sigprocmask(SIG_BLOCK)
sigrelse() -> sigprocmask(SIG_UNBLOCK)
sigpause() -> sigsuspend()
sigvec() -> sigaction()
Code: Select all
ancien code UNIX = possible de voir ces APIs
code moderne Linux/POSIX = sigaction(), sigprocmask(), sigsuspend()
Code: Select all
sigaction()
installer une réaction à un signal
sigemptyset()
vider un ensemble de signaux
sigaddset()
ajouter un signal dans un ensemble
sigdelset()
retirer un signal d’un ensemble
sigismember()
tester si un signal est dans un ensemble
sigprocmask()
bloquer / débloquer / remplacer le masque de signaux
sigpending()
voir les signaux en attente
sigsuspend()
attendre un signal avec changement atomique du masque
kill()
envoyer un signal simple
raise()
envoyer un signal à soi-même
sigqueue()
envoyer un signal avec une petite donnée
sigwaitinfo()
attendre un signal bloqué et obtenir siginfo_t
sigtimedwait()
comme sigwaitinfo(), mais avec timeout
signalfd()
recevoir les signaux via un file descriptor
epoll()
surveiller signalfd avec d’autres fd
Code: Select all
kill()
envoie un signal simple
sigqueue()
envoie un signal + int/pointeur
sigaction()
installe un handler
sigsuspend()
dort jusqu'à un signal, puis handler
sigwaitinfo()
attend et consomme le signal sans handler
sigtimedwait()
pareil avec timeout
signalfd()
transforme les signaux en événements read()
Pattern 1 : arrêt propre avec sig_atomic_t
Code: Select all
volatile sig_atomic_t running = 1;
void handler(int sig)
{
running = 0;
}
int main(void)
{
struct sigaction sa = {0};
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
while (running) {
// boucle principale
}
return 0;
}
Code: Select all
sigprocmask(SIG_BLOCK, &block_set, &old_set);
while (!flag) {
sigsuspend(&old_set);
}
sigprocmask(SIG_SETMASK, &old_set, NULL);
Code: Select all
sigemptyset(&set);
sigaddset(&set, SIGUSR1);
sigprocmask(SIG_BLOCK, &set, NULL);
sigwaitinfo(&set, &info);
Code: Select all
sigprocmask(SIG_BLOCK, &mask, NULL);
sfd = signalfd(-1, &mask, SFD_NONBLOCK);
epoll_ctl(epfd, EPOLL_CTL_ADD, sfd, &ev);
Piège 1 : croire qu’un signal bloqué est supprimé
Faux.
Code: Select all
bloqué = pending
ignoré = jeté
Faux.
Code: Select all
10 SIGUSR1 standards pendant blocage
=> pas forcément 10 handlers
Mauvais en production.
Utiliser plutôt :
Code: Select all
write()
volatile sig_atomic_t
Un signal peut interrompre un syscall.
Code: Select all
if (ret == -1 && errno == EINTR)
recommencer ou gérer proprement
Mauvais pattern.
Il faut :
Code: Select all
sigprocmask(SIG_BLOCK, &set, NULL);
sigwaitinfo(&set, &info);
Mauvais choix.
Utiliser :
Code: Select all
pipe
socket Unix
socket TCP
message queue
shared memory
eventfd
Pour bien comprendre, fais ce mini-projet :
Programme 1 : receiver
- affiche son PID ;
- bloque SIGRTMIN ;
- attend avec sigwaitinfo() ;
- affiche PID émetteur + valeur reçue ;
- quitte si valeur == 0.
- prend PID + valeur en argument ;
- envoie SIGRTMIN avec sigqueue().
- remplace sigwaitinfo() par signalfd() ;
- mets signalfd dans epoll ;
- ajoute stdin dans epoll ;
- si l’utilisateur tape quit, arrêt propre.
30. Résumé final
À retenir absolument :
Code: Select all
1. sigaction() remplace signal() dans du code propre.
2. Les handlers doivent rester minuscules.
3. Les signaux synchrones viennent du code fautif :
SIGSEGV, SIGBUS, SIGILL, SIGFPE.
4. Les signaux asynchrones viennent de l’extérieur :
SIGINT, SIGTERM, SIGALRM, SIGCHLD.
5. Les signaux standards ne sont pas une vraie file d’attente.
6. Les signaux temps réel sont queueés et peuvent transporter une petite donnée.
7. sigqueue() = signal + donnée.
8. SA_SIGINFO + siginfo_t = infos détaillées sur le signal.
9. sigsuspend() évite la race condition entre déblocage et pause.
10. sigwaitinfo() permet d’attendre un signal sans handler.
11. sigtimedwait() ajoute un timeout.
12. signalfd() transforme les signaux en fd lisible avec read().
13. signalfd + epoll est excellent pour les serveurs Linux.
14. Les signaux sont utiles pour notifier, pas pour transporter une grosse logique.
Code: Select all
Les signaux Linux sont faits pour interrompre, notifier et synchroniser légèrement.
Pour communiquer vraiment entre processus, il faut plutôt utiliser pipe, socket, eventfd, shared memory ou message queue.
Code: Select all
sigaction -> installer handler
sa_mask -> signaux bloqués pendant handler
SA_RESTART -> relancer certains syscalls interrompus
SA_SIGINFO -> handler avancé avec siginfo_t
siginfo_t -> infos signal : PID, UID, valeur, origine
sigqueue -> envoyer signal + valeur
SIGRTMIN/MAX -> signaux temps réel
sigwaitinfo -> attendre signal bloqué sans handler
sigtimedwait -> attendre avec timeout
sigsuspend -> attendre avec handler sans race
signalfd -> recevoir signaux via read()
EINTR -> syscall interrompu par signal
core dump -> image mémoire du crash
