Библиотека сайта 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) |
