Les signaux Linux : concepts fondamentaux

Ce forum est dédié à apprendre le développement de programmes user mode sur Linux

Moderator: Rick

Post Reply
Hydraxx
Site Admin
Posts: 46
Joined: Mon Jan 12, 2026 4:04 pm
Location: France
Contact:

Les signaux Linux : concepts fondamentaux

Post by Hydraxx »

Les signaux Linux : concepts fondamentaux, API et programmation en C

Objectif du cours

Ce cours explique les signaux Linux en programmation système C : ce qu'est un signal, à quoi il sert, comment le recevoir, comment l'envoyer, comment le bloquer, comment installer un gestionnaire propre avec sigaction(), et comment comprendre les notions de signal mask, pending signals et signal sets.

L'idée principale à retenir est simple :
Un signal Linux est une notification logicielle envoyée par le noyau à un processus ou à un thread pour l'avertir qu'un événement s'est produit ou pour modifier son exécution.
Par exemple :
  • Ctrl+C envoie généralement SIGINT au programme au premier plan.
  • kill PID envoie par défaut SIGTERM à un processus.
  • kill -9 PID envoie SIGKILL, qui tue le processus immédiatement.
  • Une faute mémoire provoque souvent SIGSEGV.
  • Un timer expiré peut provoquer SIGALRM.
  • La fin d'un processus fils provoque SIGCHLD chez le parent.
1. Définition simple d'un signal

Un signal est une sorte d'alerte envoyée à un processus ou à un thread.

Il sert à dire :
Un événement vient d'arriver, il faut réagir.
Cette réaction peut être :
  • terminer le processus ;
  • terminer le processus avec un core dump ;
  • ignorer le signal ;
  • stopper temporairement le processus ;
  • reprendre un processus stoppé ;
  • exécuter une fonction spéciale appelée signal handler.
Un signal n'est pas une requête I/O comme une IRP Windows. C'est plutôt une notification asynchrone.

Comparaison mentale :

Code: Select all

Windows IRP : requête I/O riche destinée à un driver.
Linux signal : notification asynchrone destinée à un processus ou un thread.
Un signal peut être généré par :
  • le noyau ;
  • un autre processus ;
  • le processus lui-même ;
  • le terminal ;
  • une erreur matérielle ou mémoire ;
  • un timer ;
  • un événement lié à un processus fils.
2. Exemples classiques de signaux

Voici les signaux les plus importants à connaître au début.

Code: Select all

SIGINT      interruption clavier, souvent Ctrl+C
SIGTERM     demande propre de terminaison
SIGKILL     terminaison forcée, impossible à intercepter
SIGSTOP     suspension forcée, impossible à intercepter
SIGCONT     reprise d'un processus stoppé
SIGSEGV     accès mémoire invalide
SIGBUS      erreur mémoire ou bus
SIGILL      instruction illégale
SIGFPE      erreur arithmétique
SIGPIPE     écriture dans un pipe ou socket fermé
SIGCHLD     un processus fils a changé d'état
SIGHUP      terminal déconnecté, souvent utilisé pour recharger une configuration
SIGQUIT     quit clavier, souvent Ctrl+\
SIGALRM     expiration d'un timer alarm()
SIGUSR1     signal utilisateur libre
SIGUSR2     signal utilisateur libre
Deux signaux sont très particuliers :

Code: Select all

SIGKILL
SIGSTOP
Ils ne peuvent pas être :
  • capturés ;
  • ignorés ;
  • bloqués.
Donc aucun handler ne peut être installé pour SIGKILL ou SIGSTOP.

3. Actions par défaut des signaux

Quand un signal est délivré à un processus, le noyau applique une action.

Cette action peut être l'action par défaut ou une action définie par le programme.

Les grandes catégories d'actions par défaut sont :
  • Term : terminer le processus.
  • Core : terminer le processus et générer un core dump.
  • Ign : ignorer le signal.
  • Stop : stopper temporairement le processus.
  • Cont : reprendre un processus stoppé.
Exemples :

Code: Select all

SIGTERM  -> termine le processus par défaut
SIGINT   -> termine le processus par défaut
SIGSEGV  -> termine avec core dump par défaut
SIGCHLD  -> souvent ignoré par défaut
SIGSTOP  -> stoppe le processus
SIGCONT  -> reprend le processus
4. Le handler de signal

Un signal handler est une fonction que ton programme installe pour réagir à un signal.

Exemple simple :

Code: Select all

void handler(int sig)
{
    write(1, "signal recu\n", 12);
}
Quand le signal arrive, le noyau interrompt temporairement le flux normal du programme et appelle cette fonction.

Mentalement :

Code: Select all

programme en cours d'exécution
        ↓
signal reçu
        ↓
le noyau interrompt le flux normal
        ↓
handler appelé
        ↓
retour au programme normal
Attention : dans un handler, on ne peut pas appeler n'importe quelle fonction. Certaines fonctions ne sont pas sûres dans un handler, notamment printf(). Pour écrire un message simple, on préfère souvent write(), car elle est async-signal-safe.

5. L'ancienne API : signal()

Header :

Code: Select all

#include <signal.h>
Prototype simplifié :

Code: Select all

void (*signal(int sig, void (*handler)(int)))(int);
En pratique, on l'utilise comme ça :

Code: Select all

signal(SIGINT, handler);
Exemple :

Code: Select all

#include <signal.h>
#include <unistd.h>

void handler(int sig)
{
    write(1, "Ctrl+C intercepte\n", 18);
}

int main(void)
{
    signal(SIGINT, handler);

    while(1){
        pause();
    }

    return 0;
}
Ce programme intercepte SIGINT. Quand l'utilisateur fait Ctrl+C, le programme affiche un message au lieu de se terminer immédiatement.

Mais en code sérieux, il vaut mieux utiliser sigaction(), car signal() est plus ancien et peut avoir des comportements moins portables selon les systèmes.

6. Envoyer un signal : kill()

La fonction kill() sert à envoyer un signal à un processus.

Header :

Code: Select all

#include <signal.h>
#include <sys/types.h>
Prototype :

Code: Select all

int kill(pid_t pid, int sig);
Attention : malgré son nom, kill() ne sert pas uniquement à tuer. Elle sert à envoyer un signal.

Exemples :

Code: Select all

kill(1234, SIGTERM);
kill(1234, SIGKILL);
kill(1234, SIGUSR1);
Interprétation du paramètre pid :
  • Si pid > 0, le signal est envoyé au processus dont le PID est donné.
  • Si pid == 0, le signal est envoyé aux processus du même groupe de processus que l'appelant.
  • Si pid == -1, le signal est envoyé à tous les processus que l'appelant a le droit de signaler.
  • Si pid < -1, le signal est envoyé au groupe de processus dont l'ID est -pid.
Exemple simple :

Code: Select all

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>

int main(int argc, char *argv[])
{
    pid_t pid;

    if(argc != 2){
        printf("usage: %s <pid>\n", argv[0]);
        return 1;
    }

    pid = atoi(argv[1]);

    if(kill(pid, SIGTERM) == -1){
        perror("kill");
        return 1;
    }

    printf("SIGTERM envoye au processus %d\n", pid);

    return 0;
}
7. Vérifier si un processus existe avec kill(pid, 0)

Cas très important :

Code: Select all

kill(pid, 0);
Ici, aucun vrai signal n'est envoyé.

Cela permet de vérifier si un processus existe et si on a le droit de lui envoyer un signal.

Résultat :

Code: Select all

0      -> le processus existe et on a la permission
EPERM  -> le processus existe, mais on n'a pas la permission
ESRCH  -> le processus n'existe pas
Exemple :

Code: Select all

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>

int main(int argc, char *argv[])
{
    pid_t pid;

    if(argc != 2){
        printf("usage: %s <pid>\n", argv[0]);
        return 1;
    }

    pid = atoi(argv[1]);

    if(kill(pid, 0) == 0){
        printf("le processus existe et on peut lui envoyer un signal\n");
    }
    else{
        if(errno == EPERM){
            printf("le processus existe, mais permission refusee\n");
        }
        else if(errno == ESRCH){
            printf("le processus n'existe pas\n");
        }
        else{
            perror("kill");
        }
    }

    return 0;
}
8. Envoyer un signal à soi-même : raise()

La fonction raise() permet à un processus de s'envoyer un signal à lui-même.

Prototype :

Code: Select all

#include <signal.h>

int raise(int sig);
Exemple :

Code: Select all

raise(SIGINT);
Dans un programme mono-thread, c'est mentalement proche de :

Code: Select all

kill(getpid(), SIGINT);
Exemple :

Code: Select all

#include <signal.h>
#include <unistd.h>

void handler(int sig)
{
    write(1, "signal envoye a soi-meme\n", 26);
}

int main(void)
{
    signal(SIGUSR1, handler);

    raise(SIGUSR1);

    return 0;
}
9. Envoyer un signal à un groupe : killpg()

La fonction killpg() envoie un signal à un groupe de processus.

Prototype :

Code: Select all

#include <signal.h>

int killpg(pid_t pgrp, int sig);
Elle est utile pour les shells, les jobs, les terminaux et les groupes de processus.

Exemple :

Code: Select all

killpg(1234, SIGTERM);
Cela envoie SIGTERM au groupe de processus 1234.

10. Afficher la description d'un signal

Pour afficher un texte humain associé à un signal, on peut utiliser :

Code: Select all

strsignal()
psignal()
Header :

Code: Select all

#include <string.h>
#include <signal.h>
Prototype :

Code: Select all

char *strsignal(int sig);
void psignal(int sig, const char *msg);
Exemple avec strsignal() :

Code: Select all

#include <signal.h>
#include <string.h>
#include <stdio.h>

int main(void)
{
    printf("SIGINT: %s\n", strsignal(SIGINT));
    printf("SIGSEGV: %s\n", strsignal(SIGSEGV));

    return 0;
}
Exemple avec psignal() :

Code: Select all

#include <signal.h>

int main(void)
{
    psignal(SIGINT, "description");
    psignal(SIGSEGV, "description");

    return 0;
}
11. sigset_t : représenter un ensemble de signaux

Un sigset_t est un ensemble de signaux.

Définition mentale :
sigset_t = une structure qui contient zéro, un ou plusieurs signaux.
On l'utilise pour :
  • définir quels signaux bloquer ;
  • définir quels signaux attendre ;
  • définir quels signaux sont pending ;
  • définir quels signaux bloquer pendant un handler.
API principales :

Code: Select all

int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int sig);
int sigdelset(sigset_t *set, int sig);
int sigismember(const sigset_t *set, int sig);
12. Initialiser un ensemble de signaux

Avant d'utiliser un sigset_t, il faut l'initialiser.

Pour créer un ensemble vide :

Code: Select all

sigemptyset(&set);
Pour créer un ensemble contenant tous les signaux :

Code: Select all

sigfillset(&set);
Exemple :

Code: Select all

#include <signal.h>
#include <stdio.h>

int main(void)
{
    sigset_t set;

    sigemptyset(&set);

    sigaddset(&set, SIGINT);
    sigaddset(&set, SIGTERM);

    if(sigismember(&set, SIGINT) == 1){
        printf("SIGINT est dans l'ensemble\n");
    }

    return 0;
}
13. Ajouter, retirer et tester un signal dans un set

Ajouter un signal :

Code: Select all

sigaddset(&set, SIGINT);
Retirer un signal :

Code: Select all

sigdelset(&set, SIGINT);
Tester si un signal est dans le set :

Code: Select all

if(sigismember(&set, SIGINT) == 1){
    printf("SIGINT est present\n");
}
Résumé :

Code: Select all

sigemptyset()  -> vide l'ensemble
sigfillset()   -> remplit avec tous les signaux
sigaddset()    -> ajoute un signal
sigdelset()    -> retire un signal
sigismember()  -> teste la présence d'un signal
14. Le signal mask

Chaque processus ou thread possède un signal mask.

Définition simple :
Le signal mask est l'ensemble des signaux actuellement bloqués pour un thread.
Si un signal est bloqué, il n'est pas délivré immédiatement. Il reste en attente.

Exemple mental :

Code: Select all

SIGINT est bloque
        ↓
l'utilisateur fait Ctrl+C
        ↓
SIGINT arrive, mais n'est pas livré
        ↓
SIGINT devient pending
        ↓
on débloque SIGINT
        ↓
SIGINT est livré
15. Modifier le signal mask : sigprocmask()

Prototype :

Code: Select all

#include <signal.h>

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
Le paramètre how indique comment modifier le masque.

Valeurs importantes :

Code: Select all

SIG_BLOCK    ajoute les signaux de set au masque
SIG_UNBLOCK  retire les signaux de set du masque
SIG_SETMASK  remplace complètement le masque par set
Exemple : bloquer SIGINT.

Code: Select all

#include <signal.h>
#include <stdio.h>
#include <unistd.h>

int main(void)
{
    sigset_t set;

    sigemptyset(&set);
    sigaddset(&set, SIGINT);

    if(sigprocmask(SIG_BLOCK, &set, NULL) == -1){
        perror("sigprocmask");
        return 1;
    }

    printf("SIGINT bloque pendant 10 secondes\n");
    sleep(10);

    printf("deblocage de SIGINT\n");

    if(sigprocmask(SIG_UNBLOCK, &set, NULL) == -1){
        perror("sigprocmask");
        return 1;
    }

    return 0;
}
Si l'utilisateur appuie sur Ctrl+C pendant les 10 secondes, le signal peut rester en attente. Quand il est débloqué, il est livré.

16. Les signaux pending

Un signal pending est un signal arrivé mais pas encore délivré.

Cela arrive souvent quand le signal est bloqué.

API :

Code: Select all

#include <signal.h>

int sigpending(sigset_t *set);
Cette fonction remplit set avec les signaux actuellement en attente.

Exemple :

Code: Select all

#include <signal.h>
#include <stdio.h>
#include <unistd.h>

int main(void)
{
    sigset_t blockSet;
    sigset_t pendingSet;

    sigemptyset(&blockSet);
    sigaddset(&blockSet, SIGINT);

    sigprocmask(SIG_BLOCK, &blockSet, NULL);

    printf("SIGINT bloque. Appuyez sur Ctrl+C maintenant.\n");
    sleep(5);

    sigpending(&pendingSet);

    if(sigismember(&pendingSet, SIGINT) == 1){
        printf("SIGINT est pending\n");
    }
    else{
        printf("SIGINT n'est pas pending\n");
    }

    sigprocmask(SIG_UNBLOCK, &blockSet, NULL);

    return 0;
}
17. Les signaux standards ne sont pas réellement mis en file

Point très important : les signaux standards ne sont pas vraiment comptés un par un.

Si un signal standard est bloqué et envoyé plusieurs fois, le noyau peut simplement retenir :
Ce signal est pending.
Il ne retient pas forcément combien de fois il est arrivé.

Exemple mental :

Code: Select all

SIGUSR1 bloque
1000 SIGUSR1 envoyes
on debloque SIGUSR1
le programme peut ne recevoir qu'un seul SIGUSR1
Donc un signal standard pending signifie souvent :
Ce signal est arrivé au moins une fois.
Il ne signifie pas forcément :
Ce signal est arrivé exactement N fois.
Les signaux temps réel ont un comportement plus adapté à la file d'attente, mais ce chapitre insiste surtout sur les signaux standards.

18. sigaction() : l'API propre pour installer un handler

La fonction sigaction() est l'API moderne et recommandée pour changer le comportement d'un signal.

Prototype :

Code: Select all

#include <signal.h>

int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact);
Elle permet de définir :
  • le handler à appeler ;
  • les signaux à bloquer pendant le handler ;
  • des options de comportement via des flags.
Structure importante :

Code: Select all

struct sigaction {
    void     (*sa_handler)(int);
    sigset_t sa_mask;
    int      sa_flags;
    void     (*sa_restorer)(void);
};
En pratique, on retient surtout :

Code: Select all

sa_handler
sa_mask
sa_flags
19. Champs importants de struct sigaction

sa_handler

C'est la fonction appelée quand le signal est livré.

Exemple :

Code: Select all

sa.sa_handler = handler;
On peut aussi utiliser :

Code: Select all

SIG_DFL  -> revenir au comportement par défaut
SIG_IGN  -> ignorer le signal
sa_mask

C'est l'ensemble de signaux à bloquer pendant l'exécution du handler.

Exemple :

Code: Select all

sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, SIGTERM);
Cela signifie que pendant le handler, SIGTERM sera temporairement bloqué.

sa_flags

Ce champ permet de modifier certains comportements.

Quelques flags importants :

Code: Select all

SA_RESTART    relance certains appels système interrompus
SA_RESETHAND  remet le comportement par défaut après la première livraison
SA_NODEFER    ne bloque pas automatiquement le signal courant pendant son handler
SA_SIGINFO    utilise un handler avancé avec des informations supplémentaires
Au début, le plus important à retenir est SA_RESTART.

20. Exemple propre avec sigaction()

Code: Select all

#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>

void handler(int sig)
{
    write(1, "SIGINT recu\n", 12);
}

int main(void)
{
    struct sigaction sa;

    memset(&sa, 0, sizeof(sa));

    sa.sa_handler = handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;

    if(sigaction(SIGINT, &sa, NULL) == -1){
        perror("sigaction");
        return 1;
    }

    while(1){
        pause();
    }

    return 0;
}
Explication :
  • On crée une variable struct sigaction.
  • On la met à zéro avec memset().
  • On définit le handler avec sa.sa_handler.
  • On initialise sa.sa_mask.
  • On met SA_RESTART dans sa.sa_flags.
  • On installe le comportement avec sigaction().
  • Le programme dort avec pause() jusqu'à recevoir un signal.
21. SIG_DFL et SIG_IGN

Quand on installe un comportement pour un signal, on peut utiliser deux constantes importantes.

SIG_DFL

Revient au comportement par défaut du signal.

Exemple :

Code: Select all

sa.sa_handler = SIG_DFL;
sigaction(SIGINT, &sa, NULL);
SIG_IGN

Ignore le signal.

Exemple :

Code: Select all

sa.sa_handler = SIG_IGN;
sigaction(SIGINT, &sa, NULL);
Cela signifie que Ctrl+C sera ignoré pour ce processus.

Attention : SIGKILL et SIGSTOP ne peuvent pas être ignorés.

22. Attendre un signal : pause()

La fonction pause() suspend l'exécution du processus jusqu'à la livraison d'un signal.

Prototype :

Code: Select all

#include <unistd.h>

int pause(void);
Elle retourne toujours -1 quand elle est interrompue par un handler, avec errno positionné à EINTR.

Exemple :

Code: Select all

#include <signal.h>
#include <unistd.h>
#include <stdio.h>

void handler(int sig)
{
    write(1, "signal recu\n", 12);
}

int main(void)
{
    signal(SIGINT, handler);

    printf("en attente d'un signal...\n");
    pause();

    printf("pause terminee\n");

    return 0;
}
Mentalement :

Code: Select all

pause()
   ↓
le processus dort
   ↓
un signal arrive
   ↓
le handler s'exécute
   ↓
pause() retourne -1 avec EINTR
23. Différence entre signal bloqué, ignoré et pending

Signal ignoré

Le signal arrive, mais le processus ne fait rien.

Exemple :

Code: Select all

SIG_IGN
Signal bloqué

Le signal arrive, mais il n'est pas livré immédiatement. Il reste en attente.

Exemple :

Code: Select all

sigprocmask(SIG_BLOCK, &set, NULL);
Signal pending

Le signal est arrivé, mais il attend d'être livré.

Exemple :

Code: Select all

sigpending(&set);
Résumé :

Code: Select all

ignore  -> le signal est jeté ou sans effet
bloque  -> le signal attend
pending -> le signal est en attente de livraison
24. Les signaux et les threads

Dans un programme multi-thread, les signaux deviennent plus subtils.

Un signal peut être :
  • adressé au processus ;
  • adressé à un thread précis ;
  • bloqué différemment selon les threads.
Chaque thread possède son propre signal mask.

Donc, dans un programme multi-thread, on préfère souvent bloquer les signaux dans tous les threads, puis créer un thread dédié qui les attend proprement avec des API adaptées comme sigwait().

Même si cette partie peut être approfondie plus tard, le point important est :
Le masque de signaux est une propriété du thread, pas seulement du processus entier.
25. Comparaison avec Windows

Les signaux Linux peuvent rappeler plusieurs mécanismes Windows, mais aucun équivalent n'est parfait.

Comparaison mentale :

Code: Select all

Linux signal                         Windows proche mentalement
--------------------------------------------------------------------------
SIGINT                               Console Ctrl Handler
SIGTERM                              demande de terminaison propre
SIGKILL                              TerminateProcess, mais pas exactement pareil
SIGSTOP / SIGCONT                    suspension / reprise
SIGSEGV                              exception Access Violation / SEH
SIGFPE                               exception arithmétique
SIGCHLD                              notification de fin/changement d'état d'un child process
SIGALRM                              timer notification
SIGUSR1 / SIGUSR2                    événement personnalisé
sigprocmask                          blocage temporaire de notifications
pause                                attente passive d'une notification
Différence importante :
Windows sépare beaucoup les mécanismes : events, handles, APC, exceptions, console handlers, TerminateProcess, WaitForSingleObject, etc. Linux utilise les signaux comme mécanisme Unix historique plus général.
Un signal est plus proche d'une notification asynchrone que d'un objet Event Windows.

26. Programme complet : handler + sigaction + pause

Code: Select all

#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

static volatile sig_atomic_t stop = 0;

void handler(int sig)
{
    if(sig == SIGINT){
        stop = 1;
        write(1, "SIGINT recu, arret demande\n", 28);
    }
}

int main(void)
{
    struct sigaction sa;

    memset(&sa, 0, sizeof(sa));

    sa.sa_handler = handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;

    if(sigaction(SIGINT, &sa, NULL) == -1){
        perror("sigaction");
        exit(EXIT_FAILURE);
    }

    printf("programme en attente. Appuyez sur Ctrl+C.\n");

    while(!stop){
        pause();
    }

    printf("fin propre du programme\n");

    return 0;
}
Pourquoi volatile sig_atomic_t ?

Parce qu'une variable modifiée dans un handler doit être manipulée avec prudence. Le type sig_atomic_t est prévu pour être lu/écrit de manière atomique vis-à-vis des handlers de signaux.

27. Programme complet : bloquer SIGINT puis vérifier pending

Code: Select all

#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(void)
{
    sigset_t blockSet;
    sigset_t pendingSet;

    sigemptyset(&blockSet);
    sigaddset(&blockSet, SIGINT);

    if(sigprocmask(SIG_BLOCK, &blockSet, NULL) == -1){
        perror("sigprocmask");
        exit(EXIT_FAILURE);
    }

    printf("SIGINT bloque pendant 5 secondes. Appuyez sur Ctrl+C.\n");
    sleep(5);

    if(sigpending(&pendingSet) == -1){
        perror("sigpending");
        exit(EXIT_FAILURE);
    }

    if(sigismember(&pendingSet, SIGINT) == 1){
        printf("SIGINT est en attente\n");
    }
    else{
        printf("SIGINT n'est pas en attente\n");
    }

    printf("deblocage de SIGINT\n");

    if(sigprocmask(SIG_UNBLOCK, &blockSet, NULL) == -1){
        perror("sigprocmask");
        exit(EXIT_FAILURE);
    }

    printf("fin\n");

    return 0;
}
Ce programme montre concrètement la différence entre :
  • un signal bloqué ;
  • un signal pending ;
  • un signal livré après déblocage.
28. Programme complet : envoyer un signal à un autre processus

Code: Select all

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>

int main(int argc, char *argv[])
{
    pid_t pid;
    int sig;

    if(argc != 3){
        printf("usage: %s <pid> <signal>\n", argv[0]);
        return 1;
    }

    pid = atoi(argv[1]);
    sig = atoi(argv[2]);

    if(kill(pid, sig) == -1){
        if(errno == ESRCH){
            printf("processus introuvable\n");
        }
        else if(errno == EPERM){
            printf("permission refusee\n");
        }
        else{
            perror("kill");
        }

        return 1;
    }

    printf("signal %d envoye au processus %d\n", sig, pid);

    return 0;
}
Exemple d'utilisation :

Code: Select all

./send_signal 1234 15
Ici, 15 correspond généralement à SIGTERM.

29. Ce qu'il faut éviter dans un handler

Un handler peut interrompre ton programme à n'importe quel moment.

Il faut donc éviter d'appeler des fonctions non sûres dans un handler.

À éviter dans un handler :
  • printf()
  • malloc()
  • free()
  • fonctions complexes de la libc
  • verrous/mutex si tu ne maîtrises pas parfaitement le contexte
À privilégier :
  • mettre à jour une variable globale de type volatile sig_atomic_t ;
  • utiliser write() pour afficher un message simple ;
  • faire le vrai travail plus tard dans la boucle principale.
Exemple propre :

Code: Select all

static volatile sig_atomic_t gotSigint = 0;

void handler(int sig)
{
    gotSigint = 1;
}
Puis dans le programme principal :

Code: Select all

if(gotSigint){
    // traitement propre ici
}
30. Résumé API à retenir

Headers principaux :

Code: Select all

#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
API fondamentales :

Code: Select all

signal()        ancienne API pour installer un handler
sigaction()     API propre pour installer un comportement de signal
kill()          envoyer un signal à un processus
raise()         s'envoyer un signal à soi-même
killpg()        envoyer un signal à un groupe de processus
pause()         attendre un signal
sigprocmask()   bloquer/débloquer des signaux
sigpending()    récupérer les signaux pending
strsignal()     obtenir une description textuelle d'un signal
psignal()       afficher une description d'un signal
API pour les ensembles de signaux :

Code: Select all

sigemptyset()   initialise un set vide
sigfillset()    initialise un set plein
sigaddset()     ajoute un signal dans un set
sigdelset()     retire un signal d'un set
sigismember()   teste si un signal est dans un set
Structures/types importants :

Code: Select all

sigset_t
struct sigaction
sig_atomic_t
pid_t
31. Résumé mental final

Définition à retenir :
Un signal Linux est une notification logicielle envoyée par le noyau à un processus ou à un thread pour l'avertir d'un événement ou modifier son exécution.
Le programme peut :
  • laisser l'action par défaut ;
  • ignorer le signal ;
  • installer un handler ;
  • bloquer temporairement le signal ;
  • voir s'il est pending ;
  • attendre passivement un signal.
Schéma mental :

Code: Select all

Signal envoyé
      ↓
Le noyau vérifie la cible et les permissions
      ↓
Le signal devient pending ou est livré directement
      ↓
Le signal est ignoré, bloque, stoppe, tue ou appelle un handler
      ↓
Le processus continue, s'arrête ou se termine selon le cas
32. Ce qu'il faut absolument retenir par coeur
  • SIGKILL et SIGSTOP ne peuvent pas être capturés, ignorés ou bloqués.
  • kill() envoie un signal, elle ne tue pas forcément.
  • kill(pid, 0) permet de tester l'existence d'un processus.
  • sigset_t représente un ensemble de signaux.
  • sigprocmask() sert à bloquer ou débloquer des signaux.
  • Un signal bloqué devient souvent pending.
  • Les signaux standards ne sont pas forcément comptés un par un.
  • sigaction() est préférable à signal().
  • pause() endort le programme jusqu'à la réception d'un signal.
  • Dans un handler, il faut éviter les fonctions non sûres comme printf().
33. Mini glossaire

Signal

Notification asynchrone envoyée à un processus ou thread.

Signal handler

Fonction appelée quand un signal est livré.

Disposition

Comportement associé à un signal : défaut, ignorer, handler.

Signal mask

Ensemble des signaux temporairement bloqués.

Pending signal

Signal arrivé mais pas encore livré.

sigset_t

Type représentant un ensemble de signaux.

sigaction

Structure/API moderne pour définir le comportement d'un signal.

SIG_DFL

Utiliser le comportement par défaut.

SIG_IGN

Ignorer le signal.

SA_RESTART

Flag qui permet de relancer certains appels système interrompus par un signal.

34. Conclusion

Les signaux sont un mécanisme central d'Unix/Linux. Ils permettent au noyau, au terminal, aux timers et aux autres processus de notifier un programme qu'un événement s'est produit.

Ils peuvent servir à terminer un programme, le stopper, le reprendre, signaler une erreur mémoire, indiquer qu'un fils est mort, ou transmettre une notification personnalisée.

Pour programmer proprement avec les signaux, il faut retenir trois idées :
  • installer les handlers avec sigaction() ;
  • utiliser sigset_t et sigprocmask() pour bloquer/débloquer les signaux ;
  • ne pas faire de traitement complexe directement dans un handler.
La phrase finale à retenir :
Un signal est une alerte logicielle du noyau vers un processus ou un thread, et le programme peut choisir comment réagir à cette alerte.

Who is online

Users browsing this forum: No registered users and 1 guest