1. FAT12 檔案系統
說起來
FAT12
檔案系統的曆史相當久遠,早在
DOS
系統的時代就使用
FAT12
作為檔案系統使用,一直沿用至今仍會在軟碟的結構上使用
FAT12
格式。
當軟碟以
FAT12
格式組織格式化後将會以如下标準設定:
- 兩個磁頭;
- 每個磁頭有
個磁道;80
- 每個磁道有
個扇區;18
- 每個扇區大小為
位元組。512
标準
FAT12
軟碟空間:
2 * 80 * 18 * 512
=
1474560B
=
1440KB
=
1.44MB
是以一個标準的
1.44MB
大小的
FAT12
格式軟碟共有
2 * 80 * 18
=
2880
個扇區。這
2880
個扇區被分為
5
個部分,如下:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIyVGduV2YfNWawNyZuBnL2EmZ0EGN5IzNjZTYxEGZilTM1QzN4cjYhljY5ETNyI2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
2. MBR (Main Boot Record)
MBR (Main Boot Record)
主引導記錄占用大小為
1
個扇區,即
512 B
。在這個扇區裡記錄了整個檔案系統的組織結構資訊和引導程式兩部分内容。
辨別 | 偏移量 | 類型 | 大小 | 預設值 | 描述 |
---|---|---|---|---|---|
BS_JmpBoot | db | 3 | - | 跳轉指令 | |
BS_OEMName | 3 | db | 8 | MSWIN4.1 | OEM字元串,必須為 8 個字元,不足會以空格填充 |
BPB_BytePerSec | 11 | dw | 2 | 0x200 | 每個扇區位元組數 |
BPB_SecPerClus | 12 | db | 1 | 1 | 每簇占用的扇區數 |
BPB_RsvdSecCnt | 14 | dw | 2 | 1 | Boot占用的扇區數 |
BPB_NumFATs | 16 | db | 1 | 2 | FAT表的數量 |
BPB_RootEntCnt | 17 | dw | 2 | 0xE0 | 根目錄可容納的目錄項數 |
BPB_TotSec16 | 19 | dw | 2 | 0xB40 | 邏輯扇區總數 |
BPB_Media | 21 | db | 1 | 0xF0 | 媒體描述符 |
BPB_FATSz16 | 22 | dw | 2 | 9 | 每個FAT占用扇區數 |
BPB_SecPerTrk | 24 | dw | 2 | 0x12 | 每個磁道扇區數 |
BPB_NumHeads | 26 | dw | 2 | 2 | 磁頭數 |
BPB_HiddSec | 28 | dd | 4 | 隐藏扇區數 | |
BPB_TotSec32 | 32 | dd | 4 | 若BPB_TotSec16是0,則在這裡記錄扇區總數 | |
BS_DrvNum | 36 | db | 1 | 中斷 13(int 13h)的驅動器号 | |
BS_Reserved1 | 37 | db | 1 | 未使用 | |
BS_Bootsig | 38 | db | 1 | 0x29 | 擴充引導标志 |
BS_VolID | 39 | dd | 4 | 卷序列号 | |
BS_VolLab | 43 | db | 11 | - | 卷标,必須為11個字元,不足會以空格填充 |
BS_FileSysType | 54 | db | 8 | FAT12 | 檔案系統類型,必須是8個字元,不足以空格填充 |
BOOT_Code | 62 | db | 448 | 0x00 | 引導代碼,由偏移0位元組(BS_JmpBoot)跳轉過來 |
END | 510 | db | 2 | 0x55, 0xAA | 系統引導辨別,引導扇區結束辨別 |
BPB_NumFATs: 描述在存儲媒體中
FAT
表的數量。此處雖然規定最小設定的值為
1
,但是為了能夠起到恢複檔案的作用,一般建議設定為
2
,即表示擁有兩份
FAT
表。
BPB_Media: 描述存儲媒體類型,對于不可移動的存儲媒體,标準值為
0xF8
,對于可移動的存儲媒體,常用值為
0xF0
。該字段的合法值有
0xF0
、
0xF8
、
0xF9
、
0xFA
、
0xFB
、
0xFC
、
0xFD
、
0xFE
、
0xFF
。此處寫入的值也必須向
FAT
的第
項的最後一個位元組寫入同樣的值。
3. FAT 表
本文采用兩個
FAT
表格式的
FAT12
檔案系統,由于
FAT2
是用于資料恢複作用,是以
FAT2
的内容與
FAT1
表的内容完全相同,即是拷貝了
FAT1
一份。
FAT12
檔案系統以簇 (
Cluster
) 為機關配置設定資料區(管理扇區),每個簇大小為
BPB_NumFATs * BPB_RootEntCnt
個位元組。
FAT
表中的表項位寬與
FAT
類型有關,
FAT12
檔案系統的表項位寬為
12bit
,
FAT16
的表項位寬為
16bit
,而
FAT32
的表項位寬為
32bit
。
FAT
表中的表項與資料區的簇是一一對應的關系,即一個表項對應資料區的一個簇大小的記憶體單元。
FAT
表項的取值如下:
FAT 項 | 可取值 | 描述 |
---|---|---|
BPB_Media | 磁盤辨別字,低位元組需與 BPB_Media 數值保持一緻 | |
1 | FFFh | 表示第一個簇已占用 |
2 ~ N | 000h | 可用簇 |
002h~FEFh | 已用簇 | |
FF0h~FF6h | 保留簇 | |
FF7h | 壞簇 | |
FF8h~FFFh | 檔案的最後一個簇 |
[注]:FAT[0] 和 FAT[1] 始終不作為資料區的索引使用。
4. 根目錄和資料區
根目錄區隻儲存目錄項 (
BootEntry
) 資訊,資料區的不僅可以儲存目錄項資訊,也可以儲存檔案資料。目錄項是由一個
32B
組成的結構體,目錄項本身可以表示一個目錄,也可以表示一個檔案,其中記錄着名字、長度 以及資料起始簇号等資訊。其完整結構如下:
名稱 | 偏移 | 長度 | 描述 |
---|---|---|---|
DIR_Name | 0x00 | 11 | 檔案名 8B,擴充名 3B |
DIR_Attr | 0x0B | 1 | 檔案屬性 |
保留 | 0x0C | 10 | 保留位 |
DIR_WrtTime | 0x16 | 2 | 最後一次寫入時間 |
DIR_WrtDate | 0x18 | 2 | 最後一次寫入日期 |
DIR_FstCtus | 0x1A | 2 | 起始簇号 |
DIR_FileSize | 0x1C | 4 | 檔案大小 |
其中
DIR_FstClus
字段描述的是檔案在磁盤中存放的具體位置,由于
FAT[0]
和
FAT[1]
已明确其作用不能用于資料區的簇索引,是以這裡的值不能取
或
1
,有效值将從
2
開始。
根目錄占用扇區數的計算方法為:
(BPB_RootEntCnt * 32 + BPB_BytesPerSec - 1) / BPB_BytesPerSec = (224 * 32 + 512 - 1) / 512 = 14
,根目錄區的扇區起始号為
MBR + FAT[0] + FAT[1] = 1+ 9 + 9 = 19
,資料區的扇區起始号為
Root + Sizeof( Root ) = 19 + 14 = 33
。
5. 用執行個體說明
聽了上面這麼多的概念性東西,總覺得講的很虛,讓我們用實際的例子來認識這個
FAT12
結構。
5.1. 建立 1.44MB 軟碟
需要用到的工具:WinImagne
打開
WinImage
軟體,選擇 檔案 > 建立,選擇
1.44MB
大小。
5.2. 準備好要放置的檔案
需要事先準備好需要放置進去的檔案,這裡筆者準備了兩個。一個名為
imboot.txt
,其中可以看到隻有簡單的一句話,這段内容長度隻有
32 KB
,在檔案系統中會占據一個簇的大小。
另一個名為
BPB.txt
的檔案,這裡的内容是摘抄一篇有關
FAT
檔案系統中
BPB
介紹的内容,該檔案總長度為
2488 KB
,按照
FAT12
中規定的每簇中僅包含
1
個扇區,即每個簇為
512 KB
,是以按照道理該檔案将會在檔案系統中為其配置設定
5
個簇來存儲,即
5 * 512 = 2560KB
。這裡讀者需要自己做實驗的話,随便填充檔案内容,隻要超過
1024KB
即可,目的是為了檢視
FAT
表項的索引簇号的原理,是以需要必須超過一個簇大小的檔案。
5.3. 放置檔案進根目錄
依次選擇 鏡像 > 加入,找到提前準備好的檔案,選擇即可。
5.4. 導出為軟碟格式
可以看到我們準備好的檔案已經放置進來。
依次選擇 檔案 > 另存為,這裡的格式選擇
vfd
或者
ima
格式均可。(其它格式筆者并未嘗試,可以自行選擇嘗試,記得留言告訴筆者哦❤️)
5.5. 打開輸出的鏡像檔案
這裡筆者選擇的使用
VSCode
軟體,當然還有很多其它文本檢視工具可以以
Hex
格式閱讀文本,根據個人習慣選擇即可。
首先簡單看一下這裡的内容,第一個扇區就不用看了,第一個扇區主要記錄檔案系統的結構資訊以及引導程式,這裡直接定位到了第二個扇區的起始位置
200h
,雖然暫時還不明白這串内容是什麼,但至少我們已經發現了,第一個扇區的最後兩個位元組為
0x55
、
0xAA
,這起碼說明我們的檔案格式是沒問題的。
5.6. 檢視根目錄區
根據
FAT12
檔案系統的結構,
MBR
、
FAT[1]
、
FAT[2]
分别占用
1
個扇區、
9
個扇區、
9
個扇區,即根目錄的起始扇區号應該為
1 + 9 + 9 = 19
。十六進制的位址為
19 * 512 = 2600h
。OK,我們直接定位到檔案的
2600h
的位置。
其實你已經從檔案右側的
ASCII
碼解碼器顯示的文本中看到了剛才我們放入的兩個檔案名。由于
Inter x86
采用的小端存儲,即低位元組存儲在地位,高位位元組存儲在高位,是以這裡看到的字母是順序的。
我們已經知道了根目錄是由一個個目錄項組成的,而在
FAT12
結構中,一個目錄項長度為
32 bit
,在這裡剛好占兩行。這裡筆者以
C
語言結構體的形式來解讀這裡的根目錄項内容。
将根目錄項視為一個
C
語言的結構體:
struct RootEntry {
char DIR_Name[11]; // 前 8B 為檔案名,後 3B 為擴充名
char DIR_Attr[1]; // 檔案屬性
char DIR_Save[10]; // 未使用,保留
char DIR_WrtTime[2]; // 最後一次寫入時間
char DIR_WrtDate[2]; // 最後一次寫入日期
char DIR_FstClus[2]; // 起始簇号
char DIR_FileSize[4]; // 檔案大小
};
先來看第一個目錄項:
struct RootEntry Entry_01 = {
.DIR_Name = {0x49, 0x4D, 0x42, 0x4F, 0x4F, 0x54, 0x20, 0x20, 0x54, 0x58, 0x54}, // IMBOOT TXT
.DIR_Attr = 0x00,
.DIR_Save = {0x18, 0x2A, 0x92, 0x7B, 0x0F, 0x55, 0x00, 0x00, 0x00, 0x00},
.DIR_WrtTime = {0xC3, 0x7B}, // 0x7BC3
.DIR_WrtDate = {0x0F, 0x55}, // 0x550F
.DIR_FstClus = {0x07, 0x00}, // 0x0007
.DIR_FileSize = {0x20, 0x00, 0x00, 0x00} // 0x00000020 = 32
};
從這裡我們可以看出這個目錄項是對
imboot.txt
檔案的描述,檔案屬性為
0x00
表示普通檔案,可任意讀寫。
DIR_FstClus
的值為
0x0007
,表示該檔案在資料區的起始簇号為
7
,
DIR_FileSize
的值為
0x20
,表示該檔案大小為
32 B
。
再看第二個目錄項:
struct RootEntry Entry_02 = {
.DIR_Name = {0x42, 0x50, 0x42, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, 0x58, 0x54}, // BPB TXT
.DIR_Attr = 0x00,
.DIR_Save = {0x10, 0x1B, 0x9D, 0x7B, 0x0F, 0x55, 0x00, 0x00, 0x00, 0x00},
.DIR_WrtTime = {0x7A, 0x7C}, // 0x7C7A
.DIR_WrtDate = {0x0F, 0x55}, // 0x550F
.DIR_FstClus = {0x02, 0x00}, // 0x0002
.DIR_FileSize = {0xB8, 0x09, 0x00, 0x00} // 0x000009B8 = 2488
};
從檔案名可以看出,該目錄項是對
BPB.txt
檔案,同樣屬性是普通檔案。
DIR_FstClus
的值為
0x0002
,表示該檔案在資料區的起始簇号為
2
,
DIR_FileSize
的值為
0x09B8
,表示該檔案大小為
2488 B
,可以對比 上圖 中檔案大小,是一樣的。
5.7. 如何根據根目錄項檢視檔案内容
當我們找到一個目錄項後,最重要關心的是該檔案儲存在資料區的起始簇号,根據這裡的簇索引便可以在資料區找到該檔案對應的第一個簇,緊接着再去查
FAT[1]
表中的對應表項(例,檔案起始簇号為
20
,需要檢視
FAT
表中的第
20
項),根據表項内容知道需要繼續找下一個簇還是已經找完所有簇到了檔案結束位置。具體流程大概如下圖這樣:
5.8. FAT 表的檢視方式
在
FAT12
中
FAT
表的每個表項長度為
12 bit
,加上小端存儲的緣故,這裡閱讀起來并不會那麼直覺。筆者來解讀一下這裡應該如何閱讀。
上圖正是以文中的
FAT
表為例,可以了解為一個
FAT
表項是由一個位元組和另一個位元組的一半拼接而成,上圖僅是為了容易了解畫的示意圖。而實際上上圖中的這幾個位元組内容從記憶體中完全拿出來并排序後會變成這樣:
0x00_40_03_FF_FF_F0
,然後再以
12 bit
斷之後就會得到這樣的效果:
0x004_003_FFF_FF0
,這樣正好會與
FAT
表中的資料對應起來,
FAT[0]
為
0xFF0
表示可移動存儲媒體,
FAT[1]
為
0xFFF
表示第
1
個簇已經被占用。
5.9. 檢視 IMBOOT.TXT 檔案
根據上文分析,
imboot.txt
檔案在資料區的起始簇号為
7
,那麼首先需要去資料區找到第
7
号簇,根據上文分析,我們知道資料區的起始扇區号為
33
,那麼資料區的起始簇的位址為
33 * 512 = 4200h
,這個位址對于資料區來講是第一個簇,但根目錄項中的起始簇有效值是從
2
開始,即根目錄中的
2
号簇對應的即為資料區的起始簇,這個内容在上文也提到過,為了避免翻來翻去,筆者将上文截圖貼在這裡。
那麼這樣計算的話,第
7
簇就應該在第
2
簇的基礎上再加上
5
個簇的大小,即得到
imboot.txt
在資料區存儲位置,
(33 + 5) * 512 = 4C00h
,讓我們直接定位到檔案的
4C00h
的位置。
雖然這裡我們很直覺的看到,該檔案的所有内容都被儲存在這裡,但對于整套檢索流程并沒有結束,在查閱完第
7
号簇後,應立馬去
FAT
表檢視第
7
個表項,這裡我們隻用看
FAT1
表即可,定位到
FAT1
表的位置
200h
。
通過上文我們已經會檢視
FAT
表項了,這裡的
FAT[7]
的值為
FFFh
,表示該簇為最後一個簇,到這裡将不會繼續索引下去,檔案内容結束。
5.10. 檢視 BPB.TXT 檔案
已經分析過
imboot.txt
檔案,再來看
BPB.txt
将會非常的快了。根據上文分析,
BPB.txt
檔案在資料區的起始簇号為
2
,也就是資料區的第一個簇單元,直接定位到資料區的第一個簇位置
4200h
。
每個簇大小為
512 B
,當讀完
2
号簇後,将會去
FAT
表中查詢第
2
号表項。
FAT[2]
的值為
003h
,那麼這時候表示下一個簇号為
3
号簇,再讀完數區的第
3
簇後又會回來查詢
FAT[3]
表項,接下來的
4
、
5
簇以及
FAT[4]
、
FAT[5]
與
3
相同,
FAT[5]
中的值為
006h
,在讀完資料區的
6
号簇後,來查詢
FAT[6]
,發現
FAT[6]
的值為
FFFh
,表示檔案結束。
#小結
到這裡,相信各位讀者已經對
FAT12
檔案系統有了清楚的認識,本來是要在接下來介紹使用
FAT12
檔案系統實作
Boot
加載
Loader
程式到記憶體中的内容,但忽然看了一下本文已經超出
8500
字了,閱讀到這裡顯然大家已經累了,那麼請休息一會,然後請繼續接着看筆者的下一篇文章《使用 FAT12 檔案系統實作簡單的 Boot 加載 Loader 到記憶體》
#參考連結
[1] FAT12/16/32 Media Boot Record: https://docs.microsoft.com/en-us/azure/rtos/filex/chapter3#fat121632-media-boot-record
[2] FAT Filesystem: http://elm-chan.org/docs/fat_e.html
[3] exFAT file system specification: https://docs.microsoft.com/en-us/windows/win32/fileio/exfat-specification
覺得這篇文章對你有幫助的話,就留下一個贊吧^v^*
請尊重作者,轉載還請注明出處!感謝配合~
[作者]: Imagine Miracle
[版權]: 本作品采用「署名-非商業性使用-相同方式共享 4.0 國際」許可協定進行許可。
[本文連結]: https://blog.csdn.net/qq_36393978/article/details/126305288