UNIX における現在時刻情報管理の変遷[First Edition UNIX 編] - インターネット時刻同期の歴史(その8)
First Edition UNIX(V1 UNIX)
ターゲットが PDP-7 から PDP-11 になったことで完全にコードが書き直されている。 アセンブラで開発する以上避けられない問題だがこれは非常に負担が大きいよね。
それもあって Ken Thompson は機種依存しないプログラミング言語 B を作り一部のコマンドの実装に使っていたのだけど、かなり限定的なものだった。
そしてカーネルは機種依存コードが多いのでいまだ全てアセンブラである。
前回同様に OCR でソースコード復元し詳細なコメントをつけたバージョンが github/DoctorWkt/unix-jun72 から利用可能となっている(以前は Google Code で公開してた)。
ただしこれ V1 UNIX のカーネルに V2 UNIX のユーザーランドというチャンポンのようである。 そもそも C コンパイラがあるんだがプログラミング幻語 C の登場は V2 UNIX のはずである。
どうも断片的にしかコードが現存していないみたいね、バージョン管理じゅーよー。 当時ベル研には 1972 年から SCCS(Source Code Control System) が存在してたけども IBM System/370 上で動くもので UNIX への移植はまだだったのである。
ところでこいつも実は tools 以下の方が面白いんじゃないか説あると思います。
- apout … PDP-11 上の V1/V7 UNIX/2.11BSD のバイナリエミュレータ
- disaout … PDP-11 ディスアセンブラ
- mkfs … V1 UNIX ファイルシステム作成
ただ apout は俺のメイン環境である Cygwin だと足りないシステムコールなどがあり修正量ちょいと多めであった。 プラスして python3 で動かなくなったスクリプトを対策した差分をご用意したので使ってくれ、まぁいまだに Cygwin 使ってる人間がいるのか謎ではあるが。
足りないものはなるべく代替機能に置換えたつもりだけど欠けたままのもいくつか。 まぁ V1 UNIX のビルドに as(1) 動かす程度なら問題無さそうだし気にせんでええ。
まずは資料を用意する
PDP-11 の資料も ここ に大量に転がってるけれど、多過ぎてどれ読めばいいやらである。
ベストセラー機なので、学生向けの「 やさしくスッキリわかるこれからはじめる独習 PDP-11 超入門 」みたいな冊子があるからそれ読めば完全理解できそうではある。
だがいまさら PDP-11 のお仕事なんて老朽化した原子力発電所とかプラントとかやべーところだから胃が牛の数だけあっても神経持たんのである。
カーネルソースコードの構成
カーネルソースは pages 以下にあるのだが、OCRしたテキストそのまんまで詠む用では無さげ。
そんでトップディレクトリで make all を実行すると build/ 以下に
- 不鮮明な印刷による OCR の認識ミス
- 元コードのバグなんかの修正
の差分が当たったカーネルソースが生成されるもよう。
$ ls build/*.s
build/init.s build/u1.s build/u4.s build/u7.s build/ux.s
build/sh.s build/u2.s build/u5.s build/u8.s
build/u0.s build/u3.s build/u6.s build/u9.s
このうち sh.s と init.s はそれぞれ sh(1) と init(8) のコードなので無視してよろしい。
いちいちビルドしたくねえというものぐさは UNIX Archive に成形済ファイルあるのでそっち参照の事。
ux.s
1 / ux -- unix
2
3 systm:
4
5 .=.+2
6 .=.+128.
7 .=.+2
8 .=.+64.
9 s.time: .=.+4
10 s.syst: .=.+4
要はカーネルで使う記憶域(変数)一覧である。
前回の PDP7-UNIX でいえば sysmap のうち記憶域のラベルを抜き出したものに近いのだが
- ラベルでグループ分けしたり
- アドレスを直値で書かずバイト単位のオフセットで書けたり
と 0 歳児が 1 歳児になって言葉のようなものを喋りはじめたかのようである。 あーうーいってるだけで何言ってんのかさっぱり判んねえんだけどな!
すでに s.time
の文字がみえてるので先は短そうである、+4
つまり 4 バイト割り当てられている。
PDP-11 は 16bit ワードなので バイトは 8bit よって s.time
は 32bit となる。
そういえば PDP-7 では +
がワード単位だったのに PDP-11 ではバイト単位なのがプログラミング幻語 C を感じませんかね、よう知らんけど。
ところで sysmap にあったシステムコールのラベルはどこいった?と頭ひねる読者の方もいるだろう(鏡と会話)、答えは sop.s のニーモニック表と一緒に as(1) のビルトインとなり不要となった、である。
なんと as(1) は 2 パスなのだが最適化あるんかな、こっちは pass1
42 / register
43
44 072270;000000;24;000000 /r0
45 072340;000000;24;000001 /r1
46 072410;000000;24;000002 /r2
...
70 / system calls
71
72 021411;076400;01;0000001 /exit
73 023752;042300;01;0000002 /fork
74 070511;014400;01;0000003 /read
こっちは pass2
12 / register
13
14 24;000000 /r0
15 24;000001 /r1
16 24;000002 /r2
...
40 / system calls
41
42 01;0000001 /exit
43 01;0000002 /fork
44 01;0000003 /read
シンボルテーブル見つからんかったがコメントから動的に作ってるのかなこれ。 さすがに今読む気にならねえ…
u[連番].s
相変わらずやる気ねえ命名のファイル群、こいつらがカーネルソースコードである。
プログラムの開始地点を探す
PDP7-UNIX での豊富な経験(ペラッペラ)から安直に u[連番].s の先頭を開いてみた。
1 / u0 -- unix
2
3 cold = 0
4 orig = 0 . / orig = 0. relocatable
5
またしても orig
の文字が見えたので多分ここやろガハハ。
PDP-7 だとハードウェア I/O は 0 から 18 番固定で 7
だとか
6
だとか直値で叩いてたけれど、PDP-11 は Unibus という方式で先頭 8k バイトが MMIO(Memory-Mapped I/O) 領域となっているそうである。
詳しくは
を参照のこと。
6 rkda = 177412 / disk address reg rk03/rk11
7 rkds = 177400 / driv status reg rk03/rk11
8 rkcs = 177404 / control status reg rk03/rk11
9 rcsr = 174000 / receiver status reg dc-11
10 rcbr = 174002 / receiver buffer reg dc-11
...
よって PDP-7 とは異なり自分で機器を列挙する必要があるわけである。
なお後期型になると Massbus というものも登場するそうだが生憎俺はバスマニアじゃないので無視する。
横浜市なのに市バスでなく神奈中バスが走る棄民の地。 さらに走る道路ときたら県内渋滞ワースト1位つーかおそらく都心抑えて日本一。 ラッシュ時は駅まで 1 - 2 時間で事故渋滞ともなれば 4 時間(通常 20 分)。 雨の日の混雑といったら朝の田園都市線すらソーシャルディスタンスってレベルの高圧縮率。 こんな僻地住んでるとバスは憎悪の対象なのである(突然 PTSD を発症する患者)。
もう少し下を読んでいくと RTC(Real-Time Clock) と思われる lks
と名付けられたレジスタを発見。
20 lks = 177546 / clock status reg kw11-l
...
135 / set devices to interrupt
136
137 mov $100,*$lks / put 100 into clock status register;
138 / enables clock interrupt
何の略かは不明であるが「Lealtime KlockS」という日本の底辺SIerでよく目撃するスペルミスの可能性が微粒子レベルで存在する、実は ken って名前もそれっぽいし日本人なのでは?(陰謀論者しぐさ)
前述の PDP-11 UNIBUS Proccessor Handbook の 2-33 を読むと
- RTC は Line-time Clock という名前
- そのレジスタ名が Clock Status Register (LKS)
となっとるので謎である、プロセッサ名も J-11 で無関係みたいだしなぁ。 DECお得意の i18n/l10n みたいな数略語 (numeronym)かもしれぬ。 k=1024 よって l1024s つまり l ではじまり s で終わる長さ 1026 の単語かもしれない。
さらに DEC 技術者の謎のこだわりを感じたのが lks
の 0~15bit がそれぞれ意味する事の解説。
[15] <- UNUSED
...
[ 7] <- LINE CLOCK MONITOR
[ 6] <- LINE CLOCK INTERRUPT EN
...
[ 0] <- UNUSED
6 と 7bit 目が PDP-7 の RTC I/O の 6 と 7 番に対応してるのねこれ。
最初このコードって PDP-7 は 1/60 秒固定だけど PDP-11 は可変で 1/100 秒つまり 100Hz でタイマー割込を発生させるんかな?と思ったんだけどこれ 10 進でなく 8 進で 6bit 目をオンにしたってこと。
そしてクロック割込ハンドラは clock
というラベルのもよう。
59 . = orig+60
...
64 clock;340 / clock interrupt vector ; processor level 7
340
が processor level 7 ってのは PDP-11 の割込優先レベルには 0 から 7 まであり
最も優先度が高い割込という事ね。
clock ラベルを読む
じゃあクロック割込ハンドラのコード読むよ。
11 clock: / interrupt from 60 cycle clock
12 mov r0,-(sp) / save r0
13 tst *$lks / restart clock?
14 mov $s.time+2,r0 / increment the time of day
15 inc (r0)
16 bne 1f
17 inc -(r0)
まず汎用レジスタの r0
を退避し RTC を再起動する。
次に r0
に s.time
の下位 16bit を置いて加算しオーバーフローフラグ立ってたら -(r0)
つまり s.time
の上位 16bit を加算ちゅーこと。
なんかエンディアンおかしくね?って思ったそこのあなた(無)、これは PDP-11 endian というやつ。
リトルとビッグの混成で 0x01234567
は以下のバイト列になるのだ。
endian | 0 | 1 | 2 | 3 |
---|---|---|---|---|
big | 0x01 | 0x23 | 0x45 | 0x67 |
little | 0x67 | 0x45 | 0x23 | 0x01 |
pdp-11 | 0x23 | 0x01 | 0x67 | 0x45 |
はい、今回も s.time
は 1/60 秒ごとに加算される値で 32bit だから 828 日つまり 2 年と 3 ヶ月でターンオーバーするってこと。
stime(2) システムコールが増えた
時刻を取得する time(2) に加えて時刻を変更する stime(2) が追加された、現在の settimeofday(2) 相当の機能ですな。
408 systime: / get time of year
409 mov s.time,4(sp)
410 mov s.time+2,2(sp) / put the present time on the stack
411 br sysret4
412
413 sysstime: / set time
414 tstb u.uid / is user the super user
415 bne error4 / no, error
416 mov 4(sp),s.time
417 mov 2(sp),s.time+2 / set the system time
418 br sysret4
システムコールのプリフィクスが .*
から sys*
に変更になってる。
とはいえ PDP-7 でバイナリ互換無いから問題にはならんのである。
これで特権ユーザーによる時刻変更が可能になった、ようやっとまともな時計カレンダー機能を UNIX は獲得したのである。
date(1) の誕生
前回 PDP7-UNIX 上で動かした date(1) は後世に追加されたもので正しい現在時刻を表示できなかった。
しかし今度はちゃんと date(1) が実装されました。
NAME date -- print the date
SYNOPSIS date
DESCRIPTION The current date is printed to the second.
NAME sdate -- set date and time
SYNOPSIS sdate mmddhhmm
DESCRIPTION sdate adjusts the system's idea of the date and time. mm is
the month number; dd is the day number in the month; hh is
the hour number (24--hour system); mm is the minute number.
For example,
sdate 10080045
sets the date to Oct. 8, 12:45 AM.
マニュアルには sdate(1) って謎のコマンドが存在する。 だが実際に simh 上で V1 UNIX 動かしてみると date(1) で時刻変更が可能だし この sdate(1) を実行しようとすると
# sdate 02211920
No command
#
と怒られるので実装とドキュメントの不一致なんやな。 ユーザーランドは V1 と V2 のチャンポンだから V1 では sdate(1) が存在して V2 で date(1) に統合されたのかなぁ。
なるほど現在でも date(1) が date [MMDDhhmm[[CC]YY][.ss]]
というクソ書式でマニュアル読まないと思い出せないのは V1 UNIX 時代からの互換性のためだったのか。
コードの解説はしないので読みたい人は読んでくれ。 なお libc おじさんとしては strftime(3) も無い時代に date(1) がアセンブラで必死にヒューマンリーダブルな形式に編集してるの涙ぐまし過ぎて飯が旨い。
代わりに日数から月を算出するためのテーブルのコードだけ貼る。
95 mtab:
96 0
97 31.
98 59.
99 90.
100 120.
101 151.
102 181.
103 212.
104 243.
105 273.
106 304.
107 334.
去年アレしたしもう 33-4 ネタもお寒い限りなのである。
結論
ついに V1 UNIX にして正しい現在時刻が取得できるようになり、壁掛時計や腕時計を見る必要が無くなったのである。
いやまぁやっぱり RTC に現在時刻を保存する機能が無いので、起動するたびに時刻再設定が必要になるけれどもな!
次回
V2 は UNIX Archive にカーネルソース(マスタード味)が存在しないので飛ばして V3 ですかね。 ついにカーネルもプログラミング眩語 C となる頃合いだし。
とはいえ本来の目的からすると蛇足もいいとこなんだよね。 それに C なら解説不要なくらい単純なコードなわけで。