1. 程式人生 > >在STM32上實現NTFS之4:GPT分區表的C語言實現(1):主GPT表頭的實現

在STM32上實現NTFS之4:GPT分區表的C語言實現(1):主GPT表頭的實現

center mbr分區 sum 對齊 字節數 決定 容器 alt 水平

題外話:在荒廢了很久沒有更新之後……某日突然收到讀者的站內信!內容大體是詢問GPT分區表信息的讀取方式,筆者激動萬分之下,決定繼續解剖NTFS……其實GPT嚴格上不算是NTFS的內容, GPT和MBR類似,都屬於像是“容器”的東西,容納的都是一個個的分卷,可以是NTFS,可以是FAT32,等等。

上一節講到了DPT與MBR的C實現,既然已經提到了GPT分區表,不妨這一節就解剖一下GPT分區表,MFT留待之後再述。好,繼續……

GPT分區全名為Globally Unique Identifier Partition Table Format。關於GPT的優點,包括無限分區數量、更高的安全性、更大的分卷容量,等等這些都不詳細敘述了,大部分電腦網站上一片GPT相關文章都有這些內容。我們主要講如何用C實現它,在這個過程中,大家會詳細的了解為什麽會有這些優點。

開始的工作於之前相同,先用DiskGenius格式化一塊硬盤,轉換成GPT的分區表,並且建立5個分區,如圖1。因為MBR只支持4個主分區-_-|||,所以為了顯示GPT的優越性之一,我們建立5個分區。

技術分享

圖1 使用DiskGenius建立的5個分區,以及ESP和MSR分區

以GPT建立分區時,DiskGenius會詢問是否建立ESP和MSR分區,在這裏解釋一下ESP和MSR。ESP分區是用於裝系統時的UEFI引導分區,這也是GPT於MBR的區別之一,MS的系統啟動方式分為老舊的BIOS和新的UEFI,BIOS啟動方式只能用於MBR分區表的硬盤,UEFI則從原則上同時支持GPT和MBR,但MS限制只能用GPT啟動UEFI。說到MS,順便提一下各個版本的Windows對GPT的支持程度,從XP開始所有Windows都可以讀寫GPT分區,但32位XP不支持從GPT分區啟動。而MSR,它是留給操作系統和別的東西用的,因為GPT不支持隱藏分區,操作系統一直使用的隱藏分區來保存關鍵數據的做法就沒法用了,所以為了不在資源管理器裏顯示一個很小的磁盤並讓用戶誤刪除,直接標記一個MSR分區,並且使MSR不顯示於資源管理器,是一個更好的做法。

值得註意的,MSP必須在第一次分區之前建立,也就是說我們要創建GPT分區,需要先創建MSR,然後在建立分區。磁盤邏輯上的順序為:ESP,可選的OEM,MSR,分區1,分區2,……

更詳細的有關操作系統的知識,網上文章超多,就不更多的介紹了。

GPT分區表結構如圖2所示,在GPT分區中,每一個數據讀寫單元成為LBA(邏輯塊地址),一個“邏輯塊”相當於傳統MBR分區中的一個“扇區”,之所以會有區別,是因為GPT除了要支持傳統硬盤,還需要支持以NAND FLASH為材料的SSD硬盤,這些硬盤的一個讀寫單元是2KB或4KB,所以GPT分區中幹脆用LBA來表示一個基礎讀寫塊,當GPT分區用在傳統硬盤上時,通常,LBA就等於扇區號,有些物理硬盤支持2KB對齊,此時LBA所表示的一個邏輯塊就是2KB的空間。

技術分享

圖2 GPT分區表結構

從圖中可以看出,整個GPT分區表被分成了70個LBA,其中第0個LBA是“Protective MBR”,從字面意思是被保護的MBR信息,這個是為了和傳統MBR分區表相兼容,以便一個識別程序可以順利識別出一塊硬盤是MBR分區結構還是GPT的。

如圖3顯示整個磁盤的物理0扇區數據,大部分是0,但這不影響我們看出他是一個MBR的分區表,這個分區表中只有一項主分區數據,可以看到0x1C2處記錄的系統信息是0xEE,我們在GPT和MBR綜合的程序中,將根據這個0xEE區分一塊硬盤使用的是GPT還是MBR,函數如下:

 1 #define PDT_GPT 0
 2 
 3 #define PDT_MBR 0
 4 
 5 ...
 6 
 7 bool PartitionTableDetermination(unsigned char* SectorData)
 8 
 9 {
10 
11     if (SectorData[0x1C2] == 0xEE)
12 
13         return PDT_GPT;
14 
15     else
16 
17         return PDT_MBR;
18 
19 }

調用方法如下:

 1     if (PartitionTableDetermination(SectorBuffer) == PDT_GPT)
 2 
 3     {
 4 
 5         printf("該硬盤是GPT分區表\n");
 6 
 7     }
 8 
 9     else if (PartitionTableDetermination(SectorBuffer) == PDT_MBR)
10 
11     {
12 
13         printf("該硬盤是MBR分區表\n");
14 
15     }

運行結果如下:技術分享

技術分享

圖3 磁盤物理0扇區數據

LBA1,這是一個叫做Primary GPT Header的東西,中文大概叫“主GPT頭”,它主要是給出了一些重要的信息,如下表所示:

字節偏移量

數據長度(字節)

範例數值

數據項說明

0x00

8

45 46 49 20 50 41 52 54

固定字符串“EFI PART”

0x08

4

00 00 01 00

GPT版本號,1.0版,所以是0100

0x0C

4

5C 00 00 00

主GPT頭大小,用了92字節

0x10

4

C2 B9 3D B6

主GPT頭的CRC32校驗數據,註意不是累加和校驗

0x14

4

00 00 00 00

保留,4個00

0x18

8

01 00 00 00 00 00 00 00

主GPT頭起始扇區號,LBA1

0x20

8

FF 67 6F 74 00 00 00 00

主GPT頭備份位置扇區號

0x28

8

22 00 00 00 00 00 00 00

GPT分區起始扇區號,LBA34

0x30

8

DE 67 6F 74 00 00 00 00

GPT分區結束扇區號,LBA-34

0x38

16

83 34 0F 39 FC 11 91 41 A0 A3 9B F6 D6 E5 32 FE

磁盤的GUID,唯一標識,這也意味著全硬盤復制是行不通的.

0x48

8

02 00 00 00 00 00 00 00

分區表起始扇區號,LBA2

0x50

4

80 00 00 00

分區表項數目,MS強行給128

0x54

4

80 00 00 00

每個分區表占用字節數,128

0x58

4

C5 04 9E 76

分區表CRC校驗數據

0x5C

-

00 ……

保留空間,必須為0,填充滿整個LBA塊

根據這個表格我們可以寫出兩個結構體,分別來裝主GPT頭的數據和信息:

 1 #pragma pack(1)
 2 
 3  typedef struct
 4 
 5 {
 6 
 7     uint8_t EFIPARTSign[8];            //固定字符串“EFI PART”
 8 
 9     uint8_t GPTVersion[4];            //GPT版本號
10 
11     uint8_t PrimaryGPTHeaderLength[4]; //主GPT頭大小
12 
13     uint8_t PrimaryGPTHeaderCRC32[4];  //主GPT頭的CRC32校驗數據
14 
15     uint8_t Reserve[4];                //保留
16 
17     uint8_t PrimaryGPTHeaderStart[8];  //主GPT頭起始扇區號
18 
19     uint8_t PrimaryGPTHeaderBackup[8]; //主GPT頭備份位置扇區號
20 
21     uint8_t GPTPartitionStart[8];      //GPT分區起始扇區號
22 
23     uint8_t GPTPartitionEnd[8];        //GPT分區結束扇區號
24 
25     uint8_t DiskGUID[16];              //磁盤的GUID
26 
27     uint8_t PartitionStart[8];         //分區表起始扇區號
28 
29     uint8_t PartitionTables[4];        //分區表項數目
30 
31     uint8_t BytesPerPartitionTable[4]; //每個分區表占用字節數
32 
33     uint8_t PartitionTableCRC32[4];    //分區表CRC校驗數據
34 
35     uint8_t Reverse2[4];                 //填充數據00
36 
37 }GPT_Byte;
38 
39 #pragma pack()
40 
41 typedef struct
42 
43 {
44 
45     uint8_t  GPTVersion[4];          //GPT版本號
46 
47     uint32_t PrimaryGPTHeaderLength; //主GPT頭大小
48 
49     uint32_t PrimaryGPTHeaderCRC32;  //主GPT頭的CRC32校驗數據
50 
51     uint64_t PrimaryGPTHeaderStart;  //主GPT頭起始扇區號
52 
53     uint64_t PrimaryGPTHeaderBackup; //主GPT頭備份位置扇區號
54 
55     uint64_t GPTPartitionStart;      //GPT分區起始扇區號
56 
57     uint64_t GPTPartitionEnd;        //GPT分區結束扇區號
58 
59     uint8_t  DiskGUID[16];           //磁盤的GUID
60 
61     uint64_t PartitionStart;         //分區表起始扇區號
62 
63     uint32_t PartitionTables;        //分區表項數目
64 
65     uint32_t BytesPerPartitionTable; //每個分區表占用字節數
66 
67     uint32_t PartitionTableCRC32;    //分區表CRC校驗數據
68 
69 }GPT_Info;

並且寫出獲取GPT信息並完成大小端模式轉換的函數:

 1 void GetGPTInfo(GPT_Info* GPTInfo)
 2 
 3 {
 4 
 5     GPT_Byte GPTbuffer;
 6 
 7     HANDLE hdl = GetDiskHandle(PHYDRIVE);//獲取一個指定物理驅動器句柄
 8 
 9     ReadSectorData(hdl, 1, (char*)(void*)&GPTbuffer);
10 
11     GPTInfoTransferLittleEnd(&GPTbuffer, GPTInfo);
12 
13 }
14 
15  
16 
17 void GPTInfoTransferLittleEnd(GPT_Byte* src, GPT_Info* dest)
18 
19 {
20 
21     memcpy(dest->GPTVersion, src->GPTVersion, 4);
22 
23     dest->PrimaryGPTHeaderLength = ArrayToU32LittleEnd(src->PrimaryGPTHeaderLength);
24 
25     dest->PrimaryGPTHeaderCRC32  = ArrayToU32LittleEnd(src->PrimaryGPTHeaderCRC32);
26 
27     dest->PrimaryGPTHeaderStart  = ArrayToU64LittleEnd(src->PrimaryGPTHeaderStart);
28 
29     dest->PrimaryGPTHeaderBackup = ArrayToU64LittleEnd(src->PrimaryGPTHeaderBackup);
30 
31     dest->GPTPartitionStart      = ArrayToU64LittleEnd(src->GPTPartitionStart);
32 
33     dest->GPTPartitionEnd        = ArrayToU64LittleEnd(src->GPTPartitionEnd);
34 
35     memcpy(dest->DiskGUID, src->DiskGUID, 16);
36 
37     dest->PartitionStart         = ArrayToU64LittleEnd(src->PartitionStart);
38 
39     dest->PartitionTables        = ArrayToU32LittleEnd(src->PartitionTables);
40 
41     dest->BytesPerPartitionTable = ArrayToU32LittleEnd(src->PartitionTables);
42 
43     dest->PartitionTableCRC32    = ArrayToU32LittleEnd(src->PartitionTableCRC32);
44 
45 }

調用方式如下:

技術分享
 1     GPT_Info GPT;
 2 
 3     GetGPTInfo(&GPT);
 4 
 5     printf("GPT版本號:%.2X%.2X%.2X%.2X\n", GPT.GPTVersion[0],
 6 
 7         GPT.GPTVersion[1],
 8 
 9         GPT.GPTVersion[2],
10 
11         GPT.GPTVersion[3]);
12 
13     printf("主GPT頭大小:%d\n", GPT.PrimaryGPTHeaderLength);
14 
15     printf("主GPT頭的CRC32校驗數據:0x%.8X\n", GPT.PrimaryGPTHeaderCRC32);
16 
17     printf("主GPT頭起始扇區號:%d\n", GPT.PrimaryGPTHeaderStart);
18 
19     printf("主GPT頭備份位置扇區號:%d\n", GPT.PrimaryGPTHeaderBackup);
20 
21     printf("GPT分區起始扇區號:%d\n", GPT.GPTPartitionStart);
22 
23     printf("GPT分區結束扇區號:%d\n", GPT.GPTPartitionEnd);
24 
25     printf("磁盤的GUID:%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X\n",
26 
27         GPT.DiskGUID[0], GPT.DiskGUID[1],
28 
29         GPT.DiskGUID[2], GPT.DiskGUID[3],
30 
31         GPT.DiskGUID[4], GPT.DiskGUID[5],
32 
33         GPT.DiskGUID[6], GPT.DiskGUID[7],
34 
35         GPT.DiskGUID[8], GPT.DiskGUID[9],
36 
37         GPT.DiskGUID[10], GPT.DiskGUID[11],
38 
39         GPT.DiskGUID[12], GPT.DiskGUID[13],
40 
41         GPT.DiskGUID[14], GPT.DiskGUID[15]);
42 
43     printf("分區表起始扇區號:%d\n", GPT.PartitionStart);
44 
45     printf("分區表項數目:%d\n", GPT.PartitionTables);
46 
47     printf("每個分區表占用字節數:%d\n", GPT.BytesPerPartitionTable);
48 
49     printf("分區表CRC校驗數據:0x%.4X\n", GPT.PartitionTableCRC32);
View Code

運行,可以看到結果如圖4,與圖5的DiskGenius顯示的數據吻合,唯一有區別的是磁盤的GUID,但因為我們將它處理成數組格式,而DiskGenius處理為5個不等長度的小端數據:

技術分享

圖4 獲取主GPT頭信息

技術分享

圖5 DiskGenius顯示的磁盤信息

GPT分區表先講到這,本節主要解析了主GPT頭的信息,關於LBA2之後的東西,下一節繼續。

這一節總體上非常混亂,因為時隔9個多月重新撿起來,加上這段時間的編碼水平略微提高,感覺之前的代碼就是一坨[高雅],所以這一節也沒有像第三節一樣吧整個文件貼出來。準備抽時間把之前的代碼重構一下,然後每一次直接發一個壓縮包,也省的每次都對大段代碼進行排版,大家都方便^_^。

在STM32上實現NTFS之4:GPT分區表的C語言實現(1):主GPT表頭的實現