Почему многие программы Unix используют сигналы, подобные USR1? Сигналы в linux. Примеры

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

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

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

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

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

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

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

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

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

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

posix_trace_event()

timer_getoverrun()

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

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

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

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

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

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

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

SIGUSR1 и SIGUSR2

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

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

Сигналы — программные прерывания. Они используются для связи между процессами в UNIX и UNIX-подобных операционных систем, таких как Linux, Mac OS.

Сигналы использовались еще с Bell Labs UNIX в 1970 и сегодня официально определено стандартом POSIX. Когда сигнал поступает на процесс, операционная система прерывает нормальный поток выполнения процесса и обеспечивает уведомление.

Не имеет значения, какую операционную систему вы используете, вы несомненно столкнетесь с тем что ваши приложения, блокируются и отказаться работать должным образом. В Unix, Linux, есть команда «kill», которую вы можете использовать, чтобы прекратить процесс немедленно, но и не только это, но об этом немного позже я расскажу. В этой статье «Команда kill в Unix/Linux» я расскажу и покажу на готовых примерах различные команды утилиты kill.

Команды kill и сигналы

Когда вы выполняете команду «kill», вы на самом деле посылает е сигнал к системе и даете указание расторгнуть работу некорректной роботы приложения. Существует в общей сложности 60 сигналов, которые можно использовать, но все, что вы действительно должны знать, SIGTERM (15) и SIGKILL (9).

Вы можете просмотреть все сигналы с помощью команды:

# kill -l

На моей MacOS:

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

Основные сигналы

Следующие сигналы являются частью стандарта POSIX. Каждый сигнал макрос, определенный в Файл заголовка системы. Они, как правило, сокращенны, без их SIG- префикса; например SIGHUP часто называют просто как HUP.

SIGTERM – Этот сигнал запрашивает остановку процесса который работает. Этот сигнал может быть проигнорирован.Процессу дается время, чтобы хорошо выключился. Когда программа хорошо выключается, это означает, что ей дано время, чтобы спасти его прогресс и освободить ресурсы. Другими словами, он не «forced» прекращение работы процесса.

SIGKILL – сигнал SIGKILL заставляет процесс прекратить выполнение своей работы немедленно. Программа не может игнорировать этот сигнал. Несохраненный прогресс будет потерян.

Синтаксис для «kill».

Синтаксис команды такой:

# kill PID(s)

# kill [-s] [-l] %pid

Сигнал по умолчанию (если он не указан) является SIGTERM. Когда данный сигнал не помогает и не работает, вы можете использовать следующие опции для «kill» чтобы завершить процесс принудительно:

# kill SIGKILL PID

# kill -9 PID

где «-9» — это флаг относится к сигналу SIGKILL.

Если вы не знаете какой нужно использовать PID приложения которого следует избавиться, то запустите команду:

$ ps -aux

А если знаете конкретное приложение (например apache), то можно отсеять ненужное и вывести все процессы по данной службе:

$ ps -aux | grep apache

И это отобразит все запущенные приложения вместе с его PID-ом(ами).

Например, чтобы убить приложение, я выполните следующую команду:

# kill -9 3629

Стоит также отметить, что вы можете использовать несколько процессов одновременно чтобы их можно было » убить»:

# kill -9 PID1 PID2 PID 3

Перезагурзить конфигурационные файлы или перезапуск утилит:

# kill -1 number_of_PID

Особенно полезными сигналы включают HUP, INT, kill, STOP, CONT, и 0.

Варианты таковы:

-s signal_name
Символическое имя сигнала, задающее сигнал для отправки сигнала не по умолчанию.

-l
Если операнд не указан, то показать имена сигналов; В противном случае, написать название сигнала, соответствующего exit_status.

-signal_name
Символическое имя сигнала, задающее сигнал для отправки в TERM по умолчанию.

-signal_number
Неотрицательное десятичное целое, задающее сигнал для отправки в TERM по умолчанию.

Следующие PID-ы, имеют особое значение:
-1 Если суперпользователь, то транслирует сигнал всем процессам; в противном случае вещает на все процессыbelong-
ing для пользователя.

Некоторые из наиболее часто используемых сигналов:

  • 1 HUP (hang up) — повесить.
  • 2 INT (interrupt) — прерывание.
  • 3 QUIT (quit) — выход.
  • 6 ABRT (abort) — прерывания.
  • 9 KILL (non-catchable, non-ignorable kill)
  • 14 ALRM (alarm clock) — будильник.
  • 15 TERM (software termination signal) — Программное обеспечение для прекращения сигнала.

PKill

Команда «pkill» позволяет использовать расширенные шаблоны регулярных выражений и других критериев соответствия. Вместо того чтобы использовать PID, теперь вы можете убить приложение, введя имя процесса. Например, чтобы убитьбраузер Firefox , просто запустите команду:

# pkill Firefox

Так как он соответствует шаблону регулярного выражения, вы также можете ввести часть имени процесса, например:

# pkill fire

Чтобы избежать «убийства» неправильных процессов, вы можете сделать «pgrep -l [имя_процесса]» в список подходящих процессов.

Pkill команда имеет гораздо больше вариантов, например если указать опцию «-u», то она позволит вам указать имя пользователя или ID. В этом примере мы посылаем сигнал TERM всем процессам, принадлежащие пользователю ‘nobody’:

# pkill -u nobody

Killall

Killall использует имя процесса, а вместо PID, и он «убивает» все экземпляры процесса с тем же именем. Например, если вы используете несколько экземпляров браузера Firefox, вы можете убить их всех с помощью команды:

# killall firefox

В Gnome, вы можете перезапустить Nautilus с помощью команды:

# killall nautilus

xkill

Xkill является графическим способом «убить» приложение. При вводе «XKill» в терминале, курсор мыши будет немедленно стать «крест». Все, что вам нужно сделать, это нажать «крест» на провинившихся приложение и он убьет приложение мгновенно. Если вы заинтересованы, вы можете добавить сочетание клавиш для включения функции XKill.

Еще сигналы которые используются

SIGABRT

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

SIGALRM, SIGVTALRM и SIGPROF

ALRM, VTALRM и / или сигнал PROF отправляется процессу, когда лимит времени, указанный при вызове функции сигнализации (например, setitimer) истекает.

ALRM

Посылается, когда настоящее время или часы с течением времени.

VTALRM

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

PROF

Посылается, когда процессорное время, используемое процессом и системы от имени процесса истекает.

SIGBUS

BUS сигнал посылает процессу, когда это приводит к ошибке шины. Условия, которые приводят к данному сигналу, например, неправильное выравнивание доступа к памяти или отсутствие физического адреса.

SIGCHLD

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

SIGCONT

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

SIGFPE

Сигнал FPE посылает процессу, когда он выполняет ошибочное, арифметические срабатывания, например, такие как деление на ноль.

SIGHUP

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

SIGILL

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

SIGINT

Сигнал INT посылает процессу от управляющего терминала, когда пользователь желает прервать процесс. Это как правило, инициируется нажатием Control-C, но на некоторых системах, «delete» или «break».

SIGKILL

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

SIGPIPE

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

SIGQUIT

QUIT сигнал посылается процессу от его управляющего терминала, когда пользователь запрашивает процесс выполнения дампа.

SIGSEGV

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

SIGSTOP

STOP сигнал указывает ОС, чтобы остановить процесс для последующего возобновления.

SIGTERM

Сигнал TERM посылается в процесс чтобы запросить о его прекращении. В отличие от сигнала «kill», он может быть и интерпретируемым или игнорируемым в процессе. Это позволяет процессу выполнять «nice» выполнение для прекращения высвобождения ресурсов и сохранения состояния в случае необходимости. Следует отметить, что SIGINT почти идентичен SIGTERM.

SIGTSTP

Сигнал TSTP посылается в процесс его управляющего терминала и говорит ему, что нужно временно приостановить. Это обычно инициируется пользователем нажатием Control-Z. В отличие от SIGSTOP, этот процесс может зарегистрировать обработчик сигнала или чтобы игнорировать сигнал.

SIGTTIN и SIGTTOU

В TTIN и TTOU сигналы посылаются процессу, когда он пытается прочитать или записать соответственно с (tty) терминала на заднем плане. Как правило, этот сигнал может быть получен только с помощью процессов находящихся под контролем рабочей панели; демоны не имеют управляющие терминалы и никогда не должны получать этот сигнал.

SIGUSR1 и SIGUSR2

Сигналы USR1 и USR2 отправляются процессу и указывают определенные пользователем условия.

SIGPOLL

Сигнал POLL отправляется в процессе, когда происходит асинхронное событие ввода / вывода.

SIGSYS

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

SIGTRAP

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

SIGURG

Сигнал URG отправляется процессу, когда socket имеет срочные или вне диапазона данных, доступных для чтения.

SIGXCPU

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

SIGXFSZ

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

SIGRTMIN to SIGRTMAX

Сигналы RTMIN — RTMAX предназначены для использования в пользовательских целей. Они сигналы реального времени.

Разные сигналы

Следующие сигналы не стандартизированы POSIX, но иногда используются на некоторых системах.

SIGEMT

Сигнал ЕМТ отправляется в процессе, когда происходит прерывание эмулятора.

SIGINFO

Сигнал INFO отправляется в процессе, когда запрос статуса получен от управляющего терминала.

SIGPWR

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

SIGLOST

LOST сигнал посылается к процессу, когда «file lock» теряется.

SIGWINCH

Сигнал WINCH отправляется в процессе, когда его управляющий терминал меняет свой размер.

Отправка сигналов с клавиатуры

Сигналы могут быть отправлены с клавиатуры. Несколько стандартных значений по умолчанию перечислены ниже. По умолчанию сочетания клавиш для отправки сигналов прерывания может быть определен с помощью команды stty.

CTRL-C

Отправить SIGINT (прерывание). По умолчанию это вызывает процесс прекращается.

CTRL-Z

Отправить SIGTSTP (Suspend). По умолчанию это вызывает процесс приостановить все операции.

CTRL-\

Отправить SIGQUIT (Quit). По умолчанию это вызывает процесс прекратить немедленно и сбросить ядро.

CTRL-T

Отправить SIGINFO (INFO). По умолчанию это заставляет операционную систему отображать информацию о команде. Не поддерживается на всех системах.

вывод

Когда приложения ведут себя неправильно и вызвают к зависанию системы, это очень заманчиво, чтобы перезагрузить компьютер и начать сеанс снова и снова. С помощью этих команд «kill», вы сможете лучше управлять плохим поведением приложений которые вызывают или могут вызвать крах системы. На этом, я завершаю данную тему «Команда kill в Unix/Linux».

Надежные сигналы

Стандарт POSIX. 1 определил новый набор функций управления сигналами. основанный на интерфейсе 4.2BSD UNIX и лишенный рассмотренных выше недостатков.

Модель сигналов, предложенная POSIX, основана на понятии набора сигналов (signal set), описываемого переменной типа sigset_t . Каждый бит этой переменной отвечает за один сигнал. Во многих системах тип sigset_t имеет длину 32 бита, ограничивая количество возможных сигналов числом 32.

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

#include

int sigempyset(sigset_t *set);

int siufillset(sigset_t *set);

int sigaddset(sigset_t *set, int signo);

int sigdelset(sigset_t *set, int signo);

int sigismember(sigset_t *set, int signo);

В отличие от функции signal(3C) , изменяющей диспозицию сигналов, данные функции позволяют модифицировать структуру данных sigset_t , определенную процессом. Для управления непосредственно сигналами используются дополнительные функции, которые мы рассмотрим позже.

Функция sigemptyset(3C) инициализирует набор, очищая все биты. Если процесс вызывает sigfillset(3C) , то набор будет включать все сигналы, известные системе. Функции sigaddset(3C) и sigdelset(3C) позволяют добавлять или удалять сигналы набора. Функция sigismember(3C) позволяет проверить, входит ли указанный параметром signo сигнал в набор.

Вместо функции signal(3C) стандарт POSIX. 1 определяет функцию sigaction(2) , позволяющую установить диспозицию сигналов, узнать ее текущее значение или сделать и то и другое одновременно. Функция имеет следующее определение:

#include

int sigaction (int sig, const struct sigaction *act,

struct sigaction *oact);

Вся необходимая для управлением сигналами информация передается через указатель на структуру sigaction , имеющую следующие поля:

Поле sa_handler определяет действие, которое необходимо предпринять при получении сигналов, и может принимать значения SIG_IGN , SIG_DFL или адреса функции-обработчика. Если значение sa_handler или sa_sigaction не равны NULL , то в поле sa_mask передается набор сигналов, которые будут добавлены к маске сигналов перед вызовом обработчика. Каждый процесс имеет установленную маску сигналов, определяющую сигналы, доставка которых должна быть заблокирована. Если определенный бит маски установлен, соответствующий ему сигнал будет заблокирован. После возврата из функции-обработчика значение маски возвращается к исходному значению. Заметим, что сигнал, для которого установлена функция-обработчик, также будет заблокирован перед ее вызовом. Такой подход гарантирует, что во время обработки, последующее поступление определенных сигналов будет приостановлено до завершения функции. Как правило, UNIX не поддерживает очередей сигналов, и это значит, что блокировка нескольких однотипных сигналов в конечном итоге вызовет доставку лишь одного.

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

SA_ONSTACK Если определена функция-обработчик сигнала, и с помощью функции sigaltstack(2) задан альтернативный стек для функции-обработчика, то при обработке сигнала будет использоваться этот стек. Если флаг не установлен, будет использоваться обычный стек процесса.
SA_RESETHAND * Если определена функция-обработчик, то диспозиция сигнала будет изменена на SIG_DFL , и сигнал не будет блокироваться при запуске обработчика. Если флаг не установлен, диспозиция сигнала остается неизменной.
SA_NODEFER * Если определена функция-обработчик, то сигнал блокируется на время обработки только в том случае, если он явно указан в поле sa_mask . Если флаг не установлен, в процессе обработки данный сигнал автоматически блокируется.
SA_RESTART Если определена функция-обработчик, ряд системных вызовов, выполнение которых было прервано полученным сигналом, будут автоматически перезапущены после обработки сигнала. Если флаг не установлен, системный вызов возвратит ошибку EINTR .
SA_SIGINFO * Если диспозиция указывает на перехват сигнала, вызывается функция, адресованная полем sa_sigaction . Если флаг не установлен, вызывается обработчик sa_handler .
SA_NOCLDWAIT * Если указанный аргументом sig сигнал равен SIGCHLD , при завершении потомки не будут переходить в состояние зомби. Если процесс в дальнейшем вызовет функции wait(2) , wait3(2) , waitid(2) или waitpid(2) , их выполнение будет блокировано до завершения работы всех потомков данного процесса.
SA_NOCLDSTOP * Если указанный аргументом sig сигнал равен SIGCHLD , указанный сигнал не будет отправляться процессу при завершении или останове любого из его потомков.

* Данные флаги не определены для UNIX BSD.

В системах UNIX BSD 4.x структура sigaction имеет следующий вид:

struct sigaction {

void (*sa_handler)();

sigset_t sa_mask;

где функция-обработчик определена следующим образом:

void handler(int signo, int code, struct sigcontext *scp);

В первом аргументе signo содержится номер сигнала, code определяет дополнительную информацию о причине поступления сигнала, a scp указывает на контекст процесса.

Для UNIX System V реализована следующая возможность получения более полной информации о сигнале. Если установлен флаг SA_SIGINFO , то при получении сигнала sig будет вызван обработчик, адресованный полем sa_sigaction . Помимо номера сигнала, обычно передаваемого обработчику сигнала, ему будет переданы указатель на структуру siginfo_t , содержащую информацию о причинах получения сигнала, а также указатель на структуру ucontext_t , содержащую контекст процесса.

Структура siginfo_t определена в файле и включает следующие поля:

В поле si_signo хранится номер сигнала. Поле si_code имеет следующий смысл: если его значение меньше или равно нулю, значит сигнал был отправлен прикладным процессом, в этом случае структура siginfo_t содержит также следующие поля:

которые адресуют процесс, пославший сигнал; если значение si_code больше нуля, то оно указывает на причину отправления сигнала. Список возможных значений si_code для некоторых сигналов, соответствующих полю si_signo , приведен в табл. 2.19

Таблица 2.19 . Значения поля si_code структуры siginfo_t для некоторых сигналов

Значение поля si_signo Значение поля si_code Описание
SIGILL Попытка выполнения недопустимой инструкции
ILL_ILLOPC Недопустимый код операции (opcode)
ILL_ILLOPN Недопустимый операнд
ILL_ADR Недопустимый режим адресации
ILL_ILLTRP Недопустимая ловушка (trap)
ILL_PRVOPC Привилегированный код операции
ILL_PRVREG Привилегированный регистр
ILL_COPROC Ошибка сопроцессора
ILL_BADSTK Ошибка внутреннего стека
SIGFPE Особая ситуация операции с плавающей точкой
FPE_INTDIV Целочисленное деление на ноль
FPE_INTOVF Целочисленное переполнение
FPE_FLTDIV Деление на ноль с плавающей точкой
FPE_FLTOVF Переполнение с плавающей точкой
FPE_FLTUND Потеря точности с плавающей точкой (underflow)
FPE_FLTRES Неоднозначный результат операции с плавающей точкой
FPE_FLTINV Недопустимая операция с плавающей точкой
FPE_FLTSUB Индекс вне диапазона
SIGSEGV Нарушение сегментации
SEGV_MAPPER Адрес не отображается на объект
SEGV_ACCERR Недостаточно прав на отображаемый объект
SIGBUS Ошибка адресации
BUS_ADRALN Недопустимое выравнивание адреса
BUS_ADRERR Несуществующий физический адрес
BUS_OBJERR Аппаратная ошибка, связанная с объектом
SIGTRAP Ловушка
TRAP_BRKPT Процессом достигнута точка останова
TRAP_TRACE Ловушка трассирования процесса
SIGCHLD Завершение выполнения дочернего процесса
CLD_EXITED Дочерний процесс завершил выполнение
CLD_KILLED Дочерний процесс был "убит"
CLD_DUMPED Ненормальное завершение дочернего процесса
CLD_TRAPPED Трассируемый дочерний процесс находится в ловушке
CLD_STOPPED Выполнение дочернего процесса было остановлено
CLD_CONTINUED Выполнение остановленного дочернего процесса было продолжено
SIGPOLL Событие на опрашиваемом устройстве
POLL_IN Поступили данные для ввода
POLL_OUT Свободны буферы данных
POLL_MSG Сообщение ожидает ввода
POLL_ERR Ошибка ввода/вывода
POLL_PRI Высокоприоритетные данные ожидают ввода
POLL_HUP Устройство отключено

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

Таблица 2.20 . Дополнительные поля структуры siginfo_t

Установить маску сигналов или получить текущую маску можно с помощью функции sigprocmask(2) :

#include

int sigprocmask(int how, sigset_t *set, sigset_t *oset);

Маска сигналов изменяется в соответствии с аргументом how , который может принимать следующие значения:

Если указатель set равен NULL , то аргумент how игнорируется. Если аргумент oset не равен NULL , то в набор, адресованный этим аргументом, помещается текущая маска сигналов.

Функция sigpending(2) используется для получения набора заблокированных сигналов, ожидающих доставки:

#include

int sigpending(int how, sigset_t *set, sigset_t *oset);

Список сигналов, ожидающих доставки, возвращается в наборе, адресованном аргументом set .

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

#include

int sigsuspend(const sigset_t *set);

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

Заметим, что в BSD UNIX вызов signal(3) является упрощенным интерфейсом к более общей функции sigaction(2) , в то время как в ветви System V signal(3) подразумевает использование старой семантики ненадежных сигналов.

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

#include

#include

#include

#include

#include

/* Вариант "надежной" функции signal() */

void (*mysignal(int signo, void (*hndlr)(int)))(int) {

struct sigaction act, oact;

/* Установим маску сигналов */

act.sa_handler = hndlr;

sigemptyset(&act.sa_mask);

act.sa_flags = 0;

if (signo != SIGALRM)

act.sa_flags = SA_RESTART;

/* Установим диспозицию */

if (sigaction(signo, &act, &oact) < 0)

return(oact.sa_handler);

/* Функция-обработчик сигнала */

static void sig_hndlr(int signo) {

/* Эта часть кода нам уже не нужна

mysignal(SIGINT, sig_hndlr);

printf("Получен сигнал SIGINT ");

/* Установим диспозицию */

mysignal(SIGINT, sig_hndlr);

mysignal(SIGUSR2, SIG_IGN);

/* Бесконечный цикл */

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

Из книги Архитектура операционной системы UNIX автора Бах Морис Дж

7.2 СИГНАЛЫ Сигналы сообщают процессам о возникновении асинхронных событий. Посылка сигналов производится процессами - друг другу, с помощью функции kill, - или ядром. В версии V (вторая редакция) системы UNIX существуют 19 различных сигналов, которые можно классифицировать

Из книги Linux-сервер своими руками автора

5.8.2. Сигналы Демон syslogd реагирует на следующие сигналы: SYGTERM, SIGINT, SIGQUIT, SIGHUP, SIGUSR1, SIGCHLD. Реакция демона на сигналы описана в табл. 5.8.Реакция демона на сигналы Таблица 5.8 Сигнал Реакция SIGTERM Завершает работу демона SIGINT, SIGQUIT Завершает работу демона, если выключена отладка

автора Реймонд Эрик Стивен

Из книги Искусство программирования для Unix автора Реймонд Эрик Стивен

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

Из книги Инфраструктуры открытых ключей автора Полянская Ольга Юрьевна

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

Из книги Разработка приложений в среде Linux. Второе издание автора Джонсон Майкл К.

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

Из книги Linux: Полное руководство автора Колисниченко Денис Николаевич

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

Из книги QNX/UNIX [Анатомия параллелизма] автора Цилюрик Олег Иванович

27.3.10. Сигналы и сокеты С сокетами связаны три сигнала:? SIGIO - сокет готов к вводу/выводу. Сигнал посылается процессу, который связан с сокетом;? SIGURG - сокет получил экспресс-данные (мы их использовать не будем, поэтому особо останавливаться на них нет смысла);? SIGPIPE - запись

Из книги Программирование для Linux. Профессиональный подход автора Митчелл Марк

3. Сигналы

Из книги Операционная система UNIX автора Робачевский Андрей М.

Из книги Linux и UNIX: программирование в shell. Руководство разработчика. автора Тейнсли Дэвид

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

Из книги Взрыв обучения: Девять правил эффективного виртуального класса автора Мердок Мэттью

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

Из книги автора

Из книги автора

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

Из книги автора

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

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

вильных обращений к памяти, при выполнении специфических инст рукций или из-за операций с плавающей точкой. Наиболее распространенные сигналы, поступающие из внешнего мира: прерывание (interrupt ) – этот сигнал посылается, когда вы нажимаете клавишу DEL ; выход (quit )– порождается символом FS (ctl -\); отключение (hangup ) – вызван тем, что повешена телефонная трубка, и завершение (terminate ) – порождается командой kill. Когда происходит одно из вышеуказанных событий, сигнал посылается всем процессам, запу щенным с данного терминала, и если не существует соглашений, пред писывающих иное, сигнал завершает процесс. Для большинства сигналов создается дамп памяти, который может потребоваться для отладки. (См. adb(1) и sdb(l).)

Системный вызов signal изменяет действие, выполняемое по умолчанию. Он имеет два аргумента: первый – это номер, который определяет сигнал, второй – это или адрес функции, или же код, предписывающий игнорировать сигнал или восстанавливать действия по умолчанию. Файл содержит описания различных аргументов. Так,

#include

signal(SIGINT, SIG_IGN);

приводит к игнорированию прерывания, в то время как

signal(SIGINT, SIG_DFL);

восстанавливает действие по умолчанию – завершение процесса. Во всех случаях signal возвращает предыдущее значение сигнала. Если второй аргумент – это имя функции (которая должна быть объявлена в этом же исходном файле), то она будет вызвана при возникновении сигнала. Чаще всего эта возможность используется для того, чтобы позволить программе подготовиться к выходу, например удалить временный файл:

#include

char *tempfile = "temp.XXXXXX";

extern onintr();

mktemp(tempfile);

/* Обработка … */ exit(0);

onintr() /* очистить в случае прерывания */

unlink(tempfile); exit(1);

Зачем нужны проверка и повторный вызов signal в main? Вспомните, что сигналы посылаются во все процессы, запущенные на данном терминале. Соответственно, когда программа запущена не в интерактивном режиме (а с помощью &), командный процессор позволяет ей игно рировать прерывания, таким образом, программа не будет остановлена прерываниями, предназначенными для не фоновых процессов. Если же программа начинается с анонсирования того, что все прерывания должны быть посланы в onintr, невзирая ни на что, это сводит на нет попытки командного процессора защитить программу, работающую в фоновом режиме.

Решение, представленное выше, позволяет проверить состояние управления прерываниями и продолжать игнорировать прерывания, если они игнорировались ранее. Код учитывает тот факт, что signal возвращает предыдущее состояние конкретного сигнала. И если сигналы ранее игнорировались, процесс будет и далее их игнорировать; в противном случае они должны быть перехвачены.

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

#include

#include jmp_buf sjbuf;

if (signal(SIGINT, SIG_IGN) != SIG_IGN) signal(SIGINT, onintr);

setjmp(sjbuf); /* сохранение текущей позиции в стеке*/ for (;;) {

/* основной цикл обработки */

onintr() /* переустановить в случае прерывания */

signal(SIGINT, onintr); /* переустановить для следующего прерывания */ printf("\nInterrupt\n");

longjmp(sjbuf, 0); /* возврат в сохраненное состояние */ }

Файл setjmp.h объявляет тип jmp_buf как объект, в котором может сохраняться положение стека; sjbuf объявляется как объект такого типа. Функция setjmp(3) сохраняет запись о месте выполнения программы. Значения переменных не сохраняются. Когда происходит прерывание, инициируется обращение к программе onintr, которая может напечатать сообщение, установить флаги или сделать что-либо другое. Функция longjmp получает объект, сохраненный в setjmp, и возвращает управление в точку программы, следующую за вызовом setjmp. Таким образом, управление (и положение стека) возвращаются к тому месту основной программы, где происходит вход в основной цикл.

Обратите внимание на то, что сигнал снова устанавливается в onintr, после того как произойдет прерывание. Это необходимо, так как сигналы при их получении автоматически восстанавливают действие по умолчанию.

Некоторые программы просто не могут быть остановлены в произвольном месте, например в процессе обработки сложной структуры данных, поэтому необходимо иметь возможность обнаруживать сигналы. Возможно следующее решение – надо сделать так, чтобы программа обработки прерываний установила флаг и возвратилась обратно вместо того, чтобы вызывать exit или longjmp. Выполнение будет продолжено с того самого места, в котором оно было прервано, а флаг прерывания может быть проверен позже.

С таким подходом связана одна трудность. Предположим, что программа читает с терминала в то время, когда послано прерывание. Надлежащим образом вызывается указанная подпрограмма, которая устанавливает флаги и возвращается обратно. Если бы дело действительно обстояло так, как было указано выше, то есть выполнение программы возобновлялось бы «с того самого места, где оно было прервано», то программа должна была бы продолжать читать с терминала до тех пор, пока пользователь не напечатал бы новую строку. Такое поведение может сбивать с толку, ведь пользователь может и не знать, что программа читает, и он, вероятно, предпочел бы, чтобы сигнал вступал в силу незамедлительно. Чтобы разрешить эту проблему, система завершает чтение, но со статусом ошибки, который указывает, что произошло; errno устанавливается в EINTR, определенный в errno.h, чтобы обозначить прерванный системный вызов.

Поэтому программы, которые перехватывают сигналы и возобновляют работу после них, должны быть готовы к «ошибкам», вызванным прерванными системными вызовами. (Системные вызовы, по отношению к которым надо проявлять осторожность, – это чтение с терминала, ожидание и пауза). Такая программа может использовать код, приведенный ниже, для чтения стандартного ввода:

#include extern int errno;

if (read(0, &c, 1) <= 0) /* EOF или прерывание */

if (errno == EINTR) { /* EOF, вызванный прерыванием */ errno = 0; /* переустановить для следующего раза */

} else { /* настоящий конец файла */

И последняя тонкость, на которую надо обратить внимание, если перехват сигналов сочетается с выполнением других программ. Предположим, что программа обрабатывает прерывания и, к тому же, содержит метод (как! в ed), посредством которого могут выполняться другие программы. Тогда код будет выглядеть примерно так:

if (fork() == 0) execlp(…);

signal(SIGINT, SIG_IGN); /* предок игнорирует прерывания */

wait(&status); /* пока выполняется потомок */ signal(SIGINT, onintr); /* восстановить прерывания */

Почему именно так? Сигналы посылаются всем вашим процессам. Предположим, что программа, которую вы вызвали, обрабатывает свои собственные прерывания, как это делает редактор. Если вы прерываете дочернюю программу, она получит сигнал и вернется в свой основной цикл, и, вероятно, прочитает ваш терминал. Но вызывающая программа также выйдет из состояния ожидания дочерней программы и прочитает ваш терминал. Наличие двух процессов чтения терминала все запутывает, так как на самом деле система «подкидывает монетку», чтобы решить, какая программа получит каждую из строк ввода. Чтобы избежать этого, родительская программа должна игнорировать прерывания до тех пор, пока не выполнится дочерняя. Это умозаключение отражено в обработке сигналов в system:

#include

system(s) /* выполнить командную строку s */ char *s;

int status, pid, w, tty;

int (*istat)(), (*qstat)();

if ((pid = fork()) == 0) {

execlp("sh", "sh", "–c", s, (char *) 0); exit(127);

istat = signal(SIGINT, SIG_IGN); qstat = signal(SIGQUIT, SIG_IGN);

while ((w = wait(&status)) != pid && w != –1)

if (w == –1) status = –1;

signal(SIGINT, istat); signal(SIGQUIT, qstat); return status;

В отступление от описания, функция signal очевидно имеет несколько странный второй аргумент. На самом деле это указатель на функцию, которая возвращает целое число, и это также тип самой функции sig– nal. Два значения, SIG_IGN и SIG_DFL, имеют правильный тип, но выби раются таким образом, чтобы они не совпадали ни с какими возможными реальными функциями. Для особо интересующихся приведем пример того, как они описываются для PDP-11 и VAX; описания должны быть достаточно отталкивающими, чтобы побудить к использованию signal.h.

#define SIG_DFL (int (*)())0

#define SIG_IGN (int (*)())1

Сигналы alarm

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

Чтобы пояснить ситуацию, рассмотрим программу, называемую time– out, она запускает некоторую другую команду; если эта команда не закончилась к определенному времени, она будет аварийно прервана, когда alarm выключится. Например, вспомните команду watchfor из главы 5. Вместо того чтобы запускать ее на неопределенное время, можно установить часовой лимит:

$ timeout -3600 watchfor dmg &

Код в timeout иллюстрирует практически все, о чем говорилось в двух предыдущих разделах. Потомок создан; предок устанавливает аварийный сигнал и ждет, пока потомок закончит свою работу. Если alarm приходит раньше, то потомок уничтожается. Предпринимается попытка вернуть статус выхода потомка.

/* timeout: устанавливает временное ограничение для процесса */

#include

#include

int pid; /* идентификатор дочернего процесса */

char *progname; main(argc, argv)

int sec = 10, status, onalarm();

progname = argv;

if (argc > 1 && argv == ‘–’) { sec = atoi(&argv);

argc––; argv++;

if (argc < 2)

error("Usage: %s [–10] command", progname); if ((pid=fork()) == 0) {

execvp(argv, &argv); error("couldn’t start %s", argv);

signal(SIGALRM, onalarm); alarm(sec);

if (wait(&status) == –1 || (status & 0177) != 0) error("%s killed", argv);

exit((status >> 8) & 0377);

onalarm() /* завершить дочерний процесс в случае получения alarm */

kill(pid, SIGKILL);

Упражнение 7.18. Можете ли вы предположить, как реализован sleep? Подсказка: pause(2). При каких условиях (если такие условия существуют) sleep и alarm могут создавать помехи друг для друга? ~

История и библиография

В книге не представлено подробного описания реализации системы UNIX, в частности из-за того, что существуют имущественные права на код. Доклад Кена Томпсона (Ken Thompson) «UNIX implementation» (Реализация UNIX), изданный в «BSTJ» в июле 1978 года, описывает основные идеи. Эту же тему поднимают статьи «The UNIX system – a retrospective» (Система UNIX в ретроспективе) в том же номере

«BSTJ» и «The evolution of the UNIX time-sharing system» (Эволюция UNIX – системы разделения времени), напечатанная в материалах Symposium on Language Design and Programming Methodology в журнале издательства Springer-Verlag «Lecture Notes in Computer Science»

№ 79 в 1979 году. Оба труда принадлежат перу Денниса Ритчи (Dennis Ritchie).

Программа readslow была придумана Питером Вейнбергером (Peter Weinberger) в качестве простого средства для демонстрации зрителям игры шахматной программы Belle Кена Томсона и Джо Кондона (Joe Condon) во время шахматного турнира. Belle записывала состояние игры в файл; наблюдатели опрашивали файл с помощью readslow, чтобы не занимать слишком много драгоценных циклов. (Новая версия оборудования для Belle осуществляет небольшие расчеты на своей главной машине, поэтому больше такой проблемы не существует.)

Том Дафф (Tom Duff) вдохновил нас на написание spname. Статья Айво ра Дерхема (Ivor Durham), Дэвида Лэмба (David Lamb) и Джеймса Сакса (James Saxe) «Spelling correction in user interfaces» (Проверка орфографии в пользовательских интерфейсах), изданная CACM в октябре 1983 года, представляет несколько отличающийся от привычного проект реализации исправления орфографических ошибок в контексте почтовой программы.

Серия контента:

В современных операционных системах существует понятие межпроцессного взаимодействия (Inter-Process Communication – IPC) – это набор способов обмена данными между процессами и/или потоками. Одним из таких способов обмена служат сигналы. Концепцию сигналов поддерживает большинство операционных систем, но, например, Windows, не имеет их полноценной поддержки для использования в качестве одного из способов IPC – в подобных операционных системах сигналы лишь реализованы в стандартной библиотеке языка C.

Концепция сигналов

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

Сигналы имеют определенный жизненный цикл. Вначале сигнал создается – высылается процессом или генерируется ядром. Затем сигнал ожидает доставки в процесс-приемник. Принято считать, что период ожидания сигнала равен промежутку времени между созданием сигнала и действием, которое этот сигнал высылает. В конце жизненного цикла сигнала происходит его перехват (прием) процессом и выполнение связанных с сигналом действий.

Простые сигналы и надежные сигналы

Существует деление сигналов на простые и надежные.

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

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

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

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

Сигналы и системные вызовы

Сигнал может прийти к процессу в тот момент, когда процесс находится внутри какого-нибудь системного вызова, например, ожидает ввода данных в read(). В этом случае развитие событий может пойти по следующему пути: приложение не пытается перехватить сигнал и прерывается ядром (например, приход сигнала SIGTERM) – тогда терминал остается в нестандартной конфигурации, что может затруднить работу пользователя. Конечно, можно перехватить сигнал, очистить терминал в обработчике сигнала, а затем выйти, но достаточно сложно написать такой обработчик сигнала, который бы знал, что делала программа в момент прерывания, чтобы решить, следует ли выполнять очистку терминала, или нет.

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

Современная реализация сигналов заставляет медленные системные вызовы возвращать код ошибки EINTR, когда они прерываются приходящим сигналом. Быстрые системные вызовы должны завершаться перед тем, как сигнал будет доставлен. Медленный системный вызов – системный вызов, требующий неопределенного количества времени для своего завершения, например read(), wait(), write(). Все системные вызовы, зависящие от непредсказуемых ресурсов, таких как действия человека, сетевые данные и т.п., являются медленными. Естественно, все остальные системные вызовы – быстрые.

Обычно программа обрабатывает код ошибки EINTR и, если не произошло ничего фатального, перезапускает системный вызов. Большинство Unix-like операционных систем в настоящее время делает это по умолчанию – нужно лишь обработать сигнал в обработчике, а системный вызов будет перезапущен автоматически. В Linux по умолчанию системные вызовы не перезапускаются, но для каждого сигнала процесс может установить флаг, указывающий системе о необходимости перезапуска медленных системных вызовов, прерванных этим сигналом.

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

Посылка сигналов от одного процесса к другому обычно осуществляется при помощи системного вызова kill(). Его первый параметр – PID процесса, которому посылается сигнал; второй параметр – номер сигнала. Если мы хотим послать сигнал SIGTERM процессу с PID 6666, то используем системный вызов kill() так:

kill(6666, SIGTERM);

Вместо положительного значения PID можно передать вызову равное по модулю, но отрицательное значение. Тогда сигнал будет послан всем процессам из группы с номером, равным модулю переданного PID"а. Если PID равен 0, то сигнал посылается всем процессам из группы, к которой относится и текущий процесс. Эти возможности используются в основном оболочками для управления заданиями.

Если в качестве PID передать в системный вызов kill() значение -1, то сигнал будет послан всем процессам за исключением init"а. Такая возможность применяется для завершения работы системы.

Более подробная информация по системному вызову kill() приведена в man 2 kill.

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

raise(SIGTERM);

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

Перехват сигналов

Все программы, подчиняющиеся стандарту POSIX, регистрируют свои обработчики сигналов при помощи системного вызова sigaction(). Этот системный вызов имеет три параметра: первый – int signum – номер перехватываемого сигнала. Второй – struct sigaction * act – указатель на структуру, описывающую правила установки обработчика. Третий параметр – struct sigaction * oact – принимает уже установленные правила обработчика сигнала. Либо второй, либо третий (но не оба сразу!) параметр можно установить в NULL при необходимости.

Структура struct sigaction имеет следующее описание:

struct sigaction { __sighandler_t sa_handler; sigset_t sa_mask; int sa_flags; };

sa_handler – указатель на обработчик сигнала, причем обработчик должен быть объявлен следующим образом:

void signal_handler(int signo);

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

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

Параметр sa_flags позволяет процессу модифицировать поведение сигнала. Параметр может принимать всего четыре значения, которые, впрочем, можно объединять при помощи битовой операции "ИЛИ":

  1. SA_NOCLDSTOP – отсылать сигнал SIGCHLD только в случае прерывания дочернего процесса. Приостановка дочернего процесса не вызывает посылки сигнала.
  2. SA_NODEFER – эмуляция простых (ненадежных) сигналов.
  3. SA_RESTHAND – после прихода сигнала его обработчик сбрасывается в SIG_DFL.
  4. SA_RESTART – перезапуск системного вызова после возврата из обработчика сигнала. Если флаг не установлен, то системный вызов возвращает ошибку EINTR.

Как обычно, при успешном выполнении sigaction() возвращается 0, а в случае ошибки – отрицательное значение.

Маска сигналов процесса

Добавление сигналов в структуру sigset_t sa_mask, ее очистка и т.п. осуществляются при помощи набора функций sigemptyset(), sigfillset(), sigaddset(), sigdelset(). Первые две функции принимают один параметр – указатель на структуру sigset_t. Эти функции очищают и заполняют всеми возможными сигналами структуру sigset_t соответственно.

Последние две функции, соответственно, добавляют и удаляют один определенный сигнал из структуры и имеют по два параметра. Их первый параметр – указатель на структуру sigset_t, а второй – номер сигнала.

Все рассмотренные выше функции возвращают 0 при успешном завершении и число, не равное нулю, – при ошибке.

Кроме того, существует еще одна функция, проверяющая, находится ли указанный сигнал в указанном наборе – sigismember(). Ее параметры совпадают с параметрами sigaddset(). Функция возвращает 1, если сигнал находится в наборе, 0 – если не находится, и отрицательное число – при возникшей ошибке.

Помимо всего прочего, мы можем задать список сигналов, доставка которых процессу будет заблокирована. Это выполняется при помощи функции sigprocmask(int how, const sigset_t * set, sigset_t * oldset).

Первый ее параметр описывает то, что должно выполняться:

  1. SIG_BLOCK – сигналы из набора set блокируются;
  2. SIG_UNBLOCK – сигналы из набора set разблокируются;
  3. SIG_SETMASK – сигналы из набора set блокируются, остальные разблокируются.

Второй параметр является указателем на тот самый набор, сигналы из которого блокируются/разблокируются. Если он равен NULL, то значение первого параметра игнорируется системным вызовом.

Третий параметр – указатель на уже используемую маску сигналов; его можно поставить в NULL, если эти данные не нужны.

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

Принципы написания обработчиков сигналов

Одно из самых главных правил написания обработчиков сигналов – обработчик должен быть реентерабельным, т.е. он должен допускать свой повторный вызов, когда процесс уже находится в обработчике. Нужно заботиться о том, чтобы обработчик сигналов не использовал глобальные структуры данных или медленные системные вызовы. Если избежать этого, увы, невозможно, то стоит позаботиться о защите от повторного вызова обработчика во время работы со структурой данных или с системным вызовом. Добиться этого можно, заблокировав на время доставку сигнала, обработчик которого сейчас работает, при помощи системного вызова sigprocmask(). Например, мы имеем обработчик сигнала SIGCHLD, выполняем в нем блокировку так:

void chld_handler(int signum) { sigset_t set; if (sigemptyset(&set)) { return;) if (sigaddset(&set, SIGCHLD)) { return; } if (sigprocmask(SIG_BLOCK, &set, NULL)) { return; } /* делаем здесь что-то важное */ if (sigprocmask(SIG_UNBLOCK, &set, NULL)) { return; } return; }

Кроме всего перечисленного выше, считается, что обработчик должен быть максимально простым – в идеале он должен выставлять некий флаг и завершаться, а все остальное должна выполнять основная часть программы.

Заключение

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

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

Ресурсы для скачивания

static.content.url=http://www.сайт/developerworks/js/artrating/

ArticleID=495997

ArticleTitle=Работа с сигналами в Linux: Часть 1. Основы работы с сигналами

Статьи по теме: