1. 程式人生 > >在STM32上實現NTFS之5:GPT分區表的C語言實現(2)GPT實現以及統一方式讀取磁盤分區

在STM32上實現NTFS之5:GPT分區表的C語言實現(2)GPT實現以及統一方式讀取磁盤分區

tfs 下載 數據 特殊 dpt 屬性列表 handle 系統分區 成了

  上一節實現了主GPT頭的信息提取,這一節繼續提取整個的GPT數據,並且將GPT分區表和MBR分區表兩種格式融合成一個模塊,使主調函數(也可以說是使用者)不需要關心磁盤的分區表類型:它太底層了,確實不需要過多的關心。

  繼續看上一節的圖1,這裏就不貼圖了,LBA1的主GPT頭給出了分區信息的總數,還有每一個分區信息所占用的字節數,分區信息的結構如表1:

表1 分區信息結構(GPT Entry)

字節偏移量

數據長度(字節)

範例數值

數據項說明

0x00

16

28 73 2A C1 1F F8 D2 11 BA 4B 00 A0 C9 3E C9 3B

用GUID表示的分區類型

0x10

16

82 63 7A C8 13 0E F8 46 95 29 E6 31 E9 16 B5 42

用GUID表示的分區唯一標示符

0x20

8

00 08 00 00 00 00 00 00

該分區的起始扇區,用LBA值表示

0x28

8

FF 27 E6 31 E9 16 B5 42

該分區的結束扇區(包含),用LBA值表示,通常是奇數

0x30

8

00 00 00 00 00 00 00 80

該分區的屬性標誌

0x38

72

UTF-16LE編碼的人類可讀的分區名稱,最大32個字符

可以看到,這個結構非常之簡潔,只標註了起始扇區,結束扇區,分區類型,GUID,屬性,分區名,而不關心磁頭、磁道等等信息。

需要特殊說明的是,整個分區信息的結構,也就是從LBA2到LBA34(MS總是給劃分128個分區信息),是完全連續的,不依靠任何扇區等信息去定位,也就是說,第n個分區信息的結構起始地址,僅僅根據LBA2的起始地址+分區字節數*n來確定,最常見的是微軟定義的128個分區信息,512字節扇區,128字節的分區信息字節數,所以最常見的是每一個扇區4個分區信息,密集的排列32個扇區。然而正確的計數方法是每128個字節一個分區信息,密集排列128個。當扇區不是512字節時(註意這裏不要跟簇混淆,目前我還沒見過不是512字節扇區的存儲器),每個扇區存儲的分區信息有可能不是4個,下面的程序規避了這個問題,采用了一個新的磁盤讀寫函數——ReadDiskData而不是ReadSectorData——來防止讀分區信息出現錯誤。

 1 /*******************************************************************************
 2 
 3 函 數 名:GetVolumeNumberOfGPT
 4 
 5 函數功能:獲取一個GPT格式的磁盤中有效分卷數
 6 
 7 輸入參數:
 8 
 9           hDisk:磁盤句柄
10 
11           DPT:  磁盤DPT
12 
13 返回參數:int型,返回DPT中包含的有效分區數量(包含未格式化的分卷),如果該DPT不是GPT
14 
15         分區形式,將會返回-1
16 
17 *******************************************************************************/
18 
19 int GetVolumeNumberOfGPT(HANDLE hDisk, DPT_Info* DPT)
20 
21 {
22 
23     if (DPTDetermination(DPT) == DPT_MBR)
24 
25         return -1;
26 
27     GPTEntry_Byte GPTEbuffer;
28 
29     GPTEntry_Info GPTEinfo;
30 
31     PGPTH_Info PGPTH;
32 
33     GetPGPTH(hDisk, &PGPTH);
34 
35     int ValidPartitions = 0;
36 
37     for (int i = 0; i < PGPTH.PartitionTables; i++)
38 
39     {
40 
41         ReadDiskData(
42 
43             hDisk, //讀取磁盤句柄
44 
45             SECTOR_SIZE * PGPTH.PartitionStart + i * PGPTH.BytesPerPartitionTable, //計算讀取的GPTE位置
46 
47             (uint8_t*)(void*)&GPTEbuffer, //緩沖區地址
48 
49             sizeof(GPTEntry_Byte)); //字節數
50 
51         GetGPTEInfo(&GPTEbuffer, &GPTEinfo);
52 
53         if (GUIDcmp(&(GPTEinfo.TypeGUID), (GUID_Info*)&GUID_ptUnuse))
54 
55             break;
56 
57         else
58 
59             ValidPartitions++;
60 
61     }
62 
63     return ValidPartitions;
64 
65 }

分區類型是有規定的,一般有表2的幾種GUID;屬性標誌也是有規定的,見表3。

表2:分區類型的16Byte GUID

數值

類型說明

00000000-0000-0000-0000-000000000000

未使用

024DEE41-33E7-11D3-9D69-0008C781F39F

MBR分區表

C12A7328-F81F-11D2-BA4B-00A0C93EC93B

EFI系統分區[EFI System partition (ESP)],必須是VFAT格式

BC13C2FF-59E6-4262-A352-B275FD6F7172

擴展boot分區,必須是VFAT格式

21686148-6449-6E6F-744E-656564454649

BIOS引導分區,其對應的ASCII字符串是"Hah!IdontNeedEFI"。

D3BFE2DE-3DAF-11DF-BA40-E3A556D89593

Intel Fast Flash (iFFS) partition (for Intel Rapid Start technology)

E3C9E316-0B5C-4DB8-817D-F92DF00215AE

微軟保留分區

EBD0A0A2-B9E5-4433-87C0-68B6B72699C7

基本數據分區

DE94BBA4-06D1-4D40-A16A-BFD50179D6AC

Windows恢復環境

0FC63DAF-8483-4772-8E79-3D69D8477DE4

數據分區。Linux曾經使用和Windows基本數據分區相同的GUID。
這個新的GUID是由 GPT fdisk 和 GNU Parted 開發者根據Linux傳統的"8300"分區代碼發明的。

44479540-F297-41B2-9AF7-D131D5F0458A

x86根分區 (/) 這是systemd的發明,可用於無fstab時的自動掛載

4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709

x86-64根分區 (/) 這是systemd的發明,可用於無fstab時的自動掛載

69DAD710-2CE4-4E3C-B16C-21A1D49ABED3

ARM32根分區 (/) 這是systemd的發明,可用於無fstab時的自動掛載

B921B045-1DF0-41C3-AF44-4C6F280D3FAE

AArch64根分區 (/) 這是systemd的發明,可用於無fstab時的自動掛載

3B8F8425-20E0-4F3B-907F-1A25A76F98E8

服務器數據分區(/srv) 這是systemd的發明,可用於無fstab時的自動掛載

933AC7E1-2EB4-4F13-B844-0E14E2AEF915

HOME分區 (/home) 這是systemd的發明,可用於無fstab時的自動掛載

0657FD6D-A4AB-43C4-84E5-0933C84B4F4F

交換分區(swap) 不是systemd的發明,但同樣可用於無fstab時的自動掛載

A19D880F-05FC-4D3B-A006-743F0F84911E

RAID分區

E6D6D379-F507-44C2-A23C-238F2A3DF928

邏輯卷管理器(LVM)分區

8DA63339-0007-60C0-C436-083AC8230908

保留

上面的GUID數值比較有意思,根據winHex得到的扇區數據,它將16字節的GUID分成了5部分,在很多編譯器中已經有GUID結構體的定義不過是4部分的,在這裏無法使用,所以我們另外定義一個結構

 1 typedef struct
 2 
 3 {
 4 
 5     uint32_t Part1;                 //GUID第1部分
 6 
 7     uint16_t Part2;                 //GUID第2部分
 8 
 9     uint16_t Part3;                 //GUID第3部分
10 
11     uint16_t Part4;                 //GUID第4部分
12 
13     uint48_t Part5;                 //GUID第5部分
14 
15 }GUID_Info;

來表示GUID。而有意思的地方在於,這5部分並不是單純的完全大端或者小端模式,它是混合著來的,前三個部分是小端,後兩個部分是大端,我們在讀取結構的時候一定要註意這點。

表3:分區屬性

數值

類型說明

0

系統分區

1

EFI隱藏分區(EFI不可見分區)

2

傳統的BIOS的可引導分區標誌

60

只讀

62

隱藏

63

不自動掛載,也就是不自動分配盤符

以上的屬性列表並不完全,至少我的硬盤分區之後,類型就是0x80。因為只有一個字節有用的信息,一個枚舉類型就足以解決問題。

現在我們實際看一個擁有4個分區信息的LBA,如圖1:

技術分享

圖1 LBA分區

如圖所示,紅框框起來的部分就是一個分區信息結構,比較精髓的部分是粉色框裏的72字節,它是以UTF16的小端模式編碼的,因為涉及到了中文問題,UTF16LE解碼比較復雜,在這裏我們直接用wchar_t來獲取這個結構:

 1 typedef struct
 2 
 3 {
 4 
 5     uint8_t TypeGUID[16];           //用GUID表示的分區類型
 6 
 7     uint8_t UniqueGUID[16];         //用GUID表示的分區唯一標示符
 8 
 9     uint8_t SectorStart[8];         //該分區的起始扇區,用LBA值表示
10 
11     uint8_t SectorEnd[8];           //該分區的結束扇區(包含),用LBA值表示,通常是奇數
12 
13     uint8_t PartitionAttrib[8];     //該分區的屬性標誌
14 
15     WCHAR   PartitionName[36];      //UTF-16LE編碼的人類可讀的分區名稱,最大32個字符。
16 
17 }GPTEntry_Byte;

現在GPT結構已經可以完整的讀取,由於博文實在實驗階段性完成後做的,所以沒有截圖(=_=尷尬)。

接下來的步驟是整合GPT和MBR。由於DPT引導到GPT和MBR的部分可以直接整合,所以原DPT部分代碼可以不動,但不同的是GPT是通過讀取LBA直接得到每個分區的起始扇區、終止扇區、分區類型等等,而MBR方式是先通過DPT得到小於等於4個引導扇區的位置,然後對這幾個引導扇區進行解析,每個引導扇區對應一個分區信息。

在使用其他的一些庫的時候,筆者受到困擾的地方就是,API函數太多,有很多的庫有不同的API功能是接近的,同時又有很多API名字相近但功能截然不同,所以為了避免這種情況,在這裏盡量縮減API。根據這個思想,可以構建大致的流程如下:首先由用戶提取分區總數,然後用戶來進行malloc操作來根據分區數建立分區列表,然後根據分區列表可以找到每個分區的引導扇區位置。在本節中,所謂的“分區信息”是針對於分區表的分區信息,事實上根據前幾節的介紹,分卷容量並不一定等於分區容量,所以目前的代碼中都是以Partition代表分區,而以Volume代表分卷。

詳細的代碼結構這裏不進行更多的截取,有關代碼的調用方式在main.cpp中很容易的看到。在壓縮包裏有基於VS2015community環境的完整工程。

最後貼上一張完整的分區列表運行結果(磁盤3是我的移動硬盤,磁盤4是我的64GB SD卡)。

技術分享

圖2 代碼運行結果

工程下載地址:https://files.cnblogs.com/files/Coder-Ku/NTFS5.rar

在STM32上實現NTFS之5:GPT分區表的C語言實現(2)GPT實現以及統一方式讀取磁盤分區