GPTとMBRはどのように違うのか?

UEFI環境下では、BIOSでサポートされているMBRに代わりGPTと呼ばれる新しいパーティーションテーブルを用います。
2TB以上の大容量なHDDをサポート出来る、と説明される事が多いのですが、具体的にどのような違いがあるのか比較してみます。

MBR

MBRはLBA 0に置かれる512byteのパーティーションテーブルで、以下の様なレイアウトになっています:

オフセット サイズ 内容
0x000 446byte ブートストラップローダ
0x1be 64byte パーティーションテーブル(4エントリ)
0x1fe 2byte ブートシグニチャ(0xaa55)

ブートストラップローダはBIOSからロード・実行されるブートプログラムで、パーティーションの情報はパーティーションテーブルに存在します。
なお、最大パーティーション数は4個と決められており、4つのパーティーションを「物理パーティーション」と呼び、拡張パーティションと呼ばれる物理パーティーション内に論理的なパーティーションを入れ子式に作成する事により、パーティーション数の制限を回避しています(論理パーティション)。

パーティーションテーブルのレイアウトは以下のようになっています。

オフセット サイズ 内容
0x00 1byte ブートフラグ
0x01 3byte 開始セクタ(CHS)
0x04 1byte パーティーションタイプ
0x05 3byte 終了セクタ(CHS)
0x08 4byte 開始セクタ(LBA)
0x0c 4byte 総セクタ数

ブートフラグはOSの置かれているパーティーションの指定に使われます。
パーティーションタイプはパーティーションの種類の表現に使われます。
開始セクタ・終了セクタのエントリがCHSとLBAで二つありますが、これは大容量のHDDをサポートするため導入されたBIOSのLBA拡張により、MBRへ後から変更が加えられたものと思われます(参考)。
開始セクタ(LBA)に値がセットされた場合、開始セクタ(CHS)/終了セクタ(CHS)は無視されLBAの値が用いられます。

MBRからのブート

BIOSMBR上のブートストラップローダをロード・実行し、これに制御を譲ります。
この為、現在のOSではブートストラップローダへブートローダの一段目を書き込み、独自の手順で起動しているものが少なくありません。
しかし、元々のブートストラップローダはパーティーションテーブルからブートフラグがセットされているパーティーションを見つけ、先頭セクタをロード・実行するというプログラムであるべき、のようです(参考)。

この時、パーテーションの先頭セクタには以下のようなPartition Boot Record(PBR)が存在する事になっているようです。

オフセット サイズ 内容
0x000 3byte ジャンプ命令
0x003 8byte OEM
0x00B 25byte BIOSパラメータブロック
0x01C 26byte 拡張BIOSパラメータブロック
0x03E 448byte ブートストラップコード
0x1FE 2byte シグニチャ

ブートストラップローダは1セクタを読み込んで先頭アドレスを実行するだけだと思われますが、先頭のジャンプ命令によりブートストラップコードが呼び出され、BIOSパラメータブロック・拡張BIOSパラメータブロックはパーテーションの情報をブートローダ/OSへ与えるデータ領域として機能するようです(参考)。

一方、本来のブートストラップローダの仕組みを取っていない例としてGRUBMBRへインストールした場合の動作を紹介します(参考)。
GRUBMBRへ書き込む一段目のブートローダ(stage1)はLBA 1以降に配置されている二段目のブートローダ(stage1_5)をロード・実行する為に使われます。
stage1_5はLinuxファイルシステムを読み込む機能を持ち、ファイルシステムから三段目のブートローダ(stage2)を見つけてロードします。
他のOSのブートローダでは、また異なる方法でブートしている場合もあると思われますが、MBR・PBRのブートコードサイズの制約を逃れるため、これに似た手法で多段ブートを行なっている場合が殆どです。

MBRBIOSからロード・実行される時は当然リアルモードで実行されますが、多くのブートローダでプロテクトモードへの切り替えが行われており、これによってメモリ空間サイズの制約を受けてOSがロードできないといった事を避けたり、アセンブリではなくCで実装しより大きく高機能なブートローダを実現したりしているようです。

LinuxからみたMBR

fdiskからMBRを覗いてみましょう。

$ sudo fdisk /dev/sda
 
Command (m for help): p
 
Disk /dev/sda: 160.0 GB, 160041885696 bytes
255 heads, 63 sectors/track, 19457 cylinders, total 312581808 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x000f2acc
 
   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *        2048   262266879   131132416   83  Linux
/dev/sda2       262268926   312580095    25155585    5  Extended
/dev/sda5       262268928   312580095    25155584   82  Linux swap / Solaris

Bootがブートフラグ、Startが開始セクタ(LBA)、Endが開始セクタ(LBA)+総セクタ数、Blocksが総セクタ数、Id・Systemがパーティーションタイプに相当します。

GPT

GPTはLBA0-33とHDDの最終33セクタに置かれるパーティーションテーブルで、以下の様なレイアウトになっています。
http://upload.wikimedia.org/wikipedia/ja/thumb/c/cc/GUID_Partition_Table.png/643px-GUID_Partition_Table.png
MBRは古いソフトウェアとの互換性のために存在しています。
GPTヘッダーやパーティーションエントリが2つあるのは、第一エントリが破損した場合に第二エントリを使ってリカバリを試みるためです。
GPTヘッダーにはユーザが使用可能なディスクの領域やパーティーション数などの値が書き込まれます。
GPTヘッダーのレイアウトは以下のようになっています。

オフセット サイズ 内容
0 8byte シグニチャ
8 4byte GPTのバージョン
12 4byte ヘッダサイズ
16 4byte ヘッダのCRC32
20 4byte reserved
24 8byte 第一GPTヘッダのLBAアドレス
32 8byte 第二GPTヘッダのLBAアドレス
40 8byte 使用可能領域の開始LBAアドレス
48 8byte 使用可能領域の終了LBAアドレス
56 16byte ディスクのGUID
72 8byte パーティーションエントリのLBAアドレス
80 4byte パーティーションエントリ数
84 4byte パーティーションエントリのサイズ
88 4byte パーティーションエントリのCRC32
92 reserved

各LBAアドレスは64bitへ拡張されています。
GPTヘッダやパーティーションエントリの破損を検出する為のCRC32の値や、第一GPTヘッダのリカバリに使う第二GPTヘッダのアドレスが存在します。
第二パーティーションエントリのLBAアドレスが見当たりませんが、恐らく第二GPTヘッダに書かれているのではないでしょうか。
また、ディスクのGUIDが設定できるようになっています。
MBRにはユーザが使用可能なHDD領域の範囲を設定する項目はありませんでしたが、ここでは任意の値がセットできるようです。
パーティションエントリの数とサイズが指定できるようになっていますが、少なくとも64bit Windows環境では128個・128byteと設定されるようです。
このため、作成できるパーティション数は128となります。

次に、パーティションエントリのレイアウトを見てみます。

オフセット サイズ 内容
0 16byte パーティションタイプGUID
16 16byte パーティーションユニークGUID
32 8byte 開始LBAアドレス
40 8byte 終了LBAアドレス
48 8byte 属性フラグ
56 72byte パーティション

パーティションエントリのレイアウトはMBRのものと似ていますが、幾つか違いがあります。
まず、パーティションタイプは小さな数値ではなく、予め定義されたファイルシステムのGUIDとなっています。
Linux filesystem dataなら0FC63DAF-8483-4772-8E79-3D69D8477DE4、Windows Basic data partitionならEBD0A0A2-B9E5-4433-87C0-68B6B72699C7といった値を指定します(GUIDのリスト)。
MBRのブートフラグに似たものとして属性フラグがありますが、ここには読み込み専用・隠しパーティションなどの属性は存在しているものの「ブートパーティション」というフラグは存在しません(表「Partition attributes」を参照)。理由は後述します。
MBRには存在していないものとして、パーティションの識別子としてOSから使われるパーティーションユニークGUIDや、パーティション名を文字列で設定するフィールドがあります。

GPTからのブート

UEFIはブートストラップローダとパーティションのブートフラグを用いるBIOSのブート方式を踏襲していません。
このため、これらのフィールドはGPT上に存在しません。
UEFIでは、ブート対象のHDDのGPTを参照し、EFI System partition(GUID:C12A7328-F81F-11D2-BA4B-00A0C93EC93B)を探して \EFI\BOOT\BOOTX64.EFI(32bit環境ならBOOTIA32.EFI)というファイル名で保存されているブートローダを実行します(ファイル名はUEFIの設定により変更できます。これについては後日改めて記事にします)。
EFI System partitionは専用のパーティションタイプGUIDを用いますが、中身は普通のFATファイルシステムで、OSからも読み書きが可能です。
BOOTX64.EFIWindowsの.exeと同じPEバイナリで、32bitまたは64bitで動作しMBR上のブートストラップローダで見られたようなサイズ制限はありません。
BIOSで動くブートローダBIOSコールを用いてディスクにアクセスしたり画面表示を行うように、BOOTX64.EFIではUEFIが定めた方式でUEFIAPIを呼び出してディスクにアクセスしたり画面表示を行います。
それらの操作は完全にC言語で記述でき、BIOSで動くブートローダのようにプロテクトモードへの切り替えとそれに伴うアセンブリコードの実装などを必要としません。

このBOOTX64.EFIのようなプログラムの事を「UEFI Application」と呼びます。
GRUB2はUEFI Applicationとして動作します。
LinuxカーネルそのものをUEFI Applicationとしてビルドする事も可能なようです。

LinuxからみたGPT

fdiskに似たgdiskでGPTを覗いてみましょう。

test@uefitest:~$ sudo gdisk /dev/sda
GPT fdisk (gdisk) version 0.8.5
 
Partition table scan:
  MBR: protective
  BSD: not present
  APM: not present
  GPT: present
 
Found valid GPT with protective MBR; using GPT.
 
Command (? for help): p
Disk /dev/sda: 16777216 sectors, 8.0 GiB
Logical sector size: 512 bytes
Disk identifier (GUID): A2E69954-099E-4905-9D0A-7615E2043BB0
Partition table holds up to 128 entries
First usable sector is 34, last usable sector is 16777182
Partitions will be aligned on 2048-sector boundaries
Total free space is 4029 sectors (2.0 MiB)
 
Number  Start (sector)    End (sector)  Size       Code  Name
   1            2048          194559   94.0 MiB    EF00 
   2          194560        15730687   7.4 GiB     0700 
   3        15730688        16775167   510.0 MiB   8200  

NumberはGPTパーティション番号、Startは開始LBAアドレス、Endは終了LBAアドレス、Sizeは終了LBAアドレス - 開始LBAアドレス、CodeはパーティションタイプGUIDが長ったらしいのでgdiskの独自のパーティションコードに置き換えて表示中、Nameはパーティション名に相当するようです。
GUIDなどが表示されなかったので、各パーティーション毎に更に細かい情報を表示してみます。

Command (? for help): i
Partition number (1-3): 1
Partition GUID code: C12A7328-F81F-11D2-BA4B-00A0C93EC93B (EFI System)
Partition unique GUID: 78F5DBF5-6275-4776-B6B7-382043216865
First sector: 2048 (at 1024.0 KiB)
Last sector: 194559 (at 95.0 MiB)
Partition size: 192512 sectors (94.0 MiB)
Attribute flags: 0000000000000000
Partition name: ''

パーティーション1はEFI System partitionでした。
ユニークGUIDは78F5DBF5-6275-4776-B6B7-382043216865、属性フラグは無し、パーティーション名も無しのようです。

Command (? for help): i
Partition number (1-3): 2
Partition GUID code: EBD0A0A2-B9E5-4433-87C0-68B6B72699C7 (Microsoft basic data)
Partition unique GUID: 30FF5CB5-2B05-4876-B65D-1C5CC1CA6F08
First sector: 194560 (at 95.0 MiB)
Last sector: 15730687 (at 7.5 GiB)
Partition size: 15536128 sectors (7.4 GiB)
Attribute flags: 0000000000000000
Partition name: ''

これがrootパーティーションとしてマウントしているsda2ですが、GUIDにWindows Basic data partitionが指定されているようです。
Ubuntu 12.10のデフォルト設定でセットアップしたのにおかしいな、と思ったのですが、LinuxではデータパーティションWindows Basic data partitionのGUIDを使っていて、最近Linux用のGUIDが割り当てられたという事のようです(詳細)。
こちらのユニークGUIDは30FF5CB5-2B05-4876-B65D-1C5CC1CA6F08、属性フラグは無し、パーティーション名も無しのようです。

Command (? for help): i
Partition number (1-3): 3
Partition GUID code: 0657FD6D-A4AB-43C4-84E5-0933C84B4F4F (Linux swap)
Partition unique GUID: D0A434B9-5378-4684-A13D-480FE40C5957
First sector: 15730688 (at 7.5 GiB)
Last sector: 16775167 (at 8.0 GiB)
Partition size: 1044480 sectors (510.0 MiB)
Attribute flags: 0000000000000000
Partition name: ''

パーティーション3はLinux swap partitionでした。
ユニークGUIDはD0A434B9-5378-4684-A13D-480FE40C5957、属性フラグは無し、パーティーション名も無しのようです。

test@uefitest:~$ ls -l /dev/disk/by-partuuid/
total 0
lrwxrwxrwx 1 root root 10 Jan  3 07:10 30ff5cb5-2b05-4876-b65d-1c5cc1ca6f08 -> ../../sda2
lrwxrwxrwx 1 root root 10 Jan  3 07:12 78f5dbf5-6275-4776-b6b7-382043216865 -> ../../sda1
lrwxrwxrwx 1 root root 10 Jan  3 07:10 d0a434b9-5378-4684-a13d-480fe40c5957 -> ../../sda3

ユニークGUIDでパーティーションへアクセス出来る事を確認してみました。

test@uefitest:~$ mount|grep sda1
/dev/sda1 on /boot/efi type vfat (rw)
test@uefitest:~$ ls /boot/efi/
EFI
test@uefitest:~$ ls /boot/efi/EFI/
ubuntu
test@uefitest:~$ ls /boot/efi/EFI/ubuntu/
grubx64.efi

EFI System partitionがどこにマウントされているか調べてみたところ、/boot/efiへマウントされていました。 
中身を覗いてみると\EFI\ubuntu\grubx64.efiというバイナリが置いてあるようです。
ファイル名が\EFI\BOOT\BOOTX64.EFIではないわけですが、この点については次回説明することとしましょう。