標籤:char 代碼結構 合成 資料 結果 div erp byte c語言實現
上一節實現了主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: 磁碟DPT12 13 返回參數:int型,返回DPT中包含的有效分區數量(包含未格式化的分卷),如果該DPT不是GPT14 15 分區形式,將會返回-116 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 else58 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實現以及統一方式讀取磁碟分割