一、Fat12 文件格式
1. 格式簡(jiǎn)要說(shuō)明
- Fat12是DOS支持的軟盤(pán)格式。
- FAT全稱是文件分配表(File Allocation Table),用來(lái)記錄存放文件名、起始扇區(qū)等文件信息。
- 除了文件分配表,還有目錄表(File Directory Table)。
- 軟盤(pán)有兩個(gè)磁頭,每個(gè)磁頭80個(gè)柱面(磁道),每個(gè)柱面有18個(gè)扇區(qū),每個(gè)扇區(qū)512個(gè)字節(jié)空間
軟盤(pán)結(jié)構(gòu):

2. 引導(dǎo)扇區(qū)
簡(jiǎn)寫(xiě)格式
- 面:C
- 磁道:H
- 扇區(qū):S(每道18個(gè)扇區(qū))
C0-H0-S1用來(lái)存放引導(dǎo)區(qū),如果最后兩個(gè)字節(jié)是0x55,0xaa(DW 0xAA55),BIOS就把這512字節(jié)讀出來(lái)執(zhí)行。
3. FAT
- C0-H0-S2~C0-H0-S10(9個(gè)扇區(qū)) 存放 FAT表
- C0-H0-S11~C1-H0-S1(9個(gè)扇區(qū)) 存放備用FAT表
- C1-H0-S2~ C1-H0-S15(14個(gè)扇區(qū))用于存放FDT(根目錄)
4. FAT結(jié)構(gòu)
- FAT12的每個(gè)文件分配表項(xiàng)占12bit, 每個(gè)表項(xiàng)代表一個(gè)扇區(qū)。
- 磁盤(pán)里所有扇區(qū)被線性索引(LBA)
- FAT12每個(gè)表項(xiàng)的值指出文件存放的下一個(gè)扇區(qū)號(hào)


5. 根目錄
- 根目錄緊跟著FAT表,從19分區(qū)開(kāi)始。
- 根目錄由若干個(gè)目錄條目組成,最多有BPB_RootEntCnt個(gè)。
- 根目錄中每個(gè)條目占32字節(jié):
名稱開(kāi)始字節(jié)長(zhǎng)度內(nèi)容DIR_Name00xB文件名8字節(jié),擴(kuò)展名3字節(jié)DIR_Attr0xB1文件屬性保留位0xC10保留DIR_WrtTime0x162最后一次寫(xiě)入的時(shí)間DIR_WrtDate0x182最后一次寫(xiě)入的日期DIR_FstClus0x1A2此文件在數(shù)據(jù)區(qū)和FAT表中的開(kāi)始簇號(hào)DIR_FileSize0x1C4文件大小
6. 數(shù)據(jù)區(qū)
二、Fat12格式的引導(dǎo)區(qū)代碼:
;%define _BOOT_DEBUG_ ; 做Boot Sector時(shí)把這行注釋掉
; 啟用這行就用nasm Boot.asm -o Boot.com生成.com文件用于調(diào)試
%ifdef _BOOT_DEBUG_
org 0100h
%else
org 07c00h
%endif
CYLS EQU 10
; 把軟盤(pán)按Fat12格式填充
JMP init ; 跳轉(zhuǎn)指令
DB 0x90 ; 空 DB,DD用來(lái)寫(xiě)單字節(jié)
DB "NotOneOS" ; 廠商名,8字節(jié),DB用來(lái)寫(xiě)雙字節(jié)
DW 512 ; 每個(gè)扇區(qū)大小512字節(jié),DW用來(lái)寫(xiě)4字節(jié)
DB 1 ; 每個(gè)簇的扇區(qū)數(shù)
DW 1 ; Boot占的扇區(qū)
DB 2 ; 有2個(gè)FAT表
DW 224 ; 根目錄大小224
DW 2880 ; 磁盤(pán)扇區(qū)總數(shù) 2880
DB 0xf0 ; 介質(zhì)描述符,磁盤(pán)種類必須為0xf0
DW 9 ; 每個(gè)FAT扇區(qū)數(shù)
DW 18 ; 每個(gè)磁道18個(gè)扇區(qū)
DW 2 ; 2個(gè)磁頭
DD 0 ; 隱藏扇區(qū)數(shù)
DD 2880 ; 同上,磁盤(pán)大小
DB 0, 0, 0x29 ; 0x29 擴(kuò)展引用標(biāo)記
DD 0xffffffff ; 無(wú)意義,固定這么寫(xiě)
DB "NotOneOS " ; 磁盤(pán)名(卷標(biāo)),11字節(jié)
DB "FAT12 " ; 磁盤(pán)格式名,8字節(jié)
RESB 18 ; 空18個(gè)字節(jié),填充0x00
init:
MOV AX,0
MOV SS,AX
MOV SP,0x7c00 ; 堆棧空間,從0x7c00向前
MOV DS,AX
MOV AX,0x0820 ; 把磁盤(pán)數(shù)據(jù)加載到內(nèi)存0x0820處。 0x8000~0x81ff的512字節(jié)給啟動(dòng)區(qū)用的,所以從0x0820開(kāi)始
MOV ES,AX ; 初始化磁盤(pán)接口
MOV CH,0 ; 柱面 0
MOV DH,0 ; 磁頭 0
MOV CL,2 ; 扇區(qū) 2
readloop:
MOV SI,0 ; 記錄失敗次數(shù)
retry:
MOV AH,0x02 ; 0x02 讀磁盤(pán)
MOV AL,1 ; 讀1個(gè)扇區(qū)
MOV BX,0
MOV DL,0x00 ; A驅(qū)動(dòng)器
INT 0x13 ; BIOS 讀磁盤(pán)功能
JNC next ; 成功跳轉(zhuǎn)
ADD SI,1 ; 失敗加一次
CMP SI,5 ; 到5次就跳到error
JAE error
MOV AH,0x00 ; 復(fù)位磁盤(pán)功能
MOV DL,0x00
INT 0x13 ; 重置磁盤(pán)驅(qū)動(dòng)器
JMP retry ; 重試
next:
MOV AX,ES ; 內(nèi)存地址向后移動(dòng)0x0020
ADD AX,0x0020
MOV ES,AX ; 通過(guò)AX給ES加0x0020
ADD CL,1 ; 扇區(qū)+1
CMP CL,18 ; 有沒(méi)有到18個(gè)扇區(qū)
JBE readloop ; CL<=18,就跳到 readloop
MOV CL,1
ADD DH,1
CMP DH,2
JB readloop ; 如果 DH < 2 ,則跳到readloop
MOV DH,0
ADD CH,1
CMP CH,CYLS
JB readloop ; 如果CH<CYLS , 則跳到readloop
JMP success
print:
MOV AL, [SI]
ADD SI, 1
CMP AL, 0
JE end
MOV AH, 0x0e
MOV BX, 15
INT 0x10
JMP print
end:
HLT
JMP end ; 無(wú)限循環(huán)
error: ; 打印錯(cuò)誤信息
MOV SI, error_msg
JMP print
success:
MOV SI, success_msg
JMP print
BootMessage:
DB "Hello, MY OS!" ; 想要開(kāi)機(jī)后在屏幕上顯示的字符串
DB 0
success_msg:
DB "Success" ; 成功
DB 0
error_msg:
DB "Error" ; 失敗
DB 0
times 510-($-$$) db 0 ; 填充剩下的空間,使生成的二進(jìn)制代碼恰好為512字節(jié) $$表示一個(gè)section的開(kāi)始處匯編后地址
DW 0xaa55 ; 結(jié)束標(biāo)志
; 繼續(xù)按Fat12填充磁盤(pán)剩余空間
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 4600
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 1469432
三、 文件讀寫(xiě)
使用BIOS中斷 int 13h,參數(shù):

知道扇區(qū)號(hào)的時(shí)候,要計(jì)算int 13h的參數(shù):
設(shè)扇區(qū)號(hào)/每磁道扇區(qū)數(shù)(18) = 商 Q,余數(shù)R ,則:
- 柱面號(hào)= Q >> 1 (相當(dāng)于 Q/2 無(wú)余數(shù))
- 磁頭號(hào)=Q & 1
- 起始扇區(qū)號(hào)=R+1
四、讀磁盤(pán)鏡像并寫(xiě)入文件,查看文件格式
下面使用VirtualBox讀入磁盤(pán)鏡像,并寫(xiě)入一個(gè)文件。(新建虛擬機(jī)并加虛擬磁盤(pán)的方法前面章節(jié)有提到。)

使用VsCode打開(kāi)磁盤(pán)鏡像,可以看到存儲(chǔ)區(qū)域已經(jīng)有了變化:

這里使用的是笨方法保存文件,也可以使用edimg.exe程序來(lái)做。
- 文件名保存到 0x002600處
- 文件的內(nèi)容存在 0x40200處
后面章節(jié)來(lái)看如何讓程序加載NotOneOS.sys里的代碼。
本章代碼與編譯運(yùn)行方法已放到gitee:
gitee.com/xundh/learn-os