10.6.4. Перехват сигналов: sigaction()

10.6.4. Перехват сигналов: sigaction()

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

#include <signal.h> /* POSIX */

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

Аргументы следующие:

int signum

Интересующий сигнал, как в случае с другими функциями обработки сигналов.

const struct sigaction *act

Определение нового обработчика для сигнала signum.

struct sigaction *oldact

Определение текущего обработчика. Если не NULL, система до установки *act заполняет *oldact. *act может быть NULL, в этом случае *oldact заполняется, но больше ничего не меняется.

Таким образом, sigaction() и устанавливает новый обработчик, и получает старый за одно действие. struct sigaction выглядит следующим образом.

/* ПРИМЕЧАНИЕ: Порядок в структуре может варьировать. Могут быть

   также и другие поля! */

struct sigaction {

 sigset_t sa_mask; /* Дополнительные сигналы для блокирования */

 int sa_flags;     /* Контролирует поведение */

 void (*sa_handler)(int);

  /* Может образовать объединение с sa_sigaction */

 void (*sa_sigaction)(int, siginfo_t*, void*);

  /* Может образовать объединение с sa_handler */

}

Поля следующие:

sigset_t sa_mask

Набор дополнительных сигналов для блокирования при запуске функции обработчика. Таким образом, когда вызывается обработчик, общий набор заблокированных сигналов является объединением сигналов в маске процесса, сигналов в act->mask и, если SA_NODEFER сброшен, signum.

int sa_flags

Флаги, контролирующие обработку сигнала ядром. См. обсуждение далее.

void (*sa_handler)(int)

Указатель на «традиционную» функцию обработчика. У нее такой же прототип (возвращаемый тип и список параметров), как у функции обработчика для signal(), bsd_signal() и sigset().

void (*sa_sigaction)(int, siginfo_t*, void*)

Указатель на функцию обработчика «нового стиля». Функция принимает три аргумента, которые вскоре будут описаны.

Которая из функций act->sa_handler и act->sa_sigaction используется, зависит от флага SA_SIGINFO в act->sa_flags. Когда имеется, используется act->sa_sigaction; в противном случае используется act->sa_handler. Как POSIX, так и справочная страница GNU/Linux указывают, что эти два поля могут перекрываться в памяти (т. е. быть частью union). Таким образом, никогда не следует использовать оба поля в одной и той же struct sigaction.

Поле sa_flags составляется с помощью побитового ИЛИ значений одного или более флагов, перечисленных в табл. 10.3.

Таблица 10.3. Значения флагов для sa_flags

Флаг Значение
SA_NOCLDSTOP Этот флаг имеет смысл лишь для SIGCHLD. Когда он установлен, родитель не получает сигнал при остановке порожденною процесса сигналами SIGSTOP, SIGTSTP, SIGTTIN или SIGTTOU. Эти сигналы обсуждаются позже, в разделе 10.8.2
SA_NOCLDWAIТ Этот флаг имеет смысл лишь для SIGCHLD. Его поведение сложно. Мы отложим объяснение на потом, см. раздел 10.8.3
SA_NODEFER Обычно данный сигнал блокируется, когда вызывается обработчик сигнала. Когда установлен один из этих флагов, данный сигнал не блокируется при запуске обработчика SA_NODEFER является официальным именем POSIX данного флага (которое и следует использовать)
SA_NOMASK Альтернативное имя для SA_NODEFER[110]
SA_SIGINFO Обработчик сигнала принимает три аргумента. Как упоминалось, при данном установленном флаге должно использоваться поле sa_sigaction вместо sa_handler.
SA_ONSTACK Это продвинутая возможность. Обработчики сигналов могут вызываться с использованием предоставленной пользователем памяти в качестве «альтернативного стека сигнала». Эта память даётся ядру для подобного использования посредством sigaltstack() (см. sigaltstack(2)). Эта особенность больше не описывается в данной книге
SA_RESETHAND Этот флаг обеспечивает поведение V7: после вызова обработчика восстанавливается действие сигнала по умолчанию. Официальным именем POSIX флага (которое следует использовать) является SA_RESETHAND
SA_ONESHOT Альтернативное имя для SA_RESETHAND.
SA_RESTART Этот флаг предоставляет семантику BSD: системные вызовы, которые могут завершиться с ошибкой EINTR и которые получают этот сигнал, запускаются повторно.

Когда в act->sa_flags установлен флаг SA_SIGINFO, поле act->sa_sigaction является указателем на функцию, объявленную следующим образом:

void action_handler(int sig, siginfo_t *info, void *context) {

 /* Здесь тело обработчика */

}

Структура siginfo_t предоставляет изобилие сведений о сигнале:

/* Определение POSIX 2001. Действительное содержание может на разных системах быть разным. */

typedef struct {

 int si_signo;  /* номер сигнала */

 int si_errno;  /* значение <errno.h> при ошибке */

 int si_code;   /* код сигнала; см. текст */

 pid_t si_pid;  /* ID процесса, пославшего сигнал */

 uid_t si_uid;  /* настоящий UID посылающего процесса */

 void *si_addr; /* адрес вызвавшей ошибку инструкции */

 int si_status; /* значение завершения, может включать death-by-signal */

 long si_band;  /* связывающее событие для SIGPOLL/SIGIO */

 union sigval si_value; /* значение сигнала (расширенное) */

} siginfo_t;

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

Почти все поля предназначены для расширенного использования. Все подробности содержатся в стандарте POSIX и справочной странице sigaction(2). Однако, мы можем описать простое использование поля si_code.

Для SIGBUS, SIGCHLD, SIGFPE, SIGILL, SIGPOLL, SIGSEGV и SIGTRAP поле si_code может принимать любое из набора предопределенных значений, специфичных для каждого сигнала, указывая на причину появления сигнала. Откровенно говоря, детали несколько чрезмерны; повседневному коду на самом деле нет необходимости иметь с ними дела (хотя позже мы рассмотрим значения для SIGCHLD). Для всех остальных сигналов член si_code имеет одно из значений из табл. 10.4.

Таблица 10.4. Значения происхождения сигнала для si_code

Значение Только GLIBC Смысл
SI_ASYNCIO Асинхронный ввод/вывод завершен (расширенный).
SI_KERNEL ? Сигнал послан ядром.
SI_MESGQ Состояние очереди сообщений изменилось (расширенный.)
SI_QUEUE Сигнал послан из sigqueue() (расширенный).
SI_SIGIO ? SIGIO поставлен в очередь (расширенный).
SI_TIMER Время таймера истекло
SI_USER Сигнал послан функцией kill(). raise() и abort() также могут его вызвать, но это не обязательно.

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

Третий аргумент обработчика сигнала с тремя аргументами, void *context, является расширенной возможностью, которая больше не обсуждается в данной книге.

Наконец, чтобы увидеть sigaction() в действии, исследуйте полный исходный код обработчика сигнала для sort.c:

2074 static void

2075 sighandler(int sig)

2076 {

2077 #ifndef SA_NOCLDSTOP /* В системе старого стиля... */

2078  signal(sig, SIG_IGN); /* - для игнорирования sig используйте signal()*/

2079 #endif - /* В противном случае sig автоматически блокируется */

2080

2081  cleanup(); /* Запуск кода очистки */

2082

2083 #ifdef SA_NOCLDSTOP /* В системе в стиле POSIX... */

2084  {

2085   struct sigaction sigact;

2086

2087   sigact.sa_handler = SIG_DFL; /* - Установить действие по умолчанию */

2088   sigemptyset(&sigact.sa_mask); /* - Нет дополнительных сигналов для блокирования */

2089   sigact.sa_flags = 0; /* - Специальные действия не предпринимаются */

2090   sigaction(sig, &sigact, NULL); /* - Поместить на место */

2091  }

2092 #else /* На системе в старом стиле... */

2093  signal(sig, SIG_DFL); /* - Установить действие по умолчанию */

2094 #endif

2095

2096  raise(sig); /* Повторно послать сигнал */

2097 }

Вот код в main(), который помещает обработчик на свое место:

2214 #ifdef SA_NOCLDSTOP /* На системе POSIX... */

2215 {

2216  unsigned i;

2217  sigemptyset(&caught_signals);

2218  for (i = 0; i < nsigs; i++) /* - Блокировать все сигналы */

2219   sigaddset(&caught_signals, sigs[i]);

2220  newact.sa_handler = sighandler; /* - Функция обработки сигнала */

2221  newact.sa_mask = caught_signals; /* - Установить для обработчика маску сигналов процесса */

2222  newact.sa_flags =0; /* - Особых флагов нет */

2223 }

2224 #endif

2225

2226 {

2227  unsigned i;

2228  for (i = 0; i < nsigs; i++) /* Для всех сигналов... */

2229  {

2230   int sig = sigs[i];

2231 #ifdef SA_NOCLDSTOP

2232   sigaction(sig, NULL, &oldact); /* - Получить старый обработчик */

2233   if (oldact.sa_handler != SIG_IGN) /* - Если этот сигнал не игнорируется */

2234    sigaction(sig, &newact, NULL); /* - Установить наш обработчик */

2235 #else

2236   if (signal(sig, SIG_IGN) != SIG_IGN)

2237    signal(sig, sighandler); /* - Та же логика со старым API */

2238 #endif

2239  }

2240 }

Мы заметили, что строки 2216–2219 и 2221 могут быть замещены одним вызовом: sigfillset(&newact.sa_mask);

Мы не знаем, почему код написан именно таким способом.

Интерес представляют также строки 2233–2234 и 2236–2237, которые показывают правильный способ проверки того, игнорируется ли сигнал, и для установки обработчика лишь в том случае, если сигнал не игнорируется.

ЗАМЕЧАНИЕ. Функции API sigaction() и signal() не должны использоваться вместе для одного и того же сигнала. Хотя POSIX идет на большие длинноты, чтобы сначала сделать возможным использование signal(), получить struct sigaction, представляющую диспозицию signal(), и восстановить ее, все равно это плохая мысль. Код будет гораздо проще читать, писать и понимать, если вы используете одну функцию или другую взаимоисключающим образам

Поделитесь на страничке

Следующая глава >

Похожие главы из других книг

Перехват

Из книги Все под контролем: Кто и как следит за тобой автора Гарфинкель Симеон

Перехват Одним из наиболее мощных средств в борьбе с преступлениями, диверсиями и мятежами является возможность перехватывать письменные и устные коммуникации. Именно эту привилегию ФБР отстаивает особенно рьяно.Перехват давно известен в американской истории. В 1624


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

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

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


14.1.2. Перехват вывода программы

Из книги Программирование на языке Ruby [Идеология языка, теория и практика применения] автора Фултон Хэл

14.1.2. Перехват вывода программы Простейший способ перехватить информацию, выведенную программой, — заключить команду в обратные кавычки, например:listing = `ls -l` # Одна строка будет содержать несколько строчек (lines).now = `date`      # "Mon Mar 12 16:50:11 CST 2001"Обобщенный ограничитель %x


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

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

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


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

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

12.2.3. Перехват сигналов Вместо использования функции signal() (чья семантика в процессе эволюции стала неправильной) POSIX-программы регистрируют обработчики сигналов с помощью sigaction().#include <signal.h>int sigaction(int signum, struct sigaction *act, struct sigaction *oact);Этот системный вызов устанавливает


12.2.6. Ожидание сигналов

Из книги Краткое введение в программирование на Bash автора Родригес Гарольд

12.2.6. Ожидание сигналов Когда программа построена преимущественно вокруг сигналов, часто необходимо, чтобы она ожидала появления какого-то сигнала, прежде чем продолжать работу. Системный вызов pause() предоставляет простую возможность для этого.#include <unistd.h>int


12.3.1. Описание сигналов

Из книги Защита от хакеров корпоративных сетей автора Автор неизвестен

12.3.1. Описание сигналов Иногда приложения нуждаются в описании сигнала для отображения пользователю или помещения в журнал. Существуют три способа сделать это (см. главу 9). К сожалению, ни один из них не стандартизован.Самый старый метод предусматривает применение sys_siglist


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

Из книги Linux программирование в примерах автора Роббинс Арнольд

23.3.4. Обработка сигналов Перед тем, как перейти к следующему пункту, нужно еще раз рассмотреть функцию gtk_signal_connect(). Данной функции нужно передать четыре параметра:? GtkObject *object — объект, которому может быть послан сигнал;? const gchar *name — имя сигнала, например, «destroy»;? GtkSignalFunc func


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

Из книги Linux глазами хакера автора Флёнов Михаил Евгеньевич

Перехват сигналов Вы можете использовать встроенную в bash программу trap для перехвата сигналов в своих программах. Это хороший способ изящно завершать работу программы. Например, если пользователь, когда ваша программа работает, нажмет CTRL-C — программе будет отправлен


Глава 11 Перехват сеанса

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

Глава 11 Перехват сеанса В этой главе обсуждаются следующие темы: • Основные сведения о перехвате сеанса • Популярные инструментальные средства перехвата сеанса • Исследование атак типа MITM в зашифрованных соединениях · Резюме · Конспект · Часто задаваемые вопросы


10.2. Действия сигналов

Из книги C++ для начинающих автора Липпман Стенли

10.2. Действия сигналов Каждый сигнал (вскоре мы представим полный список) имеет связанное с ним действие по умолчанию. POSIX обозначает это как диспозицию (disposition) сигнала. Это то действие, которое ядро осуществляет для процесса, когда поступает определенный сигнал. Действие


10.7.1. Перехват соединения

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

10.7.1. Перехват соединения Вспомним полный цикл соединения с FTP-сервером и передачи файлов:1. Соединение с сервером.2. Авторизация.3. Запрос на скачивание файлов.4. Сервер открывает порт и сообщает его клиенту.5. Клиент подключается к указанному порту и получает или передает


14.5.4. Перехват соединения

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

14.5.4. Перехват соединения Атака на компьютеры с помощью перехвата соединения была первый раз применена еще несколько десятков лет назад, и до сих пор единственным эффективным методом борьбы с ней является использование шифрования пакетов.Когда компьютеры по протоколу TCP


26.3.1. Перехват сигналов и выполнение действий

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

26.3.1. Перехват сигналов и выполнение действий А теперь создадим сценарий, выполняющий подсчет итераций до тех пор, пока пользователь не нажмет комбинацию клавиш [Ctrl+C] (сигнал 2). После этого сценарий отобразит сообщение, содержащее номер текущей итерации цикла.В этом


11.3. Перехват исключений

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

11.3. Перехват исключений В языке C++ исключения обрабатываются в предложениях catch. Когда какая-то инструкция внутри try-блока возбуждает исключение, то просматривается список последующих предложений catch в поисках такого, который может его обработать.Catch-обработчик состоит


11.3.4. Перехват всех исключений

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

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