LinuxのメジャーなブートローダーにgrubとLILOがあります。ここではLinuxのインストール時にブートローダーとしてgrubを指定し、その(grubの)インストール先としてMBRを選択した場合の動作を追います。
Linuxはハードディスクの基本パーディション、論理パーディションのどちらにもインストールが可能で、該当パーティション(のアクティブ フラグ)がアクティブである必要もありません。こうした機能はLinuxのローダーであるgrubやLILOによって実現されています。
grubはファイル サイズが大きくてMBRのサイズ(1セクター)に治まらないため、"プライマリー" と "セカンダリー" の2本のファイルに分割され、それぞれが stage1, stage2 と呼ばれています。
stage1 を MBR、stage2 をLinuxのパーティション内に配置し、stage1 が stage2 をロード、stage2 が Linux(のカーネル)をロードする仕組みになっています。
stage1 は、ハードディスクのセクターをLBAの値で直接指定して stage2 をロードします。
grubがロード対象とする vmlinuz-xxxx(Linuxのカーネルやデバイス初期化プログラムで構成)は、次に示すように3つのブロックが組み合わされています。
【 vmlinuz-xxxx の構成 】
bbootsect | bsetup | bvmlinux.out |
各ブロックは次の役割を担っています。
- bbootsect
フロッピーディスク用のブートコード。512バイト固定。通常ほとんど使われません。
ただし497バイト目に bsetup のサイズ(セクター単位)が記録されます。
- bsetup
デバイスの初期化、プロテクトモードへの移行を行います。
ファイルサイズはセクターサイズ(512バイト)単位に調整され、セクター数は bbootsect の497バイト目に記録されます。
- bvmlinux.out
zlib形式で圧縮済みのカーネル※1本体。 自己解凍(展開)コードを含んでいます。
grubは vmlinuz-xxxx の他に initrd-xxxx.img もロードします。これは初期化 ramdisk と呼ばれ、仮のrootファイルシステムとして起動直後のカーネルで利用されます。
カーネルは初期化 ramdisk を通して本来のrootファイルシステムをマウントし、各種ドライバ等を組み込んでOSとしての機能を確立します。
以下の説明ではLinuxのカーネルを "vmlinuz-2.4.21-4.EL"、初期化ramdiskを "initrd-2.4.21-4.EL.img" としていますが、これらのファイル名は使用するソース ファイルのバージョン等によって細かに変わる事にご留意下さい。
Linuxインストールのオプションでgrubのインストール先にMBRを選択すると、インストーラーは /boot/grub/stage1 に次の情報を追加してMBRへ書き込みます。
- 元々MBRに記録されていたBPBとパーティションテーブルの情報。
- ブートドライブの種別。
- /boot/grub/stage2 の先頭セクター位置(LBA)情報。
grub(stage1, stage2)によるLinux起動の動作は概ね次のようになります。
grubの分割は2分割(stage1+stage2)の外に3分割(stage1+stage1.5+stage2)になる事もありまが、Linuxと同時に(CDから)インストールしたgrubでは、通常2分割でインストールされます。ここでは2分割時の動作を追います。
MBR(512バイト)に格納されたstage1の構成は次の通りです。
名称 | 格納位置(アドレス) |
プログラム | 0x0000〜0x0002, 0x0004a〜0x0175, 0x0197〜0x01ac |
BPB | 0x0004〜0x003d |
定数部分 | 0x003e〜0x0049 |
メッセージ文字列 | 0x0176〜0x0196 |
リザーブまたは未使用 | 0x0003, 0x01ad〜0x01bd |
パーティション テーブル | 0x01be〜0x01fd |
Boot Signature | 0x01fe〜0x01ff |
stage1の定数部分(0x003e〜0x0049)には、stage2のロードに関するデータ等(下記)が記録されています。
定数部分はそれぞれ次の意味があります。
位置 | データ | 意味 |
003E | [03 02] | grubバージョン(Major, Minor) |
0040 | [80] | Boot Drive(stage2のあるドライブ番号 HDは0x80〜、 FDは0x00〜)。 この例では HD 0(1台目のHD) |
0041 | [00] | Force LBA(LBA強制フラグ) |
0042 | [00 80] | stage2 Address(stage2をロードするメモリ番地 0x8000) |
0044 | [5a fe e9 04] | stage2 sector(stage2の先頭セクター位置 0x04e9fe5a : LBA形式) |
0048 | [00 08] | stage2 segment(stage2が使用するセグメント値) |
BIOSによってメモリの 0x07C00 にロードされ、制御を渡されたstage1は、定数部分(前項参照)を参照してstage2の先頭セクター(512バイト)をロードします。
- stage1の先頭にはジャンプ命令があります(下記はその一例です)。
メインとなるプログラムがある位置へジャンプします。
0x7C00:0000 eb 48 JMP 4A (実際のジャンプ先は 0x07C4A)
- 定数部分のBoot Drive番号を参照し、0xff(無効ドライブ)でないことを確認します。
- 定数部分のstage2 sectorで得たハードディスクのセクター位置から、stage2 Address で得たメモリアドレス 0x08000 へ、stage2の先頭512バイトをロードします。
- stage2先頭部分のロード先(0x08000)へジャンプし、制御をstage2へ渡します。
stage2ファイルはいくつかのブロックに分割されてディスク上に格納されています。
最初のブロックの先頭512バイトには後続セクターのロード用コードの他に、以後ロードすべきstage2の残りブロックのセクター位置がgrubのインストール時に書き込まれています。
先頭セクター(512バイト)の構成は大きく2つに分かれます。0x0000〜0x0137までがコードやメッセージで構成され、残りの部分(0x0138〜0x01ff)は後続セクター情報やロード先セグメントアドレスを指定するブロックリストになっています。
ブロックリストはディスク上に分散しているファイルを、連続しているセクターごとにブロック(塊)として管理するためのもので、1ブロック当たり8バイトで管理し最大25ブロックまで管理可能です。
8バイトで構成するブロックリストの内部は3つのエントリーに分かれています。
【 ブロックリスト(8バイト)の構成 】
4バイト | 2バイト | 2バイト |
ディスク上のセクター位置 | 当ブロックのセクター数 | ロード先セグメント アドレス |
【 ブロックリストは後方から順に使用します 】
例えばstage2が3ブロックに分散配置されているような場合、先頭セクターの末尾32バイトには下図のような情報が記録されます。
このような例では、次の順序でロードが行われます。
- HDのセクター アドレス 0x04e9fe5b から95(0x005f)セクター分を、
メモリの 0x0820 : 0000 (0x08200)番地にロードします。
- HDのセクター アドレス 0x04e9fec2 から47(0x002f)セクター分を、
メモリの 0x1400 : 0000 (0x14000)番地にロードします。
- HDのセクター アドレス 0x04ea01f6 から65(0x0041)セクター分を、
メモリの 0x19e0 : 0000 (0x19e00)番地にロードします。
ディスク上に分散して配置されているstage2も、ロード先のセグメント アドレスを適切に指示することで、メモリ上では切れ目なく連続したアドレスに格納される事になります。
stage2は先頭セクター(512バイト)のみがロードされた状態で制御を引き継いでいます。まず自身(stage2)の第2セクター以降をロードし、その後本来の目的であるカーネル(OSの核となるプログラム群)のロードに移ります。
stage2の動作は概ね以下の通りです。
- ブロックリストの情報を基にハードディスクのセクターを直接指定して、stage2の残り部分(第2セクター以降)をメモリの 0x008200 以降へ順次ロードします。
- stage2のロードが完了すると、第2セクターのロード先である 0x08200 へジャンプします。
stage2の動作はカーネルのロードに移ります。
- stage2はLinuxのファイルシステムを使ってgrub設定ファイル(/boot/grub/grub.conf)にアクセスし、そこに記述されているコマンド(grubのコマンド)を実行します。
下記は grub.conf の参考例です。
title Red Hat Enterprise Linux ES3 (2.4.21-4.EL)
root (hd0,2)
kernel /vmlinuz-2.4.21-4.EL ro root=/dev/hda4
initrd /initrd-2.4.21-4.EL.img
それぞれのコマンドは次の働きをします。
- root (device,partition)
指定されたデバイスのパーティションをマウントし、そこをgrubのルートにします。デバイスとパーティションの番号はBIOSと同様 "0" から始まります。
- kernel /path/kernel [option...]
ロードするカーネルをフルパスで指定します。
- initrd /path/initrdFile
ブート時に使用する初期RAMディスクを指定します。
- rootコマンドを実行すると、上記の例(hd0,2)ではハードディスク "0" のパーティション "2" を、grub用のルート パーティションとして認識するようになります。
以降のコマンド(kernelやinitrd)では、パラメーターで指定されたイメージを、このパーティションからロードすることになります。
- kernelとして指示されたファイル(参考例ではvmlinuz-2.4.21-4.EL)の先頭部にアクセスし、その種別(bzImageまたはzImage形式)を判別します。
以降は "bzImage 形式" であったとして説明します。
-
vmlinuz-2.4.21-4.EL の先頭セクター512バイト(bbootsect部分)を、メモリアドレス 0x090000 以降へロードします。
このセクターの497バイト目にbsetup部分のサイズ(セクター単位)が記録されています。
- ロードしたbbootsect部分の497バイト目を参照して、次にロードするbsetupのセクター数を取得します。
- vmlinuz-2.4.21-4.EL の第2セクター以降にあるbsetup部分を、メモリアドレス 0x090200 以降(bbootsectに連続した領域)へロードします。
- メモリ上に連続してロードしたbbootsectとbsetupの境界を跨いだエリアに、ブート環境パラメーターを格納する領域を確保します(右図参照)。
- bsetupのロードアドレス以降で 0x100000 以下のメモリ上に、カーネル パラメーターを収めた領域を確保し、その先頭アドレスをブート環境パラメーター領域に設定します。
- vmlinuz-2.4.21-4.EL の3つ目のブロック(bvmlinux.out)を、メモリアドレス 0x100000 以降へロードします。
- grub.confのinitrdで指定されたイメージファイル(initrd-2.4.21-4.EL.img)をbvmlinux.outのロードエリアより上位(上図のinitrd参照)にロードし、その先頭アドレスをブート環境パラメーター領域に設定します。
- CPUの動作モードをリアルモードへ変更します。
- bsetupをロードした 0x090200 へジャンプしてgrubの役目は終了します。
bsetupにはAT互換機に特化した初期化ルーチンが収められていて、BIOSサービスを通してハードウェア情報を取得し、ブート環境パラメーター領域へ設定します。
さらにBIOSサービスや出力ポートを介して周辺機器の初期化を行った後、CPUの動作をプロテクトモード
(※1)へ遷移します。
- BIOSサービスを用いて次のハードウェア情報を取得し、ブート環境パラメーター領域に設定します。
- メモリマップ
- HDDユニットの各種パラメーター
- PS/2 マウス
- ビデオ カード
- 電源管理機能
- BIOSサービスや出力ポートを通して下記の初期化等を行います。
- キーボード コントローラーのリピート レート
- 割り込みコントローラーのマスク
- ビデオ カードの画面モード
- FPU
- "A20 マスク" を解除します。
- プロテクトモード下のCPUが使用する各種管理テーブル(GDT,IDT)を、一時的にbsetup領域上に確保します。
- CPUの動作モードをプロテクトモード(※1)に遷移します。
- ロード済みの bvmlinux.out(圧縮されたカーネル本体)の先頭(0x00100000)にジャンプしてbsetupの役目は終了します。
bvmlinux.out(圧縮されたカーネル本体)の先頭には自己展開ルーチンが納めれらています。制御を渡された自己展開ルーチンは次の処理を行います。
- 展開処理に必要なスタック領域(4KB)を確保します。
- bvmlinux.out 内にあるBSS領域(静的データを格納する領域)を識別し、ゼロ値でクリアします。
- 解凍コードが使用するヒープ領域(3000bytes)を bvmlinux.out の直後に確保します。
- カーネル イメージの前半(解凍後のサイズで568KB)を 0x00002000 〜 0x00090000 に展開します。
- 残りのカーネル イメージを3項で確保したヒープ領域の後方に展開します。
- 展開ルーチンは自分自身の一部を 0x00001000 にコピーし、そちらにジャンプして処理を続行します。
- 2つの領域に分断しているカーネル イメージを連結して 0x00100000 にコピー(上書き)します。
- ブート環境パラメーター領域の先頭アドレス (0x00090000) をESIレジスターに格納します。
- 展開済カーネル イメージの先頭アドレス (0x00100000) へジャンプすることで、Linuxのカーネルが本格的に動き始めます。
Linuxのカーネルは、仮想アドレス空間である 0xC0100000 に配置された時に正しく動作するよう作られていますが、カーネル起動直後のこの段階では、メモリ管理は未だ開始されていません。
物理メモリの先頭 8MB に限定したページング仮設定からカーネルの初期化が始まります。
- 仮ページング設定を行います。
下記仮想アドレス空間のページテーブルを設定して、両方に同じ物理メモリ領域を割り当てます。
仮想アドレス空間 | 割り当てる物理メモリ領域 |
0x00000000 〜 0x00800000 | 0x00000000 〜 0x00800000 |
0xC0000000 〜 0xC0800000 | 0x00000000 〜 0x00800000 |
こうすることで、0x00100000 の物理アドレスに構築されたカーネルは、あたかも 0xC0100000 に配置されたかのように振舞うことができ、bsetup等が記録した引き継ぎデータにもアクセス可能となります。
- 後続のプログラムのため、スタック領域(8KB)を確保します。
- カーネル内にあるBSS領域(静的データを格納する領域)を識別し、ゼロ値でクリアします。
- IDT(Interrupt Descriptor Table:割り込みディスクリプタテーブル)を再定義します。
- ESIレジスターを参照し、ブート環境パラメーターを特殊な領域(empty_zero_page)へコピーします。
- CPUの種類を検出します。
- GDT(Global Descriptor Table)を再定義します。
カーネル用セグメントの他に、ユーザー用、APM(Advanced Power Management)BIOS用のセグメントを追加します。
- 全ての物理メモリを対象としたページングの設定を行います。
- カーネル パラメーターの読み込みを行います。
- カーネル内に静的に組み込まれているデバイス ドライバを初期化します。
- ロード済みの initrd-2.4.21-4.EL.img を展開し、仮のルート "/" ファイル システムとしてマウントします。
- 展開された initrd-2.4.21-4.EL.img は初期化ramdisk(initrd)となり、デバイスド ライバや本来のルート "/" ファイル システム組み込みに必要なモジュールを提供します。
- 仮ルート ファイル システムにある /Linuxrc を実行します。
- /Linuxrcは以下の処理を行います。
- ユーザーの環境に応じたドライバ モジュールを組み込みます。
- 本来のルート ファイル システムのマウントに必要なモジュールをロードします。
- カーネル パラメーター(grub.confのkernel行にあるroot=/dev/hda14)で指定されたパーティションを、本来のルート ファイル システムとしてマウントし、ルート ディレクトリ "/" として参照可能にします。
仮にマウントされていたinitrdは参照されなくなります。
- カーネル起動時のみ使用される部分をメモリ上から開放します。
これらのイメージはカーネルの後部にまとめられているので、カーネルの末尾が縮小する形になります。
- /sbin/init を起動し、以後順次起動プロセスを実行してLinuxの起動が完了します。
ルート "/" とは別のパーティションに分けられた /usr や /home などは、rcスクリプト中に記述されている mount コマンドが /etc/fstab の記述にしたがってマウントを行います。