|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
АвтоАвтоматизацияАрхитектураАстрономияАудитБиологияБухгалтерияВоенное делоГенетикаГеографияГеологияГосударствоДомДругоеЖурналистика и СМИИзобретательствоИностранные языкиИнформатикаИскусствоИсторияКомпьютерыКулинарияКультураЛексикологияЛитератураЛогикаМаркетингМатематикаМашиностроениеМедицинаМенеджментМеталлы и СваркаМеханикаМузыкаНаселениеОбразованиеОхрана безопасности жизниОхрана ТрудаПедагогикаПолитикаПравоПриборостроениеПрограммированиеПроизводствоПромышленностьПсихологияРадиоРегилияСвязьСоциологияСпортСтандартизацияСтроительствоТехнологииТорговляТуризмФизикаФизиологияФилософияФинансыХимияХозяйствоЦеннообразованиеЧерчениеЭкологияЭконометрикаЭкономикаЭлектроникаЮриспунденкция |
Лабораторная работа №5“Сигналы” Процессы в UNIX используют много разных механизмов взаимодействия. Одним из них являются сигналы. Сигналы - это асинхронные события. Что это значит? Сначала объясним, что такое синхронные события: я два раза в день подхожу к почтовому ящику и проверяю - нет ли в нем почты (событий). Во-первых, я произвожу опрос - "нет ли для меня события?", в программе это выглядело бы как вызов функции опроса и, может быть, ожидания события. Во-вторых, я знаю, что почта может ко мне прийти, поскольку я подписался на какие-то газеты. То есть я предварительно заказывал эти события. Схема с синхронными событиями очень распространена. Кассир сидит у кассы и ожидает, пока к нему в окошечко не заглянет клиент. Поезд периодически проезжает мимо светофора и останавливается, если горит красный. Функция Си пассивно “спит” до тех пор, пока ее не вызовут; однако она всегда готова выполнить свою работу (обслужить клиента). Такое ожидающее заказа (события) действующее лицо называется сервер. После выполнения заказа сервер вновь переходит в состояние ожидания вызова. Итак, если событие ожидается в специальном месте и в определенные моменты времени (издается некий вызов для опроса) - это синхронные события. Канонический пример – функция gets, которая задержит выполнение программы, пока с клавиатуры не будет введена строка. Большинство ожиданий внутри системных вызовов - синхронны. Ядро ОС выступает для программ пользователей в роли сервера, выполняющего системные вызовы (хотя и не только в этой роли - ядро иногда предпринимает и активные действия: передача процессора другому процессу через определенное время (режим разделения времени), убивание процесса при ошибке, и.т.п.). Сигналы - это асинхронные события. Они приходят неожиданно, в любой момент времени - вроде телефонного звонка. Кроме того, их не требуется заказывать – сигнал процессу может поступить совсем без повода. Аналогия из жизни такова: человек сидит и пишет письмо. Вдруг его окликают посреди фразы - он отвлекается, отвечает на вопрос, и вновь продолжает прерванное занятие. Человек не ожидал этого оклика (быть может, он готов к нему, но он не озирался по сторонам специально). Кроме того, сигнал мог поступить когда он писал 5-ое предложение, а мог - когда 34-ое. Момент времени, в который произойдет прерывание, не фиксирован. Сигналы имеют номера, причем их количество ограничено - есть определенный список допустимых сигналов. Номера и мнемонические имена сигналов перечислены в include-файле < signal. h > и имеют вид SIG нечто. Допустимы сигналы с номерами 1.. NSIG -1, где NSIG определено в этом файле. При получении сигнала мы узнаем его номер, но не узнаем никакой иной информации: ни от кого поступил сигнал, ни что от нас хотят. Просто "звонит телефон". Чтобы получить дополнительную информацию, наш процесс должен взять ее из другого известного места; например - прочесть заказ из некоторого файла, об имени которого все наши программы заранее "договорились". Сигналы процессу могут поступать тремя путями: · от другого процесса, который явно посылает его нам вызовом kill (pid, sig); где pid - идентификатор (номер) процесса-получателя, а sig - номер сигнала. Послать сигнал можно только родственному процессу - запущенному тем же пользователем. · от операционной системы. Система может посылать процессу ряд сигналов, сигнализирующих об ошибках, например при обращении программы по несуществующему адресу или при ошибочном номере системного вызова. Такие сигналы обычно прекращают наш процесс. · от пользователя - с клавиатуры терминала можно нажимом некоторых клавиш послать сигналы SIGINT и SIGQUIT. Собственно, сигнал посылается драйвером терминала при получении им с клавиатуры определенных символов. Так можно прервать зациклившуюся или надоевшую программу. Процесс-получатель должен как-то отреагировать на сигнал. Программа может: · проигнорировать сигнал (не ответить на звонок); · перехватить сигнал (снять трубку), выполнить какие-то действия, затем продолжить прерванное занятие; · быть убитой сигналом (звонок был подкреплен броском гранаты в окно). В большинстве случаев сигнал по умолчанию убивает процесс-получатель. Однако процесс может изменить это умолчание и задать свою реакцию явно. Это делается вызовом signal: #include < signal. h > void (* signal (int sig, void (* react)())) (); Параметр react может иметь значение: SIG _ IGN сигнал sig будет отныне игнорироваться. Некоторые сигналы (например SIGKILL)невозможно перехватить или проигнорировать. SIG _ DFL восстановить реакцию по умолчанию (обычно - смерть получателя). Например void fr (gotsig){..... } /* обработчик */ ... signal (sig, fr);... /* задание реакции */ Тогда при получении сигнала sig будет вызвана функция fr, в которую в качестве аргумента системой будет передан номер сигнала, действительно вызвавшего ее - gotsig == sig. Это полезно, т.к. можно задать одну и ту же функцию в качестве реакции для нескольких сигналов: ... signal (sig1, fr); signal (sig2, fr);... После возврата из функции fr () программа продолжится с прерванного места. Перед вызовом функции-обработчика реакция автоматически сбрасывается в реакцию поумолчанию SIG _ DFL, а после выхода из обработчика снова восстанавливается в fr. Это значит, что во время работы функции-обработчика может прийти сигнал, который убьет программу. Приведем список некоторых сигналов; полное описание посмотрите в документации. Колонки таблицы: G - может быть перехвачен; D - по умолчанию убивает процесс (k), игнорируется (i); C - образуется дамп памяти процесса: файл core, который затем может быть исследован отладчиком adb; F - реакция на сигнал сбрасывается; S – посылается обычно системой, а не явно. сигнал G D C F S смысл SIGTERM + k - + - завершить процесс SIGKILL - k - + - убить процесс SIGINT + k - + - прерывание с клавиш SIGQUIT + k + + - прерывание с клавиш SIGALRM + k - + + будильник SIGILL + k + - + запрещенная команда SIGBUS + k + + + обращение по неверному SIGSEGV + k + + + адресу SIGUSR1, USR2 + i - + - пользовательские SIGCLD + i - + + смерть потомка Сигнал SIGILL используется иногда для эмуляции команд с плавающей точкой, что происходит примерно так: при обнаружении “запрещенной” команды для отсутствующего процессора "плавающей" арифметики аппаратура дает прерывание и система посылает процессу сигнал SIGILL. По сигналу вызывается функция-эмулятор плавающей арифметики (подключаемая к выполняемому файлу автоматически), которая и обрабатывает требуемую команду. Это может происходить много раз, именно поэтому реакция на этот сигнал не сбрасывается. SIGALRM посылается в результате его заказа вызовом alarm () (см. ниже). Сигнал SIGCLD посылается процессу-родителю при выполнении процессом-потомком системного вызова exit (или при смерти вследствие получения сигнала). Обычно процесс-родитель при получении такого сигнала (если он его заказывал) реагирует, выполняя в обработчике сигнала вызов wait (см. ниже). По умолчанию этот сигнал игнорируется. Реакция SIG _ IGN не сбрасывается в SIG _ DFL при приходе сигнала, т.е. сигнал игнорируется постоянно. Вызов signal возвращает старое значение реакции, которое может быть запомнено в переменную вида void (* f)(); а потом восстановлено. Синхронное ожидание (системный вызов) может иногда быть прервано асинхронным событием (сигналом), но об этом ниже. Некоторые версии UNIX предоставляют более развитые средства работы с сигналами. Опишем некоторые из средств, имеющихся в BSD (в других системах они могут быть смоделированы другими способами). Пусть у нас в программе есть "критическая секция", во время выполнения которой приход сигналов нежелателен. Мы можем "заморозить" (заблокировать) сигнал, отложив момент его поступления до "разморозки": | sighold (sig); заблокировать сигнал |: КРИТИЧЕСКАЯ:<---процессу послан сигнал sig, СЕКЦИЯ: но он не вызывает реакцию немедленно, |: а "висит", ожидая разрешения. |: sigrelse (sig); разблокировать |<----------- sig | накопившиеся сигналы доходят, | вызывается реакция. Если во время блокировки процессу было послано несколько одинаковых сигналов sig, то при разблокировании поступит только один. Поступление сигналов во время блокировки просто отмечается в специальной битовой шкале в паспорте процесса (примерно так): mask |= (1 << (sig - 1)); и при разблокировании сигнала sig, если соответствующий бит выставлен, то приходит один такой сигнал (система вызывает функцию реакции). То есть sighold заставляет приходящие сигналы "накапливаться" в специальной маске, вместо того, чтобы немедленно вызывать реакцию на них. А sigrelse разрешает "накопившимся" сигналам (если они есть) прийти и вызывает реакцию на них. Функция sigset (sig, react); аналогична функции signal, за исключением того, что на время работы обработчика сигнала react, приход сигнала sig блокируется; то есть перед вызовом react как бы делается sighold, а при выходе из обработчика - sigrelse. Это значит, что если во время работы обработчика сигнала придет такой же сигнал, то программа не будет завершена, а “запомнит” пришедший сигнал, и обработчик будет вызван повторно (когда сработает sigrelse). Функция sigpause (sig); вызывается внутри "рамки" sighold (sig); ... sigpause (sig); ... sigrelse (sig); и вызывает задержку выполнения процесса до прихода сигнала sig. Функция разрешает приход сигнала sig (обычно на него должна быть задана реакция при помощи sigset), и “засыпает” до прихода сигнала sig. В UNIX стандарта POSIX для управления сигналами есть вызовы sigaction, sigprocmask, sigpending, sigsuspend. Сигнал прерывания можно игнорировать. Это делается так: signal (SIGINT, SIG _ IGN); Такую программу нельзя прервать с клавиатуры. Напомним, что реакция SIG _ IGN сохраняется при приходе сигнала. Системный вызов, находящийся в состоянии ожидания какого-то события (read - ждущий нажатия кнопки на клавиатуре, wait ждущий окончания процесса-потомка, и.т.п.), может быть прерван сигналом. При этом сисвызов вернет значение "ошибка" (-1) и errno станет равно EINTR. Это позволяет нам писать системные вызовы с выставлением таймаута: если событие не происходит в течение заданного времени, то завершить ожидание и прервать сисвызов. Для этой цели используется вызов alarm (sec), заказывающий посылку сигнала SIGALRM нашей программе через целое число sec секунд (0 - отменяет заказ): #include < signal. h > void (* oldaction)(); int alarmed; /* прозвонил будильник */ void onalarm (nsig){ alarmed ++; } ... /* установить реакцию на сигнал */ oldaction = signal (SIGALRM, onalarm); /* заказать будильник через TIMEOUT сек. */ alarmed = 0; alarm (TIMEOUT /* sec */);
sys _ call (...); /* ждет события */ // если нас сбил сигнал, то по сигналу будет // еще вызвана реакция на него - onalarm
if(alarmed){ // событие так и не произошло. // вызов прерван сигналом т.к. истекло время. }else{ alarm (0); /* отменить заказ сигнала */ // событие произошло, сисвызов успел // завершиться до истечения времени. } signal (SIGALRM, oldaction); Следующая программа выдает текущее время каждые 3 секунды: #include <signal.h> #include <time.h> #include <stdio.h> void tick (nsig){ time_t tim; char * s; signal (SIGALRM, tick); alarm (3); time (& tim); s = ctime (& tim); s [ strlen (s)-1 ] = '\0'; /* обрубить '\n' */ fprintf (stderr, "\r%s", s); } main (){ tick (0); for(;;) pause (); } Linux поддерживает нижеописанные сигналы. Некоторые номера сигналов зависят от используемой архитектуры. Сначала идут сигналы, описанные в стандарте POSIX.1.
Следующие сигналы не входят в стандарт POSIX.1, но описаны в SUSv2.
(Здесь - является признаком того, что сигнал отсутствует; там, где приведено три значения, первое -- для архитектур alpha и sparc, второе для архитектур i386, ppc и sh, последнее для mips. 29-й сигнал -- это SIGINFO / SIGPWR для alpha, но SIGLOST для sparc.) Буквы в колонке "Действие" имеют следующее значение: A Действие по умолчанию -- прекращение выполнения процесса. B Действие по умолчанию -- игнорировать сигнал. C Действие по умолчанию -- прекращение выполнения процесса и запись дампа памяти. D Действие по умолчанию -- приостановка выполнения процесса. E Сигнал не может быть перехвачен. F Сигнал не может быть проигнорирован. Поиск по сайту: |
Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Студалл.Орг (0.014 сек.) |