パソコン実習室
ブート ストラップ - パソコンの起動 by BIOS
≪ previous  

X.Linux の起動 (by grub)

 LinuxのメジャーなブートローダーにgrubとLILOがあります。ここではLinuxのインストール時にブートローダーとしてgrubを指定し、その(grubの)インストール先としてMBRを選択した場合の動作を追います。

 Linuxのインストーラーにはブートローダーのインストール先を選択するオプションがありますが、この選択によってインストール先が変わるのはプライマリー ブートローダーだけです。他のファイルは通常、ルート パーティションにインストールされます。

X.1.予備知識

 Linuxはハードディスクの基本パーディション、論理パーディションのどちらにもインストールが可能で、該当パーティション(のアクティブ フラグ)がアクティブである必要もありません。こうした機能はLinuxのローダーであるgrubやLILOによって実現されています。

 grubはファイル サイズが大きくてMBRのサイズ(1セクター)に治まらないため、"プライマリー" と "セカンダリー" の2本のファイルに分割され、それぞれが stage1, stage2 と呼ばれています。
 stage1 を MBR、stage2 をLinuxのパーティション内に配置し、stage1 が stage2 をロード、stage2 が Linux(のカーネル)をロードする仕組みになっています。

 stage1 は、ハードディスクのセクターをLBAの値で直接指定して stage2 をロードします。
※ 特定のファイルシステムに依存せず、処理の手間とコードが大きくなることを防いでいます。

X.1.1.grubがロードするLinuxのファイル

 grubがロード対象とする vmlinuz-xxxx(Linuxのカーネルやデバイス初期化プログラムで構成)は、次に示すように3つのブロックが組み合わされています。

【 vmlinuz-xxxx の構成 】
bbootsectbsetupbvmlinux.out

各ブロックは次の役割を担っています。
  • bbootsect
    フロッピーディスク用のブートコード。512バイト固定。通常ほとんど使われません。
    ただし497バイト目に bsetup のサイズ(セクター単位)が記録されます。
  • bsetup
    デバイスの初期化、プロテクトモードへの移行を行います。
    ファイルサイズはセクターサイズ(512バイト)単位に調整され、セクター数は bbootsect の497バイト目に記録されます。
  • bvmlinux.out
    zlib形式で圧縮済みのカーネル※1本体。 自己解凍(展開)コードを含んでいます。
※1 OSの核となるプログラム群をカーネルといいます。 vmlinuz-xxxx も便宜上カーネルと呼ばれています。

▼ Linuxカーネルのソースファイルをビルド(make)する方法に bzImage形式 と zImage形式 があります。
  それぞれの形式で生成されるイメージファイルとその内容は次のようになります。
   bzImage 形式 :イメージファイル bzImage : 内容 bbootsect, bsetup, bvmlinux.out
   zImage 形式 :イメージファイル zImabe : 内容 bootsect, setup, vmlinux.out
  (zImage 形式は現在ほとんど使われません)
▼ イメージファイルは vmlinuz-xxxx にリネームして配布されます。

 grubは vmlinuz-xxxx の他に initrd-xxxx.img もロードします。これは初期化 ramdisk と呼ばれ、仮のrootファイルシステムとして起動直後のカーネルで利用されます。
 カーネルは初期化 ramdisk を通して本来のrootファイルシステムをマウントし、各種ドライバ等を組み込んでOSとしての機能を確立します。
※ Linuxカーネルは基礎的な機能しかありません。単体ではファイル アクセスさえ出来ず、ファイル システ
  ム、デバイスドライバ、拡張機能等のモジュールを環境に応じてディスクから読み出して、システムに組
  み込む仕様になっています。

 以下の説明ではLinuxのカーネルを "vmlinuz-2.4.21-4.EL"、初期化ramdiskを "initrd-2.4.21-4.EL.img" としていますが、これらのファイル名は使用するソース ファイルのバージョン等によって細かに変わる事にご留意下さい。

X.1.2.grubの動作概要

 Linuxインストールのオプションでgrubのインストール先にMBRを選択すると、インストーラーは /boot/grub/stage1 に次の情報を追加してMBRへ書き込みます。
  • 元々MBRに記録されていたBPBとパーティションテーブルの情報。
  • ブートドライブの種別。
  • /boot/grub/stage2 の先頭セクター位置(LBA)情報。
※ grubのインストール先にパーティションを選択した場合は、stage1はパーティションの先頭セクター(ブー
  トセクター)に書き込まれます。当然上記と同様の編集が行われますが、参照されるのはMBRではなくブー
  トセクターになります。

grub(stage1, stage2)によるLinux起動の動作は概ね次のようになります。



 grubの分割は2分割(stage1+stage2)の外に3分割(stage1+stage1.5+stage2)になる事もありまが、Linuxと同時に(CDから)インストールしたgrubでは、通常2分割でインストールされます。ここでは2分割時の動作を追います。
※ 3分割のgrubでは、stage1.5はMBRに続く連続した数セクターにインストールされます(上図参照)。
  この位置はBIOSコール(INT 13H)に伴う指定(C/H/S=0/0/2)が簡単であり、小さなコードのstage1で容
  易にロードできます。
※ stage1.5はファイル システムを解釈します。ファイル システムを使ってstage2のパス探索が可能なので
  インストール後にstage2を移動(セクター位置を変更)した場合でもstage2のロードが可能です。


X.2.stage1

X.2.1.stage1の構成

 MBR(512バイト)に格納されたstage1の構成は次の通りです。

名称格納位置(アドレス)
プログラム0x0000〜0x0002, 0x0004a〜0x0175, 0x0197〜0x01ac
BPB0x0004〜0x003d
定数部分0x003e〜0x0049
メッセージ文字列0x0176〜0x0196
リザーブまたは未使用0x0003, 0x01ad〜0x01bd
パーティション テーブル0x01be〜0x01fd
Boot Signature0x01fe〜0x01ff
※ プログラムやメッセージのサイズはgrubのバージョンで異なります。

 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が使用するセグメント値)


X.2.2.stage1の動作

 BIOSによってメモリの 0x07C00 にロードされ、制御を渡されたstage1は、定数部分(前項参照)を参照してstage2の先頭セクター(512バイト)をロードします。
  1. stage1の先頭にはジャンプ命令があります(下記はその一例です)。
    メインとなるプログラムがある位置へジャンプします。
  2. 0x7C00:0000 eb 48 JMP 4A (実際のジャンプ先は 0x07C4A)
  3. 定数部分のBoot Drive番号を参照し、0xff(無効ドライブ)でないことを確認します。
  4. 定数部分のstage2 sectorで得たハードディスクのセクター位置から、stage2 Address で得たメモリアドレス 0x08000 へ、stage2の先頭512バイトをロードします。
    ※ この時ディスプレイには "grub" とだけ表示されます。
      stage2が存在しない場合はその状態で停止します。
  5. stage2先頭部分のロード先(0x08000)へジャンプし、制御をstage2へ渡します。


X.3.stage2

 stage2ファイルはいくつかのブロックに分割されてディスク上に格納されています。
最初のブロックの先頭512バイトには後続セクターのロード用コードの他に、以後ロードすべきstage2の残りブロックのセクター位置がgrubのインストール時に書き込まれています。

X.3.1.stage2の構成

 先頭セクター(512バイト)の構成は大きく2つに分かれます。0x0000〜0x0137までがコードやメッセージで構成され、残りの部分(0x0138〜0x01ff)は後続セクター情報やロード先セグメントアドレスを指定するブロックリストになっています。

 ブロックリストはディスク上に分散しているファイルを、連続しているセクターごとにブロック(塊)として管理するためのもので、1ブロック当たり8バイトで管理し最大25ブロックまで管理可能です。
※ ブロックリスト用に割り当てられたエリアは0x0138〜0x01ffの200バイト。
  1ブロックあたり8バイトなので管理可能数は最大25ブロックになります。

8バイトで構成するブロックリストの内部は3つのエントリーに分かれています。
【 ブロックリスト(8バイト)の構成 】
4バイト2バイト2バイト
ディスク上のセクター位置当ブロックのセクター数ロード先セグメント アドレス

【 ブロックリストは後方から順に使用します 】
 例えばstage2が3ブロックに分散配置されているような場合、先頭セクターの末尾32バイトには下図のような情報が記録されます。
※ ブロックリストのセクターアドレスはLBA形式です。

このような例では、次の順序でロードが行われます。
  1. HDのセクター アドレス 0x04e9fe5b から95(0x005f)セクター分を、
    メモリの 0x0820 : 0000 (0x08200)番地にロードします。
  2. HDのセクター アドレス 0x04e9fec2 から47(0x002f)セクター分を、
    メモリの 0x1400 : 0000 (0x14000)番地にロードします。
  3. HDのセクター アドレス 0x04ea01f6 から65(0x0041)セクター分を、
    メモリの 0x19e0 : 0000 (0x19e00)番地にロードします。
 ディスク上に分散して配置されているstage2も、ロード先のセグメント アドレスを適切に指示することで、メモリ上では切れ目なく連続したアドレスに格納される事になります。

X.3.2.stage2の動作

 stage2は先頭セクター(512バイト)のみがロードされた状態で制御を引き継いでいます。まず自身(stage2)の第2セクター以降をロードし、その後本来の目的であるカーネル(OSの核となるプログラム群)のロードに移ります。
stage2の動作は概ね以下の通りです。
  1. ブロックリストの情報を基にハードディスクのセクターを直接指定して、stage2の残り部分(第2セクター以降)をメモリの 0x008200 以降へ順次ロードします。
  2. ※ この時ディスプレイには "grub Loading stage2 ....." と表示されます。
      stage2が損傷している場合は "grub Loading stage2 Read Error" を表示して停止します。
  3. stage2のロードが完了すると、第2セクターのロード先である 0x08200 へジャンプします。
    stage2の動作はカーネルのロードに移ります。
    stage2第2セクターの 0x0017 〜 0x002f には、grubがインストールされた時に
    設定ファイル(/boot/grub/grub.conf)へのパスが書き込まれています。下記はその記述例です。
    (hd0,2)/grub/grub.conf
    この記述は、ブート(boot)パーティション配下のgrubディレクトリを指示しています。
  4. 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
    ※ 1行目はタイトルなので、実際にコマンドが実行されるのは2行目からです。
    それぞれのコマンドは次の働きをします。
    • root (device,partition)
      指定されたデバイスのパーティションをマウントし、そこをgrubのルートにします。デバイスとパーティションの番号はBIOSと同様 "0" から始まります。
    • kernel /path/kernel [option...]
      ロードするカーネルをフルパスで指定します。
    • initrd /path/initrdFile
      ブート時に使用する初期RAMディスクを指定します。
  5. rootコマンドを実行すると、上記の例(hd0,2)ではハードディスク "0" のパーティション "2" を、grub用のルート パーティションとして認識するようになります。
    以降のコマンド(kernelやinitrd)では、パラメーターで指定されたイメージを、このパーティションからロードすることになります。
    ※ このパーティションはLinuxから見ると /boot になります。
  6. kernelとして指示されたファイル(参考例ではvmlinuz-2.4.21-4.EL)の先頭部にアクセスし、その種別(bzImageまたはzImage形式)を判別します。
    ※ vmlinuz-2.4.21-4.EL の構成は、X.1.予備知識 の【vmlinuz-xxxx の構成】を参照して下さい。
    以降は "bzImage 形式" であったとして説明します。
  7. vmlinuz-2.4.21-4.EL の先頭セクター512バイト(bbootsect部分)を、メモリアドレス 0x090000 以降へロードします。
    このセクターの497バイト目にbsetup部分のサイズ(セクター単位)が記録されています。
  8. ロードしたbbootsect部分の497バイト目を参照して、次にロードするbsetupのセクター数を取得します。
  9. vmlinuz-2.4.21-4.EL の第2セクター以降にあるbsetup部分を、メモリアドレス 0x090200 以降(bbootsectに連続した領域)へロードします。
  10. メモリ上に連続してロードしたbbootsectとbsetupの境界を跨いだエリアに、ブート環境パラメーターを格納する領域を確保します(右図参照)。
  11. bsetupのロードアドレス以降で 0x100000 以下のメモリ上に、カーネル パラメーターを収めた領域を確保し、その先頭アドレスをブート環境パラメーター領域に設定します。
  12. vmlinuz-2.4.21-4.EL の3つ目のブロック(bvmlinux.out)を、メモリアドレス 0x100000 以降へロードします。
    ▼ bvmlinux.out は圧縮されたカーネル本体。
  13. grub.confのinitrdで指定されたイメージファイル(initrd-2.4.21-4.EL.img)をbvmlinux.outのロードエリアより上位(上図のinitrd参照)にロードし、その先頭アドレスをブート環境パラメーター領域に設定します。
  14. CPUの動作モードをリアルモードへ変更します。
  15. bsetupをロードした 0x090200 へジャンプしてgrubの役目は終了します。
    ▼ stage1やstage2がロードされていた領域は不要になります。
    ▼ 実際のgrubは、リアルモードとプロテクトモードの切り替えを何度も行いながら、システムを
      ブートします。
    リアルモードは[U.BIOSの動作 - 2.制御の開始] の囲み記事 [リアル モード] を、プロテクトモードは [W.Windowsの起動 - 3.Ntldrの動作] の囲み記事 [プロテクトモード] を参照して下さい。

▼ MBRのstage1がロードするstage2の先頭セクター、およびstage2の先頭セクターがロードする後続のセ
  クターは、ファイルシステム(ext2/ext3)を介さずにセクター位置を直接指定してロードします。
  これは次の事を意味します。
  ・stage2のファイル名を変更しても、ディスク上の位置(セクター)に変更がなければロード可能です。
  ・stage2へのパスが同じでも、セクター位置が変更されているとロードできません。


X.4.bsetup

 bsetupにはAT互換機に特化した初期化ルーチンが収められていて、BIOSサービスを通してハードウェア情報を取得し、ブート環境パラメーター領域へ設定します。
さらにBIOSサービスや出力ポートを介して周辺機器の初期化を行った後、CPUの動作をプロテクトモード(※1)へ遷移します。

  1. BIOSサービスを用いて次のハードウェア情報を取得し、ブート環境パラメーター領域に設定します。
    • メモリマップ
    • HDDユニットの各種パラメーター
    • PS/2 マウス
    • ビデオ カード
    • 電源管理機能
  2. BIOSサービスや出力ポートを通して下記の初期化等を行います。
    • キーボード コントローラーのリピート レート
    • 割り込みコントローラーのマスク
    • ビデオ カードの画面モード
    • FPU
  3. "A20 マスク" を解除します。
  4. プロテクトモード下のCPUが使用する各種管理テーブル(GDT,IDT)を、一時的にbsetup領域上に確保します。
  5. CPUの動作モードをプロテクトモード(※1)に遷移します。
  6. ロード済みの bvmlinux.out(圧縮されたカーネル本体)の先頭(0x00100000)にジャンプしてbsetupの役目は終了します。
(※1) プロテクトモード(Protected Virtual Address Mode)
   [W.Windowsの起動 - 3.Ntldrの動作] の囲み記事 [プロテクトモード] を参照して下さい。


X.5.カーネル イメージの展開

 bvmlinux.out(圧縮されたカーネル本体)の先頭には自己展開ルーチンが納めれらています。制御を渡された自己展開ルーチンは次の処理を行います。
  1. 展開処理に必要なスタック領域(4KB)を確保します。
  2. bvmlinux.out 内にあるBSS領域(静的データを格納する領域)を識別し、ゼロ値でクリアします。
  3. 解凍コードが使用するヒープ領域(3000bytes)を bvmlinux.out の直後に確保します。
  4. カーネル イメージの前半(解凍後のサイズで568KB)を 0x00002000 〜 0x00090000 に展開します。
  5. 残りのカーネル イメージを3項で確保したヒープ領域の後方に展開します。
  6. 展開ルーチンは自分自身の一部を 0x00001000 にコピーし、そちらにジャンプして処理を続行します。
  7. 2つの領域に分断しているカーネル イメージを連結して 0x00100000 にコピー(上書き)します。
  8. ブート環境パラメーター領域の先頭アドレス (0x00090000) をESIレジスターに格納します。
  9. 展開済カーネル イメージの先頭アドレス (0x00100000) へジャンプすることで、Linuxのカーネルが本格的に動き始めます。

X.6.カーネルの初期化

 Linuxのカーネルは、仮想アドレス空間である 0xC0100000 に配置された時に正しく動作するよう作られていますが、カーネル起動直後のこの段階では、メモリ管理は未だ開始されていません。
物理メモリの先頭 8MB に限定したページング仮設定からカーネルの初期化が始まります。
  1. 仮ページング設定を行います。
    下記仮想アドレス空間のページテーブルを設定して、両方に同じ物理メモリ領域を割り当てます。
    仮想アドレス空間割り当てる物理メモリ領域
    0x00000000 〜 0x008000000x00000000 〜 0x00800000
    0xC0000000 〜 0xC08000000x00000000 〜 0x00800000
    こうすることで、0x00100000 の物理アドレスに構築されたカーネルは、あたかも 0xC0100000 に配置されたかのように振舞うことができ、bsetup等が記録した引き継ぎデータにもアクセス可能となります。
  2. 後続のプログラムのため、スタック領域(8KB)を確保します。
  3. カーネル内にあるBSS領域(静的データを格納する領域)を識別し、ゼロ値でクリアします。
  4. IDT(Interrupt Descriptor Table:割り込みディスクリプタテーブル)を再定義します。
  5. ESIレジスターを参照し、ブート環境パラメーターを特殊な領域(empty_zero_page)へコピーします。
  6. CPUの種類を検出します。
  7. GDT(Global Descriptor Table)を再定義します。
    カーネル用セグメントの他に、ユーザー用、APM(Advanced Power Management)BIOS用のセグメントを追加します。

X.7.カーネルの開始

  1. 全ての物理メモリを対象としたページングの設定を行います。
  2. カーネル パラメーターの読み込みを行います。
  3. カーネル内に静的に組み込まれているデバイス ドライバを初期化します。
  4. ロード済みの initrd-2.4.21-4.EL.img を展開し、仮のルート "/" ファイル システムとしてマウントします。
  5. 展開された initrd-2.4.21-4.EL.img は初期化ramdisk(initrd)となり、デバイスド ライバや本来のルート "/" ファイル システム組み込みに必要なモジュールを提供します。
  6. 仮ルート ファイル システムにある /Linuxrc を実行します。
  7. /Linuxrcは以下の処理を行います。
    • ユーザーの環境に応じたドライバ モジュールを組み込みます。
    • 本来のルート ファイル システムのマウントに必要なモジュールをロードします。
  8. カーネル パラメーター(grub.confのkernel行にあるroot=/dev/hda14)で指定されたパーティションを、本来のルート ファイル システムとしてマウントし、ルート ディレクトリ "/" として参照可能にします。
    仮にマウントされていたinitrdは参照されなくなります。
  9. カーネル起動時のみ使用される部分をメモリ上から開放します。
    これらのイメージはカーネルの後部にまとめられているので、カーネルの末尾が縮小する形になります。
  10. /sbin/init を起動し、以後順次起動プロセスを実行してLinuxの起動が完了します。
    ルート "/" とは別のパーティションに分けられた /usr や /home などは、rcスクリプト中に記述されている mount コマンドが /etc/fstab の記述にしたがってマウントを行います。
【 initプロセス 】
initプロセスは全てのプロセスの祖先となり、カーネルのシャットダウンまで存続します。

【 ルート "/" ファイル システムのマウント 】
 ローカル ファイル システムを書き込み可能でマウントするには、fsckコマンドによるファイル システムのチェックが必要ですが、カーネルがルート ファイル システムをマウントする時点では、/sbin にあるfsckコマンドにはアクセスできません。
 そこでカーネルによるマウント時にはリード オンリーでマウントし、/sbin/init によって起動されるrcスクリプト中でfsckを実行後、書き込み可能状態でremountします。




≪ previous [[ ブート ストラップ - パソコンの起動 by BIOS ]]