                     LINUX/I386 ブートプロトコル
                     ----------------------------

                    H. Peter Anvin <hpa@zytor.com>
                        最終更新日 2000-10-29


                    日本語訳:JF プロジェクト

                      翻訳者:
                        川崎 貴彦 <takahiko@hakubi.co.jp>
                      校正協力者:
                        森本 淳 <morimoto@xantia.citroen.org>
                        瀬戸口 崇 <setzer@mx3.tiki.ne.jp>
                        野本 浩一 <hng@ps.ksky.ne.jp>

                        最終更新日 2002-01-08


i386 プラットフォームでは、Linux カーネルは多少複雑なブートの慣習を
用いています。これは一部には、カーネル自身を起動可能イメージにしたい
という初期の要望に加え、複雑な PC のメモリモデルという歴史的側面に
よって、及び、主要オペレーティングシステムとしてのリアルモード DOS の
事実上の消滅による PC 産業界の期待の変化によって進化してきました。

現在では、四つのバージョンの Linux/i386 ブートプロトコルが存在します。

古いカーネル:    zImage/Image のサポートのみ。ごく初期のカーネルには
                 コマンドラインすらサポートしていないものもあります。

プロトコル 2.00: (カーネル 1.3.73) ブートローダとカーネル間の連携をとる
                 ための形式化された方法に加えて、bzImage と initrd の
                 サポートが追加されました。従来のセットアップ領域が
                 書込み可能であることを依然として仮定してはいますが、
                 setup.S は再配置可能になりました。

プロトコル 2.01: (カーネル 1.3.76) ヒープオーバーラン警告が追加されました。

プロトコル 2.02: (カーネル 2.4.0-test3-pre3) 新しいコマンドラインプロトコル。
                 従来のメモリ上限を下げました。従来のセットアップ領域を上書き
                 しないことにより、SMM もしくは 32 ビット BIOS エントリ
                 ポイントから EBDA を使用するシステムでも、ブートが安全に
                 なりました。zImage は推奨されませんが、サポートされます。

**** メモリレイアウト

Image もしくは zImage カーネルに用いられるカーネルローダの従来の
メモリマップは、典型的には次のようになっています。

        |                                  |
0A0000  +----------------------------------+
        |  BIOS 用に予約済み               |  使用不可。BIOS EBDA 用に予約済み
09A000  +----------------------------------+
        | スタック/ヒープ/コマンドライン   |  カーネルのリアルモードコードが使用
098000  +----------------------------------+
        | カーネルセットアップ             |  カーネルのリアルモードコード
090200  +----------------------------------+
        | カーネルブートセクタ             |  カーネルのレガシーブートセクタ
090000  +----------------------------------+
        | プロテクトモードカーネル         |  カーネルイメージの大部分
010000  +----------------------------------+
        | ブートローダ                     |  <- ブートセクタ・エントリポイント 0000:7C00
001000  +----------------------------------+
        | MBR/BIOS 用に予約済み            |
000800  +----------------------------------+
        | 典型的には MBR が使用            |
000600  +----------------------------------+
        | BIOS 使用のみ                    |
000000  +----------------------------------+


bzImage を使用するとき、プロテクトモードカーネルは 0x100000 ("高位
メモリ") に再配置され、カーネルのリアルモードブロック (ブートセクタ、
セットアップ、スタック/ヒープ)は、0x10000 と低位メモリの終了位置の
間ならばどこにでも再配置可能とされていました。不運にも、プロトコル
2.00 とプロトコル 2.01 は、0x9XXXX のメモリ範囲にコマンドラインが
存在することを要求し、そのメモリ範囲は初動時カーネルによって依然として
上書きされます。プロトコル 2.02 では、これを修正します。

"メモリ上限" ――ブートローダが扱う低位メモリ内の最高位ポイント―― を
可能な限り低く保つことは望ましいことです。なぜなら、新しい BIOS の中には、
低位メモリの上限近くに、拡張 BIOS データ領域と呼ばれるかなり大きな量の
メモリを配置するものもあるからです。ブートローダは、"INT 12h" BIOS
コールを使って、低位メモリがどれくらい使用できるかを確認しなければ
なりません。

不運にも INT 12h がメモリ不足を検出した場合には、ブートローダは大抵
ユーザにエラーメッセージを出すくらいのことしかできません。そのため、
ブートローダは、合理的に可能な限り、できるだけ少ない領域を占めるように
設計されるべきです。0x90000 セグメントにデータが書き込まれることを
必要とする zImage カーネルや古い bzImage カーネルのためには、ブート
ローダは 0x9A000 より上のメモリを使わないようにしなければなりません。
非常に多くの BIOS が、これより上の領域を破壊してしまうのです。

**** リアルモードカーネルヘッダ

下記の文書内、及び、カーネルブートシーケンス内のどの位置においても、
"セクタ" は 512 バイトのことです。メディア内の実際のセクタサイズとは
関係ありません。

Linux カーネルをロードする最初のステップでは、リアルモードコード
(ブートセクタとセットアップコード) をロードし、オフセット 0x01f1 に
存在する下記のヘッダを検査しなければなりません。ブートローダは最初の
二つのセクタ (1K) だけをロードし、それからブートアップセクタサイズを
調べることもできますが、リアルモードコードは合計で 32K まで可能です。

ヘッダは次のようになっています。

オフセット プロトコル 名称            意味
/サイズ

01F1/1     ALL        setup_sects     セクタ単位のセットアップのサイズ
01F2/2     ALL        root_flags      セットされていれば、リードオンリーで root がマウントされる
01F4/2     ALL        syssize         使用不可。bootsect.S による使用のみ。
01F6/2     ALL        swap_dev        使用不可。旧式。
01F8/2     ALL        ram_size        使用不可。bootsect.S による使用のみ。
01FA/2     ALL        vid_mode        ビデオモード制御
01FC/2     ALL        root_dev        デフォルトのルートデバイス番号
01FE/2     ALL        boot_flag       マジックナンバー 0xAA55
0200/2     2.00+      jump            ジャンプ命令
0202/4     2.00+      header          マジックナンバー "HdrS"
0206/2     2.00+      version         サポートされるブートプロトコルのバージョン
0208/4     2.00+      realmode_swtch  ブートローダフック (下記参照)
020C/4     2.00+      start_sys       カーネルバージョン文字列を指す
0210/1     2.00+      type_of_loader  ブートローダ識別子
0211/1     2.00+      loadflags       ブートプロトコルオプションフラグ
0212/2     2.00+      setup_move_size 高位メモリサイズへ移動 (フックと共に使用)
0214/4     2.00+      code32_start    ブートローダフック (下記参照)
0218/4     2.00+      ramdisk_image   initrd ロードアドレス (ブートローダによりセットされる)
021C/4     2.00+      ramdisk_size    initrd サイズ (ブートローダによりセットされる)
0220/4     2.00+      bootsect_kludge 使用不可。bootsect.S による使用のみ。
0224/2     2.01+      heap_end_ptr    セットアップ終了位置以降のフリーメモリ
0226/2     N/A        pad1            未使用
0228/4     2.02+      cmd_line_ptr    カーネルコマンドラインへの 32 ビットポインタ

後方互換性のため、setup_sects フィールドが 0 の場合には、実際の値は
4 となります。

オフセット 0x202 の位置にマジックナンバー "HdrS" (0x53726448) を発見できない
場合、ブートプロトコルのバージョンは "古い" です。古いカーネルをロードする
ときには、下記のパラメータが仮定されます。

        イメージタイプ = zImage
        initrd 未サポート
        リアルモードカーネルは 0x90000 に配置されなければならない。

それ以外ならば、"version" フィールドはプロトコルのバージョンを含んで
います。例えば、プロトコルバージョン 2.01 は、0x0201 をこのフィールドに
含んでいます。ヘッダ内のフィールドをセットするときには、使用中のプロ
トコルバージョンでサポートされているフィールドのみをセットするように
しなければなりません。

ほとんどのブートローダは、単にそのターゲットアドレスにカーネルを直接
ロードするでしょう。そのようなブートローダは、ヘッダ内のほとんどの
フィールドについては、情報を埋めることについて心配する必要はありません。
とはいうものの、下記のフィールドは埋めなければなりません。


  vid_mode:
        「特別なコマンドラインオプション」を参照してください。

  type_of_loader:
        あなたのブートローダが arch/i386/boot/setup.S で割り当てられ
        ている識別子を持っているならば、その値を入れます。そうでなけ
        れば、ここには 0xFF を入れます。

  loadflags, heap_end_ptr:
        プロトコルバージョンが 2.01 以上ならば、セットアップヒープの
        オフセットリミットを head_end_ptr に入れ、loadflags の 0x80
        ビット (CAN_USE_HEAP) をセットします。heap_end_ptr は、セット
        アップの開始位置 (オフセット 0x0200) からの相対位置です。

  setup_move_size:
        プロトコル 2.00 もしくは 2.01 を使っているとき、リアルモード
        カーネルが 0x90000 にロードされないならば、リアルモードカーネルは
        ローディングシーケンス内で、のちほどそこに移動させられます。
        リアルモードカーネル自体の他に追加のデータ (カーネルコマンド
        ラインなど) も移動させたい場合には、このフィールドを埋めます。

  ramdisk_image, ramdisk_size:
        ブートローダがイニシャル RAM ディスク (initrd) をロードしている場合、
        RAM ディスクデータを指す 32 ビットポインタを ramdisk_image にセットし、
        その RAM ディスクデータのサイズを ramdisk_size にセットします。

        initrd は典型的には、メモリ内のできる限り高位に配置されるべきです。
        なぜなら、そうしなければ、初動時カーネルの初期化シーケンスによって
        上書きされてしまうかもしれないからです。しかしながら、全種類の
        カーネルに initrd を参照させたいのならば、initrd をアドレス
        0x3C000000 より上に配置してはいけません。

  cmd_line_ptr:
        プロトコルバージョンが 2.02 以上ならば、これは、カーネルコマン
        ドラインを指す 32 ビットポインタです。カーネルコマンドラインは、
        セットアップ終了位置から 0xA0000 の間ならば、どこにでも配置す
        ることができます。たとえコマンドラインをサポートしていない場合
        でも、このフィールドは埋めなければなりません。そのような場合に
        は、空文字列を指すようにします (もしくは、さらに良いのは文字列
        "auto" を指すようにすることです) 。このフィールドがゼロのまま
        ですと、カーネルは、ブートローダがプロトコル 2.02 をサポートし
        ていないと仮定してしまいます。

**** カーネルコマンドライン

カーネルコマンドラインは、ブートローダがカーネルと連携するための
重要な方法となってきています。幾つかのオプションは、ブートローダ
自身にも関係しています。下記の "特別なコマンドラインオプション" を
参照してください。

カーネルコマンドラインは、ヌル終端された長さ 255 までの文字列、
プラス、最後のヌルです。

ブートプロトコルバージョンが 2.02 以降ならば、カーネルコマンド
ラインのアドレスは、ヘッダフィールド cmd_line_ptr で与えられます
(上述) 。

プロトコルバージョンが 2.02 よりも上ならば、カーネルコマンドラインは
下記のプロトコルを用いて入力されます。

        オフセット 0x0020 (ワード) の "cmd_line_magic" に、マジック
        ナンバー 0xA33F を入れます。

        オフセット 0x0022 (ワード) の "cmd_line_offset" に、カーネル
        コマンドラインのオフセットを入れます (リアルモードカーネルの
        開始位置に対して相対) 。

        カーネルコマンドラインは setup_move_size でカバーされる
        メモリ範囲になければならないので、このフィールドを調整
        する必要があります。


**** ブート設定の例

設定例として、下記のリアルモードセグメントレイアウトを仮定します。

        0x0000-0x7FFF   リアルモードカーネル
        0x8000-0x8FFF   スタックとヒープ
        0x9000-0x90FF   カーネルコマンドライン

このブートローダは、ヘッダに下記のフィールドを入れなければなりません。

        unsigned long base_ptr; /* リアルモードセグメントのベースアドレス */

        if ( setup_sects == 0 ) {
                setup_sects = 4;
        }

        if ( protocol >= 0x0200 ) {
                type_of_loader = <type code>;
                if ( loading_initrd ) {
                        ramdisk_image = <initrd_address>;
                        ramdisk_size = <initrd_size>;
                }
                if ( protocol >= 0x0201 ) {
                        heap_end_ptr = 0x9000 - 0x200;
                        loadflags |= 0x80; /* CAN_USE_HEAP */
                }
                if ( protocol >= 0x0202 ) {
                        cmd_line_ptr = base_ptr + 0x9000;
                } else {
                        cmd_line_magic  = 0xA33F;
                        cmd_line_offset = 0x9000;
                        setup_move_size = 0x9100;
                }
        } else {
                /* とても古いカーネル */

                cmd_line_magic  = 0xA33F;
                cmd_line_offset = 0x9000;

                /* とても古いカーネルは、そのリアルモードコードを
                   0x90000 にロードしなければならない */

                if ( base_ptr != 0x90000 ) {
                        /* リアルモードカーネルをコピーする */
                        memcpy(0x90000, base_ptr, (setup_sects+1)*512);
                        /* コマンドラインをコピーする */
                        memcpy(0x99000, base_ptr+0x9000, 256);

                        base_ptr = 0x90000;                 /* 再配置された */
                }

                /* 32K までメモリをクリアすることが推奨される */
                memset(0x90000 + (setup_sects+1)*512, 0,
                       (64-(setup_sects+1))*512);
        }


**** カーネルの残りの部分をロードする

非リアルモードカーネルは、カーネルファイルのオフセット (setup_sects+1)*512
から始まります (再度述べますが、setup_sects が 0 のときは、実際の値は
4 です) 。非リアルモードカーネルは、Image/zImage カーネルならば、
アドレス 0x10000 に、bzImage カーネルならば 0x100000 にロードされ
なければなりません。

プロトコルが 2.00 以上で、loadflags フィールドの 0x01 ビット
(LOAD_HIGH) がセットされているならば、カーネルは bzImage です。

        is_bzImage = (protocol >= 0x0200) && (loadflags & 0x01);
        load_address = is_bzImage ? 0x100000 : 0x10000;

Image/zImage カーネルは、512K のサイズまで可能なので、0x10000 から
0x90000 の全メモリ範囲を使うということに注意してください。これは、
これらのカーネルがリアルモード部分を 0x90000 にロードするという
ことがかなり大変な要求であるということを意味します。bzImage の
カーネルは、もっと多くの柔軟性を提供します。


**** 特別なコマンドラインオプション

ブートローダによって提供されるコマンドラインがユーザによって入力される
ならば、ユーザは下記に示すコマンドラインオプションが動作することを期待
してもかまいません。これらは通常、それら全てがカーネルにとって実質的な
意味があるわけではないにしても、カーネルコマンドラインから削除される
べきではありません。ブートローダ自身用のコマンドラインオプションの追加を
必要とするブートローダの作者は、それらのオプションが、現在もしくは将来の
カーネルオプションと競合しないようにするため、
linux/Documentation/kernel-parameters.txt に、それらのオプションを
登録するべきです。

  vga=<mode>
        ここにおける <mode> は、整数 (C の記法で、10 進数、8 進数、
        16 進数のいずれか) か、もしくは、"normal" (0xFFFF を意味する)、
        "ext" (0xFFFE を 意味する)、"ask" (0xFFFD を意味する) という
        文字列のいずれかです。この値は、コマンドラインが解析される前に
        カーネルがそれを使用するときに、vid_mode フィールドにセット
        されなければなりません。


  mem=<size>
        <size> は C の記法による整数で、K, M, G (<<10, <<20, <<30 を
        意味します) を後ろにつけることもできます。これにより、カー
        ネルにメモリの終了位置を伝えます。これは、initrd の配置可能
        位置に影響を与えます。というのは、initrd はメモリの終了位置
        近くに配置されるからです。このオプションは、カーネルとブート
        ローダ *双方* に対するオプションであることに注意してください!

  initrd=<file>
        initrd がロードされます。<file> の意味は、明らかにブートローダ
        依存事項であり、ブートローダ (LILO など) の中には、そのような
        コマンドを持っていないものもあります。


さらに、ユーザ指定のコマンドラインに下記のオプションを加えている
ブートローダもあります。

  BOOT_IMAGE=<file>
        ロードされるブートイメージ。再度述べますが、<file> の意味は、
        明らかにブートローダに依存しています。

  auto
        明示的なユーザによる介入なしでブートされるカーネル。

これらのオプションがブートローダにより追加されるならば、ユーザ指定の
もしくはコンフィギュレーション指定のコマンドラインよりも前に、これら
が *最初* に配置されることが強く推奨されます。そうでない場合、
"init=/bin/sh" は "auto" オプションで混乱してしまうでしょう。


**** カーネルを走らせる

カーネルエントリポイントにジャンプすることにより、カーネルはスタート
します。カーネルエントリポイントは、リアルモードカーネルの開始位置
から *セグメント* オフセット 0x20 のところに配置されます。これは、
もしもリアルモードカーネルコードを 0x90000 にロードした場合、カーネル
エントリポイントが 9020:0000 になることを意味しています。

エントリでは、ds = es = ss はリアルモードカーネルコードの開始位置
(もしもコードが 0x90000 にロードされているならば 0x9000) を指し、
sp は適切にセットされ ――通常はヒープのトップを指す――、割込みは
禁止されるべきです。さらに、カーネル内のバグから保護するため、ブート
ローダが fs = gs = ds = es = ss とセットすることが推奨されます。


上記の例では、次のようにすることになるでしょう。

        /* 注意: "古い" カーネルプロトコルの場合、base_ptr は、この時点で
           0x90000 でなければならない。前のサンプルコードを参照のこと。*/

        seg = base_ptr >> 4;

        cli();  /* 割込みを禁止にして入る! */

        /* リアルモードカーネルスタックをセットする */
        _SS = seg;
        _SP = 0x9000;   /* SS をロード後、すぐに SP をロードする! */

        _DS = _ES = _FS = _GS = seg;
        jmp_far(seg+0x20, 0);   /* カーネルを走らせる */


ブートセクタがフロッピードライブにアクセスするならば、カーネルを
走らせる前にフロッピーモーターを切ることを推奨します。というのは、
カーネルのブートは割込みを禁止のままにしておくので、モーターが
オフにならないからです。特に、ロードされたカーネルがフロッピー
ドライバを要求時ロードモジュールとして持っていた場合に!


**** 高度なブート時フック

もしもブートローダが特に敵対的な環境 (DOS 下で走る LOADLIN など) で
走る場合は、標準的メモリ配置要求に従うことは不可能でしょう。そのような
ブートローダは、もしセットされているならば、適切な段階でカーネルに
よって実行される下記に示すフックを使ってもかまいません。これらの
フックの使用は、おそらく、絶対的最終手段としてのみ考慮されるべきです!

重要: 全てのフックは、おまじないとして %esp, %ebp, %esi, %edi を保存する
ことが要求されます。

  realmode_swtch:
        プロテクトモードに移行する直前に実行される 16 ビットリアルモードの
        far サブルーチン。デフォルトのルーチンは、NMI を無効にするので、
        あなたのルーチンもおそらくそうするべきでしょう。

  code32_start:
        プロテクトモード移行直後、カーネル解凍前に *ジャンプされる*
        32 ビットのフラットモードのルーチン。CS 以外のセグメントは
        セットされていません。他のセグメントは、あなた自身が、
        KERNEL_DS (0x18) にセットしなければなりません。

        フック処理完了後、あなたのブートローダが上書きする前にこの
        フィールドにセットされていたアドレスへジャンプしなければ
        なりません。

