Содержание


Работа с сигналами в Linux

Часть 2. Дополнительные сведения о сигналах

Comments

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

Этот контент является частью # из серии # статей: Работа с сигналами в Linux

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Работа с сигналами в Linux

Следите за выходом новых статей этой серии.

Сигналы реального времени

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

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

Отличия сигналов реального времени от обычных сигналов

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

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

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

Упорядочивание и постановка в очередь сигналов реального времени

Точные номера сигналов реального времени не специфицированы, но можно быть уверенным в том, что все сигналы с номерами между SIGRTMIN и SIGRTMAX являются сигналами реального времени.

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

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

#include «stdio.h» 
#include «stdlib.h» 
#include «string.h» 
#include «unistd.h» 
#include «sys/signal.h» 

/* Максимальное число сигналов, которые можно принять */ 
#define MAX_SENDED_SIGS 10 

/* Массив, в который записываются номера принятых сигналов */ 
int rec_sig[MAX_SENDED_SIGS]; 
/* Следующий свободный для записи элемент в массиве */ 
unsigned int nfree_elem = 0; 

/* Пишем обработчик сигнала как можно проще! 
 * 
 * В данном случае обработчик сигнала лишь заносит номер принятого  
 * сигнала в массив  
 */ 
void sighandler(int signo) { 
 rec_sig[nfree_elem++] = signo; 
 return; 
} 

int main(int argc, char * argv[]) { 
 sigset_t mask; 
 struct sigaction act; 
 int i; 

 memset(&act, 0, sizeof(act)); 
 sigemptyset(&mask); 

 /* Добавляем в маску те сигналы, которые мы будем  
 * отправлять/принимать 
 */ 
 sigaddset(&mask, SIGRTMIN); 
 sigaddset(&mask, SIGRTMIN + 1); 
 sigaddset(&mask, SIGUSR1); 

 /* Устанавливаем обработчик для наших сигналов. 
 * Добавляем наши сигналы в список блокируемых при вызове  
 * обработчика сигналов – во избежание гонок. 
 */ 
 act.sa_handler = sighandler; 
 act.sa_mask = mask; 
 sigaction(SIGRTMIN, &act, NULL); 
 sigaction(SIGRTMIN + 1, &act, NULL); 
 sigaction(SIGUSR1, &act, NULL); 

 /* Блокируем доставку наших сигналов, чтобы избежать их  
 * немедленной доставки 
 */ 
 sigprocmask(SIG_BLOCK, &mask, NULL); 

 /* Отправляем сигналы (не забываем о блокировке строчкой выше) */ 
 raise(SIGRTMIN); 
 raise(SIGRTMIN + 1); 
 raise(SIGRTMIN); 
 raise(SIGRTMIN + 1); 
 raise(SIGRTMIN); 
 raise(SIGRTMIN); 
 raise(SIGUSR1); 
 raise(SIGUSR1); 

 /* Разблокируем сигналы – все сигналы приходят одновременно */ 
 sigprocmask(SIG_UNBLOCK, &mask, NULL); 

 /* Выводим на экран список принятых сигналов в порядке их поступления */ 
 for(i = 0; i « nfree_elem; ++i) { 
  if (rec_sig[i] == SIGUSR1) { 
   printf("We get SIGUSR1\n"); 
  } else if (rec_sig[i] == SIGRTMIN) { 
   printf("We get SIGRTMIN\n"); 
  } else if (rec_sig[i] == SIGRTMIN + 1) { 
   printf("We get SIGRTMIN + 1\n"); 
  } 
 } 

 return 0; 
}

Программа компилируется командой:

 gcc -o signals filename.c

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

Результат работы программы

   We get SIGUSR1 
   We get SIGRTMIN 
   We get SIGRTMIN 
   We get SIGRTMIN 
   We get SIGRTMIN 
   We get SIGRTMIN + 1 
   We get SIGRTMIN + 1

полностью подтверждает все, что было сказано выше о сигналах – одинаковые сигналы из стандарта POSIX, отосланные друг за другом, сливаются в один сигнал (SIGUSR1), а сигналы реального времени доходят все, без потерь, причем сигналы с меньшими номерами доставляются перед сигналами с большими номерами (упорядочиваются).

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

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

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

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

где указатель на siginfo_t предоставляет доступ к контексту сигнала, а указатель void * context используется некоторыми системными библиотеками, рассмотрение которых выходит за рамки данной статьи.

Поскольку определение обработчика сигнала изменилось, мы не можем использовать старый член структуры sigaction – sa_handler для передачи системному вызову указателя на обработчик. Для этого следует использовать новый, не рассмотренный ранее член структуры – sa_sigaction. Стоит помнить, что и sa_handler, и sa_sigaction используют одну и ту же память (реализовано через union), поэтому нельзя использовать одновременно два способа передачи указателя на обработчик сигнала системному вызову.

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

Структура siginfo_t содержит информацию о том, где и почему был сгенерирован сигнал. Эта структура содержит два обязательных члена: sa_signo и si_code и ряд необязательных членов, зависящих от конкретного сигнала. sa_signo содержит номер доставленного сигнала и всегда равен значению параметра signum обработчика сигнала. Второй член – si_code – содержит код причины генерации сигнала. Для большинства сигналов si_code принимает следующие значения.

  • SI_KERNEL – сигнал сгенерирован ядром.
  • SI_QUEUE – пользовательское приложение вызвало системный вызов sigqueue() для отправки сигнала.
  • SI_TKILL – пользовательское приложение использовало системный вызов tkill().
  • SI_USER – пользовательское приложение вызвало системный вызов kill() для отправки сигнала.

Несколько сигналов, посылаемых ядром (а именно SIGILL, SIGFPE, SIGSEGV, SIGBUS, SIGCHLD), имеют свои значения si_code вместо стандартных. Часть из возможных значений si_code для каждого из этих сигналов перечислена ниже.

  • SIGILL:

    ILL_ILLOPC – неправильный код операции, привилегированный код операции, ошибка сопроцессора.

  • SIGFPE:

    FPE_INTDIV – деление целого на ноль.
    FPE_FLTDIV – деление числа с плавающей точкой на ноль.
    FPE_INTOVF – переполнение целого.
    FPE_FLTOVF – переполнение числа с плавающей точкой.

  • SIGSEGV:

    SEGV_MAPERR – адрес не отображается на объект.
    SEGV_ACCERR – неверные права доступа для адреса.

  • SIGBUS:

    BUS_ADRALN – неверное выравнивание адреса.
    BUS_ADRERR – несуществующий физический адрес.
    BUS_OBJERR – специфичный для объекта сбой оборудования.

  • SIGCHLD:

    CLD_EXITED – дочерний процесс завершен.
    CLD_KILLED – дочерний процесс уничтожен.
    CLD_STOPPED – дочерний процесс приостановлен.

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

 #include «stdio.h» 
 #include «stdlib.h» 
 #include «string.h» 
 #include «sys/signal.h» 

 void sighandler(int signum, siginfo_t * info, void * f) { 
  /* Печатаем данные о сигнале. В принципе, использовать  
  * по определению медленные функции ввода/вывода в обработчиках  
  * сигналов нежелательно, но в тестовых целях можно. 
  */ 
  if (info-»si_signo == SIGSEGV) { 
    printf("We tryed access %p, ", info-»si_addr); 
    switch (info-»si_code) { 
      case SEGV_MAPERR: 
        printf("SEGV_MAPERR\n"); 
        break; 
      case SEGV_ACCERR: 
        printf("SIGV_ACCERR\n"); 
        break; 
      default: 
        printf("unknown si_code\n"); 
    } /* switch() */ 
  } 

  /* При нарушении сегментации лучшим выходом будет аварийный выход */ 
  exit(1); 

  return; 
} 

int main(int argc, char * argv[]) { 
  struct sigaction act; 
  /* Обратимся  по адресу 0x00042; естественно, он не  
  * принадлежит нашей программе 
  */ 
  int * bad_pointer = 0x0042; 

  memset(&act, 0, sizeof(act)); 
  act.sa_sigaction = sighandler; 
  act.sa_flags = SA_SIGINFO; 
  sigaction(SIGSEGV, &act, NULL); 

  *bad_pointer = 666; 

  return 0; 
} 
Программа выводит на терминал то, что и следовало ожидать: 
We tryed access 0x42, SEGV_MAPERR

Отправка данных с сигналом

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

 union sigval { 
   int sigval_int; 
   void * sigval_ptr; 
 };

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

Чтобы привязать к сигналу данные при помощи union sigval, нужно для генерации сигнала использовать функцию sigqueue(), имеющую следующий прототип:

 void * sigqueue(pid_t pid, int signal, const union sigval value),

где pid должен быть корректным идентификатором процесса, отрицательные значения не допускаются. signal – номер посылаемого сигнала; он может быть равен нулю – тогда sigqueue() проверит возможность посылки сигнала процессу-получателю. Последний параметр – value – представляет собой данные, посылаемые с сигналом.

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

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

Механизм сигналов, включающий в себя и сигналы стандарта POSIX, и Real-Time сигналы, представляет собой довольно мощное средство для межпроцессного взаимодействия. В Unix-like-системах сигналы довольно часто используются для того, чтобы сообщить приложению о некоем событии, способном повлиять на работоспособность приложения. Впрочем, рассмотрение остальных аспектов применения сигналов выходит за рамки данного цикла, и для их изучения стоит обратиться к специальной литературе.


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


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux
ArticleID=496001
ArticleTitle=Работа с сигналами в Linux: Часть 2. Дополнительные сведения о сигналах
publish-date=06152010