Библиотека сайта rus-linux.net
Цилюрик О.И. Модули ядра Linux | ||
Назад | Внутренние механизмы ядра | Вперед |
Таймеры ядра
Последним классом рассматриваемых задач относительно времени будут таймерные функции. Понятие таймера существенно шире и сложнее в реализации, чем просто выжидание некоторого интервала времени, как мы рассматривали это ранее. Таймер (экземпляров которых может одновременно существовать достаточно много) должен асинхронно возбудить некоторое предписанное ему действие в указанный момент времени в будущем.
Ядро предоставляет драйверам API таймера: ряд функций для декларации, регистрации и удаления таймеров ядра:
#include <linux/timer.h> struct timer_list { struct list_head entry; unsigned long expires; void (*function)( unsigned long ); unsigned long data; ... }; void init_timer( struct timer_list *timer ); struct timer_list TIMER_INITIALIZER( _function, _expires, _data ); void add_timer( struct timer_list *timer ); void mod_timer( struct timer_list *timer, unsigned long expires ); int del_timer( struct timer_list *timer );
- expires - значение jiffies, наступления которого таймер ожидает для срабатывания (абсолютное время);
- при срабатывании функция function() вызывается с data в качестве аргумента;
- чаще всего data — это преобразованный указатель на структуру;
Функция таймера в ядре выполняется в контексте прерывания (Не в контексте процесса! А конкретнее: в контексте обработчика прерывания системного таймера.), что накладывает на неё дополнительные ограничения:
- Не разрешён доступ к пользовательскому пространству. Из-за отсутствия контекста процесса, нет пути к пользовательскому пространству, связанному с любым определённым процессом.
- Указатель current не имеет смысла и не может быть использован, так как соответствующий код не имеет связи с процессом, который был прерван.
- Не может быть выполнен переход в блокированное состояние и переключение контекста. Код в контексте прерывания не может вызвать schedule() или какую-то из форм wait_event(), и не может вызвать любые другие функции, которые могли бы перевести его в пассивное состояние, семафоры и подобные примитивы синхронизации также не должны быть использованы, поскольку они могут переключать выполнение в пассивное состояние.
Код ядра может понять, работает ли он в контексте прерывания, используя макрос: in_interrupt().
Примечание: утверждается, что а). в системе 512 списков таймеров, каждый из которых с фиксированным expires, б). они, в свою очередь, разделены на 5 групп по диапазонам expires, в). с течением времени (по мере приближения expires) списки перемещаются из группы в группу... Но это уже реализационные нюансы.
Таймеры высокого разрешения
Таймеры высокого разрешения появляются с ядра 2.6.16, структуры представления времени для них определяются в файлах <linux/ktime.h>. Поддержка осуществляется только в тех архитектурах, где есть поддержка аппаратных таймеров высокого разрешения. Определяется новый временной тип данных ktime_t — временной интервал в наносекундном выражении, представление его сильно разнится от архитектуры. Здесь же определяются множество функций установки значений и преобразований представления времени (многие из них определены как макросы, но здесь записаны как прототипы):
ktime_t ktime_set(const long secs, const unsigned long nsecs); ktime_t timeval_to_ktime( struct timeval tv ); struct timeval ktime_to_timeval( ktime_t kt ); ktime_t timespec_to_ktime( struct timespec ts ); struct timespec ktime_to_timespec( ktime_t kt );
Сами операции с таймерами высокого разрешения определяются в <linux/hrtimer.h>, это уже очень напоминает модель таймеров реального времени, вводимую для пространства пользователя стандартом POSIX 1003b:
struct hrtimer { ... ktime_t _expires; enum hrtimer_restart (*function)(struct hrtimer *); ... }
- единственным определяемым пользователем полем этой структуры является функция реакции function, здесь обращает на себя внимание прототип этой функции, которая возвращает:
enum hrtimer_restart { HRTIMER_NORESTART, HRTIMER_RESTART, }; void hrtimer_init( struct hrtimer *timer, clockid_t which_clock, enum hrtimer_mode mode ); int hrtimer_start( struct hrtimer *timer, ktime_t tim, const enum hrtimer_mode mode ); extern int hrtimer_cancel( struct hrtimer *timer ); ... enum hrtimer_mode { HRTIMER_MODE_ABS = 0x0, /* Time value is absolute */ HRTIMER_MODE_REL = 0x1, /* Time value is relative to now */ ... };
Параметр which_clock типа clockid_t, это вещь из области стандартов POSIX, то, что называется стандартом временной базис (тип задатчика времени): какую шкалу времени использовать, из общего числа определённых в <linux/time.h> (часть из них из POSIX, а другие расширяют число определений):
// The IDs of the various system clocks (for POSIX.1b interval timers): #define CLOCK_REALTIME 0 #define CLOCK_MONOTONIC 1 #define CLOCK_PROCESS_CPUTIME_ID 2 #define CLOCK_THREAD_CPUTIME_ID 3 #define CLOCK_MONOTONIC_RAW 4 #define CLOCK_REALTIME_COARSE 5 #define CLOCK_MONOTONIC_COARSE 6
Примечание: Относительно временных базисов в Linux известно следующее:
CLOCK_REALTIME
— системные часы, со всеми их плюсами и минусами. Могут быть переведены вперёд или назад, в этой шкале могут попадаться «вставные секунды», предназначенные для корректировки неточностей представления периода системного тика. Это наиболее используемая в таймерах шкала времени.CLOCK_MONOTONIC
— подобноCLOCK_REALTIME
, но отличается тем, что, представляет собой постоянно увеличивающийся счётчик, в связи с чем, естественно, не могут быть изменены при переводе времени. Обычно это счётчик от загрузки системы.CLOCK_PROCESS_CPUTIME_ID
— возвращает время затрачиваемое процессором относительно пользовательского процесса, время затраченное процессором на работу только с данным приложением в независимости от других задач системы. Естественно, что это базис для пользовательского адресного пространства.CLOCK_THREAD_CPUTIME_ID
— похоже наCLOCK_PROCESS_CPUTIME_ID
, но только отсчитывается время, затрачиваемое на один текущий поток.CLOCK_MONOTONIC_RAW
— то же что иCLOCK_MONOTONIC
, но в отличии от первого не подвержен изменению через сетевой протокол точного времени NTP.
Последние два базиса CLOCK_REALTIME_COARSE и CLOCK_MONOTONIC_COARSE добавлены недавно (2009 год), авторами утверждается (http://lwn.net/Articles/347811/), что они могут обеспечить гранулярность шкалы мельче, чем предыдущие базисы. Работу с различными базисами времени обеспечивают в пространстве пользователя малоизвестные API вида clock_*() (clock_gettext(), clock_nanosleep(), clock_settime(), ... ), в частности, разрешение каждого из базисов можно получить вызовом:
long sys_clock_getres( clockid_t which_clock, struct timespec *tp );
Для наших примеров временной базис таймеров вполне может быть, например, CLOCK_REALTIME или CLOCK_MONOTONIC. Пример использования таймеров высокого разрешения (архив time.tgz) в периодическом режиме может быть показан таким модулем (код только для демонстрации техники написания в этом API, но не для рассмотрения возможностей высокого разрешения):
htick.c :
#include <linux/module.h> #include <linux/version.h> #include <linux/time.h> #include <linux/ktime.h> #include <linux/hrtimer.h> static ktime_t tout; static struct kt_data { struct hrtimer timer; ktime_t period; int numb; } *data; #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) static int ktfun( struct hrtimer *var ) { #else static enum hrtimer_restart ktfun( struct hrtimer *var ) { #endif ktime_t now = var->base->get_time(); // текущее время в типе ktime_t printk( KERN_INFO "timer run #%d at jiffies=%ld\n", data->numb, jiffies ); hrtimer_forward( var, now, tout ); return data->numb-- > 0 ? HRTIMER_RESTART : HRTIMER_NORESTART; } int __init hr_init( void ) { enum hrtimer_mode mode; #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) mode = HRTIMER_REL; #else mode = HRTIMER_MODE_REL; #endif tout = ktime_set( 1, 0 ); /* 1 sec. + 0 nsec. */ data = kmalloc( sizeof(*data), GFP_KERNEL ); data->period = tout; hrtimer_init( &data->timer, CLOCK_REALTIME, mode ); data->timer.function = ktfun; data->numb = 3; printk( KERN_INFO "timer start at jiffies=%ld\n", jiffies );... hrtimer_start( &data->timer, data->period, mode ); return 0; } void hr_cleanup( void ) { hrtimer_cancel( &data->timer ); kfree( data ); return; } module_init( hr_init ); module_exit( hr_cleanup ); MODULE_LICENSE( "GPL" );
Результат:
$ sudo insmod ./htick.ko
$ dmesg | tail -n5
timer start at jiffies=10889067 timer run #3 at jiffies=10890067 timer run #2 at jiffies=10891067 timer run #1 at jiffies=10892067 timer run #0 at jiffies=10893067
$ sudo rmmod htick
Предыдущий раздел: | Оглавление | Следующий раздел: |
Временные задержки | Часы реального времени (RTC) |