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

Предыдущая  Содержание  Следующая V*D*V

Сигналы расширения POSIX 1003.1b играют очень важную роль в приложениях реального времени. Они используются для уведомления процессов о возникновении асинхронных событий, таких как завершение работы таймера высокого разрешения, завершение асинхронного ввода/вывода, приём сообщения в пустой очереди сообщений POSIX, и так далее. Некоторые преимущества, которыми обладают сигналы реального времени по отношению к нативным сигналам, перечислены в Таблице 7.7. Эти преимущества делают их пригодными для приложений реального времени. Сигнальные интерфейсы реального времени POSIX.1b перечислены в Таблице 7.8.

 

Таблица 7.7 Сравнение сигналов реального времени и нативных сигналов

 

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

Нативные сигналы

Диапазон предназначенных для приложения сигналов от SIGRTMIN до SIGRTMAX. Все сигналы реального времени определяются в этом диапазоне, например, SIGRTMIN + 1, SIGRTMIN + 2, SIGRTMAX – 2, и так далее.

Только два предназначенных для приложения сигнала, SIGUSR1 и SIGUSR2.

Доставка сигналов может иметь приоритет. Чем меньше номер сигнала, тем выше приоритет. Например, если ждут обработки сигналы SIGRTMIN и SIGRTMIN + 1, то первым будет доставлен SIGRTMIN.

Нет приоритета для доставки сигнала.

Отправитель может передать принимающему процессу вместе с сигналом реального времени дополнительную информацию.

Вместе с сигналом нельзя послать дополнительную информацию.

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

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

 

Таблица 7.8 Функции сигналов реального времени POSIX.1b

 

Метод

Описание

sigaction

Регистрация дескриптора сигнала и механизма уведомления.

sigqueue

Отправка процессу сигнала и дополнительной информации.

sigwaitinfo

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

sigtimedwait

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

sigsuspend

Приостановка процесса, пока не получен сигнал.

sigprocmask

Изменение текущей маски блокировки процесса.

sigaddset

Добавление сигнала к набору сигналов.

sigdelset

Удаление сигнала из набора сигналов.

sigemptyset

Очистка набора сигналов от всех сигналов.

sigfillset

Установка всех сигналов в наборе сигналов.

sigismember

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

 

Объясним вышеописанные интерфейсы на примере. В этом примере родительский процесс посылает сигналы реального времени дочернему процессу, а затем они обрабатываются. Пример разделён на две части, как показано на Рисунке 7.4.

 

Рисунок 7.4 Сигналы реального времени: пример приложения.

Рисунок 7.4 Сигналы реального времени: пример приложения.

 

Основное приложение

 

#include <signal.h>

#include <sys/types.h>

#include <unistd.h>

#include <errno.h>

 

int child(void);

int parent(pid_t);

/* Обработчик сигнала */

void rt_handler(int signum, siginfo_t *siginfo,

                void * extra);

int main(){

  pid_t cpid;

  if ((cpid = fork()) == 0)

    child();

  else

    parent(cpid);

}

 

Родительский процесс

 

Родительский процесс использует для отправки дочернему процессу SIGRTMIN и SIGRTMIN + 1 функцию sigqueue. Последний аргумент функции используется, чтобы передавать вместе с сигналом дополнительную информацию.

 

int parent(pid_t cpid){

  union sigval value;

 

  /* -------- НАЧАЛО 1-ой ЧАСТИ  ----------- */

 

  /* Спим, пока запускается дочерний процесс */

  sleep(3);

  /* Дополнительная информация для SIGRTMIN */

  value.sival_int = 1;

  /* Отправка дочернему процессу SIGRTMIN */

  sigqueue(cpid, SIGRTMIN, value);

  /* Отправка дочернему процессу SIGRTMIN+1 */

  sleep(3);

  value.sival_int = 2;

  sigqueue(cpid, SIGRTMIN+1, value);

 

  /* -------- НАЧАЛО 2-ой ЧАСТИ  ----------- */

 

  /* Наконец, посылаем SIGRTMIN ещё раз */

  sleep(3);

  value.sival_int = 3;

  sigqueue(cpid, SIGRTMIN, value);

 

  /* --------- КОНЕЦ 2-ой ЧАСТИ  ----------- */

}

 

Дочерний процесс

 

int child(void){

  sigset_t mask, oldmask;

  siginfo_t siginfo;

  struct sigaction action;

  struct timespec tv;

  int count = 0, recv_sig;

 

Для хранения сигналов, которые необходимо блокировать, мы определяем маску типа sigset_t. Прежде чем продолжить, очищаем маску, вызывая sigemptyset. Эта функция инициализирует маску, чтобы исключить все сигналы. Затем вызывается sigaddset, чтобы добавить к заблокированному набору сигналы SIGRTMIN и SIGRTMIN + 1. Наконец, для блокировки доставки сигнала вызывается sigprocmask. (Мы блокируем эти сигналы в процессе их доставки с помощью функции ожидания  сигнала, вместо того, чтобы использовать обработчик сигнала.)

 

/* -------- НАЧАЛО 1-ой ЧАСТИ  ----------- */

 

/* Очищаем маску */

sigemptyset(&mask);

/* Добавляем к маске SIGRTMIN */

sigaddset(&mask, SIGRTMIN);

/* Добавляем к маске SIGRTMIN+1 */

sigaddset(&mask, SIGRTMIN+1);

 

/*

 * Блокируем доставку сигналов SIGRTMIN, SIGRTMIN+1.

 * После возвращения предыдущее значение

 * заблокированной сигнальной маски хранится в oldmask

 */

sigprocmask(SIG_BLOCK, &mask, &oldmask);

 

Теперь дочерний процесс ждёт доставки SIGRTMIN и SIGRTMIN + 1. Для ожидания блокированных сигналов используются функция sigwaitinfo или sigtimedwait. Набором сигналов для ожидания является первый аргумент. Второй аргумент заполняется любой дополнительной информацией, полученной вместе с сигналом (подробнее об siginfo_t позже). Последним аргументом для sigtimedwait является время ожидания.

 

/* Задаём время ожидания 1 с */

tv.tv_sec = 1;

tv.tv_nsec = 0;

 

/*

 * Ждём доставки сигнала. Мы ожидаем 2 сигнала,

 * SIGRTMIN и SIGRTMIN+1. Цикл завершится, когда

 * будут приняты оба сигнала

 */

while(count < 2){

  if ((recv_sig = sigtimedwait(&mask, &siginfo, &tv)) == -1){

    if (errno == EAGAIN){

      printf("Timed out\n");

      continue;

    }else{

      perror("sigtimedwait");

      return -1;

    }

  }else{

    printf("signal %d received\n", recv_sig);

    count++;

  }

}

/* --------- КОНЕЦ 1-ой ЧАСТИ  ----------- */

 

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

 

struct sigaction {

  void (*sa_handler)(int);

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

  sigset_t sa_mask;

  int sa_flags;

}

 

Полями этой структуры являются:

 

sa_handler: функция регистрации обработчика сигналов для сигналов не реального времени.

sa_sigaction: функция регистрации обработчика сигналов для сигналов реального времени.

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

sa_flags: для сигналов реального времени используется SA_SIGINFO. При получении сигнала, который имеет установленный SA_SIGINFO, вместо sa_handler вызывается функция sa_sigaction.

 

Теперь для обработки сигнала реального времени SIGRTMIN дочерний процесс регистрирует обработчик сигналов rt_handler. Эта функция будет вызываться, когда дочернему процессу доставляется SIGRTMIN.

 

/* -------- НАЧАЛО 2-ой ЧАСТИ  ----------- */

 

/* Устанавливаем SA_SIGINFO */

action.sa_flags = SA_SIGINFO;

/* Очищаем маску */

sigemptyset(&action.sa_mask);

 

/*

 * Регистрируем обработчик сигнала для SIGRTMIN.

 * Обратите внимание, что для регистрации

 * обработчика мы используем интерфейс

 * action.sa_sigaction 

 */

action.sa_sigaction = rt_handler;

if (sigaction(SIGRTMIN, &action, NULL) == -1){

  perror("sigaction");

  return 0;

}

 

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

 

  /* Wait from SIGRTMIN */

  sigsuspend(&oldmask);

 

  /* --------- КОНЕЦ 2-ой ЧАСТИ  ----------- */

}

 

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

Наконец, обработчик сигнала:

 

/* Обработчик сигнала для SIGRTMIN */

void rt_handler(int signum, siginfo_t *siginfo,

                void * extra){

  printf("signal %d received. code = %d, value = %d\n",

           siginfo->si_signo, siginfo->si_code,

           siginfo->si_int);

}

 

Второй аргумент обработчика сигнала имеет тип siginfo_t и содержит всю информацию о полученном сигнале. Эта структура определяется так:

 

siginfo_t {

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

  int si_errno; // Ошибка сигнала

  int si_code;  // Код сигнала

  union {

      ...

    // Сигналы POSIX .1b

    struct {

      pid_t pid;

      uid_t uid;

      sigval_t sigval;

    }_rt;

      ...

  }_sifields

}

 

Чтобы узнать о всех полях этой структуры, обратитесь, пожалуйста, к /usr/include/bits/siginfo.h. Кроме si_signo, si_errno и si_code все остальные поля входят в объединение. Так что следует читать только те поля, которые имеют смысл в данном контексте. Например, поля, приведённые выше, действительны только для сигналов POSIX.1b.

 

si_signo является номером сигнала принятого сигнала. Это то же самое, что и первый аргумент обработчика сигналов.

si_code передаёт источник сигнала. Важные значения si_code для сигналов реального времени перечислены в Таблице 7.9.

si_value это дополнительная информация, передаваемая отправителем.

pid и uid являются идентификатором процесса и идентификатором пользователя отправителя, соответственно.

 

Таблица 7.9 Коды сигналов

 

Код сигнала

Значение

Источник

SI_USER

0

kill, sigsend, или raise

SI_KERNEL

0x80

Ядро

SI_QUEUE

-1

Функция sigqueue

SI_TIMER

-2

Окончание работы таймера POSIX

SI_MESGQ

-3

Изменение состояния очереди сообщений POSIX с не-пусто на пусто

SI_ASYNCIO

-4

Завершение асинхронного ввода-вывода

 

Обращения к вышеописанным поля должны осуществляться с помощью макросов, которые определены в siginfo.h:

 

#define si_value   _sifields._rt._sigval

#define si_int     _sifields._rt._sigval.sival_int

#define si_ptr     _sifields._rt._sigval.sival_ptr

#define si_pid     _sifields._kill._pid

#define si_uid     _sifields._kill._uid

 

Таким образом, для доступа к дополнительной информации, передаваемой отправителем, можно использовать siginfo->si_int и siginfo->si_ptr.

 

Предыдущая  Содержание  Следующая