Чтение онлайн

ЖАНРЫ

Linux программирование в примерах
Шрифт:

Типичная высокоуровневая структура основанного на сигналах приложения выглядит таким образом:

for(;;){

 /* Ожидание сигнала */

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

}

Оригинальным интерфейсом V7 для ожидания сигнала является

pause
:

#include <unistd.h> /* POSIX */

int pause(void);

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

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

volatile sig_atomic_t signal_waiting = 0; /* true, если не обрабатываются сигналы */

void handler(int sig) {

 signal_waiting = 1;

 /* Установка других данных, указывающих вид сигнала */

В основном коде флаг проверяется:

for (;;) {

 if (!signal_waiting) { /* Если возник другой сигнал, */

pause; /* этот код пропускается */

signal_waiting = 1;

 }

 /* Определение поступившего сигнала */

 signal_waiting = 0;

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

}

К сожалению, этот код изобилует условиями гонки:

for (;;) {

 if (!signal_waiting) {

/* <--- Сигнал может появиться здесь, после проверки условия! */

pause; /* pause будет вызвана в любом случае */

signal_waiting = 1;

 }

 /* Определение поступившего сигнала

<--- Сигнал может переписать здесь глобальные данные */

 signal_waiting = 0;

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

<--- То же и здесь, особенно для нескольких сигналов */

}

Решением является блокирование интересующего сигнала в любое время, кроме ожидания его появления. Например, предположим, что интересующим нас сигналом является

SIGINT
:

void handler(int sig) {

 /* sig автоматически блокируется функцией sigaction */

 /* Установить глобальные данные, касающиеся этого сигнала */

}

int main(int argc, char **argv) {

 sigset_t set;

 struct sigaction act;

 /* ...обычная настройка, опции процесса и т.д. ... */

 sigemptyset(&set); /* Создать
пустой набор */

 sigaddset(&set, SIGINT); /* Добавить в набор SIGINT */

 sigprocmask(SIG_BLOCK, &set, NULL); /* Заблокировать его */

 act.sa_mask = set; /* Настроить обработчик */

 act.sa_handler = handler;

 act.sa_flags = 0;

 sigaction(sig, &act, NULL); /* Установить его */

 ... /* Возможно, установить отдельные обработчики */

 ... /* для других сигналов */

 sigemptyset(&set); /* Восстановить пустой, допускает SIGINT */

 for (;;) {

sigsuspend(&set); /* Ждать появления SIGINT */

/* Обработка сигнала. SIGINT здесь снова блокируется */

 }

 /* ...любой другой код... */

 return 0;

}

Ключом к использованию этого является то, что

sigsuspend
временно заменяет маску сигналов процесса маской, переданной в аргументе. Это дает
SIGINT
возможность появиться. При появлении он обрабатывается; обработчик сигнала возвращается, а вслед за ним возвращается также
sigsuspend
. Ко времени возвращения
sigsuspend
первоначальная маска процесса снова на месте.

Вы легко можете расширить этот пример для нескольких сигналов, блокируя в

main
и в обработчике все интересующие сигналы и разблокируя их лишь в вызове
sigsuspended
.

При наличии всего этого не следует в новом коде использовать

pause
.
pause
был стандартизован POSIX главным образом для поддержки старого кода. То же самое верно и для функции
sigpause
System V Release 3. Вместо этого, если нужно структурировать свое приложение с использованием сигналов для IPC, используйте исключительно функции API
sigsuspend
и
sigaction
.

ЗАМЕЧАНИЕ. Приведенный выше код предполагает, что маска сигналов процесса начинается пустой. Код изделия должен вместо этого работать с любой маской сигналов, имеющейся на момент запуска программы.

10.8. Важные сигналы специального назначения

Некоторые сигналы имеют особое назначение. Здесь мы опишем наиболее важные.

10.8.1. Сигнальные часы:

sleep
,
alarm
и
SIGALARM

Часто бывает необходимо написать программу в виде

while (/* некоторое неверное условие */) {

 /* подождать некоторое время */

}

Часто такая потребность возникает в сценариях оболочки, например, в ожидании регистрации определенного пользователя:

until who | grep '^arnold' > /dev/null

do

 sleep 10

done

Поделиться с друзьями: