NetBSD/x68k 10.0 時代のブート解説。

一体何番煎じか分からない、NetBSD/x68k ブートローダあたりのまとめ。 この記事は 9.0 リリースの後、10.0 (や 9.1) のリリースより前に -current のブートローダを見直す過程で書いている。 この成果が表立って現れるのは 10.0 の予定。

NetBSD 側の仕様と制約

X680x0 の説明をする前に NetBSD 側から見た仕様をいくつか。 まず disklabel。 細かいことは知らんけど、ディスクにはディスク全体につき1つの disklabel 領域があって(必要で)、その場所は port ごとに定義されている。 NetBSD/x68k の場合 sys/arch/x68k/include/disklabel.h 内で定義されている LABELSECTOR と LABELOFFSET がそれで、つまり セクタ#0 の 0x40 バイトの位置からとなっている。 ここにいわゆる a: がルートで、b: がスワップで…というあの 見覚えのある(?)パーティション情報が書かれている (ことになっている、通常は。これについては後述)。
+0x0000 +--------------------------
        |
+0x0040 |- - - - - - - - - - - - -
        | BSD disklabel (404 bytes)
+0x01d4 |- - - - - - - - - - - - -
        |
        :

次にファイルシステム。 こっちについても細かいことは知らんけど NetBSD に限らず BSD UNIX で一般的に使われる FFSv1 はざっくり言うと以下図のような構造をしている。 超絶適当だけど、 ミソは、ファイルシステムが一切関知しない 8KB (固定) の領域が "ファイルシステム内の先頭に"あるということ。 なので 「このファイルシステムの開始位置」は +0x0000 だが、 このファイルシステムを読む人はおそらく大抵黙って先頭の 8KB を スキップした +0x2000 から処理を開始する。 FFSv2 の場合はこのブート用領域は 64KB 固定。

+0x0000 +-----------------------
        | FFSv1 Boot Area
        | (8KB)
+0x2000 +-----------------------
        | FFS Super Block
        | 
        | FFS Data Block とか (たぶん…)
        :
ついでにインストールフロッピーで使われる USTARFS は以下のような感じ。 先頭に 8KB (固定) のブートローダ用領域があって (サイズは FFS のそれに由来してるんだろうか)、 その直後に TAR 形式のデータが続く。 読み込み専用で処理が楽なのが特徴。
+0x0000 +-----------------------
        | Boot Area
        | (8KB)
+0x2000 +-----------------------
        | USTARFS
        :

HD 起動の場合

で話を戻して X680x0 の場合。

X680x0 で (SCSI) HD 起動する場合、IPLROM は ディスクのセクタ#0 の先頭に "X68SCSI1" という8バイトのマジックがあることを確認する。 続いてディスクのセクタ#1 から1セクタ分をメモリの 0x2000 番地〜に読み込み、 その先頭バイトが $60 (BRA.[BW]命令の1バイト目) であることを確認してから、 0x2000 番地に処理を移す。出 IPLROM 記である。 ただし、この「セクタ」は Human68k の 1024 byte/sector のこと。 NetBSD カーネルの中ではメディアのセクタサイズに関わらず 512 byte/sector が用いられるようで、大変紛らわしいことになるので、 この記事ではこれ以降 1024 byte/sector のほうを「ブロック」、 512 byte/sector のほうを「セクタ」と勝手に呼び分けることにする。 この場合 1ブロック = 2セクタが常に成り立つ。 この記法に従ってこの段落前半の要点を書き直すと、 ディスクのブロック#0 の先頭のマジックを確認する、 続いてブロック#1 から1ブロック分をメモリの 0x2000 番地〜に読み込んで 0x2000 番地に処理を移す、となる。

もう一つ、 IPLROM が HD に処理を移す際には Human68k パーティションテーブルには関知していないのがミソ。

Human68k の FORMAT.X で HD にパーティションを切っていれば(かな?)、 ディスクのブロック#1 には Human68k のブートセレクタが書き込まれていて、 これが Human68k パーティションを見て起動先を選択、決定するはず。 Human68k パーティションテーブルはブロック#2 に書いてある。 つまりこういうこと↓

Block  Sector Offset
#0     #0     0x0000  +-------------------------------------
                      | MagicString ("X68SCSI1", 8bytes)
       #1     0x0200  |
                      |
#1     #2     0x0400  +-------------------------------------
                      | BootLoader (1024 bytes)
       #3     0x0600  |
                      |
#2     #4     0x0800  +-------------------------------------
                      | Human68k Partition Table (256 bytes)
                      |- - - - - - - - - - - - - - - - - - -
       #5     0x0a00  |
                      |
#3     #6     0x0c00  +-------------------------------------
                      :
NetBSD/x68k で HD から起動するには以下の相容れない2つの方式のうち どちらか一方を選択することになる。 以下でそれぞれ説明する。

HD 起動の場合、その1 (Human68k と共存しない方式)

NetBSD/x68k を Human68k と共存せず起動する方式は、 ここまでの図を心の目で重ねて、こんな感じ。 FFSv2 は FFSv1 よりブート領域が広く制約が緩いので、 ここではより制約の厳しい FFSv1 の話だけをする。
Block  Sector Offset
#0     #0     0x0000  +-------------------------------------------
                      | MagicString ("X68SCSI1", 8 bytes)       ^
              0x0040  | BSD disklabel (404 bytes)               |
       #1     0x0200  |                                         |
                      |                                         |
#1     #2     0x0400  +---------------------------------------- |
                      | ^                                       |
       #3     0x0600  | | BootLoader (<7KB)                     |
                      | |                                       |
#2     #4     0x0800  | |  Human68k Partition Table (256 bytes) |
                      | |  (But not available)                  |
       #5     0x0a00  | |                                       |
                      | |                                       |

                      : :                                       :

      #15     0x1e00  | |                 FFSv1 Boot Area (8KB) |
                      | v                                       v
#8    #16     0x2000  +-------------------------------------------
                      | FFS Super Block
前提として とすると、
  1. 0x0000 に Magic String があるので IPLROM はこの SCSI メディアからブート可能。
  2. IPLROM はブロック#1 から1ブロック (プライマリブートローダの先頭) を 0x2000 番地に読み込む。
  3. その先頭(0x2000番地) が $60 なので、ここに処理を移す。
  4. ブートローダの 1KB を越える残りの部分はブートローダ自身が読み込まなくてはならないが、 今実行している自分が誰によってセクタ何番からメモリにロードされたのかを 統一的に知る方法はなく、レジスタ値の残痕からあの手この手で調べる。
  5. ブートローダを読み込むのは IPLROM、SCSI IPL、BOOT MENU の三者だが、 この方式の場合は IPLROM か SCSI IPL かの二択になる (BOOT MENU は 1 ブロック目に書かれるやつのはずで、 今そこにはこっちのプライマリブートローダがいる)。
  6. IPLROM、SCSI IPL ともレジスタ値から自身の読み込み元(の開始)ブロック番号 (この場合 1) が分かるので、そこから再び 8KB をロードして処理を進める。
  7. もしこのブートローダが (次項で説明する方式により) Human68k パーティションからロードされていたなら、 読み込み元ブロック番号は 4 以下になるはずはない (最初の図の通りそこは管理領域なので)。 だが今の場合 1 なのでこれを 0 とする (これが SCSI_PARTTOP)。 ここがミソで分かりにくいところ。
  8. 8KB フルに読み込まれたブートローダは FFS を解釈してセカンダリブートローダを 探す。 この時のファイルシステム先頭は SCSI_PARTTOP でつまりブロック#0。 繰り返しになるけど、実際の FFS Super Block はここから 8KB 先から始まる。
ということで無事セカンダリブートローダがロード出来る。 この方式の場合 Human68k パーティションテーブルは存在せず (その領域はブートローダで書き潰されている)、 また、ここまでは BSD disklabel が一切出てこないので BSD disklabel がなくてもここまでは問題なく動く (が、もちろんこの後のセカンダリブートローダ以降ではこれを使うので、 あくまでもここまではというだけ)。

またこの方式では、 プライマリブートローダはルートファイルシステムがセクタ#0 から始まると勝手に仮定しているだけで(手順7)、 BSD disklabel を読んではいないため、 セクタ#0 から始まらないルートパーティションからは起動できない。 まあディスクまるごと使うからには大抵セクタ#0 からルートパーティションを始めるだろうから問題になることは少なそうだけど。 今時の感覚から言えば、動くように組み合わせたから偶然動いてる状態とも言えるが、 当時のブートローダなんて多かれ少なかれそんなもんだと言われればそうかも知れない。

HD 起動の場合、その2 (Human68k と共存する方法)

次に NetBSD/x68k を Human68k と共存させて起動する方式。 こちらは先程の応用編。 ここでも同様に FFSv1 だけに限定する。
Block  Sector Offset
#0     #0     0x0000  +-------------------------------------
                      | MagicString ("X68SCSI1", 8 bytes)
              0x0040  | BSD disklabel (404 bytes, Not Used)
       #1     0x0200  |
                      |
#1     #2     0x0400  +-------------------------------------
                      | 0-th BootLoader (1024 bytes)
       #3     0x0600  | (BOOT MENU or mboot)
                      |
#2     #4     0x0800  +-------------------------------------
                      | Human68k Partition Table (256 bytes)
                      |  [0] start = N, size = ...
                      |  [1] ...
                      |- - - - - - - - - - - - - - - - - - -
       #5     0x0a00  |
                      |
                      +-------------------------------------

                      :

#N    #2*N  0x0400*N  +-------------------------------------
                      | Primary BootLoader (<8KB)
                      | (boot_ufs or xxboot_ffsv1)
                      :                FFSv1 Boot Area (8KB)
#(N+8)                +-------------------------------------
                      | FFS Super Block
こちらは以下のような前提。 で、
  1. 0x0000 に Magic String があるので IPLROM はこの SCSI メディアからブート可能。
  2. IPLROM はブロック#1 から1ブロック (ブートセレクタ) を メモリの 0x2000 番地に読み込む。
  3. その先頭(0x2000番地) が $60 なので、ここに処理を移す。
  4. ブートセレクタが Human68k パーティションテーブルから起動先パーティションを決定し、 その先頭 1 ブロックをメモリの 0x2400 番地に読み込んで処理を移す。 この例の場合ブロック#N を読み込む。
  5. ここでも、ブートローダの 1KB を越える部分はブートローダ自身が読み込む必要がある。 先程とは逆にこの 1KB は必ず BOOT MENU によって読み込まれているはずである (IPLROM と SCSI IPL は1セクタ目しか読めないはずなので)。
  6. BOOT MENU から読み込まれた場合はレジスタとメモリに Human68k パーティション情報が残っているので、 これによって自分が読み込まれたブロック番号が分かる。 そのブロックを SCSI_PARTTOP としつつ、 SCSI_PARTTOP から 8KB をロードして処理を進める。
  7. 8KB フルに読み込まれたブートローダは SCSI_PARTTOP から FFS を解釈してセカンダリブートローダを探す。 また繰り返すけど、実際の FFS Super Block はここから 8KB 先から始まる。
ということでこちらも無事セカンダリブートローダがロード出来る。 こちらのケースでは Human68k パーティションテーブルのみが必要で BSD disklabel は不要。 BSD disklabel がここまでのところ使われないのは先程のケースと同じだけど、 今のカーネルはディスク上に Human68k パーティションテーブルがある場合 Human68k パーティションテーブルだけを見て ディスク上の BSD disklabel は一切読み書きしないので、 結果として BSD disklabel は一切不要ということに。 これが一番最初のほうで触れた「通常は」のオチ。

CD 起動の場合

CD 起動と言っても X680x0 的には CD も HD もどちらも SCSI 起動という同じカテゴリなので、 2つ前の「Human68k と共存しない HD 起動」とほぼ同じ。 ただし CD は 2048 byte/sector であり、 Human68k (X680x0 IPLROM) もこれに従うので、 ここでは 1ブロック = 2048 byte とする。

前提として、

  1. ブロック#0 の先頭に Magic String ("X68SCSI1") がある。
  2. ブロック#1 からプライマリブートローダ (xxboot_cd9660) がある (ちなみに CD9660 フォーマットのブート用領域は 32KB とかあったはずだが、 現行の xxboot_cd9660 は xxboot 内共通の制約により 8KB を上限としている)。
  3. あとは CD9660 フォーマットに従う。
とすると、 ということで、こちらでも無事セカンダリブートローダがロード出来る。 disklabel という概念もない。

FD 起動の場合

FD 起動の場合、IPLROM はフロッピーのブロック#0 から(先頭から?) 1KB をメモリに読み込むところから始まる。 ちなみにこの時の読み込み長 1KB はメディアのセクタサイズによらず固定。

フロッピーの場合主用途がインストールフロッピーなので、 ファイルシステムを USTARFS として話を進める。

Block  Sector Offset
#0     #0     0x0000  +-------------------------------------------
                      | ^
              0x0040  | | BSD disklabel (404 bytes, But not used)
       #1     0x0200  | |
                      | |BootLoader (<8KB)
                      : :
      #15     0x1e00  | |
                      | v
#8    #16     0x2000  +-------------------------------------------
                      | USTARFS
前提として とすると、
  1. IPLROM はセクタ#0 から 1KB をメモリの 0x2000 番地に読み込む。
  2. その先頭(0x2000番地) が $60 なので、ここに処理を移す。
  3. ブートローダの 1KB を超える残りの部分はブートローダ自身が読み込む必要があるが、 フロッピーの場合メディア先頭から書かれていると分かっているので、 メディア先頭から 8KB を読み込み直す。
  4. 8KB フルに読み込まれたブートローダが USTARFS を解釈してセカンダリブートローダを探す。 ここでも USTARFS の開始位置は 8KB 目からと決まっているので特に困らないはず。
と HD 起動に比べてとても単純。

フロッピーの場合でもここまでのところ BSD disklabel は登場しないが、 USTARFS の場合そもそもこれより後も disklabel 自体が不要。 セカンダリブートローダは fd0a の 'a' は無視する仕様だし (そもそもカーネルでもここの 'a' はパーティションを表してはいない)、 インストールカーネルが、 n 枚組インストーラ (NetBSD/x68k の場合は通常2枚組) の もう排出された(かも知れない) 1枚目のフロッピーにアクセス行くのもおかしいし。

余談だけど、 フロッピー上に USTARFS ではなく FFS を作るのなら、 上記 HD 起動の Human68k と共存しないケースと同様に BSD disklabel で a: が0セクタから始まるように書いて、 ディスク先頭から xxboot_ffsv? を置けばいいはず (このケースだけ xxboot_ffsv? 内に BSD disklabel 用の空隙が必要)。 果たしてどれだけの使い道があるかは別として…。

セカンダリブートローダ以降の disklabel

ここまで見てきたように BSD 的なパーティション情報は、 セクタ#0 の BSD disklabel のみを使う方法と、 ブロック#1 の Human68k パーティションテーブルのみを使う方法の 2通りがある。 セカンダリブートローダ(/boot)は、 まず先に BSD disklabel の有無を調べてあればこれを、 なければ次に Human68k パーティションテーブルを調べる、となっている (はず、sys/arch/x68k/stand/libsa/sdcd.c)。 そのため、HD 起動した場合は上述のどちらであっても対応できる。 カーネルも同様 (sys/arch/x68k/x68k/disksubr.c)。

Human68k パーティションテーブルは全部で 15個設定できるが、 このうち BSD disklabel として認識できるのは先頭の7つのみ。 そして BSD disklabel の c: は特別なパーティションなのでマッピングから除く。 つまり、以下のような対応になる。このマッピング順は固定。

Human68k
パーティション
テーブル
対応する
BSD disklabel
[0]a:
[1]b:
[2]d:
[3]e:
[4]f:
[5]g:
[6]h:
[7]〜[14]割り当て不可

NetBSD では伝統的に通常 a: をルートパーティション、b: をスワップとするので (しなくても動くのかも知れないけどお勧めはしない)、 Human68k と共存する場合は、実質、先頭の 2つを NetBSD 用に割り当てて、 Human68k パーティションはそれ以降に置くことになると思う。

改定履歴

2020/08/20 … 初出。
2020/08/21 … FFSv1/v2 について修正、加筆。
2020/09/02 … タイトルを変更など。

isaki@NetBSD.org