timed(8) の実装を読む[後編] - インターネット時刻同期の歴史(その4)
前編は こちら 。
スレーブ側のコードを読む
スレーブは最初に TSP_SLAVEUP コマンドで奴隷ネットに参加する。
19 slave()
20 {
...
39 struct tsp resp;
...
45 if (slavenet) {
46 resp.tsp_type = TSP_SLAVEUP;
47 resp.tsp_vers = TSPVERSION;
48 (void)strcpy(resp.tsp_name, hostname);
49 bytenetorder(&resp);
50 if (sendto(sock, (char *)&resp, sizeof(struct tsp), 0,
51 &slavenet->dest_addr, sizeof(struct sockaddr_in)) < 0) {
52 syslog(LOG_ERR, "sendto: %m");
53 exit(1);
54 }
55 }
マスター側での TSP_SLAVEUP コマンドの処理は前回解説した通り。
スレーブもビジーループだが逐一コマンド処理する必要あるしもういいやこれで(世界陸上投げやり男子金メダリスト)。
83 (void)gettimeofday(&time, (struct timezone *)0);
84 electiontime = time.tv_sec + delay2;
...
93 loop:
...
95 (void)gettimeofday(&time, (struct timezone *)0);
96 if (time.tv_sec > electiontime) {
...
99 longjmp(jmpenv, 1);
100 }
...
146 wait.tv_sec = electiontime - time.tv_sec + 10;
147 wait.tv_usec = 0;
148 msg = readmsg(TSP_ANY, (char *)ANYADDR, &wait, (struct netinfo *)NULL);
149 if (msg != NULL) {
150 switch (msg->tsp_type) {
...
531 }
532 }
533 goto loop;
この delay2 と longjump(jmpenv) については main()
を読む必要がある。
32 #define MINTOUT 360
33 #define MAXTOUT 900
...
82 extern long delay1, delay2;
34 long delay2;
...
71 main(argc, argv)
72 int argc;
73 char **argv;
74 {
...
332 /* election timer delay in secs. */
333 delay2 = casual((long)MINTOUT, (long)MAXTOUT);
334
335 if (Mflag) {
...
349 ret = setjmp(jmpenv);
350
351 switch (ret) {
...
388 }
399
390 if (status == MASTER)
391 master();
392 else
393 slave();
394 } else {
...
397 if (setjmp(jmpenv)) {
...
400 }
...
406 slave();
407 }
408 }
...
575 long
576 casual(inf, sup)
577 long inf;
578 long sup;
579 {
580 float value;
581
582 value = (float)(random() & 0x7fffffff) / 0x7fffffff;
583 return(inf + (sup - inf) * value);
584 }
delay2
は 360 秒から900 秒までのランダムな値。
casual()
が何やってんのかというと random()
の戻り値を 0x7fffffff
でマスクし 0x7fffffff
で割ると value
は必ず 1 以下になるので戻り値は inf
から sup
の間のランダムな数字になる。
これ
RANDOM_MAX
使えばマスク要らない(イーロンもね、衛生マスクはまだ着用しろ)よなと思ったが当時まだ存在しなかったわ。
それよりも今の時代なら
arc4random_uniform(3)
で書き直せとなる。
なぜ delay2
ひいては electiontime
をランダムにするかというと、新しいリーダーの選出要求投げるタイミングをスレーブの間でバラバラにしたいから。
electiontime
に達するとスレーブは脱奴隷化を図るために
longjmp(3)
で main()
に戻る。
ただし Mflag
が立ってて自らもマスター立候補しリーダー選挙に勝たない限りはそのまま slave()
に戻ってくるだけである。
脳の腐食が進行してるとマゾフラグ立ってるのに奴隷止めてご主人様になりたいとは…と混乱するが Mflag
はマゾの意味じゃねえから!
-M Allow this host to become a timed master if necessary.
つーわけで数分毎にリーダー選挙やっててずいぶんと忙しいなって印象を受けるのだが、マスターの死活監視も兼ねてるのでそう考えると妥当なのかもしれない。
TSP_ADJTIME コマンドを処理する
マスターから TSP_ADJTIME コマンドを受信したら時刻調整である。
181 case TSP_ADJTIME:
...
184 (void)gettimeofday(&time, (struct timezone *)0);
185 electiontime = time.tv_sec + delay2;
186 if (seq != msg->tsp_seq) {
187 seq = msg->tsp_seq;
...
192 adjclock(&(msg->tsp_time));
...
194 }
195 break;
adjclock()
についてはマスター側で説明したからいいよねもう。
さて adjtime(2) の実装も読まないとならないがそれはまた次回に。
TSP_SETTIME コマンドを処理する
マスターから TSP_SETTIME コマンドを受信したら時刻の強制同期である。
196 case TSP_SETTIME:
...
204 (void)strcpy(olddate, date());
205 (void)gettimeofday(&otime, (struct timezone *)0);
206 (void)settimeofday(&msg->tsp_time,
207 (struct timezone *)0);
208 syslog(LOG_NOTICE, "date changed by %s from: %s",
209 msg->tsp_name, olddate);
...
229 break;
これも RFC868 Time Protocol の回 で解説済の settimeofday(2) なのでそっち参照。
次回
ついに本丸 adjtime(2) システムコールに攻め込むはず。