作者简介 必圣,一个看起来不咋正经但颇有技术追求的小伙儿,现任饿了么高级Python工程师,之前有一段和文件系统“搞暧昧”的经历。据说他C++写的挺好,有多好不得而知。
引言
MBR和GPT都是硬盘分区表,在硬盘分区表之后才是我们日常使用的文件系统比如NTFS,FAT32,EXT等。虽然现在MBR已经是一个不常用的硬盘分区表了,但是GPT为了兼容老的硬盘分区表所以在GPT的结构头部加入了MBR。在下面篇幅中,笔者先简单的介绍一下MBR,接着再介绍GPT结构。
本文中贴出一部分结构体代码,这些结构体都是笔者从010editor的模板中提取出来修改得到的。这些结构体方便了编程使用,只要把数据buffer指针强制转换成结构体指针,就能用这个结构体指针来提取数据,免除了使用代码位移来读取数据的麻烦。(尽量采用1字节结构体的对齐方式,避免不必要的麻烦)
1.MBR
1.1 MBR的组成
硬盘首一个扇区存放MBR表,由4个部分组成,分别是主引导记录(Master Boot Record)、数据区、分区表(包含4个分区项)以及结束标志。
在下面表格中展示了这4个部分的偏移位置和简介:
- Master Boot Record 是硬盘分区表的引导程序和数据区,一共占用446个字节,BIOS 读取并执行这段代码,如果被破坏系统就无法读取到硬盘上的数据导致,系统无法启动。
- 分区项在之后会详细介绍
- 结束标识是MBR的结束位,如果错误就会读取出错。
下面就是c++中的结构体代码,bootinst就是主引导程序和数据区、partitions是4个分区项、mbr_table这个类型会在下文详细介绍,此类型占据16个字节、signature就是结束表示了。
typedef struct {
char bootinst[446]; /* space to hold actual boot code */
mbr_table partitions[4];
unsigned short signature;/* set to 0xAA55 to indicate PC MBR format */
} mbr_head;
硬盘数据截图
1.2 MBR中的分区项结构
下表是MBR中一个独立分区项16个字节的解释
存储字节位 | 内容及含义 |
---|---|
第1字节 | 引导标志。若值为80H表示活动分区,若值为00H表示非活动分区。第2、3、4字节 本分区的起始磁头号、扇区号、柱面号。其中:磁头号——第2字节;扇区号——第3字节的低6位;柱面号——为第3字节高2位+第4字节8位。 |
第5字节 | 分区类型符。00H——该分区未用(即没有指定);06H——FAT16基本分区;0BH——FAT32基本分区;05H——扩展分区;07H——NTFS分区;0FH——(LBA模式)扩展分区(83H为Linux分区等)。 |
第6、7、8字节 | 本分区的结束磁头号、扇区号、柱面号。其中:磁头号——第6字节;扇区号——第7字节的低6位;柱面号——第7字节的高2位+第8字节。 |
第9、10、11、12字节 | 本分区第一个扇区。 |
第13、14、15、16字节 | 本分区的总扇区数。 |
下面的结构体中的阿拉伯数字是指bit,unsigned short占用2个字节,一共是16个bit
-
unsigned short begsect : 6; 表示前6个bit
-
unsigned short begcyl : 10; 表示之后的10个bit
校稿人注
这里涉及到C/C++的位域概念,在结构体里通过 “type [name] : bitsize” 的形式(name是optional的,没有name的位域称作无名位域),告诉编译器仅使用该成员的其中bitsize个比特位存储数据。当结构体中相邻两个成员都是位域、type类型相同且类型长度(bit位数)大于两个位域bit位数的和,绝大多数编译器会进行压缩存储。 例如下面结构体中的begsect和begcyl,都是位域,type都是unsigned short,且两个位域的bit位数相加小于unsigned short类型的bit长度,所以这两个位域会压缩在一个unsigned short类型的存储空间内存储,他们一共占用2个字节。
typedef struct {
unsigned char bootid; /* bootable? 0=no, 128=yes */
unsigned char beghead ; /* beginning head number */
unsigned short begsect : 6; /* beginning sector number */
unsigned short begcyl : 10; /* 10 bit nmbr */
unsigned char systid; /* Operating System type indicator code */
unsigned char endhead ; /* ending head number */
unsigned short endsect : 6; /* ending sector number */
unsigned short endcyl : 10; /* also a 10 bit nmbr */
unsigned int relsect; /* first sector relative to start of disk */
unsigned int numsect; /* number of sectors in partition */
} mbr_table;
硬盘数据截图
1.3 MBR的限制
MBR存储数据在4个分区上,这些分区称为主分区。分区采用“柱面/磁头/扇区”标记法,即CHS标记法。前面的结构体中beghead、begsect、begcyl分别表示分区起始的磁头、扇区和柱面,endhead、endsect、endcyl分别表示分区结束的磁头、扇区和柱面。每个扇区的大小是512B,总的扇区数=(,即),因此它最多只能描述8G()的磁盘区域。
校稿人注
有使用MBR分区经验的同学可能会觉得奇怪,怎么会只能表示8G? 实际装系统分区的时候,明明轻轻松松分出了一个几百G的分区啊。这是因为,现代的BIOS对超过8G的磁盘使用LBA模式,对于超出的部分,CHS值通常设为0xFEFFFF,并加以忽略,直接使用Offset 0x08-0x0c的4字节相对值,再进行内部转换。 但是LBA用relsect(32位)来描述起始扇区号,relsect + numsect表示结束扇区号,由于都是用32位数寻址导致整个硬盘大小不能超过2TB个数据()。
除了这个2TB问题之外,MBR 还有其他困难。主要困难是4个主分区的限制。要克服这个限制,可能的方法是将一个主分区放到一边,作为一个占位符(称为扩展分区),用于容纳任意数量的附加分区(称为逻辑分区)。
MBR 还有数据完整性问题。它是一个单一数据结构,容易受到误操作和磁盘故障的损坏。另外,由于逻辑分区以一种链接表结构定义,如果一个逻辑分区损坏,就会阻止对剩余的逻辑分区的访问。
2.GPT
2.1 GPT的组成
让我们先贴上GPT的整体结构图,让大家有个整体的印象。
GPT结构图
GPT结构数据
2.2 保护MBR
在GPT头部有个保护MBR(参考GPT结构图中的LBA 0:Protective MBR),保护MBR数据如下表。
保护MBR只有第一个分区项有数值,其他分区项为空。
-
第一个分区项的PartitionType(分区类型)也就是前文中的systid(分区类型)为0xEE
-
StartSectors(起始扇区)这值为2就是表示LBA1,也就是GPT结构头所在的位置。在小于2T的硬盘上SectorsInPartition(总扇区数量)就是前文中numsect(总扇区数量),值是整个磁盘大小,由于只有4个字节只能表示2TB,所以大于2TB该值会被固定为FFFFFFFFh
保护MBR第一个分区项把整个GPT当做分区项数据记录下来,它还有的作用是阻止不能识别GPT分区的磁盘工具试图对其进行格式化等操作,所以该扇区被称为“保护MBR”。实上EFI根本不使用这个分区表。
2.3 EFI部分
EFI部分又可以分为4个区域:EFI信息区(GPT头)、分区表、GPT分区、备份区。
-
EFI信息区(GPT头):起始于磁盘的LBA1,通常也只占用这个单一扇区。其作用是定义分区表的位置和大小。GPT头还包含头和分区表的校验和,这样就可以及时发现错误
-
分区表:分区表区域包含分区表项。这个区域由GPT头定义,一般占用磁盘LBA2~LBA33扇区。分区表中的每个分区项由起始地址、结束地址、类型值、名字、属性标志、GUID值组成。分区表建立后,128位的GUID对系统来说是唯一的
-
GPT分区:最大的区域,由分配给分区的扇区组成。这个区域的起始和结束地址由GPT头定义
-
备份区:备份区域位于磁盘的尾部,包含GPT头和分区表的备份。它占用硬盘数据的末尾的33个扇区。其中最后一个扇区用来备份LBA1扇区的EFI信息区(GPT头),其余的32个扇区用来备份LBA2~LBA33扇区的分区表
以下对EFI信息区(GPT头)、分区表和备份区分别进行探讨。
2.3.1 EFI信息区数据结构
相对字节偏移量(十六进制) | 字节数 | 说明[整数皆以little endian方式表示] |
---|---|---|
00~07 | 8 | GPT头签名“45 46 49 20 50 41 52 54”(ASCII码为“EFI PART”) |
08~0B | 4 | 版本号,目前是1.0版,其值是“00 00 01 00” |
0C~0F | 4 | GPT头的大小(字节数),通常为“5C 00 00 00”(0x5C),也就是92字节。 |
10~13 | 4 | GPT头CRC校验和(计算时把这个字段本身看做零值) |
14~17 | 4 | 保留,必须为“00 00 00 00” |
18~1F | 8 | EFI信息区(GPT头)的起始扇区号,通常为“01 00 00 00 00 00 00 00”,也就是LBA1。 |
20~27 | 8 | EFI信息区(GPT头)备份位置的扇区号,也就是EFI区域结束扇区号。通常是整个磁盘最末一个扇区。 |
28~2F | 8 | GPT分区区域的起始扇区号,通常为“22 00 00 00 00 00 00 00”(0x22),也即是LBA34。 |
30~37 | 8 | GPT分区区域的结束扇区号,通常是倒数第34扇区。 |
38~47 | 16 | 磁盘GUID(全局唯一标识符,与UUID是同义词) |
48~4F | 8 | 分区表起始扇区号,通常为“02 00 00 00 00 00 00 00”(0x02),也就是LBA2。 |
50~53 | 4 | 分区表总项数,通常限定为“80 00 00 00”(0x80),也就是128个。 |
54~57 | 4 | 每个分区表项占用字节数,通常限定为“80 00 00 00”(0x80),也就是128字节。 |
58~5B | 4 | 分区表CRC校验和 |
5C~ | 保留,通常是全零填充 |
GPT分区项数量可以根据GPT中的分区表属性扩展。也就是GPT的分区表数量是可以配的,但是一般大小都是128个分区项。而MBR只有4个分区项。
typedef struct {
BYTE SIGNATURE[8];
DWORD Revision;
DWORD Headersize;
DWORD CRC32OfHeader;
DWORD Reserved;
UINT64 CurrentLBA;
UINT64 BackupLBA; //location of the other head copy
UINT64 FirstUsableLBA; //primary partition table last LBA+1
UINT64 LastUsableLBA; //secondary parition table first LBA-1
BYTE DiskGUID[16];
UINT64 PartitionEntries;
DWORD NumOfPartitions;
DWORD SizeOfPartitionEntry;
DWORD CRC32ofPartitionArray;
BYTE reserved[420];
} gpt_head_table;
GPT头部数据
2.3.2 分区表
分区项结构
相对字节偏移量(十六进制) | 字节数 | 说明[整数皆以little endian方式表示] |
---|---|---|
00~0F | 16 | 用GUID表示的分区类型 |
10~1F | 16 | 用GUID表示的分区唯一标示符 |
20~27 | 8 | 该分区的起始扇区,用LBA值表示。 |
28~2F | 8 | 该分区的结束扇区(包含),用LBA值表示,通常是奇数。 |
30~37 | 8 | 该分区的属性标志 |
38~7F | 72 | UTF-16LE编码的人类可读的分区名称,最大32个字符。 |
分区项中的PartitionStartLBA(起始扇区)和PartitionEndLBA(该分区的结束扇区)都是64位,也就从MBR24位增加到了64位,而且添加CRC的校验。
typedef struct {
BYTE PartitionTypeGUID[16];
BYTE PartitionGUID[16];
UINT64 PartitionStartLBA;
UINT64 PartitionEndLBA;
UINT64 PartitionProperty;
wchar_t PartitionName[36]; //Unicode
} gpt_paptition_table;
下面表格是PartitionTypeGUID(分区类型GUID)值。
相关操作系统 | 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)] |
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 | 微软保留分区 |
Windows | EBD0A0A2-B9E5-4433-87C0-68B6B72699C7 | 基本数据分区 |
Windows | DE94BBA4-06D1-4D40-A16A-BFD50179D6AC | Windows恢复环境 |
Linux | 0FC63DAF-8483-4772-8E79-3D69D8477DE4 | 数据分区。Linux曾经使用和Windows基本数据分区相同的GUID。这个新的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 | 3B8F8425-20E0-4F3B-907F-1A25A76F98E8 | Server Data (/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 | 保留 |
Microsoft还进一步对分区的属性进行了细分:低位4字节表示与分区类型无关的属性,高位4字节表示与分区类型有关的属性。Microsoft目前使用了下列属性:
Bit | 解释 |
---|---|
0 | 系统分区(磁盘分区工具必须将此分区保持原样,不得做任何修改) |
1 | EFI隐藏分区(EFI不可见分区) |
2 | 传统的BIOS的可引导分区标志 |
60 | 只读 |
62 | 隐藏 |
63 | 不自动挂载,也就是不自动分配盘符 |
分区项数据
2.3.3 备份分区表,备份GPT头
备份分区表和分区头在硬盘数据的末尾,里面的数据和结构跟分区表和分区头内一样。是头部的一个备份。
2.4 GPT优势
由于分区数量的增多和分区表中起始扇区和结束扇区存储位数的增加,GPT它消除了 2TB 这个障碍。GPT没像MBR 单一数据结构,有备份分区表和CRC,可以有效防止结构损坏。
工具
本文中使用到的工具: 010Editor 十六进制编辑器
阅读博客还不过瘾?
欢迎大家扫二维码加入交流群,讨论和博客有关的技术问题,还可以和博主有更多互动
博客转载、线下活动及合作等问题请邮件至 shadowfly_zyl@hotmail.com 进行沟通