Операционные системы 2011 (1114689), страница 39
Текст из файла (страница 39)
В этом плане сигналы можно рассматривать как программный аналогаппаратных прерываний.Так же, как и аппарат прерываний, имеющий фиксированное количестворазличных прерываний, Unix-системы имеют фиксированный набор сигналов. Переченьсигналов, реализованных в конкретной операционной системе, обычно находится в файле<signal.h>. В этом файле перечисляется набор пар «имя сигнала — его целочисленноезначение». Ниже приведено несколько примеров (следует заметить, что в разных версияхUNIX имена сигналов могут различаться).2 - SIGINT/*прерывание*/3 - SIGQUIT /*аварийный выход*/9 - SIGKILL /*уничтожение процесса*/14 - SIGALRM /*прерывание от таймера*/18 – SIGCHLD /* процесс-потомок завершился */При получении процессом сигнала возможны три типа реакции на него (Рис. 96).Во-первых, это обработка сигнала по умолчанию.
В подавляющем большинстве случаевобработка сигнала по умолчанию означает завершение процесса. В этом случаесистемным кодом завершения процесса становится номер пришедшего сигнала.Во-вторых, процесс может перехватывать обработку пришедшего сигнала. Еслипроцесс получает сигнал, то вызывается функция, принадлежащая телу процесса, котораябыла специальным образом зарегистрирована в системе как обработчик сигнала. Следуетотметить, что часть реализованных в ОС сигналов можно перехватывать, а часть сигналовперехватывать нельзя. Примером неперехватываемого сигнала может служить сигналSIGKILL (код 9), предназначенный для безусловного уничтожения процесса. Аупомянутый выше сигнал SIGINT (код 2) перехватить можно.В-третьих, сигналы можно игнорировать, т.е. приход некоторых сигналов процессможет проигнорировать.
Как и в случае с перехватываемыми сигналами, часть сигналовможно игнорировать (например, SIGINT), а часть — нет (например, SIGKILL).136Рис. 96. Реакции на получение процессом сигнала.Если в процесс одновременно доставляется несколько различных сигналов, топорядок их обработки не определен. Если же обработки ждут несколько экземпляроводного и того же сигнала, то ответ на вопрос, сколько экземпляров будет доставлено впроцесс – все или один – зависит от конкретной реализации ОС.Отдельного рассмотрения заслуживает ситуация, когда сигнал приходит в моментвыполнения системного вызова. Обработка такой ситуации в разных версиях UNIXреализована по-разному, например, обработка сигнала может быть отложена дозавершения системного вызова; либо системный вызов автоматически перезапускаетсяпосле его прерывания сигналом; либо системный вызов вернет –1, а в переменной errnoбудет установлено значение EINTR.Для отправки сигнала процессу в ОС Unix имеется системный вызов kill().#include <sys/types.h>#include <SIGNAL.H>INT KILL (PID_T PID, INT SIG);Первый параметр вызова – идентификатор процесса, которому посылается сигнал(в частности, процесс может послать сигнал самому себе).
Существует также возможностьодновременно послать сигнал нескольким процессам. Например, если значение этогопараметра есть 0, сигнал будет передан всем процессам, которые принадлежат той жегруппе, что и процесс, посылающий сигнал, за исключением процессов сидентификаторами 0 и 1.Во втором параметре передается номер посылаемого сигнала. Если этот параметрравен 0, то будет выполнена проверка корректности обращения к kill() (в частности,существование процесса с идентификатором pid), но никакой сигнал в действительностипосылаться не будет.Если процесс-отправитель не обладает правами привилегированного пользователя,то он может отправить сигнал только тем процессам, у которых реальный илиэффективный идентификатор владельца процесса совпадает с реальным или эффективнымидентификатором владельца процесса-отправителя.При успешном завершении системный вызов kill() возвращает 0, иначевозвращается -1.Чтобы установить реакцию процесса на приходящий сигнал, используетсясистемный вызов signal().#INCLUDE <SIGNAL.H>137void (*signal( int sig, void (*disp) (int))) (int);Аргумент sig определяет номер сигнала, реакцию на приход которого надоустановить.
Второй аргумент disp определяет новую реакцию на приход указанногосигнала. То есть disp — это либо определенная пользователем функция-обработчиксигнала, либо одна из констант: SIG_DFL и SIG_IGN. Первая из них указывает, чтонеобходимо установить для данного сигнала обработку по умолчанию, т.е. стандартнуюреакцию системы, а вторая — что данный сигнал необходимо игнорировать. Приуспешном завершении системный вызов возвращает указатель на предыдущийобработчик данного сигнала (он может использоваться процессом, например, длявосстановления прежней реакции на сигнал).Как видно из прототипа вызова signal(), определенная пользователем функцияобработчик сигнала должна принимать один целочисленный аргумент (в нем будетпередан номер обрабатываемого сигнала), и не возвращать никаких значений.Отметим, что механизм сигналов является достаточно ресурсоемким, ибо отправкасигнала представляет собой системный вызов, а доставка сигнала - прерываниевыполнения процесса-получателя.
Вызов функции-обработчика и возврат требуетопераций со стеком. Если мы успешно установили в качестве обработчика сигнала своюфункцию, то при возникновении сигнала выполнение процесса прерывается, фиксируетсяточка возврата и управление в процессе передается данной функции, при этом в качествефактического целочисленного параметра передается номер пришедшего сигнала (темсамым возможно использование одной функции в качестве обработчика несколькихсигналов). Соответственно, при выходе из функции-обработчика управление передается вточку возврата и процесс продолжает свою работу.Стоит обратить внимание на то, что возможны и достаточно часто происходятситуации, когда сигнал приходит во время вызова процессом некоторого системноговызова.
В этом случае последующие действия зависят от реализации системы. В одномслучае системный вызов прерывается с отрицательным кодом возврата, а в переменнуюerrno заносится код ошибки. Либо системный вызов «дорабатывает» до конца. Мы будемпридерживаться первой стратегии (прерывание системного вызова).Рассмотрим ряд примеров.Пример. Перехват и обработка сигнала. Отметим одну особенность реализациисигналов в ранних версиях UNIX: каждый раз при получении сигнала его диспозиция (т.е.действие при получении сигнала) сбрасывается на действие по умолчанию, т.о. еслипроцесс желает многократно обрабатывать сигнал своим собственным обработчиком, ондолжен каждый раз при обработке сигнала заново устанавливать реакцию на него.В данном примере при получении сигнала SIGINT (что соответствует нажатиюCTRL+C) четыре раза вызывается специальный обработчик.
На пятый же раз происходитобработка сигнала обработчиком по умолчанию, в результате чего процесс завершается.#include <sys/types.h>#include <signal.h>#include <stdio.h>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);/* восстанавливаем обработчик сигнала */}138int main(int argc, char **argv){signal (SIGINT, SigHndlr); /* установка реакции на сигнал */while (1); /*”тело программы” */return 0;}Пример. Удаление временных файлов при завершении программы.
Приразработке программ нередко приходится создавать временные файлы, которые позжеудаляются. Если произошло непредвиденное событие, такие файлы могут остаться неудаленными. Ниже приведена программа, которая и в случае «дорабатывания» до конца, ив случае получения сигнала SIGINT перед завершением удаляет созданный ею временныйфайл.#include <unistd.h>#include <signal.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>const char * tempfile = “abc”;void SigHndlr (int s){unlink(tempfile);/* уничтожение временного файла в случае прихода сигналаSIGINT. В случае, если такой файл не существует (еще несоздан или уже удален), вызов вернет -1 */}int main(int argc, char **argv){signal (SIGINT, SigHndlr); /*установка реакции на сигнал */…creat(tempfile, 0666); /*создание временного файла*/…unlink(tempfile);/*уничтожение временного файла в случае нормальногофункционирования процесса */return 0;}В данном примере для создания временного файла используется системный вызовcreat():#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>int creat(const char *pathname, mode_t mode);А системный вызов unlink() удаляет имя и файл, на который оно ссылается.#include <unistd.h>139int unlink(const char *pathname);Пример.
Программа «будильник». Существуют задачи, в которых необходимопрервать выполнение процесса по истечении некоторого времени. В данном примересредствами ОС “заводится” будильник, который будет поторапливать ввести некотороеимя. Системный вызов alarm():#include <unistd.h>unsigned int alarm(unsigned int seconds);инициализирует отложенное появление сигнала SIGALRM - процесс запрашивает ядроотправить ему самому сигнал по прошествии определенного времени.#include <unistd.h>#include <signal.h>#include <stdio.h>void alrm(int s) /*обработчик сигнала SIG_ALRM */{printf(“\n жду имя \n”);alarm(5); /* заводим будильник */}int main(int argc, char **argv){char s[80];signal(SIGALRM, alrm);/* установка обработчика alrm на приход сигнала SIG_ALRM */alarm(5); /* заводим будильник */printf(“Введите имя \n”);for (;;){printf(“имя:”);if (gets(s) != NULL) break; /*ожидаем ввода имени */};printf(“OK! \n”);return 0;}В данном примере происходит установка обработчика сигнала SIGALRM (функцияalrm()).