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

UnixForum





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

На главную -> MyLDP -> Электронные книги по ОС Linux
Цилюрик О.И. Модули ядра Linux
Назад Внутренние механизмы ядра Вперед

Часы реального времени (RTC)

Часы реального времени — это сугубо аппаратное расширение, которое принципиально зависит от аппаратной платформы, на которой используется Linux. Это ещё одно расширение службы системных часов, на некоторых архитектурах его может и не быть. Используя такое расширение можно создать ещё одну независимую шкалу отсчётов времени, с которой можно связать измерения, или даже асинхронную активацию действий.

Убедиться наличии такого расширения на используемой аппаратной платформе можно по присутствию интерфейса к таймеру часов реального времени в пространстве пользователя. Такой интерфейс предоставляется (о чём чуть позже) через функции ioctl() драйвера присутствующего в системе устройства /dev/rtc :

$ ls -l /dev/rtc*

	lrwxrwxrwx 1 root root      4 Апр 25 09:52 /dev/rtc -> rtc0
	crw-rw---- 1 root root 254, 0 Апр 25 09:52 /dev/rtc0

В архитектуре Intel x86 устройство этого драйвера называется Real Time Clock (RTC). RTC предоставляет функцию для работы со 114-битовым значением в NVRAM. На входе этого устройства установлен осциллятор с частотой 32768 КГц, подсоединенный к энергонезависимой батарее. Некоторые дискретные модели RTC имеют встроенные осциллятор и батарею, тогда как другие RTC встраиваются прямо в контроллер периферийной шины (например, южный мост) чипсета процессора. RTC возвращает не только время суток, но, помимо прочего, является и программируемым таймером, имеющим возможность посылать системные прерывания (IRQ 8). Частота прерываний варьируется от 2 до 8192 Гц. Также RTC может посылать прерывания ежедневно, наподобие будильника. Все определения находим в <linux/rtc.h>:

	struct rtc_time {
	   int tm_sec;
	   int tm_min;
	   int tm_hour;
	   int tm_mday;
	   int tm_mon;
	   int tm_year;
	   int tm_wday;
	   int tm_yday;
	   int tm_isdst;
	};

Только некоторые важные коды команд ioctl():

	#define RTC_AIE_ON    _IO( 'p', 0x01 ) /* Включение прерывания alarm  */ 
	#define RTC_AIE__OFF  _IO( 'р', 0x02 ) /* ... отключение */ 
	...
	#define RTC_PIE_ON    _IO( 'p', 0x05)   /* Включение периодического прерывания */ 
	#define RTC_PIE_OFF   _IO( 'p', 0x06)   /* ... отключение */ 
	...
	#define RTC_ALM_SET   _IOW( 'p', 0x07, struct rtc_time) /* Установка времени time  */
	#define RTC_ALM_READ  _IOR( 'p', 0x08, struct rtc_time) /* Чтение времени alarm */
	#define RTC_RD_TIME   _IOR( 'p', 0x09, struct rtc_time) /* Чтение времени RTC */ 
	#define RTC_SET_TIME  _IOW( 'p', 0x0a, struct rtc_time) /* Установка времени RTC */ 
	#define RTC_IRQP_READ _IOR( 'p', 0x0b, unsigned long)<> /* Чтение частоты IRQ */
	#define RTC_IRQP_SET  _IOW( 'p', 0x0c, unsigned long)<> /* Установка частоты IRQ */

Пример использования RTC из пользовательской программы для считывания абсолютного значения времени (архив time.tgz):

rtcr.c :

	#include <fcntl.h>
	#include <stdio.h>
	#include <sys/ioctl.h>
	#include <string.h>
	#include <linux/rtc.h>
	
	int main( void ) {
	   int fd, retval = 0;
	   struct rtc_time tm;
	   memset( &tm, 0, sizeof( struct rtc_time ) );
	   fd = open( "/dev/rtc", O_RDONLY );
	   if( fd < 0 ) printf( "error: %m\n" );
	   retval = ioctl( fd, RTC_RD_TIME, &tm );  // Чтение времени RTC
	   if( retval ) printf( "error: %m\n" );
	   printf( "current time: %02d:%02d:%02d\n", tm.tm_hour, tm.tm_min, tm.tm_sec );
	   close( fd );
	   return 0;
	}

$ ./rtcr

	current time: 12:58:13

$ date

	Пнд Апр 25 12:58:16 UTC 2011

Ещё одним примером (по мотивам [5], но сильно переделанным) покажем, как часы RTC могут быть использованы как независимый источник времени в программе, генерирующей периодические прерывания с высокой (значительно выше системного таймера) частотой следования:

rtprd.c :

	#include <stdio.h> 
	#include <linux/rtc.h> 
	#include <sys/ioctl.h> 
	#include <sys/time.h> 
	#include <fcntl.h> 
	#include <pthread.h> 
	#include <linux/mman.h> 
	#include "libdiag.h" 
	
	unsigned long ts0, worst = 0, mean = 0;     // для загрузки тиков 
	unsigned long cali; 
	unsigned long long sum = 0;                 // для накопления суммы 
	int cycle = 0; 
	
	void do_work( int n ) { 
	   unsigned long now = rdtsc(); 
	   now = now - ts0 - cali; 
	   sum += now; 
	   if( now > worst ) { 
	      worst = now;                          // Update the worst case latency 
	      cycle = n; 
	   } 
	   return; 
	} 
	
	int main( int argc, char *argv[] ) { 
	   int fd, opt, i = 0, rep = 1000, nice = 0, freq = 8192; // freq - RTC частота - hz 
	   /* Set the periodic interrupt frequency to 8192Hz 
	      This should give an interrupt rate of 122uS */ 
	   while ( ( opt = getopt( argc, argv, "f:r:n") ) != -1 ) { 
	      switch( opt ) { 
	         case 'f' : if( atoi( optarg ) > 0 ) freq = atoi( optarg ); break; 
	         case 'r' : if( atoi( optarg ) > 0 ) rep = atoi( optarg ); break; 
	         case 'n' : nice = 1; break; 
	         default : 
	            printf( "usage: %s [-f 2**n] [-r #] [-n]\n", argv[ 0 ] ); 
	            exit( EXIT_FAILURE ); 
	      } 
	   }; 
	   printf( "interrupt period set %.2f us\n", 1000000. / freq ); 
	   if( 0 == nice ) { 
	      struct sched_param sched_p;           // Information related to scheduling priority 
	      sched_getparam( getpid(), &sched_p );  // Change the scheduling policy to SCHED_FIFO 
	      sched_p.sched_priority = 50;           // RT Priority 
	      sched_setscheduler( getpid(), SCHED_FIFO, &sched_p ); 
	   } 
	   mlockall( MCL_CURRENT );                // Avoid paging and related indeterminism 
	   cali = calibr( 10000 ); 
	   fd = open( "/dev/rtc", O_RDONLY );       // Open the RTC 
	   unsigned long long prochz = proc_hz(); 
	   ioctl( fd, RTC_IRQP_SET, freq ); 
	   ioctl( fd, RTC_PIE_ON, 0 );             // разрешить периодические прерывания 
	   while ( i++ < rep ) { 
	      unsigned long data; 
	      ts0 = rdtsc(); 
	      // блокировать до следующего периодического прерывния 
	      read( fd, &data, sizeof(unsigned long) ); 
	      // выполнять периодическую работу ... измерять латентность 
	      do_work( i ); 
	   } 
	   ioctl( fd, RTC_PIE_OFF, 0 );           // запретить периодические прерывания 
	   printf( "worst latency was %.2f us (on cycle %d)\n", tick2us( prochz, worst ), cycle ); 
	   printf( "mean latency was %.2f us\n", tick2us( prochz, sum / rep ) ); 
	   exit( EXIT_SUCCESS ); 
	} 

В примере прерывания RTC прерывают блокирующую операцию read() гораздо чаще периода системного тика. Очень показательно в этом примере запуск без перевода процесса (что делается по умолчанию) в реал-тайм диспетчирование (ключ -n), когда дисперсия временной латентности возрастает сразу на 2 порядка (это эффекты вытесняющего диспетчирования, которые должны всегда приниматься во внимание при планировании измерений временных интервалов):

$ sudo ./rtprd

	interrupt period set 122.07 us 
	worst latency was 266.27 us (on cycle 2) 
	mean latency was 121.93 us 

$ sudo ./rtprd -f16384

	interrupt period set 61.04 us 
	worst latency was 133.27 us (on cycle 2) 
	mean latency was 60.79 us 

$ sudo ./rtprd -f16384 -n

	interrupt period set 61.04 us 
	worst latency was 8717.90 us (on cycle 491) 
	mean latency was 79.45 us 

Показанный выше код пространства пользователя в заметной мере проясняет то, как и на каких интервалах могут использоваться часы реального времени. То же, каким образом время RTC считывается в ядре, не скрывается никакими обёртками, и радикально зависит от использованного оборудования RTC. Для наиболее используемого чипа Motorola 146818 (который в таком наименовании давно уже не производится, и заменяется дженериками), можно упоминание соответствующих макросов (и другую информацию для справки) найти в <asm-generic/rtc.h>:

	spin_lock_irq( &rtc_lock ) ; 
	rtc_tm->tm_sec = CMOS_READ( RTC_SECONDS ) ; 
	rtc_tm->tm_min = CMOS_READ( RTC_MINUTES ); 
	rtc_tm->tm_hour = CMOS_READ( RTC_HOURS ); 
	...
	spin_unlock_irq( &rtc_lock ); 

А все нужные для понимания происходящего определения находим в <linux/mc146818rtc.h>:

	#define RTC_SECONDS 0
	#define RTC_SECONDS_ALARM 1
	#define RTC_MINUTES2
	...
	#define CMOS_READ(addr) ({ \ 
	   outb_p( (addr), RTC_PORT(0) ); \ 
	   inb_p( RTC_PORT(1) ); \ 
	}) 
	#define RTC_PORT(x)     (0x70 + (x))
	#define RTC_IRQ 8

- в порт 0х70 записывается номер требуемого параметра, а по порту 0х71 считывается/записывается требуемое значение — так традиционно организуется обмен с данными памяти CMOS.


Предыдущий раздел: Оглавление Следующий раздел:
Таймеры ядра   Время и диспетчирование в ядре