7.3.7 Время и таймеры POSIX.1b |
Предыдущая Содержание Следующая |
Традиционные обычные таймеры 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
Объясним использование описанных выше интерфейсов на примере. В этом примере мы создаём таймер 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 в качестве первого аргумента. ▪Таймеры, предназначенные для каждого процесса, не наследуются дочерним процессом.
| ||||||||||||||||||||
Предыдущая Содержание Следующая |