The OpenNET Project / Index page

[ новости /+++ | форум | wiki | теги | ]

Каталог документации / Раздел "Программирование, языки" / Оглавление документа
Вперед Назад Содержание

21. Обработка Сигнала

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

Библиотека GNU C определяет ряд типов сигналов, каждый для конкретного вида события. Некоторые виды событий делают нецелесообразным или невозможным обычное продолжение программы, и соответствующие сигналы обычно прерывают программу. Другие виды сигналов сообщают о безобидных событиях, и игнорируются по умолчанию.

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

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

21.1 Базисные Понятия Сигналов

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

Некоторые виды Сигналов

Сигнал сообщает об исключительном событии. Вот - некоторые из событий, которые могут вызывать (или генерировать) сигнал:

Большинство сред допускают пользователю приостанавливать программу, печатая C-z, или завершать с C-c.

Каждый из этих видов событий (за исключением явных обращений, чтобы уничтожать и вызывать) генерирует собственный вид сигнала. Различные виды сигналов перечислены и описываются подробно в Разделе 21.2 [Стандартные Сигналы].

Понятия Порождения Сигналов

Вообще, события, которые генерируют сигналы, относятся к трем главным категориям: ошибки, внешние события, и явные запросы.

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

Явный запрос означает использование библиотечной функции типа kill, чья цель - специально генерировать сигнал.

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

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

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

Как Передаются Сигналы

Когда сигнал сгенерирован, он откладывается. Обычно он задерживается на короткий период времени и потом передаеся процессу. Однако, если этот вид сигнала в настоящее время блокирован, он может оставаться отложенным, пока сигнал этого вида не откроют. Если только он откроется, он будет передан немедленно. См. Раздел 21.7 [Блокированные Сигналы].

Когда сигнал - передан, или сразу же или после задержки, выполняется заданное действие для этого сигнала. Для некоторых сигналов, типа SIGKILL и SIGSTOP, действие фиксировано, но для большинства сигналов, программа имеет выбор: игнорировать сигнал, определить функцию обработчика, или принять заданное по умолчанию действие для этого вида сигнала. Программа определяет свой выбор используя функции типа signal или sigaction (см. Раздел 21.3 [Действия Сигнала]). Мы иногда говорим, что обработчик захватывает сигнал. В то время как обработчик выполняется, этот специфический сигнал обычно блокируется.

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

Если прибывает сигнал, который программа не обрабатывает, и не игнорирует, происходит заданное по умолчанию действие. Каждый вид сигнала имеет собственное заданное по умолчанию действие, зарегистрированное ниже (см. Раздел 21.2 [Стандартные Сигналы]). Для большинства видов сигналов, заданное по умолчанию действие должно завершить процесс. Для некоторых видов сигналов, которые представляют "безобидные" события, заданное по умолчанию действие должны не делать ничего.

Когда сигнал завершает процесс, родительский процесс может определять причину окончания, исследуя код состояния окончания, сообщенный wait или waitpid функциями. (Это обсуждено более подробно в Разделе 23.6 [Завершение Процесса].) информация, которую он может получать, включает предложение, что окончание было из-за сигнала, и вида включаемого сигнала. Если программа, которую Вы выполняете из оболочки, завершена сигналом, оболочка обычно печатает некоторое сообщение об ошибках.

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

Если Вы вызываете сигнал "ошибки в программе" явным запросом, и он завершает процесс, он сделает core-файл, точно как если бы сигнал был непосредственно благодаря ошибке.

21.2 Стандартные Сигналы

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

Имена сигналов определены в заглавном файле " signal.h ".

       int NSIG  (макрос)
Значение этой символической константы - общее число определенных сигналов. Так как номера сигналов размещены последовательно, NSIG на один больше чем самое большое определенное число сигнала.

Сигналы Ошибки в программе

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

Некоторые программы обрабатывают сигналы, возникающие при ошибке в программе, чтобы закончиться логически перед завершением. Обработчик должен завершить работу, определяя заданное по умолчанию действие для сигнала, который случался; это заставит программу завершаться с этим сигналом, как будто без обработчика. (См. Раздел 21.4.2 [Окончание Обработчика].)

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

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

Когда один из этих сигналов об ошибке в программе завершает процесс, он также формирует core-файл, который записывает состояние процесса во время окончания. Файл называется " core " и записан в текущий каталог процесса. (В системе GNU, Вы можете определять имя файла для дампов переменной среды COREFILE.)

Цель core-файлов - чтобы Вы могли исследовать их отладчиком, чтобы найти то, что вызвало ошибку.

       int SIGFPE  (макрос)
Сигнал SIGFPE сообщает фатальную арифметическую ошибку. Хотя имя происходит от " исключение с плавающей запятой ", этот сигнал фактически покрывает все арифметические ошибки, включая деление на нуль и переполнение. Если программа сохраняет целочисленные данные в стандарте, который используется операциями с плавающей запятой, это часто, вызывает исключение " недопустимая операция ", потому что процессор не может распознать данные как число с плавающей запятой.
       FPE_INTOVF_TRAP
Целочисленное переполнение (невозможно в C программе, если Вы не даете возможность прерыванию переполнения в аппаратно-специфическом режиме).
       FPE_INTDIV_TRAP
Целочисленное деление на нуль.
       FPE_SUBRNG_TRAP
Переход нижнего индекса (кое-что, что программы C никогда не проверяют).
       FPE_FLTOVF_TRAP
Плавающее переполнения.
       FPE_FLTDIV_TRAP
Плавающее/десятичное деление на нуль.
       FPE_FLTUND_TRAP
Плавающее антипереполнение (переполнение снизу - слишком маленькое число).
       FPE_DECOVF_TRAP
Десятичное переполнения. (Только несколько машин имеют десятичную арифметику, и C никогда не использует ее.)

       int SIGILL  (макрос)
Имя этого сигнала происходите от " запрещенная команда "; это означает что ваша программа пробует выполнить привилегированную команду. Так как компилятор C генерирует только допустимые команды, SIGILL обычно указывает, что исполняемый файл разрушен, или что Вы пробуете выполнять данные. Некоторые общие положения входящие в последнюю ситуацию - передача недопустимого объекта там, где ожидался указатель на функцию, или запись после конца автоматического массива (или подобные проблемы с указателями на динамические локальные переменные) и разрушение других данных стека типа адреса возврата.
       int SIGSEGV  (макрос)
Этот сигнал сгенерирован, когда программа пробует читать или писать вне памяти, которая размещена для этого. (Фактически, сигналы происходят только, когда программа идет достаточно далеко, чтобы быть обнаруженной механизмом защиты памяти системы.) имя ­ сокращение " нарушение сегментации ".
       int SIGBUS  (макрос)
Этот сигнал сгенерирован, когда недопустимый указатель применяется. Подобно SIGSEGV, этот сигнал - обычно результат применения неинициализированного указателя. Различие между ними в том, что SIGSEGV указывает на недопустимый доступ к допустимой памяти, в то время как SIGBUS указывает на доступ к недопустимому адресу. В частности SIGBUS сигналы часто следуют из применения расположенного с нарушением границ указателя, типа целого числа (из четырех слов) с адресом, не делимым на четыре. (Каждый вид компьютера имеет собственные требования для выравнивания адреса.)

Имя этого сигнала - сокращение для " ошибка шины ".

       int SIGABRT  (макрос)
Этот сигнал указывает ошибку, обнаруженную программой непосредственно и сообщается, вызовом abort. См. Раздел 22.3.4 [Прерывание выполнения Программы].

Сигналы Завершения

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

Причина обработки этих сигналов - обычно то, что ваша программа должна выполнить некоторые действия перед фактическим завершением. Например, Вы могли бы хотеть сохранять информацию о состоянии, удалить временные файлы, или восстанавливать предыдущие режимы терминала. Такой обработчик должен закончиться определением заданного по умолчанию действия для сигнала, см. раздел 21.4.2 [Окончание Обработчика].) (Очевидное) заданное по умолчанию действие для всех этих сигналов должно заставить процесс завершаться.

       int SIGHUP  (макрос)
SIGHUP ("зависание") сигнал используется, чтобы сообщить, что терминал пользователя разъединен, возможно, потому что сетевое или телефонное соединение было прервано. Для получения более подробной информации см. Раздел 12.4.6 [Режимы Управления].

Этот сигнал также используется, чтобы сообщить окончание процесса управления на терминале; это окончание действительно разъединяет все процессы в сеансе с терминалом управления. Для подробной информации см. раздел 22.3.5 [Внутренняя организация Окончания].

       int SIGINT  (макрос)
SIGINT (" прерывание программы ") сигнал посылается, когда пользователь печатает INTR символ (обычно C-c). См. Раздел 12.4.9 [Специальные Символы], для уточнения инфрмации относительно поддержки драйвера терминала для C-c.
       int SIGQUIT  (макрос)
Сигнал SIGQUIT подобен SIGINT, за исключением того, что он управляется другой клавишей (символом QUIT), обычно C-\ и производит core-файл, когда он завершает процесс, точно так же как сигнал ошибки в программе. Вы можете думать об этом как об условии ошибки в программе "обнаруженном" пользователем.

См. Раздел 21.2.1 [Сигналы Ошибки в программе], для уточнения инфрмации относительно core-файлов. См. Раздел 12.4.9 [Специальные Символы], для уточнения инфрмации относительно поддержки драйвера терминала.

       int SIGTERM  (макрос)
Сигнал SIGTERM - обобщенный сигнал, используемый, чтобы вызвать окончание программы. В отличие от SIGKILL, этот сигнал может быть блокирован, обрабатываться, и игнорироваться.

Команда оболочки kill генерирует SIGTERM по умолчанию.

       int SIGKILL  (макрос)
Сигнал SIGKILL используется, чтобы вызвать непосредственное окончание программы. Он не может быть обработан или игнорироваться, и следовательно всегда фатален. Также не возможно блокировать этот сигнал.

Этот сигнал сгенерирован только явным запросом. Так как он не может быть обработан, Вы должны генерировать его только после попытки применения менее сильнодействующего лекарственного средства типа C-c или SIGTERM.

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

Сигнализация

Эти сигналы используются, чтобы указать окончание времени таймеров. См. Раздел 17.3 [Установка Сигнализаци], для уточнения информации относительно функций, которые заставляют эти сигналы быть посланными.

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

       int SIGALRM  (макрос)
Этот сигнал обычно указывает окончание таймера, который измеряет реальное время или время часов.

Он используется функцией alarm, например.

       int SIGVTALRM  (макрос)
Этот сигнал обычно указывает окончание таймера, который измеряет CPU время, используемое текущим процессом. Имя - сокращение " виртуальный таймер ".
       int SIGPROF  (макрос)
Этот сигнал - обычно указывает окончание таймера, который измеряет оба и CPU время, используемое текущим процессом, и CPU время, израсходованное от имени процесса системой.

Асинхронные Сигналы ввода - вывода

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

       int SIGIO  (макрос)
Этот сигнал посылается, когда дескриптор файла готов выполнить ввод или вывод.

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

       int SIGURG  (макрос)
Этот сигнал послан, когда "срочные" данные или данные вне потока прибывают в этот сокет. См. Раздел 11.8.8 [Данные вне потока].

Сигналы Управления заданиями

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

Вы должны вообще оставить эти сигналы, если Вы действительно не понимаете, как управление заданиями работает. См. Главу 24 [Управление заданиями].

       int SIGCHLD  (макрос)
Этот сигнал послан родительскому процессу всякий раз, когда один из дочерних процессов завершается или останавливается.

Заданное по умолчанию действие для этого сигнала - игнорировать это. Если Вы устанавливаете обработчик для этого сигнала, в то время как имеются дочерние процессы, которые завершились, но не сообщили об их состоянии через wait или waitpid (см. Раздел 23.6 [Завершение Процесса]), то применяется ли ваш обработчик к этим процессам или нет зависит от специфической операционной системы.

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

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

       int SIGSTOP  (макрос)
Сигнал SIGSTOP останавливает процесс. Он не может быть обработан, игнорироваться, или блокироваться.
       int SIGTSTP  (макрос)
Сигнал SIGTSTP - интерактивный сигнал останова. В отличие от SIGSTOP, этот сигнал может быть обработан и игнорироваться.

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

Этот сигнал сгенерирован, когда пользователь печатает символ SUSP (обычно C-z). Для получения более подробной информации, см. Раздел 12.4.9 [Специальные Символы].

       int SIGTTIN  (макрос)
Процесс не может читать с терминала пользователя, в то время как он выполняется как фоновый. Когда любой фоновый процесс пробует читать с терминала, всем процессам посылется сигнал SIGTTIN. Заданное по умолчанию действие для этого сигнала должно остановить процесс. Для получения более подробной информации, относительно того как он взаимодействует с драйвером терминала, см. Раздел 24.4 [Доступ к Терминалу].
       int SIGTTOU  (макрос)
Подобен SIGTTIN, но сгенерирован, когда фоновый процесс делает попытку записи на терминал или устанавливать режимы. Снова, заданное по умолчанию действие должно остановить процесс.

В то время когда процесс приостановлен, сигналы не могут быть переданы ему, за исключением сигналов SIGKILL и (очевидно) SIGCONT. Сигнал SIGKILL всегда вызывает окончание процесса и не может быть блокирован или игнорироваться. Вы можете блокировать или игнорировать SIGCONT, но он всегда заставляет процесс быть продолженным во всяком случае, если он остановлен. Посылка сигнала SIGCONT на процесс заставляет любые отложенные сигналы останова для этого процесса быть отброшенными. Аналогично, любая задержка SIGCONT сигнала для процесса отброшена, когда он получает сигнал останова.

Разнообразные Сигналы

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

       int SIGPIPE  (макрос)
Если Вы используете водопроводы или FIFO, Вы должны разработать ваше приложение так, чтобы один процесс открыл водопровод для чтения перед тем как другой начал запиись. Если процесс считывания никогда не начинается, или завершается неожиданно, запись в водопровод или FIFO вызывает сигнал SIGPIPE. Если SIGPIPE блокирован, обрабатывается или игнорируется, дргие обращения вызывают EPIPE взамен.

Водопроводы и FIFO обсуждены более подробно в Главе 10 [Водопроводы и FIFO].

Другая причина SIGPIPE - когда Вы пробуете выводить на сокет, который не соединен. См. Раздел 11.8.5.1 [Посылка Данных].

       int SIGUSR1  (макрос)
       int SIGUSR2  (макрос)
SIGUSR1 и SIGUSR2 отложены для Вас, чтобы использовать их любым способом, которым Вы хотите.

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

Имеется пример, показывающий использование SIGUSR1 и SIGUSR2 в Разделе 21.6.2 [Передача сигналов Другому Процессу].

Нестандартные Сигналы

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

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

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

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

       SIGCLD  Устаревшее имя для SIGCHLD.
       SIGTRAP Сгенерированный командой breakpoint машины. Используется
               отладчиками. Заданное по умолчанию действие должно формировать
               core-файл.
       SIGIOT  Сгенерированный PDP-11 "iot" командой; эквивалент SIGABRT. 
               Заданное по умолчанию действие должно формировать core­файл.
       SIGEMT  Ловушка эмулятора; следует из некоторых невыполненных
               команд. Это - сигнал ошибки в программе.
       SIGSYS  Ошибочный системный вызов; то есть команда
               системного вызова была выполнена, но код системного вызова
               недопустим. Это - сигнал ошибки в программе.
       SIGPOLL Это - имя сигнала System V, более или менее подобное SIGIO.
       SIGXCPU Превышение ограничения времени CPU. Заданное по
               умолчанию действие - окончание программы.
       SIGXFSZ Файлом превышено ограничение размера. Заданное по
               умолчанию действие - окончание программы.
       SIGWINCH Изменение размера окна. Он генерируется на некоторых
               системах, когда размер текущего окна на экране изменен. 
               Заданное по умолчанию действие - игнорировать это.

Сообщения Сигнала

Мы упомянули выше, что оболочка печатает сообщение, описывающее сигнал, который завершил дочерний процесс. Простой способ печатать сообщение, описывающее сигнал состоит в том, чтобы использовать функции strsignal и psignal. Эти функции используют номер сигнала, чтобы определить, какой вид сигнала описывать. Номер сигнала может исходить из состояния окончания дочернего процесса (см. Раздел 23.6 [Завершение Процесса]) или он может исходить из обработчика сигнала на том же самом процессе.

       char * strsignal (int signum)  (функция)
Эта функция возвращает указатель на статически размещенную строку, содержащую сообщение, описывающее сигнал. Вы не должны изменять содержимое этой строки; и, так как она может быть перезаписана при последующих обращениях, Вы должны сохранить его копию, если Вы должны сослаться на него позже.

Эта функция - расширение GNU, объявленное в заглавном файле "string.h".

       void psignal (int signum, const char *message)  
Эта функция печатает сообщение, описывающее сигнал, в стандартный поток ошибкок - stderr; см. Раздел 7.2 [Стандартные Потоки].

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

Если Вы обеспечиваете непустой аргумент сообщения, то psignal предворяет вывод этой строкой. Она добавляет двоеточие и пробел, чтобы отделить сообщение от строки, соответствующей сигналу.

Эта функция - возможность BSD, объявленная в заглавном файле "stdio.h ".

Имеется также массив sys_siglist, который содержит сообщения для различных кодов сигнала. Этот массив существует в системах BSD, в отличие от strsignal.

21.3 Определение Действий Сигнала

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

Библиотека GNU также осуществляет более универсальное средство sigaction. Этот раздел описывает эти средства и дает предложения, когда их использовать.

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

Функция signal обеспечивает простой интерфейс для установки действия для специфического сигнала.

Функция и связанные макрокоманды объявлены в заголовочном файле "signal.h ".

       sighandler_t  (тип данных)
Это - тип функций обработчика сигнала. Обработчики Сигнала воспринимают один целочисленный аргумент, определяющий номер сигнала, и возвращают тип void. Вы должны определять функции обработчика примерно так:
                         void handler (int signum) { . . . }
Имя sighandler_t для этого типа данных - расширение GNU. sighandler_t signal (int signum, sighandler_t action) (функция)

Функция signal устанавливает action как действие для сигнала signum.

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

Второй аргумент, action, определяет действие используемое для сигнала signum. Оно может быть одно из следующих: SIG_DFL определяет заданное по умолчанию действие для специфического сигнала. Заданные по умолчанию действия для различных видов сигналов установлены в Разделе 21.2 [Стандартные Сигналы].

SIG_IGN определяет, что сигнал должен игнорироваться.

Ваша программа вообще не должна игнорировать сигналы, которые представляют серьезные события, или обычно используется, чтобы запросить окончание. Вы не можете игнорировать SIGKILL или SIGSTOP вообще. Вы можете игнорировать сигналы ошибки в программе подобно SIGSEGV, но игнорирование ошибки не будет давать возможность программе продолжить осмысленное выполнение. Игнорирование запросов пользователя типа SIGINT, SIGQUIT, и SIGTSTP некультурно!!.

Когда Вы не желаете, чтобы сигналы были передан в некоторую части программы, нужно блокировать их, а не игнорировать. См. Раздел 21.7 [Блокированные Сигналы]. handler обеспечивает адрес функции обработчика в вашей программе, выполнение этого обработчика определяется как способ передать сигнал.

Для получения более подробной информации относительно функции­ обработчика сигнала, см. Раздел 21.4 [Определение Обработчиков].

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

Если signal не может выполнить запрос, она возвращает SIG_ERR. Следующие errno условия ошибки определены для этой функции:

EINVAL

Вы определили недопустимый signum; или Вы пробовали игнорировать или обеспечивать обработчик для SIGKILL или SIGSTOP.

Вот простой пример установки обработчика, чтобы удалить временные файлы, при получении некоторых фатальных сигналов:
                 #include <signal.h>
                 void
                 termination_handler (int signum)
                 {
                         struct temp_file *p;
                         for (p = temp_file_list; p; p = p->next)
                                 unlink (p->name);
                 }
                 int
                 main (void)
                 {
                         . . .
                         if (signal (SIGINT, termination_handler)
                                 == SIG_IGN)
                                 signal (SIGINT, SIG_IGN);
                         if (signal (SIGHUP, termination_handler)
                                 == SIG_IGN)
                                 signal (SIGHUP, SIG_IGN);
                         if (signal (SIGTERM, termination_handler)
                                         == SIG_IGN)
                                 signal (SIGTERM, SIG_IGN);
                         . . .
                 }
Обратите внимание, что, если данный сигнал был предварительно установлен, чтобы игнорироваться, то код избегает изменять эту установку. Потому что оболочки часто игнорируют некоторые сигналы, при старте дочерних процессов, и эти процессы не долхны изменять это.

Мы не обрабатываем SIGQUIT или сигналы ошибки в программе в этом примере, потому что они разработаны, чтобы обеспечить информацию для отладки (core-файл), и временные файлы могут давать полезную информацию.

       sighandler_t ssignal (int signum, sighandler_t action) (функция)
Функция ssignal делает ту же самую вещь что signal; она предоставляется только для совместимости с SVID.
       sighandler_t SIG_ERR  (макрос)
Значение этой макрокоманды используется как возвращаемое значение из signal, чтобы указать ошибку.

Сложная Обработка Сигнала

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

Функция sigaction объявлена в "signal.h".

       struct sigaction  (тип данных)
Структуры типа struct sigaction используются в функции sigaction, чтобы определить всю информацию относительно того, как обработать специфический сигнал. Эта структура содержит по крайней мере следующие элементы:
                sighandler_t sa_handler
Это используется таким же образом, как аргумент action функции signal. Значение может быть SIG_DFL, SIG_IGN, или указатель на функцию. См. Раздел 21.3.1 [Общая Обработка сигнала].
                 sigset_t sa_mask
Этот определяет набор сигналов, которые будут блокированы, в то время как обработчик выполняется. Блокирование объясняется в Разделе 21.7.5 [Блокирование для Обработчика]. Обратите внимание, что сигнал, который был передан, автоматически блокирован по умолчанию прежде, чем обработчик начат; это - истина независимо от значения в sa_mask. Если Вы хотите, чтобы этот сигнал не был блокирован внутри обработчика, Вы должен записать в обработчике код чтобы разблокировать его.
                 int sa_flags
Определяет различные флаги, которые могут воздействовать на поведение сигнала. Они описаны более подробно в Разделе 21.3.5 [Флаги для Sigaction].
       int sigaction (int signum, const struct sigaction *action, struct sigaction *old_action)
Аргумент action используется, чтобы установить новое действие для указанного сигнала, в то время как old_action аргумент используется, чтобы возвратить информацию относительно действия, предварительно связанного с этим сигналом. (Другими словами, Вы можете выяснить, что старое действие в действительности делало для сигнала, и восстановить его позже, если Вы хотите.)

Возвращаемое значение от sigaction - нуль, если она преуспевает, и -1 при отказе. Следующие errno условия ошибки определены для этой функции:

EINVAL

аргумент signum не допустим, или Вы пробуете обрабатывать или игнорировать SIGKILL или SIGSTOP.

Взаимодействие signal и sigaction

Возможно использовать signal и sigaction внутри одной программы, но Вы должны быть внимательным, потому что они могут взаимодействовать немного странными способами.

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

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

Если Вы устанавливаете действие с signal, а потом исследуете его sigaction, адрес обработчика, который Вы получаете, может быть не такой же как тот, что Вы определили с signal. Он так же не может использоваться как аргумент action в signal. Но Вы можете полагаться на использование его как аргумента sigaction.

Так что лучше отказаться от использования того и другого механизма последовательно внутри однй программы.

Примечание о переносимости: общая функция сигнала - возможность ANSI C, в то время как sigaction - часть POSIX.1 стандарта. Если Вы беспокоитесь относительно переносимости на не-posix системы, то Вы должены использовать функцию signal.

Пример Функции sigaction

В Разделе 21.3.1 [Общая Обработка сигнала], мы привели пример установки простого обработчика для сигналов окончания, используя signal. Вот эквивалентный пример, использующий sigaction:

                 #include <signal.h>
                 void
                 termination_handler (int signum)
                 {
                         struct temp_file *p;
                         for (p = temp_file_list; p; p = p->next)
                                 unlink (p->name);
                 }
                 int
                 main (void)
                 {
                         . . .
                         struct sigaction new_action, old_action;
                         new_action.sa_handler = termination_handler;
                         sigemptyset (&new_action.sa_mask);
                         new_action.sa_flags = 0;
                         sigaction (SIGINT, NULL, &old_action);
                         if (old_action.sa_handler != SIG_IGN)
                                 sigaction (SIGINT, &new_action, NULL);
                         sigaction (SIGHUP, NULL, &old_action);
                         if (old_action.sa_handler != SIG_IGN)
                                 sigaction (SIGHUP, &new_action, NULL);
                         sigaction (SIGTERM, NULL, &old_action);
                         if (old_action.sa_handler != SIG_IGN)
                                 sigaction (SIGTERM, &new_action, NULL);
                         . . .
                 }
Программа просто загружает структуру new_action с желательными параметрами и передает ее в sigaction. Использование sigemptyset описано позже; см. Раздел 21.7 [Блокированные Сигналы].

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

Вот другой пример. Он отыскивает информацию относительно текущего действия для SIGINT без замены этого действия.

                 struct sigaction query_action;
                 if (sigaction (SIGINT, NULL, &query_action) < 0)
                         /* sigaction возвращает -1 в случае ошибки. */
                 else if (query_action.sa_handler == SIG_DFL)
                         /* SIGINT обработан заданным по умолчанию,
                         фатальным способом. */
                 else if (query_action.sa_handler == SIG_IGN)
                         / * SIGINT игнорируется. * /
                 else
                         /* Определенный программистом
         обработчик сигнала. */

Флаги для sigaction

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

Значение sa_flags интерпретируется как битовая маска. Таким образом, Вы должны выбрать флаги, которые Вы хотите установить, сделать операцию OR для этих флагов, и сохранить результат в sa_flags элементе вашей структуры sigaction.

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

В библиотеке GNU C, установка обработчика сигнала обнуляет все флаги, кроме SA_RESTART, чье значение зависит от установок, которые Вы сделали в siginterrupt. См. Раздел 21.5 [Прерванные Примитивы].

Эти макрокоманды определены в заголовочном файле " signal.h ".

       int SA_NOCLDSTOP  (макрос)
Этот флаг имеет смысл только для сигнала SIGCHLD. Когда флаг установлен, система передает сигнал для завершенного дочернего процесса, но не для того, который остановлен. По умолчанию, SIGCHLD - передан для и завершенных дочерних процессов и для остановленных дочерних процессов.

При установке этого флага для сигнала отличного от SIGCHLD не имеет никакого эффекта.

       int SA_ONSTACK  (макрос)
Если этот флаг установлен для конкретного сигнала, система использует стек сигнала при сообщении этого вида сигнала. См. Раздел 21.9 [Обработка Сигнала BSD].
       int SA_RESTART  (макрос)
Этот флаг управляет тем, что случается, когда сигнал - передан в течение выполнения некоторых примитивов (типа open, read или write). Имеются два варианта: библиотечная функция может продолжиться, или она может возвратить отказ с кодом ошибки EINTR.

Выбор управляется SA_RESTART флагом для конкретного вида сигнала, который был передан. Если флаг установлен, по возвращении из обработчика продолжается библиотечная функция. Если флаг не установлен, происходит сбой функции. См. Раздел 21.5 [Прерванные Примитивы].

Начальные Действия Сигнала

После создания, новый процесс (см. Раздел 23.4 [Создание Процесса]) наследует обработку сигналов из родительского процесса. Однако, когда Вы загружаете новый образ процесса, используя функцию exec (см. Раздел 23.5 [Выполнение Файла] ) обработчики любых сигналов возвращаются к SIG_DFL. Конечно, новая программа может устанавливать собственные обработчики.

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

Вот пример того, как устанавливать обработчик для SIGHUP, но если SIGHUP в настоящее время не игнорируется:

                         . . .
                         struct sigaction temp;
                         sigaction (SIGHUP, NULL, &temp);
                         if (temp.sa_handler != SIG_IGN)
                         {
                                 temp.sa_handler = handle_sighup;
                                 sigemptyset (&temp.sa_mask);
                                 sigaction (SIGHUP, &temp, NULL);
                         }

21.4 Определение Обработчиков Сигнала

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

Обработчик сигнала - функция, которую Вы компилируете вместе с остальной частью программы. Вместо непосредственно вызова функции, Вы используете signal или sigaction, чтобы сообщить, чтобы операционная система вызвала ее, когда приходит сигнал. Это называется установкой обработчика. См. Раздел 21.3 [Действия Сигнала].

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

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

Обработчики Сигнала, которые Возвращаются

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

Небезопасно возвращаться из обработчика при сигнале ошибки в программе, потому что поведение программы, когда функция обработчика возвращается, не определено после ошибки в программе. См. Раздел 21.2.1 [Сигналы Ошибки в программе].

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

Обычно, эта переменная исследуется периодически программой. Тип данных должен быть sig_atomic_t по причинам, описанным в Разделе 21.4.7 [Быстрый Доступ к данным].

Вот простой пример такой программы. Она выполняет тело цикла, пока она не отметит, что сигнал SIGALRM прибыл.

                         #include <signal.h>
                         #include <stdio.h>
                         #include <stdlib.h>
                         volatile sig_atomic_t keep_going = 1;
                 void
                 catch_alarm (int sig)
                 {
                         keep_going = 0;
                         signal (sig, catch_alarm);
                 }


                 void
                 do_stuff (void)
                 {
                         puts ("Doing stuff while waiting
                 for alarm....");
                 }
                 int
                 main (void)
                 {
                         signal (SIGALRM, catch_alarm);
                         alarm (2);
                         while (keep_going)
                                 do_stuff ();
                         return EXIT_SUCCESS;
                 }

Обработчики, которые Завершают Процесс

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

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

                 volatile sig_atomic_t fatal_error_in_progress = 0;
                 void
                 fatal_error_signal (int sig)
                 {
                         if (fatal_error_in_progress)
                                 raise (sig);
                         fatal_error_in_progress = 1;
                         / * Теперь делаем очищающие действия:
                                          - установка режимов терминала
                                          - уничтожение дочерних процессов
                                          - удаление временных файлов * /
                         . . .
                         raise (sig);
                 }

Нелокальная Передача Управления в Обработчиках

Вы можете делать нелокальную передачу управления вне обработчика сигнала, используя setjmp и longjmp средства (см. Главу 20 [Нелокальные Выходы]).

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

Имеются два способа избежать этой проблемы. Первый - блокировать сигнал для частей программы, которые изменяют важные структуры данных. Блокирование сигнала задерживает выдачу, пока он не открыт, как только критическое изменение закончено. См. Раздел 21.7 [Блокированные Сигналы].

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

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

                 #include <signal.h>
                 #include <setjmp.h>
                 jmp_buf return_to_top_level;
                 volatile sig_atomic_t waiting_for_input;
                 void
                 handle_sigint (int signum)
                 {
                         waiting_for_input = 0;
                         longjmp (return_to_top_level, 1);
                 }
                 int
                 main (void)
                 {
                         . . .
                         signal (SIGINT, sigint_handler);
                         . . .
                         while (1) {
                                 prepare_for_command ();
                                 if (setjmp (return_to_top_level) == 0)
                                         read_and_execute_command ();
                         }
                 }
                 char *
                 read_data ()
                 {
                         if (input_from_terminal) {
                                 waiting_for_input = 1;
                                 . . .
                                 waiting_for_input = 0;
                         }
                         else {
                                 . . .
                         }
                 }

Прибытие Сигналов во Время Выполнения Обработчика

Что случается, если другой сигнал приходит, когда выполняется ваша функция обработчика сигнала?

Когда вызывается обработчик для конкретного сигнала, этот сигнал обычно блокируется, пока обработчик не возвращается. Это означает что, если два сигнала того же самого вида приходят через очень короткий интервал времени, второй будет приостановлен, пока первый не будет обработан. (Обработчик может явно открыть сигнал, используя sigprocmask, если Вы хотите позволить прибывать большему количеству сигналов этого типа; см. Раздел 21.7.3 [Маска Сигнала Процесса].)

Однако, ваш обработчик может все еще прерываться получением другого вида сигнала. Чтобы избежать этого, Вы можете использовать sa_mask - элемент структуры action, передаваемой sigaction, чтобы явно определить, какие сигналы должны быть блокированы в то время как обработчик сигнала выполняется. См. Раздел 21.7.5 [Блокирование для Обработчика].

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

Близкие (по времени) Сигналы Обьединяются в Один

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

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

                 struct process
                 {
                         struct process *next;
                         int pid;
                         int input_descriptor;
                         int status;
                 };
                 struct process *process_list;
Этот пример также использует флаг, чтобы указать, прибыли ли сигналы начиная с некоторого времени в прошлом, когда программа в последний раз обнулила его.
                 int process_status_change;
Вот обработчик непосредственно:
                 void
                 sigchld_handler (int signo)
                 {
                         int old_errno = errno;
                         while (1) {
                                 register int pid;
                                 int w;
                                 struct process *p;
                                 do
                                         {
                                                 errno = 0;
                                                 pid = waitpid (WAIT_ANY,
                                                 &w,
                 WNOHANG | WUNTRACED);
                                         }
                                 while (pid <= 0 && errno == EINTR);
                                 if (pid <= 0) {
                                         errno = old_errno;
                                         return;
                                 }
                                 for (p = process_list; p; p = p->next)
                                         if (p->pid == pid) {
                                                 p->status = w;
                                                 p->have_status = 1;
                                                 if (WIFSIGNALED (w) ||
                                         WIFEXITED (w))
                                                 if (p->input_descriptor)
                  FD_CLR (p->input_descriptor, &input_wait_mask);
                                                 ++process_status_change;
                                         }
                         }
                 }
Вот соответствующий способ проверять флаг process_status_change:
                 if (process_status_change) {
                         struct process *p;
                         process_status_change = 0;
                         for (p = process_list; p; p = p->next)
                                 if (p->have_status) {
                                         ... Исследуют p->status ...
                                 }
                 }
Важно очистить флаг перед исследованием списка; иначе, если сигнал был передан только перед очисткой флага, и после того, как соответствующий элемент списка процесса был проверен, изменение состояния будет неотмеченным, пока следующий сигнал не прибыл, чтобы установить флаг снова. Вы могли бы, конечно, избегать этой проблемы, блокировав сигнал при просмотре списка, но более важно гарантировать правильность, делая все в правильном порядке.

Цикл, который проверяет состояние процесса избегает исследовать p->status, пока он не видит, что состояние было законно сохранено. Он должен удостовериться, что status не может изменяться в середине доступа к нему. Как только p->have_status установлен, это означает что дочерний процесс остановлен или завершен, и в любом случае он не может останавливаться или завершаться снова. См. Раздел 21.4.7.3 [Быстрое Использование], для получения более подробной информации относительно копирования с прерываниями во время доступов к переменной.

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

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

                 sig_atomic_t process_status_change;
                 sig_atomic_t last_process_status_change;
                 . . .
                 {
                         sig_atomic_t prev=last_process_status_change;
                         last_process_status_change =
         process_status_change;
                         if (last_process_status_change != prev) {
                                 struct process *p;
                                 for (p = process_list; p; p = p->next)
                                         if (p->have_status) {
                                                 ... Проверка p->status ...
                                         }
                         }
                 }

Обработка Сигнала и Неповторно используемые Функции

Функции-Обработчики обычно делают не очень много. Самый лучший обработчик - который не делает ничего, но устанавливает внешнюю переменную, которую программа проверяет регулярно, и оставляет всю серьезную работу программе. Это самое лучшее, потому что обработчик может вызываться асинхронно, в непредсказуемое время, возможно в середине системного вызова, или даже между началом и концом оператора Cи, который требует выполнения многократных команд. Изменяемые структуры данных могут быть в несогласованном состоянии, когда вызывается функция обработчика. Даже копирование одной int переменной в другую занимает две команды на большинстве машин.

Это означает, что Вы должны быть очень осторожын относительно того, что Вы делаете в обработчике сигнала.

Быстрый Доступ к данным и Обработка Сигнала

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

Имеются три способа, при помощи которых Вы можете справляться с этой проблемой. Вы можете использовать типы данных, к которым всегда обращаются быстро; Вы можете тщательно упорядочивать их, так что ничего неблагоприятного не случается, если доступ прерван, или Вы можете блокировать все сигналы вокруг любого доступа, который лучше не прерывать (см. Раздел 21.7 [Блокированные Сигналы]).

Проблемы с Немгновенным Доступом

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

                         #include <signal.h>
                         #include <stdio.h>
                         struct two_words { int a, b; } memory;
                         void
                         handler(int signum)
                         {
                                 printf ("%d,%d\n", memory.a, memory.b);
                                 alarm (1);
                         }
                         int
                         main (void)
                         {
                                 static struct two_words zeros = {0,0},
                                                          ones  = {1,1};
                                 signal (SIGALRM, handler);
                                 memory = zeros;
                                 alarm (1);
                                 while (1)
                                 {
                                         memory = zeros;
                                         memory = ones;
                                 }
                         }
Эта программа заполняет память с нулями, еденицами, нулями, еденицами, чередуя все время; тем временем, раз в секунду, обработчик сигнала таймера печатает текущее содержимое. (Вызов printf в обработчике безопасен в этой программе, потому что она конечно не вызывается снаружи обработчика, когда случается сигнал.)

Ясно, эта программа может печатать пару нулей или пару едениц. Но это - не все что она может делать! На большинстве машин сохранение нового значения в памяти занимает несколько команд, и значение сохраняется по одному слову. Если сигнал передан между этими командами, обработчик, может находить, что memory.a - нуль, а memory.b - один (или наоборот).

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

Типы Данных, к которым Быстрый Доступ

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

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

       sig_atomic_t 
Это - целочисленный тип данных. К объектам этого типа всегда обращаются быстро.

Практически, Вы можете считать, что int и другие целочисленные типы не большие, чем int - быстрые.

Вы можете также считать, что к типам pointer быстрый доступ; это очень удобно. Оба этих утверждения верны для всех машин, которые поддерживает библиотека GNU C, и на всех системах POSIX, о которых мы знаем.

Быстрое Использование Шаблонов

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

Прерывание в середине тестирования флага безопасно, потому что либо он распознан отличным от нуля, тогда точное значение не важно, либо он будет отличным от нуля, в следующий раз.

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

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

Иногда Вы можете обеспечивать непрерывный доступ к одному объекту, защищая его использование другим объектом, возможно тем, чей тип гарантирует быстроту. См. Раздел 21.4.5 [Обьединенные Сигналы].

21.5 Примитивы, прерванные Сигналами

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

POSIX определяет один подход: делайте примитивный сбой сразу же. Код ошибки для этого вида отказа - EINTR. Это гибко, но обычно неудобно. Обычно, приложения POSIX, которые используют обработчики сигнала, должны проверить EINTR после каждой библиотечной функции, которая может возвращать его, чтобы пытаться обратиться снова. Часто программисты забывают это проверять, что является общим источником ошибок.

Библиотека GNU обеспечивает удобный способ повторить обращение после временного отказа макрокомандой TEMP_FAILURE_RETRY:

       TEMP_FAILURE_RETRY (expression)  (макрос)
Эта макрокоманда оценивает выражение один раз. Если оно терпит неудачу и код ошибки EINTR, TEMP_FAILURE_RETRY оценивает это снова, и много раз, пока результат не временный отказ.

Значение, возвращенное TEMP_FAILURE_RETRY - любое произведенное выражением значение.

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

Вы можете выбирать любой подход в библиотеке GNU. Если Вы используете sigaction, чтобы установить обработчик сигнала, Вы можете определять, как этот обработчик должен вести себя. Если Вы определяете SA_RESTART флаг, после возврата из этого обработчика продолжится примитив; иначе, возврат из этого обработчика вызовет EINTR. См. Раздел 21.3.5 [Флаги для Sigaction].

Другой способ определять выбор - siginterrupt функцией. См. Раздел 21.9.1 [POSIX против BSD].

Когда Вы не определяете с sigaction или siginterrupt, что специфический обработчик должен делать, он использует заданный по умолчанию выбор. Заданный по умолчанию выбор в библиотеке GNU зависит от возможностей макрокоманд, которые Вы определили. Если Вы определяете _BSD_SOURCE или _GNU_SOURCE перед вызовом сигнала, значение по умолчанию - продолжить примитивы; иначе, значение по умолчанию должно делать сбой с EINTR. (Библиотека содержит альтернативные версии функции signal, и макрокоманды возможностей определяют, которую Вы действительно вызываете.) См. Раздел 1.3.4 [Макрокоманды Возможностей].

Примитивы, на которые воздействует это: close, fcntl (операция F_SETLK), open, read, recv, recvfrom, select, send, sendto, tcdrain, waitpid, wait, и write.

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

Это может вызвать ненадежное поведение на устройствах для записи (включая датаграммный сокет; см. Раздел 11.9 [Датаграммы]), при разбивании одного чтения или записи на два чтения или две записи для двух единиц. Фактически, не имеется никакой проблемы, потому что прерывание после частичной передачи не может случаться на таких устройствах; они всегда передают всю запись в одном пакете, без ожидания, если только передача данных началась.

21.6 Сигналы Производства

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

Передача Сигналов Самому себе

Процесс может посылать себе сигнал функцией raise. Эта функция объявлена в "signal.h".

       int raise (int signum) 
Функция raise посылает сигнал процессу вызова. Она возвращает нуль, если она успешна и значение отличное от нуля, если она терпит неудачу. Единственная причина для отказа - если значение signum недопустимо.
       int gsignal (int signum)  
Функция gsignal функция делает то же самое, что и raise; она нужна только для совместимости с SVID.

Удобное использование raise - воспроизвести заданное по умолчанию поведение сигнала, который Вы обработали. Например, предположите, что пользователь вашей программы печатает символ SUSP (обычно C-z; см. Раздел 12.4.9 [Специальные Символы]) чтобы послать интерактивный сигнал останова (SIGTSTP), и Вы хотите очистить некоторые внутренние буфера данных перед остановкой. Вы можете сделать это примерно так:

                         #include <signal.h>
                         void
                         tstp_handler (int sig)
                         {
                                 signal (SIGTSTP, SIG_DFL);
                                 . . .
                                 raise (SIGTSTP);
                         }
                         void
                         cont_handler (int sig)
                         {
                                 signal (SIGCONT, cont_handler);
                                 signal (SIGTSTP, tstp_handler);
                         }
                         int
                         main (void)
                         {
                                 signal (SIGCONT, cont_handler);
                                 signal (SIGTSTP, tstp_handler);
                                 . . .
                         }
Примечание о переносимости: raise произошла из ANSI C. Более старые системы не могут поддерживать ее, так что ее использование уничтожает возможность переноса. См. Раздел 21.6.2 [Передача сигналов Другомму Процессу].

Передача сигналов Другому Процессу

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

Функция kill объявлена в " signal.h ".
       int kill (pid_t pid, int signum)  (функция)
Функция kill посылает сигнал signum процессу или группе процесса, заданной pid. Кроме сигналов, перечисленных в Разделе 21.2 [Стандартные Сигналы], signum может также иметь нулевое значение, чтобы проверить правильность pid. Pid определяет процесс или группу процесса, как получателя сигнала:
Pid > 0

Процесс, чей идентификатор - pid.

Pid == 0

Все процессы в той же самой группе что и отправитель. Отправитель непосредственно не получает сигнал.

Pid < -1

Группа процесса, чей идентификатор есть -pid.

Pid == -1

Если процесс привилегирован, посылает сигнал всем процессам, кроме некоторых специальных процессов системы. Иначе, посылает сигнал всем процессам с тем же самым эффективным ID пользователя.

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

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

Следующие errno условия ошибки определены для этой функции:

EINVAL

аргумент signum - недопустимое или неподдерживаемое число.

EPERM

Вы не имеете привилегий, чтобы послать сигнал процессу или любому из процессов в группе процесса, именованной pid.

ESCRH

id аргумент не относится к существующему процессу или группе.

       int killpg (int pgid, int signum)  (функция)
Подобна kill, но посылает сигнала группе процесса pgid. Эта функция предусмотрена для совместимости с BSD.

Как простой пример kill, обращение kill (getpid (), sig) имеет тот же самый эффект как raise (sig).

Права для использования kill

Имеются ограничения, которые запрещают Вам использовать kill, чтобы послать сигнал любому процессу.

Они предназначены, чтобы предотвратить антиобщественное поведение типа произвольного уничтожения процессов, принадлежащих другому пользователю. Обычно, kill используется, чтобы передать сигналы между родителем и дочерним процессами, или процессами братьями, и в этих ситуациях Вы обычно имеете право послать сигнал. Единственное общее исключение - когда Вы выполняете программу setuid на дочернем процессе; если программа изменяет реальный UID также как эффективный UID, Вы не можете иметь право, чтобы послать сигнал. Программа su делает это.

Имеет ли процесс право, чтобы послать сигнал другому процессу, определяется пользовательскими ID этих двух процессов. Это понятие обсуждено подробно в Разделе 25.2 [Владелец Процесса].

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

Сигнал SIGCONT - частный случай. Он может быть послан, если отправитель - часть того же самого сеанса что и получатель, независимо от пользовательских ID.

Использование kill для Связи

Вот более длинный пример, показывающий, как сигналы могут использоваться для межпроцессорной связи. Это то, для чего предусмотрены сигналы SIGUSR1 и SIGUSR2. Так как эти сигналы фатальны по умолчанию, процесс, который, как предполагается, получает их, должен обрабатывать их через signal или sigaction.

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

                 #include <signal.h>
                 #include <stdio.h>
                 #include <sys/types.h>
                 #include <unistd.h>
                 volatile sig_atomic_t usr_interrupt = 0;
                 void
                 synch_signal (int sig)
                 {
                         usr_interrupt = 1;
                 }
                 /* Дочерний процесс выполняет эту функцию. */
                 void
                 child_function (void)
                 {
                         printf ("I'm here!!!  My pid is %d.\n",
                                 (int) getpid ());
                         kill (getppid (), SIGUSR1);
                         puts ("Bye, now....");
                         exit (0);
                 }
                 int
                 main (void)
                 {
                         struct sigaction usr_action;
                         sigset_t block_mask;
                         pid_t child_id;
                         sigfillset (&block_mask);
                         usr_action.sa_handler = synch_signal;
                         usr_action.sa_mask = block_mask;
                         usr_action.sa_flags = 0;
                         sigaction (SIGUSR1, &usr_action, NULL);
                 /* Создание дочернего процесса. */
                         child_id = fork ();
                         if (child_id == 0)
                                 child_function ();
                         while (!usr_interrupt)
                                 ;
                         puts ("That's all, folks!");
                         return 0;
                 }
Этот пример использует активное ожидание, которое является плохим, потому что это потеря времени CPU, которое другие программы могли бы иначе использовать. Лучше просить, чтобы система ждала, пока сигнал не прибывает. См. пример в Разделе 21.8 [Ожидание Сигнала].

21.7 Блокированные Сигналы

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

Почему Полезно Блокированиие Сигналов

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

Например, это полезно - для совместного использования данных обработчиком сигнала и остальной частью программы. Если тип данных - не sig_atomic_t (см. Раздел 21.4.7 [Быстрый Доступ к данным]), то обработчик сигнала может выполняться, когда остальная часть программы закончила только половину чтения или запииси данных.

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

Блокирование сигналов также необходимо, когда Вы хотите выполнить некоторое действие только, если сигнал не прибыл. Предположите, что обработчик для сигнала устанавливает флаг типа sig_atomic_t; Вы хотели бы проверить флаг и выполнить действие, если флаг не установлен. Это ненадежно. Предположите, что сигнал ­ передан немедленно после того, как Вы проверяете флаг, но перед последовательным действием: тогда программа выполнит действие, даже если сигнал прибыл.

Единственый способ проверять, надежно ли прибыл ли сигнал, состоит в том, чтобы проверять это в то время, когда сигнал блокирован.

Наборы Сигналов

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

Эти средства объявлены в заглавном файле " signal.h ".

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

Для переносимости, чтобы инициализировать, изменять и читать информацию из объектов sigset_t, используйте только функции, описанные в этом разделе, не пробуйте манипулировать ими непосредственно.

Имеются два способа инициализировать набор сигналов. Вы можете первоначально определить его пустыми через sigemptyset и затем добавлять заданные сигналы индивидуально. Или Вы можете определить его полными через sigfillset и тогда удалять заданные сигналы индивидуально.

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

       int sigemptyset (sigset_t *set)  
Эта функция инициализирует набор наборов сигналов, чтобы исключить все определенные сигналы. Она всегда возвращает 0.
       int sigfillset (sigset_t *set)  
Эта функция инициализирует набор наборов сигналов, чтобы включить все определенные сигналы. Снова, возвращаемое значение - 0.
       int sigaddset (sigset_t *set, int signum) 
Эта функция добавляет сигнал signum к набору наборов сигналов. Все что она делает - изменяет набор; она не блокирует или не открывает никаких сигналов.

Возвращаемое значение - 0 при успехе и -1 при отказе. Следующее errno - условие ошибки определено для этой функции:

EINVAL

аргумент знака не определяет допустимый сигнал.

       int sigdelset (sigset_t *set, int signum) 
Эта функция удаляет сигнал signum из набора сигналов. Возвращаемое значение и условия ошибки - такие же как для sigaddset.

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

       int sigismember (const sigset_t *set, int signum)
Функция sigismember проверяет, является ли сигнал signum элементом набора сигналов. Она возвращает 1, если сигнал находится в наборе, 0 если нет, и -1, если имеется ошибка.

Следующее errno условие ошибки определено для этой функции:

EINVAL

аргумент signum не определяет допустимый сигнал.

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

Набор сигналов, которые в настоящее время блокированы, называется маской сигналов. Каждый процесс имеет собственную маску сигналов. Когда Вы создаете новый процесс (см. Раздел 23.4 [Создание Процесса]), он наследует маску родителя. Вы можете блокировать или открывать сигналы с большей гибкостью, изменяя маску сигналов.

Прототип для sigprocmask функции находится в " signal.h ".

       int sigprocmask (int how, const sigset_t *set, sigset_t *oldset)
Функция Sigprocmask используется, чтобы исследовать или изменять маску сигналов процесса, аргумент how определяет, как изменяется маска сигналов, и должен быть одним из следующих значений:
                 SIG_BLOCK
Блокирует сигналы в set, и добавляет их к существующей маске. Другими словами, новая маска - объединение существующей маски и set.
                 SIG_UNBLOCK
Открывает сигналы в set и удаляет их из существующей маски.
                 SIG_SETMASK
Использует set для маски; игнорируя предыдущее значение маски.

Последний аргумент, oldset, используется, чтобы возвратить информацию относительно старой маски сигналов процесса. Если Вы хотите только изменять маску без того, чтобы рассмотривать ее, передавайте пустой указатель как oldset аргумент. Аналогично, если Вы хотите знать что находится в маске, без того, чтобы заменить ее, передайте пустой указатель для set. Oldset аргумент часто используется, чтобы запомнить предыдущую маску сигналов, чтобы восстановить ее позже.

Если вызов sigprocmask открывает любые отложенные сигналы, то по крайней мере один из этих сигналов будет передан процессу прежде, чем sigprocmask возвртится. Порядок, в котором передаются отложенные сигналы является не определенным, но Вы можете управлять порядком явно, делая несколько обращений к sigprocmask, чтобы открывать различные сигналы по одному. Sigprocmask функция возвращает 0, если она успешна, и -1, в противном случае. Следующие errno условия ошибки определены для этой функции:

EINVAL

Аргумент how недопустим.

Вы не можете блокировать SIGKILL и SIGSTOP, но если набор сигналов включает их, то sigprocmask только игнорируют их вместо того, чтобы возвратить состояние ошибки.

Блокирование для Проверки Наличия Сигнала

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

                 sig_atomic_t flag = 0;
                 int
                 main (void)
                 {
                         sigset_t block_alarm;
                         . . .
                         sigemptyset (&block_alarm);
                         sigaddset (&block_alarm, SIGALRM);
                         while (1)
                         {
                                 sigprocmask (SIG_BLOCK, &block_alarm,
                                                 NULL);
                                 if (flag)
                                 {
                                         actions-if-not-arrived
                                         flag = 0;
                                 }
                                 sigprocmask (SIG_UNBLOCK, &block_alarm,
                                                 NULL);
                                         .       .       .
                         }
                 }

Блокирование Сигналов для Обработчика

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

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

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

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

Вот пример:

                #include <signal.h>
                #include <stddef.h>
                void catch_stop ();
                void
                install_handler (void)
                {
                        struct sigaction setup_action;
                        sigset_t block_mask;
                        sigemptyset (&block_mask);
                        sigaddset (&block_mask, SIGINT);
                        sigaddset (&block_mask, SIGQUIT);
                        setup_action.sa_handler = catch_stop;
                        setup_action.sa_mask = block_mask; 
                        setup_action.sa_flags = 0; 
                        sigaction (SIGTSTP, &setup_action, NULL);
                }
<tscreen><verb>      Это более надежно чем блокирование других сигналов явно в коде
 обработчика. Если Вы блокируете сигналы в обработчике, Вы не может
 избежать по крайней мере короткого интервала в начале обработчика, где
 они еще не блокированы.

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

      В любом случае, когда обработчик возвращается, система
 восстанавливает маску, которая была до обработчика.

<sect2>Прверка Отложенных Сигналов
<p>

      Вы можете выяснять, какие сигналы отложены в любое время, вызывая
 sigpending. Эта функция объявлена в " signal.h ".
<tscreen><verb>       int sigpending (sigset_t *set)  (функция)
Sigpending функция сохраняет информацию относительно отложенных сигналов в set. Если там - отложенный сигнал, который блокирован, то этот сигнал - элемент возвращенного set. (Вы можете проверять является ли специфический сигнал элемент этого set, использующего sigismember; см. Раздел 21.7.2 [Наборы Сигналов].)

Возвращаемое значение - 0 при успехе, и -1 при отказе.

Тестирование отложен ли сигнал полезно не часто. Тестирование сигнала который не блокирован - почти всегда бессмысленно.

Вот пример.

                #include <signal.h>
                #include <stddef.h>
                sigset_t base_mask, waiting_mask;
                sigemptyset (&base_mask);
                sigaddset (&base_mask, SIGINT);
                sigaddset (&base_mask, SIGTSTP);
                sigprocmask (SIG_SETMASK, &base_mask, NULL);
                . . .
                sigpending (&waiting_mask);
                if (sigismember (&waiting_mask, SIGINT)) {
                 /* Пользователь пробовал уничтожать процесс. */
                }
                else if (sigismember (&waiting_mask, SIGTSTP)) { 
                 /*Пользователь пробовал остановить процесс.*/
                }
Не забудьте, что, если имеется задержка некоторого сигнала для вашего процесса, дополнительные сигналы этого же самого типа могут быть отброшены. Например, если сигнал SIGINT отложен, когда прибывает другой сигнал SIGINT, ваша программа будет возможно видеть только один из них, когда Вы откроете этот сигнал.

Примечание Переносимости: функция sigpending новая в POSIX.1. Более старые системы не имеют никакого эквивалентного средства.

Запоминание Сигнала, для отложенного вызова

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

                volatile sig_atomic_t signal_pending;
                volatile sig_atomic_t defer_signal;
                void
                handler (int signum)
                {
                        if (defer_signal)
                                signal_pending = signum;
                        else
                         ... /*"Действительно" обрабатываем сигнал.*/
                }
                . . .
                void
                update_mumble (int frob)
                {
                        defer_signal++;
                        mumble.a = 1;
                        mumble.b = hack ();
                        mumble.c = frob;
                        defer_signal--;
                        if (defer_signal == 0 && signal_pending != 0)
                                raise (signal_pending);
                }
Обратите внимание, как специфический сигнал сохранен в signal_pending. Этим способом, мы можем обрабатывать несколько типов неудобных сигналов.

Мы увеличиваем и уменьшаем defer_signal так, чтобы вложенные критические разделы работали правильно; таким образом, если update_mumble вызывалась с signal_pending, уже отличным от нуля, сигналы будут отсрочены не только внутри update_mumble, но также внутри вызывающего оператора. Вот почему мы не проверяем signal_pending, если defer_signal все еще отличен от нуля.

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

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

                        if (defer_signal == 1 && signal_pending != 0)
                        raise (signal_pending);
                defer_signal--;
то сигнал, прибывающий между условным оператором и оператором уменьшения был бы эффективно "потерян" на неопределенное количество времени. Обработчик просто установил бы defer_signal, но программа, уже проверявшая эту переменную, не будет проверять переменную снова.

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

21.8 Ожидание Сигнала

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

Использование pause

Простой способ ждать прибытия сигнала - вызвать pause.

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

Если сигнал выполняет функцию обработчика, то pause возвращается. Это рассматривается как неудача (так как "успешное" поведение должно было бы приостановить программу навсегда), так что возвращаемое значение -1. Даже если Вы определяете, что другие примитивы должны продолжиться, когда обработчик системы возвращается (см. Раздел 21.5 [Прерванные Примитивы]), это не имеет никакого эффекта на pause; она всегда терпит неудачу, когда сигнал обработан.

Следующие errno условия ошибки определены для этой функции:

EINTR

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

Если сигнал вызывает окончание программы, pause не возвращается (очевидно).

Функция pause объявлена в " unistd.h ".

Проблемы с pause

Простота pause может скрывать серьезные ошибки синхронизации, которые могут привести программу к зависанию.

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

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

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

                if (!usr_interrupt)
                pause ();
                /* работа, после прибытия сигнала. * /
                . . .
Она имеет ошибку: сигнал может прибывать после того, как переменная usr_interrupt проверена, но перед обращением к pause. Если никакие дальнейшие сигналы не прибывают, процесс никогда не выполнится снова.

Вы можете изменять верхнее ограничение ожидания, используя sleep в цикле, вместо того чтобы использовать pause. (См. Раздел 17.4 [Бездействие].) Вот, на что это походит:

                while (!usr_interrupt)
                        sleep (1);
                /* работа, после прибытия сигнала. */
                . . .
Для некоторых целей это достаточно удобно. Но немного более сложно. Вы можете ждать, пока специфический обработчик сигнала не выполнен, надежно, используя sigsuspend.

Использование sigsuspend

Чистый и надежный способ ждать сигнал состоит в том, чтобы блокировать его и тогда использовать sigsuspend.

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

       int sigsuspend (const sigset_t *set)  (функция)
Эта функция заменяет маску сигналов процесса на set и тогда приостанавливает процесс, пока не передан сигнал, чье действие должно завершать процесс или вызывать функцию обработки сигнала. Другими словами, программа действительно будет приостановлена, пока один из сигналов, который - не элемент set, не прибудет.

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

Маска остается set только, пока sigsuspend ждет. Функция sigsuspend всегда восстанавливает предыдущую маску сигналов, когда она возвращается.

Возвращаемое значение и условия ошибки - такие же как для pause.

С sigsuspend, Вы можете заменять pause или цикл sleep в предыдущем разделе кое-чем полностью надежным:

                sigset_t mask, oldmask;
                . . .
                sigemptyset (&mask);
                sigaddset (&mask, SIGUSR1);
                . . .
                / * Ждем получения сигнала. * /
                sigprocmask (SIG_BLOCK, &mask, &oldmask);
                while (!usr_interrupt)
                        sigsuspend (&oldmask);
                sigprocmask (SIG_UNBLOCK, &mask, NULL);
Этот последний фрагмент кода немного сложен. Отметте, что когда sigsuspend возвращается, она сбрасывает маску сигналов процесса к первоначальному значению, в этом случае сигнал SIGUSR1 еще раз блокирован. Второе обращение к sigprocmask необходимо чтобы явно открыть этот сигнал.

21.9 BSD Обработка Сигнала

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

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

POSIX и BSD Средства Обработки Сигналов

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

Средства BSD объявлены в " signal.h ".

21.10 Функция BSD, чтобы Установить Обработчик

       struct sigvec  (тип данных)
Этот тип данных - эквивалент BSD struct sigaction (см. Раздел 21.3.2 [Сложная Обработка Сигнала] ); он используется, чтобы опреде­ лить действия сигнала для sigvec функции. Он содержит следующие эле­ менты:
                                sighandler_t sv_handler
Это - функция обработчика.
                                int sv_mask
Это - маска дополнительных сигналов, которые будут блокированы, в то время как функция обработчика вызывается.
                                int sv_flags
Это - битовая маска, используемая, чтобы определить различные флаги, которые воздействуют на поведение сигнала. Вы можете также обратиться к этому полю как sv_onstack. Эти символические константы могут использоваться, чтобы обеспе­ чить значения для sv_flags поля структуры sigvec. Это поле - значение битовой маски,следовательно Вам необходимо слить флаги, представляющие интерес для Вас вместе через OR.
       int SV_ONSTACK
Если этот бит установлен в sv_flags поле структуры sigvec, это означает - использовать стек сигнала при получении сигнала.
       int SV_INTERRUPT  (макрос)
Если этот бит установлен в sv_flags поле структуры sigvec, это означает что, системные вызовы, прерванные этим видом сигнала не долж­ ны быть перезапущены, если обработчик возвращается; взамен, системные вызовы должны возвратиться с EINTR состоянием ошибки. См. Раздел 21.5 [Прерванные Примитивы].
       int SV_RESETHAND  (макрос)
Если этот бит усткновлен в sv_flags поле структуры sigvec, это означает - сбросить действие для сигнала обратно к SIG_DFL, когда сиг­ нал получен.
       int sigvec (int signum, const struct sigvec *action,struct  sigvec *old_action)
Эта функция - эквивалент sigaction; она устанавливает действие для сигнала signum, возвращая информацию относительно предыдущего действия для этого сигнала в old_action.
       int siginterrupt (int signum, int failflag)  (функция)
Эта функция определяет, что использовать, когда некоторые прими­ тивы прерваны обрабаткой сигнала signum. Если failflag - ложь, то при­ митивы рестартуют после сигнала. Если failflag - истина, обработка signum заставляет эти примитивы терпеть неудачу с кодом ошибки EINTR. См. Раздел 21.5 [Прерванные Примитивы].

Функции BSD для Блокирования Сигналов

       int sigmask (int signum)  (макрос)
Эта макрокоманда возвращает маску сигналов, которая имеет бит для установки сигнала signum. Вы можете слить через OR результаты отдель­ ных обращений к sigmask вместе, чтобы определять больше чем один сиг­ нал. Например,
                (sigmask (SIGTSTP) | sigmask (SIGSTOP)
                        | sigmask (SIGTTIN) | sigmask (SIGTTOU))
определяет маску, которая включает все сигналы останова управления заданиями.

       int sigblock (int mask)  (функция)
Эта функция эквивалентна sigprocmask (см. Раздел 21.7.3 [Маска сигналов Процесса]) с аргументом how - SIG_BLOCK: она добавляет сигна­ лы, заданные маской к набору блокированных сигналов процесса вызова. Возвращаемое значение - предыдущий набор блокированных сигналов.
       int sigsetmask (int mask)  (функция)
Это эквивалент функции sigprocmask (см. Раздел 21.7.3 [Маска сиг­ налов Процесса]) с аргументом how - SIG_SETMASK: она устанавливает маску сигналов вызывающего процесса как mask. Возвращаемое значение ­ предыдущий набор блокированных сигналов.
       int sigpause (int mask)  (функция)
Эта функция - эквивалент sigsuspend (см. Раздел 21.8 [Ожидание Сигнала]): она устанавливает маску сигналов вызывающего процесса как mask, и ждет прибытия сигнала. Она при возвращаении восстанавливает предыдущий набор блокированных сигналов.

Использование Отдельного Стека Сигнала

Стек сигнала - специальная область памяти, которую нужно исполь­ зовать как стек в течение выполнения обработчиков сигнала. Он должен быть довольно большим, чтобы избежать переполнения; макрокоманда SIGS­ TKSZ определяет канонический размер для стеков сигналов. Вы можете ис­ пользовать malloc, чтобы зарезервировать пространство для стека. Вызо­ вите sigaltstack или sigstack, чтобы система использовала это прост­ ранство для стека сигнала.

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

Имеются два интерфейса для сообщения системе использовать отдель­ ный стек сигнала. Sigstack - более старый интерфейс, который исходит из 4.2 BSD. Sigaltstack - более новый интерфейс, и исходит из 4.4 BSD. Интерфейс sigaltstack имеет преимущество - не требуется, чтобы ваша программа знала в каком направлении растет стек, что зависит от специ­ фической машины и операционной системы.

       struct sigaltstack         (тип данных)
Эта структура описывает стек сигнала. Она содержит следующие эле­ менты:
                void *ss_sp
Этим указываем на основание стека сигнала.
                size_t ss_size
- размер (в байтах) стека сигнала, на который указывает "ss_sp ". Вы должны установить здесь - сколько места Вы зарезервирова­ ли для стека. Есть две макрокоманды, определенные в " signal.h " которые Вы должны использовать в вычислении этого размера:
       SIGSTKSZ  
- каноническиий размер для стека сигнала. Он должен быть дос­ таточным для нормальных использований.
       MINSIGSTKSZ
- количество пространства стека сигнала, нужное операционной системе только, чтобы выполнить сигнал. Размер стека сигнала должен быть больший чем этот.

Для большинства случаев SIGSTKSZ для ss_size достаточен. Но Вы можете захотеть использовать различный размер. В этом случае, Вы долж­ ны зарезервировать MINSIGSTKSZ дополнительных байт для стека сигнала и увеличивать ss_size.

                int ss_flags
Это поле содержит поразрядное OR этих флагов:
                                SA_DISABLE
Сообщает системе, что она не должна использовать стек сигнала.
                                SA_ONSTACK
Устанавливается системой, и указывает, что стек сигнала использован в настоящее время.

       int sigaltstack (const struct sigaltstack *stack, struct  sigaltstack *oldstack) (функция)
Sigaltstack функция определяет альтернативный стек для использо­ вания в течение обработки сигнала.

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

Возвращаемое значение - 0 при успехе и -1 при отказе. Если si­ galtstack сбоит, она устанавливает errno как одно из этих значений:

EINVAL

Вы пробовали отключать стек, который был фактически использован в настоящее время.

ENOMEM

Размер альтернативного стека был слишком мал. Он должен быть большее чем MINSIGSTKSZ.

Вот более старый интерфейс sigstack.
       struct sigstack  (тип данных)
Эта структура описывает стек сигнала. Она содержит следующие эле­ менты:
                void *ss_sp
- указатель вершины стека. Если стек растет вниз на вашей машине, он должен указывать на начало области, которую Вы зарезервировали. Если стек растет вверх, он должен указывать на нижнюю часть.
                int ss_onstack
Это поле истинно, если процесс в настоящее время использует этот стек.

       int sigstack (const struct sigstack *stack, struct sigstack  *oldstack) (функция)
Sigstack функция определяет альтернативный стек для использования в течение обработки сигнала.

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

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

Возвращаемое значение - 0 при успехе и -1 при отказе.


Вперед Назад Содержание

Закладки на сайте
Проследить за страницей
Created 1996-2019 by Maxim Chirkov
Добавить, Поддержать, Вебмастеру
Hosting by Ihor