Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

На главную -> MyLDP -> Электронные книги по ОС Linux
Цилюрик О.И. Модули ядра 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)