在STM32上實現NTFS之4:GPT分區表的C語言實現(1):主GPT表頭的實現
題外話:在荒廢了很久沒有更新之後……某日突然收到讀者的站內信!內容大體是詢問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表頭的實現