この記事は自作OS Advent Calendar 2020 7日目の記事となります。
はじめに
現在のオープンソースOSは、たとえばLinux開発ボードであればボードベンダーから移植済みのLinux環境が提供されたり、たとえばNetBSDであればクロスコンパイル環境が整備済みでドキュメントも用意されていて、最低限の移植作業で移植が完了したりします。
ぼくがNetBSDを移植した当時(1993年)はそうではありませんでした。ドキュメントもなくいろいろ手探りで、それも一人でやらざるを得ませんでした。苦労話のことは置いておいて、技術的にどういう物が用意され何を調べてどういう手順で移植していったかを記録に残せればと思います。(って前置きした割に苦労話が多いような気がします、すみません)
かなり昔の話なので、けっこう忘れてることも多く、微妙に記憶が間違っていたりすることも、順番が前後していることもあるかもしれませんが、予めご了承ください。
以降、文体変わりますがご容赦ください。ではスタート!
移植を始めるまで
X68030発売前夜
1990年前半。X68000ユーザーの中にはUNIXにかぶれた人が大勢いた。筆者もその一人である。メーカー製のOSであるHuman68kはMS-DOSのようなつくりだったが、自作プログラムや移植プログラムを持ち寄ってUNIX風のCLI環境を構築できるほどだった。bash
が動き、vi
クローンやemacs
で編集でき、gcc
でコンパイルできる。
正確な年は把握できていないが(追記:1992-1993年とのこと)、当時国産パソコンの雄であるPC-9801シリーズに386BSD 0.1を移植しした有志がいたことを覚えている。それに対してX68000のプロセッサはメモリ管理ユニット(MMU)を持たないMC68000。仮想記憶を必要とするメモリ保護機能を持ったOSを動かすには力不足だった。本物のBSDを動かすのは諦めるしかなかった。
そんな中、UNIX-likeと言われたOS-9/68000に魅力を感じた人も、当時は教育向けとされていたMINIX 1.5を移植しようとする人もいた。OS-9/68000の移植版であるOS-9/X68000は実際に市販され、筆者も一時期使っていた。
1993年3月 X68030発売
X68000発売から5年。待望の新機種! と思いきや機能的な拡張はほぼなく、高速なX68000という位置づけのマシンだった。MPUは68EC030、つまりMMUを内蔵していない。ただし、MPUはメインボードのソケットに装着されているため、ピン互換であるMMUを内蔵したMC68030と差し替えることが技術的に容易であった(筐体を開ける必要があり、当然メーカー保証の対象外となる)。
ハードウェア的には、道が開けた!
1993年4月 NetBSD 0.8公開
386BSDのpatchkitを基として作られたNetBSDでは、アーキテクチャとしてi386, hp300, amigaがサポートされていた。マジか! と驚いたのを覚えている。
i386は言わずと知れたIBM-PC/AT互換機。hp300はHPのワークステーション HP 9000 モデル300シリーズでamigaはコモドールのAmiga、これらはモトローラ系プロセッサを搭載したハードウェア。プロセッサ周りのコードを一からすべて用意するのはしんどいと思っていたので、X68030と同系のプロセッサを搭載したハードウェアがサポートリストに乗っているのは非常に助かると思ったのを覚えている。
NetBSDのソースコードは公開後あまり時間をおかずNIFTY-Serve FUNIX(UNIXフォーラム)にアップロードされたため、これをダウンロード。カーネルソースコードのアーカイブだけで何時間かかったか、もはや覚えていない。ishエンコードされたテキストが何分割だったかも、もう覚えていない。
あとになって、カーネルソースコードだけではビルドできないことがわかり、周辺ツールのソースコードもダウンロードした。
1993年5月 X68030、MC68030、MC68881購入
X68030本体を買ったのは正確にはいつか覚えていない。GWだったのは確かで、4月下旬か5月頭。秋葉原のツクモパソコン本店の3Fだか4Fあたりで買って、ハンドキャリーで持ち帰った。当時は横浜に住んでいて、家は駅から徒歩20分。重くてしんどかった記憶しかない。
MC68030(プロセッサ)とMC68881(浮動小数点コプロセッサ)は後日ラジオデパートで。たしかエレパ。
移植開始
スタートライン
- 当時勤めていた会社でSunOS 4をさわってたのでUNIXオペレーションの知識はある
- オープンソースなOSを扱うのは利用を含めて初めて
- m68k asm, Cはそれなりに読み書きできる
- デバイスドライバはMS-DOSのようなもので大雑把な構造だけ知ってた
- 当時は、インターネットに情報が落ちてたりはしなかった
- 基本的に作業は会社勤務が終わって帰宅したあと、あるいは休日
- 完全に趣味なので期限はない。好きなだけやる。諦めたらそこで試合終了
その1 ビルド方法を調べる
カーネルのソースコードを広げてみた。Makefile
を探してみる。
ない。
えー?
どうやってコンパイルすればいいんだ。カーネルソースコードのアーカイブにはドキュメントが含まれていないので皆目見当がつかない。アーカイブを全部ダウンロードできていればどこかに書いてあったのかも知れないが、通信費も時間もなかったし親切なドキュメントの存在しないソースコードは当時はけっこうあったので、探そうとはしなかった。
しかたなく広げたファイルに何があるか眺めていると、Makefile
そのものではないが、Makefile.i386
だのMakefile.hp300
だのいうファイルが見つかる。
中を見るとMakefile
のようで部分的にMakefile
じゃない。コメントを見ると、どうやらconfig
というコマンドとこのファイルを使ってMakefile
を生成するようだ。
# Copyright 1990 W. Jolitz
# @(#)Makefile.i386 7.1 5/10/91
# Makefile for 4.3 BSD-Reno
#
# This makefile is constructed from a machine description:
# config machineid
# Most changes should be made in the machine description
# /sys/i386/conf/``machineid''
# after which you should do
# config machineid
# Generic makefile changes should be made in
# /sys/i386/conf/Makefile.i386
# after which config should be rerun for all machines.
一言余計な補足をすると、現在のNetBSDではsys/arch/i386
だが、0.8のころはarch
ディレクトリがなかった。FreeBSDはいまでも同じ流儀でarch
がない。
その2 クロスコンパイル環境構築
まずconfig
だ。専用のコマンドということでアーカイブもあったので、拾ってビルドしようとする。が、make
するとエラー。コンパイルエラーでなくmake
自身のエラー。Makefile
を確認してみたところ、記述が知っているものと違う。
.include <bsd.prog.mk>
ナニコレ?
当時すでにHuman68kでは、有志によって移植されたGNU Makeが動いていた。が、どうもこのMakefile
はGNU Makeと互換性がないようだった。結論としては、この互換性のないMakefile
は、BSD Makeのsyntaxで書かれていた。config
だけなら書き換えてしまうのも手だったが、カーネルのMakefile
も同様のsyntax。つまり、BSD Makeが必要だ。
もし仮にBSD Makeを用意しconfig
からカーネルのMakefile
が作れてmake
できるようになったとして、Cコンパイラは?
Human68kには、Human68kで動かすオブジェクトフォーマットを吐き出すgcc
がすでに移植されていたが、当時のNetBSDはa.outフォーマットだったので、a.outフォーマットのバイナリを吐くビルドツールが必要と考えた。したがって、構築に最低限必要なのは下記。
- Human68kで動作するBSD Make
- Human68kで動作する
config
コマンド - Human68kで動作する、a.outを吐くgas, GNU ldとそれを呼び出すgcc
幸い、元々Human68kにあったファイル名の制限はTwentyOneというプログラムで緩和されていて、execution bitやsymbolic linkは自前のプログラムで使えるようになっていた。それにくわえてBSD Make自身はそれがない環境でビルドできるよう簡易なスクリプトだったかバッチファイルのようなMakefile
だったかが用意されていたので、1.と2.はすんなりコンパイルするだけで完了した。
(思い出したので12/8追記: Human68k用に、UNIX風のlibcを作成するプロジェクトがあり、パブリックドメインだったかフリーソフトウェアだったかとして公開、また収録されたメディアを付録とした書籍も発売されていて、それのおかげでソースコードの変更が全く不要だった)
3.はターゲットOSであるNetBSDがまだautoconfに組み込まれてなかったので、そのあたりをいじくったような記憶がある。またHuman68k上でビルドすることはつまりクロスコンパイルになるため、そのあたりどうやればいいかも若干悩んだ記憶がある。gccのビルドには結構時間がかかった。Human68kはシングルタスクOSで、それ以前にX68030はさほどパワフルなマシンではなかったので、待つしかない。
sed
やawk
も動きがちょっと違っていたのか、移植のためにビルドしたような、しなかったような。残念ながら覚えていない。
その3 x68kのツリーを生やす
ビルドを試せそうなところまで環境構築できたので、ひとまずベースとなるアーキテクチャからファイルをコピーしてx68kのツリーを用意する。hp300とamiga、どちらもm68kだがどちらをベースにするか考えて、ホビーパソコンでカテゴリが似ていることもありamigaをベースにすることにした。最初はコピーしてリネームするだけ。形から入る。
その4 NetBSDカーネルのコンパイル
コピーできたらビルドしてみよう。まずは何も考えずconfig
して、cd
で移動して、make
。カーネルだけ。でも綺麗な状態からすんなり最後まで走って数時間かかる。make clean
をサボっても30分とかかかる。これは待つしかない。当時のNetBSDにはIPv6対応コードが含まれていなかったので、現在と比較するとかなりコンパクトではあったのだけど。ビルドエラーになったら原因を調べて対処。
以降の移植作業では、コードを用意する作業を終えたらmake
を仕掛けて寝る、朝にはカーネルが出来上がってるかエラーで落ちるかしてる、の繰り返しだった。
その5 不要なコードの削除
amigaから持ってきたコード、再利用できそうにないものもそれなりにあったので、削除、削除、削除。
その6 メモリレイアウトの変更
amigaとx68k、プロセッサが同じでも他が違う。いろいろ違う。まずメモリレイアウトからして違う。
メモリの物理アドレスに関して調べると、たしかamigaの場合カーネルのアドレスは仮想アドレス(VA)=物理アドレス(PA)となっていたと思う。ブート時にMMUが無効であっても、VA=PAとなるようページテーブルを構築してMMUを有効化すれば、なんの問題もなく続きを実行してくれる。X68030の物理0番地にはRAMが配置されている(起動直後はROMが見える細工がしてあったと思う)。せっかくなのでamigaのやりかたを踏襲することにした。と思う。まったく覚えてないけど。
m68kはx86と違ってI/O空間というものがなく、メモリアドレスのどこかにI/Oをつなげる、いわゆるメモリマップドI/Oとなっている。当然どんなI/Oデバイスがどのアドレスにつながっているかはハードウェアによってまったく異なるため、変更が必要となった。
その7 シリアルドライバの作成
最初はシンプルにシリアルコンソールにしようと考え、シリアルドライバを調べてみる。IBM-PC/AT互換機では16550 UARTが標準的だが、amigaは独自? X68030のシリアル通信LSIはZ8530 SCC。当時NetBSDにはZ8530ドライバがなかったので、他のドライバの中身を削ってスケルトンとし、作成することにした。
カーネル起動直後は割り込みが無効なので、そのときは割り込み無しで読み書きするようなコードを用意した。つまり汎用のシリアルドライバとは別にコンソールドライバのエントリも用意することとなった。
ユーザ空間からデバイスドライバをアクセスするためにはスペシャルファイル(/dev/com0
みたいなやつ)を作る必要がある。スペシャルファイルの名前はさほど重要ではなく、重要なのは
- デバイスタイプ(
character
,block
,fifo
のどれか) - メジャーデバイスナンバー
- マイナーデバイスナンバー
当時のNetBSDでは、これらの情報をカーネル側とスペシャルファイル作成側で整合性をとって手作業で採番する必要があった。
スペシャルファイルの例は下記。
$ ls -l /dev/console
crw------- 1 masaru tty 0, 0 Dec 4 08:42 /dev/console
$ ls -l /dev/sd0
brw-r----- 1 root operator 4, 3 Apr 14 2020 /dev/sd0
ls -l
で見たとき先頭がデバイスタイプ、日付の前のカンマ区切りの2つの数字がmajor, minorとなっている。つまり下記のように読み解ける。
-
/dev/console
はキャラクタデバイス、major=0, minor=0 -
/dev/sd0
はブロックデバイス、major=4, minor=3
手作業でスペシャルファイルを作成するときはmknod
を使うが、その時はこれらの情報を知っている必要がある。NetBSDでは/dev/MAKEDEV
というシェルスクリプトを使って、スペシャルファイル名の指定だけでスペシャルファイルを作成することができるが、その実現のため、移植時はファイル名とデバイスタイプ、major, minor等の対応を手作業で埋めていく必要があった。
その8 仮ブートローダーの作成
カーネルのバイナリができあがった。そのままじゃ動かないのはわかってるけど、試さないことには始まらないので、ブートローダーを作ることに。m68kアセンブラでゴリゴリ書く。そもそもファイルシステムをどこにどう置くか。とりあえず一番最初は、BSDのファイルシステムはどこかにあるとして、それとは別にHuman68kのFATファイルシステムに置かれたカーネルを読めればいいだろうということで、仮のブートローダを作成する。IOCS(BIOSみたいの)で読めるので楽勝。
その9 モニターとキーボードドライバ
さすがにシリアルコンソールのままでは誰にも使ってもらえないだろう、ということで、シリアルドライバと並行して、内蔵ROMからフォントパターンを拾ってフレームバッファに描画するドライバを書いた。amigaのコードが参考になった。デイスプレイモードを変えるような芸当はせず、起動時の設定そのままだったと思う。
キーボードのドライバも書いた。はずなのだけど、あまり記憶がない。わりとインテリジェントだったからなのか、すんなり動いたからか?
カーネル単体テスト
その1 何が起こったかわからない
起動してもうんともすんとも言わず。そりゃいきなり起動するとは思わないけど。デバッグ環境みたいな気の利いたものはない。さてどうするか。
カーネルをビルドするMakefile
のテンプレート Makefile.x68k
から、エントリポイントはstart
と判明している。(現在エントリポイントの情報はsys/conf/Makefile.kern.inc
に移動している)
LINKFLAGS= -n -Ttext 0 -e start
これを捜すとlocore.s
にラベルがあることがわかる。そこで、start
先頭にX68030本体にくっついているLEDを光らせる命令を埋め込んでカーネルビルド。光ればそこは通過した(成功)。光らなければ、ブートローダーのデバッグから。近ごろ流行りのLチカ。ポカミスはわりとあったと思うけど、さっと書き直してしまうので記憶に残っていない。
作業が進んでくると、LEDだと確認に手間がかかるので、割り込み不要シリアルドライバを呼び出して1文字出力を試みる。
1文字出るのを確認したら、main()
に到達するまでの何箇所かの分岐点に同じように文字出力を埋め込む。起動。何文字出たか、どんな文字が出たかで詰まった箇所を絞り込む。いわゆるprintf
デバッグの超原始的バージョン。
バナーがでるまでのあいたはだいたいこれでしのいでいた。その後カーネルスタックなどがちゃんと整って、カーネル内のprintf
が使えるようになったあとはかなり楽になった。
その2 MMUを有効にしたあとどこかで詰まる
pmap
関連のコードやpage fault周りのコードをひたすら読んで追跡して状況も確認しつつ、みたいなことをえんえんやっていたと思う。sys/vm
(当時はUVMじゃなかった)もかなり読んだ。あとTLB周りやメモリキャッシュ周りをみていた。
その3 ありえない分岐、おかしな挙動
ソースコードを読む限り判定が狂ってるとしか思えない分岐や挙動に見舞われる。さんざん調べた結果、.bss
の初期値の問題とわかった。.bss
はC言語で言うところの初期値を入れないグローバル変数が配置される領域と思っていただければよくて、つまり本来初期値として0 fillされている想定。
ブートローダーが0で埋めず、カーネルも初期化をしなかった。そう、誰も.bssを初期化していなかったのである。そのためゴミデータを読んだ結果、表面上不思議な挙動となっていた。プロセスの.bss
はカーネルがケアしてくれるけど、カーネルの.bss
を誰がケアするのかすっかり忘れていたというオチ。
その4 スタックポインタはどこ指してる?
カーネルの.text, .data, .bss以外のメモリ空間は仮想記憶でなんかやってたと思う。指してるところがおかしかったことがあった記憶はあるけど詳細は覚えていない。
その5 バナー。出た!
当時のログはどこかの古いメディアのなか、ソースコードから再現しようとしたけれど削除されてて当時のバナーがどうだったかはわからずじまい。
シングルユーザブートまでの道のり
使うつもりで移植しているので、カーネルがブートしただけでおしまいではない。というわけで続き。
その1 SCSIコントローラドライバの作成
X68030に搭載されているSCSIコントローラは富士通のMB89352。当時のNetBSDにこのチップのドライバは存在していなかったので、作成した。内容を破壊されるとめっちゃ怖いので、最初は書き込み部分をコメントアウト。
その2 disklabelのフェイク
ストレージの区画に関して、BSDにはdisklabel
という管理情報があるが、当時amigaはなんかフェイクしていた。X68000/X68030のディスクのジオメトリ情報としては、元々パーティションを8つとか作れるようだったので、このときはamigaと同じ要領でフェイクして、disklabelのioctl
でその情報を読むことにした。後年、大きく一つパーティションを確保してその中を本来のdisklabel
で区分けするよう変更されたと思う。
(12/8追記: Human68kパーティション内でdisklabelを使うというのは現在もやっていないとのことです。筒井さんコメントありがとうございます)
その3 ファイルシステムの用意
本当の最初はamiga用のファイルシステムイメージが配布されてたので利用させてもらうことにした。ベタ書きのためにrawrite32
をHuman68kに移植。当時一世を風靡していた光磁気ディスク(MO)のメディアに書き込んだ。
デバッグ
その1 root filesystemをmountできない!
cannot mount root, error = xx
SCSIドライバがバグってるのか、ちゃんとイメージを書き込めてないのか、読み込むブロック位置の計算をミスってるのか、もっと他でボケているのか、とにかくroot filesystemをmountできずにけっこう苦しんだ。いまどきだとramdiskからはじめましょうという感じ。
なにがどう悪かったかは覚えていないが、superblockを読めてないのを追跡した記憶は残っている。
その2 init died
panic: init died (signal XX, exit XX)
mountできたけど/sbin/init
がお亡くなりになるという症状でpanic。これはなんだったかな、読めてないのかpmap
が腐っててちゃんとメモリ割り当ててきてなかったか。残念なことに、これもまた覚えていない。いまなら進捗をどこかに書き留めるのだが、当時は忘れるなんて思いもしなかったのだろう。
その3 ハングアップ、そしてハングアップ
init died
が解決して、なんか止まるなーと思ってたら、タイマー割り込み作ってなかった! (あほ
ユーザープロセスを動かす段になってコンテキストスイッチがなきゃ、そら動かんわ。
ということで、MFPのタイマー制御ドライバを書いた。
RTCの読み書きもこのときついでに作ったと思う。
しくじるとカーネル起動のメッセージが途中で止まる。
ddb(NetBSDのカーネル内蔵デバッガ。panicをトリガーに起動できたりする)を有効にしてバックトレースとかしょっちゅうやってた。
その4 シングルユーザモードで起動!
ようやく!!
あまりはっきり覚えてないけど、たしか1993年9月だったと思う。
その5 SCSIディスクを読み書きモードでmount
思ってたよりすんなり動いた。と思う。苦しんだ記憶はない。マルチユーザーモードもそんなに苦労せず動いたと思う。覚えてないけど。
機能拡張
大きな山を超えたので、勢いに任せていろいろやった。
技術と関係の薄いところではMOの回覧とかやった。当時はネットワークが遅くてしかたなかったので、物理メディアを使って配布。MOのメディアも何千円かしたので、順繰りに郵送で回覧。参加者だけが参照できるとは言え、住所と本名をずらっとリストにして誰も疑問に思わないおおらかな時代だった。誰かが持ち逃げするといったこともなく無事メディアは手元に戻ってきていた。
その1 カーネルのセルフビルド
すんなりビルドできるの、嬉しい!
ここで問題発覚。初期に構築したクロスビルド環境で生成されたa.outとセルフビルドで生成されたa.outでどこかパラメータに違いがあるらしく、うまく起動しない! (たしかalignmentの計算でページサイズか何かが違っていた)
動き出してしまえばHuman68kでのビルド環境は不要なので、セルフビルドの方に寄せることにした。
その2 ブートローダ作成
Human68kのFATファイルシステムからではなくBSD ffsから読むコードを用意した。libstand
ってこのためにあるのかーと知った次第。バイナリサイズは問題に鳴らなかったのか、いまいち覚えていない。待てよ、本当に自分が作ったのか? 記憶があやふや過ぎる。残念。
その3 Human68kのファイルシステムを読み書きしたい
NetBSDにはすでにmsdosfs
があり、うまくやればMS-DOSのファイルシステムをmount
できた。これを改造して、Human68kの拡張されたFATをmount
できるようにした。元々msdosfs
にあったバグも1件デバッグしたような微かな記憶。
その4 QIC-150
秋葉原でジャンクのQIC-150ドライブが売られていた。当時ワークステーション向けの可搬メディアとして一般的だった。買った。つけた。動いた。mt
コマンドを自宅で初めて使った。
後年、EXABYTEやDDS4ドライブも繋いだ。
その5 デバイスドライバいろいろ
フロッピーとかADPCMのドライバは友人が書いてくれた。感謝。
その6 X11は動かせるか?
いろいろ調べた最終結果として、FC2ピンを折るという荒業を披露してしまった。プロセッサの特権モードじゃないとフレームバッファにアクセスできない制約を力技で消し飛ばした。まったくもっておすすめできない。
その7 フルビルドしてみた
カーネルだけでなくコマンド群も含めたフルビルドもやってみたら、1日かかる有様だった。現在なら速いマシンでクロスコンパイルするするのが妥当だと思う。
その後
- 040turboを貸していただき、これに移植したり(つまりXC68040対応)
- 書籍を執筆することになったり(共著だけど)
- manを雑に日本語訳してみたり(書籍執筆者で手分けして)
- NetBSDオフィシャルのリポジトリにつっこんでNetBSD developerになったり
- NetBSDを使ってお仕事している会社にお誘いいただいたり
なんかいろいろありました。
サマリー
- クロスビルド環境: 作った
- ブートローダー: 書いた
- メモリ管理周り: いじった
- SCSIコントローラドライバ: 書いた
- シリアルドライバ: 書いた
- コンソールドライバとキーボードドライバ: 書いた
- 割り込み周り: 書いた
- ビルド: めっちゃ遅かった
- デバッグ: めっちゃした
- FC2ピン: 折った
おわりに
現在ではやらなくて済む余計な苦労が必要だった時代と言うべきか、貴重な学習の機会を自ら得られたと言うべきか、多分両方とも本当なんだろうと思う。やる気に満ちていて必死で相当疲れてたけど他のことに見向きもしないほど集中もしていたと思う。結果が伴った(のか? コード品質を振り返ると微妙な気もするけど) のは素直によかったと思う。
参考文献
- Inside X68000
- UNIXデバイスドライバとかそっち系の書籍何冊か
- 富士通のSCSIコントローラ MB89352データシート(当時は紙)
謝辞
- Human68k UNIX風コマンドや環境を作られた皆様(NetBSDに興味を持つ最初のきっかけでした)
- 当時NECの清水さん (QIC-150メディアでNetBSD-currentのソースコードをいただきました)
- 当時富士通のみのうらさん (データシートありがとうございました)
- シャープさん (支援だかでMOメディアをいただいたようなかすかな記憶)
- masanobuさん、はらかわさん (いろいろドライバ書いていただきました)
- いまもなおNetBSD/x68kの保守にかかわられてる皆様(頭が上がりません)
- 他たくさんのみなさま
手伝っていただいたり応援いただいたり、暇人の相手してくださったり、いろいろ本当にありがとうございました!