Пример 2.
Пример 1.
В данном примере при получении сигнала SIGINT четырежды вызывается специальный обработчик, а в пятый раз происходит обработка по умолчанию.
#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);
/* восстанавливаем обработчик сигнала */
}
int main(int argc, char **argv)
{ signal (SIGINT, SigHndlr); /* установка реакции на сигнал */
while (1); /*”тело программы” */
return 0;
}
При разработке программ нередко приходится создавать временные файлы, которые позже удаляются. Если произошло непредвиденное событие, такие файлы могут остаться не удаленными. Ниже приведено решение этой задачи.
#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);
/*уничтожение временного файла в случае нормального функционирования процесса */
return 0;
}
Существуют задачи, в которых необходимо прервать выполнение процесса по истечении некоторого количества времени. Средствами ОС “заводится” будильник, который будет поторапливать ввести некоторое имя. Системный вызов 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); /* заводим будильник */
signal(SIGALRM, alrm); /* переустанавливаем реакцию на сигнал */
}
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 - функцию alarm(), далее мы заводим будильник, запрашиваем “ Введите имя ” и ожидаем ввода строки символов. Если ввод строки задерживается, то будет вызвана функция alarm(), которая напомнит, что программа “ждет имя”, опять заведет будильник и поставит себя на обработку сигнала SIGALRM еще раз. И так будет до тех пор, пока не будет введена строка. Здесь имеется один нюанс: если в момент выполнения системного вызова возникает событие, связанное с сигналом, то система прерывает выполнение системного вызова и возвращает код ответа, равный «-1».
|
|
Пример. Двухпроцессный вариант программы “Будильник”.
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
void alr(int s)
{
printf(“\n Быстрее!!! \n”);
signal(SIGALRM, alr);
/* переустановка обработчика alr на приход сигнала SIGALRM */
}
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. После этого мы запускаем сыновний процесс, и отцовский процесс (бесконечный цикл) “засыпает” на 5 единиц времени, после чего сыновнему процессу будет отправлен сигнал SIGALRM. Все, что ниже цикла, будет выполняться в процессе-сыне: мы ожидаем ввода строки, если ввод осуществлен, то происходит уничтожение отца (SIGKILL).
Неименованные каналы.
Одним из простейших средств взаимодействия процессов в операционной системе UNIX является механизм каналов. Неименованный канал- область на диске, к которой не возможен доступ по имени, а только с помощью двух дискрипторов с ней ассоциированных. Один для чтения, другой – для записи.
Отличитльные свойства:
1. В отличии от файла, который тоже является областью на диске и доступ к нему может быть также осуществлен по дескриптору, канал не имеет имени, т.е. доступ к нему возможен только по этим двум дескрипторам, т.е. его невозможно, как правило, открыть, используя какое-то имя. Его можно только создать, и в ответ будет получено два дескриптора, и ими уже можно пользоваться.
2. Канал обладает фиксированным размером, т.е. возможна ситуация переполнения, чего невозможно в файлах.
3. Канал не существует вне процессора, его породившего. Он является ресурсом, который создал тот или иной процесс, и если породивший его процесс завершается, то и канал уничтожается. Т.е. точно так же, как все открытые файлы закрываются и прочие ресурсы освобождаются при завершении процесса, так один из этих ресурсов это и канал, который в этом случае тоже освобождается. И соответственно если файл при этом сохраняется на диске, то содержимое канала исчезает бесследно.
4. Главное отличие от файла – это то, что в нем реализуется строго последовательный доступ к данным. Это означает, что данные могут быть получены только в том порядке, в котором они были в канал положены. Если в файле был возможен произвольный доступ к данным, то в канале это невозможно.
Одним из простейших средств взаимодействия процессов в операционной системе UNIX является механизм каналов. Неименованный канал есть некая сущность, в которую можно помещать и извлекать данные, для чего служат два файловых дескриптора, ассоциированных с каналом: один для записи в канал, другой — для чтения. Для создания канала служит системный вызов pipe():
int pipe (int *fd)
Данный системный вызов выделяет в оперативной памяти некоторое ограниченное пространство и возвращает че6рез параметр fd массив из двух файловых дескрипторов: один для записи в канал — fd[1], другой для чтения — fd[0].
Эти дескрипторы являются дескрипторами открытых файлов, с которыми можно работать, используя такие системные вызовы как read(), write(), dup() и пр.
|
|
Однако существуют различия в организации использования обычного файла и канала.
Особенности организации чтения данных из канала:
если прочитано меньше байтов, чем находится в канале, оставшиеся сохраняются в канале;
если делается попытка прочитать больше данных, чем имеется в канале, и при этом существуют открытые дескрипторы записи, ассоциированные с каналом, будет прочитано (т.е. изъято из канала) доступное количество данных, после чего читающий процесс блокируется до тех пор, пока в канале не появится достаточное количество данных для завершения операции чтения;
процесс может избежать такого блокирования, изменив для канала режим блокировки с использованием системного вызова fcntl(), в этом случае будет считано доступное количество данных, и управление будет сразу возвращено процессу;
при закрытии записывающей стороны канала, в него помещается символ EOF (т.е. ситуация когда закрыты все дескрипторы, ассоциированные с записью в канал), после этого процесс, осуществляющий чтение, может выбрать из канала все оставшиеся данные и признак конца файла, благодаря которому блокирования при чтении в этом случае не происходит.
Особенности организации записи данных в канал:
если процесс пытается записать большее число байтов, чем помещается в канал (но не превышающее предельный размер канала) записывается возможное количество данных, после чего процесс, осуществляющий запись, блокируется до тех пор, пока в канале не появится достаточное количество места для завершения операции записи;
процесс может избежать такого блокирования, изменив для канала режим блокировки с использованием системного вызова fcntl(). В неблокирующем режиме в ситуации, описанной выше, будет записано возможное количество данных, и управление будет сразу возвращено процессу.
если же процесс пытается записать в канал порцию данных, превышающую предельный размер канала, то будет записано доступное количество данных, после чего процесс заблокируется до появления в канале свободного места любого размера (пусть даже и всего 1 байт), затем процесс разблокируется, вновь производит запись на доступное место в канале, и если данные для записи еще не исчерпаны, вновь блокируется до появления свободного места и т.д., пока не будут записаны все данные, после чего происходит возврат из вызова write()
|
|
если процесс пытается осуществить запись в канал, с которым не ассоциирован ни один дескриптор чтения, то он получает сигнал SIGPIPE (тем самым ОС уведомляет его о недопустимости такой операции).
В стандартной ситуации (при отсутствии переполнения) система гарантирует атомарность операции записи, т. е. при одновременной записи нескольких процессов в канал их данные не перемешиваются.