Windows 7, XP

Сигналы в linux. Примеры

Сигналы в linux. Примеры

Надежные сигналы

Стандарт POSIX. 1 определил новый набор функций управления сигналами. основанный на интерфейсе 4.2BSD UNIX и лишенный рассмотренных выше недостатков.

Модель сигналов, предложенная POSIX, основана на понятии набора сигналов (signal set), описываемого переменной типа sigset_t . Каждый бит этой переменной отвечает за один сигнал. Во многих системах тип sigset_t имеет длину 32 бита, ограничивая количество возможных сигналов числом 32.

Следующие функции позволяют управлять наборами сигналов:

#include

int sigempyset(sigset_t *set);

int siufillset(sigset_t *set);

int sigaddset(sigset_t *set, int signo);

int sigdelset(sigset_t *set, int signo);

int sigismember(sigset_t *set, int signo);

В отличие от функции signal(3C) , изменяющей диспозицию сигналов, данные функции позволяют модифицировать структуру данных sigset_t , определенную процессом. Для управления непосредственно сигналами используются дополнительные функции, которые мы рассмотрим позже.

Функция sigemptyset(3C) инициализирует набор, очищая все биты. Если процесс вызывает sigfillset(3C) , то набор будет включать все сигналы, известные системе. Функции sigaddset(3C) и sigdelset(3C) позволяют добавлять или удалять сигналы набора. Функция sigismember(3C) позволяет проверить, входит ли указанный параметром signo сигнал в набор.

Вместо функции signal(3C) стандарт POSIX. 1 определяет функцию sigaction(2) , позволяющую установить диспозицию сигналов, узнать ее текущее значение или сделать и то и другое одновременно. Функция имеет следующее определение:

#include

int sigaction (int sig, const struct sigaction *act,

struct sigaction *oact);

Вся необходимая для управлением сигналами информация передается через указатель на структуру sigaction , имеющую следующие поля:

Поле sa_handler определяет действие, которое необходимо предпринять при получении сигналов, и может принимать значения SIG_IGN , SIG_DFL или адреса функции-обработчика. Если значение sa_handler или sa_sigaction не равны NULL , то в поле sa_mask передается набор сигналов, которые будут добавлены к маске сигналов перед вызовом обработчика. Каждый процесс имеет установленную маску сигналов, определяющую сигналы, доставка которых должна быть заблокирована. Если определенный бит маски установлен, соответствующий ему сигнал будет заблокирован. После возврата из функции-обработчика значение маски возвращается к исходному значению. Заметим, что сигнал, для которого установлена функция-обработчик, также будет заблокирован перед ее вызовом. Такой подход гарантирует, что во время обработки, последующее поступление определенных сигналов будет приостановлено до завершения функции. Как правило, UNIX не поддерживает очередей сигналов, и это значит, что блокировка нескольких однотипных сигналов в конечном итоге вызовет доставку лишь одного.

Поле sa_flags определяет флаги, модифицирующие доставку сигнала. Оно может принимать следующие значения:

SA_ONSTACK Если определена функция-обработчик сигнала, и с помощью функции sigaltstack(2) задан альтернативный стек для функции-обработчика, то при обработке сигнала будет использоваться этот стек. Если флаг не установлен, будет использоваться обычный стек процесса.
SA_RESETHAND * Если определена функция-обработчик, то диспозиция сигнала будет изменена на SIG_DFL , и сигнал не будет блокироваться при запуске обработчика. Если флаг не установлен, диспозиция сигнала остается неизменной.
SA_NODEFER * Если определена функция-обработчик, то сигнал блокируется на время обработки только в том случае, если он явно указан в поле sa_mask . Если флаг не установлен, в процессе обработки данный сигнал автоматически блокируется.
SA_RESTART Если определена функция-обработчик, ряд системных вызовов, выполнение которых было прервано полученным сигналом, будут автоматически перезапущены после обработки сигнала. Если флаг не установлен, системный вызов возвратит ошибку EINTR .
SA_SIGINFO * Если диспозиция указывает на перехват сигнала, вызывается функция, адресованная полем sa_sigaction . Если флаг не установлен, вызывается обработчик sa_handler .
SA_NOCLDWAIT * Если указанный аргументом sig сигнал равен SIGCHLD , при завершении потомки не будут переходить в состояние зомби. Если процесс в дальнейшем вызовет функции wait(2) , wait3(2) , waitid(2) или waitpid(2) , их выполнение будет блокировано до завершения работы всех потомков данного процесса.
SA_NOCLDSTOP * Если указанный аргументом sig сигнал равен SIGCHLD , указанный сигнал не будет отправляться процессу при завершении или останове любого из его потомков.

* Данные флаги не определены для UNIX BSD.

В системах UNIX BSD 4.x структура sigaction имеет следующий вид:

struct sigaction {

void (*sa_handler)();

sigset_t sa_mask;

где функция-обработчик определена следующим образом:

void handler(int signo, int code, struct sigcontext *scp);

В первом аргументе signo содержится номер сигнала, code определяет дополнительную информацию о причине поступления сигнала, a scp указывает на контекст процесса.

Для UNIX System V реализована следующая возможность получения более полной информации о сигнале. Если установлен флаг SA_SIGINFO , то при получении сигнала sig будет вызван обработчик, адресованный полем sa_sigaction . Помимо номера сигнала, обычно передаваемого обработчику сигнала, ему будет переданы указатель на структуру siginfo_t , содержащую информацию о причинах получения сигнала, а также указатель на структуру ucontext_t , содержащую контекст процесса.

Структура siginfo_t определена в файле и включает следующие поля:

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

которые адресуют процесс, пославший сигнал; если значение si_code больше нуля, то оно указывает на причину отправления сигнала. Список возможных значений si_code для некоторых сигналов, соответствующих полю si_signo , приведен в табл. 2.19

Таблица 2.19 . Значения поля si_code структуры siginfo_t для некоторых сигналов

Значение поля si_signo Значение поля si_code Описание
SIGILL Попытка выполнения недопустимой инструкции
ILL_ILLOPC Недопустимый код операции (opcode)
ILL_ILLOPN Недопустимый операнд
ILL_ADR Недопустимый режим адресации
ILL_ILLTRP Недопустимая ловушка (trap)
ILL_PRVOPC Привилегированный код операции
ILL_PRVREG Привилегированный регистр
ILL_COPROC Ошибка сопроцессора
ILL_BADSTK Ошибка внутреннего стека
SIGFPE Особая ситуация операции с плавающей точкой
FPE_INTDIV Целочисленное деление на ноль
FPE_INTOVF Целочисленное переполнение
FPE_FLTDIV Деление на ноль с плавающей точкой
FPE_FLTOVF Переполнение с плавающей точкой
FPE_FLTUND Потеря точности с плавающей точкой (underflow)
FPE_FLTRES Неоднозначный результат операции с плавающей точкой
FPE_FLTINV Недопустимая операция с плавающей точкой
FPE_FLTSUB Индекс вне диапазона
SIGSEGV Нарушение сегментации
SEGV_MAPPER Адрес не отображается на объект
SEGV_ACCERR Недостаточно прав на отображаемый объект
SIGBUS Ошибка адресации
BUS_ADRALN Недопустимое выравнивание адреса
BUS_ADRERR Несуществующий физический адрес
BUS_OBJERR Аппаратная ошибка, связанная с объектом
SIGTRAP Ловушка
TRAP_BRKPT Процессом достигнута точка останова
TRAP_TRACE Ловушка трассирования процесса
SIGCHLD Завершение выполнения дочернего процесса
CLD_EXITED Дочерний процесс завершил выполнение
CLD_KILLED Дочерний процесс был "убит"
CLD_DUMPED Ненормальное завершение дочернего процесса
CLD_TRAPPED Трассируемый дочерний процесс находится в ловушке
CLD_STOPPED Выполнение дочернего процесса было остановлено
CLD_CONTINUED Выполнение остановленного дочернего процесса было продолжено
SIGPOLL Событие на опрашиваемом устройстве
POLL_IN Поступили данные для ввода
POLL_OUT Свободны буферы данных
POLL_MSG Сообщение ожидает ввода
POLL_ERR Ошибка ввода/вывода
POLL_PRI Высокоприоритетные данные ожидают ввода
POLL_HUP Устройство отключено

Уже отмечалось, что при получении сигнала от пользовательского процесса структура siginfo_t содержит дополнительные поля (табл. 2.20).

Таблица 2.20 . Дополнительные поля структуры siginfo_t

Установить маску сигналов или получить текущую маску можно с помощью функции sigprocmask(2) :

#include

int sigprocmask(int how, sigset_t *set, sigset_t *oset);

Маска сигналов изменяется в соответствии с аргументом how , который может принимать следующие значения:

Если указатель set равен NULL , то аргумент how игнорируется. Если аргумент oset не равен NULL , то в набор, адресованный этим аргументом, помещается текущая маска сигналов.

Функция sigpending(2) используется для получения набора заблокированных сигналов, ожидающих доставки:

#include

int sigpending(int how, sigset_t *set, sigset_t *oset);

Список сигналов, ожидающих доставки, возвращается в наборе, адресованном аргументом set .

Системный вызов sigsuspend(2) замещает текущую маску набором, адресованным аргументом set , и приостанавливает выполнение процесса до получения сигналов, диспозиция которых установлена либо на завершение выполнения процесса, либо на вызов функции-обработчика сигнала.

#include

int sigsuspend(const sigset_t *set);

При получении сигнала, завершающего выполнение процесса, возврата из функции sigsuspend(2) не происходит. Если же диспозиция полученного сигнала установлена на вызов функции-обработчика, возврат из sisuspend(2) происходит сразу после завершения обработки сигнала. При этом восстанавливается маска, существовавшая до вызова sigsuspend(2) .

Заметим, что в BSD UNIX вызов signal(3) является упрощенным интерфейсом к более общей функции sigaction(2) , в то время как в ветви System V signal(3) подразумевает использование старой семантики ненадежных сигналов.

В заключение для иллюстрации изложенных соображений, приведем версию функции signal() , позволяющую использовать надежные сигналы. Похожая реализация используется в BSD UNIX. С помощью этой "надежной" версии мы повторим пример, рассмотренный нами выше, в измененном виде.

#include

#include

#include

#include

#include

/* Вариант "надежной" функции signal() */

void (*mysignal(int signo, void (*hndlr)(int)))(int) {

struct sigaction act, oact;

/* Установим маску сигналов */

act.sa_handler = hndlr;

sigemptyset(&act.sa_mask);

act.sa_flags = 0;

if (signo != SIGALRM)

act.sa_flags = SA_RESTART;

/* Установим диспозицию */

if (sigaction(signo, &act, &oact) < 0)

return(oact.sa_handler);

/* Функция-обработчик сигнала */

static void sig_hndlr(int signo) {

/* Эта часть кода нам уже не нужна

mysignal(SIGINT, sig_hndlr);

printf("Получен сигнал SIGINT ");

/* Установим диспозицию */

mysignal(SIGINT, sig_hndlr);

mysignal(SIGUSR2, SIG_IGN);

/* Бесконечный цикл */

Заметим, что при использовании надежных сигналов, не нужно восстанавливать диспозицию в функции-обработчике при получении сигнала.

Из книги Архитектура операционной системы UNIX автора Бах Морис Дж

7.2 СИГНАЛЫ Сигналы сообщают процессам о возникновении асинхронных событий. Посылка сигналов производится процессами - друг другу, с помощью функции kill, - или ядром. В версии V (вторая редакция) системы UNIX существуют 19 различных сигналов, которые можно классифицировать

Из книги Linux-сервер своими руками автора

5.8.2. Сигналы Демон syslogd реагирует на следующие сигналы: SYGTERM, SIGINT, SIGQUIT, SIGHUP, SIGUSR1, SIGCHLD. Реакция демона на сигналы описана в табл. 5.8.Реакция демона на сигналы Таблица 5.8 Сигнал Реакция SIGTERM Завершает работу демона SIGINT, SIGQUIT Завершает работу демона, если выключена отладка

автора Реймонд Эрик Стивен

Из книги Искусство программирования для Unix автора Реймонд Эрик Стивен

7.2.6.2. Сигналы Самый простой и грубый способ сообщения между двумя процессами на одной машине заключается в том, что один из них отправляет другому какой-либо сигнал (signal). Сигналы в операционной системе Unix представляют собой форму программного прерывания. Каждый сигнал

Из книги Инфраструктуры открытых ключей автора Полянская Ольга Юрьевна

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

Из книги Разработка приложений в среде Linux. Второе издание автора Джонсон Майкл К.

12.1.3. Надежные сигналы Реализация BSD для решения проблемы множества сигналов полагается на простое ожидание завершения работы каждого обработчика сигналов в процессе перед доставкой следующего. Это гарантирует то, что каждый сигнал будет рано или поздно обработан, а

Из книги Linux: Полное руководство автора Колисниченко Денис Николаевич

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

Из книги QNX/UNIX [Анатомия параллелизма] автора Цилюрик Олег Иванович

27.3.10. Сигналы и сокеты С сокетами связаны три сигнала:? SIGIO - сокет готов к вводу/выводу. Сигнал посылается процессу, который связан с сокетом;? SIGURG - сокет получил экспресс-данные (мы их использовать не будем, поэтому особо останавливаться на них нет смысла);? SIGPIPE - запись

Из книги Программирование для Linux. Профессиональный подход автора Митчелл Марк

3. Сигналы

Из книги Операционная система UNIX автора Робачевский Андрей М.

Из книги Linux и UNIX: программирование в shell. Руководство разработчика. автора Тейнсли Дэвид

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

Из книги Взрыв обучения: Девять правил эффективного виртуального класса автора Мердок Мэттью

Сигналы Сигналы являются способом передачи от одного процесса другому или от ядра операционной системы какому-либо процессу уведомления о возникновении определенного события. Сигналы можно рассматривать как простейшую форму межпроцессного взаимодействия. В то же

Из книги автора

Из книги автора

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

Из книги автора

26.2. Сигналы Сигнал относится к типу сообщений, которые пересылаются из системы для информирования команды или сценария о совершении какого?либо события. Обычно речь идет об ошибках, связанных с функционированием памяти, о проблемах с доступом к информации или об

Сигналы в ОС Unix

Сигналы представляют собой средство уведомления процесса о наступлении некоторого события в системе.

Инициатором посылки сигнала может выступать как другой процесс, так и сама ОС.

Сигналы, посылаемые ОС, уведомляют о наступлении некоторых строго предопределенных ситуаций (как, например, завершение порожденного процесса, прерывание процесса нажатием комбинации Ctrl-C, попытка выполнить недопустимую машинную инструкцию, попытка недопустимой записи в канал и т.п.), при этом каждой такой ситуации сопоставлен свой сигнал.

Кроме того, зарезервирован один или несколько номеров сигналов, семантика которых определяется пользовательскими процессами по своему усмотрению (например, процессы могут посылать друг другу сигналы с целью синхронизации).

Количество различных сигналов в современных версиях UNIX около 30, каждый из них имеет уникальное имя и номер.

Описания представлены в файле .

В таблице приведено несколько примеров сигналов:

Числовое значение

Константа

Значение сигнала

Прерывание выполнения по нажатию Ctrl-C

Аварийное завершение работы

Уничтожение процесса

Прерывание от программного таймера

Завершился процесс-потомок

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

При получении сигнала процессом возможны три варианта реакции на полученный сигнал:

    Процесс реагирует на сигнал стандартным образом, установленным по умолчанию (для большинства сигналов действие по умолчанию – это завершение процесса)

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

    Процесс может проигнорировать сигнал

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

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

Если в процесс одновременно доставляется несколько различных сигналов, то порядок их обработки не определен . Если же обработки ждут несколько экземпляров одного и того же сигнала, то ответ на вопрос, сколько экземпляров будет доставлено в процесс – все или один – зависит от конкретной реализации ОС.

Отдельного рассмотрения заслуживает ситуация, когда сигнал приходит в момент выполнения системного вызова. Обработка такой ситуации в разных версиях UNIX реализована по-разному, например, обработка сигнала может быть отложена до завершения системного вызова; либо системный вызов автоматически перезапускается после его прерывания сигналом; либо системный вызов вернет –1, а в переменной errno будет установлено значение EINTR

Для отправки сигнала существует системный вызов kill():

#include

#include

int kill (pid _ t pid , int sig );

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

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

sig номер посылаемого сигнала.

Если этот параметр равен 0, то будет выполнена проверка корректности обращения к kill() (в частности, существование процесса с идентификатором pid), но никакой сигнал в действительности посылаться не будет.

Если процесс-отправитель не обладает правами привилегированного пользователя, то он может отправить сигнал только тем процессам, у которых реальный или эффективный идентификатор владельца процесса совпадает с реальным или эффективным идентификатором владельца процесса-отправителя.

Для определения реакции на получение того или иного сигнала в процессе служит системный вызов signal():

#include

void (*signal

(int sig, void (*disp) (int))) (int);

sig - номер сигнала, для которого устанавливается реакция, disp - либо определенная пользователем функция-обработчик сигнала, либо одна из констант: SIG_DFL (обработка по умолчанию, т.е. стандартная реакцию системы,)и SIG_IGN (данный сигнал необходимо игнорировать).

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

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

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

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

В данном примере при получении сигнала S

IGINT четырежды вызывается специальный

обработчик, а в пятый раз происходит

обработка по умолчанию.

#include

#include

#include

int count = 0;

void SigHndlr (int s) /* обработчик сигнала */

printf("\n I got SIGINT %d time(s) \n",

++ count);

if (count == 5) signal (SIGINT, SIG_DFL);

/* ставим обработчик сигнала по умолчанию */

else signal (SIGINT, SigHndlr);

/* восстанавливаем обработчик сигнала */

int main(int argc, char **argv)

signal (SIGINT, SigHndlr); /* установка реакции на сигнал */

while (1); /*”тело программы” */

return 0;

Высокоуровневые средства межпроцессного

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

вильных обращений к памяти, при выполнении специфических инст рукций или из-за операций с плавающей точкой. Наиболее распространенные сигналы, поступающие из внешнего мира: прерывание (interrupt ) – этот сигнал посылается, когда вы нажимаете клавишу DEL ; выход (quit )– порождается символом FS (ctl -\); отключение (hangup ) – вызван тем, что повешена телефонная трубка, и завершение (terminate ) – порождается командой kill. Когда происходит одно из вышеуказанных событий, сигнал посылается всем процессам, запу щенным с данного терминала, и если не существует соглашений, пред писывающих иное, сигнал завершает процесс. Для большинства сигналов создается дамп памяти, который может потребоваться для отладки. (См. adb(1) и sdb(l).)

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

#include

signal(SIGINT, SIG_IGN);

приводит к игнорированию прерывания, в то время как

signal(SIGINT, SIG_DFL);

восстанавливает действие по умолчанию – завершение процесса. Во всех случаях signal возвращает предыдущее значение сигнала. Если второй аргумент – это имя функции (которая должна быть объявлена в этом же исходном файле), то она будет вызвана при возникновении сигнала. Чаще всего эта возможность используется для того, чтобы позволить программе подготовиться к выходу, например удалить временный файл:

#include

char *tempfile = "temp.XXXXXX";

extern onintr();

mktemp(tempfile);

/* Обработка … */ exit(0);

onintr() /* очистить в случае прерывания */

unlink(tempfile); exit(1);

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

Решение, представленное выше, позволяет проверить состояние управления прерываниями и продолжать игнорировать прерывания, если они игнорировались ранее. Код учитывает тот факт, что signal возвращает предыдущее состояние конкретного сигнала. И если сигналы ранее игнорировались, процесс будет и далее их игнорировать; в противном случае они должны быть перехвачены.

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

#include

#include jmp_buf sjbuf;

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. Выполнение будет продолжено с того самого места, в котором оно было прервано, а флаг прерывания может быть проверен позже.

С таким подходом связана одна трудность. Предположим, что программа читает с терминала в то время, когда послано прерывание. Надлежащим образом вызывается указанная подпрограмма, которая устанавливает флаги и возвращается обратно. Если бы дело действительно обстояло так, как было указано выше, то есть выполнение программы возобновлялось бы «с того самого места, где оно было прервано», то программа должна была бы продолжать читать с терминала до тех пор, пока пользователь не напечатал бы новую строку. Такое поведение может сбивать с толку, ведь пользователь может и не знать, что программа читает, и он, вероятно, предпочел бы, чтобы сигнал вступал в силу незамедлительно. Чтобы разрешить эту проблему, система завершает чтение, но со статусом ошибки, который указывает, что произошло; errno устанавливается в EINTR, определенный в errno.h, чтобы обозначить прерванный системный вызов.

Поэтому программы, которые перехватывают сигналы и возобновляют работу после них, должны быть готовы к «ошибкам», вызванным прерванными системными вызовами. (Системные вызовы, по отношению к которым надо проявлять осторожность, – это чтение с терминала, ожидание и пауза). Такая программа может использовать код, приведенный ниже, для чтения стандартного ввода:

#include extern int errno;

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

if (errno == EINTR) { /* EOF, вызванный прерыванием */ errno = 0; /* переустановить для следующего раза */

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

И последняя тонкость, на которую надо обратить внимание, если перехват сигналов сочетается с выполнением других программ. Предположим, что программа обрабатывает прерывания и, к тому же, содержит метод (как! в ed), посредством которого могут выполняться другие программы. Тогда код будет выглядеть примерно так:

if (fork() == 0) execlp(…);

signal(SIGINT, SIG_IGN); /* предок игнорирует прерывания */

wait(&status); /* пока выполняется потомок */ signal(SIGINT, onintr); /* восстановить прерывания */

Почему именно так? Сигналы посылаются всем вашим процессам. Предположим, что программа, которую вы вызвали, обрабатывает свои собственные прерывания, как это делает редактор. Если вы прерываете дочернюю программу, она получит сигнал и вернется в свой основной цикл, и, вероятно, прочитает ваш терминал. Но вызывающая программа также выйдет из состояния ожидания дочерней программы и прочитает ваш терминал. Наличие двух процессов чтения терминала все запутывает, так как на самом деле система «подкидывает монетку», чтобы решить, какая программа получит каждую из строк ввода. Чтобы избежать этого, родительская программа должна игнорировать прерывания до тех пор, пока не выполнится дочерняя. Это умозаключение отражено в обработке сигналов в system:

#include

system(s) /* выполнить командную строку s */ char *s;

int status, pid, w, tty;

int (*istat)(), (*qstat)();

if ((pid = fork()) == 0) {

execlp("sh", "sh", "–c", s, (char *) 0); exit(127);

istat = signal(SIGINT, SIG_IGN); qstat = signal(SIGQUIT, SIG_IGN);

while ((w = wait(&status)) != pid && w != –1)

if (w == –1) status = –1;

signal(SIGINT, istat); signal(SIGQUIT, qstat); return status;

В отступление от описания, функция signal очевидно имеет несколько странный второй аргумент. На самом деле это указатель на функцию, которая возвращает целое число, и это также тип самой функции sig– nal. Два значения, SIG_IGN и SIG_DFL, имеют правильный тип, но выби раются таким образом, чтобы они не совпадали ни с какими возможными реальными функциями. Для особо интересующихся приведем пример того, как они описываются для PDP-11 и VAX; описания должны быть достаточно отталкивающими, чтобы побудить к использованию signal.h.

#define SIG_DFL (int (*)())0

#define SIG_IGN (int (*)())1

Сигналы alarm

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

Чтобы пояснить ситуацию, рассмотрим программу, называемую time– out, она запускает некоторую другую команду; если эта команда не закончилась к определенному времени, она будет аварийно прервана, когда alarm выключится. Например, вспомните команду watchfor из главы 5. Вместо того чтобы запускать ее на неопределенное время, можно установить часовой лимит:

$ timeout -3600 watchfor dmg &

Код в timeout иллюстрирует практически все, о чем говорилось в двух предыдущих разделах. Потомок создан; предок устанавливает аварийный сигнал и ждет, пока потомок закончит свою работу. Если alarm приходит раньше, то потомок уничтожается. Предпринимается попытка вернуть статус выхода потомка.

/* timeout: устанавливает временное ограничение для процесса */

#include

#include

int pid; /* идентификатор дочернего процесса */

char *progname; main(argc, argv)

int sec = 10, status, onalarm();

progname = argv;

if (argc > 1 && argv == ‘–’) { sec = atoi(&argv);

argc––; argv++;

if (argc < 2)

error("Usage: %s [–10] command", progname); if ((pid=fork()) == 0) {

execvp(argv, &argv); error("couldn’t start %s", argv);

signal(SIGALRM, onalarm); alarm(sec);

if (wait(&status) == –1 || (status & 0177) != 0) error("%s killed", argv);

exit((status >> 8) & 0377);

onalarm() /* завершить дочерний процесс в случае получения alarm */

kill(pid, SIGKILL);

Упражнение 7.18. Можете ли вы предположить, как реализован sleep? Подсказка: pause(2). При каких условиях (если такие условия существуют) sleep и alarm могут создавать помехи друг для друга? ~

История и библиография

В книге не представлено подробного описания реализации системы UNIX, в частности из-за того, что существуют имущественные права на код. Доклад Кена Томпсона (Ken Thompson) «UNIX implementation» (Реализация UNIX), изданный в «BSTJ» в июле 1978 года, описывает основные идеи. Эту же тему поднимают статьи «The UNIX system – a retrospective» (Система UNIX в ретроспективе) в том же номере

«BSTJ» и «The evolution of the UNIX time-sharing system» (Эволюция UNIX – системы разделения времени), напечатанная в материалах Symposium on Language Design and Programming Methodology в журнале издательства Springer-Verlag «Lecture Notes in Computer Science»

№ 79 в 1979 году. Оба труда принадлежат перу Денниса Ритчи (Dennis Ritchie).

Программа readslow была придумана Питером Вейнбергером (Peter Weinberger) в качестве простого средства для демонстрации зрителям игры шахматной программы Belle Кена Томсона и Джо Кондона (Joe Condon) во время шахматного турнира. Belle записывала состояние игры в файл; наблюдатели опрашивали файл с помощью readslow, чтобы не занимать слишком много драгоценных циклов. (Новая версия оборудования для Belle осуществляет небольшие расчеты на своей главной машине, поэтому больше такой проблемы не существует.)

Том Дафф (Tom Duff) вдохновил нас на написание spname. Статья Айво ра Дерхема (Ivor Durham), Дэвида Лэмба (David Lamb) и Джеймса Сакса (James Saxe) «Spelling correction in user interfaces» (Проверка орфографии в пользовательских интерфейсах), изданная CACM в октябре 1983 года, представляет несколько отличающийся от привычного проект реализации исправления орфографических ошибок в контексте почтовой программы.

Сигналы - это запросы на прерывание, реализуемые на уровне процессов. Определено свыше тридцати различных сигналов, и они находят самое разное применение.

  • Сигналы могут посылаться между процессами как средство коммуникации.
  • Сигналы могут посылаться драйвером терминала для уничтожения или приостановки процессов, когда пользователь нажимает специальные комбинации клавиш, такие как или .
  • Сигналы могут посылаться в самых разных целях пользователем или администратором с помощью команды kill.
  • Сигналы могут посылаться ядром, когда процесс выполняет нелегальную инструкцию, например деление на нуль.
  • Сигналы могут посылаться ядром для уведомления процесса о "представляющем интерес" событии, таком как прекращение дочернего процесса или доступность данных в канале ввода-вывода.
  • Дамп памяти - это файл, содержащий образ памяти процесса. Его можно использовать для отладки.
Функции, связанные с этими комбинациями клавиш, могут назначаться другим клавишам помощью команды stty, но на практике такое встречается очень редко. Мы подразумеваем, что с данными клавишами связаны их стандартные функции. Когда поступает сигнал, возможен один из двух вариантов развития событий. Если процесс назначил сигналу подпрограмму обработки, то после вызова ей предоставляется информация о контексте, в котором был сгенерирован сигнал. В противном случае ядро выполняет от имени процесса действия, заданные по умолчанию. Эти действия зависят от сигнала. Многие сигналы приводят к завершению процесса, а в некоторых случаях при этом еще и создается дамп памяти. Процедура вызова обработчика называется перехватом сигнала. Когда выполнение обработчика завершается, процесс возобновляется с той точки, где был получен сигнал. Для того чтобы определенные сигналы не поступали в программу, нужно задать их игнорирование или блокирование. Игнорируемый сигнал просто пропускается и не влияет на работу процесса. Блокируемый сигнал ставится в очередь на обработку, но ядро не требует от процесса никаких действий до явного разблокирования сигнала. Обработчик вызывается для разблокированного сигнала только один раз, даже если в течение периода блокировки поступило несколько аналогичных сигналов. Ниже перечислены сигналы, которые должны быть известны любому системному администратору. Традиционно имена сигналов записываются прописными буквами. Иногда к именам добавляется префикс SIG (например, SIGHUP). Имя Описание Реакция Перехваты- Блоки- Дамп умолчанию 1 HUP Отбой Завершение Да Да Нет 2 INT Прерывание Завершение Да Да Нет 3 QUIT Выход Завершение Да Да Да 9 KILL Уничтожение Завершение Нет Нет Нет 10 BUS Ошибка на шине Завершение Да Да Да 11 SEGV Ошибка сегментации Завершение Да Да Да 12 TERM Запрос на завершение завершение Да Да Нет 13 STOP Останов Останов Нет Нет Нет 14 TSTP Сигнал останова, посылаемый Останов Да Да Нет с клавиатуры 15 CONT Продолжение после останова Игнорируется Да Нет Нет 16 WINCH Изменение окна Игнорируется Да Да Нет 17 USR1 Определяется пользователем Завершение Да Да Нет 18 USR2 Определяется пользователем Завершение Да Да Нет Существуют и другие сигналы, не показанные в табл. 5.1; большинство из них сообщает о всяких загадочных ошибках, например "неверная инструкция". По умолчанию такие сигналы, как правило, приводят к завершению программы и созданию дампа памяти. Перехват и блокирование сигналов обычно разрешены, так как есть достаточно "умные" программы, устраняющие последствия ошибок. Сигналы BUS и segv также посылаются при возникновении ошибок. Мы включили их в таблицу, поскольку они чрезвычайно распространены: обычно программа аварийно завершается именно из-за них. Сами по себе эти сигналы не имеют диагностической ценности. Они лишь указывают на факт неправильного обращения к памяти6. Сигналы KILL и STOP нельзя ни перехватить, ни заблокировать, ни проигнорировать. Сигнал KILL приводит к уничтожению процесса, которому он посылается, а сигнал STOP приостанавливает выполнение процесса до получения сигнала CONT. Сигнал CONT можно перехватить и проигнорировать, но нельзя заблокировать. Сигнал tstp представляет собой более "гибкую" версию сигнала STOP. Проще всего описать его как запрос на останов. Он генерируется драйвером терминала при нажатии пользователем комбинации клавиш . Программы, перехватывающие этот сигнал, обычно выполняют операции очистки, а затем посылают сами себе сигнал STOP. С другой стороны, программы могут игнорировать сигнал TSTP, чтобы их нельзя было остановить командой с клавиатуры. Эмуляторы терминалов посылают сигнал WINCH, когда происходит изменение их конфигурационных параметров (например, числа строк на виртуальном терминале). Это позволяет программам, которые взаимодействуют с эмуляторами, таким как текстовые редакторы, автоматически переконфигурировать себя в ответ на изменения. Если размер окна не удается правильно изменить, убедитесь в том, что сигнал WINCH генерируется и корректно обрабатывается. Хотя назначение сигналов KILL, INT, TERM, HUP и QUIT может показаться одинаковым, в действительности они совершенно различны.
  • Сигнал KILL не блокируется и приводит к безусловному завершению процесса на уровне ядра. По сути, процесс не успевает даже принять этот сигнал.
  • Сигнал INT посылается драйвером терминала при нажатии пользователем комбинации клавиш и служит запросом на завершение текущей операции. Перехватив этот сигнал, простые программы должны завершить работу или позволить уничтожить себя стандартному обработчику сигнала. Программы, в которых есть интерактивный режим командной строки, должны прекратить текущую операцию, выполнить очистку и снова перейти в режим ожидания.
  • Сигнал TERM представляет собой запрос на завершение программы. Предполагается, что процесс, получивший этот сигнал, осуществляет очистку и завершается.
  • У сигнала HUP есть две распространенные интерпретации. Во-первых, многие демоны воспринимают его как команду сброса. Если демон способен повторно про честь свой конфигурационный файл и адаптироваться к изменениям без перезапуска, сигнал HUP позволяет менять его поведение.
  • Во-вторых, этот сигнал иногда генерируется драйвером терминала при попытке уничтожить связанные с терминалом процессы. В основном это поведение сохранилось со времен использования проводных соединений терминалов и модемов. Отсюда и название "отбой".
  • Интерпретаторы семейства csh (tcsh и другие) обычно делают фоновые процессы невосприимчивыми к сигналу HUP, чтобы они могли продолжать свою работу, даже когда пользователь выходит из системы. Пользователи интерпретаторов семейства sh (ksh, bash и так далее) могут эмулировать такое поведение с помощью команды nohup.
  • Сигнал QUIT напоминает сигнал TERM, за исключением того, что по умолчанию стандартный обработчик создает дамп памяти.
Сигналы USR1 и USR2 не имеют стандартного назначения. Ими можно пользоваться в различных целях. Например, веб-сервер Apache интерпретирует сигнал USR1 как запрос на перезапуск. Отправка сигналов: команда kill Команду kill чаще всего используют для уничтожения процессов. Эта команда может послать процессу любой сигнал, но по умолчанию это сигнал TERM. Команду kill могут выполнять как рядовые пользователи (для своих собственных процессов), так и суперпользователь (для любого процесса). Она имеет следующий синтаксис: kill [-сигнал] идентификатор, где сигнал - это номер или символическое имя посылаемого сигнала, а идентификатор - номер искомого процесса. Команда kill без номера сигнала не гарантирует, что процесс будет уничтожен, поскольку сигнал TERM можно перехватывать, блокировать и игнорировать. Команда kill -9 идентификатор "гарантированно" уничтожает процесс, так как сигнал с номером 9 (KILL) не перехватывается. Используйте команду kill -9 только в случае, если "вежливый" запрос на завершение программы не был выполнен. Мы написали слово "гарантированно" в кавычках, так как иногда процессы переходят в такое состояние, в котором их нельзя завершить даже таким способом (обычно это связано с блокировкой ввода-вывода, например, при остановке жесткого диска). Единственный выход в такой ситуации - перезагрузка. Команда killall выполняет различные функции в системах UNIX и Linux. В Linux команда killall уничтожает процессы, заданные именем. Например, следующая команда уничтожает все процессы веб-сервера Apache. ubuntu$ sudo killall httpd Стандартная UNIX-команда killall, предусмотренная в дистрибутивах Solaris, HP-UX и AIX, не принимает параметры и просто уничтожает все процессы текущего пользователя. Ее выполнение от имени суперпользователя приведет к уничтожению процесса init и выключению компьютера. Ух! Команды pgrep и pkill в системах Solaris, HP-UX и Linux (но не в АЕХ) осуществляют поиск процессов, заданных именами (или другими атрибутами). Команда pgrep выводит идентификаторы процессов, удовлетворяющих заданному в командной строке критерию, а команда pkill посылает найденным процессам сигнал. Например, следующая команда посылает сигнал TERM всем процессам, выполняемым от имени пользователя ben. $ sudo pkill -u ben