Опубликован: 15.06.2004 | Доступ: свободный | Студентов: 2557 / 712 | Оценка: 4.35 / 3.96 | Длительность: 27:47:00
ISBN: 978-5-9556-0011-6
Лекция 8:

Средства межпроцессного взаимодействия

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

#include <signal.h>
int sigpending (sigset_t *set);
Листинг 8.16. Описание функции sigpending().
#include <signal.h>
int sigwait (const sigset_t *restrict set, 
             int *restrict sig);
Листинг 8.17. Описание функции sigwait().

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

Отметим, что стандарт POSIX-2001 не специфицирует воздействие функции sigwait() на обработку сигналов, включенных в набор set. Чтобы дождаться доставки обрабатываемого или терминирующего процесс сигнала, можно воспользоваться функцией pause() (см. листинг 8.18).

#include <unistd.h>
int pause (void);
Листинг 8.18. Описание функции pause().

Функция pause() может ждать доставки сигнала неопределенно долго. Возврат из pause() осуществляется после возврата из функции обработки сигнала (результат при этом равен -1 ). Если прием сигнала вызывает завершение процесса, возврата из функции pause(), естественно, не происходит.

Несмотря на внешнюю простоту, использование функции pause() сопряжено с рядом тонкостей. При наивном подходе сначала проверяют некоторое условие, связанное с сигналом, и, если оно не выполнено (сигнал отсутствует), вызывают pause(). К сожалению, сигнал может быть доставлен в промежутке между проверкой и вызовом pause(), что нарушает логику работы процесса и способно привести к его зависанию. Решить подобную проблему позволяет функция sigsuspend() (см. листинг 8.19) в сочетании с рассмотренной выше функцией sigprocmask().

#include <signal.h>
int sigsuspend (const sigset_t *sigmask);
Листинг 8.19. Описание функции sigsuspend().

Функция sigsuspend() заменяет текущую маску сигналов вызывающего процесса на набор, заданный аргументом sigmask, а затем переходит в состояние ожидания, аналогичное функции pause(). После возврата из sigsuspend() (если таковой произойдет) восстанавливается прежняя маска сигналов.

Обычно парой функций sigprocmask() и sigsuspend() обрамляют критические интервалы. Перед входом в критический интервал посредством sigprocmask() блокируют некоторые сигналы, а на выходе вызывают sigsuspend() с маской, которую возвратила sigprocmask(), восстанавливая тем самым набор блокированных сигналов и дожидаясь их доставки.

В качестве примера использования описанных выше функций работы с сигналами рассмотрим упрощенную реализацию функции abort() (см. листинг 8.20).

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

void abort (void) {
 struct sigaction sact;
 sigset_t sset;

 /* Вытолкнем буфера */
 (void) fflush (NULL);

 /* Снимем блокировку сигнала SIGABRT */
 if ((sigemptyset (&sset) == 0) && (sigaddset (&sset, SIGABRT) == 0)) {
   (void) sigprocmask (SIG_UNBLOCK, &sset, (sigset_t *) NULL);
 }

 /* Пошлем себе сигнал SIGABRT.                     */
 /* Возможно, его перехватит функция обработки,     */
 /* и тогда вызывающий процесс может не завершиться */
 raise (SIGABRT);

 /* Установим подразумеваемую реакцию на сигнал SIGABRT */
 sact.sa_handler = SIG_DFL;
 sigfillset (&sact.sa_mask);
 sact.sa_flags = 0;
 (void) sigaction (SIGABRT, &sact, NULL);

 /* Снова пошлем себе сигнал SIGABRT */
 raise (SIGABRT);

 /* Если сигнал снова не помог, попробуем еще одно средство завершения */
 _exit (127);
}

int main (void) {
 printf ("Перед вызовом abort()\n");
 abort ();
 printf ("После вызова abort()\n");
 return 0;
}
Листинг 8.20. Упрощенная реализация функции abort() как пример использования функций работы с сигналами.

В качестве нюанса, характерного для работы с сигналами, отметим, что до первого обращения к raise() нельзя закрыть потоки (можно только вытолкнуть буфера), поскольку функция обработки сигнала   SIGABRT, возможно, осуществляет вывод.

Еще одним примером использования механизма сигналов может служить приведенная в листинге 8.21 упрощенная реализация функции sleep(), предназначенной для "засыпания" на заданное число секунд. (Можно надеяться, что не описанные пока средства работы с временем интуитивно понятны.)

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

/* Функция обработки сигнала SIGALRM.                  */
/* Она ничего не делает, но игнорировать сигнал нельзя */
static void signal_handler (int sig) {
/* В демонстрационных целях распечатаем номер обрабатываемого сигнала */
 printf ("Принят сигнал %d\n", sig);
}

/* Функция для "засыпания" на заданное число секунд        */
/* Результат равен разности между заказанной и фактической */
/* продолжительностью "сна"                                */
unsigned int sleep (unsigned int seconds) {
 time_t before, after;
 unsigned int slept;
 sigset_t set, oset;
 struct sigaction act, oact;

 if (seconds == 0) {
   return 0;
 }

 /* Установим будильник на заданное время,    */
 /* но перед этим блокируем сигнал SIGALRM    */
 /* и зададим свою функцию обработки для него */
 if ((sigemptyset (&set) < 0) || (sigaddset (&set, SIGALRM) < 0) ||
     sigprocmask (SIG_BLOCK, &set, &oset)) {
   return seconds;
 }

 act.sa_handler = signal_handler;
 act.sa_flags = 0;
 act.sa_mask = oset;
 if (sigaction (SIGALRM, &act, &oact) < 0) {
   return seconds;
 }

 before = time ((time_t *) NULL);
 (void) alarm (seconds);

 /* Как атомарное действие восстановим старую маску сигналов */
 /* (в надежде, что она не блокирует SIGALRM)                */
 /* и станем ждать доставки обрабатываемого сигнала          */
 (void) sigsuspend (&oset);
 /* сигнал доставлен и обработан */

 after = time ((time_t *) NULL);

 /* Восстановим прежний способ обработки сигнала SIGALRM */
 (void) sigaction (SIGALRM, &oact, (struct sigaction *) NULL);

 /* Восстановим первоначальную маску сигналов */
 (void) sigprocmask (SIG_SETMASK, &oset, (sigset_t *) NULL);

 return ((slept = after - before) > seconds ? 0 : (seconds - slept));
}

int main (void) {
 struct sigaction act;

 /* В демонстрационных целях установим обработку прерывания с клавиатуры */
 act.sa_handler = signal_handler;
 (void) sigemptyset (&act.sa_mask);
 act.sa_flags = 0;
 (void) sigaction (SIGINT, &act, (struct sigaction *) NULL);

 printf ("Заснем на 10 секунд\n");
 printf ("Проснулись, не доспав %d секунд\n", sleep (10));
 return (0);
}
Листинг 8.21. Упрощенная реализация функции sleep() как пример использования механизма сигналов.

Обратим внимание на применение функции sigsuspend(), которая реализует (неделимую) транзакцию снятия блокировки сигналов и перехода в режим ожидания. Отметим также, что по умолчанию при входе в функцию обработки к маске добавляется принятый сигнал для защиты от бесконечной рекурсии. Наконец, если происходит возврат из функции sigsuspend() (после возврата из функции обработки), то автоматически восстанавливается маска сигналов, существовавшая до вызова sigsuspend(). В данном случае в этой маске блокирован сигнал   SIGALRM, и потому можно спокойно менять способ его обработки.

Вызвать "недосыпание" приведенной программы можно, послав ей сигнал   SIGALRM (например, посредством команды kill -s SIGALRM идентификатор_процесса) или SIGINT (путем нажатия на клавиатуре терминала комбинации клавиш CTRL+C).

Антон Коновалов
Антон Коновалов

В настоящее время актуальный стандарт - это POSIX 2008 и его дополнение POSIX 1003.13
Планируется ли актуализация материалов данного очень полезного курса?