LINUX/I386 ブートプロトコル ---------------------------- H. Peter Anvin 最終更新日 2000-10-29 日本語訳:JF プロジェクト 翻訳者: 川崎 貴彦 校正協力者: 森本 淳 瀬戸口 崇 野本 浩一 最終更新日 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 = ; if ( loading_initrd ) { ramdisk_image = ; ramdisk_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= ここにおける は、整数 (C の記法で、10 進数、8 進数、 16 進数のいずれか) か、もしくは、"normal" (0xFFFF を意味する)、 "ext" (0xFFFE を 意味する)、"ask" (0xFFFD を意味する) という 文字列のいずれかです。この値は、コマンドラインが解析される前に カーネルがそれを使用するときに、vid_mode フィールドにセット されなければなりません。 mem= は C の記法による整数で、K, M, G (<<10, <<20, <<30 を 意味します) を後ろにつけることもできます。これにより、カー ネルにメモリの終了位置を伝えます。これは、initrd の配置可能 位置に影響を与えます。というのは、initrd はメモリの終了位置 近くに配置されるからです。このオプションは、カーネルとブート ローダ *双方* に対するオプションであることに注意してください! initrd= initrd がロードされます。 の意味は、明らかにブートローダ 依存事項であり、ブートローダ (LILO など) の中には、そのような コマンドを持っていないものもあります。 さらに、ユーザ指定のコマンドラインに下記のオプションを加えている ブートローダもあります。 BOOT_IMAGE= ロードされるブートイメージ。再度述べますが、 の意味は、 明らかにブートローダに依存しています。 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) にセットしなければなりません。 フック処理完了後、あなたのブートローダが上書きする前にこの フィールドにセットされていたアドレスへジャンプしなければ なりません。