timerfd_create - таймеры, уведомляющие
timerfd_create(2)
таймеры, уведомляющие
Other Alias
timerfd_settime, timerfd_gettime
ОБЗОР
#include <sys/timerfd.h>
int timerfd_create(int clockid, int flags);
int timerfd_settime(int fd, int flags,
const struct itimerspec *new_value,
struct itimerspec *old_value);
int timerfd_gettime(int fd, struct itimerspec *curr_value);
ОПИСАНИЕ
Эти системные вызовы создают и работают с таймером, доставляя уведомления о
срабатывании через файловый дескриптор. Они предоставляют альтернативу
setitimer(2) или
timer_create(2); их преимущество в том, что за
файловым дескриптором можно следить с помощью
select(2),
poll(2) и
epoll(7).
Использование данных трёх системных вызовов аналогично timer_create(2),
timer_settime(2) и timer_gettime(2) (аналога timer_getoverrun(2)
нет, так как его возможности предоставляет read(2) как описано ниже).
timerfd_create()
Вызов
timerfd_create() создаёт новый объект таймера и возвращает файловый
дескриптор, который ссылается на таймер. В аргументе
clockid задаются
часы, которые используются для хода таймера; значение должно быть
CLOCK_REALTIME или
CLOCK_MONOTONIC.
CLOCK_REALTIME — регулируемые
системные часы.
CLOCK_MONOTONIC — нерегулируемые часы, на которые не
влияют скачкообразные изменения системных часов (например, ручное изменение
системного времени). Текущее значение каждых часов можно получить с помощью
clock_gettime(2).
Начиная с Linux 2.6.27, для изменения поведения timerfd_create() можно
использовать следующие значения flags (через OR):
TFD_NONBLOCK
Устанавливает флаг состояния файла O_NONBLOCK для нового открываемого
файлового дескриптора. Использование данного флага заменяет дополнительные
вызовы fcntl(2) для достижения того же результата.
TFD_CLOEXEC
Устанавливает флаг close-on-exec (FD_CLOEXEC) для нового открытого
файлового дескриптора. Смотрите описание флага O_CLOEXEC в open(2) для
того, чтобы узнать как это может пригодиться.
До Linux 2.6.26 включительно аргумент flags должен быть равен нулю.
timerfd_settime()
Вызов
timerfd_settime() запускает или останавливает таймер, на который
ссылается файловый дескриптор
fd.
В аргументе new_value задаётся начальное срабатывание и интервал
таймера. Для этого аргумента используется структура itimer, содержащая
два поля, каждое из которых, в свою очередь, является структурой
timespec:
struct timespec {
time_t tv_sec; /* секунды */
long tv_nsec; /* наносекунды */
};
struct itimerspec {
struct timespec it_interval; /* интервал для периодического
таймера */
struct timespec it_value; /* первое срабатывание */
};
В new_value.it_value задаётся первое срабатывание таймера, в секундах и
наносекундах. Установка любого из полей new_value.it_value в ненулевое
значение включает таймер. Установка обоих полей new_value.it_value в ноль
выключает таймер.
Установка одного или обоих полей new_value.it_interval в ненулевое
значение задаёт период, в секундах и наносекундах, периодического
срабатывания таймера после первого срабатывания. Если оба поля
new_value.it_interval равны нулю, то таймер срабатывает только один раз,
во время, указанное в new_value.it_value.
Значение аргумента flags может быть 0 — для запуска относительного
таймера (в new_value.it_value задаётся время относительно текущего
значения часов, заданных в clockid), или TFD_TIMER_ABSTIME — для
запуска абсолютного таймера (в new_value.it_value задаётся абсолютное
время часов, заданных в clockid; то есть таймер сработает, когда значение
часов будет равно значению new_value.it_value).
Если old_value не равно NULL, то это указатель на структуру
itimerspec, и он будет использоваться для возврата текущих на момент
вызова настроек таймера; смотрите описание timerfd_gettime() далее.
timerfd_gettime()
Вызов
timerfd_gettime() возвращает в
curr_value, которое указывает на
структуру
itimerspec, текущие настройки таймера, на который ссылается
файловый дескриптор
fd.
В поле it_value возвращается время до следующего срабатывания
таймера. Если оба поля этой структуры равны нулю, то таймер в данный момент
не запущен. Это поле всегда содержит относительное значение, независимо от
того, был ли указан флаг TFD_TIMER_ABSTIME при настройке таймера.
В поле it_interval возвращается интервал таймера. Если оба поля этой
структуры равны нулю, то таймер настроен на однократное срабатывание, на
время, заданное в curr_value.it_value.
Работа с файловым дескриптором таймера
Файловый дескриптор, возвращаемый
timerfd_create(), поддерживает
следующие операции:
read(2)
Если таймер уже сработал один или более раз с момента настройки с помощью
timerfd_settime(), или после последнего успешного read(2), то в буфер,
указанный в read(2), будет возвращено беззнаковое 8-байтное целое
(uint64_t), содержащее количество произошедших срабатываний (возвращаемое
значение хранится в порядке байт узла, то есть родном порядке для целых
чисел машины выполнения).
Если таймер ещё не срабатывал до вызова
read(2), то вызов блокирует
выполнение до следующего срабатывания таймера, или завершается с ошибкой
EAGAIN, если файловый дескриптор был создан неблокирующим (с помощью
вызова
fcntl(2) и операции
F_SETFL с флагом
O_NONBLOCK).
Вызов
read(2) завершится с ошибкой
EINVAL, если размер указанного
буфера будет меньше 8 байт.
poll(2), select(2) (и подобные)
Файловый дескриптор доступен для чтения (в select(2) аргумент readfds;
в poll(2) флаг POLLIN), если произошло одно или более срабатываний
таймера.
Файловый дескриптор также поддерживает другие мультиплексные вызовы:
pselect(2),
ppoll(2) и
epoll(7).
ioctl(2)
Поддерживается следующая команда, относящаяся к timerfd:
TFD_IOC_SET_TICKS (начиная с Linux 3.17)
Корректирует количество истечений таймера, которые произошли. Аргументом
является указатель на ненулевое 8-байтовое целое (uint64_t*), содержащее
новое количество истечений. После установки количества, все ожидающие
таймера пробуждаются. Единственная цель данной команды — восстановить
истечений для отсечки/восстановления. Данная операция доступна только, если
ядро собрано с параметром CONFIG_CHECKPOINT_RESTORE.
close(2)
Если файловый дескриптор больше не требуется, его нужно закрыть. Когда все
файловые дескрипторы, связанные с одним объектом таймера, будут закрыты,
таймер выключается и ядро освобождает его ресурсы.
Поведение при fork(2)
После
fork(2) потомки наследуют копию файлового дескриптора, созданного
timerfd_create(). Файловый дескриптор потомка ссылается на тот же объект
таймера, что и файловый дескриптор его родителя, и операция
read(2) в
потомке будет возвращать информацию о срабатываниях таймера.
Поведение при execve(2)
Файловый дескриптор, созданный
timerfd_create(), сохраняется при
execve(2), и продолжает генерировать срабатывания таймера, если он
включён.
ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
При успешном выполнении
timerfd_create() возвращает новый файловый
дескриптор. При ошибке возвращается -1, и
errno устанавливается в
соответствующее значение.
При успешном выполнении timerfd_settime() и timerfd_gettime()
возвращают 0; в случае ошибки возвращается -1, а errno устанавливается в
соответствующее значение ошибки.
ОШИБКИ
Вызов
timerfd_create() может завершиться со следующими ошибками:
EINVAL
Аргумент clockid не равен CLOCK_MONOTONIC или CLOCK_REALTIME;
EINVAL
Неправильное значение flags или, для Linux 2.6.26 и старее, flags не
равно 0.
EMFILE
Было достигнуто ограничение по количеству открытых файловых дескрипторов на
процесс.
ENFILE
Достигнуто максимальное количество открытых файлов в системе.
ENODEV
Не удалось смонтировать (внутреннее) безымянное устройство inode.
ENOMEM
Недостаточно памяти ядра для создания таймера.
Вызовы timerfd_settime() и timerfd_gettime() могут завершаться со
следующими ошибками:
EBADF
Значение fd не является правильным файловым дескриптором.
EFAULT
Указатели new_value, old_value или curr_value являются
некорректными.
EINVAL
Значение fd не является правильным файловым дескриптором timerfd.
Вызов timerfd_settime() также может завершиться со следующими ошибками:
EINVAL
Значение new_value некорректно инициализировано (одно из tv_nsec вне
диапазона от 0 до 999999999).
EINVAL
Значение flags неверно.
ВЕРСИИ
Данные системные вызовы доступны в Linux начиная с ядра версии
2.6.25. Поддержка в библиотеке glibc появилась в версии 2.8.
СООТВЕТСТВИЕ СТАНДАРТАМ
Данные системные вызовы есть только в Linux.
ДЕФЕКТЫ
В настоящее время
timerfd_create() поддерживает только несколько типов
идентификаторов часов, поддерживаемых
timer_create(2).
ПРИМЕР
Следующая программа создаёт таймер и затем следит за его работой. Программа
получает до трёх аргументов из командной строки. В первом аргументе задаётся
количество секунд до первого срабатывания таймера. Во втором аргументе
задаётся интервал таймера в секундах. В третьем аргументе задаётся сколько
программа должна позволить сработать таймеру до завершения. Второй и третий
аргументы необязательны.
Следующий сеанс работы в оболочке показывает использование программы:
$ a.out 3 1 100
0.000: таймер запущен
3.000: read: 1; всего=1
4.000: read: 1; всего=2
^Z # нажато control-Z для приостанова программы
[1]+ Stopped ./timerfd3_demo 3 1 100
$ fg # возобновление выполнения после нескольких
#секунд
a.out 3 1 100
9.660: read: 5; всего=7
10.000: read: 1; всего=8
11.000: read: 1; всего=9
^C # нажато control-C для приостанова программы
Исходный код программы
#include <sys/timerfd.h>
#include <time.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h> /* определение uint64_t */
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)
static void
print_elapsed_time(void)
{
static struct timespec start;
struct timespec curr;
static int first_call = 1;
int secs, nsecs;
if (first_call) {
first_call = 0;
if (clock_gettime(CLOCK_MONOTONIC, &start) == -1)
handle_error("clock_gettime");
}
if (clock_gettime(CLOCK_MONOTONIC, &curr) == -1)
handle_error("clock_gettime");
secs = curr.tv_sec - start.tv_sec;
nsecs = curr.tv_nsec - start.tv_nsec;
if (nsecs < 0) {
secs--;
nsecs += 1000000000;
}
printf("%d.%03d: ", secs, (nsecs + 500000) / 1000000);
}
int
main(int argc, char *argv[])
{
struct itimerspec new_value;
int max_exp, fd;
struct timespec now;
uint64_t exp, tot_exp;
ssize_t s;
if ((argc != 2) && (argc != 4)) {
fprintf(stderr, "%s нач-сек [интервал макс-сраб]\n",
argv[0]);
exit(EXIT_FAILURE);
}
if (clock_gettime(CLOCK_REALTIME, &now) == -1)
handle_error("clock_gettime");
/* создаём абсолютный таймер CLOCK_REALTIME с начальным
срабатыванием и интервалом, заданными из командной строки */
new_value.it_value.tv_sec = now.tv_sec + atoi(argv[1]);
new_value.it_value.tv_nsec = now.tv_nsec;
if (argc == 2) {
new_value.it_interval.tv_sec = 0;
max_exp = 1;
} else {
new_value.it_interval.tv_sec = atoi(argv[2]);
max_exp = atoi(argv[3]);
}
new_value.it_interval.tv_nsec = 0;
fd = timerfd_create(CLOCK_REALTIME, 0);
if (fd == -1)
handle_error("timerfd_create");
if (timerfd_settime(fd, TFD_TIMER_ABSTIME, &new_value, NULL) == -1)
handle_error("timerfd_settime");
print_elapsed_time();
printf("таймер запущен\n");
for (tot_exp = 0; tot_exp < max_exp;) {
s = read(fd, &exp, sizeof(uint64_t));
if (s != sizeof(uint64_t))
handle_error("read");
tot_exp += exp;
print_elapsed_time();
printf("read: %llu; всего=%llu\n",
(unsigned long long) exp,
(unsigned long long) tot_exp);
}
exit(EXIT_SUCCESS);
}