Библиотека сайта rus-linux.net
Цилюрик О.И. Модули ядра 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.
Предыдущий раздел: | Оглавление | Следующий раздел: |
Таймеры ядра | Время и диспетчирование в ядре |