Signalverarbeitung mit mehreren Threads unter Linux

119

Was passiert unter Linux, wenn ein Programm (das möglicherweise mehrere Threads hat) ein Signal wie SIGTERM oder SIGHUP empfängt?

Welcher Thread fängt das Signal ab? Können mehrere Threads das gleiche Signal erhalten? Gibt es einen speziellen Thread, der ausschließlich dem Umgang mit Signalen gewidmet ist? Wenn nicht, was passiert innerhalb des Threads, der das Signal verarbeiten soll? Wie wird die Ausführung fortgesetzt, nachdem die Signalhandler-Routine beendet ist?


quelle

Antworten:

35

Dies ist leicht nuanciert, je nachdem, welche Version des Linux-Kernels Sie verwenden.

Unter der Annahme von 2.6-Posix-Threads und wenn Sie über das Betriebssystem sprechen, das SIGTERM oder SIGHUP sendet, wird das Signal an den Prozess gesendet, der vom Root-Thread empfangen und verarbeitet wird. Mit POSIX-Threads können Sie SIGTERM auch an einzelne Threads senden, aber ich vermute, Sie fragen, was passiert, wenn das Betriebssystem das Signal an den Prozess sendet.

In 2.6 bewirkt SIGTERM, dass untergeordnete Threads "sauber" beendet werden, während unter 2.4 untergeordnete Threads in einem unbestimmten Zustand belassen wurden.

Alan
quelle
Und was passiert im Root-Thread, wenn ein Signal empfangen wird? Angenommen, ich habe einen benutzerdefinierten Signalhandler für SIGUSR1 geschrieben und sende dieses Signal jetzt an den Prozess. Der Root-Thread erhält dieses Signal. Vielleicht ist es in diesem Moment mitten in einer Funktion. Was wird passieren?
1
Wenn Sie ein Handler-Setup haben, wird es als Interrupt behandelt und der Programmfluss wird angehalten und Ihr benutzerdefinierter Handler wird ausgeführt. Sobald es ausgeführt wird, kehrt die Steuerung zurück, vorausgesetzt, Sie haben nichts getan, um den normalen Fluss (Beenden usw.) zu ändern.
Alan
Beachten Sie, dass dies spezifisch für SIGUSR1 ist, bei dem IIRC Systemaufrufe nicht unterbricht. Wenn Sie dies beispielsweise mit SIGINT versucht haben, kann dies das Lesen eines Streams unterbrechen. Wenn Sie zum Lesen zurückkehren, gibt der Stream möglicherweise den Fehler zurück, dass er unterbrochen wurde.
Alan
10
Ich bin ein wenig verwirrt darüber, was unter "Root-Thread" zu verstehen ist. Bedeutet dies, dass der Handler für SIGTERM immer im Hauptthread ausgeführt wird, oder kann er in einem beliebigen Thread ausgeführt werden?
Stephen Nutt
3
Diese Antwort , die besagt, dass ein beliebiger Thread ausgewählt wurde, um das Signal zu verarbeiten, widerspricht Ihrer Antwort.
user202729
134

pthreads(7) beschreibt, dass POSIX.1 alle Threads in einem Prozess erfordert, die Attribute gemeinsam nutzen, einschließlich:

  • Signal Dispositionen

POSIX.1 erfordert auch einige Attribute sein verschieden für jeden Thread, einschließlich:

Die complete_signalRoutine des Linux-Kernels hat den folgenden Codeblock - die Kommentare sind sehr nützlich:

/*
 * 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.
         */

Sie sehen also, dass Sie dafür verantwortlich sind, wohin die Signale geliefert werden:

Wenn Ihr Prozess die Disposition eines Signals auf SIG_IGNoder gesetzt hat SIG_DFL, wird das Signal für alle Threads ignoriert (oder standardmäßig - töten, kernen oder ignorieren).

Wenn Ihr Prozess die Disposition eines Signals auf eine bestimmte Handlerroutine festgelegt hat, können Sie steuern, welcher Thread die Signale empfängt, indem Sie bestimmte Thread-Signalmasken mit bearbeiten pthread_sigmask(3). Sie können einen Thread benennen, um alle zu verwalten, oder einen Thread pro Signal oder eine beliebige Mischung dieser Optionen für bestimmte Signale erstellen, oder Sie verlassen sich auf das aktuelle Standardverhalten des Linux-Kernels bei der Übermittlung des Signals an den Hauptthread.

Einige Signale sind jedoch laut signal(7)Manpage speziell :

Ein Signal kann für einen gesamten Prozess (z. B. beim Senden mit kill (2) ) oder für einen bestimmten Thread (z. B. bestimmte Signale wie SIGSEGV und SIGFPE, die als Folge der Ausführung generiert werden) generiert werden (und somit anstehen) Eine bestimmte maschinensprachliche Anweisung ist threadgesteuert , ebenso wie Signale, die mit pthread_kill (3) auf einen bestimmten Thread gerichtet sind . Ein prozessgesteuertes Signal kann an einen der Threads geliefert werden, bei denen das Signal derzeit nicht blockiert ist. Wenn bei mehr als einem der Threads das Signal entsperrt ist, wählt der Kernel einen beliebigen Thread aus, an den das Signal geliefert werden soll.

Sarnold
quelle