Н.В. Вдовикина, И.В. Машечкин, А.Н. Терехин, А.Н. Томилин - Операционные системы - взаимодействие процессов (2008) (1114653), страница 13
Текст из файла (страница 13)
Существует также возможность одновременнопослать сигнал нескольким процессам, например, если значениепервого параметра есть 0, сигнал будет передан всем процессам,которые принадлежат той же группе, что и процесс, посылающийсигнал, за исключением процессов с идентификаторами 0 и 1.Во втором параметре передается номер посылаемого сигнала.Если этот параметр равен 0, то будет выполнена проверкакорректности обращения к kill() (в частности, существованиепроцесса с идентификатором pid), но никакой сигнал вдействительности посылаться не будет.Еслипроцесс-отправительнеобладаетправамипривилегированного пользователя, то он может отправить сигналтолько тем процессам, у которых реальный или эффективныйидентификатор владельца процесса совпадает с реальным илиэффективным идентификатором владельца процесса-отправителя.Для определения реакции на получение того или иногосигнала в процессе служит системный вызов signal():#include <signal.h>void (*signal ( int sig, void (*disp) (int))) (int)где аргумент sig — номер сигнала, для которогоустанавливается реакция, а disp — либо определеннаяпользователем функция-обработчик сигнала, либо одна из констант:SIG_DFL и SIG_IGN.
Первая из них указывает, что необходимоустановить для данного сигнала обработку по умолчанию, т.е.стандартную реакцию системы, а вторая — что данный сигналнеобходимо игнорировать. При успешном завершении функциявозвращает указатель на предыдущий обработчик данного сигнала(он может использоваться процессом, например, для последующеговосстановления прежней реакции на сигнал).Как видно из прототипа вызова signal(), определеннаяпользователем функция-обработчик сигнала должна принимать одинцелочисленный аргумент (в нем будет передан номеробрабатываемого сигнала), и не возвращать никаких значений.Отметим одну особенность реализации сигналов в раннихверсиях UNIX: каждый раз в момент получения сигнала его71диспозиция (т.е.
способ реакции на сигнал) сбрасывается в действиепо умолчанию, т.о. если процесс желает многократно обрабатыватьсигнал своим собственным обработчиком, он должен каждый разпри обработке сигнала заново устанавливать реакцию на него (см.Пример 10).В заключение отметим, что механизм сигналов являетсядостаточно ресурсоемким, ибо отправка сигнала представляет собойсистемный вызов, а доставка сигнала влечет за собой прерываниенормального порядка выполнения процесса-получателя.
Вызовфункции-обработчика и возврат требует операций со стеком.Сигналы также несут весьма ограниченную информацию.Пример 10. Обработка сигнала.В данном примере при получении сигнала SIGINT четыреждывызывается специальный обработчик, а в пятый раз происходитобработка по умолчанию.#include <sys/types.h>#include <signal.h>#include <stdio.h>int count = 1;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;}72Пример 11.
Удаление временных файлов при завершениипрограммы.При разработке программ нередко приходится создаватьвременные файлы, которые позже удаляются. Если произошлонепредвиденное событие, такие файлы могут остаться неудаленными. Ниже приведено решение этой задачи13.#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 */exit(0);}int main(int argc, char **argv){signal (SIGINT, SigHndlr); /*установка реакциина сигнал */…creat(tempfile, 0666); /*создание временногофайла*/…unlink(tempfile);13Отметим, что в стандартной библиотеке Си существует специальная функцияtmpfile(), позволяющая создать такой временный файл, который будет автоматическиуничтожен при его закрытии либо нормальном завершении программы (путем выхода изфункции main() или обращения к сервисной функции exit()).
Однако, поставленной в примерезадачи это не решает, т.к. окончание работы программы в результате получениянеобработанного сигнала SIGINT не является случаем «нормального завершения», которыйможет быть обработан средствами стандартной библиотеки.73/*уничтожение временного файла в случаенормального функционирования процесса */return 0;}В данном примере для созданияиспользуется системный вызов creat():временногофайла#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>int creat(const char *pathname, mode_t mode);А системный вызов unlink() удаляет файл с заданнымименем14.#include <unistd.h>int unlink(const char *pathname);Пример 12.
Программа “Будильник”.Программа “Будильник”. Существуют задачи, в которыхнеобходимо прервать выполнение процесса по истечениинекоторого количества времени. Средствами ОС“заводится”будильник, который будет поторапливать ввести некоторое имя.Системный вызов alarm():#include <unistd.h>unsigned int alarm(unsigned int seconds);инициализирует отложенное появление сигнала SIGALRM - процессзапрашивает ядро отправить ему самому сигнал по прошествииопределенного времени.#include <unistd.h>#include <signal.h>#include <stdio.h>void alrm(int s) /*обработчик сигнала SIGALRM */{14точнее говоря, unlink() разрывает связь между путевым именем файла и егосодержимым (иначе говоря, доступ к содержимому файла по указанному пути после вызоваunlink() станет невозможен).
Само содержимое файла при этом будет уничтожено лишь в томслучае, если не останется ни одной другой жесткой ссылки на файл74signal(SIGALRM,alrm);реакцию на сигнал *//*переустанавливаемprintf(“\n жду имя \n”);alarm(5); /* заводим будильник */}int main(int argc, char **argv){char s[80];signal(SIGALRM, alrm);/* установка обработчика alrm на приход сигналаSIGALRM */alarm(5); /* заводим будильник */printf(“Введите имя \n”);for (;;){printf(“имя:”);if (gets(s) !=ввода имени */NULL)break;/*ожидаем};printf(“OK! \n”);return 0;}В начале программы мы устанавливаем реакцию на сигналSIGALRM - функцию alrm(), далее мы заводим будильник,запрашиваем “Введите имя” и ожидаем ввода строки символов.Если ввод строки задерживается, то по приходу сигнала будетвызвана функция alrm(), которая напомнит, что программа «ждетимя» и опять заведет будильник (обратите внимание, что длясовместимости с ранними версиями UNIX в начале обработчикапервым делом переустанавливается пользовательская обработкасигнала SIGALRM).
Так будет происходить до тех пор, пока не будетвведена строка. Здесь имеется один нюанс: если в моментвыполнения системного вызова возникает событие, связанное ссигналом, то система прерывает выполнение системного вызова ивозвращает код ответа, равный -1.75Пример 13. Двухпроцессный“Будильник”.вариантпрограммы#include <signal.h>#include <sys/types.h>#include <unistd.h>#include <stdio.h>void alr(int s){signal(SIGALRM, alr);/* переустановкасигнала SIGALRM */обработчикаalrнаприходprintf(“\n Быстрее!!! \n”);}int main(int argc, char **argv){char s[80];int pid;signal(SIGALRM, alr);/* установка обработчика alr на приход сигналаSIGALRM */if(pid = fork()) { // процесс-предокfor (;;){sleep(5);/*приостанавливаемпроцесс на 5 секунд */kill(pid, SIGALRM);/*отправляем сигнал SIGALRM процессусыну */}}else { // процесс-потомокprintf(“Введите имя \n”);for (;;)76{printf(“имя:”);if (gets(s) != NULL) break; /*ожидаемввода имени*/}printf(“OK!\n”);kill(getppid(), SIGKILL);/* уничтожаем зациклившегося отца */}return 0;}В данном случае программа реализуется в двух процессах,причем роль «будильника» выполняет один из процессов (неприбегая к вызову alarm()).
Как и в предыдущем примере, имеетсяфункция реакции на сигнал alr(), которая выводит на экрансообщение и заново переустанавливает пользовательскую реакциюна сигнал. В основной программе мы также устанавливаем alr() вкачестве обработчика SIGALRM. После этого порождается сыновнийпроцесс, и отцовский процесс в бесконечном цикле «засыпает» на 5единиц времени, после чего отправляет сыновнему процессу сигналSIGALRM. В процессе-сыне ожидается ввод строки, а если вводосуществлен, то происходит уничтожение отца (путем отправки емусигнала SIGKILL).4.2 Надежные сигналыВышеописанная реализация механизма сигналов имела местов ранних версиях UNIX (UNIX System V.2 и раньше).
Позднее этареализация подверглась критике за недостаточную надежность. Вчастности, это касалось сброса диспозиции перехваченного сигналав реакцию по умолчанию всякий раз перед вызовом функцииобработчика. Хотя и существует возможность заново установитьреакцию на сигнал в функции-обработчике, возможна ситуация,когда между моментом вызова пользовательского обработчиканекоторого сигнала и моментом, когда он восстановит нужнуюреакцию на этот сигнал, будет получен еще один экземпляр того жесигнала. В этом случае второй экземпляр не будет перехвачен, таккак на момент его прихода для данного сигнала действует реакцияпо умолчанию.Поэтому в новых версиях UNIX (начиная с BSD UNIX 4.2 иSystem V.4) была реализована альтернативная модель так77называемых надежных сигналов, которая вошла и в стандарт POSIX.В этой модели при перехватывании сигнала ядро не меняет егодиспозицию, тем самым появляется гарантия перехвата всехэкземпляров сигнала.
Кроме того, чтобы избежать нежелательныхэффектов при рекурсивном вызове обработчика для множестваэкземпляров одного и того же сигнала, ядро блокирует доставкудругих экземпляров того же сигнала в процесс до тех пор, покафункция-обработчик не завершит свое выполнение.В модели надежных сигналов также появилась возможность навремя блокировать доставку того или иного вида сигналов впроцесс. Отличие блокировки сигнала от игнорирования в том, чтопришедшие экземпляры сигнала не будут потеряны, а произойдетлишь откладывание их обработки на тот период времени, покапроцесс не разблокирует данный сигнал.
Таким образом процессможет оградить себя от прерывания сигналом на тот период, когдаон выполняет какие-либо критические операции. Для реализациимеханизма такого блокирования вводится понятие сигнальноймаски, которая описывает, какие сигналы из посылаемых процессублокируются. Процесс наследует свою сигнальную маску отродителя при порождении15, и имеет возможность изменять ее впроцессе своего выполнения.Рассмотрим системные вызовы для работы с сигнальноймаской процесса.