7.3.7 Время и таймеры POSIX.1b

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

Традиционные обычные таймеры UNIX в Linux, функции setitimer и getitimer, не отвечают требованиям большинства приложений реального времени. Таймеры POSIX.1b имеют следующие преимущества по сравнению с обычными таймерами UNIX.

 

Процесс может иметь несколько таймеров.

Лучшая точность таймеров. Таймеры могут использовать значения с точностью до наносекунд.

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

Таймеры POSIX.1b предоставляют поддержку различных часов, таких как CLOCK_REALTIME, CLOCK_MONOTONIC, и так далее, которые могут иметь различные источники с различным разрешением. Обычный таймер UNIX, с другой стороны, связан с системными часами.

 

Ядро таймеров POSIX.1b представляет собой набор часов, которые используются как привязка ко времени. Linux обеспечивает поддержку следующих часов:

 

CLOCK_REALTIME: общесистемные часы реального времени, видимые для всех процессов, работающих в системе. Часы измеряют количество времени в секундах и наносекундах с начала эпохи (то есть 00:00:00 1 января 1970 по Гринвичу).Разрешение часов равно 1/HZ секунд. Таким образом, если HZ равно 100, то разрешение часов составляет 10 мс. Если HZ равно 1000, то разрешение часов составляет 1 мс. Чтобы узнать значение HZ в вашей системе, посмотрите, пожалуйста, файл <kernel-source>/include/asm/param.h. Так как это время базируются на времени настенных часов, оно может быть изменено.

CLOCK_MONOTONIC: время непрерывной работы системы, видимое всем процессам в системе. В Linux оно измеряется  как количество времени в секундах и наносекундах после загрузки системы. Его разрешение равно 1/HZ с. Его поддержка доступна начиная с ядра версии 2.5 и glibc 2.3.3. Это время не может быть изменено каким-либо процессом.

CLOCK_PROCESS_CPUTIME_ID: часы, измеряющие время работы процесса. Время текущего процесса, потраченное на выполнение в системе, измеряется в секундах и наносекундах. Разрешение равно 1/HZ. Это время может быть изменено.

CLOCK_THREAD_CPUTIME_ID: То же, что и CLOCK_PROCESS_CPUTIME_ID, но для текущего потока.

 

Обычно CLOCK_REALTIME используется для указания абсолютного времени ожидания. CLOCK_MONOTONIC используется для относительного времени ожидания и периодических задач. Поскольку время этих часов не может быть изменено, периодическим задачам не нужно беспокоиться о преждевременном или задержанном пробуждении, которое могло бы произойти с CLOCK_REALTIME. Двое других часов могут использоваться для целей учёта. Интерфейсы времени и таймеров POSIX.1b  перечислены в Таблице 7.10.

 

Таблица 7.10 Функции времени и таймеров POSIX.1b

 

Метод

Описание

clock_settime

Установка значения указанным часам.

clock_gettime

Получение времени.

clock_getres

Получение разрешения (точности) часов.

clock_nanosleep

Приостановка выполнения вызывающего приложения на указанное время.

timer_create

Создание таймера на основе указанного времени.

timer_delete

Удаление таймера.

timer_settime

Установка времени работы таймера.

timer_gettime

Получение текущего значения таймера.

timer_getoverrun

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

 

Объясним использование описанных выше интерфейсов на примере. В этом примере мы создаём таймер POSIX, основанный на CLOCK_MONOTONIC. Это периодический таймер с периодом четыре секунды. По окончании работы таймера происходит уведомление процесса с помощью сигнала реального времени SIGRTMIN. Процесс зарегистрировал обработчик сигнала для SIGRTMIN, который хранит счётчик числа раз завершения работы таймера. Когда счётчик достигает заданного значения, названного в примере как MAX_EXPIRE, таймер останваливается и процесс завершается.

 

#include <unistd.h>

#include <time.h>

#include <signal.h>

 

#define MAX_EXPIRE 10

int expire;

 

void timer_handler(int signo, siginfo_t *info,

                   void *context);

 

int main(){

  struct timespec ts, tm, sleep;

  sigset_t mask;

  siginfo_t info;

  struct sigevent sigev;

  struct sigaction sa;

  struct itimerspec ival;

  timer_t tid;

 

Сначала распечатаем некоторые статистические данные о CLOCK_MONOTONIC. clock_getres даёт разрешение часов, а clock_gettime даёт время работы системы. Обратите внимание, что разрешение CLOCK_MONOTONIC равно 1/HZ.

 

clock_getres(CLOCK_MONOTONIC, &ts);

clock_gettime(CLOCK_MONOTONIC, &tm);

printf("CLOCK_MONOTONIC res: [%d]sec [%d]nsec/n",

                          ts.tv_sec, ts.tv_nsec);

printf("system up time: [%d]sec [%d]nsec\n",

                          tm.tv_sec, tm.tv_nsec);

 

Настраиваем обработчик сигнала для SIGRTMIN. Как упоминалось ранее, по истечении времени работы таймера процесс получит сигнал реального времени SIGRTMIN.

 

/* Мы не хотим никаких блокированных сигналов */

sigemptyset(&mask);

sigprocmask(SIG_SETMASK, &mask, NULL);

 

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

sa.sa_flags = SA_SIGINFO;

sigemptyset(&sa.sa_mask);

sa.sa_sigaction = timer_handler;

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

  perror("sigaction failed");

  return -1;

}

 

Создаём таймер. Второй аргумент timer_create представляет тип желаемого уведомления об окончании работы таймера. Вспомните, пожалуйста, из нашего рассказа об очереди сообщений POSIX, что уведомления бывают двух типов, SIGEV_SIGNAL и SIGEV_THREAD. В случае таймеров POSIX, в качестве механизма уведомления может быть использован любой из них. В этом примере мы используем механизм уведомления SIGEV_SIGNAL.

 

/*

 * По завершении работы таймера должен быть послан

 * сигнал SIGRTMIN с произвольным значением 1

 */

sigev.sigev_notify = SIGEV_SIGNAL;

sigev.sigev_signo = SIGRTMIN;

sigev.sigev_value.sival_int = 1;

 

/*

 * Создаём таймер. Обратите внимание, что если вызов

 * успешен, идентификатор таймера возвращается в

 * третьем аргументе.

 */

if (timer_create(CLOCK_MONOTONIC, &sigev, &tid) == -1){

  perror("timer_create");

  return -1;

}

printf("timer-id = %d\n", tid);

 

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

 

ival.it_value.tv_sec = 5;

ival.it_value.tv_nsec = 0;

ival.it_interval.tv_sec = 4;

ival.it_interval.tv_nsec = 0;

if (timer_settime(tid, 0, &ival, NULL) == -1){

  perror("timer_settime");

  return -1;

}

 

Наконец, ждём истечения времени работы таймера. Если счётчик числа раз окончания работы таймера достигает MAX_EXPIRE, выключаем таймер и выходим.

 

  /* Спим и ждём сигнал */

  for(;;){

    sleep.tv_sec = 3;

    sleep.tv_nsec = 0;

    clock_nanosleep(CLOCK_MONOTONIC, 0, &sleep, NULL);

    printf("woken up\n");

    if (expire >= MAX_EXPIRE){

      printf("Program quitting.\n");

      /*

       * Если it_value == 0, вызываем timer_settime,

       * чтобы отключить таймер

       */

      memset(&ival, 0, sizeof (ival));

      timer_settime(tid, 0, &ival, NULL);

      return 0;

    }

  }

  return 0;

}

 

Наконец, мы имеем timer_handler: вспомните наше обсуждение обработчиков сигналов из Раздела 7.3.6. Второй аргумент обработчика имеет тип siginfo_t, который содержит информацию относительно принимаемого сигнала. В этом случае значением info->si_code является SI_TIMER.

 

void timer_handler(int signo, siginfo_t *info,

                   void *context)

{

  int overrun;

  printf("signal details: signal (%d), code (%d)\n",

                      info->si_signo, info->si_code);

  if (info->si_code == SI_TIMER){

    printf("timer-id = %d \n", info->si_timerid);

    expire++;

 

    /*

     * Спецификация говорит, что в один момент времени

     * для данного таймера только один экземпляр сигнала

     * помещается в очередь процесса. Если таймер, сигнал

     * которого всё еще ожидает доставки, заканчивает

     * работу, сигнал не помещается в очередь и происходит

     * ситуация переполнения таймера. timer_getoverrun

     * возвращает дополнительное число окончаний работы

     * таймера, которые произошли между моментом, когда

     * был сгенерирован сигнал (помещён в очередь) и

     * моментом, когда он был доставлен или принят

     */

    if ((overrun = timer_getoverrun(info->si_timerid)) !=

                                  -1 && overrun != 0){

      printf("timer overrun %d\n", overrun);

      expire += overrun;

    }

  }

}

 

Таймеры высокого разрешения

 

Для часов, о которых говорилось выше, Linux обеспечивает наилучшее разрешение в 1 мс (HZ = 1000). Этого разрешения не достаточно для большинства приложений реального времени, так как они требуют точности порядка микросекунд и наносекунд. Для поддержки таких приложений инженерами MontaVista был начат проект High-Resolution Timers (HRT), Таймеры Высокого Разрешения. HRT являются таймерами POSIX с разрешением до микросекунды. Введены двое дополнительных часов POSIX, CLOCK_REALTIME_HR и CLOCK_MONOTONIC_HR. Они такие же, как их коллеги, не обладающие высокой точностью; отличие в том, что разрешение времени у них имеет порядок микросекунд или наносекунд, в зависимости от генератора частоты для аппаратных часов. На момент написания, поддержка HRT не включена в основное дерево исходных текстов и доступна в виде патча. Более подробно о проекте можно узнать на www.sourceforge.net/projects/high-res-timers.

 

Что следует помнить

 

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

Чтобы деактивировать таймер, вызовите timer_settime со значением члена it_value структуры itimespec, равным нулю.

Таймер может быть периодическим или однократным. Если член it_interval структуры itimerspec во время вызова timer_setime равен нулю, то таймер однократный; в противном случае он является периодическим.

POSIX.1b также обеспечивает функцию nanosleep. Она такая же, как и clock_nanosleep, с CLOCK_REALTIME в качестве первого аргумента.

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

 

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