从MBR到GPT

Last Updated: 2023-06-13 09:44:50 Tuesday

-- TOC --

MBR和GPT是两种不同的硬盘分区方式,它们与系统启动以及OS的支持都有关系。

Windows系统中每个盘符对应一个分区。Linux系统中没有盘符的概念,所有的分区都挂载(mount)在根目录(/)中的某个分支上,就一棵树!

MBR

MBR是Master Boot Record的缩写,它位于启动硬盘的第一个扇区(sector),512 Bytes。对于使用传统BIOS启动的计算机,BIOS在完成POST(Power On Self Test)后,根据CMOS中设定的启动顺序选择引导设备(硬盘,光盘,网络,U盘等等)。如果引导设备设置为硬盘,那么BIOS就会去读取硬盘的第一个扇区的这512个字节。

BIOS将MBR的512字节读取到内存的0x7C00地址,然后让CPU跳转到这里开始执行!

这512个字节里面,包含了一段引导程序,一个分区表和最后两个字节的Magic Number。其结构如下图:

mbr

512字节的MBR包含3部分:

  1. 第1-446字节:一段代码,一般用于加载操作系统。
  2. 第447-510字节:64字节的分区表(Partition table)。
  3. 第511-512字节:主引导记录签名(0x55和0xAA)。

分区表的长度只有64字节,里面又分成4项,每项16字节。所以,一个MBR硬盘最多只能分出4个一级分区,又叫做主分区。每个主分区的16个字节,由如下6个部分组成:

  1. 第1个字节:如果为0x80,就表示该主分区是激活分区,控制权要转交给这个分区,4个主分区里面只能有1个激活分区
  2. 第2-4个字节:主分区第一个扇区的物理位置(柱面、磁头、扇区号)。
  3. 第5个字节:主分区类型。
  4. 第6-8个字节:主分区最后一个扇区的物理位置(柱面、磁头、扇区号)。
  5. 第9-12字节:该主分区第一个扇区的逻辑地址。
  6. 第13-16字节:主分区的扇区总数。

最后的4个字节(主分区的扇区总数),决定了这个主分区的容量。也就是说,一个主分区的扇区总数最多不超过2的32次方。每个扇区为512个字节,这就意味着单个主分区最大不超过2TB。再考虑到扇区的逻辑地址也是32位的,所以单个硬盘可利用的空间最大也不超过2TB

随着硬盘越来越大,4个主分区也许不够,需要更多的分区。但是,分区表只有4项,因此MBR技术规定,有且仅有1个主分区,可以被定义成扩展分区(Extended partition)。所谓扩展分区,就是指这个区里面又分成多个区。这种分区里面的分区,就叫做逻辑分区(logical partition)。程序先读取扩展分区的第一个扇区,叫做"扩展引导记录"(Extended boot record,缩写为EBR)。它里面也包含一张64字节的分区表,但是最多只有两项(也就是两个逻辑分区)。计算机程序接着读取第二个逻辑分区的第一个扇区,再从里面的分区表中找到第三个逻辑分区的位置,以此类推,直到某个逻辑分区的分区表只包含它自身为止(即只有一个分区项)。因此,扩展分区可以包含无数个逻辑分区。

在虚拟机中安装Ubuntu桌面,它默认就使用了一个扩展分区,在此扩展分区中,包含一个逻辑分区,此逻辑分区映射为根。

2T成了MBR硬盘的容量上限,有限制就会有突破,这就是GPT硬盘。(由于硬盘制造商采用1:1000进行单位换算,因此也有2.2TB的说法)

分区类型(文件系统类型)

如果只是用mkfs对硬盘的某个分区进行格式化(删除所有文件,重新创建文件系统),用fdisk -l看到的分区类型(文件系统类型)很可能是失真的(fdisk读取的是MBR中的分区类型字段)。此时:

GPT

GPT是GUID Partition Table的缩写,其含义为“全局唯一标识磁盘分区表”,GUID就是全局唯一标识符(Globally Unique Identifier)的简写。GUID分区表(GPT)是作为Universal Extensible Firmware Interface(UEFI)计划的一部分引入的,一个比较独立的技术部分。(有些软件使用UUID这个词,与GUID是一会事儿

参考资料:理解BIOS和UEFI

gpt

GPT用GUID来表示分区类型,也必然要突破硬盘容量2T的限制!GPT还提供两个备份区域,提升系统整体健壮性。备份区域在最后,下文的测试发现,部分备份区域紧挨着最后一块分区的末尾,并没有在最后。

Protective MBR

硬盘的第1个扇区还是MBR,不过成了protective MBR,有的地方称PMBR

在PMBR扇区中,没有引导程序,分区表内只有一个表项,这个表项描述了一个类型为0xEE的分区(0xEE就是GPT类型),分区起始地址是1号扇区,大小为四个字节所能存储的最大值。该分区的存在可以使MBR-only的计算机程序认为这个磁盘是合法的,并且已被使用,从而不再去试图对其进行分区、格式化等操作,起到保护的作用。

EFI根本不使用这个分区表。

使用fdisk对硬盘进行GPT分区时,要先用g命令创建GPT分区表,然后再一个个创建具体分区。

GPT分区

网上有些文章把GPT分区说成EFI分区,EFI部分(GPT头前8个字节的签名,EFI PART).....都是指一个东西。

其实,从上图可以看出来,整块硬盘(被使用的部分)被分成了4个部分:GPT头、分区表、GPT分区、备份区域。

GPT头

GPT头位于GPT磁盘的第2个扇区,即1号扇区(LBA1),该扇区是在创建GPT磁盘时生成的,GPT头定义分区表的起始位置、分区表的结束位置、每个分区表项的大小、分区表项的个数,以及分区表的checksum等信息。GPT头包含头和分区表的checksum,这样就可以及时发现错误。

gpt_header

LBA用8字节记录,突破了MBR用4字节记录造成的硬盘2T容量限制。

都是CRC32。

GPT分区表

GPT没有规定最大分区数量,但一般分区工具最大支持分128个区。每个分区表项大小为128字节,这样每512bytes的sector可以存放4个分区信息。每个分区表项中记录着分区的起始和结束地址、分区类型的GUID、分区名字、分区属性。

gpt_table_item

注意,扇区尺寸不能假定为512字节,也就是说,一个扇区内可能存放4个以上的分区项。也就是说,除了硬盘的前两个扇区(LBA0和LBA1)之外,GPT规范仅定义了数据结构的尺寸,而不关心使用多少个扇区进行存储。

好多GUID,硬盘分区表中有一个GUID用于表示硬盘,每个分区用预先固定下来的GUID来表达类型,再用一个GUID来作为标识。

网上找到一份GPT分区类型的说明,供参考:

相关操作系统 GUID[little endian] 含义
None 00000000-0000-0000-0000-000000000000 未使用
None 024DEE41-33E7-11D3-9D69-0008C781F39F MBR分区表
None C12A7328-F81F-11D2-BA4B-00A0C93EC93B EFI系统分区[EFI System partition (ESP)],必须是FAT32格式
None BC13C2FF-59E6-4262-A352-B275FD6F7172 扩展boot分区,必须是VFAT格式
None 21686148-6449-6E6F-744E-656564454649 BIOS引导分区,其对应的ASCII字符串是"Hah!IdontNeedEFI"。
None D3BFE2DE-3DAF-11DF-BA40-E3A556D89593 Intel Fast Flash (iFFS) partition (for Intel Rapid Start technology)
Windows E3C9E316-0B5C-4DB8-817D-F92DF00215AE 微软保留分区(MSR
Windows EBD0A0A2-B9E5-4433-87C0-68B6B72699C7 基本数据分区
Windows DE94BBA4-06D1-4D40-A16A-BFD50179D6AC Windows恢复环境
Linux 0FC63DAF-8483-4772-8E79-3D69D8477DE4 数据分区,这个GUID是由 GPT fdisk 和 GNU Parted 开发者根据Linux传统的 8300 分区代码创造。
Linux 44479540-F297-41B2-9AF7-D131D5F0458A x86根分区 (/) 这是systemd的发明,可用于无fstab时的自动挂载
Linux 4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709 x86-64根分区 (/) 这是systemd的发明,可用于无fstab时的自动挂载
Linux 69DAD710-2CE4-4E3C-B16C-21A1D49ABED3 ARM32根分区 (/) 这是systemd的发明,可用于无fstab时的自动挂载
Linux B921B045-1DF0-41C3-AF44-4C6F280D3FAE AArch64根分区 (/) 这是systemd的发明,可用于无fstab时的自动挂载
Linux 3B8F8425-20E0-4F3B-907F-1A25A76F98E8 服务器数据分区(/srv) 这是systemd的发明,可用于无fstab时的自动挂载
Linux 933AC7E1-2EB4-4F13-B844-0E14E2AEF915 HOME分区 (/home) 这是systemd的发明,可用于无fstab时的自动挂载
Linux 0657FD6D-A4AB-43C4-84E5-0933C84B4F4F 交换分区(swap) 不是systemd的发明,但同样可用于无fstab时的自动挂载
Linux A19D880F-05FC-4D3B-A006-743F0F84911E RAID分区
Linux E6D6D379-F507-44C2-A23C-238F2A3DF928 逻辑卷管理器(LVM)分区
Linux 8DA63339-0007-60C0-C436-083AC8230908 保留

关于硬盘分区的考虑

硬盘分区就像给一间空荡的房子划分出卧室,厨房,客厅等相互隔离的空间一样。主要是为了方便用户的使用。一方面,通过合理的硬盘分区,有效保护系统盘空间,确实能够提高系统运行速度,再者,硬盘分区也可以有效地对数据进行保护。你当然可以不分区,只不过,当你面对越来越多的子目录,或者是越来越慢的Windows,不得不费功夫去管理你的文件,或者重装Windows的时候,恐怕会悔不当初。 “不要把所有的鸡蛋放在同一个篮子里”,这句至理名言在经济学以外的其他领域也同样是句警世恒言。

一般情况下,系统分区和数据分区相互独立,在重装系统的时候会比较安全和方便。

Linux查看分区的方法

$ sudo fdisk -l /dev/xxx
$ df -h

使用 df -h 命令,可以非常方便的查看每个分区的mount point。

fdisk工具

fdisk工具的使用体验很不错,交互式的,只有最后输入w,才会将所有修改写入硬盘。刚进入命令交互式环境,建议用p查看一下命令行键入的硬盘是否正确,如果不正确,后果可能很严重。fdisk默认创建MBR分区,如果要创建GPT分区,要先使用g命令。不管用fdisk做那种分区,默认都会建议用户不要使用硬盘的前1MB区域,即分区起始扇区LBA为2048。

分区不是格式化,格式化是创建文件系统,要使用mkfs命令。

lsblk命令

使用fdisk必须sudo,而且这个命令有一定危险性。如果仅仅是查看系统中的block device,建议使用lsblk命令。lsblk命令也能够将分区信息显示出来,查看文件系统类型,添加-f参数。

xxd命令

xxd命令用来查看二进制的输入,可以用xxd命令直接读取设备文件并显示。

用fdisk创建一块GPT分区,用xxd查看各项数据

用一块U盘做测试。

$ sudo fdisk /dev/sdb

Welcome to fdisk (util-linux 2.38).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.


Command (m for help): p
Disk /dev/sdb: 28.65 GiB, 30765219840 bytes, 60088320 sectors
Disk model:  SanDisk 3.2Gen1
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xff4d60d9

Device     Boot Start      End  Sectors  Size Id Type
/dev/sdb1        2048 60088319 60086272 28.7G 83 Linux

Command (m for help): g
Created a new GPT disklabel (GUID: 4F7A3610-F722-1248-B05C-5506A7048CBB).
The device contains 'dos' signature and it will be removed by a write command. See fdisk(8) man page and --wipe option for more details.

Command (m for help): n
Partition number (1-128, default 1): 
First sector (2048-60088286, default 2048): 
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-60088286, default 60086271): 

Created a new partition 1 of type 'Linux filesystem' and of size 28.7 GiB.
Partition #1 contains a vfat signature.

Do you want to remove the signature? [Y]es/[N]o: y

The signature will be removed by a write command.

Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.

$ lsblk /dev/sdb
NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
sdb      8:16   1 28.7G  0 disk 
└─sdb1   8:17   1 28.7G  0 part 

$ sudo fdisk /dev/sdb -l
[sudo] password for xinlin: 
Disk /dev/sdb: 28.65 GiB, 30765219840 bytes, 60088320 sectors
Disk model:  SanDisk 3.2Gen1
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 4F7A3610-F722-1248-B05C-5506A7048CBB

Device     Start      End  Sectors  Size Type
/dev/sdb1   2048 60086271 60084224 28.7G Linux filesystem

用xxd命令查看LBA0:

$ sudo xxd -a -l512 /dev/sdb
[sudo] password for xinlin: 
00000000: 0000 0000 0000 0000 0000 0000 0000 0000  ................
*
000001c0: 0200 eeff ffff 0100 0000 ffdf 9403 0000  ................
000001d0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000001e0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000001f0: 0000 0000 0000 0000 0000 0000 0000 55aa  ..............U.

PMBR中存在一个分区表项,该表项地址从446开始,大小为16字节:

$ sudo xxd -s446 -l16 /dev/sdb
000001be: 0000 0200 eeff ffff 0100 0000 ffdf 9403  ................

非激活,类型0xEE,按little endian解读第1个扇区的逻辑地址和扇区总数:

$ python -c 'print(0x00000001)'
1
$ python -c 'print(0x0394dfff)'
60088319

前面用fdisk看到整块U盘的扇区总数是60088320,上面的数据很靠谱。

下面查看GPT头(LBA1)信息:

$ sudo xxd -a -s512 -l512 -u /dev/sdb 
00000200: 4546 4920 5041 5254 0000 0100 5C00 0000  EFI PART....\...
00000210: C148 E123 0000 0000 0100 0000 0000 0000  .H.#............
00000220: FFDF 9403 0000 0000 0008 0000 0000 0000  ................
00000230: DEDF 9403 0000 0000 1036 7A4F 22F7 4812  .........6zO".H.
00000240: B05C 5506 A704 8CBB 0200 0000 0000 0000  .\U.............
00000250: 8000 0000 8000 0000 6C7E CC5C 0000 0000  ........l~.\....
00000260: 0000 0000 0000 0000 0000 0000 0000 0000  ................
*
000003f0: 0000 0000 0000 0000 0000 0000 0000 0000  ................

一眼就能看到,最开始的8字节EFI PART签名。中间的16字节Disk identifier: 4F7A3610-F722-1248-B05C-5506A7048CBB,存储在LBA1偏移0x38开始的位值:

$ sudo xxd -s$((512+0x38)) -l16 -u /dev/sdb
00000238: 1036 7A4F 22F7 4812 B05C 5506 A704 8CBB  .6zO".H..\U.....

版本号,1.0,值为0000 0100

GPT头大小,92字节,5C00 0000

然后是CRC校验和(计算时置0),C148 E123

接着是保留的4字节,全0

下面是GPT头所在扇区,8字节,0100 0000 0000 0000,这就是LBA1

然后是GPT头的备份扇区,8字节,FFDF 9403 0000 0000,值为:

$ python -c 'print(0x000000000394dfff)'
60088319

这刚好是U盘的最后一个扇区,其内容如下:

$ sudo xxd -a -s$((512*60088319)) -l512 -u /dev/sdb
729bffe00: 4546 4920 5041 5254 0000 0100 5C00 0000  EFI PART....\...
729bffe10: 61E2 F253 0000 0000 FFDF 9403 0000 0000  a..S............
729bffe20: 0100 0000 0000 0000 0008 0000 0000 0000  ................
729bffe30: DEDF 9403 0000 0000 1036 7A4F 22F7 4812  .........6zO".H.
729bffe40: B05C 5506 A704 8CBB DFDF 9403 0000 0000  .\U.............
729bffe50: 8000 0000 8000 0000 6C7E CC5C 0000 0000  ........l~.\....
729bffe60: 0000 0000 0000 0000 0000 0000 0000 0000  ................
*
729bffff0: 0000 0000 0000 0000 0000 0000 0000 0000  ................

仔细观察就能发现,这块备份GPT头的内容,所在LBA,和备份LBA,与LBA1中的内容刚好交换了一下,这就是相互备份呀。

再然后,就是GPT分区起始扇区,0008 0000 0000 0000,和终止扇区,DEDF 9403 0000 0000,分别是:

$ python -c 'print(0x0000000000000800)'
2048
$ python -c 'print(0x000000000394dfde)'
60088286

前面用fdisk分区时,使用的是默认提供的值60086271,我们来做一组简单的小学数学题:

>>> 60088286 - 2048 + 1
60086239  # 整个GPT分区的大小
>>> 60088286 - 60086271 + 1
2016      # GPT分区剩余未分配区域
>>> 60088319 - 60088286 + 1
34        # GPT分区末端到U盘末端的扇区数
>>> (60086271 - 2048 + 1) / 8
7510528.0 # GPT分区大小是4K倍数,8*512=4096

总有一些扇区隐藏在分区之外...

GPT分区表开始的位值,0200 0000 0000 0000,LBA2。

分区表的总项数,以及每个分区表所占用字节数,8000 0000 8000 0000,都是128。

前面的步骤说明,我只分了一个区,下面是LBA2的前128字节的内容:

$ sudo xxd -a -s$((512*2)) -l128 -u /dev/sdb
[sudo] password for xinlin: 
00000400: AF3D C60F 8384 7247 8E79 3D69 D847 7DE4  .=....rG.y=i.G}.
00000410: E23D 8E8F AAF7 4906 8DAD B4C4 E804 6A54  .=....I.......jT
00000420: 0008 0000 0000 0000 FFD7 9403 0000 0000  ................
00000430: 0000 0000 0000 0000 0000 0000 0000 0000  ................
*
00000470: 0000 0000 0000 0000 0000 0000 0000 0000  ................

前面32字节,是2个GUID

起始扇区,0008 0000 0000 0000,即2048

结束扇区,FFD7 9403 0000 0000,即60086271

属性和人类可读区域全0。

所有信息和数据都能够对上!

最后,备份扇区记录了一个GPT分区表的备份扇区,这个扇区的位置,在GPT分区后面第1个扇区:

$ sudo xxd -a -s$((60088287*512)) -l128 -u /dev/sdb
[sudo] password for xinlin: 
729bfbe00: AF3D C60F 8384 7247 8E79 3D69 D847 7DE4  .=....rG.y=i.G}.
729bfbe10: E23D 8E8F AAF7 4906 8DAD B4C4 E804 6A54  .=....I.......jT
729bfbe20: 0008 0000 0000 0000 FFD7 9403 0000 0000  ................
729bfbe30: 0000 0000 0000 0000 0000 0000 0000 0000  ................
*
729bfbe70: 0000 0000 0000 0000 0000 0000 0000 0000  ................

与LBA2前128字节完全一样!

ESP分区

UEFI规定,ESP分区的GUID:C12A7328-F81F-11D2-BA4B-00A0C93EC93B,FAT32文件系统。UEFI系统在ESP分区中寻找bootloader。

下面的数据,来自我的移动fedora系统:

Disk /dev/sdb: 335.35 GiB, 360080695296 bytes, 703282608 sectors
Disk model:  SL500 360GB
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 33553920 bytes
Disklabel type: gpt
Disk identifier: B11F9587-9643-4155-B0B4-9815BABC9029

Device       Start       End   Sectors   Size Type
/dev/sdb1    65535   1245164   1179630   576M EFI System
/dev/sdb2  1245165   3407819   2162655     1G Linux filesystem
/dev/sdb3  3407820 703190549 699782730 333.7G Linux filesystem

首先查看GTP分区表所在扇区:

$ sudo xxd -a -s512 -l512 /dev/sdb
00000200: 4546 4920 5041 5254 0000 0100 5c00 0000  EFI PART....\...
00000210: cf8f bd35 0000 0000 0100 0000 0000 0000  ...5............
00000220: af3d eb29 0000 0000 2200 0000 0000 0000  .=.)....".......
00000230: 8e3d eb29 0000 0000 8795 1fb1 4396 5541  .=.)........C.UA
00000240: b0b4 9815 babc 9029 0200 0000 0000 0000  .......)........
00000250: 8000 0000 8000 0000 787a 9ce6 0000 0000  ........xz......
00000260: 0000 0000 0000 0000 0000 0000 0000 0000  ................
*
000003f0: 0000 0000 0000 0000 0000 0000 0000 0000  ................

GPT分区表开始的位值,0200 0000 0000 0000,LBA2。我们查看第1项的128字节信息:

$ sudo xxd -a -u -s1024 -l128 /dev/sdb
00000400: 2873 2AC1 1FF8 D211 BA4B 00A0 C93E C93B  (s*......K...>.;
00000410: BCCE 2CB2 9D73 E045 8C6A 2151 78F3 F00F  ..,..s.E.j!Qx...
00000420: FFFF 0000 0000 0000 ECFF 1200 0000 0000  ................
00000430: 0000 0000 0000 0000 4500 4600 4900 2000  ........E.F.I. .
00000440: 5300 7900 7300 7400 6500 6D00 2000 5000  S.y.s.t.e.m. .P.
00000450: 6100 7200 7400 6900 7400 6900 6F00 6E00  a.r.t.i.t.i.o.n.
00000460: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000470: 0000 0000 0000 0000 0000 0000 0000 0000  ................

前16字节,就是ESP的GUID,起始扇区为LBA 65535(FFFF),右侧清晰可见ASCII字符串,EFI...

这就是/dev/sdb1分区,FAT32,它被挂载在/boot/efi路径:

$ df -T
Filesystem     Type     1K-blocks     Used Available Use% Mounted on
devtmpfs       devtmpfs      4096        0      4096   0% /dev
tmpfs          tmpfs      3964808        0   3964808   0% /dev/shm
tmpfs          tmpfs      1585924     1940   1583984   1% /run
/dev/sdb3      btrfs    349891364 63776908 285565204  19% /
tmpfs          tmpfs      3964812       16   3964796   1% /tmp
/dev/sdb3      btrfs    349891364 63776908 285565204  19% /home
/dev/sdb2      ext4       1028904   301300    657156  32% /boot
/dev/sdb1      vfat        588636    17780    570856   4% /boot/efi
tmpfs          tmpfs       792960      220    792740   1% /run/user/1000

这个分区下,包含/efi/boot路径:

$ sudo ls -l /boot/efi/efi
total 8
drwx------. 2 root root 4096 Sep 12  2022 BOOT
drwx------. 2 root root 4096 Apr 29 11:46 fedora

没有ESP分区也可以

我在介绍将Grub装入U盘的笔记中,实践了这种情况:U盘甚至都可以不分区(一整块U盘就是一个分区),只需要FAT32文件系统,有/efi/boot/bootx64.efi文件即可。这个efi程序就是grub。这可能是UEFI的fallback机制,或者是非标实现。

本文链接:https://cs.pynote.net/hd/hdisk/202110165/

-- EOF --

-- MORE --