まずは 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 timetick マイクロ秒だけ進めてるだけだ。 よし 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 秒の間に処理終わらせる必要あるから関数呼出のコストすら嫌なんですかねこれ。

まぁいいや、ざっと読み解くと

  • timedelta0 ならこれまで通り tick だけ時刻を進める
  • timedelta が負の値なら ticktickdelta だけ短くして時刻を進める
  • timedelta が正の値なら ticktickdelta だけ長くして時刻を進める

どうですこれがクロック補正すなわち時計が逆回転しない時刻調整の正体である、どうですクッソ単純でしょ?これで hardclock(9) 完全に理解しましたァ(群盲評象)!

このたった数行のコードによって人類は時間退行の恐怖から解放されたのである。 ユービックのスプレー缶すなわち「最大出力25キロボルトのヘリウム電池で動く、自己充足的な高電圧・低増幅ユニットを備えた、ポータブル陰イオン化装置」なんて要らんかったんや!と安息所に眠るフィリップ・K・ディックもお喜びです。

4.4BSD での些細な変更

前回 4.3BSD の adjtime(2) を読んで、何か変だな札幌ドームと書いた以下の点

  • timedelta が非ゼロの場合は tickdelta を変更しない理由がよくわからない
  • ndeltatickadj の倍数で揃える条件は ndeltatickdelta でなく 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 の字までしか出したことねーや。