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

12.7.1. Получение контекста сигнала

12.7.1. Получение контекста сигнала

Информация о том, как и почему был сгенерирован сигнал, называется контекстом[68] сигнала. Приложения, которые должны видеть этот контекст, используют обработчики сигналов, отличающиеся от нормальных. Они включают два дополнительных параметра — указатель на siginfo_t, предоставляющий контекст сигнала, и указатель на void*, который может быть использован некоторыми низкоуровневыми системными библиотеками[69]. Вот как выглядит полный прототип такого обработчика.

void handler(int signum, siginfo_t *siginfo, void *context);

Приложение должно указать ядру на необходимость передачи полной информации о контексте, устанавливая флаг SA_SIGINFO члена sa_mask структуры struct sigaction, применяемой для регистрации обработчика сигнала. Член sa_handler также не используется, потому что он является указателем на функцию с другим прототипом. Вместо этого новый член, sa_sigaction, указывает на обработчик сигнала с правильным прототипом. Чтобы снизить потребление памяти, sa_handler и sa_sigaction разрешено использовать один и тот же участок памяти, поэтому только один из двух должен применяться в одно и то же время. Чтобы сделать это прозрачным, библиотека С определяет struct sigaction следующим образом.

#include <signal.h>
struct sigaction {
 union {
  __sighandler_t sa_handler;
  __sigaction_t sa_sigaction;
 } __sigaction_handler;
 sigset_t sa_mask;
 unsigned long sa_flags;
};
#define sa_handler __sigaction_handler.sa_handler
#define sa_sigaction __sigaction_handler.sa_sigaction

Использование представленной комбинации объединений и макросов позволяет этим двум членам разделять одну и ту же память без необходимости усложнения с точки зрения приложений.

Структура siginfo_t содержит информацию о том, где и почему был сгенерирован сигнал. Всем сигналам доступны два члена: sa_signo и si_code. Какие другие члены доступны — зависит от конкретного сигнала, и эти члены разделяют память подобно тому, как это делают члены sa_handler и sa_sigaction структуры struct sigaction. Член sa_signo содержит номер доставленного сигнала и всегда равен значению первого параметра, переданного обработчику сигнала, в то время как si_code указывает, почему сигнал был сгенерирован, и изменяется в зависимости от номера сигнала. Для большинства сигналов он может принимать перечисленные ниже значения.[70]

SI_USER

Приложение пространства пользователя вызвало kill() для отправки сигнала. Примечание. Функция sigsend(), включенная в Linux для совместимости с некоторыми системами Unix, также выдает SI_USER.

SI_QUEUE

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

SI_TKILL

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

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

#ifndef SI_TKILL
#define SI_TKILL -6
#endif
SI_TKILL
не специфицирован ни в каком стандарте (хотя допускается ими), поэтому его следует применять осторожно в переносимых программах.

SI_KERNEL

Сигнал сгенерирован ядром.

Когда SIGILL, SIGFPE, SIGSEGV, SIGBUS и SIGCHLD посылаются ядром, то si_code вместо si_kernel принимает значения, перечисленные в табл. 12.3[71].

Таблица 12.3. Значения si_code для специальных сигналов

Сигнал si_code Описание
SIGILL ILL_ILLOPC Неправильный код операции (opcode).
ILL_ILLOPC Неправильный операнд.
ILL_ILLOPC Неправильный режим адресации.
ILL_ILLOPC Неправильная ловушка (trap).
ILL_ILLOPC Привилегированный код операции.
ILL_ILLOPC Привилегированный регистр.
ILL_ILLOPC Внутренняя ошибка стека.
ILL_ILLOPC Ошибка сопроцессора.
SIGFPE FPE_INTDIV Деление целого на ноль.
FPE_INTOVF Переполнение целого.
FPE_FLTDIV Деление числа с плавающей точкой на ноль.
FPE_FLTOVF Переполнение числа с плавающей точкой.
FPE_FLTUND Потеря значимости числа с плавающей точкой.
FPE_FLTRES Неточный результат числа с плавающей точкой.
FPE_FLTINV Неверная операция с плавающей точкой.
FPE_FLTSUB Число с плавающей точкой вне диапазона.
SIGSEGV SEGV_MAPPER Адрес не отображается на объект.
SEGV_ACCERR Неверные права доступа для адреса.
SIGBUS BUS_ADRALN Неверное выравнивание адреса.
BUS_ADRERR Несуществующий физический адрес.
BUS_OBJERR Специфичный для объекта сбой оборудования.
SIGCHLD CLD_EXITED Дочерний процесс завершен.
CLD_KILLED Дочерний процесс уничтожен.
CLD_DUMPED Дочерний процесс уничтожен с выводом дампа памяти в файл.
CLD_TRAPPED Дочерний процесс достиг точки останова.
CLD_STOPPED Дочерний процесс приостановлен.

Чтобы помочь прояснить разные значения, которые может принимать si_code, рассмотрим пример, в котором SIGCHLD генерируется четырьмя разными способами: kill(), sigqueue(), raise() (использует системный вызов tkill()) и созданием дочернего процесса, который немедленно прерывается.

 1: /* sicode.с */
 2:
 3: #include <sys/signal.h>
 4: #include <stdlib.h>
 5: #include <stdio.h>
 6: #include <unistd.h>
 7:
 8: #ifndef SI_TKILL
 9: #define SI_TKILL -6
10: #endif
11:
12: void handler(int signo, siginfo_t *info, void *f ) {
13:  static int count = 0;
14:
15:  printf("перехвачен сигнал, отправленный ");
16:  switch(info->si_code) {
17:  case SI_USER:
18:   printf("kill()n"); break;
19:  case SI_QUEUE:
20:   printf("sigqueue()n"); break;
21:  case SI_TKILL:
22:   printf("tkill() или raise()n"); break;
23:  case CLD_EXITED:
24:   printf ("ядро сообщает, что дочерний процесс завершенn"); exit(0);
25:  }
26:
27:  if (++count == 4) exit(1);
28: }
29:
30: int main() {
31:  struct sigaction act;
32:  union sigval val;
33:  pid_t pid = getpid();
34:
35:  val.sival_int = 1234;
36:
37:  act.sa_sigaction = handler;
38:  sigemptyset(&act.sa_mask);
39:  act.sa_flags = SA_SIGINFO;
40:  sigaction(SIGCHLD, &act, NULL);
41:
42:  kill(pid, SIGCHLD);
43:  sigqueue(pid, SIGCHLD, val);
44:  raise(SIGCHLD);
45:
46:  /* Чтобы получить SIGCHLD от ядра, мы создаем дочерний процесс
47:     и немедленно завершаем его. Обработчик сигнала выйдет после
48:     получения сигнала от ядра, поэтому мы просто засыпаем
49:     на время и позволяем программе прерваться подобным образом. */
50:
51:  if (!fork()) exit(0);
52:  sleep(60);
53:
54:  return 0;
55: }

Если si_code равно SI_USER, SI_QUEUE или SI_TKILL, то доступны два дополнительных члена siginfo_t: si_pid и si_uid, которые представляют идентификатор процесса, пославшего сигнал и действительный идентификатор пользователя этого процесса.

Когда ядром посылается SIGCHLD, доступны члены si_pid, si_status, si_utime и si_stime. Первый из них, si_pid, задает идентификатор процесса, состояние которого изменилось[72]. Информация о новом состоянии доступна как в si_code (как показано в табл. 12.3) и в si_status, что идентично целому значению состояния, возвращаемому семейством функций wait().

Последние два члена, si_utime и si_stime, определяют период времени, которое потрачено дочерним приложением на работу в пользовательском режиме и в режиме ядра, соответственно (это подобно тому, что возвращают вызовы wait3() и wait4() в структуре struct rusage). Это время измеряется в тиках часов, заданных целым числом. Количество тиков в секунду задает макрос _SC_CLK_TCK, определенный в <sysconf.h>.

SIGSEGV, SIGBUS, SIGILL и SIGFPE — все они представляют si_addr, специфицирующий адрес, который вызвал сбой, описанный si code.

Ниже приведен простой пример проверки контекста сигнала. Он устанавливает обработчик сигнала для SIGSEGV, который печатает контекст сигнала и прерывает процесс. Нарушение сегментации генерируется попыткой обращения к NULL.

 1: /* catch-segv.c */
 2:
 3: #include <sys/signal.h>
 4: #include <stdlib.h>
 5: #include <stdio.h>
 6:
 7: void handler(int signo, siginfo_t *info, void *f) {
 8:  printf("перехват");
 9:  if (info->si_signo == SIGSEGV)
10:   printf("segv accessing %p", info->si_addr);
11:  if (info->si_code == SEGV_MAPERR)
12:   printf("SEGV_MAPERR");
13:  printf("n");
14:
15:  exit(1);
16: }
17:
18: int main() {
19:  struct sigactin act;
20:
21:  act.sa_sigaction = handler;
22:  sigemptyset(&act.sa_mask);
23:  act.sa_flags = SA_SIGINFO;
24:  sigaction(SIGSEGV, &act, NULL);
25:
26:  *((int *)NULL) = 1 ;
27:
28:  return 0;
29: }

Оглавление книги


Генерация: 1.203. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз