4.4 訪問FAT條目
FAT區由一條條FAT條目構成,關于 FAT[N] 對應的條目具體位置計算如下:
格外需要注意的是,不同格式,對應的FAT條目的長度和格式不一樣:
此外對于FAT32格式,高4位是保留位,只有低28位有效!
具體如下圖所示:
4.5 文件與簇之間的關系
那么文件和簇之間的相互關系又是怎樣的呢?我們又是如何準確的找到存放在flash上的文件的呢?接下來讓我們看下文件與簇之間的關系映射。
在FAT卷上文件通過目錄管理,==目錄是一個32字節數組組成的目錄條目結構==,此目錄結構包含:文件名、文件大小、時間戳以及文件所在的第一個簇號。
簇號為0和1的簇被保留,由參數BPB_RootClus可知,有效簇從第2號簇開始。==FAT2對應數據區的第一個簇==。
因此第N個簇的位置計算公式如下:
FirstSectorofCluster = DataStartSector + (N - 2) * BPB_SecPerClus
==每個條目所在的位置,對應一個簇。當文件長度大于一個簇長度時,條目內的值為下一個條目的索引,直到文件所在的最后一個簇,由此構成簇鏈!文件所在的最有一個簇所對應的FAT條目內的值由一個特殊的值(EOC)組成,它永遠不會匹配任何有效的簇號==,如下:
FAT12: 0xFF8 - 0xFFF (typically 0xFFF)
FAT16: 0xFFF8 - 0xFFFF (typically 0xFFFF)
FAT32: 0x0FFFFFF8 - 0x0FFFFFFF (typically 0x0FFFFFFF)
存在一些特殊的值被用來做損壞簇的標記,如下:
FAT12: 0xFF7
FAT16:0xFFF7
FAT32:0xFFFFFFF7
不過此處需要注意,在FAT12/16系統上,上述特殊值絕不會和任何有效簇匹配,但是在FAT32上有可能,因此為了防止混淆,你在創建FAT32系統的時候應該避免這種情況發生!因此FAT32系統上簇的上限為268435445(大于256M個簇)
FAT條目初始化的時候,FAT[2] 及以后的數據應被初始化為0,指示未被使用處于空閑狀態,如果值不為0,則意味著簇被損壞或被使用狀態。在FAT12/16系統上,空閑簇的數量未被記錄,而在FAT32系統上,FAT32支持FSInfo結構體,里面記錄了空閑簇的數量。
==關于FAT[0]和FAT[1]:==
此兩個保留的條目,沒有與任何簇有聯系;不過具有其他意義,如下:
FAT12: FAT[0] = 0xF??; FAT[1] = 0xFFF;
FAT16: FAT[0] = 0xFF??; FAT[1] = 0xFFFF;
FAT32: FAT[0] = 0xFFFFFF??; FAT[1] = 0xFFFFFFFF;
FAT[0]中的?? 與 BPB_Media 相同;
FAT[1] 記錄了錯誤歷史記錄:卷臟標志(FAT16:bit15、FAT32:bit31),系統在啟動的時候清除此位,正常關閉的時候恢復。
如果此位已經清除,表明上次未被正常關閉,可能存在邏輯卷錯誤;硬件錯誤標志(FAT16:bit14、FAT32:bit30)當出現無法恢復的讀寫錯誤時清除,表明需要進行全面檢查。
==關于FAT區域,有兩個重點注意事項:==
第一個是FAT的最后一個扇區可能沒有被完全使用。在大多數情況下,FAT在扇區的中間結束。FAT驅動程序不應該對未使用的區域做出任何假設。在格式化卷時,應該用零填充它,并且在此之后不應更改它。
另一個是BPB_FATSz16/32可以指示比卷需要的值大的值。換句話說,未使用的扇區可以跟隨每個FAT。這可能是數據區域對齊或其他原因導致的。同時,在格式化時這些扇區也會被用零填充。
下表展示了不同FAT類型中FAT值所對應的含義解釋:
4.6 FSInfo扇區結構及備份引導扇區
此部分內容只在FAT32系統上存在,對于FAT12系統FAT區域大小最大6KB,對于FAT16系統FAT區域最大128KB,但是在FAT32系統上FAT區域通常上達數MB,這是因為FAT32系統支持FSInfo數據結構。
在FAT32系統上新增FSInfo數據結構的原因是:在FAT12/16系統上,想要知道flash上剩余的簇數需要掃描整個FAT區才能計算出來,但隨著flash容量的不斷擴大,掃描花費的時長越來越長,為了避免掃描浪費過多的時間,因此在FAT32系統上增加了FSInfo結構,用于記錄flash上剩余的簇數。
FSInfo數據結構如下:
| 字段名 | 偏移 | 大小 | 描述 |
| — | — | — | — |
| FSI_LeadSig | 0 | 4 | 固定值為0x41615252,頭部簽名 |
| FSI_Reserved1 | 4 | 480 | 保留區域,采用0x00覆蓋 |
| FSI_StrucSig | 484 | 4 | 固定值為0x61417272,也是一個簽名 |
| FSI_Free_Count | 488 | 4 | 記錄了空閑的簇數,如果這個值為0xFFFF FFFF,則表示不知道具體的空閑簇數 |
| FSI_Nxt_Free | 492 | 4 | 提示驅動程序應該從此參數提示的簇開始尋找空閑的簇,通過此參數便可以不用從FAT區頭開始尋找下一個空閑簇了,節省了大量時間;如果此參數為0xFFFF FFFF,則驅動程序應該從頭部(2號簇)開始尋找空閑簇|
| FSI_Reserved2 |496 | 12 | 保留區域,采用0x00覆蓋 |
| FSI_TrailSig | 508 | 4 | 固定值0xAA550000,尾部簽名 |
注意:當扇區大小大于512字節時, 剩余空間采用0x00填充
4.7 FAT目錄
FAT目錄分為長文件名目錄(LFN)以及短文件名目錄(SFN),長文件目錄是在短文件名目錄上的一個擴展,具體采用長文件名還是短文件名由讀取FAT文件系統的操作系統決定,如windows;設置長文件名時短文件名也被設置,具有兼容性。
此外,有一個很重要的概念:在FAT文件系統上目錄也是一個文件,只是此文件的屬性不一樣而已。
在所有目錄中,有一個比較特殊的是根目錄,且根目錄作為頂層目錄必須存在。
在FAT12/16系統中,根目錄不是一個文件,且放在根目錄區,根目錄的最大條目數由 BPB_RootEntCnt 參數指示;
在FAT32系統中,根目錄與子目錄沒有什么區別,且根目錄的起始簇由 BPB_RootClus 參數指示。
根目錄與子目錄的另外一個區別是,根目錄不包含 . .. 此兩個點目錄,且它可以包含卷標(具有ATTR_VOLUME_ID屬性的條目)
4.7.1 SFN 短文件名目錄
目錄條目結構如下:
關于目錄結構的第一個字段 DIR_Name 的第一個元素 DIR_Name[0] 在目錄表中有著特殊作用,如下:
當此值為 0xE5 時,代表此目錄條目未被使用(或已廢棄)
當此值為 0x00 時,也代表此目錄條目未被使用;此外還提示后續目錄條目也未被使用,因為后續的目錄條目 DIR_Name[0] 都會是 0x00
如果文件名的第一個字符為 0xE5 這個特殊值,則使用 0x05 替代。
這么設計的意義是什么呢?將 DIR_Name[0] 用作特殊字符,其目錄在于方便文件刪除!當我們刪除一個文件的時候,文件系統并不會將此文件所對應的數據全部刪除,因為那樣太費時間了,也沒有必要,而是直接將對應文件的目錄項中的 DIR_Name[0] 修改為 0xE5 即可!
關于文件名字段 DIR_Name,在FAT文件系統中還有如下規定:
DIR_Name 字段的11字節的文件名分為兩個部分:8 字節的主文件名 + 3字節的擴展名;
文件名中主文件名與擴展名中間的 . 被省略,不在此記錄
如果主文件名長度不夠,小于8字節,則使用 0x20 空格填充
用于設置文件名的字符也有限制,支持的字符有 ==0~9 A~Z ! # $ % & ‘ ( ) - @ ^ _ ` { } ~==
主文件名和擴展名中的(a~z)ASCII字符都會被轉化成大寫字符保存
以下為文件名存儲示例:
4.7.2 LFN長文件名
長文件名是文件名的另外一種存儲方式,由于SFN短文件名具有長度、字符等限制,在一些場景下不能很好的滿足需求,因此就需要使用到長文件名,關于長文件名的具體內容如下:
長文件名是一個具有特殊屬性的目錄條目。長文件名目錄屬性 DIR_Attr 字段的值 ATTR_LONG_NAME = (ATTR_READ_ONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME_ID) = 0x0F;
關于長文件名的目錄屬性如下:
| 字段名 | 偏移 | 大小 | 描述 |
| — | — | — | — |
| LDIR_Ord | 0 | 1 | 序號(1-20),用來表示此條目屬于長文件名序列條目中的第幾條。且長文件名序列首條條目的值應& 0x40以進行標識! |
| LDIR_Name1 | 1 | 10 | 長文件名 第1 ~ 第5 字符(注意此處一個字符占兩個字節) |
| LDIR_Attr | 11 | 1 | 長文件名屬性,此值永遠為 ATTR_LONG_NAME 0x0F
| LDIR_Type | 12 | 1 | 類型,必須為0 |
| LDIR_Chksum | 13 | 1 | 和校驗 |
| LDIR_Name2 | 14 | 12 | 長文件名 第6 ~ 第11 字符(注意此處一個字符占兩個字節) |
| LDIR_FstClusLO | 26 | 2 | 必須為0 |
| LDIR_Name3 | 28 | 4 | 長文件名 第12 ~ 第13 字符(注意此處一個字符占兩個字節) |
關于長文件名,有以下幾點重要概念:
一個文件一定有短文名SFN,但不一定有長文件名LFN
長文件名LFN字段中==僅包含文件名信息==,不包含其他內容,其他內容需要通過短文件名SFN查看
如果一個文件既有長文件名也有短文件名,則長文件名是其主要名字,而短文件名則為附加名字
==長文件名LFN條目在對應的短文件名SFN條目前面==
一個文件的長文件名最長255字符,對應最多20個長文件名LFN條目
長文件名簡單理解起始就是存儲一個字符串,因此沒有類似SFN的限制,允許有空格、支持大小寫、允許多個.符號等
LFN條目文件名長度不夠,仍然采用0x20填充
下圖是官方關于一個名為 “MultiMediaCard System Summary.pdf” 的長文件名在flash上的長文件名條目,如下所示,一眼沒看明白也沒關系,后文有實例說明,對長文件名有概念了就行!
關于長文件名的checksum字段和計算,算法如下:
uint8_t create_sum (const DIR* entry)
{
int i;
uint8_t sum;
for (i = sum = 0; i < 11; i++) { /* Calculate sum of DIR_Name[] field */
sum = (sum >> 1) + (sum << 7) + entry->DIR_Name[i];
}
return sum;
}
4.7.3 LFN系統對于SFN的兼容
在使用LFN長文件名的系統中,會自動生成SFN短文件名已確保此文件在短文件名的文件系統中可使用。同時為了防止生成的短文件名沖突,SFN的生成采用 名稱+數字后綴+擴展 的格式,同時采用以下規則生成SFN:
小寫自動轉大寫
如果存在空格,則刪去空格,設置有損轉換標識
已.開頭的文件刪除頭部的.,并設置有損轉換標識
存在多個.的文件名,僅保留最后一個作為文件名與擴展的分隔,并設置有損轉換標識
其他不支持的字符,采用_代替,并設置有損轉換標識
文件名如果是Unicode編碼,則轉化為ANSI/OEM編碼;不能轉換的字符采用_代替,并設置有損轉換標識
長度超過8字節的部分,截斷,并設置有損轉換標識
擴展名字段超過3字節的,截斷,并設置有損轉換標識
有損轉轉換標識為:~,ASCII值為0x7E,十進制126
示例如下:
至此,FAT文件系統的理論部分已經描述完了,接下來我們繼續使用winhex對數據進行分析。
5.分區分析
繼續回顧我們一開始的這張布局圖
5.1 保留分區分析
保留分區為第一個分區,其中引導扇區位于保留分區的第一個扇區。
根據 4.3 章節計算結果可知,保留分區起始地址為 0x00,大小 0xC00
保留分區數據如下,保留分區內最重要的內容即為引導扇區,除引導扇區外,其他剩余空間全部保留,采用0x00覆蓋。關于引導扇區已在 4.2 章節詳細分析,此處不再做介紹。
5.2 FAT區分析
根據 4.3 章節描述,FAT區的起始地址為 ==0xC00==,大小為 ==0x3B400==,此外存在兩個FAT區,FAT1和FAT2,起始地址分別為:==0xC00==、==0x1E600==,對應地址數據如下:
FAT1 數據:
FAT2 數據如下:
==由于此處采用FAT16格式,所以每個FAT條目占據兩個字節!==
根據上述數據進行分析:
確認 FAT2 為 FAT1 的備份;
存在5個FAT條目其中 FAT[0] 和 FAT[1] 為保留條目,FAT[0] 的內容與 BPB_Media 媒體類型字段一致,FAT[1] 用來記錄錯誤歷史記錄 (詳見 4.5 章節描述)
==根據4.5章節描述,FAT2對應數據區的第一個簇==,又FAT[2]、FAT[3]、FAT[4] 數據均為 0xFF,表明存在三個文件,且每個文件的大小小于等于一個簇的空間;且分別存放在數據區第1到第3個簇上!
此處可能大家會由疑問,剛剛格式化的sd卡為什么會存在文件內,其實這個是系統文件,格式化后自帶的,默認是隱藏的,只有使用winhex才能看到,也就是對應的System Volume Information文件夾。
-
FlaSh
+關注
關注
10文章
1640瀏覽量
148285 -
OEM
+關注
關注
4文章
402瀏覽量
50405 -
ASCII
+關注
關注
5文章
172瀏覽量
35140 -
FAT32
+關注
關注
0文章
33瀏覽量
13806 -
SFN
+關注
關注
0文章
5瀏覽量
9033
發布評論請先 登錄
相關推薦
評論