Курынин Р.В., Машечкин И.В., Терехин А.Н. - Конспект лекций по ОС (1114685), страница 33
Текст из файла (страница 33)
Дальше процессреагирует на получение сигнала, но об этом чуть позже.121Инициатором посылки сигнала может выступать другой процесс. В качестве примераможно привести следующую ситуацию. Пользователь ОС Unix запустил некоторый процесс,который в некоторый момент времени зацикливается. Чтобы снять этот процесс со счета,пользователь может послать ему сигнал об уничтожении (например, нажав на клавиатурекомбинацию клавиш Ctrl+C, а это есть команда интерпретатору команд послать код сигналаSIGINT). В данном случае процесс интерпретатора команд пошлет сигнал пользовательскомупроцессу.Аппарат сигналов является механизмом асинхронного взаимодействия, момент приходасигнала процессу заранее неизвестен.
Так же, как и аппарат прерываний, имеющийфиксированное количество различных прерываний, Unix-системы имеют фиксированный наборсигналов. Перечень сигналов, реализованных в конкретной операционной системе, обычнонаходится в файле signal.h. В этом файле перечисляется набор пар «имя сигнала — егоцелочисленное значение».При получении процессом сигнала возможны 3 типа реакции на него.
Во-первых, этообработка сигнала по умолчанию. В подавляющем большинстве случаев обработка сигнала поумолчанию означает завершение процесса. В этом случае системным кодом завершения процессастановится номер пришедшего сигнала.Во-вторых, процесс может перехватывать обработку пришедшего сигнала. Если процессполучает сигнал, то вызывается функция, принадлежащая телу процесса, которая быласпециальным образом зарегистрирована в системе как обработчик сигнала. Следует отметить,что часть реализованных в ОС сигналов можно перехватывать, а часть сигналов перехватыватьнельзя. Примером неперехватываемого сигнала может служить сигнал SIGKILL (код 9),предназначенный для безусловного уничтожения процесса. А упомянутый выше сигнал SIGINT(код 2) перехватить можно.В-третьих, сигналы можно игнорировать, т.е.
приход некоторых сигналов процесс можетпроигнорировать. Как и в случае с перехватываемыми сигналами, часть сигналов можноигнорировать (например, SIGINT), а часть — нет (например, SIGKILL).Для отправки сигнала в ОС Unix имеется системный вызов kill().#include <sys/types.h>#include <signal.h>int kill(pid_t pid, int sig);В данной функции первый параметр (pid) — идентификатор процесса, которомунеобходимо послать сигнал, а второй параметр (sig) — номер передаваемого сигнала.
Если первыйпараметр отличен от нуля, то он трактуется как идентификатор процесса-адресата; если же оннулевой, то сигнал посылается всем процессам данной группы. При удачном выполнениивозвращает 0, иначе возвращается -1.Чтобы установить реакцию процесса на приходящий сигнал, используется системныйвызов signal().#include <signal.h>void (*signal (int sig, void (*disp)(int)))(int);Аргумент sig определяет сигнал, реакцию на приход которого надо изменить. Второйаргумент disp определяет новую реакцию на приход указанного сигнала.
Итак, disp — это либоопределенная пользователем функция-обработчик сигнала, либо одна из констант: SIG_DFL122(обработка сигнала по умолчанию) или SIG_IGN (игнорирование сигнала). В случае успешногозавершения системного вызова signal() возвращается значение предыдущего режима обработкиданного сигнала (т.е. либо указатель на функцию-обработчик, либо одну из указанных констант).Если мы успешно установили в качестве обработчика сигнала свою функцию, то привозникновении сигнала выполнение процесса прерывается, фиксируется точка возврата, иуправление в процессе передается данной функции, при этом в качестве фактическогоцелочисленного параметра передается номер пришедшего сигнала (тем самым возможноиспользование одной функции в качестве обработчика нескольких сигналов). Соответственно, привыходе из функции-обработчика управление передается в точку возврата, и процесс продолжаетсвою работу.Стоит обратить внимание на то, что возможны и достаточно часто происходят ситуации,когда сигнал приходит во время вызова процессом некоторого системного вызова.
В этом случаепоследующие действия зависят от реализации системы. В одном случае системный вызовпрерывается с отрицательным кодом возврата, а в переменную errno заносится код ошибки. Либосистемный вызов «дорабатывает» до конца. Мы будем придерживаться первой стратегии(прерывание системного вызова).Рассмотрим ряд примеров.Пример. Перехват и обработка сигнала. В данной программе 4 раза можно нажатьCTRL+C (послать сигнал SIGINT), и ничего не произойдет. На 5-ый раз процесс обработаетсигнал обработчиком по умолчанию и поэтому завершится.#include <sys/types.h>#include <signal.h>#include <stdio.h>int count = 1;/* обработчик сигнала */void SigHndlr(int s){printf(“\nI got SIGINT %d time(s)\n”, count++);if(count == 5){/* установка обработчика по умолчанию */signal(SIGINT, SIG_DFL);}}123/* тело программы */int 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);/* завершение работы */exit(0);}int main(int argc, char **argv)124{signal(SIGINT, SigHndlr);.../* открытие временного файла */creat(tempfile, 0666);.../* удаление временного файла */unlink(tempfile);return 0;}Пример.
Программа «будильник». При запуске программа просит ввести имя и ожидаетввод этого имени. А дальше в цикле будут происходить следующие действия. Если по прошествиинекоторого времени пользователь так и не ввел имени, то программа повторяет свою просьбу.#include <unistd.h>#include <signal.h>#include <stdio.h>void Alrm(int s){printf(“\n жду имя \n”);alarm(5);}int main(int argc, char **argv){char s[80];signal(SIGALRM, Alrm);alarm(5);printf(“Введите имя\n”);125for(;;){printf(“имя:”);if(gets(s) != NULL) break;}printf(“OK!\n”);return 0;}В данном примере происходит установка обработчика сигнала SIGALRM. Затемпроисходит обращение к системному вызову alarm(), который заводит будильник на 5 единицвремени.
Поскольку продолжительность единицы времени зависит от конкретной реализациисистемы, то мы будем считать в нашем примере, что происходит установка будильника на 5секунд. Это означает, что по прошествии 5 секунд процесс получит сигнал SIGALRM. Дальшеуправление передается бесконечному циклу for, выход из которого возможен лишь при вводенепустой строки текста. Если же по истечении 5 секунд ввода так и не последовало, то приходитсигнал SIGALRM, управление передается обработчику Alrm, который печатает на экраннапоминание о необходимости ввода имени, а затем снова устанавливает будильник на 5 секунд.Затем управление возвращается в функцию main в бесконечный цикл. Далее последовательностьдействий повторяется.Пример. Двухпроцессный вариант программы «будильник».
Данный пример будетповторять предыдущий, но теперь функции ввода строки и напоминания будут разнесены поразным процессам.#include <signal.h>#include <sys/types.h>#include <unistd.h>#include <stdio.h>Void Alrm(int s){printf(“\nБыстрее!!!\n”);}int main(int argc, char **argv){126char s[80];int pid;signal(SIGALRM, Alrm);if(pid = fork()){/* ОТЦОВСКИЙ ПРОЦЕСС */for(;;){sleep(5);kill(pid, SIGALRM);}}else{/* СЫНОВИЙ ПРОЦЕСС */printf(“Введите имя\n”);for(;;){printf(“имя: “);if(gets(s) != NULL) break;}printf(“OK!\n”);/* уничтожение отцовского процесса */kill(getppid, SIGKILL);}return 0;}127В этом примере происходит установка обработчика сигнала SIGALRM.
Затем происходитобращение к системному вызову fork(), который породит дочерний процесс. Далее отцовскийпроцесс в бесконечном цикле производит одну и ту же последовательность действий. Засыпает на5 единиц времени (посредством системного вызова sleep()), затем шлет сигнал SIGALRM своемусыну с помощью системного вызова kill().
Первым параметром данному системному вызовупередается идентификатор дочернего процесса (PID), который был получен после вызова fork().Дочерний процесс запрашивает ввод имени, а дальше в бесконечном цикле ожидает вводастроки текста до тех пор, пока не получит непустую строку. При этом он периодически получаетот отцовского процесса сигнал SIGALRM, вследствие чего выводит на экран напоминание. Послеполучения непустой строки он печатает на экране подтверждение успешности ввода (“OK!”),посылает процессу-отцу сигнал SIGKILL и завершается.
Послать сигнал безусловного завершенияотцовскому процессу необходимо, поскольку после завершения дочернего процесса тот будетнекорректно слать сигнал SIGALRM (возможно, что идентификатор процесса-сына потом получитсовершенно иной процесс со своей логикой работы, а процесс-отец так и будет слать на его PIDсигналы SIGALRM).3.1.2Неименованные каналыНеименованный канал (или программный канал) представляется в виде области памятина внешнем запоминающем устройстве, управляемой операционной системой, котораяосуществляет выделение взаимодействующим процессам частей из этой области памяти длясовместной работы, т.е. это область памяти является разделяемым ресурсом.Для доступа к неименованному каналу система ассоциирует с ним два файловыхдескриптора.
Один из них предназначен для чтения информации из канала, т.е. с ним можноассоциировать файл, открытый только на чтение. Другой дескриптор предназначен для записиинформации в канал. Соответственно, с ним может быть ассоциирован файл, открытый только назапись.Организация данных в канале использует стратегию FIFO, т.е. информация, первойзаписанная в канал, будет и первой прочитанной из канала. Это означает, что для данныхфайловых дескрипторов недопустимы работы по перемещению файлового указателя. В отличие отфайлов канал не имеет имени.
Кроме того, в отличие от файлов неименованный канал существуетв системе, пока существуют процессы, его использующие. Предельный размер канала, которыйможет быть выделен процессам, декларируется параметрами настройки операционной системы.Для создания неименованного канала используется системный вызов pipe().#include <unistd.h>int pipe(int *fd);Аргументом данного системного вызова является массив fd из двух целочисленныхэлементов.
Если системный вызов pipe() прорабатывает успешно, то он возвращает код ответа,равный нулю, а массив будет содержать два открытых файловых дескриптора. Соответственно, вfd[0] будет содержаться дескриптор чтения из канала, а в fd[1] — дескриптор записи в канал.После этого с данными файловыми дескрипторами можно использовать всевозможные средстваработы с файлами, поддерживающие стратегию FIFO, т.е.
любые операции работы с файлами, заисключением тех, которые касаются перемещения файлового указателя.Неименованные каналы в общем случае предназначены для организации взаимодействияродственных процессов, осуществляющегося за счет передачи по наследству ассоциированных сканалом файловых дескрипторов. Но иногда встречаются вырожденные случаи использованиянеименованного канала в рамках одного процесса.128Пример.
Использование неименованного канала. В нижеприведенном примерепроизводится копирование текстовой строки с использованием канала. Этот пример является«надуманным»: он иллюстрирует случай использования канала в рамках одного процесса.int main(int argc, char **argv){char *s = “channel”;char buf[80];int pipes[2];pipe(pipes);write(pipes[1], s, strlen(s) + 1);read(pipes[0], buf, strlen(s) + 1);close(pipes[0]);close(pipes[1]);printf(“%s\n”, buf);return 0;}В приведенном примере имеется текстовая строка s, которую хотим скопировать в буферbuf. Для этого дополнительно декларируется массив pipes, в котором будут храниться файловыедескрипторы, ассоциированные с каналом. После обращения к системному вызову pipe() элементpipe[1] хранит открытый файловый дескриптор, через который можно писать в канал, а pipe[0] —файловый дескриптор, через который можно писать из канала. Затем происходит обращение ксистемному вызову write(), чтобы скопировать содержимое строки s в канал, а после этого идетобращение к системному вызову read(), чтобы прочитать данные из канала в буфер buf.