对于硬盘来说,它里面就是一堆0和1的数据,至于它们表示的是什么意思硬盘并不关心,这需要操作系统获取这些数据后根据它们所在的分区的文件系统等信息来对数据进行解读,那我们怎样才能知道这些数据所在的分区是什么文件系统呢?这就需要在硬盘中要有一块区域专门存放分区和文件系统等信息,并且它要与文件系统无关。
程序(一般是操作系统或者BIOS)访问硬盘时会先找到这块区域,并读取相关信息,根据这些信息进行下一步操作。这块区域被称为硬盘的分区结构。
MBR和GPT是两种不同的硬盘的分区结构,它们记录了硬盘分成了几个分区,每个分区的起始位置和大小,以及从每个分区内采用的是何种文件系统等。开机启动时BIOS(或UEFI)就是通过读取MBR或者GPT来找到操作系统的引导代码的位置,并将控制权交给操作系统的。一旦该部分损坏,整个硬盘就相当于报废了,无法再使用。
MBR:
全称 master boot recorder,主引导记录;位置固定于硬盘的第一个扇区,第0扇区(0磁道0柱面1扇区)。大小固定为512字节;主要分为三个部分:引导程序,分区表和扇区有效标记位。对应的结构体如下:
typedef struct _MASTER_BOOT_RECORD
{
char boot_code[446];
DPT dpt[4];
char boot_sign[2]; //0xaa55
}MBR;
一、引导程序(boot_code)
在MBR的0-445字节,共446个字节。里面记录了引导程序,这部分的内容主要与硬件相关,没研究过。
二、分区表(DPT)
DPT紧跟在引导程序之后,从446字节开始,以16个字节为单位,记录各分区的信息,最多能记录四个分区,一共64个字节。对应的结构体如下:
typedef struct _SECTOR_AND_CYLINDER
{
#ifdef __LITTLE_ENDIAN__
ushort sylinder :10; //开始柱面,最大值为1023
ushort sector :6; //开始扇区,用的是0-5位
#endif
}SEC_AND_CYL;
typedef struct DISK_PARTITION_TABLE
{
uchar boot_indicator; //引导指示符,0x80:活动分区;0x00:非活动分区
uchar start_head; //开始磁头
SEC_AND_CYL start_offset; //开始扇区和开始柱面
uchar system_id; //系统ID fat32:0x0C;ntfs:0x07
uchar end_head; //结束磁头
SEC_AND_CYL end_offset; //结束扇区和结束柱面
uint relative_sectors; //相对扇区数,从磁盘的开始到该分区的开始的位移量,以扇区来计算
uint total_sectors; //总扇区数,该分区中的扇区总数
}DPT;
DPT中每个变量对应的含义见变量后面的注释。但有几点需要注意一下的:
1.只能有一个分区被设为活动分区,BIOS会从这个分区中载入操作系统,并将控制权交给操作系统;在Windows中就是我们的C盘。
2.DPT中的system_id指明了各个分区的文件系统类型;有资料说Windows下这个值必须要与实际文件系统格式对应,否则操作系统将无法识别;而linux允许这个值与真实文件系统不一致,linux会自动按照真实的文件系统对该分区进行处理的。这个说法还没验证过,不知道真假。下面是一些常用文件系统对应的系统ID:
enum
{
UNVALID = 0x00, //无效值,windows下不允许使用
FAT12 = 0x01,
FAT16 = 0x04, //小于32M的FAT16
FAT16 = 0x06, //大于等于32M的FAT16
WIN_FAT16 = 0x0E, //不知道与0x06有什么区别
FAT32 =0x0B,0x0C, //资料上记录的是win95的FAT32,但没找到其他的fat32啊
HIDDEN_FAT32 = 0x1B,0x1C, //不清楚什么意思
NTFS = 0x07,
NTFS_VOLUME_SET = 0x86, 0x87, //不清楚什么意思
WIN_EXTENDED = 0x05,0x0F, //扩展分区,没猜错的话应该就是电脑上显示的逻辑分区和扩展分区,貌似0x0F是大于8G的,0x05不清楚
LINUX = 0x83, //ext2/ext3都是这个值,其他linux下的文件系统不清楚是否也是这个值
LINUX_SWAP = 0x82, //linux的swap分区
LINUX_EXTENDED = 0x85, //linux下的扩展分区?不清楚什么意思
EFI GPT = 0xEE, //硬盘使用GPT引导方式时为了防止不支持GPT的设备对硬盘误操作赋的一个非法值
}SYSTEM_TYPE;
3.DPT中指的扇区都是物理扇区,这点需要注意下。
4.现在的硬盘都是用的LBA寻址方式,因此像磁头、柱面等数据就是非必要的了。有看到资料说这些值可以乱填,系统不会去校验和使用它们的,不知道是不是真的。
三、扇区有效标记位
位于MBR的最后,占2个字节(510-512);固定为0xaa55(小端模式下)。如果系统没检测到这个值,则会认为该扇区的数据无效。
四、补充:扩展MBR(EBR)
MBR中的DPT最多只能记录四个分区的信息,当需要划分出四个以上的分区时就必须要用到EBR了。介绍EBR前需要厘清三种分区的概念:主分区、扩展分区和逻辑分区。
主分区:分区的信息全都记录的DPT中的分区称为主分区,MBR结构的硬盘最多只有四个主分区。
扩展分区:用于扩展的分区,且同样在DPT中有记录的分区称为扩展分区(不知道怎么概况比较好,先这样解释吧),MBR硬盘中最多只能有一个扩展分区。扩展分区对应的系统ID是0x0F。扩展分区并不是真实的存在的分区。
逻辑分区:在扩展分区中再次划分出来的分区称为逻辑分区,逻辑分区的分区信息记录在EBR中。理论上逻辑分区没有数量限制。
下图是电脑上的分区图,可以从中看出它们三者的关系(结合图左下方的颜色说明看):
EBR:
EBR是一个链式结构,这个结构开始于MBR的扩展分区,结束于最后一个逻辑分区。每个逻辑分区的开始位置都有一个EBR。
EBR的数据结构类似于MBR,关于它的结构可以直接看上面MBR的结构体。区别在于EBR的分区表最多只会用到前两个。第一个分区项记录了EBR所在的逻辑分区的分区信息,它指向该分区的引导扇区(fat32和ntfs文件系统中就是DBR扇区);第二个分区项则记录了下一个逻辑分区的分区信息,指向下一个逻辑分区的EBR。如果没有下一个逻辑分区的话,这一项全部用0填充。第三、四分区项全部用0进行填充。
需要注意的是,EBR中关于分区开始位置的偏移是个相对偏移,它是相对于扩展分区的开始位置的偏移量。如果需要知道逻辑分区的真实偏移值一定要记得加上扩展分区的偏移量。
GPT:
GUID Partition Table全称:全局唯一标识分区表。是不同于MBR的另一种硬盘分区结构。这种分区结构可以解决MBR无法管理硬盘偏移扇区大于2的32次之后的空间(假设物理扇区大小为512字节,则硬盘只能识别到前2T的空间,超过2T之后的空间将无法被识别),已经最多只能分四个主分区,每个分区不能大于2的32次个扇区的问题(以扇区大小为512字节进行计算,每个分区不能超过2T)。
关于GPT的了解,大部分都来自于这里:http://www.jinbuguo.com/storage/gpt.html;现在只记录下我目前知道的,未知部分以后再慢慢补充。
GPT对于分区的数量没有限制,只要求支持的分区数不得少于128个分区(Windows下最多只支持128个分区)。且每个分区的最大值可以达到18EB(以扇区大小为512字节计算)。
GPT主要分为四个部分:PMBR、GPT分区表头、GPT分区项和备份GPT。大致的结构体如下:
typedef struct _GUID_PARTITION_TABLE
{
MBR protectMBR; //用于保护GPT分区不会被只支持MBR的设备破坏
GPT_HEADER gpt_head; //EFI信息区,大小一个扇区,位于第1扇区(第二个扇区),如果一个扇区大小大于512字节,则第0扇区除MBR之外的部分空着
GPT_TABLE gpt_table[128]; //分区项,EFI中无限制,只要求不小于128,windows限制为128个
}GPT;
PMBR:
即protect MBR,这是为了兼容MBR的,为了防止不支持GPT分区的设备对GPT记录的破坏,在硬盘的第0扇区即MBR的位置,存放了一个PMBR,它将GPT记录所存放的扇区作为一个分区记录在PMBR的DPT中,系统ID为0XEE,这样对于只支持MBR的设备来说这只是个未识别的分区而不会对这部分扇区进行操作。
而对于GPT而言,PMBR根本没用。
GPT分区表头:
存放EFI信息,位于硬盘的第1扇区(MBR之后的扇区),占一个扇区。结构体如下:
#define BYTE_PER_SECTOR //每个扇区的大小,不一定是512
typedef unsigned int uint
typedef unsigned char uchar
typedef struct EFI_INFO
{
uchar gpt_head_sign[8]; //GPT头签名“45 46 49 20 50 41 52 54”(ASCII码为“EFI PART”)
uint version; //版本号,目前是1.0版,其值是“00 00 01 00”(照这个注释来看应该是ushort型的)
uint gpt_head_size; // GPT头的大小(字节数),通常为“5C 00 00 00”(0x5C),也就是92字节
uint gpt_chk_sum; //GPT头CRC校验和(计算时把这个字段本身看做零值)
uint reserved; //保留,必须为“00 00 00 00”
uint head_startsector[2]; //EFI信息区(GPT头)的起始扇区号,通常为“01 00 00 00 00 00 00 00”,也就是LBA1。
uint backup_sector[2]; // EFI信息区(GPT头)备份位置的扇区号,也就是EFI区域结束扇区号。通常是整个磁盘最末一个扇区。
uint part_startsector[2]; //GPT分区区域的起始扇区号,通常为“22 00 00 00 00 00 00 00”(0x22),也即是LBA34。
uint part_endsector[2]; //GPT分区区域的结束扇区号,通常是倒数第34扇区。
uint guid[4]; // 磁盘GUID(全球唯一标识符,与UUID是同义词)
uint table_startsector[2]; //分区表起始扇区号,通常为“02 00 00 00 00 00 00 00”(0x02),也就是LBA2。
uint item_size; //分区表总项数,通常限定为“80 00 00 00”(0x80),也就是128个。
uint byte_per_item; // 每个分区表项占用字节数,通常限定为“80 00 00 00”(0x80),也就是128字节。
uint table_chksum; //分区表CRC校验和
uchar reserved[BYTE_PER_SECTOR - 92]; //保留,通常是全零填充
}GPT_HEADER;//sizeof==BYTE_PER_SECTOR(一个扇区)
GPT分区项:
记录每个分区的信息,类似于MBR中的DPT。不同的是它的大小为128个字节。结构如下:
struct GPT_TABLE
{
#if 1 //两种表达方式都可以,其实是一样的
char type[16]; //用GUID表示的分区类型
char flag[16]; //用GUID表示的分区唯一标示符
long start; //该分区的起始扇区,用LBA值表示。
long end; //该分区的结束扇区(包含),用LBA值表示,通常是奇数。
long attr; //该分区的属性标志
char name[72]; //UTF-16LE编码的人类可读的分区名称,最大32个字符。
#else
uint part_type[2]; // 用GUID表示的分区类型
uint indetifier[2]; //用GUID表示的分区唯一标示符
uint start_sector[2]; //该分区的起始扇区,用LBA值表示。
uint endsector[2]; // 该分区的结束扇区(包含),用LBA值表示,通常是奇数。
uint attribute[2]; //该分区的属性标志
uchar part_name[72]; // UTF-16LE编码的人类可读的分区名称,最大32个字符。
#endif
}; //sizeof==128
备份GPT:
备份GPT位于磁盘的尾部,包含GPT头和分区项的备份。它占用GPT结束扇区和EFI结束扇区之间的33个扇区。其中最后一个扇区用来备份1号扇区的EFI信息,其余的32个扇区用来备份LBA2~LBA33扇区的分区表。