2. Linux はどのように時間を計っているのか

2.1. 基本的な仕組み

Linux システムには 2 種類の時計があります。ひとつは、バッテリー駆動の リアルタイムクロック(RTC や CMOS クロックもしくは ハードウェアクロックとも呼ばれています)であり、これはシステムの電源が オフの時にも動いていますが、システムの稼働中は使われません。 もうひとつは、システムクロック (System Clock, カーネルクロックもしくは ソフトウェアクロックとも呼ばれます) であり、これはタイマ割り込みに基づく ソフトウェア・カウンタ(software counter)です。 システムクロックは システムの稼働中以外には存在しないので、起動時に RTC (もしくは それ以外の外部時計) によって初期化される必要があります。ntpd の付属文書で「クロック」を参照するという場合、RTC ではなくシステム クロックの参照を意味しています。

それら 2 種類の時計のズレ具合は異なっているので、両者は徐々に食い違っていき、 双方ともだんだんと実際の正確な時間から離れていきます。これらを正確に 保つ上で最も簡単な方法は、時間がズレる割合を計算してソフトウェア上で それを修正することです。RTC はシステムの稼働していない時だけ使用されるもの なので、RTC の修正は、起動時に時刻を読み込む際に clock(8)hwclock(8) を使って行います。システムクロックの 修正は、タイマ割り込みによりシステム時間が進む際に adjtimex(8) を使ってズレを修正することにより実施します。

adjtimex(8) の代わりに cron を使って clock(8)hwclock(8) を 定期的に実行し、システムクロックを(正確に調整した)RTC に同期させる という方法もないわけではありません。これは clock(8) のマニュアルページで推奨されていますし、ある程度頻繁に同期させることで 両者の時間のズレを小さくしてシステムクロックの時刻が大幅に「ジャンプ」する ことがなければ問題なく機能しますが、adjtimex(8) を使って調整した方がエレガントです。アプリケーションのなかには、 時間調整で時計を遡らせた場合にはエラーになるものがあるかもしれません。

時間調整の方法としては、他にも ntpd というプログラムを 使ってネットワークタイムサーバや電波時計から定期的に時間を読み込んで 継続的にシステムクロックの誤差を調整することで、システムクロックの時刻の大幅な 「ジャンプ」を避ける方法もあります。もし起動時に必ずネットワークコネクションを 張ることが出来るなら、RTC を完全に無視し、ntpdate (これは ntpd パッケージに付属しています) を使って LAN 内のローカルマシンやインターネット上のリモートマシンにあるタイム サーバによりシステムクロックを初期化することもできます。しかし、もし 必ずしもネットワークコネクションを張れないときがある場合や、起動中に 正確な時間を取得した後にネットワークに繋がなければならない場合は、 RTC の時間も調整しておく必要があります。

2.2. コンフリクトの可能性

ntpd のようなプログラムを使っているなら、RTC を (調整済みの)システムクロックに同期させようと思うかもしれません。 しかし、これはシステムが数分以上に渡って遮断される可能性がある場合には 予想通りに上手くはいきません。起動時に RTC を調整するプログラム と干渉してしまうからです。

システムが土日なしで 24 時間稼働し、電源が遮断された場合でもすぐに再起動する ようになっているなら、システムクロック時間に基づいて再起動直前に RTC を 設定することは可能です。その際は RTC にはリブート中の経過時間の範囲内でしか 誤差が生じないので、RTC のズレ具合を知る必要もなくなります。

もちろんシステムには予想外の障害が付きものです。そこで、システムクロックが 何らかのプログラムによって正確に調整されている場合、カーネルのバージョンに よっては RTC を 11 分ごとにシステムクロックに同期させるものがあります。通常 11 分間のうちに RTC がシステムクロックと大きく食い違うことはありませんが、 システムが長時間ダウンして RTC 時間が大きくズレた場合は問題が生じます。 なぜなら RTC の誤差を調整するプログラムは、自分が RTC を最後にリセットした 時刻を正確に記憶している必要があるのですが、カーネルには そのような情報は記録されていないからです。

UNIX の伝統を重んじる人の中には、Linux システムはもともと 24 時間連続稼働 させるものなのだと思っている方もいることでしょう。しかし、デュアルブート で時々は別の OS を実行しているユーザもいますし、ラップトップで Linux を使っているユーザは電源を落とすことで使用中以外の時間でのバッテリーの 消耗を防いだりもしています。それ以外にも、単に使っていないマシンを 長時間実行させっ放しにするのを好まない人もいます (むろん、電源を入れた ままにする方が良いという議論は充分承知はしていますが)。そのような場合、 11 分ごとに同期を行う機能は非常に不都合な機能ということになります。

この(場合によっては不都合な)機能は、カーネルのバージョンが異なると 振る舞いも異なるようです (おそらく xntpdntpd のバージョンが異なる場合も、 振る舞いが変わるようです)。それゆえ、もし ntpdhwclock の両方を 実行しているなら、予めテストを行って、その機能が実際にどういう振る舞いを するのか確認する必要があるでしょう。もしカーネルが RTC をリセットする のを止めさせることができない場合は、RTC に対する時間調整を一切行わない ようにする必要があるでしょう。

こうしたことを制御しているのは、カーネル内の /usr/src/linux-2.0.34/arch/i386/kernel/time.c です (パス中のバージョン番号は、お手持ちのカーネルのバージョン番号に置き換えて ください)。変数 time_statusTIME_OK に設定されると、カーネルはシステム時間を 11 分ごとに RTC に書き込もうと し、それ以外の値だと RTC には干渉しません。(例えば、ntpdtimed を使って) adjtimex(2) を 呼び出すことで、これをオンにすることができます。settimeofday (2) を呼べば、time_statusTIME_UNSYNC に設定することができます。これは、カーネルに RTC の調整をしないよう指示するものです。この辺りのことを詳しく説明した 文書を著者は知りません。

カーネルのバージョンによっては、CPU への電源供給を遮断してバッテリー を長持ちさせる sleep 機能に問題があるものがあるという報告を受けています。 最良の解決策は、カーネルを常に最新の状態に更新してゆくことであり、 カーネルの管理者に問題を報告するようにすることです。

RTC の狂い方が妙な場合は、ハードウェア上の問題かもしれません。RTC チップにはリチウム電池が内蔵されていますが、この電池が切れたのかも しれません。また、マザーボードによっては外部バッテリーを使えるように 設計されたものもあります(ジャンパの設定を確認してください)。 CMOS RAM も RTC と同じ電池で動いているのですが、時計の方が 電力の消費が激しいのでたいてい先に動かなくなります。システム クロックの表示がおかしい場合は、割り込みに問題があるかもしれません。

2.3. RTC にはローカルタイムか UTC のどちらを使うべきか? DST って何?

Linux のシステムクロックが実際にカウントしているのは、1970年1月1日 からの経過秒数であり、時刻は必ず協定世界時 (UTC, Coodinated Universal Time) を使います (UTC は GMT (Greenwich Mean Time) とほぼ同義です。 両者は技術的には別物ですが非常に似通っているので、一般のユーザは 同じ意味で使うことが多いようです)。UTC は、夏時間 (DST, Daylight Saving Time) が必要な場合でも変更されることはありません。変わるのは、UTC とローカルタイム との間での変換方法だけです。ローカルタイムへの変換は、アプリケーション プログラムにリンクされたライブラリ関数によって実行されます。 (訳注:DST (いわゆるサマータイム)については、 いまのところ日本では関係ありません。)

これは、結果的に二つの意味を持ちます。まず第一に、アプリケーションが ローカルタイム知る必要がある場合は、DST が有効であるか否かに関わらず、 同時にユーザのタイムゾーンも知る必要があるということです (タイムゾーン については、次章を参考にしてください)。第二に、DST が問題になる場合でも、 UTC 自体に変更はないので、カーネルはシステムクロックや RTC を変更する必要 がないということです。それゆえ Linux しか走らせないマシンの場合、 RTC はローカルタイムではなく UTC に設定すべきです。

しかしながら、RTC がローカルタイムを表示していることを前提にした OS を 同居させて、デュアルブートシステムにしている人は多いと思います。その場合、 hwclock は RTC がローカルタイムか UTC であるか を知る必要があり、さらにその時刻を 1970年1月1日 (UTC) からの経過秒数へと 変換する必要があります。ただ、これは RTC に対する夏時間調整までは行わない ことから、夏時間への変更は Linux 以外の OS 側で実行する必要があります (これは RTC の時間を変更する際は単一プログラムを使い、重複した変更を行わない というルールの例外に当たります)。

残念ながら、RTC や CMOS RAM にはローカルタイムの表示に関して標準時間か DST なのかを区別するためのフラグがないので、OS は、おのおの他の OS からは分からない 場所にその情報を保存しています。つまり、夏時間の始期や終期の前後に Linux 以外の OS を起動して時間調整していない場合であっても、 hwclock は、RTC が いつも正しいローカルタイムを表示していると仮定しなければならないわけです。

夏時間調整の始期や終期の前後に Linux が動いていた場合、システムクロック自体は 影響を受けないわけですし、アプリケーションも正しい時刻変換をするでしょう。 しかし、何らかの理由で Linux を再起動しなければいけない場合、 システムクロックは RTC 時間に合わせて設定されます。その場合、 システムクロックは他の OS (通常は Windows) が実行されるまで 時刻が 1 時間狂ってしまいます。

これに対処する方法はないのですが、Linux はめったなことではクラッシュ しないので、デュアルブートシステムでリブートをかけるのは たいてい Linux から他の OS に切り替えるためです。しかし、 もし長時間コンピュータを使わないときに電源を切るタイプの人なら、つまり 夏時間調整の変更があっても他の OS を使う機会がなかったなら、 他の OS を走らせるまで RTC は一時間狂うことになるのを忘れないで ください。

解説書のなかには、RTC を UTC に設定することで Linux が DST を上手く 扱えるようになると説明しているものがあります。これは全くの間違い ではないですが、不正確です。リブートをしない限り、(RTC のバッテリー が切れていた場合でも、) RTC がどちらの時間を表示しているかは問題 になりません。Linux は、リブートがかかるまではいずれかの方法で 正しい時間を表示しているわけです。理論的には、もし一年に一回しか リブートしないなら(これは、Linux の場合、特に珍しいことでは ありません)、DST が問題になる場合でもシステムクロックはずっと 正確な時間を伝えているので、RTC が狂ったとしても何ヶ月も気付かない でしょう。しかし、いつリブートをするかということは予め予測が付かない ので、ローカルタイムを必要とする OS を使っていない場合は、 RTC を UTC に設定した方がいいでしょう。

Dallas Semiconductor の RTC チップ (これは、Motorola チップの代わりに IBM AT 機やその互換機に搭載されるようになったチップです) は、実際に チップ自体に UTC を DST に変換する能力が備わっていますが、変更すべき 日付があらかじめハード的に固定されていてそれをソフト的に変えることが できないので、この機能は使われていません。最新のバージョン更新が 2000年4月の第一日曜日と 10月の最終日曜日に実施されましたが、それ 以前のバージョンのチップでは、それぞれ異なった日時が使われています。 (また、これは(合衆国と)違う日付を使っている国では、明らかに機能しません。) さらに、RTC は (単体のチップではなく) マザーボードのチップセットの 中に組み込まれている場合も多くなっているので、それらすべてがこの 能力を持っているかどうか著者には分かりません。

2.4. タイムゾーンの設定について

おそらく Linux インストールの際に、読者はタイムゾーンを正しく 設定していると思います。しかし、もし何らかの理由でそれを 変更しなければならない場合、もしくは DST に関する自国の法律が 変更された場合(こういう法律変更が頻繁に起こる国もあります)、 タイムゾーンを変更する方法を知る必要が生じるでしょう。 システム時間がきっかり数時間ずれているようなときは、 タイムゾーン (もしくは、DST) の設定に問題があります。

タイムゾーンや DST の情報は、/usr/share/zoneinfo (もしくは /usr/lib/zoneinfo) に保存されて います。ローカルなタイムゾーンは、/etc/localtime から上記のファイル群のひとつにシンボリックリンクを張ることで決定 されています。タイムゾーンを変更するには、そのリンクを張り替え ましょう。もし DST の日付が変わらない場合は、そのファイルを編集する 必要があります。

また、環境変数 TZ を使って現在のタイムゾーンを変更することも できます。これは異なるタイムゾーンにある遠隔マシンにログインしている 場合には、便利な方法です。詳しくは、tzsettzfile のマニュアルページをご覧ください。

これについては、次のサイトで分かり易く説明されています。 http://www.linuxsa.org.au/tips/time.html

2.5. どの程度の精度が必要か

ミリ秒単位での正確さが不要なら、hwclock(8)adjtimex(8) だけでおそらく充分です。タイムサーバや電波時計 等に夢中になったとしても無理はありませんが、著者は古い clock(8) プログラムだけでここ数年すばらしい結果を得ています。 他方、LAN 上に複数のマシンがある場合は、互いに時計を自動的に同期させる ことができれば便利です(あるいは、それが不可欠な場合もあります)。それ以外 にも、特に必要がなくても使ってみて面白いツール類もあります。

Linux だけしか走らせないマシンならば、RTC を UTC (もしくは GMT) に 設定しましょう。RTC をローカルタイムに設定する必要のあるデュアルブート システムの場合は、注意すべき点があります。もし夏時間の変更前後に Linux をリブートしなければならないときは、時計が暫定的に一時間ずれてしまうことが あるということです。その状態は、もう一つの OS を起動するまで続くことになり ます。3 つ以上の OS が載っている場合は、そのうちひとつの OS だけで DST を 調整するよう気を付けてください。