Опубликован: 15.06.2004 | Уровень: специалист | Доступ: платный
Лекция 12:

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

Работа с интервальными таймерами

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

Описываемые далее средства для работы с интервальными таймерами входят в необязательную часть стандарта POSIX-2001, именуемую "X/Open-расширение системного интерфейса" (XSI).

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

ITIMER_REAL

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

ITIMER_VIRTUAL

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

ITIMER_PROF

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

Согласно стандарту POSIX-2001, интервальные таймеры обслуживаются функциями getitimer() и setitimer() (см. листинг 12.32).

#include <sys/time.h>
int getitimer (int timer_id, 
    struct itimerval *cvalue);
int setitimer (int timer_id, 
    const struct itimerval *restrict nvalue, 
    struct itimerval *restrict ovalue);
Листинг 12.32. Описание функций getitimer() и setitimer().

Поддерживаемые стандартом значения аргумента timer_id перечислены выше. Другие характеристики интервальных таймеров задаются в структуре типа itimerval, которая должна содержать по крайней мере следующие поля:

struct timeval it_interval;   
/* Интервал таймера */

struct timeval it_value;      
/* Текущие показания таймера */
/* (ведется обратный отсчет) */

(Напомним, что структура типа timeval была описана выше. Она содержит по крайней мере два поля, задающие время, соответственно, в секундах и микросекундах.)

Значение поля it_value (если оно отлично от нуля) показывает время, оставшееся до срабатывания таймера. После срабатывания таймер запускается вновь с начальным значением поля it_value, равным it_interval (если последнее отлично от нуля).

Установка нулевого значения в поле it_value, независимо от величины it_interval, снимает таймер со взвода. Если сделать нулевым значение it_interval, таймер будет разряжен после очередного срабатывания. Таким способом можно реализовать таймер с ограниченной периодичностью (в частности, одноразовый).

Функция getitimer() запоминает текущие характеристики таймера с заданным идентификатором в структуре, на которую указывает аргумент cvalue. Функция setitimer() - ее можно назвать многоцелевой - взводит или снимает таймер со взвода, устанавливает новые характеристики, пользуясь значением аргумента nvalue, и сохраняет старые по указателю ovalue (если он отличен от NULL ).

Признаком успешного завершения обеих функций является нулевой результат.

Здесь уместно акцентировать внимание на одной тонкости, которую до сих пор мы старательно замалчивали. Поскольку разрешающая способность любых часов конечна, для показаний часов и таймеров можно установить только те значения, которые кратны этой разрешающей способности. Если соответствующие аргументы функций clock_settime(), nanosleep() или setitimer() не удовлетворяют данному условию, производится округление. Для часов используется наилучшее приближение снизу, для длительности приостановки выполнения и таймеров - наилучшее приближение сверху ( стандарт POSIX-2001 требует, чтобы длительности приостановки выполнения и промежутка времени до срабатывания таймера были не меньше заказанных).

Последнее обстоятельство позволяет выяснить размер такта часов реального времени довольно неожиданным образом (впрочем, вполне стандартным и мобильным с точки зрения спецификаций POSIX-2001 ) (см. листинг 12.33).

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа выясняет размер такта часов реального времени */ 
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

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

int main (void) {
 struct itimerval itvl;

 itvl.it_interval.tv_sec = 0;
 itvl.it_interval.tv_usec = 1;
 itvl.it_value.tv_sec = 0;
 itvl.it_value.tv_usec = 0;

 if (setitimer (ITIMER_REAL, &itvl, NULL) < 0) {
    perror ("SETITIMER");
    return (1);
 }

 if (getitimer (ITIMER_REAL, &itvl) < 0) {
    perror ("GETITIMER");
    return (2);
 }

 if (itvl.it_interval.tv_usec < 2) {
    printf ("Не удалось выяснить размер такта часов реального времени\n");
 } else {
    printf ("Размер такта часов реального времени: %ld мксек\n",
              itvl.it_interval.tv_usec);
 }

 return 0;
}
Листинг 12.33. Пример программы, использующей функции getitimer() и setitimer().

Здесь интервальный таймер не взводится (поскольку значение it_value - нулевое) и, соответственно, не срабатывает. Он нужен лишь для того, чтобы операционная система подкорректировала должным образом (с учетом разрешающей способности часов реального времени ) значение поля it_interval.tv_usec. В результате корректировки первоначально присвоенная этому полю единица может превратиться, например, в 10000 (микросекунд), что дает тактовую частоту часов реального времени 100 Гц.

Следующая программа (см. листинг 12.34) сравнивает ход реального и виртуального времени процесса, определяя, сколько реального времени прошло за один квант виртуального (в данном случае квант - это 100000 мксек, т. е. 0.1 сек). Колебания показаний, естественно, вызываются нагрузкой на процессор, индуцируемой другими, параллельно работающими процессами. В принципе, целенаправленная организация подобных колебаний и их анализ могут быть использованы для создания так называемых скрытых каналов по времени (см. [7]).

/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа сравнивает ход реального и виртуального */
/* времени процесса                                  */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
#include <signal.h>

#define TM_BUF_SIZE     128

                /* Массив для хранения показаний часов     */
                /* реального времени в момент срабатывания */
                /* таймера виртуального времени процесса   */
static struct timeval rt_vls [TM_BUF_SIZE];
                /* Указатель на текущий элемент */
                /* массива rt_vls               */
static struct timeval *prt_vls = rt_vls;

/* Функция обработки срабатывания таймеров */
/* (сигналы SIGALRM и SIGVTALRM)           */
static void proc_itexprtn (int signo) {
 struct itimerval itmvl = {{0, 0}, {0, 0}};

 /* Запомним текущее астрономическое время */
 (void) gettimeofday (prt_vls++, NULL);
 if (signo == SIGALRM) {
    /* Сработал таймер реального времени.   */
    /* Разрядим таймер виртуального времени */
    /* и завершимся (с выдачей результатов) */
    (void) setitimer (ITIMER_VIRTUAL, &itmvl, NULL);
    exit (0);
 }
}

/* Функция выдачи данных о ходе реального времени             */
/* на фоне равномерного течения виртуального времени процесса */
static void print_rt_data (void) {
 struct timeval *tprt_vls = rt_vls;
 int i = 0;

 printf ("Прошедшее реальное время за один квант виртуального\n");
 while (++tprt_vls != prt_vls) {
    printf (" %3d %10ld мксек\n", ++i,
         (tprt_vls->tv_sec - (tprt_vls - 1)->tv_sec) * 1000000 +
         (tprt_vls->tv_usec - (tprt_vls - 1)->tv_usec));
 }
}

int main (void) {
 struct itimerval itvl;
 struct sigaction sact;
 sigset_t sset;

 /* Установим реакцию на сигналы SIGALRM и SIGVTALRM.           */
 /* Позаботимся, чтобы функция обработки не могла быть прервана */
 /* срабатыванием другого таймера                               */
 if ((sigemptyset (&sset) < 0) || (sigaddset (&sset, SIGALRM) < 0) ||
      (sigaddset (&sset, SIGVTALRM) < 0)) {
    perror ("SIGEMPTYSET or SIGADDSET");
    return (1);
 }

 sact.sa_handler = proc_itexprtn;
 sact.sa_flags = 0;
 sact.sa_mask = sset;
 if (sigaction (SIGALRM, &sact, NULL) < 0) {
    perror ("SIGACTION-SIGALRM");
    return (2);
 }
 if (sigaction (SIGVTALRM, &sact, NULL) < 0) {
    perror ("SIGACTION-SIGVTALRM");
    return (3);
 }

 /* Зарегистрируем функцию print_rt_data() в atexit() */
 if (atexit (print_rt_data) != 0) {
    perror ("ATEXIT");
    return (4);
 }

 /* Взведем таймер реального времени как одноразовый */
 itvl.it_interval.tv_sec = 0;
 itvl.it_interval.tv_usec = 0;
 itvl.it_value.tv_sec = 10;
 itvl.it_value.tv_usec = 0;
 if (setitimer (ITIMER_REAL, &itvl, NULL) < 0) {
    perror ("SETITIMER-REAL");
    return (5);
 }

 /* Установим начало отсчета для данных о реальном времени */
 (void) gettimeofday (prt_vls++, NULL);

 /* Таймер виртуального времени сделаем периодическим */
 itvl.it_interval.tv_sec = 0;
 itvl.it_interval.tv_usec = 100000;    /* 0.1 сек */
 itvl.it_value.tv_sec = 0;
 itvl.it_value.tv_usec = 100000;
 if (setitimer (ITIMER_VIRTUAL, &itvl, NULL) < 0) {
    perror ("SETITIMER-VIRTUAL");
    return (6);
 }

 /* Убедимся, что пока процесс приостановлен, */
 /* его виртуальное время также стоит         */
 sleep (5);

 /* Нагрузим процессор вычислениями, которые не должны завершиться */
 /* до срабатывания таймера реального времени                      */
 {
    double s = 0;
    double d = 1;
    int i;

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

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

Результаты работы программы могут выглядеть так, как показано в листинге 12.35. Можно видеть, что, во-первых, доля процессорного времени, которая достается процессу, может существенно колебаться (см., например, отсчеты 4, 6 и 8) и, во-вторых, пока выполнение процесса приостановлено, его виртуальное время также стоит (см. инструкцию sleep (5) в листинге 12.34, отсчет 1 в результатах).

Прошедшее реальное время за один квант 
виртуального
1   5255378 мксек
2   249993  мксек
3   250003  мксек
4   100000  мксек
5   250177  мксек
6   859831  мксек
7   100054  мксек
8   729943  мксек
9   580003  мксек
10  99989   мксек
11  580129  мксек
12  429881  мксек
13  99990   мксек
Листинг 12.35. Возможные результаты работы программы, использующей интервальные таймеры реального и виртуального времени.
Антон Коновалов
Антон Коновалов

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