Обработка сигналов несколькими потоками в Linux

В Linux, что происходит, когда программа (которая может иметь несколько потоков) получает сигнал, например SIGTERM или SIGHUP?

Какой поток перехватывает сигнал? Может ли несколько потоков получить один и тот же сигнал? Есть ли специальный поток, полностью посвященный обработке сигналов? Если нет, что происходит внутри потока, который должен обрабатывать сигнал? Как возобновление выполнения после завершения процедуры обработчика сигнала?

Ответ 1

Это немного нюансировано, основываясь на том, какую версию ядра Linux вы используете.

Предполагая потоки 2.6 posix, и если вы говорите об ОС, отправляющей SIGTERM или SIGHUP, сигнал отправляется процессу, который принимается и обрабатывается корневым нитьем. Используя потоки POSIX, вы также можете отправить SIGTERM в отдельные потоки, но я подозреваю, что вы спрашиваете, что произойдет, когда ОС отправит сигнал процессу.

В версии 2.6 SIGTERM вызовет дочерние потоки "чисто", где в качестве 2.4 дочерние потоки остались в неопределенном состоянии.

Ответ 2

pthreads(7) описывает, что для POSIX.1 требуются все потоки в атрибутах доступа к процессу, включая:

  • расположение сигналов

POSIX.1 также требует, чтобы для каждого потока были разные атрибуты, включая:

  • сигнальная маска (pthread_sigmask(3))

  • запасной стек сигнала (sigaltstack(2))

В программе Linux kernel complete_signal() есть следующий блок кода - комментарии весьма полезны:

    /*
     * 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_UNKILLABLE | SIGNAL_GROUP_EXIT)) &&
        !sigismember(&t->real_blocked, sig) &&
        (sig == SIGKILL || !t->ptrace)) {
            /*
             * This signal will be fatal to the whole group.
             */

Итак, вы видите, что вы отвечаете за доставку сигналов:

Если ваш процесс установил расположение сигнала на SIG_IGN или SIG_DFL, тогда сигнал игнорируется (или по умолчанию - kill, core или ignore) для всех потоков.

Если ваш процесс установил расположение сигнала к определенной процедуре обработчика, вы можете контролировать, какой поток будет принимать сигналы, манипулируя определенными масками сигналов потока с помощью pthread_sigmask(3). Вы можете назначить один поток для управления ими всех или создать один поток для каждого сигнала или любую смесь этих опций для определенных сигналов или полагаться на текущее поведение ядра Linux для подачи сигнала в основной поток.

Некоторые сигналы, однако, являются особенными:

   A signal may be generated (and thus pending) for a process as
   a whole (e.g., when sent using kill(2)) or for a specific
   thread (e.g., certain signals, such as SIGSEGV and SIGFPE,
   generated as a consequence of executing a specific machine-
   language instruction are thread directed, as are signals
   targeted at a specific thread using pthread_kill(3)).  A
   process-directed signal may be delivered to any one of the
   threads that does not currently have the signal blocked.  If
   more than one of the threads has the signal unblocked, then
   the kernel chooses an arbitrary thread to which to deliver
   the signal.