磁盘分区原理:从MBR到GPT

做者简介 必圣,一个看起来不咋正经但很有技术追求的小伙儿,现任饿了么高级Python工程师,以前有一段和文件系统“搞暧昧”的经历。听说他C++写的挺好,有多好不得而知。html

引言

MBR和GPT都是硬盘分区表,在硬盘分区表以后才是咱们平常使用的文件系统好比NTFS,FAT32,EXT等。虽然如今MBR已是一个不经常使用的硬盘分区表了,可是GPT为了兼容老的硬盘分区表因此在GPT的结构头部加入了MBR。在下面篇幅中,笔者先简单的介绍一下MBR,接着再介绍GPT结构。c++

本文中贴出一部分结构体代码,这些结构体都是笔者从010editor的模板中提取出来修改获得的。这些结构体方便了编程使用,只要把数据buffer指针强制转换成结构体指针,就能用这个结构体指针来提取数据,免除了使用代码位移来读取数据的麻烦。(尽可能采用1字节结构体的对齐方式,避免没必要要的麻烦)编程

1.MBR

1.1 MBR的组成

硬盘首一个扇区存放MBR表,由4个部分组成,分别是主引导记录(Master Boot Record)、数据区、分区表(包含4个分区项)以及结束标志。api

在下面表格中展现了这4个部分的偏移位置和简介:数据结构

MasterBoot

  1. Master Boot Record 是硬盘分区表的引导程序和数据区,一共占用446个字节,BIOS 读取并执行这段代码,若是被破坏系统就没法读取到硬盘上的数据致使,系统没法启动。
  2. 分区项在以后会详细介绍
  3. 结束标识是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;
复制代码

硬盘数据截图工具

MasterBootRecord

1.2 MBR中的分区项结构

下表是MBR中一个独立分区项16个字节的解释编码

存储字节位 内容及含义
第1字节 引导标志。若值为80H表示活动分区,若值为00H表示非活动分区。第二、三、4字节 本分区的起始磁头号、扇区号、柱面号。其中:磁头号——第2字节;扇区号——第3字节的低6位;柱面号——为第3字节高2位+第4字节8位。
第5字节 分区类型符。00H——该分区未用(即没有指定);06H——FAT16基本分区;0BH——FAT32基本分区;05H——扩展分区;07H——NTFS分区;0FH——(LBA模式)扩展分区(83H为Linux分区等)。
第六、七、8字节 本分区的结束磁头号、扇区号、柱面号。其中:磁头号——第6字节;扇区号——第7字节的低6位;柱面号——第7字节的高2位+第8字节。
第九、十、十一、12字节 本分区第一个扇区。
第1三、1四、1五、16字节 本分区的总扇区数。

下面的结构体中的阿拉伯数字是指bit,unsigned short占用2个字节,一共是16个bites5

  1. unsigned short begsect : 6; 表示前6个bitspa

  2. 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;
复制代码

硬盘数据截图

FdiskPartitioinPartitions

1.3 MBR的限制

MBR存储数据在4个分区上,这些分区称为主分区。分区采用“柱面/磁头/扇区”标记法,即CHS标记法。前面的结构体中beghead、begsect、begcyl分别表示分区起始的磁头、扇区和柱面,endhead、endsect、endcyl分别表示分区结束的磁头、扇区和柱面。每一个扇区的大小是512B,总的扇区数=2^{24}磁头数\times扇区数\times柱面数,即2^{8}\times2^{6}\times2^{10}),所以它最多只能描述8G(8G=2^{24}\times512B\div1024\div1024\div1024)的磁盘区域。

校稿人注

有使用MBR分区经验的同窗可能会以为奇怪,怎么会只能表示8G? 实际装系统分区的时候,明明轻轻松松分出了一个几百G的分区啊。这是由于,现代的BIOS对超过8G的磁盘使用LBA模式,对于超出的部分,CHS值一般设为0xFEFFFF,并加以忽略,直接使用Offset 0x08-0x0c的4字节相对值,再进行内部转换。 可是LBA用relsect(32位)来描述起始扇区号,relsect + numsect表示结束扇区号,因为都是用32位数寻址致使整个硬盘大小不能超过2TB个数据(2T=2^{32}\times512B\div1024\div1024\div1024\div1024)。

除了这个2TB问题以外,MBR 还有其余困难。主要困难是4个主分区的限制。要克服这个限制,可能的方法是将一个主分区放到一边,做为一个占位符(称为扩展分区),用于容纳任意数量的附加分区(称为逻辑分区)。

MBR 还有数据完整性问题。它是一个单一数据结构,容易受到误操做和磁盘故障的损坏。另外,因为逻辑分区以一种连接表结构定义,若是一个逻辑分区损坏,就会阻止对剩余的逻辑分区的访问。


2.GPT

2.1 GPT的组成

让咱们先贴上GPT的总体结构图,让你们有个总体的印象。

GPT结构图

GPT结构

GPT结构数据

GPT结构数据

2.2 保护MBR

在GPT头部有个保护MBR(参考GPT结构图中的LBA 0:Protective MBR),保护MBR数据以下表。

保护MBR

保护MBR只有第一个分区项有数值,其余分区项为空。

  1. 第一个分区项的PartitionType(分区类型)也就是前文中的systid(分区类型)为0xEE

  2. StartSectors(起始扇区)这值为2就是表示LBA1,也就是GPT结构头所在的位置。在小于2T的硬盘上SectorsInPartition(总扇区数量)就是前文中numsect(总扇区数量),值是整个磁盘大小,因为只有4个字节只能表示2TB,因此大于2TB该值会被固定为FFFFFFFFh

保护MBR第一个分区项把整个GPT当作分区项数据记录下来,它还有的做用是阻止不能识别GPT分区的磁盘工具试图对其进行格式化等操做,因此该扇区被称为“保护MBR”。实上EFI根本不使用这个分区表。

2.3 EFI部分

EFI部分又能够分为4个区域:EFI信息区(GPT头)、分区表、GPT分区、备份区。

  1. EFI信息区(GPT头):起始于磁盘的LBA1,一般也只占用这个单一扇区。其做用是定义分区表的位置和大小。GPT头还包含头和分区表的校验和,这样就能够及时发现错误

  2. 分区表:分区表区域包含分区表项。这个区域由GPT头定义,通常占用磁盘LBA2~LBA33扇区。分区表中的每一个分区项由起始地址、结束地址、类型值、名字、属性标志、GUID值组成。分区表创建后,128位的GUID对系统来讲是惟一的

  3. GPT分区:最大的区域,由分配给分区的扇区组成。这个区域的起始和结束地址由GPT头定义

  4. 备份区:备份区域位于磁盘的尾部,包含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头部数据

Gptheader

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 进行沟通
相关文章
相关标签/搜索