2011. Машбук (1114722), страница 39
Текст из файла (страница 39)
В одномслучае системный вызов прерывается с отрицательным кодом возврата, а в переменную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()).
Затем происходит обращение к системному вызову alarm(), который заводитбудильник на 5 единиц времени. Поскольку продолжительность единицы времени зависитот конкретной реализации системы, то мы будем считать в нашем примере, чтопроисходит установка будильника на 5 секунд. Это означает, что по прошествии 5 секундпроцесс получит сигнал SIGALRM. Далее выводится запрос “Введите имя” и управлениепередается бесконечному циклу for, выход из которого возможен лишь при вводенепустой строки текста. Если же по истечении 5 секунд ввода так и не последовало, топриходит сигнал SIGALRM, управление передается обработчику alrm, который печатаетна экран напоминание о необходимости ввода имени, а затем снова устанавливаетбудильник на 5 секунд. Далее управление возвращается в функцию main в бесконечныйцикл, и последовательность действий повторяется.
Отметим, что если в момент140выполнения системного вызова возникает событие, связанное с сигналом, то системапрерывает выполнение системного вызова и возвращает код ответа, равный «-1».Пример. Двухпроцессный вариант программы “Будильник”.#include#include#include#include<signal.h><sys/types.h><unistd.h><stdio.h>void alr(int s){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 (;;){printf(“имя:”);if (gets(s) != NULL) break; /*ожидаем вводаимени*/}printf(“OK!\n”);kill(getppid(), SIGKILL);/* убиваем зациклившегося отца */}return 0;}В данном случае программа реализуется в двух процессах.
Как и в предыдущемпримере, имеется функция реакции на сигнал alr(), которая выводит на экрансообщение-запрос. В основной программе мы также указываем alr() как реакцию насигнал SIGALRM. После этого мы запускаем сыновний процесс, и отцовский процесс141(бесконечный цикл) “засыпает” на 5 единиц времени, после чего отправляет сыновнемупроцессу сигнал SIGALRM с помощью системного вызова kill(). Первым параметромданному системному вызову передается идентификатор сыновнего процесса (PID),который был получен после вызова fork().Сыновний процесс запрашивает ввод имени, а дальше в бесконечном циклеожидает ввода строки текста до тех пор, пока не получит непустую строку. При этом онпериодически получает от отцовского процесса сигнал SIGALRM, вследствие чеговыводит на экран напоминание.
После получения непустой строки он печатает на экранеподтверждение успешности ввода (“OK!”), посылает процессу-отцу сигнал SIGKILL изавершается. Послать сигнал безусловного завершения отцовскому процессу необходимо,поскольку после завершения сыновнего процесса тот будет некорректно слать сигналSIGALRM (возможно, что идентификатор процесса-сына потом получит совершенно инойпроцесс со своей логикой работы, а процесс-отец так и будет слать на его PID сигналыSIGALRM).3.1.2 Надежные сигналы.Вышеописанная реализация механизма сигналов имела место в ранних версияхUNIX (UNIX System V.2 и раньше). Позднее эта реализация подверглась критике занедостаточную надежность.
В частности, это касалось сброса диспозиции перехваченногосигнала в реакцию по умолчанию всякий раз перед вызовом функции-обработчика. Хотя исуществует возможность заново установить реакцию на сигнал в функции-обработчике,возможна ситуация, когда между моментом вызова пользовательского обработчиканекоторого сигнала и моментом, когда он восстановит нужную реакцию на этот сигнал,будет получен еще один экземпляр того же сигнала.
В этом случае второй экземпляр небудет перехвачен, так как на момент его прихода для данного сигнала действует реакцияпо умолчанию.Поэтому в новых версиях UNIX (BSD UNIX 4.2 и System V.4) была реализованаальтернативная модель так называемых надежных сигналов, которая вошла и в стандартPOSIX. В этой модели при перехватывании сигнала ядро не меняет его диспозицию, темсамым появляется гарантия перехвата всех экземпляров сигнала.
Кроме того, чтобыизбежать нежелательных эффектов при рекурсивном вызове обработчика для множестваэкземпляров одного и того же сигнала, ядро блокирует доставку других экземпляров тогоже сигнала в процесс до тех пор, пока функция-обработчик не завершит свое выполнение.В модели надежных сигналов также появилась возможность на время блокироватьдоставку того или иного вида сигналов в процесс. Отличие блокировки сигнала отигнорирования в том, что пришедшие экземпляры сигнала не будут потеряны, апроизойдет лишь откладывание их обработки на тот период времени, пока процесс неразблокирует данный сигнал.
Таким образом процесс может оградить себя от прерываниясигналом на тот период, когда он выполняет какие-либо критические операции. Дляреализации механизма блокирования вводится понятие сигнальной маски, котораяописывает, какие сигналы из посылаемых процессу блокируются. Процесс наследует своюсигнальную маску от родителя при порождении2, и имеет возможность изменять ее впроцессе своего выполнения.Рассмотрим системные вызовы для работы с сигнальной маской процесса.Сигнальная маска описывается битовым полем типа sigset_t.
Для управлениясигнальной маской процесса служит системный вызов:#include <signal.h>2Несмотря на это, как уже говорилось, сами сигналы, ожидающие своей обработки родительскимпроцессом на момент порождения потомка, в том числе и блокированные, не наследуются потомком142int sigprocmask(int how, const sigset_t *set, sigset_t*old_set);Значения аргумента how влияют на характер изменения маски сигналов:SIG_BLOCK – к текущей маске добавляются сигналы, указанные в наборе set;SIG_UNBLOCK – из текущей маски удаляются сигналы, указанные в наборе set;SIG_SETMASK – текущая маска заменяется на набор set.Если в качестве аргумента set передается NULL-указатель, то сигнальная маска неизменяется, значение аргумента how при этом игнорируется.
В последнем аргументевозвращается прежнее значение сигнальной маски до изменения ее вызовомsigprocmask(). Если процесс не интересуется прежним значением маски, он можетпередать в качестве этого аргумента NULL-указатель.Если один или несколько заблокированных сигналов будут разблокированыпосредством вызова sigprocmask(), то для их обработки будет использованадиспозиция сигналов, действовавшая до вызова sigprocmask(). Если за времяблокирования процессу пришло несколько экземпляров одного и того же сигнала, то ответна вопрос о том, сколько экземпляров сигнала будет доставлено – все или один – зависитот реализации конкретной ОС.Существует ряд вспомогательных функций, используемых для того, чтобысформировать битовое поле типа sigset_t нужного вида:Инициализация битового набора - очищение всех битов:#include <signal.h>int sigemptyset(sigset_t *set);Противоположная предыдущей функция устанавливает все биты в наборе:#include <signal.h>int sigfillset(sigset_t *set);Две следующие функции позволяют добавить или удалить флаг,соответствующий сигналу, в наборе:#include <signal.h>int sigaddset(sigset_t *set, int signo);int sigdelset(sigset_t *set, int signo);В качестве второго аргумента этим функциям передается номер сигналаПриведенная ниже функция проверяет, установлен ли в наборе флаг,соответствующий сигналу, указанному в качестве параметра:#include <signal.h>int sigismember(sigset_t *set, int signo);Этот вызов возвращает 1, если в маске set установлен флаг,соответствующий сигналу signo, и 0 в противном случае.Чтобы узнать, какие из заблокированных сигналов ожидают доставки,используется функция sigpending():#include <signal.h>int sigpending(sigset_t *set);Через аргумент этого вызова возвращается набор сигналов, ожидающих доставки.Пример.