... signal (sig1, fr); signal(sig2, fr); ...
После возврата из функции fr() программа продолжится с прерванного места. Перед
вызовом функции-обработчика реакция автоматически сбрасывается в реакцию по
умолчанию SIG_DFL, а после выхода из обработчика снова восстанавливается в fr.
Это значит, что во время работы функции-обработчика может прийти сигнал, который
убьет программу.
Приведем список некоторых сигналов; полное описание посмотрите в документации.
Колонки таблицы: G - может быть перехвачен; D - по умолчанию убивает процесс (k),
игнорируется (i); C - образуется дамп памяти процесса: файл core, который затем может
быть исследован отладчиком adb; F - реакция на сигнал сбрасывается; S - посылается
обычно системой, а не явно.
сигнал G D C F S смысл
SIGTERM + k - + - завершить процесс
SIGKILL - k - + - убить процесс
SIGINT + k - + - прерывание с клавиш
SIGQUIT + k + + - прерывание с клавиш
SIGALRM + k - + + будильник
SIGILL + k + - + запрещенная команда
SIGBUS + k + + + обращение по неверному
SIGSEGV + k + + + адресу
SIGUSR1, USR2 + i - + - пользовательские
SIGCLD + i - + + смерть потомка
- Сигнал SIGILL используется иногда для эмуляции команд с плавающей точкой, что
происходит примерно так: при обнаружении "запрещенной" команды для отсутствую-
щего процессора "плавающей" арифметики аппаратура дает прерывание и система
посылает процессу сигнал SIGILL. По сигналу вызывается функция-эмулятор плаваю-
щей арифметики (подключаемая к выполняемому файлу автоматически), которая и
обрабатывает требуемую команду. Это может происходить много раз, именно поэтому
А. Богатырев, 1992-95 - 214 - Си в UNIX
реакция на этот сигнал не сбрасывается.
- SIGALRM посылается в результате его заказа вызовом alarm() (см. ниже).
- Сигнал SIGCLD посылается процессу-родителю при выполнении процессом-потомком
сисвызова exit (или при смерти вследствие получения сигнала). Обычно процесс-
родитель при получении такого сигнала (если он его заказывал) реагирует, выпол-
няя в обработчике сигнала вызов wait (см. ниже). По-умолчанию этот сигнал игно-
рируется.
- Реакция SIG_IGN не сбрасывается в SIG_DFL при приходе сигнала, т.е. сигнал игно-
рируется постоянно.
- Вызов signal возвращает старое значение реакции, которое может быть запомнено в
переменную вида void (*f)(); а потом восстановлено.
- Синхронное ожидание (сисвызов) может иногда быть прервано асинхронным событием
(сигналом), но об этом ниже.
Некоторые версии UNIX предоставляют более развитые средства работы с сигналами.
Опишем некоторые из средств, имеющихся в BSD (в других системах они могут быть смоде-
лированы другими способами).
Пусть у нас в программе есть "критическая секция", во время выполнения которой
приход сигналов нежелателен. Мы можем "заморозить" (заблокировать) сигнал, отложив
момент его поступления до "разморозки":
|
sighold(sig); заблокировать сигнал
| :
КРИТИЧЕСКАЯ :<---процессу послан сигнал sig,
СЕКЦИЯ : но он не вызывает реакцию немедленно,
| : а "висит", ожидая разрешения.
| :
sigrelse(sig); разблокировать
|<----------- sig
| накопившиеся сигналы доходят,
| вызывается реакция.
Если во время блокировки процессу было послано несколько одинаковых сигналов sig, то
при разблокировании поступит только один. Поступление сигналов во время блокировки
просто отмечается в специальной битовой шкале в паспорте процесса (примерно так):
mask |= (1 << (sig - 1));
и при разблокировании сигнала sig, если соответствующий бит выставлен, то приходит
один такой сигнал (система вызывает функцию реакции).
То есть sighold заставляет приходящие сигналы "накапливаться" в специальной маске,
вместо того, чтобы немедленно вызывать реакцию на них. А sigrelse разрешает "нако-
пившимся" сигналам (если они есть) прийти и вызывает реакцию на них.
Функция
sigset(sig, react);
аналогична функции signal, за исключением того, что на время работы обработчика сиг-
нала react, приход сигнала sig блокируется; то есть перед вызовом react как бы дела-
ется sighold, а при выходе из обработчика - sigrelse. Это значит, что если во время
работы обработчика сигнала придет такой же сигнал, то программа не будет убита, а
"запомнит" пришедший сигнал, и обработчик будет вызван повторно (когда сработает
sigrelse).
Функция
sigpause(sig);
вызывается внутри "рамки"
sighold(sig);
...
sigpause(sig);
...
sigrelse(sig);
А. Богатырев, 1992-95 - 215 - Си в UNIX
и вызывает задержку выполнения процесса до прихода сигнала sig. Функция разрешает
приход сигнала sig (обычно на него должна быть задана реакция при помощи sigset), и
"засыпает" до прихода сигнала sig.
В UNIX стандарта POSIX для управления сигналами есть вызовы sigaction, sigproc-
mask, sigpending, sigsuspend. Посмотрите в документацию!
6.4.1. Напишите программу, выдающую на экран файл /etc/termcap. Перехватывайте сиг-
нал SIGINT, при получении сигнала запрашивайте "Продолжать?". По ответу 'y' - про-
должить выдачу; по 'n' - завершить программу; по 'r' - начать выдавать файл с начала:
lseek(fd,0L,0). Не забудьте заново переустановить реакцию на SIGINT, поскольку после
получения сигнала реакция автоматически сбрасывается.
#include
void onintr(sig){ /* sig - номер сигнала */
signal (sig, onintr); /* восстановить реакцию */
... запрос и действия ...
}
main(){ signal (SIGINT, onintr); ... }
Сигнал прерывания можно игнорировать. Это делается так:
signal (SIGINT, SIG_IGN);
Такую программу нельзя прервать с клавиатуры. Напомним, что реакция SIG_IGN сохраня-
ется при приходе сигнала.
6.4.2. Системный вызов, находящийся в состоянии ожидания какого-то события (read
ждущий нажатия кнопки на клавиатуре, wait ждущий окончания процесса-потомка, и.т.п.),
может быть прерван сигналом. При этом сисвызов вернет значение "ошибка" (-1) и errno
станет равно EINTR. Это позволяет нам писать системные вызовы с выставлением тайма-
ута: если событие не происходит в течение заданного времени, то завершить ожидание и
прервать сисвызов. Для этой цели используется вызов alarm(sec), заказывающий посылку
сигнала SIGALRM нашей программе через целое число sec секунд (0 - отменяет заказ):
#include
void (*oldaction)(); int alarmed;
/* прозвонил будильник */
void onalarm(nsig){ alarmed++; }
...
/* установить реакцию на сигнал */
oldaction = signal (SIGALRM, onalarm);
/* заказать будильник через TIMEOUT сек. */
alarmed = 0; alarm ( TIMEOUT /* sec */ );
sys_call(...); /* ждет события */
// если нас сбил сигнал, то по сигналу будет
// еще вызвана реакция на него - onalarm
if(alarmed){
// событие так и не произошло.
// вызов прерван сигналом т.к. истекло время.
}else{
alarm(0); /* отменить заказ сигнала */
// событие произошло, сисвызов успел
// завершиться до истечения времени.
}
signal (SIGALRM, oldaction);
Напишите программу, которая ожидает ввода с клавиатуры в течение 10 секунд. Если
ничего не введено - печатает "Нет ввода", иначе - печатает "Спасибо". Для ввода
можно использовать как вызов read, так и функцию gets (или getchar), поскольку
А. Богатырев, 1992-95 - 216 - Си в UNIX
функция эта все равно внутри себя издает системный вызов read. Исследуйте, какое
значение возвращает fgets (gets) в случае прерывания ее системным вызовом.
/* Копирование стандартного ввода на стандартный вывод
* с установленным тайм-аутом.
* Это позволяет использовать программу для чтения из FIFO-файлов
* и с клавиатуры.
* Небольшая модификация позволяет использовать программу
* для копирования "растущего" файла (т.е. такого, который в
* настоящий момент еще продолжает записываться).
* Замечание:
* В ДЕМОС-2.2 сигнал НЕ сбивает чтение из FIFO-файла,
* а получение сигнала откладывается до выхода из read()
* по успешному чтению информации. Пользуйтесь open()-ом
* с флагом O_NDELAY, чтобы получить требуемый эффект.
*
* Вызов: a.out /dev/tty
*
* По мотивам книги М.Дансмура и Г.Дейвиса.
*/
#define WAIT_TIME 5 /* ждать 5 секунд */
#define MAX_TRYS 5 /* максимум 5 попыток */
#define BSIZE 256
#define STDIN 0 /* дескриптор стандартного ввода */
#define STDOUT 1 /* дескриптор стандартного вывода */
#include
#include
#include
#include
#include
#include
char buffer [ BSIZE ];
extern int errno; /* код ошибки */
void timeout(nsig){ signal( SIGALRM, timeout ); }
void main(argc, argv) char **argv;{
int fd, n, trys = 0; struct stat stin, stout;
if( argc != 2 ){
fprintf(stderr, "Вызов: %s файл\n", argv[0]); exit(1);
}
if((fd = !strcmp(argv[1],"-")? STDIN : open(argv[1],O_RDONLY)) < 0){
fprintf(stderr, "Не могу читать %s\n", argv[1]); exit(2);
}
/* Проверить, что ввод не совпадает с выводом,
* hardcat aFile >> aFile
* кроме случая, когда вывод - терминал.
* Такая проверка полезна для программ-фильтров (STDIN->STDOUT),
* чтобы исключить порчу исходной информации */
fstat(fd, &stin); fstat(STDOUT, &stout);
if( !isatty(STDOUT) && stin.st_ino == stout.st_ino &&
stin.st_dev == stout.st_dev
){ fprintf(stderr,
"\aВвод == выводу, возможно потеряна информация в %s.\n",argv[1]);
exit(33);
}
А. Богатырев, 1992-95 - 217 - Си в UNIX
signal( SIGALRM, timeout );
while( trys < MAX_TRYS ){
alarm( WAIT_TIME ); /* заказать сигнал через 5 сек */
/* и ждем ввода ... */
n = read( fd, buffer, BSIZE );
alarm(0); /* отменили заказ сигнала */
/* (хотя, возможно, он уже получен) */
/* проверяем: почему мы слезли с вызова read() ? */
if( n < 0 && errno == EINTR ){
/* Мы были сбиты сигналом SIGALRM,
* код ошибки EINTR - сисвызов прерван
* неким сигналом.
*/
fprintf( stderr, "\7timed out (%d раз)\n", ++trys );
continue;
}
if( n < 0 ){
/* ошибка чтения */
fprintf( stderr, "read error.\n" ); exit(4);
}
if( n == 0 ){
/* достигнут конец файла */
fprintf( stderr, "Достигнут EOF.\n\n" ); exit(0);
}
/* копируем прочитанную информацию */
write( STDOUT, buffer, n );
trys = 0;
}
fprintf( stderr, "Все попытки провалились.\n" ); exit(5);
}
Если мы хотим, чтобы сисвызов не мог прерываться сигналом, мы должны защитить его:
#include
void (*fsaved)();
...