adjtimexによる時計の調整

あるマシンでetch→lennyとしたところ、いつまで待ってもNTPで時刻同期できない状態になってしまった。こんなときはadjtimexを使えば良かったんだっけと、あまりあてにならない記憶に従ってみた。

# dpkg-reconfigure adjtimex

これは/usr/sbin/adjtimexconfigを実行すると同じようなことで、adjtimexconfigはadjtimex --adjustを実行するとの同じようなことである。adjtimex --adjustは時刻のズレを調整してくれるものであったと考えていたのだが、結果からすると状況を改善することができなかった。

以前にも何度かこの手の格闘をしてきており、その都度、調べたり試したりで状況を治めてきたはずなのに、などと今度も思いつつ、しょうがないからadjtimexのマニュアルをななめに見てからadjtimexconfigをななめに見て、/etc/default/adjtimexに記述されているTICKとFREQを調整すれば良さそうだというところにたどりついた。

TICKとFREQはadjtimexの--tickおよび--frequencyという二つのオプションに対する引数として使われている。

USER_HZ=100の一般的な環境では一秒間に100回の割り込みがあり、システム時計はそのたびに10,000msずつ進められる…… というのが理想的な状態。実際にはそううまくいかないわけで、システム時計の進め幅を調整する必要が出てくる。

--tickオプションはこの進め幅を指定する。単位はmsなので10,000前後の値となる。10,000→10,001と変えたとすると、一秒(割り込み100回)あたり100ms、一日で8.64秒だけシステム時計の進みが速くなる。--tickオプションよりも小さな幅で調整するのが--frequencyオプションで、0を基本として、進めるなら正の値、遅らせるなら負の値を指定する。--frequency 65536とすると、一秒あたり1msだけシステム時計が速く進むようになる。

# vi /etc/default/adjtimex
# update-rc.d adjtimex start; ntpdate -bv ntpserver; sleep 100; ntpdate -bv ntpserver
なんて感じで、前述の二つの値の上下を繰り返しながら、システム時計の進みを調整し、ようやくNTPによる時刻同期がとれるようになった。

adjtimexの使い方

後になってadjtimex(8)を見直してみると、そんな繰り返しをしなくてもよさそうな機能が備っていることがわかった。当然ともいえる。 まず--compareオプション。これを指定して実行すると、10秒間隔でRTC(ハードウェア上の時計)とシステム時計を読み取って、そのズレの度合いを導き出すことができる。また適切と考えられるTICK/FREQの値も同時に導き出される。このときの基準はRTCだが、adjtimex --log --host ntpserverのように指定して実行すると、ntpdateによりNTP時刻を参照した上で、それとのズレが/var/log/clocks.logに記録される。このファイルの内容はadjtimex --reviewにより参照でき、NTP時刻を基準にした場合のTICK/FREQが導かれる。 ここで--adjustオプションに戻ってみると、これは--compareの結果をそのまま設定してしまうという動作をさせるオプションである。つまり、RTCを信頼してシステム時計を調整するためのオプションである。となると、発端となったホストのRTCはあまり正確ではなく、adjtimex  --adjustをしてもシステム時計の進みはおかしなまま。その結果としてNTPが許容できる範囲を越えたシステム時計の進み具合い(または遅れ具合い)となり、同期がとれなくなったものと考えられる。 実際、以下のようにadjtimexを実行すると、RTCがどんどんズレていく様子が見られる。
# adjtimex --compare
                                      --- current ---   -- suggested --
cmos time     system-cmos  error_ppm   tick      freq    tick      freq
1240113783      -0.037287
1240113792       0.063180    10046.7   9999   4807606
1240113801       0.163457    10027.8   9999   4807606    9899   2986972
1240113810       0.263660    10020.3   9999   4807606    9899   3479160
1240113819       0.363839    10017.9   9999   4807606    9899   3635410
1240113828       0.463941    10010.2   9999   4807606    9899   4140098
1240113837       0.564190    10024.9   9999   4807606    9899   3176035
1240113846       0.664455    10026.6   9999   4807606    9899   3066660

adjtimexでシステム時計を調整する

問題のホストとは別のホストでadjtimexによるシステム時計の調整を試みた。

まず、adjtimexconfigを使ってみる。前述の通り、これはadjtimex --adjustを実行するのと同等であり、システム時計の進み具合をRTCの進み具合に合わせるための調整となる。設定された内容はadjtimex --printにより確認できる。

$ adjtimex --print
         mode: 0
       offset: 0
    frequency: 5951052
     maxerror: 16000000
     esterror: 16000000
       status: 65
time_constant: 10
    precision: 1
    tolerance: 32768000
         tick: 9999
     raw time:  1240109330s 387925us = 1240109330.387925
 return value = 5
NTP時刻との比較を行ってみたところ、以下のようにこのホストのRTCはなかなか正確であることがわかる。
$ ntpdate -q ntpserver; sleep 100; ntpdate -q ntpserver
server 172.16.100.100, stratum 3, offset 0.079893, delay 0.02580
19 Apr 11:45:42 ntpdate[24278]: adjust time server 172.16.100.100 offset 0.079893 sec
server 172.16.100.100, stratum 3, offset 0.084867, delay 0.02580
19 Apr 11:47:22 ntpdate[24289]: adjust time server 172.16.100.100 offset 0.084867 sec

adjtimexでシステム時計をNTP時刻に合わせる

次に--logオプションと--hostオプションを使って、NTP時刻を基準にした調整を行ってみる。adjtimexconfigでは調整に70秒の時間をとっているため、ここでも70秒だけおいてみることにした。まずはシステム時計の進み具合いを調査する。
# adjtimex --tick 10000 --frequency 0 (上の設定をリセットするために実行)
# adjtimex --log --host ntpserver; sleep 70; adjtimex --log --host ntpserver
      reference time is Sun Apr 19 11:49:11 2009
reference time - system time = 1240109351.090 - 1240109351.000 = 0.090 sec
No previous clock comparison in log file

Are you sure that, since Thu Jan  1 09:00:00 1970,
  the real time clock (cmos clock) has run continuously,
  it has not been reset with `/sbin/hwclock',
  no operating system other than Linux has been running, and
  ntpd has not been running? (y/n) [n] (ここで一回目のadjtimexが終了)
      reference time is Sun Apr 19 11:50:27 2009
reference time - system time = 1240109427.093 - 1240109427.000 = 0.093 sec
Last clock comparison was at Sun Apr 19 11:49:11 2009
Kernel time variables are unchanged - good.
System clock is currently not disciplined - good.
Checking wtmp file...
System has not booted since Sun Apr 19 11:49:11 2009 - good.
System time has not been changed since Sun Apr 19 11:49:11 2009 - good.
Checking /etc/adjtime...
/sbin/hwclock has not set system time and adjusted the cmos clock 
since Sun Apr 19 11:49:11 2009 - good.

Are you sure that, since Sun Apr 19 11:49:11 2009,
  the system clock has run continuously,
  it has not been reset with `date' or `/sbin/hwclock`,
  the kernel time variables have not been changed, and
  the computer has not been suspended? (y/n) [y] 
The estimated error in system time is -40.6706732 +- 0.0458581 ppm

Are you sure that, since Sun Apr 19 11:49:11 2009,
  the real time clock (cmos clock) has run continuously,
  it has not been reset with `/sbin/hwclock',
  no operating system other than Linux has been running, and
  ntpd has not been running? (y/n) [y] 
The estimated error in the cmos clock is -52.00 +- 0.05 ppm
これにより得られた情報で設定をする。このために指定するのはやはり--adjustオプションだが、さらに--reviewオプションも指定する。 --reviewは--logにより記録された情報を参照するためのオプションで、記録情報からTICK/FREQを導き出すことができる。これを--adjustとともに使うと、RTCとの比較にからず、記録情報か得られたTICK/FREQを設定できる。
# adjtimex --adjust --review
start                     finish                    days    sys - cmos (ppm)
Sun Apr 19 11:49:11 2009  Sun Apr 19 11:50:27 2009  0.0009  13.2 +- 0.2
start                     finish                    days    cmos_error (ppm)
Sun Apr 19 11:49:11 2009  Sun Apr 19 11:50:27 2009  0.0009  -53.83 +- 0.03
start                     finish                    days    sys_error (ppm)
Sun Apr 19 11:49:11 2009  Sun Apr 19 11:50:27 2009  0.0009  -40.67 +- 0.03
least-squares solution:
   cmos_error = -53.8 +- 0.3 ppm
      suggested adjustment = 4.6511 sec/day
        current adjustment = 0.0038 sec/day
   sys_error = -40.7 +- 0.3 ppm
      suggested tick = 10000  freq =   2665447
        current tick = 10000  freq =         0
note: clock variations and unstated data errors may mean that the
least squares solution has a bigger error than estimated here
new tick = 10000  freq = 2665447

以上により、NTP時刻を基準としてシステム時計の調整を行うことができたはずである。先程と同様にNTP時刻との比較をしてみる。

$ ntpdate -q ntpwerver; sleep 100; ntpdate -q ntpserver
server 172.16.100.100, stratum 3, offset 0.094815, delay 0.02580
19 Apr 11:51:24 ntpdate[24319]: adjust time server 172.16.100.100 offset 0.094815 sec
server 172.16.100.100, stratum 3, offset 0.094798, delay 0.02580
19 Apr 11:53:04 ntpdate[24325]: adjust time server 172.16.100.100 offset 0.094798 sec

ここではNTPによる時刻同期をしたわけではないので時刻のズレ自体は問題ではない。時刻のズレの進み具合いが問題となる。adjtimexconfigによる調整では一秒につき(0.084867 - 0.079893)/100 = 49.74msづつNTP時刻よりも進んでいくことがわかる。一方、NTPを使ってadjtimexしたあとでは一秒につき(0.094798 - 0.094815)/100 = -0.17msづつNTP時刻よりも進んでいく(つまり0.17msずつ遅れていく)。

hwclockとの関係

adjtimex --compareや--adjustでは、RTCから値を読み取る際に/etc/adjtimeの内容による補正を行っている。同ファイルはhwclockにより作られるものである。

hwclockは、RTCを書き換えるときに、RTCの値との差分を/etc/adjtimeに記録する。そしてhwclockでシステム時刻を設定する際にはRTCの値そのものではなく/etc/adjtimeに記録されている情報でRTCの値を補正したものを設定する。つまりシステム時計を基準にしたときのRTCのズレ具合いが/etc/adjtimeに記録されているということを意味する。

hwclockによって行われる調整は、RTCからシステム時刻を得るときに使われるものである。これはシステム時計の進み方や遅れ方を調整するものではない。むしろシステム時計が未調整であるならばhwclockの仕組み使った結果としてシステム時刻は適切にズレてしまうことになるとも言える。

簡単なまとめ

RTCは通常、一定の進み方を示すか、一定の遅れ方を示す。この幅が小さければ実用上、時刻のズレで困ることはない。あるいはNTPにより時刻同期が可能である。最近のPCであれば、NTPで対処できないほどにRTCがズレるということもないのではないかと思う。したがってここに書いたようなことをする必要はあまりない。

ただ、NTPでもどうにもならないといったときにはadjtimexの出番となる。adjtimexを使えばシステム時計の進み方や遅れ方を調整できる。