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

ЖАНРЫ

UNIX — универсальная среда программирования
Шрифт:

Системный вызов

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

#include <signal.h>

signal(SIGINT, SIG_IGN);

Специфицирует игнорирование прерываний, тогда как

signal(SIGINT, SIG_DEL);

восстанавливает действие по умолчанию, означающее завершение процесса. В любом случае

signal
возвращает предыдущее значение сигнала. Если второй аргумент
signal
представляет собой имя функции, которая уже должна быть описана в том же самом исходном файле, то функция будет вызвана, когда возникнет сигнал. Это практикуется довольно часто, чтобы программа могла "подчищать" неоконченные работы перед своим завершением, например удалять временный файл:

#include <signal.h>

char *tempfile = "temp.xxxxxx";

main {

 extern onintr;

 if (signal(SIGINT, SIG_IGN) != SIG_IGN)

signal(SIGINT, onintr);

 mktemp(tempfile);

 /* Process ... */

 exit(0);

}

onintr { /* почистить, если прервано */

 unlink(tempfile);

 exit(1);

}

Почему в

main
имеют место проверки и двойной вызов
signal
? Вспомните, что сигналы посылаются всем процессам, запущенным с данного терминала. Соответственно если программа должна быть запущена не в диалоговом режиме (с помощью
&
),
shell
делает так, что она будет игнорировать прерывания. Поэтому сигналы прерывания, посланные основным процессам, не остановят ее. Если бы эта программа началась с объявления о том, что все прерывания, которые должны быть посланы подпрограмме
onintr
, не принимаются во внимание, были бы сведены на нет все усилия
shell
защитить ее при запуске в фоновом режиме.

Решение, показанное выше, состоит в том, чтобы проверить состояние обработки прерываний, если они игнорировались ранее. Функции программы в том виде, в каком она написана, зависят от возвращаемого

signal
предыдущего состояния конкретного сигнала. Если сигналы уже игнорировались, процесс должен продолжить это дело; в противном случае их следует перехватывать.

Более сложная программа может перехватить прерывание и интерпретировать его как запрос на прекращение своих действий и возврат к основному циклу обработки команд. Подумаем о текстовом редакторе: прерывание длинного вывода на печать не должно вызывать завершения редактирования и потерю уже отредактированного текста. Программа для такого случая может быть написана следующим образом:

#include <signal.h>

#include <setjmp.h>

jmp_buf sjbuf;

main {

 int onintr;

 if(signal(SIGINT, SIG_IGN) != SIG_IGN)

signal(SIGINT, onintr);

 setjmp(sjbuf);

 /* сохранить текущую позицию стека */

 for(;;) {

/* главный рабочий цикл */

 }

 ...

}

onintr { /* установить если прервано */

 signal(SIGINT, onintr); /*
установить

для следующего прерывания */

 printf("\nInterrupt\n");

 longjmp(sjbuf, 0); /* вернуться

в сохраненное состояние */

}

Файл

<setjmp.h>
описывает тип
jmp_buf
как объект, в котором сохраняется позиция стека;
sjbuf
считается таким объектом. Функция
setjmp(3)
сохраняет запись о том, где выполняется программа. Значения переменных не сохраняются. Когда происходит прерывание, выполняется обращение к подпрограмме
onintr
, которая может печатать сообщения, устанавливать флаги и т.д. Функция
longjmp
берет в качестве аргумента объект, сохраненный
setjmp
, и возвращает управление в ячейку после вызова
setjmp
. Поэтому управление (и значение уровня стека) будет возвращено обратно в основную программу — ко входу в головной цикл.

Отметим, что после прерывания сигнал вновь настраивается на

onintr
. Это обусловлено тем, что когда сигналы возникают, они автоматически настраиваются на реакцию по умолчанию.

Некоторые программы, которые "хотят" обнаружить сигналы, просто не могут быть остановлены в произвольный момент, например в середине обновления сложных составных данных. Решение состоит в том, что подпрограмма обработки прерывания должна установить флаг и вернуться к месту вызова

exit
или
longjmp
. Выполнение программы продолжится точно с того места, где оно было прервано, а флаг прерывания будет проверен позднее.

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

read
, но с сообщением об ошибке, указывающим, что произошло:
errno
присваивается
EINTR
, определенное в заголовке
<errno.h>
, чтобы обозначить прерванный системный вызов.

Так, программы, которые "ловят" сигналы и продолжают после этого свою работу, должны быть готовы к появлению ошибок, вызванных прерванными системными вызовами. (Следует остерегаться системных вызовов

read
— чтение с терминала,
wait
,
pause
). Такая программа при чтении стандартного входного потока могла бы использовать фрагмент, подобный следующему:

#include <errno.h>

extern int errno;

...

if (read(0, &c, 1) <= 0) /* EOF или прерывание */

 if (errno == EINTR) { /* EOF, вызванный прерыванием */

errno = 0; /* устанавливается для следующего раза */

 } else { /* настоящий конец файла */

...

 }

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

!
ed
) для выполнения других программ. Тогда программа могла бы выглядеть так:

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