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