LinuxにおけるMBRのまとめとバックアップ方法

ブート方式も間もなくMBRからEFIへ移行しようとしているし、GRUBGRUB 2へ移行しようとしているので、MBRGRUB 0.9xについて今さら調べても仕方ないのだが、ちゃんとまとめておいた方がいいと思い、ちょっと調査してみた。

また、後半はMBRのバックアップについて考察してみた(⇒MBRのバックアップ)。バックアップの方法よりも実践的なバックアップの保存先をどうするかを視点としている。一番実践的なのはHDDの最終セクタにMBRをコピーする方法だろう。

以下の調査にははGRUB 0.97を使っている。OSはFedora 8を使っている。

MBRについて

MBR(Master Boot Record)はシステムディスク(HDD)の先頭セクタ(第0セクタ)に置かれて、BIOSがシステムをブートするために使うソフトウェアとデータが格納されている。1つのセクタに格納するので512バイトという限られたサイズにプログラムやデータが格納されている。OSの種類によらず、次の2つの領域が確保されている。

  1. ブートストラップローダ:BIOSがHDDからメモリ上に読み込んで実行する。400バイト程のソフトウェアなので、このプログラムは更に次の段階のブートローダ(2次ローダ)を読み込むために使われる。
  2. パーティションテーブル:ひとつのディスクを複数のミニディスクとして扱うための各パーティションの開始位置と大きさ等を記述したデータ。4つのパーティションを定義できる。

MBRの「仕様書」なるものは色々探してみたが見当たらなかった。歴史的にはIBMIBM-AT機でフロッピーやHDDからOSをブートするために採用したらしい。現在では色々なOSで利用されているが、概ね次のような構成になっている。

この情報は次のLinuxコマンドの出力を編集したものである。

# hexdump -C -n 512 /dev/sda

ブートストラップローダは446バイト(0x0000〜0x01bd)、パーティションテーブルは64バイト(0x01be〜0x1fd)、シグネチャが2バイト(0x01fe〜0x01ff)。

ブートストラップローダの構造

それでは次にブートストラップローダの領域の構造を見てみる。ここはOSによって異なってくる。ここではGRUBの構造を概観する。

プログラム部分は3ヵ所に分けれている。MBRの最初の3バイトは

7C00 EB48          JMP     7C4A
7C02 90            NOP

とうプログラムになっており、2番目の0x004a番地から始まるブートストラップローダの本体にジャンプしている。なお、MBRのブートストラップローダはx86系のマシンではメモリ上の0000:7C00番地にロードされ実行されるので、上のようなアドレス表現になる。
プログラム部分の3番目(0x01a0〜0x01ac)はメッセージを表示するためのサブルーチンとなっている。
プログラム部分の詳細に関しては「The GRUB MBR」が詳しい。

GRUBブートストラップローダが使うデータ領域は 1.BPB(BIOS Parameter Block)、2.システム定数、3.メッセージがある。BPBはBIOSコールで使うBIOSとの間でデータを受け渡しするための領域。システム定数は2次ローダのディスク上の位置やロードするメモリのアドレスなどを保持している。

なお、ドライブシリアル番号はWindows NT系のOSが使用するのでGRUBLinuxにとっては意味を持たないと思われる。ここを0x00000000にしてもブートすることは確認したが、Linuxの全てのソフトウェアがこの値を使っていないとは言えない。例えば fdiskコマンドでディスクの情報を出力すると、

[root@linux ~]# fdisk -l /dev/sda

Disk /dev/sda: 500.1 GB, 500107862016 bytes
255 heads, 63 sectors/track, 60801 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Disk identifier: 0xba41ba41

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *           2         961     7711200   83  Linux
/dev/sda2             962        1921     7711200   82  Linux swap / Solaris
/dev/sda3            1922       60793   472889340   83  Linux
/dev/sda4           60794       60801       64260   83  Linux
[root@linux ~]#

と、“Disk identifier”として表示される。

BPBの構造

BPBの構造は以下の通り。

ブートストラップローダはHDDがCHS(シリンダ/ヘッド/セクタ)によるアクセス方式なのか、LBA(Logical Block Addressing、論理ブロックアドレス)によるアクセス方式なのかにより各バイトの利用目的が異なる。BIOSの使い方は等は「INT 13 - Wikipedia」などを参照のこと。

なお、GRUBをHDDにインストールするとBPB部分には色々な値が書き込まれているが、このBPB部分はブートストラップローダが初期化して使うので、元々書かれているデータには意味がないと思われる。多分、メモリに展開された雛型をMBRへ書き込むのでゴミが入っているのではないか?と思われる。例えば、次のようにして(3バイト目を含む)BPB部分59バイトを全てゼロにクリアした状態でリブートしても問題なく起動する。

 [root@fedora-8 ~]# dd if=/dev/zero of=/dev/sda bs=1 seek=3 count=59
 59+0 records in
 59+0 records out
 59 bytes (59 B) copied, 0.000123151 s, 471 kB/s
 [root@fedora-8 ~]# hexdump -C -n 512 /dev/sda
 00000000  eb 48 90 00 00 00 00 00  00 00 00 00 00 00 00 00  |.H..............|
 00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
 *
 00000030  00 00 00 00 00 00 00 00  00 00 00 00 00 00 03 02  |................|
 00000040  80 00 00 80 6f 7c 1e 00  00 08 fa 90 90 f6 c2 80  |....o|..........|
  :
  :
 [root@fedora-8 ~]# reboot

システム定数

ここにはGRUBブートストラップローダで使うためのシステム定数が保存されている。(この部分は特に名称が見つからなかったので、ここでは「システム定数」と呼んでいる。)

幾つかポイントだけ補足する:

  • 0x0040番地:2次ローダのあるドライブ。0xffは無効ドライブなのでここが0xffであればBIOSから渡されたドライブを使う。0xff以外であれば、そのドライブを使って2次ローダを読み込む。明示的にドライブを指定する場合は大抵0x80であり、0x80はHDDの第1ドライブ(システムドライブ)。
  • 0x0042番地:2次ローダのメモリ上の開始番地。0000:8000
  • 0x0044番地:2次ローダが格納されているHDDのセクタ番号。
  • 0x0048番地:2次ローダの保存セグメント。0800:0000

grubによるインストールとそれによる変更部分は「LinuxにおけるgrubによるMBRの修復方法」の“grubによるMBRの修復”部分を参照)
ここで重要なのは44番地〜47番地に格納されている2次ローダのHDD上のセクタ番号。サイズの小さいブートストラップローダでは複雑なことは出来ず、ファイルシステムを解釈してHDD上のファイルの位置を特定するのは無理なので、ここに2次ローダのHDD上の位置(セクタ番号)を直接埋め込んでいる。従って、システムをインストールした後、次のような形で2次ローダ(/boot/grub/stage2)をバックアップ&リストアしてstage2のHDD上の位置を変更してしまうとシステムは立ち上がらなくなってしまう。

# tar cf backup.tar /boot/grub/stage2
# rm -f /boot/grub/stage2
# dd if=/dev/zero of=/tmp/zero bs=512M ; rm /tmp/zero
# tar xf backup.tar
# reboot

パーティションテーブルの構造

4つの基本パーティションの各開始位置、サイズなどが保存されている。(基本パーティションが4つしか設定できないのは、このため。IBMが元凶だった。)

パーティションのエントリの構造は次のようになっている。(ここでは第1パーティションのエントリを使って例示している。)

8GB以上の最近のHDDはLBAアクセスなのでCHSの開始位置や終了位置は意味を持たないが、いくつかのソフトウェアでは物理(CHS)の値と論理(LBA)の値をチェックして警告してくるので、合わせておいた方がいい。
また、ブートフラグもGRUB(やLinux)では意味を持たないと思われる。
参考までに(お遊びで)これらのブートフラグやCHS表現の開始位置、終了位置を全てゼロにクリアしても問題なくブートする。

[root@fedora-8 ~]# dd if=/dev/zero bs=1 count=4 of=/dev/sda seek=446
4+0 records in
4+0 records out
4 bytes (4 B) copied, 3.5625e-05 s, 112 kB/s
[root@fedora-8 ~]# dd if=/dev/zero bs=1 count=3 of=/dev/sda seek=451
3+0 records in
3+0 records out
3 bytes (3 B) copied, 4.2833e-05 s, 70.0 kB/s
[root@fedora-8 ~]# hexdump -C -n 512 /dev/sda
00000000  eb 48 90 10 8e d0 bc 00  b0 b8 00 00 8e d8 8e c0  |.H..............|
 :
 :
000001b0  00 00 00 00 00 00 00 00  2f dc 00 00 00 00 00 00  |......../.......|
000001c0  00 00 83 00 00 00 3f 00  00 00 d3 0c e0 00 00 00  |......?.........|
000001d0  c1 92 82 fe ff ff 12 0d  e0 00 41 9f 1f 00 00 00  |..........A.....|
000001e0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000001f0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 55 aa  |..............U.|
00000200
[root@fedora-8 ~]# reboot

つまり、今時のHDDを使った今時のLinuxに関して言えば、パーティションテーブルの内、パーティションタイプ、開始セクタ番号、パーティションサイズの3つのデータだけ合っていればブートは問題なく出来る。(fdiksコマンド等を実行すると警告メッセージが出る場合があるが。)
なお、MBRに保存されているCHSのデータは次のように解釈され、実際のヘッド番号、セクタ番号、シリンダ番号に変換している。(今時、CHS表現は使わないが。)

昔のシステムではブートストラップローダがCHSアクセスしかできないため、stage2ファイルはHDDの先頭から8GB以内に配置しなければならなかったそうだ(未確認)。しかし、今のgrubはCHSアクセスでもLBAアクセスでも可能になっているのでstage2ファイルはHDDの何処に配置しても大丈夫。/bootディレクトリはシステム起動時以外は殆どアクセスすることはないので、私はHDDの一番内側(=HDDの先頭から最も遠い位置)に配置している(「vmserverのHDD」、「HDDの外側と内側」参照)。と言ってもGRUBのシステム定数で扱うLBAのセクタ番号は4バイトなので、2.2TBまでしかアドレスできないか。つまりstage2がHDDの先頭から2.2TB以上離れるとアクセスできなくなるのかな? 2.2TBを超えるHDDを持っていないので確認できない。

パーティションの情報は開始位置とサイズ(8バイト)だけ保持しておいて、パーティションタイプやブートフラグはパーティションの中で定義すれば良いのではないか、と思ってしまう。(そうすれば基本パーティションも8つにできただろうし。)

stage1ファイルの内容について

MBRにインストールされるGRUBのデータは /boot/grub/stage1 を元に作成される。つまり stage1 がほぼMBRの内容になっている。stage1のファイルサイズはMBRのサイズと同じく512バイトである。が、このファイルをそのままMBRへ書き込んだのではシステムばブートしない。(2次ローダは同じく /boot/grub/stage2 であり、このファイルの先頭があるHDD上のセクタ番号がMBRに書き込まれていなければならないが、stage1ファイルにはその情報は書き込まれていない。)

参考までに stage1ファイルの内容を載せておく。

MBRの構造と比べると分かるとおり“システム定数”領域にあるstage2のHDD上のセクタ番号やパーティションテーブルの値は書き込まれていない。また、Boot Driveも“無効”の0xffになっている(システムHDDであれば0x80)。 MBRをインストールする時にこれらの値が設定されMBRに書き込まれる。
なお、パーティションテーブルの部分にはフロッピーからブートする際のプログラムが書き込まれている。HDDベースのシステムではこのプログラムを必要ないのでパーティションテーブルが書き込まれる。

stage2ファイルの内容について

MBRというタイトルからは少し離れてしまうが、stage1(=MBRのブートストラップローダ)によりHDDからメモリに読み込まれる2次ローダのstage2の構成についてちょっと触れておく。

stage1で読みこまれるstage2は512バイトしかない。しかし、stage2の大きさは105KBもある。つまりstage2は先ず、自分自身をHDDからメモリ読み込む必要がある。とはいっても最初に読み込まれる512バイトではstage1と同様に大したことはできない。ext3などのファイルシステムを解釈してHDD上からファイルを読み込むことはできない。そこでstage1と同様にstage2の残りの部分のHDD上の位置=セクタ番号を使ってファイルシステムを通さず直接HDDから読み込む。
先ず、stage2の先頭512バイトの構造を眺めてみる。

最初の部分はstage1と同様、プログラムの本体、BPB、メッセージなどから構成される。後半にstage2の残りの部分がHDDの何処にあるかを示す“ブロックリスト”がある。stage2は105KBもあるので、HDD上の連続した領域に配置される保証はない。基本的にはHDD上のあちらこちらに分散されて配置される。その塊の一つ一つの場所をリストにしている。このリストは8バイトのデータの集まりで、リストは512バイトの後ろから前に向かって連なっている。つまり、残りのstage2の最初の塊は0x1f8〜0x1ffであらわされて、2番目の塊は0x1f0〜0x1f7という風に記憶されている。
各ブロックリストのエントリの構成は以下の通り。

後ろから順に、HDDから読み込まれる塊のメモリ上のロード先(セグメントアドレス)、読み込むセクタ数、読み込む塊の先頭セクタとなる。
上の例では、

最初の塊はHDDの第0x3a36ae54セクタから 7(0x0007)セクタで、メモリの0x0820:0000番地にロードする
2番目の塊はHDDの第0x3a36b72dセクタから 16(0x0010)セクタで、メモリの0x0900:0000番地にロードする
3番目の塊はHDDの第0x3a36b73fセクタから182(0x00b6)セクタで、メモリの0x0b00:0000番地にロードする

となる。つまり、このシステムの場合、/boot/grub/stage2というファイルはHDD上で3つの塊として置かれていることになる。読み込むセクタ数の総数は(stage1が読み込んだ1セクタ分も含めて)206セクタで105,472バイトとなる(stage2のファイルサイズは105,468)。また、計算すると分かるが、各塊はメモリ上では一続きとして配置される。
これで/boot/grub/stage2の全内容をメモリ上へ展開できることになる。

これから分かるようにstage1と同様、stage2もファイルのHDD上の位置が埋め込まれた状態でインストールされるので、stage2のファイルの配置が変ってしまった場合はGRUBを再度インストールする必要がある。

参考リンク

The GRUB MBR:英語だが、大変詳しい解析が載っている。GRUBだけでなく(というか)Windows環境でのMBR(の方が主)もある。ブートローダについて勉強したい場合は強くお勧め。(ただ英語なのでちょっと読むのが大変だが。)
ブートの仕組み:日本語であれば、ここが詳しい。Windows環境を使った説明になっているが、一般的な説明にも言及している。
MBR(Master Boot Recode)の構造:ちょっと古いページだがBSD環境におけるMBRの構造について書いている。パーティションテーブルの構造について詳しい。日本語なので読みやすい。
GNU GRUBGNUGRUBのホームページ。主流はすでにGRUB 2.0へ移行している。もちろん英語。