Gestion du signal avec plusieurs threads sous Linux

119

Sous Linux, que se passe-t-il quand un programme (qui a éventuellement plusieurs threads) reçoit un signal, comme SIGTERM ou SIGHUP?

Quel thread intercepte le signal? Plusieurs threads peuvent-ils recevoir le même signal? Existe-t-il un thread spécial entièrement dédié à la gestion des signaux? Sinon, que se passe-t-il à l'intérieur du thread qui doit gérer le signal? Comment l'exécution reprend-elle une fois la routine du gestionnaire de signaux terminée?


la source

Réponses:

35

Ceci est légèrement nuancé, en fonction de la version du noyau Linux que vous utilisez.

En supposant que les threads 2.6 posix, et si vous parlez du système d'exploitation envoyant SIGTERM ou SIGHUP, le signal est envoyé au processus, qui est reçu et géré par le thread racine. En utilisant les threads POSIX, vous pouvez également envoyer SIGTERM à des threads individuels, mais je suppose que vous vous demandez ce qui se passe lorsque le système d'exploitation envoie le signal au processus.

Dans la version 2.6, SIGTERM provoquera la fermeture «proprement» des threads enfants, alors qu'en 2.4, les threads enfants étaient laissés dans un état indéterminé.

Alan
la source
Et que se passe-t-il à l'intérieur du thread racine lorsqu'un signal est reçu? Disons que j'ai écrit un gestionnaire de signal personnalisé pour SIGUSR1, et maintenant j'envoie ce signal au processus. Le thread racine recevra ce signal. C'est peut-être au milieu d'une fonction à ce moment-là. Ce qui va se passer?
1
si vous avez une configuration de gestionnaire, elle sera traitée comme une interruption, et le déroulement du programme s'arrêtera et votre gestionnaire personnalisé sera exécuté. Une fois qu'il est exécuté, le contrôle reviendra, en supposant que vous n'ayez rien fait pour modifier le flux normal (sortie, etc.).
Alan
Notez que ceci est spécifique à SIGUSR1, dont l'IIRC n'interrompt pas les appels système. Si vous avez essayé cela avec SIGINT par exemple, cela pourrait interrompre la lecture d'un flux, et lorsque vous revenez à la lecture, le flux peut renvoyer une erreur indiquant qu'il a été interrompu.
Alan
10
Je ne sais pas trop ce que l'on entend par "fil racine". Cela signifie-t-il que le gestionnaire de SIGTERM s'exécutera toujours dans le thread principal ou peut-il s'exécuter dans n'importe quel thread?
Stephen Nutt
3
Cette réponse , qui déclare qu'un fil arbitraire est choisi pour gérer le signal, contredit votre réponse.
user202729
135

pthreads(7) décrit que POSIX.1 requiert tous les threads dans un attribut de partage de processus, notamment:

  • dispositions du signal

POSIX.1 nécessite également que certains attributs soient distincts pour chaque thread, notamment:

La complete_signalroutine du noyau Linux a le bloc de code suivant - les commentaires sont très utiles:

/*
 * Now find a thread we can wake up to take the signal off the queue.
 *
 * If the main thread wants the signal, it gets first crack.
 * Probably the least surprising to the average bear.
 */
if (wants_signal(sig, p))
        t = p;
else if (!group || thread_group_empty(p))
        /*
         * There is just one thread and it does not need to be woken.
         * It will dequeue unblocked signals before it runs again.
         */
        return;
else {
        /*
         * Otherwise try to find a suitable thread.
         */
        t = signal->curr_target;
        while (!wants_signal(sig, t)) {
                t = next_thread(t);
                if (t == signal->curr_target)
                        /*
                         * No thread needs to be woken.
                         * Any eligible threads will see
                         * the signal in the queue soon.
                         */
                        return;
        }
        signal->curr_target = t;
}

/*
 * Found a killable thread.  If the signal will be fatal,
 * then start taking the whole group down immediately.
 */
if (sig_fatal(p, sig) &&
    !(signal->flags & SIGNAL_GROUP_EXIT) &&
    !sigismember(&t->real_blocked, sig) &&
    (sig == SIGKILL || !p->ptrace)) {
        /*
         * This signal will be fatal to the whole group.
         */

Ainsi, vous voyez que vous êtes en charge de l'endroit où les signaux sont délivrés:

Si votre processus a défini la disposition d'un signal sur SIG_IGNou SIG_DFL, alors le signal est ignoré (ou par défaut - kill, core ou ignore) pour tous les threads.

Si votre processus a défini la disposition d'un signal sur une routine de gestionnaire spécifique, vous pouvez contrôler quel thread recevra les signaux en manipulant des masques de signal de thread spécifiques à l'aide de pthread_sigmask(3). Vous pouvez désigner un thread pour tous les gérer, ou créer un thread par signal, ou tout mélange de ces options pour des signaux spécifiques, ou vous vous fiez au comportement par défaut actuel du noyau Linux pour fournir le signal au thread principal.

Certains signaux, cependant, sont spéciaux selon la signal(7)page de manuel:

Un signal peut être généré (et donc en attente) pour un processus dans son ensemble (par exemple, lorsqu'il est envoyé à l'aide de kill (2) ) ou pour un thread spécifique (par exemple, certains signaux, tels que SIGSEGV et SIGFPE, générés à la suite de l'exécution une instruction spécifique en langage machine est dirigée par thread, tout comme les signaux ciblés sur un thread spécifique utilisant pthread_kill (3) ). Un signal dirigé par le processus peut être délivré à l'un quelconque des threads dont le signal n'est actuellement pas bloqué. Si plus d'un des threads a le signal débloqué, alors le noyau choisit un thread arbitraire auquel fournir le signal.

sarnold
la source