Почему многие программы Unix используют сигналы, подобные USR1? Простые сигналы и надежные сигналы. Модификация предыдущей программы для пользовательской обработки сигналов SIGINT и SIGQUIT

Сигналы в ОС Unix

Сигналы представляют собой средство уведомления процесса о наступлении некоторого события в системе.

Инициатором посылки сигнала может выступать как другой процесс, так и сама ОС.

Сигналы, посылаемые ОС, уведомляют о наступлении некоторых строго предопределенных ситуаций (как, например, завершение порожденного процесса, прерывание процесса нажатием комбинации Ctrl-C, попытка выполнить недопустимую машинную инструкцию, попытка недопустимой записи в канал и т.п.), при этом каждой такой ситуации сопоставлен свой сигнал.

Кроме того, зарезервирован один или несколько номеров сигналов, семантика которых определяется пользовательскими процессами по своему усмотрению (например, процессы могут посылать друг другу сигналы с целью синхронизации).

Количество различных сигналов в современных версиях UNIX около 30, каждый из них имеет уникальное имя и номер.

Описания представлены в файле .

В таблице приведено несколько примеров сигналов:

Числовое значение

Константа

Значение сигнала

Прерывание выполнения по нажатию Ctrl-C

Аварийное завершение работы

Уничтожение процесса

Прерывание от программного таймера

Завершился процесс-потомок

Сигналы являются механизмом асинхронного взаимодействия, т.е. момент прихода сигнала процессу заранее неизвестен. Однако, процесс может предвидеть возможность получения того или иного сигнала и установить определенную реакцию на его приход.

При получении сигнала процессом возможны три варианта реакции на полученный сигнал:

    Процесс реагирует на сигнал стандартным образом, установленным по умолчанию (для большинства сигналов действие по умолчанию – это завершение процесса)

    Процесс может установить специальную обработку сигнала, в этом случае по приходу сигнала вызывается функция-обработчик, определенная процессом (при этом говорят, что сигнал перехватывается)

    Процесс может проигнорировать сигнал

Для каждого сигнала процесс может устанавливать свой вариант реакции, например, некоторые сигналы он может игнорировать, некоторые перехватывать, а на остальные установить реакцию по умолчанию. При этом в процессе своей работы процесс может изменять вариант реакции на тот или иной сигнал. Однако, необходимо отметить, что

некоторые сигналы невозможно ни перехватить, ни игнорировать. Они используются ядром ОС для управления работой процессов (например, SIGKILL, SIGSTOP).

Если в процесс одновременно доставляется несколько различных сигналов, то порядок их обработки не определен . Если же обработки ждут несколько экземпляров одного и того же сигнала, то ответ на вопрос, сколько экземпляров будет доставлено в процесс – все или один – зависит от конкретной реализации ОС.

Отдельного рассмотрения заслуживает ситуация, когда сигнал приходит в момент выполнения системного вызова. Обработка такой ситуации в разных версиях UNIX реализована по-разному, например, обработка сигнала может быть отложена до завершения системного вызова; либо системный вызов автоматически перезапускается после его прерывания сигналом; либо системный вызов вернет –1, а в переменной errno будет установлено значение EINTR

Для отправки сигнала существует системный вызов kill():

#include

#include

int kill (pid _ t pid , int sig );

pid идентификатор процесса, которому посылается сигнал (в частности, процесс может послать сигнал самому себе). Существует также возможность одновременно послать сигнал нескольким процессам,

например, если значение этого параметра есть 0, сигнал будет передан всем процессам, которые принадлежат той же группе, что и процесс, посылающий сигнал, за исключением процессов с идентификаторами 0 и 1.

sig номер посылаемого сигнала.

Если этот параметр равен 0, то будет выполнена проверка корректности обращения к kill() (в частности, существование процесса с идентификатором pid), но никакой сигнал в действительности посылаться не будет.

Если процесс-отправитель не обладает правами привилегированного пользователя, то он может отправить сигнал только тем процессам, у которых реальный или эффективный идентификатор владельца процесса совпадает с реальным или эффективным идентификатором владельца процесса-отправителя.

Для определения реакции на получение того или иного сигнала в процессе служит системный вызов signal():

#include

void (*signal

(int sig, void (*disp) (int))) (int);

sig - номер сигнала, для которого устанавливается реакция, disp - либо определенная пользователем функция-обработчик сигнала, либо одна из констант: SIG_DFL (обработка по умолчанию, т.е. стандартная реакцию системы,)и SIG_IGN (данный сигнал необходимо игнорировать).

При успешном завершении функция возвращает указатель на предыдущий обработчик данного сигнала (он может использоваться процессом, например, для восстановления прежней реакции на сигнал).

Как видно из прототипа вызова signal(), определенная пользователем функция-обработчик сигнала должна принимать один целочисленный аргумент (в нем будет передан номер обрабатываемого сигнала), и не возвращать никаких значений.

Отметим одну особенность реализации сигналов в ранних версиях UNIX: каждый раз при получении сигнала его диспозиция (т.е. действие при получении сигнала) сбрасывается на действие по умолчанию, т.о. если процесс желает многократно обрабатывать сигнал своим собственным обработчиком, он должен каждый раз при обработке сигнала заново устанавливать реакцию на него.

механизм сигналов является достаточно ресурсоемким, ибо отправка сигнала представляет собой системный вызов, а доставка сигнала - прерывание выполнения процесса-получателя. Вызов функции-обработчика и возврат требует операций со стеком. Сигналы также несут весьма ограниченную информацию.

В данном примере при получении сигнала S

IGINT четырежды вызывается специальный

обработчик, а в пятый раз происходит

обработка по умолчанию.

#include

#include

#include

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;

Высокоуровневые средства межпроцессного

IPC, inter-process communication ). Фактически, сигнал - это асинхронное уведомление процесса о каком-либо событии. Когда сигнал послан процессу, операционная система прерывает выполнение процесса. Если процесс установил собственный обработчик сигнала , операционная система запускает этот обработчик, передав ему информацию о сигнале. Если процесс не установил обработчик, то выполняется обработчик по умолчанию.

Названия сигналов «SIG…» являются числовыми константами (макроопределениями Си) со значениями, определяемыми в заголовочном файле signal.h . Числовые значения сигналов могут меняться от системы к системе, хотя основная их часть имеет в разных системах одни и те же значения. Утилита kill позволяет задавать сигнал как числом, так и символьным обозначением.

Посылка сигналов

Сигналы посылаются:

  • с терминала, нажатием специальных клавиш или комбинаций (например, нажатие Ctrl-C генерирует SIGINT , Ctrl-\ SIGQUIT , а Ctrl-Z SIGTSTP);
  • ядром системы:
    • при возникновении аппаратных исключений (недопустимых инструкций, нарушениях при обращении в память, системных сбоях и т. п.);
    • ошибочных системных вызовах;
    • для информирования о событиях ввода-вывода;
  • одним процессом другому (или самому себе), с помощью системного вызова kill() , в том числе:

Сигналы не могут быть посланы завершившемуся процессу, находящемуся в состоянии «зомби» .

Обработка сигналов

Обработчик по умолчанию для большинства сигналов завершает выполнение процесса. Для альтернативной обработки всех сигналов, за исключением SIGKILL и SIGSTOP , процесс может назначить свой обработчик или игнорировать их возникновение модификацией своей сигнальной маски . Единственное исключение - процесс с pid 1 (init), который имеет право игнорировать или обрабатывать любые сигналы, включая KILL и STOP.

Безопасность

Процесс (или пользователь из оболочки) с эффективным UID , не равным 0 (UID суперпользователя), может посылать сигналы только процессам с тем же UID.

Классификация сигналов

POSIX определяет 28 сигналов, которые можно классифицировать следующим образом:

Название Код Действие по умолчанию Описание Тип
SIGABRT 6 Завершение с дампом памяти Сигнал посылаемый функцией abort() Управление
SIGALRM 14 Завершение Сигнал истечения времени, заданного alarm() Уведомление
SIGBUS 10 Завершение с дампом памяти Неправильное обращение в физическую память Исключение
SIGCHLD 18 Игнорируется Дочерний процесс завершен или остановлен Уведомление
SIGCONT 25 Продолжить выполнение Продолжить выполнение ранее остановленного процесса Управление
SIGFPE 8 Завершение с дампом памяти Ошибочная арифметическая операция Исключение
SIGHUP 1 Завершение Закрытие терминала Уведомление
SIGILL 4 Завершение с дампом памяти Недопустимая инструкция процессора Исключение
SIGINT 2 Завершение Сигнал прерывания (Ctrl-C) с терминала Управление
SIGKILL 9 Завершение Безусловное завершение Управление
SIGPIPE 13 Завершение Запись в разорванное соединение (пайп, сокет) Уведомление
SIGQUIT 3 Завершение с дампом памяти Сигнал «Quit» с терминала (Ctrl-\) Управление
SIGSEGV 11 Завершение с дампом памяти Нарушение при обращении в память Исключение
SIGSTOP 23 Остановка процесса Остановка выполнения процесса Управление
SIGTERM 15 Завершение Сигнал завершения (сигнал по умолчанию для утилиты kill) Управление
SIGTSTP 20 Остановка процесса Сигнал остановки с терминала (Ctrl-Z). Управление
SIGTTIN 26 Остановка процесса Попытка чтения с терминала фоновым процессом Управление
SIGTTOU 27 Остановка процесса Попытка записи на терминал фоновым процессом Управление
SIGUSR1 16 Завершение Пользовательский сигнал № 1 Пользовательский
SIGUSR2 17 Завершение Пользовательский сигнал № 2 Пользовательский
SIGPOLL 22 Завершение Событие, отслеживаемое poll() Уведомление
SIGPROF 29 Завершение Истечение таймера профилирования Отладка
SIGSYS 12 Завершение с дампом памяти Неправильный системный вызов Исключение
SIGTRAP 5 Завершение с дампом памяти Ловушка трассировки или брейкпоинт Отладка
SIGURG 21 Игнорируется На сокете получены срочные данные Уведомление
SIGVTALRM 28 Завершение Истечение «виртуального таймера» Уведомление
SIGXCPU 30 Завершение с дампом памяти Процесс превысил лимит процессорного времени Исключение
SIGXFSZ 31 Завершение с дампом памяти Процесс превысил допустимый размер файла Исключение

При обработке исключений и отладочных сигналов перед завершением процесс может записать в текущий каталог файл с дампом памяти процесса (англ. core image ), используя который, отладчик может восстановить условия, при которых возникло данное исключение. Иногда (например, для программ, выполняемых от имени суперпользователя) дамп памяти не создаётся из соображений безопасности.

SA_SIGINFO

Обычно обработчик сигнала получает только один аргумент - номер сигнала (это позволяет использовать одну функцию-обработчик для нескольких сигналов). Если при задании обработчика сигнала (функцией sigaction()) указать опцию SA_SIGINFO, то в обработчик будут переданы ещё два аргумента:

  1. указатель на структуру siginfo_t , включающую:
    • битовую маску дополнительных «кодов сигнала», определяющих причину его возникновения;
    • идентификатор процесса (PID), пославшего сигнал;
    • эффективный идентификатор пользователя (UID), от имени которого выполняется процесс (например, утилита kill), пославший сигнал;
    • адрес инструкции, в которой возникло исключение;
    • и т. п.
  2. указатель на «машинный контекст» на момент возникновения сигнала (со «стеком сигнала» - дополнительными данными, которые помещаются в стек при вызове некоторых сигналов-исключений).

Большинство дополнительных кодов специфичны для каждого сигнала. Коды, общие для всех сигналов:

См. также

Напишите отзыв о статье "Сигналы (UNIX)"

Ссылки

  • - Концепция сигналов (IEEE Std 1003.1, 2004 Edition)
  • - Описание структур и констант, связанных с сигналами (IEEE Std 1003.1, 2004 Edition)
  • - «Правила использования сигналов в Unix» (Сообщение в конференции RU.UNIX.PROG)

Отрывок, характеризующий Сигналы (UNIX)

– Целый вечер вам буду петь, – сказала Наташа.
– Волшебница всё со мной сделает! – сказал Денисов и отстегнул саблю. Он вышел из за стульев, крепко взял за руку свою даму, приподнял голову и отставил ногу, ожидая такта. Только на коне и в мазурке не видно было маленького роста Денисова, и он представлялся тем самым молодцом, каким он сам себя чувствовал. Выждав такт, он с боку, победоносно и шутливо, взглянул на свою даму, неожиданно пристукнул одной ногой и, как мячик, упруго отскочил от пола и полетел вдоль по кругу, увлекая за собой свою даму. Он не слышно летел половину залы на одной ноге, и, казалось, не видел стоявших перед ним стульев и прямо несся на них; но вдруг, прищелкнув шпорами и расставив ноги, останавливался на каблуках, стоял так секунду, с грохотом шпор стучал на одном месте ногами, быстро вертелся и, левой ногой подщелкивая правую, опять летел по кругу. Наташа угадывала то, что он намерен был сделать, и, сама не зная как, следила за ним – отдаваясь ему. То он кружил ее, то на правой, то на левой руке, то падая на колена, обводил ее вокруг себя, и опять вскакивал и пускался вперед с такой стремительностью, как будто он намерен был, не переводя духа, перебежать через все комнаты; то вдруг опять останавливался и делал опять новое и неожиданное колено. Когда он, бойко закружив даму перед ее местом, щелкнул шпорой, кланяясь перед ней, Наташа даже не присела ему. Она с недоуменьем уставила на него глаза, улыбаясь, как будто не узнавая его. – Что ж это такое? – проговорила она.
Несмотря на то, что Иогель не признавал эту мазурку настоящей, все были восхищены мастерством Денисова, беспрестанно стали выбирать его, и старики, улыбаясь, стали разговаривать про Польшу и про доброе старое время. Денисов, раскрасневшись от мазурки и отираясь платком, подсел к Наташе и весь бал не отходил от нее.

Два дня после этого, Ростов не видал Долохова у своих и не заставал его дома; на третий день он получил от него записку. «Так как я в доме у вас бывать более не намерен по известным тебе причинам и еду в армию, то нынче вечером я даю моим приятелям прощальную пирушку – приезжай в английскую гостинницу». Ростов в 10 м часу, из театра, где он был вместе с своими и Денисовым, приехал в назначенный день в английскую гостинницу. Его тотчас же провели в лучшее помещение гостинницы, занятое на эту ночь Долоховым. Человек двадцать толпилось около стола, перед которым между двумя свечами сидел Долохов. На столе лежало золото и ассигнации, и Долохов метал банк. После предложения и отказа Сони, Николай еще не видался с ним и испытывал замешательство при мысли о том, как они свидятся.
Светлый холодный взгляд Долохова встретил Ростова еще у двери, как будто он давно ждал его.
– Давно не видались, – сказал он, – спасибо, что приехал. Вот только домечу, и явится Илюшка с хором.
– Я к тебе заезжал, – сказал Ростов, краснея.
Долохов не отвечал ему. – Можешь поставить, – сказал он.
Ростов вспомнил в эту минуту странный разговор, который он имел раз с Долоховым. – «Играть на счастие могут только дураки», сказал тогда Долохов.
– Или ты боишься со мной играть? – сказал теперь Долохов, как будто угадав мысль Ростова, и улыбнулся. Из за улыбки его Ростов увидал в нем то настроение духа, которое было у него во время обеда в клубе и вообще в те времена, когда, как бы соскучившись ежедневной жизнью, Долохов чувствовал необходимость каким нибудь странным, большей частью жестоким, поступком выходить из нее.
Ростову стало неловко; он искал и не находил в уме своем шутки, которая ответила бы на слова Долохова. Но прежде, чем он успел это сделать, Долохов, глядя прямо в лицо Ростову, медленно и с расстановкой, так, что все могли слышать, сказал ему:
– А помнишь, мы говорили с тобой про игру… дурак, кто на счастье хочет играть; играть надо наверное, а я хочу попробовать.
«Попробовать на счастие, или наверное?» подумал Ростов.
– Да и лучше не играй, – прибавил он, и треснув разорванной колодой, прибавил: – Банк, господа!
Придвинув вперед деньги, Долохов приготовился метать. Ростов сел подле него и сначала не играл. Долохов взглядывал на него.
– Что ж не играешь? – сказал Долохов. И странно, Николай почувствовал необходимость взять карту, поставить на нее незначительный куш и начать игру.
– Со мной денег нет, – сказал Ростов.
– Поверю!
Ростов поставил 5 рублей на карту и проиграл, поставил еще и опять проиграл. Долохов убил, т. е. выиграл десять карт сряду у Ростова.
– Господа, – сказал он, прометав несколько времени, – прошу класть деньги на карты, а то я могу спутаться в счетах.
Один из игроков сказал, что, он надеется, ему можно поверить.
– Поверить можно, но боюсь спутаться; прошу класть деньги на карты, – отвечал Долохов. – Ты не стесняйся, мы с тобой сочтемся, – прибавил он Ростову.
Игра продолжалась: лакей, не переставая, разносил шампанское.
Все карты Ростова бились, и на него было написано до 800 т рублей. Он надписал было над одной картой 800 т рублей, но в то время, как ему подавали шампанское, он раздумал и написал опять обыкновенный куш, двадцать рублей.
– Оставь, – сказал Долохов, хотя он, казалось, и не смотрел на Ростова, – скорее отыграешься. Другим даю, а тебе бью. Или ты меня боишься? – повторил он.
Ростов повиновался, оставил написанные 800 и поставил семерку червей с оторванным уголком, которую он поднял с земли. Он хорошо ее после помнил. Он поставил семерку червей, надписав над ней отломанным мелком 800, круглыми, прямыми цифрами; выпил поданный стакан согревшегося шампанского, улыбнулся на слова Долохова, и с замиранием сердца ожидая семерки, стал смотреть на руки Долохова, державшего колоду. Выигрыш или проигрыш этой семерки червей означал многое для Ростова. В Воскресенье на прошлой неделе граф Илья Андреич дал своему сыну 2 000 рублей, и он, никогда не любивший говорить о денежных затруднениях, сказал ему, что деньги эти были последние до мая, и что потому он просил сына быть на этот раз поэкономнее. Николай сказал, что ему и это слишком много, и что он дает честное слово не брать больше денег до весны. Теперь из этих денег оставалось 1 200 рублей. Стало быть, семерка червей означала не только проигрыш 1 600 рублей, но и необходимость изменения данному слову. Он с замиранием сердца смотрел на руки Долохова и думал: «Ну, скорей, дай мне эту карту, и я беру фуражку, уезжаю домой ужинать с Денисовым, Наташей и Соней, и уж верно никогда в руках моих не будет карты». В эту минуту домашняя жизнь его, шуточки с Петей, разговоры с Соней, дуэты с Наташей, пикет с отцом и даже спокойная постель в Поварском доме, с такою силою, ясностью и прелестью представились ему, как будто всё это было давно прошедшее, потерянное и неоцененное счастье. Он не мог допустить, чтобы глупая случайность, заставив семерку лечь прежде на право, чем на лево, могла бы лишить его всего этого вновь понятого, вновь освещенного счастья и повергнуть его в пучину еще неиспытанного и неопределенного несчастия. Это не могло быть, но он всё таки ожидал с замиранием движения рук Долохова. Ширококостые, красноватые руки эти с волосами, видневшимися из под рубашки, положили колоду карт, и взялись за подаваемый стакан и трубку.

Сигнал или виртуальное прерывание является сообщением, которое система посылает процессу или один процесс посылает другому. Когда процесс получает сигнал, выполнение программы процесса прерывается, и управление передается на подпрограмму (функцию) - обработчик сигнала. После выполнения обработчика сигнала выполнение прерванной программы возобновляется с той точки, на которой она была прервана.

В операционной системе предусмотрено большое число типов сигналов, но большинство из этих типов зарезервировано для системных целей - это сигналы, которые операционная система посылает процессу. Однако есть и сигналы, которыми процессы могут обмениваться между собой.

По умолчанию реакция на большинство сигналов - прекращение процесса, получившего сигнал, то есть, если процесс получает сигнал, обработка которого в нем не предусмотрена, то процесс-получатель сигнала завершается. Однако для большинства типов сигналов процесс может установить обработчик данного сигнала или установить игнорирование данного сигнала.

Если процесс находится в состоянии "добровольного" приостанова (вызванного, например, выполнением системного вызова sleep), то получение сигнала "пробуждает процесс от сна", независимо от того, в чем состояла обработка сигнала, системный вызов sleep заканчивается немедленно.

Обработчик сигнала в процессе имеет вид функции с прототипом:

Void имя_функции (int sigtype);

Параметром данной функции является тип сигнала (один и тот же обработчик может быть установлен для обработки сигналов разных типов).

Для установки своего обработчика сигнала, для его отмены или для установки игнорирования сигнала используется системный вызов signal

Функции-обработчики сигналов – это обычные функции Си, они имеют доступ ко всем глобально видимым переменным и функциям. Однако, поскольку мы не знаем, в какой момент выполнения программы будет вызвана функция-обработчик, мы должны проявлять особую осторожность при обращении к глобальным структурам данных из этой функции. Для функций, обрабатывающих потоки, существует и еще одно важное требование – реентерабильность. Поскольку обработчик сигнала может быть вызван в любой точке выполнения программы (а при не кототорых условиях во время обработки одного сигнала может быть вызван другой обработчик сигнала) в обработчиках додлжны использоваться функции, которые удовлетворяют требованию реентерабельности, то есть, могут быть вызваны в то время, когда они уже вызваны где-то в другой точке программы. Фактически, требование реентерабельности сводится к тому, чтобы функция не использовала никаких глобальных ресурсов, не позаботившись о синхронизации доступа к этим ресурсам. Некоторые функции ввода-вывода, в том числе, функция printf(), реентерабельными не являются. Это значит, что выводу одной функции printf() может помешать вывод другой функции. Ниже приводится список реентерабельных функций, которые безопасно вызвать из обработчиков сигналов.

Список реентерабельных функций

posix_trace_event()

timer_getoverrun()

Процесс может послать сигнал любому другому процессу, PID которого ему известен, при помощи системного вызова kill (несмотря на грозное название, этот системный вызов не обязательно убивает тот процесс, которому он адресован). В некоторых случаях процессу бывает нужно послать сигнал самому себе, это можно сделать при помощи системного вызова raise .

Некоторые типы сигналов

Типы сигналов идентифицируются числовыми номерами, но при программировании часто используются символьные имена сигналов, определенные в системных включаемых файлах. Ниже приведены некоторые наиболее часто употребляемые имена сигналов:

Этот сигнал приводит к завершению получившего его процесса. Это единственный сигнал, который не может игнорироваться и для которого нельзя назначить собственный обработчик

Этот сигнал - запрос на завершение процесса. Выдачу этого сигнала, например, включает в себя команда (не системный вызов!) kill . Подразумевается, что процесс, получивший этот сигнал, должен завершиться, однако процесс может установить игнорирование этого сигнала или назначить для него собственный обработчик.

Этот сигнал система посылает родительскому процессу при завершении любого его дочернего процесса. Реакция на этот сигнал, установленная по умолчанию, - игнорирование. Родительский процесс может не заботиться об обработке этого сигнала, если только он не хочет использовать его для синхронизации своего выполнения с дочерним процессом.

Этот сигнал используется для отсчета временных интервалов. Процесс может установить некоторый временной интервал при помощи системных вызовов alarm или setitimer , и по истечении заданного интервала система пошлет ему сигнал SIGALRM .

SIGUSR1 и SIGUSR2

За этими сигналами не зарезенвированы никакие системные назначения. Процессы могут посылать эти сигналы друг другу и интерпретировать их по своему усмотрению.

Более подробно про типы сигналов см. в описании функции

Рассмотрим теперь другую программу – 13–14-3.c :

/* Программа с пользовательской обработкой сигнала SIGINT */ #include #include /* Функция my_handler – пользовательский обработчик сигнала */ void my_handler(int nsig){ printf("Receive signal %d, CTRL-C pressed\n", nsig); } int main(void){ /* Выставляем реакцию процесса на сигнал SIGINT */ (void)signal(SIGINT, my_handler); /*Начиная с этого места, процесс будет печатать сообщение о возникновении сигнала SIGINT */ while(1); return 0; } Листинг 13-14.3. Программа (13–14-3.c) с пользовательской обработкой сигнала SIGINT.

Эта программа отличается от программы из раздела "Прогон программы, игнорирующей сигнал SIGINT " тем, что в ней введена обработка сигнала SIGINT пользовательской функцией. Наберите, откомпилируйте и запустите эту программу, проверьте ее реакцию на нажатие клавиш < CTRL > и и на нажатие клавиш < CTRL > и <4>.

Модификация предыдущей программы для пользовательской обработки сигналов SIGINT и SIGQUIT

Модифицируйте программу из предыдущего раздела так, чтобы она печатала сообщение и о нажатии клавиш < CTRL > и <4>. Используйте одну и ту же функцию для обработки сигналов SIGINT и SIGQUIT . Откомпилируйте и запустите ее, проверьте корректность работы. Снимать программу также придется с другого терминала командой kill .

Восстановление предыдущей реакции на сигнал

До сих пор в примерах мы игнорировали значение , возвращаемое системным вызовом signal() . На самом деле этот системный вызов возвращает указатель на предыдущий обработчик сигнала , что позволяет восстанавливать переопределенную реакцию на сигнал . Рассмотрим пример программы 13-14-4.c , возвращающей первоначальную реакцию на сигнал SIGINT после 5 пользовательских обработок сигнала .

/* Программа с пользовательской обработкой сигнала SIGINT, возвращающаяся к первоначальной реакции на этот сигнал после 5 его обработок*/ #include #include int i=0; /* Счетчик числа обработок сигнала */ void (*p)(int); /* Указатель, в который будет занесен адрес предыдущего обработчика сигнала */ /* Функция my_handler – пользовательский обработчик сигнала */ void my_handler(int nsig){ printf("Receive signal %d, CTRL-C pressed\n", nsig); i = i+1; /* После 5-й обработки возвращаем первоначальную реакцию на сигнал */ if(i == 5) (void)signal(SIGINT, p); } int main(void){ /* Выставляем свою реакцию процесса на сигнал SIGINT, запоминая адрес предыдущего обработчика */ p = signal(SIGINT, my_handler); /*Начиная с этого места, процесс будет 5 раз печатать сообщение о возникновении сигнала SIGINT */ while(1); return 0; } Листинг 13-14.4. Программа (13-14-4.c) с пользовательской обработкой сигнала SIGINT.

Наберите, откомпилируйте программу и запустите ее на исполнение .

Сигналы SIGUSR1 и SIGUSR2. Использование сигналов для синхронизации процессов

В операционной системе UNIX существует два сигнала , источниками которых могут служить только системный вызов kill() или команда kill , – это сигналы SIGUSR1 и SIGUSR2 . Обычно их применяют для передачи информации о происшедшем событии от одного пользовательского процесса другому в качестве сигнального средства связи .

В материалах семинара 5 (раздел "Написание, компиляция и запуск программы для организации двунаправленной связи между родственными процессами через pipe "), когда рассматривалась связь родственных процессов через pipe , речь шла о том, что pipe является однонаправленным каналом связи, и что для организации связи через один pipe в двух направлениях необходимо задействовать механизмы взаимной синхронизации процессов. Организуйте двустороннюю поочередную связь процесса-родителя и процесса-ребенка через pipe , используя для синхронизации сигналы SIGUSR1 и SIGUSR2 , модифицировав программу из раздела. "Прогон программы для организации однонаправленной связи между родственными процессами через pipe " семинара 5.

Задача повышенной сложности : организуйте побитовую передачу целого числа между двумя процессами, используя для этого только сигналы SIGUSR1 и SIGUSR2 .

При реализации нитей исполнения в операционной системе Linux (см. семинары 6–7, начиная с раздела "Понятие о нити исполнения ( thread ) в UNIX . Идентификатор нити исполнения . Функция pthread_self() ") сигналы SIGUSR1 и SIGUSR2 используются для организации синхронизации между процессами, представляющими нити исполнения , и процессом-координатором в служебных целях. Поэтому пользовательские программы, применяющие в своей работе нити исполнения , не могут задействовать сигналы SIGUSR1 и SIGUSR2 .

Завершение порожденного процесса. Системный вызов waitpid(). Сигнал SIGCHLD

В материалах семинаров 3–4 (раздел " Завершение процесса . Функция exit() ") при изучении завершения процесса говорилось о том, что если процесс-ребенок завершает свою работу прежде процесса-родителя, и процесс-родитель явно не указал, что он не заинтересован в получении информации о статусе завершения процесса-ребенка, то завершившийся процесс не исчезает из системы окончательно, а остается в состоянии закончил исполнение ( зомби-процесс ) либо до завершения процесса-родителя, либо до того момента, когда родитель соблаговолит получить эту информацию.

Для получения такой информации процесс-родитель может воспользоваться системным вызовом waitpid() или его упрощенной формой wait() . Системный вызов waitpid() позволяет процессу-родителю синхронно получить данные о статусе завершившегося процесса-ребенка либо блокируя процесс-родитель до завершения процесса-ребенка, либо без блокировки при его периодическом вызове с опцией WNOHANG. Эти данные занимают 16 бит и в рамках нашего курса могут быть расшифрованы следующим образом:

Каждый процесс-ребенок при завершении работы посылает своему процессу-родителю специальный сигнал SIGCHLD , на который у всех процессов по умолчанию установлена реакция "игнорировать сигнал ". Наличие такого сигнала совместно с системным вызовом waitpid() позволяет организовать асинхронный сбор информации о статусе завершившихся порожденных процессов процессом-родителем.

Системные вызовы wait() и waitpid()

Прототипы системных вызовов

#include #include pid_t waitpid(pid_t pid, int *status, int options); pid_t wait(int *status);

Описание системных вызовов

Это описание не является полным описанием системных вызовов, а адаптировано применительно к нашему курсу. Для получения полного описания обращайтесь к UNIX Manual.

Системный вызов waitpid() блокирует выполнение текущего процесса до тех пор, пока либо не завершится порожденный им процесс, определяемый значением параметра pid , либо текущий процесс не получит сигнал , для которого установлена реакция по умолчанию "завершить процесс" или реакция обработки пользовательской функцией. Если порожденный процесс, заданный параметром pid , к моменту системного вызова находится в состоянии закончил исполнение, то системный вызов возвращается немедленно без блокирования текущего процесса.

Параметр pid определяет порожденный процесс, завершения которого дожидается процесс-родитель, следующим образом:

  • Если pid > 0 ожидаем завершения процесса с идентификатором pid .
  • Если pid = 0 , то ожидаем завершения любого порожденного процесса в группе , к которой принадлежит процесс-родитель.
  • Если pid = -1 , то ожидаем завершения любого порожденного процесса.
  • Если pid < 0 , но не –1 , то ожидаем завершения любого порожденного процесса из группы , идентификатор которой равен абсолютному значению параметра pid .

Параметр options в нашем курсе может принимать два значения: 0 и WNOHANG . Значение WNOHANG требует немедленного возврата из вызова без блокировки текущего процесса в любом случае.

Если системный вызов обнаружил завершившийся порожденный процесс, из числа специфицированных параметром pid , то этот процесс удаляется из вычислительной системы, а по адресу, указанному в параметре status, сохраняется информация о статусе его завершения. Параметр status может быть задан равным NULL , если эта информация не имеет для нас значения.

При обнаружении завершившегося процесса системный вызов возвращает его идентификатор. Если вызов был сделан с установленной опцией WNOHANG , и порожденный процесс, специфицированный параметром pid , существует, но еще не завершился, системный вызов вернет значение 0 . Во всех остальных случаях он возвращает отрицательное значение. Возврат из вызова, связанный с возникновением обработанного пользователем сигнала , может быть в этом случае идентифицирован по значению системной переменной errno == EINTR , и вызов может быть сделан снова.

Системный вызов wait является синонимом для системного вызова waitpid со значениями параметров pid = -1 , options = 0 . порожденного процесса.

/* Программа с асинхронным получением информации о статусе двух завершившихся порожденных процессов */ #include #include #include #include #include /* Функция my_handler – обработчик сигнала SIGCHLD */ void my_handler(int nsig){ int status; pid_t pid; /* Опрашиваем статус завершившегося процесса и одновременно узнаем его идентификатор */ if((pid = waitpid(-1, &status, 0)) < 0){ /* Если возникла ошибка – сообщаем о ней и продолжаем работу */ printf("Some error on waitpid errno = %d\n", errno); } else { /* Иначе анализируем статус завершившегося процесса */ if ((status & 0xff) == 0) { /* Процесс завершился с явным или неявным вызовом функции exit() */ printf("Process %d was exited with status %d\n", pid, status >> 8); } else if ((status & 0xff00) == 0){ /* Процесс был завершен с помощью сигнала */ printf("Process %d killed by signal %d %s\n", pid, status &0x7f,(status & 0x80) ? "with core file" : "without core file"); } } } int main(void){ pid_t pid; /* Устанавливаем обработчик для сигнала SIGCHLD */ (void) signal(SIGCHLD, my_handler); /* Порождаем Сhild 1 */ if((pid = fork()) < 0){ printf("Can\"t fork child 1\n"); exit(1); } else if (pid == 0){ /* Child 1 – завершается с кодом 200 */ exit(200); } /* Продолжение процесса-родителя – порождаем Сhild 2 */ if((pid = fork()) < 0){ printf("Can\"t fork child 2\n"); exit(1); } else if (pid == 0){ /* Child 2 – циклится, необходимо удалять с помощью сигнала! */ while(1); } /* Продолжение процесса-родителя – уходим в цикл */ while(1); return 0; } Листинг 13-14.5. Программа (13-14-5.c) с асинхронным получением информации о статусе двух завершившихся порожденных процессов. kill с каким-либо номером сигнала . Родительский процесс также будет необходимо завершать командой kill .

Понравилась статья? Поделиться с друзьями: