adjtime(2) の実装を読む[後編] - インターネット時刻同期の歴史(その6)
まずは 4.2BSD の hardclock(9) を読む
adjtime(2) 導入以前の hardclock(9) ということで 4.2BSD まで戻る。
16 struct timeval time;
28 #define HZ 100
29 int hz = HZ;
30 int tick = 1000000 / HZ;
46 /*
47 * The hz hardware interval timer.
48 * We update the events relating to real time.
49 * If this timer is also being used to gather statistics,
50 * we run through the statistics gathering routine as well.
51 */
52 /*ARGSUSED*/
53 hardclock(pc, ps)
54 caddr_t pc;
55 int ps;
56 {
...
185 /*
186 * Increment the time-of-day, and schedule
187 * processing of the callouts at a very low cpu priority,
188 * so we don't keep the relatively high clock interrupt
189 * priority any longer than necessary.
190 */
191 bumptime(&time, tick);
...
193 }
...
299 /*
300 * Bump a timeval by a small number of usec's.
301 */
302 bumptime(tp, usec)
303 register struct timeval *tp;
304 int usec;
305 {
306
307 tp->tv_usec += usec;
308 if (tp->tv_usec >= 1000000) {
309 tp->tv_usec -= 1000000;
310 tp->tv_sec++;
311 }
312 }
前回 も書いたように hardclock(9) の完全(そうかな…そうかも…)な解説については BSD の設計と実装を読め。 ここで切り抜いたのはあくまで時計の針を進めている部分だけで他はバッサリ切り捨てている。 分量としてはアポロ宇宙船の指令モジュールを指してこれがサターンVロケットですというようなものである。
しかし切り捨てたおかげで一目瞭然だよね。
bumptime()
で現在時刻 struct timeval time
を tick
マイクロ秒だけ進めてるだけだ。
よし hardclock(9) 完全に理解した。
4.3BSD の hardclock(9) を読む
それじゃ adjtime(2) 導入で hardclock(9) がどう変わったか 4.3BSD のコード読むゾ。
22 struct timeval time;
40 #define HZ 100
41 int hz = HZ;
42 int tick = 1000000 / HZ;
43 int tickadj = 240000 / (60 * HZ); /* can adjust 240ms in 60s */
53 /*
54 * Bump a timeval by a small number of usec's.
55 */
56 #define BUMPTIME(t, usec) { \
57 register struct timeval *tp = (t); \
58 \
59 tp->tv_usec += (usec); \
60 if (tp->tv_usec >= 1000000) { \
61 tp->tv_usec -= 1000000; \
62 tp->tv_sec++; \
63 } \
64 }
65
66 /*
67 * The hz hardware interval timer.
68 * We update the events relating to real time.
69 * If this timer is also being used to gather statistics,
70 * we run through the statistics gathering routine as well.
71 */
72 /*ARGSUSED*/
73 hardclock(pc, ps)
74 caddr_t pc;
75 int ps;
76 {
...
81 extern int tickdelta;
82 extern long timedelta;
...
197 /*
198 * Increment the time-of-day, and schedule
199 * processing of the callouts at a very low cpu priority,
200 * so we don't keep the relatively high clock interrupt
201 * priority any longer than necessary.
202 */
203 if (timedelta == 0)
204 BUMPTIME(&time, tick)
205 else {
206 register delta;
207
208 if (timedelta < 0) {
209 delta = tick - tickdelta;
210 timedelta += tickdelta;
211 } else {
212 delta = tick + tickdelta;
213 timedelta -= tickdelta;
214 }
215 BUMPTIME(&time, delta);
216 }
...
228 }
bumptime()
が BUMPTIME()
マクロになってるけどやってることは同じ。
単にインライン展開したいだけの変更(まだ inline は存在しない)。
hardclock(9) は必ず 1 tick = 1/100 秒の間に処理終わらせる必要あるから関数呼出のコストすら嫌なんですかねこれ。
まぁいいや、ざっと読み解くと
timedelta
が0
ならこれまで通りtick
だけ時刻を進めるtimedelta
が負の値ならtick
をtickdelta
だけ短くして時刻を進めるtimedelta
が正の値ならtick
をtickdelta
だけ長くして時刻を進める
どうですこれがクロック補正すなわち時計が逆回転しない時刻調整の正体である、どうですクッソ単純でしょ?これで hardclock(9) 完全に理解しましたァ(群盲評象)!
このたった数行のコードによって人類は時間退行の恐怖から解放されたのである。 ユービックのスプレー缶すなわち「最大出力25キロボルトのヘリウム電池で動く、自己充足的な高電圧・低増幅ユニットを備えた、ポータブル陰イオン化装置」なんて要らんかったんや!と安息所に眠るフィリップ・K・ディックもお喜びです。
4.4BSD での些細な変更
前回 4.3BSD の adjtime(2) を読んで、何か変だなと書いた以下の点
timedelta
が非ゼロの場合はtickdelta
を変更しない理由がよくわからないndelta
をtickadj
の倍数で揃える条件はndelta
をtickdelta
でなくtickadj
で割って余りがあった場合では?
単なるバグだったね(ゲッソリ)、4.4BSD では修正されている。
149 /*
150 * Compute the total correction and the rate at which to apply it.
151 * Round the adjustment down to a whole multiple of the per-tick
152 * delta, so that after some number of incremental changes in
153 * hardclock(), tickdelta will become zero, lest the correction
154 * overshoot and start taking us away from the desired final time.
155 */
156 ndelta = atv.tv_sec * 1000000 + atv.tv_usec;
157 if (ndelta > bigadj)
158 ntickdelta = 10 * tickadj;
159 else
160 ntickdelta = tickadj;
161 if (ndelta % ntickdelta)
162 ndelta = ndelta / ntickdelta * ntickdelta;
あと tickdelta
が負なら符号反転する処理を入れることで hardclock(9) のコードもシンプルになってる。
164 /*
165 * To make hardclock()'s job easier, make the per-tick delta negative
166 * if we want time to run slower; then hardclock can simply compute
167 * tick + tickdelta, and subtract tickdelta from timedelta.
168 */
169 if (ndelta < 0)
170 ntickdelta = -ntickdelta;
...
172 odelta = timedelta;
173 timedelta = ndelta;
174 tickdelta = ntickdelta;
181 /*
182 * Increment the time-of-day. The increment is just ``tick'' unless
183 * we are still adjusting the clock; see adjtime().
184 */
185 if (timedelta == 0)
186 delta = tick;
187 else {
188 delta = tick + tickdelta;
189 timedelta -= tickdelta;
190 }
191 BUMPTIME(&time, delta);
あとは N 時代に 負の方向に bigadj
= 1 秒補正するときに10倍界王拳が効いてなかった些細なバグも潰されている。
Index: src/sys/kern/kern_time.c
diff -u src/sys/kern/kern_time.c:1.40 src/sys/kern/kern_time.c:1.41
--- src/sys/kern/kern_time.c:1.40 Mon Aug 16 18:53:55 1999
+++ src/sys/kern/kern_time.c Sun Oct 10 18:41:53 1999
@@ -1,4 +1,4 @@
-/* $NetBSD: kern_time.c,v 1.40 1999/08/16 18:53:55 tron Exp $ */
+/* $NetBSD: kern_time.c,v 1.41 1999/10/10 18:41:53 hwr Exp $ */
/*
* Copyright (c) 1982, 1986, 1989, 1993
@@ -358,7 +358,7 @@ sys_adjtime(p, v, retval)
* overshoot and start taking us away from the desired final time.
*/
ndelta = atv.tv_sec * 1000000 + atv.tv_usec;
- if (ndelta > bigadj)
+ if (ndelta > bigadj || ndelta < -bigadj)
ntickdelta = 10 * tickadj;
else
ntickdelta = tickadj;
この後 2006 年くらいまではほとんどコードの変更は無い、そう黒船が来航するまでは。
次回
2006 年に何が起きたのか、ビール奢れおじさんとは誰なのか。
いやそれよりもっと前 NTPv3 のリファレンス実装である xntpd が 1992 年にリリースされた前後からの話になるんだけども。
次回からはついに NTP による時刻調整のお話になるが、大河ドラマ過ぎて俺の手に余るからもうやめていい?
なんせ USL vs BSDi 訴訟によって BSD が死んで停滞してる間に世界は先に進み過ぎた。 そして追いつこうとする側も N から O がそして F から D が分裂してそれぞれオレオレ実装やりやがるから説明が 4 倍増だものやっとれんわ。
まぁ自分もウンザリして小規模な fork (ただの老人の盆栽弄り)やってるから偉そうなことはいえんが。
いっそ今や大正義 UNIX といえる GNU/Linux の話しようかと考えたがそもそも俺仕事以外だと LILO の LI の字までしか出したことねーや。