先に settimeofday(2) のおさらい

N ではざっくりいうと

  • libc のシステムコール foo() を呼ぶと
  • kernel の sys_foo() が実行される

事になっている、いや本当は SYS_foo というシステムコール番号とペアになる関数ポインタをテーブルから引くのだがそこまで知らんでもいい、話が長くなるし。

なので settimeofday()sys_settimeofday() となるのだが、 N5 で time_t を 64bit 化した関係でちょっと事情が異なる。

time_t のサイズが変わると libc の後方互換性が失われるので soname というものを変更しなくてはならない。 N における運用では libc.so.12.* を libc.so.13.* とする、つまりメジャーバージョン変更が必要だったのだ。

しかしあまりにインパクトが大きいので、ちょっとしたトリックを使ってバイナリの後方互換性を保ち再コンパイル不要なまま 64bit time_t 化を成し遂げたのである。

方法については存在しない過去回「 どのようにしてlibcは後方互換を保つのか?(その3) 最終回 」で解説済なのでそっちを読んでもらえばいい。 要約すれば __RENAME マクロという邪悪なプリプロセッサの魔法でシステムコール名を強制変更しているということ。

この魔法により settimeofday() はプログラマが一切意識することなく __settimeofday50() に置換えられている。 なので実際にカーネル側で呼ばれているのは sys___settimeofday50() である事に注意。

526 /* ARGSUSED */
527 int
528 sys___settimeofday50(struct lwp *l, const struct sys___settimeofday50_args *uap,
529     register_t *retval)
530 {
531 	/* {
532 		syscallarg(const struct timeval *) tv;
533 		syscallarg(const void *) tzp; really "const struct timezone *";
534 	} */
535 
536 	return settimeofday1(SCARG(uap, tv), true, SCARG(uap, tzp), l, true);
537 }
538 
539 int
540 settimeofday1(const struct timeval *utv, bool userspace,
541     const void *utzp, struct lwp *l, bool check_kauth)
542 {
...
570 	return settime1(l->l_proc, &ts, check_kauth);
571 }

sys___settimeofday50()settimeofday1() を呼出しているが、これは後方互換のために残されている compat_50_sys_settimeofday() と実装を共有するためである。

237 /* ARGSUSED */
238 int
239 compat_50_sys_settimeofday(struct lwp *l,
240     const struct compat_50_sys_settimeofday_args *uap, register_t *retval)
241 {
242 	/* {
243 		syscallarg(const struct timeval50 *) tv;
244 		syscallarg(const void *) tzp; really "const struct timezone *";
245 	} */
...
252 	return settimeofday1(&atv, false, SCARG(uap, tzp), l, true);
253 }

そして settimeofday1()settime1() を呼出している。 これは kernel 内では不必要なエラーチェックを分離することで最適化している。

176 /*
177  * Time of day and interval timer support.
178  *
179  * These routines provide the kernel entry points to get and set
180  * the time-of-day and per-process interval timers.  Subroutines
181  * here provide support for adding and subtracting timeval structures
182  * and decrementing interval timers, optionally reloading the interval
183  * timers when they expire.
184  */
185 
186 /* This function is used by clock_settime and settimeofday */
187 static int
188 settime1(struct proc *p, const struct timespec *ts, bool check_kauth)
189 {
...
214 	tc_setclock(ts);
215 
216 	resettodr();
...
236 }
156 static struct {
157 	struct bintime bin;
158 	volatile unsigned gen;	/* even when stable, odd when changing */
159 } timebase __cacheline_aligned;
...
874 /*
875  * Step our concept of UTC.  This is done by modifying our estimate of
876  * when we booted.
877  */
878 void
879 tc_setclock(const struct timespec *ts)
880 {
...
890 	timebase.gen |= 1;	/* change in progress */
...
892 	timebase.bin = bt;
...
894 	timebase.gen++;		/* commit change */
...
905 }

N の vmt(4) による時刻同期 はここで再登場した tc_setclock() という 関数で時刻同期を行っていたのを思い出してほしい。 さらに RFC868 Time Protocol 回 で解説した resettodr() も再登場している。

もうおわかりですね、tc_setclock() の正体は 4.3BSD の settimeofday(2) が行っていた現在時刻 struct timeval time に相当する timebase.bin 「だけ」を更新し resettodr() を呼ばないので RTC(RealTime-Clock) は更新されないという事。

VMware Tools では普通に settimeofday(2) で RTC 更新するんだけど、実マシンと同じイメージで RTC 更新は重い処理って回避したのだろうかね。 仮想マシンなんだから無意味と思うんだよなこれおそらく。

adjtime(2) を読む

ということで settimeofday(2) と同じように sys___adjtime50() を探せとなるが、最新の実装は読むのクソかったるいので、またまた 4.3BSD へと回帰する。

86 adjtime()
87 {
88 	register struct a {
89 		struct timeval *delta;
90 		struct timeval *olddelta;
91 	} *uap = (struct a *)u.u_ap;
...
98 	u.u_error = copyin((caddr_t)uap->delta, (caddr_t)&atv,
99 		sizeof (struct timeval));
...
120 		(void) copyout((caddr_t)&oatv, (caddr_t)uap->olddelta,
121 			sizeof (struct timeval));
122 }

この時代は libc 側も kernel 側も同じ名前であるが引数がない。 なので 引数 deltaolddeltau.u_ap 経由でアクセスする。

このu という変数は以下で定義されている。

30 struct	user {
...
36 /* syscall parameters, results and catches */
37 	int	u_arg[8];		/* arguments to current system call */
38 	int	*u_ap;			/* pointer to arglist */
...
50 	char	u_error;		/* return error code */
125 };
...
140 extern	struct user u;

user 構造体の u_ap フィールドで引数リストのポインタとしてアクセスできるって寸法。

引数がポインタの場合アドレスだけもらっても困るので、ユーザースペースとカーネルの間でコピー処理が必要。 よって copyin(2) を使え。

現代のカーネルの sys_* プリフィクス付システムコールだともうちょっとめんどくさい。 グローバル変数 u ではなくシステムコール自身の第二引数からさらに SCARG というマクロを使う。

575 /* ARGSUSED */
576 int
577 sys___adjtime50(struct lwp *l, const struct sys___adjtime50_args *uap,
578     register_t *retval)
579 {
580 	/* {
581 		syscallarg(const struct timeval *) delta;
582 		syscallarg(struct timeval *) olddelta;
583 	} */
584 	int error;
585 	struct timeval atv, oldatv;
...
592 		error = copyin(SCARG(uap, delta), &atv,
593 		    sizeof(*SCARG(uap, delta)));
...
600 		error = copyout(&oldatv, SCARG(uap, olddelta),
601 		    sizeof(*SCARG(uap, olddelta)));
602 	return error;
603 }
138 #if	BYTE_ORDER == BIG_ENDIAN
139 #define	SCARG(p,k)	((p)->k.be.datum)	/* get arg from args pointer */
140 #elif	BYTE_ORDER == LITTLE_ENDIAN
141 #define	SCARG(p,k)	((p)->k.le.datum)	/* get arg from args pointer */
142 #else
143 #error	"what byte order is this machine?"
144 #endif

この辺については大阪人の行けたら行くくらいの実現性でいずれまた解説記事を書きたいと思う。

そんで以下が adjtime(2) が参照あるいは書換を行うグローバル変数である。

81 extern	int tickadj;			/* "standard" clock skew, us./tick */
82 int	tickdelta;			/* current clock skew, us. per tick */
83 long	timedelta;			/* unapplied time correction, us. */
84 long	bigadj = 1000000;		/* use 10x skew above bigadj us. */

クロックスキューとは何ぞや、あの一時期やたらと流行した合成樹脂のサンダルの事かな?

電気回路屋なら知ってて当然なんだろうが俺はチャラい大学の文系卒で経済学部で西洋音楽史を研究していたオッサンだ。 ギターの可変抵抗を右に回したら抵抗が上がるのか下がるかレベルの電気回路知識すら無いぞ俺。

たぶん音がデカくなるんだから抵抗もどーんとデカくなるんやろロックってのは世間様への精一杯の抵抗やガハハ。 David Bowie 神も TO BE PLAYED AT MAXIMUM VOLUME とおっしゃられとるわ。

通常クロックスキューって遅延で生じたズレの差のことだと思うんだが、tickadjtickdelta はズレを補正するために一回のクロック割り込みでどれだけ調整するかの値だからニュアンス違うよな。

うーんズレを打ち消すためにズラすのもクロックスキューと呼んでいいのか電気回路屋じゃないし英語ネイティブでもないしわからん。

まあいいやクロック割込みすなわち hardclock(9) の解説については

3.4 時計割込み

3.4 クロック割込み

をどうぞ、俺は hardclock(9) とハードロックの違いすら判らないのだ(流れる Led Zeppelin の Im My Time Of Dying)。

この記事読む上で知っとけばいい事はクロック割込みは実時間タイマーともいい、例えば 過去回 で書いた time_uptime(9) やさっき登場した timebase.bin をカウントアップするのもこいつのお仕事であるくらいだな。

とはいえ本読め本だとこの後の話進まなくなるのでこっちも軽く実装みてみよか。

40 #define	HZ 100
41 int	hz = HZ;
42 int	tick = 1000000 / HZ;

クロック割込みが 1 秒間に 100 回発生する場合

  • 周波数で表現すると 100 Hz となり hz は 100 となる
  • マイクロ秒で表現すると 10000 マイクロ秒 となり tick は 10000 となる

わけですな。

しかしクロックジェネレーターはたいして精度が高くない。 原子時計ですら数億年に 1 秒くらいズレるし、いわんやクオーツの精度などお察しである。 このクロックがズレていく現象がクロックドリフト、テンポがズレて気持ちいいのは Steve Reich の音楽くらいなので、補正が必要になる。

その補正は 60 秒間に最大 240 ミリ秒まで可能、それを一回のクロック割込分にした値が tickadk である。

43 int	tickadj = 240000 / (60 * HZ);		/* can adjust 240ms in 60s */

以上の事をふまえて ajdtime(2) の残り部分読んでこか。

ノイズとなるシステムコール作法や排他制御を取り除くと以下のようになる。

93 	register long ndelta;
...
102 	ndelta = atv.tv_sec * 1000000 + atv.tv_usec;
103 	if (timedelta == 0)
104 		if (ndelta > bigadj)
105 			tickdelta = 10 * tickadj;
106 		else
107 			tickdelta = tickadj;
108 	if (ndelta % tickdelta)
109 		ndelta = ndelta / tickadj * tickadj;
...
112 	if (uap->olddelta) {
113 		oatv.tv_sec = timedelta / 1000000;
114 		oatv.tv_usec = timedelta % 1000000;
115 	}
116 	timedelta = ndelta;

ndelta は第一引数で指定された補正が必要な時間をマイクロ秒に変換したもの。

timedelta ゼロは現在 hardclock(9) で補正が行われてないということ。 補正が行われてる間は tickdelta は変更しない(理由がよくわからない)。

tickdelta は一回のクロック割込みで補正する時間。 通常は tickadj となるが ndelta が 1 秒を超える場合はその 10 倍として補正を急ぐ。

ndeltatickdelta で割って余りがある場合は ndeltatickadj の倍数で揃える (これ tickdeltatickadj の倍数ではあるけどここで剰余求めるのは tickadj だよなぁ)。

timedeltandelta で更新(以前の値は第二引数で返す)

次回以降のクロック割込みで timedelta で指定した時間分のクロック補正が開始される。

まぁこんなかんじ。

次回

次回は hardclock(9) で timedelta および tickdelta がどう扱われるかを確認する。 それと adjtime(2) の 4.3BSD 以降の進化について書けたら書く。