Опубликован: 15.06.2004 | Доступ: свободный | Студентов: 2558 / 712 | Оценка: 4.35 / 3.96 | Длительность: 27:47:00
ISBN: 978-5-9556-0011-6
Лекция 12:

Время и работа с ним

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

/* * * * * * * * * * * * * * * * * * * * */
/* Программа вызывает функции обработки  */
/* и контролирует время их выполнения    */
/* с помощью интервального таймера       */
/* * * * * * * * * * * * * * * * * * * * */

#include <stdio.h>
#include <sys/time.h>
#include <signal.h>
#include <setjmp.h>

/* Период интервального таймера (в секундах) */
#define IT_PERIOD       1

static jmp_buf buf_env; /* Буфер для функций setjmp и longjmp */

static double s;        /* Результат функций обработки данных */

/* Функция обработки срабатывания таймера реального времени */
/* (сигнал SIGALRM)                                         */
static void proc_sigalrm (int dummy) {
 printf ("Текущий результат текущей функции обработки данных: %g\n", s);
 longjmp (buf_env, 1);
}

/* Первая функция обработки данных */
/* (вычисляет ln (2))              */
static void proc_data_1 (void) {
 double d = 1;
 int i;

 s = 0;
 for (i = 1; i <= 100000000; i++) {
    s += d / i;
    d = -d;
 }
}

/* Вторая функция обработки данных */
/* (вычисляет sqrt (2))            */
static void proc_data_2 (void) {
 s = 1;
 do {
    s = (s + 2 / s) * 0.5;
 } while ((s * s - 2) > 0.000000001);
}

int main (void) {
                              /* Массив указателей на функции обработки данных */
 void (*fptrs []) (void) = {proc_data_1, proc_data_2, NULL};
                              /* Указатель на указатель              */
                              /* на текущую функцию обработки данных */
 void (**tfptr) (void);
                              /* Вспомогательная временная переменная */
 void (*tmpfptr) (void);
 struct itimerval itvl;
 struct sigaction sact;
 sigset_t sset;
 int i;

 /* Установим реакцию на сигнал SIGALRM */
 if (sigemptyset (&sset) < 0) {
    perror ("SIGEMPTYSET");
    return (1);
 }

 sact.sa_handler = proc_sigalrm;
 sact.sa_flags = SA_NODEFER;   /* Не блокировать SIGALRM            */
                                         /* в функции обработки этого сигнала */
 sact.sa_mask = sset;
 if (sigaction (SIGALRM, &sact, NULL) < 0) {
    perror ("SIGACTION");
    return (2);
 }

 /* На всякий случай сделаем таймер реального времени периодическим */
 itvl.it_interval.tv_sec = IT_PERIOD;
 itvl.it_interval.tv_usec = 0;

 /* Цикл вызова функций обработки данных. */
 /* Выполним его дважды                   */
 for (i = 0; i < 2; i++) {
    tfptr = fptrs;
    (void) setjmp (buf_env);
    /* Сюда вернется управление после срабатывания таймера */
    while ((tmpfptr = *tfptr++) != NULL) {
      /* Даже если предыдущая функция обработки данных */
      /* закончилась до того, как сработал таймер,     */
      /* обеспечим текущей функции полный интервал     */
      itvl.it_value.tv_sec = IT_PERIOD;
      itvl.it_value.tv_usec = 0;
      if (setitimer (ITIMER_REAL, &itvl, NULL) < 0) {
         perror ("SETITIMER");
         return (3);
      }
      (*tmpfptr) ();
      printf ("Результат текущей функции обработки данных: %g\n", s);
    }
 }

 return 0;
}
Листинг 12.36. Пример программы, использующей интервальные таймеры реального времени для управления ходом выполнения программы.

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

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

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

Во-первых, обратим внимание на использование флага SA_NODEFER, предписывающего не блокировать сигнал при входе в функцию его (сигнала) обработки. Этот флаг необходим, поскольку не происходит нормального выхода из функции proc_sigalrm(), а значит, не происходит и восстановления маски сигналов процесса. В результате, после первого входа в функцию обработки сигнала SIGALRM он рискует остаться заблокированным, вследствие чего процесс не будет извещен о последующих срабатываниях интервального таймера реального времени.

Во-вторых, следует исходить из возможности приостановки выполнения процесса в произвольном месте, в частности, внутри вычисления выражений. Если оформить вызов функции обработки данных в виде инструкции (**tfptr++) (), то нет гарантии, что компилятор не запланирует увеличение указателя tfptr после возврата из функции, выполнение которой может быть прервано срабатыванием таймера. В таком случае управление вернется в начало внутреннего цикла, а указатель не увеличится, что приведет к повторному вызову той же функции обработки данных. Чтобы справиться с этой проблемой, пришлось задействовать вспомогательную временную переменную tmpfptr.

В-третьих, процессу может выделяться сколь угодно малая доля процессорного времени, а потому соотношение между виртуальным и реальным временем оказывается недетерминированным. Если, например, расположить вызов setjmp() после взведения таймера (см. листинг 12.38), то таймер реального времени может сработать до инициализации буфера buf_env, после чего нелокальный переход из функции обработки сигнала приведет к полному хаосу.

if (setitimer (ITIMER_REAL, 
    &itvl, NULL) < 0) {
     . . . 
 }

     . . .

 (void) setjmp (buf_env);
Листинг 12.38. Пример некорректной последовательности взведения таймера и инициализации структуры для нелокального перехода.

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

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

К теме интервальных таймеров логически примыкает функция alarm() (см. листинг 12.39), позволяющая вызывающему процессу заказать доставку сигнала SIGALRM через заданное число секунд реального времени. Если в момент обращения к alarm() предыдущий заказ существовал и еще не был выполнен, он отменяется, а если значение аргумента seconds отлично от нуля, его место занимает новый.

#include <unistd.h>
unsigned alarm (unsigned seconds);
Листинг 12.39. Описание функции alarm().

При наличии предыдущего заказа функция alarm() возвращает число секунд, оставшееся до его выполнения; в противном случае результат равен нулю.

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

Антон Коновалов
Антон Коновалов

В настоящее время актуальный стандарт - это POSIX 2008 и его дополнение POSIX 1003.13
Планируется ли актуализация материалов данного очень полезного курса?