Управление сигналами

Сигналы в программах с несколькими нитями представляют собой расширения сигналов, применяемых в обычных программах с одной нитью.

Управление сигналами в процессах с несколькими нитями осуществляется совместно процессом и нитями. Ниже перечислены элементы управления сигналами:
  • Обработчики сигналов на уровне процесса
  • Маски сигналов на уровне нитей
  • Средства индивидуальной доставки каждого сигнала

Обработчики сигналов и маски сигналов

Обработчики сигналов относятся к уровню процесса. Для ожидания сигналов настоятельно рекомендуется применять функцию sigwait. Функцию sigaction применять не рекомендуется, так как список обработчиков сигналов хранится на уровне процесса, и любая нить процесса может изменять его. Если две нити указали один и тот же сигнал в обработчике сигналов, то вторая нить, вызвавшая функцию sigaction, переопределит параметры, заданные первой нитью; предсказать порядок, в котором будут выполняться нити, в общем случае невозможно.

Маски сигналов обслуживаются на уровне нитей. У каждой нити может быть свой набор сигналов, доставку которых она заблокирует. Для работы с маской сигналов вызывающей нити следует применять функцию sigthreadmask. Функцию sigprocmask не следует применять в программах с несколькими нитями - это может привести к непредсказуемым результатам.

Функция pthread_sigmask во многом схожа с функцией sigprocmask. Параметры и способы применения обеих функций одинаковы. При преобразовании существующей программы в версию с поддержкой библиотеки нитей функцию sigprocmask можно заменить на pthread_sigmask.

Генерация сигналов

Сигналы, относящиеся к конкретной нити, например, сигнал аппаратной неполадки, передаются в ту нить, которая послужила причиной их генерации. Сигналы, связанные с ИД процесса, ИД группы процессов или асинхронным событием (например, рабочие сигналы терминала), передаются в процесс.

  • Функция pthread_kill передает сигнал в нить. Поскольку ИД нитей идентифицируют их только в рамках процесса, эта функция может передавать сигналы в нити только данного процесса.
  • Функция kill (а следовательно, и команда kill) передает сигнал в процесс. Нить может отправить сигнал Signal в процесс, запустивший ее, вызвав следующую функцию:
    kill(getpid(), Signal);
  • Функция raise не может применяться для передачи сигнала в процесс, запустивший вызывающую нить. Функция raise передает сигнал в вызывающую нить, как в следующем случае:
    
    pthread_kill(pthread_self(), Signal);
    Таким образом обеспечивается передача сигнала нити, вызвавшей функцию raise. Следовательно, библиотечные функции, предназначенные для применения в программах с одной нитью, можно легко перенести в систему с несколькими нитями - функция raise обычно применяется для передачи сигнала инициатору вызова.
  • Функция alarm запрашивает последующую передачу сигнала в процесс; при этом аварийные состояния обрабатываются на уровне процесса. Следовательно, последняя нить, вызвавшая функцию alarm, уничтожает соответствующие параметры для других нитей процесса. В программе с несколькими нитями сигнал SIGALRM не всегда передается в нить, вызвавшую функцию alarm. Может случиться, что вызывающая нить уже завершена и поэтому не может принять сигнал.

Обработка сигналов

Обработчики сигналов вызываются нитями, в которые поступили сигналы. В библиотеке нитей существуют следующие ограничения на обработчики сигналов:
  • Обработчики сигналов могут вызывать функцию longjmp или siglongjmp только в том случае, если в той же нити уже выполнен вызов функции setjmp или sigsetjmp.

    Как правило, для организации ожидания сигнала в программе вызывается обработчик сигналов, который вызывает функцию longjmp для продолжения работы в момент вызова соответствующей функции setjmp. В программах с несколькими нитями такой подход неприменим, так как сигнал может быть доставлен не в ту нить, которая вызвала функцию setjmp: в этом случае обработчик сигналов был бы вызван из неправильной нити.

    Прим.: Вызов функции longjmp из обработчика сигналов может привести к непредсказуемому поведению.
  • Из обработчика сигналов нельзя вызывать функции pthread. В результате вызова функции pthread из обработчика сигналов в приложении может возникнуть тупиковая ситуация.

Для того чтобы нить могла ожидать поступления асинхронного сигнала, в библиотеке нитей предусмотрена функция sigwait. Функция sigwait блокирует вызывающую нить до тех пор, пока из процесса в эту нить не поступит один из ожидаемых сигналов. Для сигналов, поступления которых ожидает функция sigwait, нельзя вызывать обработчик.

Обычно в программе создается отдельная нить для ожидания поступления асинхронных сигналов. Такая нить вызывает функцию sigwait в цикле и обрабатывает поступающие сигналы. Рекомендуется, чтобы такая нить блокировала все сигналы. Приведенный ниже фрагмент кода дает пример такой нити:
#include <pthread.h>
#include <signal.h>

static pthread_mutex_t mutex;
sigset_t set;
static int sig_cond = 0;

void *run_me(void *id)
{
       int sig;
       int err;
       sigset_t sigs;
       sigset_t oldSigSet;
       sigfillset(&sigs);
       sigthreadmask(SIG_BLOCK, &sigs, &oldSigSet);

       err = sigwait(&set, &sig);

       if(err)
       {
              /* обработка ошибок */
       }
       else
       {
              printf("SIGINT caught\n");
              pthread_mutex_lock(&mutex);
              sig_cond = 1;
              pthread_mutex_unlock(&mutex);
       }

       return;
}

main()
{
       pthread_t tid;

       sigemptyset(&set);
       sigaddset(&set, SIGINT); 
       pthread_sigmask(SIG_BLOCK, &set, 0);

       pthread_mutex_init(&mutex, NULL);

       pthread_create(&tid, NULL, run_me, (void *)1);

       while (1)
       {
              sleep(1);
              /* или выполнить какую-либо операцию */

              pthread_mutex_lock(&mutex);
              if(sig_cond)
              {
                     /* выполнить операции для выхода */
                     return;
              }
              pthread_mutex_unlock(&mutex);

       }

}

Если функция sigwait вызвана из нескольких нитей, то при поступлении ожидаемого сигнала происходит возврат ровно из одной функции sigwait. В общем случае нельзя предсказать, какая именно нить продолжит свое выполнение. Если нить будет выполнять sigwait, а также управлять некоторыми другими сигналами, не выполняющих sigwait, то обработчики пользовательских сигналов должны блокировать сигналы sigwaiter. Следует обратить внимание, что функция sigwait позволяет отменить операцию.

Поскольку выделенная нить в действительности не является обработчиком сигналов, она может передать сигнал о выполнении условия в любую другую нить. Можно создать функцию sigwait_multiple, которая будет возобновлять выполнение всех нитей, ожидающих данного сигнала. Каждая нить, вызывающая функцию sigwait_multiple, может зарегистрировать свой набор сигналов. После этого нить будет ожидать изменения переменной условия. Одна нить вызывает функцию sigwait для всех зарегистрированных сигналов. При возврате из функции sigwait устанавливается соответствующее состояние и генерируются сигналы о выполнении условий. При повторном вызове функции sigwait_multiple ожидающий вызов функции sigwait будет отменен и потом возобновлен с новым набором сигналов.

Доставка сигналов

Сигнал поступает в нить, если он не должен игнорироваться. Доставка сигналов в процессах с несколькими нитями подчиняется следующим правилам:

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

    Рассмотрим пользовательскую команду с несколькими нитями, например, grep. Пользователь может запустить эту команду из оболочки, а затем попытаться прервать ее выполнение, передав соответствующий сигнал командой kill. Этот сигнал прервет весь процесс, в котором выполняется команда grep.

  • Сигналы, предназначенные для конкретной нити и отправленные с помощью функции pthread_kill или raise, передаются в эту нить. Если эта нить заблокировала доставку данного сигнала, то сигнал переходит в состояние ожидания на уровне нити, пока доставка не будет разблокирована. Если выполнение нити завершилось раньше доставки сигнала, то сигнал будет проигнорирован.
  • Сигналы, предназначенные для процесса и отправленные, например, с помощью функции kill, передаются только одной нити процесса. Если одна или несколько нитей вызвали функцию sigwait, то сигнал будет передан одной из этих нитей. В противном случае сигнал передается ровно в одну нить из числа тех нитей, которые не блокировали его доставку. Если нитей, удовлетворяющих этим условиям, нет, то сигнал переходит в состояние ожидания на уровне процесса до тех пор, пока какая-либо нить не вызовет функцию sigwait с указанием этого сигнала или пока доставка не будет разблокирована.

Если ожидающий сигнал (на уровне нити или процесса) должен игнорироваться, то он игнорируется.