Linux 用リアルタイムクロックドライバ ==================================== 全ての PC (Alpha マシンも含みます) には、リアルタイムクロックが組み 込まれています。通常、これらはコンピュータのチップセットに組み込まれ ているのですが、Motorola MC146818 (もしくはそのクローン) がボード上に 搭載されている場合もあります。このクロックは、コンピュータの電源が 落ちているあいだも、時を刻み続けています。 リアルタイムクロックは、2Hz という低速から比較的高速の 8192Hz までの 範囲にある周波数 ――但し、2 の累乗値の周波数―― で発生するシグナルを 生成するのにも使うことができます。このシグナルは、割込み番号 8 で通知 されます (まさにこのために IRQ 8 が使われているのです!) 。設定された 時刻になったときに IRQ 8 を発生させる 24 時間アラームとして使うことも できます。プログラミング可能な 3 つの値の何らかの組み合わせ ――例えば、 毎『時』三十『分』三十『秒』―― をチェックするためだけにアラームを 用いることもできます。更新クロックのたびに割込みを発生させるようにす れば、1Hz のシグナルを生成させることもできます。 割込みは、unsigned long 型の変数として /dev/rtc (メジャー番号 10, マイ ナー番号 135, 読込み専用キャラクタデバイス) 経由で通知されます。下位の 1 バイトで、発生した割込みの種類 (「更新が行われた」、「アラームが鳴った」、 もしくは「周期」) を示し、残りの複数バイトで、最後の読込み処理以降に 発生した割込み回数を記録しています。/proc ファイルシステムが有効ならば、 仮想ファイル /proc/driver/rtc からステータス情報を取得できます。ドラ イバは、一度に一つのプロセスしか /dev/rtc インターフェースをオープン できないようにするため、ロック機能を備えています。 ユーザプロセスは、/dev/rtc を read(2) するか select(2) することによって、 割込みを監視することができます。read も select も、次の割込みが発生する までユーザプロセスをブロック/停止させます。gettimeofday などでポーリング 処理をして CPU パワーを使い果たすようなことはしたくないけれども、それでも かなりの頻度でデータ取得処理をおこないたい、というような場合には、この 機能が役に立ちます。 RTC を高い周波数に設定しているか、もしくは負荷が高い状態のときには、 いわゆる割込みの累積が発生してしまっていないかどうかを確認するため、 最後に読出し処理をおこなった時点からの割込み受信回数をチェックした ほうがよいでしょう。参考までに言いますと、/dev/rtc を読み出すタイト ループを実行する典型的な 486/33Hz システムでは、1024Hz より高い周波 数の場合、ときおり割込みの累積が発生してしまいます (つまり、最後の 読込み処理時から 2 つ以上の IRQ イベントが発生している) 。ですので、 読み出した値の上位バイトはチェックすべきでしょう。特に、通常のタイマ 割込みの周波数 (100Hz) よりも高い周波数の場合は、是非ともチェック してください。 64Hz より大きい割込み周波数を設定したり有効にしたりすることが許されて いるのは、root のみです。これは若干保守的過ぎるような気もしますが、 しかしながら、IRQ の頻発がパフォーマンスに悪影響を与える可能性のある 386SX/16Hz のような低速マシンで、IRQ を大量発生させようとする悪意の ユーザがいないとも限りません。なお、このようなパフォーマンスへの 影響を最小化するため、割込みハンドラはほんの数行のコードのみで構成 されています。 また、カーネル時刻が外部ソースと同期している場合、カーネルは 11 分ごとに CMOS へ時刻を書き込みます。この処理をおこなっているあいだは、RTC 周期 割込みは一時的にオフにされています。RTC を使って重要な処理をおこなって いる場合は、このことに留意しておいてください。外部ソース (NTP など) と カーネル時刻を同期させていない場合は、カーネルは RTC に一切関知しない ので、アプリケーション専用のデバイスとして RTC を排他的に使用することが できます。 include/linux/rtc.h にリストアップされている様々な ioctl(2) 呼出しを 使い、アラームや割込み周波数を RTC にプログラミングすることができます。 ioctl() などについて 50 ページも記述するよりも、それらの使い方やドラ イバの機能を示す小さなテストプログラムを提示したほうが有益だと思います。 このドライバを使うアプリケーションを作成することに興味のある人々にとっ ては、コードのほうがずっと役に立つでしょう。 Paul Gortmaker 翻訳団体:JF プロジェクト < http://www.linux.or.jp/JF/ > 翻訳者:川崎 貴彦 < takahiko@hakubi.co.jp > 校正者:野本 浩一 < hng@ps.ksky.ne.jp > -------------------- 8< ---------------- 8< ----------------------------- /* * Real Time Clock Driver Test/Example Program * * Compile with: * gcc -s -Wall -Wstrict-prototypes rtctest.c -o rtctest * * Copyright (C) 1996, Paul Gortmaker. * * Released under the GNU General Public License, version 2, * included herein by reference. * */ #include #include #include #include #include #include #include #include int main(void) { int i, fd, retval, irqcount = 0; unsigned long tmp, data; struct rtc_time rtc_tm; fd = open ("/dev/rtc", O_RDONLY); if (fd == -1) { perror("/dev/rtc"); exit(errno); } fprintf(stderr, "\n\t\t\tRTC Driver Test Example.\n\n"); /* Turn on update interrupts (one per second) */ retval = ioctl(fd, RTC_UIE_ON, 0); if (retval == -1) { perror("ioctl"); exit(errno); } fprintf(stderr, "Counting 5 update (1/sec) interrupts from reading /dev/rtc:"); fflush(stderr); for (i=1; i<6; i++) { /* This read will block */ retval = read(fd, &data, sizeof(unsigned long)); if (retval == -1) { perror("read"); exit(errno); } fprintf(stderr, " %d",i); fflush(stderr); irqcount++; } fprintf(stderr, "\nAgain, from using select(2) on /dev/rtc:"); fflush(stderr); for (i=1; i<6; i++) { struct timeval tv = {5, 0}; /* 5 second timeout on select */ fd_set readfds; FD_ZERO(&readfds); FD_SET(fd, &readfds); /* The select will wait until an RTC interrupt happens. */ retval = select(fd+1, &readfds, NULL, NULL, &tv); if (retval == -1) { perror("select"); exit(errno); } /* This read won't block unlike the select-less case above. */ retval = read(fd, &data, sizeof(unsigned long)); if (retval == -1) { perror("read"); exit(errno); } fprintf(stderr, " %d",i); fflush(stderr); irqcount++; } /* Turn off update interrupts */ retval = ioctl(fd, RTC_UIE_OFF, 0); if (retval == -1) { perror("ioctl"); exit(errno); } /* Read the RTC time/date */ retval = ioctl(fd, RTC_RD_TIME, &rtc_tm); if (retval == -1) { perror("ioctl"); exit(errno); } fprintf(stderr, "\n\nCurrent RTC date/time is %d-%d-%d, %02d:%02d:%02d.\n", rtc_tm.tm_mday, rtc_tm.tm_mon + 1, rtc_tm.tm_year + 1900, rtc_tm.tm_hour, rtc_tm.tm_min, rtc_tm.tm_sec); /* Set the alarm to 5 sec in the future, and check for rollover */ rtc_tm.tm_sec += 5; if (rtc_tm.tm_sec >= 60) { rtc_tm.tm_sec %= 60; rtc_tm.tm_min++; } if (rtc_tm.tm_min == 60) { rtc_tm.tm_min = 0; rtc_tm.tm_hour++; } if (rtc_tm.tm_hour == 24) rtc_tm.tm_hour = 0; retval = ioctl(fd, RTC_ALM_SET, &rtc_tm); if (retval == -1) { perror("ioctl"); exit(errno); } /* Read the current alarm settings */ retval = ioctl(fd, RTC_ALM_READ, &rtc_tm); if (retval == -1) { perror("ioctl"); exit(errno); } fprintf(stderr, "Alarm time now set to %02d:%02d:%02d.\n", rtc_tm.tm_hour, rtc_tm.tm_min, rtc_tm.tm_sec); /* Enable alarm interrupts */ retval = ioctl(fd, RTC_AIE_ON, 0); if (retval == -1) { perror("ioctl"); exit(errno); } fprintf(stderr, "Waiting 5 seconds for alarm..."); fflush(stderr); /* This blocks until the alarm ring causes an interrupt */ retval = read(fd, &data, sizeof(unsigned long)); if (retval == -1) { perror("read"); exit(errno); } irqcount++; fprintf(stderr, " okay. Alarm rang.\n"); /* Disable alarm interrupts */ retval = ioctl(fd, RTC_AIE_OFF, 0); if (retval == -1) { perror("ioctl"); exit(errno); } /* Read periodic IRQ rate */ retval = ioctl(fd, RTC_IRQP_READ, &tmp); if (retval == -1) { perror("ioctl"); exit(errno); } fprintf(stderr, "\nPeriodic IRQ rate was %ldHz.\n", tmp); fprintf(stderr, "Counting 20 interrupts at:"); fflush(stderr); /* The frequencies 128Hz, 256Hz, ... 8192Hz are only allowed for root. */ for (tmp=2; tmp<=64; tmp*=2) { retval = ioctl(fd, RTC_IRQP_SET, tmp); if (retval == -1) { perror("ioctl"); exit(errno); } fprintf(stderr, "\n%ldHz:\t", tmp); fflush(stderr); /* Enable periodic interrupts */ retval = ioctl(fd, RTC_PIE_ON, 0); if (retval == -1) { perror("ioctl"); exit(errno); } for (i=1; i<21; i++) { /* This blocks */ retval = read(fd, &data, sizeof(unsigned long)); if (retval == -1) { perror("read"); exit(errno); } fprintf(stderr, " %d",i); fflush(stderr); irqcount++; } /* Disable periodic interrupts */ retval = ioctl(fd, RTC_PIE_OFF, 0); if (retval == -1) { perror("ioctl"); exit(errno); } } fprintf(stderr, "\n\n\t\t\t *** Test complete ***\n"); fprintf(stderr, "\nTyping \"cat /proc/interrupts\" will show %d more events on IRQ 8.\n\n", irqcount); close(fd); return 0; } /* end main */ -------- リアルタイムクロックドライバ・テストプログラム日本語版 -------- /* * リアルタイムクロックドライバ * テストプログラム日本語版 * * * Copyright (C) 2002, Takahiko KAWASAKI. * * * Paul Gortmaker さんが書かれた 'Real Time * Clock Driver Test/Example Program' を、 * 日本語用に読みやすく書きかえたものです。 * * 更新割込み、アラーム、周期割込み、の例は、 * それぞれ、update_interrupt_test(), * alarm_test(), periodic_interrupt_test() * にあります。 * * GNU General Public License, version 2 で * ライセンスされます。 */ /* * [ 2.4.17 時点の include/linux/rtc.h より ] * * RTC_AIE_ON アラーム割込みを有効にする * RTC_AIE_OFF アラーム割込みを無効にする * RTC_UIE_ON 更新割込みを有効にする * RTC_UIE_OFF 更新割込みを無効にする * RTC_PIE_ON 周期割込みを有効にする * RTC_PIE_OFF 周期割込みを無効にする * RTC_WIE_ON ウォッチドッグ割込みを有効にする * RTC_WIE_OFF ウォッチドッグ割込みを有効にする * * RTC_ARM_SET アラーム時刻をセットする * RTC_ARM_READ アラーム時刻を読み出す * RTC_RD_TIME RTC 時刻を読み出す * RTC_SET_TIME RTC 時刻をセットする * RTC_IRQP_READ IRQ 周波数を読み出す * RTC_IRQP_SET IRQ 周波数をセットする * RTC_EPOCH_READ エポック時刻を読み出す * RTC_EPOCH_SET エポック時刻をセットする * * RTC_WKALM_SET 起床アラームをセットする * RTC_WKALM_RD 起床アラームを取得する */ /**************************************** ヘッダファイル ****************************************/ #include #include #include #include #include #include #include #include #include #include /**************************************** static 関数プロトタイプ宣言 ****************************************/ static void do_tests(void); static void perror_exit(const char *msg); static void print_msg(const char *fmt, ...); /* 更新割込みテスト */ static void update_interrupt_test(void); static void count_by_reading(void); static void count_by_selecting(void); /* アラームテスト */ static void alarm_test(void); static void set_alarm(int sec); static void read_current_rtc(struct rtc_time *rtc_tm); static void add_sec_to_rtc_time(struct rtc_time *rtc_tm, int sec); /* 周期割込みテスト */ static void print_periodic_irq_rate(void); static void periodic_interrupt_test(void); static void count_periodic_interrupt(void); static void count_one_pirq(unsigned long pirq_rate); /* ラッパー関数 */ static int Open(const char *path, int flags); static int Ioctl(int fd, int request, void *arg); static ssize_t Read(int fd, void *buf, size_t count); static int Select(int n, fd_set *rfds, fd_set *wfds, fd_set *efds, struct timeval *tout); /**************************************** static 変数 ****************************************/ static unsigned long m_irqcount; /* 割込み発生回数 */ static int m_fd; /* /dev/rtc ファイル記述子 */ /************************************************** main() **************************************************/ int main(void) { /************************************************************ テスト開始メッセージ ************************************************************/ print_msg("\n\t\t\tRTC ドライバテスト\n\n"); /************************************************************ 各種テストを実行する。 ************************************************************/ do_tests(); /************************************************************ テスト終了メッセージ ************************************************************/ print_msg("\n\n\t\t\t *** テスト完了 ***\n\n"); print_msg( "IRQ 8 に %lu 以上のイベントが追加されていることを\n" "'cat /proc/interrupts' で確認することができます。\n\n", m_irqcount ); return EXIT_SUCCESS; } /************************************************** do_tests() **************************************************/ static void do_tests(void) { /************************************************************ RTC のデバイスファイル '/dev/rtc' をオープンする。 ************************************************************/ m_fd = Open("/dev/rtc", O_RDONLY); /************************************************************ 更新割込みテスト ************************************************************/ update_interrupt_test(); /************************************************************ アラームテスト ************************************************************/ alarm_test(); /************************************************************ 周期割込みテスト ************************************************************/ periodic_interrupt_test(); /************************************************************ RTC のデバイスファイル '/dev/rtc' をクローズする。 ************************************************************/ close(m_fd); } /************************************************** perror_exit() **************************************************/ static void perror_exit(const char *msg) { perror(msg); exit(errno); } /************************************************** print_msg() **************************************************/ static void print_msg(const char *fmt, ...) { va_list args; va_start(args, fmt); vfprintf(stderr, fmt, args); /************************************************************ stderr はオープン時にはバッファリングされていないので、 わざわざ fflush(stderr) は実行しない。 ************************************************************/ va_end(ap); } /* * * * 更新割込みテスト * * */ /************************************************** update_interrupt_test() **************************************************/ static void update_interrupt_test(void) { print_msg("更新割込みテスト\n"); /************************************************************ 一秒間に一回のペースで更新割込みを発生させるようにする。 ************************************************************/ Ioctl(m_fd, RTC_UIE_ON, 0); /************************************************************ /dev/rtc を read することで RTC の値を読み出す。 ************************************************************/ count_by_reading(); /************************************************************ /dev/rtc が読出し可能であることを select で確認してから、 /dev/rtc を read し、RTC の値を読み出す。 ************************************************************/ count_by_selecting(); /************************************************************ 更新割込みの発生をやめる。 ************************************************************/ Ioctl(m_fd, RTC_UIE_OFF, 0); } /************************************************** count_by_reading() **************************************************/ static void count_by_reading(void) { int i; unsigned long data; print_msg( "\t* 更新割込み (1回/秒) を /dev/rtc を read することにより\n" "\t 5 回数えます:" ); /************************************************************ RTC を 5 回読み出す。 ************************************************************/ for ( i = 1; i < 6; i++ ) { /************************************************************ RTC を読み出す。 ************************************************************/ Read(m_fd, &data, sizeof(unsigned long)); print_msg(" %d", i); /* 割込み回数をカウントする */ m_irqcount++; } } /************************************************** count_by_selecting() **************************************************/ static void count_by_selecting(void) { int i; unsigned long data; struct timeval tv; fd_set readfds; print_msg( "\n\n\t* 同じテストですが、read を実行する前に /dev/rtc が読込み\n" "\t 可能であるかどうかを select(2) で調べます。更新割込みを\n" "\t 5 回数えます:" ); /************************************************************ RTC を 5 回読み出す。 ************************************************************/ for ( i = 1; i < 6; i++ ) { /************************************************************ select のタイムアウトを 5 秒に設定する。 ************************************************************/ tv.tv_sec = 5; tv.tv_usec = 0; /************************************************************ m_fd が読込み可能になったかどうかを調べるための準備をする。 ************************************************************/ FD_ZERO(&readfds); FD_SET(m_fd, &readfds); /************************************************************ select は、更新割込みが発生するまで待つ (ただし 5 秒たって も割込みが発生しなければタイムアウトする) 。 ************************************************************/ Select(m_fd+1, &readfds, NULL, NULL, &tv); /************************************************************ 前の例とは異なり、次の read はブロックしない。 ************************************************************/ Read(m_fd, &data, sizeof(unsigned long)); print_msg(" %d", i); /* 割込み回数をカウントする */ m_irqcount++; } } /* * * * アラームテスト * * */ /************************************************** alarm_test() **************************************************/ static void alarm_test(void) { unsigned long data; print_msg("\n\nアラームテスト\n"); /************************************************************ 5 秒後にアラームが発生するように設定する。 ************************************************************/ set_alarm(5); print_msg("\t* アラームが発生するまで 5 秒間待ちます..."); /************************************************************ RTC の値を読み出す。 ************************************************************/ Read(m_fd, &data, sizeof(unsigned long)); print_msg(" 成功。\n\t アラームが鳴りました。\n"); /* 割込み回数をカウントする */ m_irqcount++; /************************************************************ アラーム割込みを無効にする。 ************************************************************/ Ioctl(m_fd, RTC_AIE_OFF, 0); } /************************************************** set_alarm() **************************************************/ static void set_alarm(int sec) { struct rtc_time rtc_tm; /************************************************************ 現在の RTC 時刻を読み出して rtc_tm にセットする。 ************************************************************/ read_current_rtc(&rtc_tm); /************************************************************ rtc_tm の値を sec 秒進める。 ************************************************************/ add_sec_to_rtc_time(&rtc_tm, sec); /************************************************************ sec 秒後にアラームが発生するように設定する。 ************************************************************/ Ioctl(m_fd, RTC_ALM_SET, &rtc_tm); /************************************************************ たった今設定したアラームの設定値を読み出す。 ************************************************************/ Ioctl(m_fd, RTC_ALM_READ, &rtc_tm); /************************************************************ 設定されているアラームの情報を表示する。 ************************************************************/ print_msg("\t* アラーム時刻が '%02d 時 %02d 分 %02d 秒' に設定されました。\n", rtc_tm.tm_hour, rtc_tm.tm_min, rtc_tm.tm_sec ); /************************************************************ アラーム割込みを有効にする。 ************************************************************/ Ioctl(m_fd, RTC_AIE_ON, 0); } /************************************************** read_current_rtc() **************************************************/ static void read_current_rtc(struct rtc_time *rtc_tm) { /************************************************************ 現在の RTC 時刻を読み出して rtc_tm にセットする。 ************************************************************/ Ioctl(m_fd, RTC_RD_TIME, rtc_tm); /************************************************************ 現在の RTC 時刻を表示する。 ************************************************************/ print_msg("\t* RTC 現在時刻: %d 年 %d 月 %d 日 %02d 時 %02d 分 %02d 秒\n", rtc_tm->tm_year + 1900, rtc_tm->tm_mon + 1, rtc_tm->tm_mday, rtc_tm->tm_hour, rtc_tm->tm_min, rtc_tm->tm_sec ); } /************************************************** add_sec_to_rtc_time() **************************************************/ static void add_sec_to_rtc_time(struct rtc_time *rtc_tm, int sec) { rtc_tm->tm_sec += sec; if ( rtc_tm->tm_sec >= 60 ) { rtc_tm->tm_sec %= 60; rtc_tm->tm_min++; } if ( rtc_tm->tm_min == 60 ) { rtc_tm->tm_min = 0; rtc_tm->tm_hour++; } if ( rtc_tm->tm_hour == 24 ) { rtc_tm->tm_hour = 0; } } /* * * * 周期割込みテスト * * */ /************************************************** periodic_interrupt_test() **************************************************/ static void periodic_interrupt_test(void) { print_msg("\n周期割込みテスト\n"); /************************************************************ 周期 IRQ レートを表示する。 ************************************************************/ print_periodic_irq_rate(); /************************************************************ 周期 IRQ レートを変化させながら、RTC の読込みテストを行う。 ************************************************************/ count_periodic_interrupt(); } /************************************************** print_periodic_irq_rate() **************************************************/ static void print_periodic_irq_rate(void) { unsigned long pirq_rate; /************************************************************ 周期 IRQ レートを読み出す。 ************************************************************/ Ioctl(m_fd, RTC_IRQP_READ, &pirq_rate); /************************************************************ 周期 IRQ レートを表示する。 ************************************************************/ print_msg("\t* 周期 IRQ レートは %luHz でした。\n", pirq_rate); } /************************************************** count_periodic_interrupt() **************************************************/ static void count_periodic_interrupt(void) { unsigned long pirq_rate; unsigned long rate_max; print_msg("\t* 周波数を変化させながら RTC 読取りテストを実行します。\n"); /************************************************************ テストする周波数の最大値を決める。このプロセスの実効ユーザ ID が root の場合は、8192Hz までテストする。root ではない 場合は、64Hz までのテストとする。 ************************************************************/ if ( geteuid() == 0 ) { rate_max = 8192; /* 8192Hz */ } else { rate_max = 64; /* 64Hz */ } /************************************************************ 周波数を 2Hz, 4Hz, 8Hz, 16Hz, 32Hz, 64Hz に順次変えて テストをおこなう。128Hz, 256Hz, ... 8192Hz は、root のみに許されている。 ************************************************************/ for ( pirq_rate = 2; pirq_rate <= rate_max; pirq_rate *= 2 ) { count_one_pirq(pirq_rate); } } /************************************************** count_one_pirq() **************************************************/ static void count_one_pirq(unsigned long pirq_rate) { int i; unsigned long data; /************************************************************ RTC の周波数を設定する。 ************************************************************/ Ioctl(m_fd, RTC_IRQP_SET, (void *)pirq_rate); print_msg("\n\t%4luHz:\t", pirq_rate); /************************************************************ 周期割込みを有効にする。 ************************************************************/ Ioctl(m_fd, RTC_PIE_ON, 0); /************************************************************ 20 回 RTC を読み出す。 ************************************************************/ for ( i = 1; i < 21; i++ ) { /************************************************************ RTC を読み出す。 ************************************************************/ Read(m_fd, &data, sizeof(unsigned long)); print_msg(" %d", i); /* 割込み回数をカウントする */ m_irqcount++; } /************************************************************ 周期割込みを無効にする。 ************************************************************/ Ioctl(m_fd, RTC_PIE_OFF, 0); } /* * * * ラッパー関数 * * */ /************************************************** Open() **************************************************/ static int Open(const char *path, int flags) { int fd; fd = open(path, flags); if ( fd == -1 ) { perror_exit("open"); } return fd; } /************************************************** Ioctl() **************************************************/ static int Ioctl(int fd, int request, void *arg) { int ret; ret = ioctl(fd, request, arg); if ( ret == -1 ) { perror_exit("ioctl"); } return ret; } /************************************************** Read() **************************************************/ static ssize_t Read(int fd, void *buf, size_t count) { ssize_t n; n = read(fd, buf, count); if ( n == -1 ) { perror_exit("read"); } return n; } /************************************************** Select() **************************************************/ static int Select(int n, fd_set *rfds, fd_set *wfds, fd_set *efds, struct timeval *tout) { int ret; ret = select(n, rfds, wfds, efds, tout); if ( ret == -1 ) { perror_exit("select"); } return ret; }