LINUX/I386 ブートプロトコル ---------------------------- H. Peter Anvin 最終更新日 2002-01-01 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 は推奨 されませんが、サポートされています。 プロトコル 2.03: (カーネル 2.4.18-pre1) 有効な initrd アドレスの最上位を、ブートローダ から明示的に取得できるようにしました。 **** メモリレイアウト 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 使用不可。obsolete. 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/2 2.00+ start_sys ロード低位セグメント (0x1000) (obsolete) 020E/2 2.00+ kernel_version カーネルバージョン文字列へのポインタ 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 ビットポインタ 022C/4 2.03+ initrd_addr_max 有効な initrd アドレスの最上位 後方互換性のため、setup_sects フィールドが 0 の場合には、実際の値は 4 となります。 オフセット 0x202 の位置にマジックナンバー "HdrS" (0x53726448) を発見 できない場合、ブートプロトコルのバージョンは "古い" です。古いカーネル をロードするときには、下記のパラメータが仮定されます。 イメージタイプ = zImage initrd 未サポート リアルモードカーネルは 0x90000 に配置されなければならない。 それ以外ならば、"version" フィールドはプロトコルのバージョンを含んで います。例えば、プロトコルバージョン 2.01 は、0x0201 をこのフィールドに 含んでいます。ヘッダ内のフィールドをセットするときには、使用中のプロ トコルバージョンでサポートされているフィールドのみをセットするように しなければなりません。 "kernel_version" フィールドは、もしもゼロ以外の値がセットされている場合、 人が読んで理解できるヌル終端されたカーネルバージョン番号文字列へのポイ ンタであり、値は 0x200 未満です。ユーザに対してカーネルバージョンを表示 するのに利用できます。この値は (0x200*setup_sects) 未満でなければなり ません。たとえば、この値が 0x1c00 にセットされている場合、カーネルバー ジョン番号文字列はカーネルファイル内の 0x1e00 にあります。これは、 "setup_sects" フィールドが 14 以上の値を含んでいる場合にのみ、有効な 値となります。 ほとんどのブートローダは、単にそのターゲットアドレスにカーネルを直接 ロードするでしょう。そのようなブートローダは、ヘッダ内のほとんどの フィールドについては、情報を埋めることについて心配する必要はありません。 とはいうものの、下記のフィールドは埋めなければなりません。 vid_mode: 「特別なコマンドラインオプション」を参照してください。 type_of_loader: ブートローダに割当て済みの ID (下記表を参照) がある場合は、0xTV をセットします。ここで、T はブートローダの識別子、V はバージョン 番号です。 割当て済みブートローダ ID: 0 LILO 1 Loadlin 2 bootsect-loader 3 SYSLINUX 4 EtherBoot ブートローダ ID を割り当ててもらう必要がある場合は、 宛に連絡をお願いします。 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_addr_max フィールドで指定されるアドレスより上に配置し てはいけません。initrd は少なくとも 4K ページ単位でアライン しておくべきです。 cmd_line_ptr: プロトコルバージョンが 2.02 以上ならば、これは、カーネルコマンド ラインを指す 32 ビットポインタです。カーネルコマンドラインは、 セットアップ終了位置から 0xA0000 の間ならば、どこにでも配置する ことができます。たとえブートローダがコマンドラインをサポートして いない場合でも、このフィールドは埋めなければなりません。そのような 場合には、空文字列を指すようにします (もしくは、さらに良いのは 文字列 "auto" を指すようにすることです) 。このフィールドがゼロの ままですと、カーネルは、ブートローダがプロトコル 2.02+ をサポート していないと仮定してしまいます。 ramdisk_max: initrd の内容が占める領域の最大アドレスです。ブートプロトコル 2.02 以前にはこのフィールドはなく、最大アドレスは 0x37FFFFFF です。(このアドレスは安全に使えるバイトの最上位アドレスとして 定義されていますので、RAM ディスクがぴったり 131072 バイトで このフィールドが 0x37FFFFFF の場合、RAM ディスクを 0x37FE0000 から開始することができます。 **** カーネルコマンドライン カーネルコマンドラインは、ブートローダがカーネルと連携するための重要な 方法となってきています。幾つかのオプションは、ブートローダ自身にも関係 しています。下記の "特別なコマンドラインオプション" を参照してください。 カーネルコマンドラインは、ヌル終端された長さ 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) にセットしなければなりません。 フック処理完了後、あなたのブートローダが上書きする前にこの フィールドにセットされていたアドレスへジャンプしなければ なりません。 ------------------------------------------------------------ 翻訳団体: JF プロジェクト < http://www.linux.or.jp/JF/ > 翻訳日: 2004/04/11 翻訳者: 川崎 貴彦 校正者: 森本 淳 瀬戸口 崇 野本 浩一 Seiji Kaneko