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

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


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

知道扇區號的時候,要計算int 13h的參數:
設扇區號/每磁道扇區數(18) = 商 Q,余數R ,則:
- 柱面號= Q >> 1 (相當于 Q/2 無余數)
- 磁頭號=Q & 1
- 起始扇區號=R+1
四、讀磁盤鏡像并寫入文件,查看文件格式
下面使用VirtualBox讀入磁盤鏡像,并寫入一個文件。(新建虛擬機并加虛擬磁盤的方法前面章節有提到。)

使用VsCode打開磁盤鏡像,可以看到存儲區域已經有了變化:

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