(1)操作系統學習筆記——FAT12文件系統與Loader的加載


(1)操作系統學習筆記——FAT12文件系統與Loader的加載

之前我們利用BIOS實現了一個打印字符串的操作,現在我們要在這基礎上加入文件加載的功能,以完成boot的操作。Boot程序主要的是負責開機啟動和加載Loader程序;Loader引導加載程序則用於完成配置硬件工作環境、引導加載內核等任務。
加載Loader程序最理想的方法自然是從文件系統中把Loader程序加載到內存中。那么此處的文件系統就選擇比較簡單的FAT12。在將軟盤格式化成FAT12文件系統的過程中,FAT類文件系統會對軟盤里的扇區進行結構化處理,進而把軟盤扇區划分成: 引導扇區、FAT表、根目錄區和數據區 這4部分。

  • 引導扇區
    FAT12文件系統的引導扇區不僅包含有引導程序,還有FAT12文件系統的整個組成結構信息。下表描述了FAT12文件系統的引導扇區結構:

    名稱 偏移 長度 內容 本系統引導程序數據
    BS_jmpBoot 0 3 跳轉指令 jmp short Label_Start nop
    BS_OEMName 3 8 生產廠商名 'MINEboot'
    BPB_BytesPreSec 11 2 每扇區字節數 512
    BPB_SecPreClus 13 1 每簇扇區數 1
    BPB_RsvSecCnt 14 2 保留扇區數 1
    BPB_NumFATs 16 1 FAT表的份數 2
    BPB_RootEntCnt 17 2 根目錄可容納的目錄項數 224
    BPB_TotSec16 19 2 總扇區數 2880
    BPB_Media 21 1 介質描述符 0xF0
    BPB_FATSz16 22 2 每FAT扇區數 9
    BPB_SecPreTrk 24 2 每磁道扇區數 18
    BPB_NumHeads 26 2 磁頭數 2
    BPB_HiddSec 28 4 隱藏扇區數 0
    BPB_TotSec32 32 4 如果BPB_Tot16值為0,則由這個值記錄扇區數 0
    BS_DrvNum 36 1 int 13h的驅動器號 0
    BS_Reserved1 37 1 未使用 0
    BS_BootSig 38 1 擴展引導標記(0x29) 0x29
    BS_VolID 39 4 卷序列號 0
    BS_VolLab 43 11 卷標 'boot loader'
    BS_FileSysType 54 8 文件那系統類型 'FAT12'
    引導代碼 62 448 引導代碼、數據及其他信息
    結束標志 510 2 結束標志0xAA55 0xAA55
  • FAT表
    FAT文件系統以簇來為單位來分配數據區的存儲空間(扇區), 每個簇的長度為:BPB_BytesPreSec * BPB_SecPreClus字節,數據區的簇號與FAT表的表項是一一對應關系。因此,文件在FAT類文件系統的存儲單位是簇,而非字節或扇區。這種設計方式可以將磁盤存儲空間按固定儲片(頁)有效管理起來,進而按照文件偏移,分片段訪問文件內的數據,就不必一次將文件里的數據全部讀取出來。
    FAT表中的表項位寬與FAT類型有關,例如,FAT12文件系統的表項位寬就是12bit、FAT16文件系統的表項位寬就是16bit、FAT32文件系統的表項位寬就是32bit。當文件的體積增大時,其所需的磁盤存儲空間也會增加,隨時間的推移,文件系統將無法保證文件中的數據存儲在連續的磁盤扇區內,文件往往被分成若干個片段。借助FAT表項,可將這些不連續的文件片段按簇號鏈接起來,這個鏈接原理與鏈表極為相似。下面是對FAT表項取值的說明:

    FAT項 實例值 描述
    0 0xFF0 磁盤標志字,低字節與BPB_Media數值保持一致
    1 0xFFF 第一個簇已經被占用
    0x000:可用簇
    0x002~0xFEF:已用簇標識下一個簇的簇號
    0xFF0 ~ 0xFF6:保留簇
    0xFF7:壞簇
    0xFF8~0xFFF:文件的最后一個簇
    

    在編寫程序的時候我們直接跳過FAT[0]和FAT[1]即可,並不使用它們,不必要理會其中的值。

  • 根目錄和數據區
    從本質上講,根目錄和數據區都保存着與文件相關的數據,只不過根目錄只能保存目錄項信息,而數據區不但可以保存目錄項信息,還可以保存文件內的數據。
    此處所提及的目錄項是由一個32B組成的結構體,它既可以 表示成一個目錄,又可以表示成一個文件。

    名稱 偏移 長度 描述
    DIR_Name 0x00 11 文件名8B,擴展名3B
    DIR_Attr 0x0B 1 文件屬性
    保留 0x0C 10 保留位
    DIR_WrtTime 0x16 2 最后一次寫入時間
    DIR_WrtDate 0x18 2 最后一次寫入日期
    DIR_FstClus 0x1A 2 起始簇號
    DIR_FileSize 0x1C 4 文件大小

    注: 對於DIR_FstClus字段必須特別注意,它描述了文件在磁盤中存放的具體位置。由於 FAT[0] 和 FAT[1] 是保留項,不能用於數據區的簇索引,因此數據區的第一個有效簇號是2,而不是0或1。

程序編寫

FAT12文件系統數據

首先是為虛擬軟盤創建FAT12文件系統引導扇區數據:

org 0x7c00

; 設置棧指針的位置
BaseOfStack equ 0x7c00

; Loader的位置(20位)
; BaseOfLoader << 4 + OffsetOfLoader = 0x10000
BaseOfLoader	equ	0x1000
OffsetOfLoader	equ	0x00

; 根目錄占用的扇區數:(BPB_RootEntCnt * 32 + BPB_BytesPerSec - 1) / BPB_BytesPerSec
; (224 * 32 + 512 - 1) / 512 = 14
RootDirSectors	equ	14

; 根目錄的起始扇區號 = 
; 保留扇區數(BPB_RsvdSecCnt) + FAT表扇區數(BPB_FATSz16) + FAT表份數(BPB_NumFATs) = 1 + 9 * 2 = 19
SectorNumOfRootDirStart	equ	19

; FAT表的起始扇區號,引導扇區的扇區號是0
SectorNumOfFAT1Start	equ	1

; 用於平衡文件(或目錄)的起始簇號與數據區起始簇號的差值。
SectorBalance	equ	17	

;****************************************************
        ; 程序一開始就跳轉到Label_Start的位置處執行
        jmp	short Label_Start
        nop
;****************************************************
        ; 生產廠商名(8 Bits)
        BS_OEMName	db	'Testboot'
        ; 每扇區字節數
        BPB_BytesPerSec	dw	512
        ; 每簇扇區數
        BPB_SecPerClus	db	1
        ; 保留扇區數
        BPB_RsvdSecCnt	dw	1
        ; FAT表的份數
        BPB_NumFATs	db	2
        ; 根目錄可容納的的目錄項數
        BPB_RootEntCnt	dw	224
        ; 總扇區數(因為使用的是1.44M的軟盤)
        BPB_TotSec16	dw	2880
        ; 介質描述符
        BPB_Media	db	0xf0
        ; 每FAT扇區數
        BPB_FATSz16	dw	9
        ; 每磁道扇區數
        BPB_SecPerTrk	dw	18
        ; 磁頭數
        BPB_NumHeads	dw	2
        ; 隱藏扇區數
        BPB_HiddSec	dd	0
        ; 如果BPB_FATSz16=0,那么這個值記錄扇區數
        BPB_TotSec32	dd	0
        ; int 0x13的驅動器號
        BS_DrvNum	db	0
        ; 未使用(不用管)
        BS_Reserved1	db	0
        ; 擴展引導標記
        BS_BootSig	db	0x29
        ; 卷序列號
        BS_VolID	dd	0
        ; 卷標(11 Bits)
        BS_VolLab	db	'boot loader'
        ; 文件系統類型(8 Bits)
        BS_FileSysType	db	'FAT12   '

這段代碼是在我們的程序一開始就定義的,但是程序是從上往下順序執行的,代碼中有想當一部分是數據,並不是代碼,所以我們一開始就需要使用無條件跳轉指令jmp short Label_Start,跳轉到Label_Start標簽處開始執行代碼。

讀取磁盤扇區模塊(函數)

有了FAT12文件系統引導扇區的數據,那么還要一個軟盤的讀取功能模塊(也可以理解為函數),該模塊的作用就是讀取軟盤,每次讀取一個扇區:

;==============================
; 模塊(可以理解為C語言中的函數)
; 從軟驅中讀取扇區
; 參數說明:
;       AX = 待讀取的磁盤起始扇區號
;       CL= 讀入的扇區數量
;       EX : BX => 數據緩沖區起始地址
Func_ReadOneSector:
	
	push	bp
	mov	bp,	sp
	sub	esp,	2
	mov	byte	[bp - 2],	cl
	push	bx
	mov	bl,	[BPB_SecPerTrk]
	div	bl
	inc	ah
	mov	cl,	ah
	mov	dh,	al
	shr	al,	1
	mov	ch,	al
	and	dh,	1
	pop	bx
	mov	dl,	[BS_DrvNum]
Label_Go_On_Reading:
	mov	ah,	2
	mov	al,	byte	[bp - 2]
	int	13h
	jc	Label_Go_On_Reading
	add	esp,	2
	pop	bp
	ret
;==============================

同樣地,讀取扇區,也借助了BIOS的 0x13號中斷,功能號為 AH=0x02 ,該中斷服務各寄存器參數說明如下:

  • INT 0x13 AH = 0x02: 讀取磁盤扇區
    • AL = 讀入的扇區數(非0)
    • CH = 磁道號(柱面號)的低8位
    • CL = 扇區號1~63(bit 0~5),磁道號(柱面號)的高兩位(bit 6~7,只對硬盤有效)
    • DH = 磁頭號
    • DL=驅動器號(如果是硬盤驅動器,bit 7必須被置位)
    • ES : BX => 數據緩沖區
    • 出口參數:CF=0——操作成功,AH=00H,AL=傳輸的扇區數,否則,AH=狀態代碼(具體可以自行百度或Google)

模塊Func_ReadOneSector僅僅是對BIOS的0x13號中斷0x02號功能的封裝,以簡化磁盤讀取扇區的的操作過程,該模塊是需要參數的,參數說明如下:

  • AX = 待讀取的磁盤起始扇區號
  • CL= 讀入的扇區數量
  • EX : BX => 數據緩沖區起始地址

需要說明的是,模塊Func_ReadOneSector的參數中傳入的磁盤扇區號是 LBA(Logical Block Address,邏輯塊尋址) 格式的,而BIOS的0x13號中斷0x02號功能只能接受 CHS(Cylinder/Head/Sector,柱面/磁頭/扇區) 格式的磁盤扇區號,那么就需要將LBA格式的轉換為CHS格式的,通過以下公式,便可以轉換:

\[LBA扇區號 \div 每磁道扇區數 \begin{cases} 商Q → \begin{cases} 柱面號 = Q >> 1,\\ 磁頭號 = Q \& 1,\\ \end{cases}\\ 余數R → 起始扇區號 = R + 1,\\ \end{cases} \]

  1. 模塊Func_ReadOneSector一開始,先保存棧幀寄存器(bp)和棧寄存器(sp)中的數值(bp存儲在棧中,sp保存在bp寄存器中)
  2. 然后從棧中開辟兩個字節的存儲空間,即是sp向下移動兩個字節(sub esp, 2)。然后將CL中的內容(1個字節,參數,讀入扇區數)存入棧中,因為接下來要使用到CL寄存器。注: CL中的內容是一個字節,而棧項要對齊,所以要要用兩個字節保存一個字節的內容,即使浪費了一個字節。
  3. 然后就是把BX的內容也保存到棧中,因為接下來要用到BX
  4. 使用AX寄存器(待讀取的磁盤起始扇區號)除以BL寄存器(每磁道扇區數),計算出目標磁道號(商:AL寄存器)和目標磁道內的起始扇區號(余數:AH寄存器),又因為磁道內的起始扇區號是從1開始計數的,故將此余數值加1(inc ah; mov cl, ah)緊接着按照以上公式計算出磁道號(即是柱面號)、磁頭號。
  5. 恢復BX
  6. 獲取驅動器號BS_DrvNum
  7. 設置0x13中斷功能號AH=0x02,設置讀入扇區數mov al, byte [bp - 2](我們之前把參數CL讀入扇區數保存在了棧中,所以現在用bp去索引它)。
  8. 然后調用0x13號中斷。
  9. 判斷是否讀取成功(即是若CF位為0,則成功),否則(CF=1)回到步驟7

LOADER.BIN文件搜索

當我們實現了讀取扇區的功能后,我們就可以實現文件搜索功能了:

; ================查找文件loader.bin================
    ;保存根目錄的起始扇區號([SectorNo]是一個臨時變量地址)
    ; 賦值為:SectorNumOfRootDirStart(根目錄的起始扇區號)
    ; 初始值為:19(前面已算出)
    mov	word	[SectorNo],	SectorNumOfRootDirStart

Lable_Search_In_Root_Dir_Begin:

    ; RootDirSizeForLoop也是一個臨時變量,值是:RootDirSectors(14)
    ; 判斷是否為0,如果是提示找不到loader.bin
    cmp	word	[RootDirSizeForLoop],	0
    jz	Label_No_LoaderBin
    ; 查找的扇區減1
    dec	word	[RootDirSizeForLoop]

    ;設置讀扇區模塊的參數 EX:BX、AX、CL
    ; 對應緩沖區起始、待讀取的磁盤起始扇區號、讀入扇區數量
    mov	ax,	00h
    mov	es,	ax
    mov	bx,	8000h
    mov	ax,	[SectorNo]
    mov	cl,	1
    ; 調用讀扇區模塊
    call	Func_ReadOneSector
    ; 開始查找loader.bin
    mov	si,	LoaderFileName  ;文件名源地址
    mov	di,	8000h           ;文件名目標地址
    cld     ;DF復位=0,往高地址遞進
    ; dx記錄每個扇區可容納的目錄項目個數
    mov	dx,	10h
	
Label_Search_For_LoaderBin:

    ;如果dx為0,說明比較完了,開始讀取下一個根目錄扇區
    cmp	dx,	0
    jz	Label_Goto_Next_Sector_In_Root_Dir
    
    ;否則dx減1(目錄項減1)
    dec	dx
    ; cx保存目錄項文件名長度 11 bits
    mov	cx,	11

Label_Cmp_FileName:

    ; 判斷文件名是否比較完
    ; 即是cx是否為0,能比較完的話,說明已經找到了
    cmp	cx,	0
    jz	Label_FileName_Found

    ;否則沒有比較完,cx -= 1,開始比較下一個字符
    dec	cx
    ; 從DS:SI讀取一個字節到寄存器AL,然后SI += 1(取決於DF)
    lodsb
    ; 比較目標字串中對應的字符與al中的字符,看是否相等
    cmp	al,	byte	[es:di]
    ; 如果相等,繼續比較下一個字符(跳轉到Label_Go_On)
    jz	Label_Go_On
    ; 如果不相等,就下一個文件項
    jmp	Label_Different

Label_Go_On:
	
    ; 目標字串指針加一,然后繼續比較
    inc	di
    jmp	Label_Cmp_FileName

Label_Different:

    ; di的低5位清零,
    and	di,	0ffe0h
    ; di += 32,表示指向下一個目錄項
    add	di,	20h
    ; si指向LoaderFileName的起始地址
    mov	si,	LoaderFileName
    ; 繼續比較
    jmp	Label_Search_For_LoaderBin

Label_Goto_Next_Sector_In_Root_Dir:
	
    ; 在當前扇區找不到loader.bin
    ; 那么就在下一個扇區找,扇區號+1:[SectorNo]+1
    add	word	[SectorNo],	1
    jmp	Lable_Search_In_Root_Dir_Begin

注意: FAT12文件系統的文件名是不分大小寫的,即是將小寫字母命名的文件賦值到FAT12文件系統內,文件系統也會為其創建大寫字母的文件名和目錄項。而小寫字母文件名只作為其顯示名,而真正的數據內容皆保存在對應的大寫字母目錄項。所以這里應該搜索大寫字母的文件名字符串。

找不到LOADER.BIN文件

當上面那一段程序的指令cmp word [RootDirSizeForLoop], 0判斷發現[RootDirSizeForLoop]中的數據為0的時候,說明沒有找到LOADER.BIN文件,那么就提示找不到LOADER.BIN。

; ========= 打印屏幕信息 : ERROR:No LOADER Found =========

Label_No_LoaderBin:

        mov	ax,	1301h
        mov	bx,	008ch
        mov	dx,	0100h
        mov	cx,	21
        push	ax
        mov	ax,	ds
        mov	es,	ax
        pop	ax
        mov	bp,	NoLoaderMessage
        int	10h
        jmp	$
; =======================================================

當boot程序找不到LOADER.BIN文件會有如下所示的結果:

FAT表項解析

當找到loader.bin文件后,便可以根據FAT表項提供的簇號順序依次加載扇區數據到內存中,這個過程會涉及FAT表項的解析工作:

;==============================
; 模塊(可以理解為C語言中的函數)
; 根據當前FAT表項索引出下一個FAT表項
; 參數說明:
;       AX = FAT表項號(輸入/輸出參數)
Func_GetFATEntry:

    push	es
    push	bx
    push	ax
    mov	ax,	00
    mov	es,	ax
    pop	ax
    ; Odd為奇偶標志變量(奇數1,偶數0)
    mov	byte	[Odd],	0
    mov	bx,	3
    mul	bx
    mov	bx,	2
    div	bx
    ; 判斷余數的奇偶性
    cmp	dx,	0
    ; 如果為偶數,則直接跳轉
    jz	Label_Even
    mov	byte	[Odd],	1

Label_Even:

    ; 讀取FAT表扇區,總共兩個扇區
    xor	dx,	dx
    mov	bx,	[BPB_BytesPerSec]
    div	bx
    push	dx
    mov	bx,	8000h
    add	ax,	SectorNumOfFAT1Start
    mov	cl,	2
    call	Func_ReadOneSector

    pop	dx
    add	bx,	dx
    mov	ax,	[es:bx]
    cmp	byte	[Odd],	1
    jnz	Label_Even_2
    shr	ax,	4

Label_Even_2:
    and	ax,	0fffh
    pop	bx
    pop	es
    ret
;==============================
  • 該模塊需要的參數: AX = FAT表項號(輸入/輸出參數)

在這段程序中,首先把FAT表項號(AX)保存,並將奇偶標志變量置0,因為每個FAT表項占1.5B,並不是偶數對齊,所以就將FAT表項乘以3再除以2(也就是擴大1.5倍),然后判斷余數的奇偶性並保存在變量[Odd]中(奇數為1,偶數為0),再將計算結果除以每扇區字節數,商即為FAT表項的偏移扇區號,余數為FAT表項在扇區中的位置。然后通過Func_ReadOneSector模塊,連續讀入兩個扇區,這樣做的目的是,為了解決FAT表項橫跨兩個扇區的問題,最后根據奇偶標志變量[Odd]處理奇偶項錯位問題,即是奇數項向右移動4位。當然如果覺得麻煩的話,可以將FAT12文件系統換成FAT16文件系統。

從FAT12文件系統中加載loader.bin到內存

在完成Func_ReadOneSectorFunc_GetFATEntry模塊后,就可以借助這兩個模塊把loader.bin文件的數據從軟盤扇區讀取到指定地址中:

; =======================================================
; 在根目錄中找到名為loader.bin的文件了
Label_FileName_Found:
        ;如果找到了的話,當前es:di指向的就是目標表項
        ; AX保存根目錄占用扇區數
        mov	ax,	RootDirSectors
        ; 定位至表項起始簇號的位置(偏移0x1a)
        and	di,	0ffe0h
        add	di,	01ah
        ; 起始簇號保存到cx
        mov	cx,	word	[es:di]
        push	cx
        ; 計算數據區起始扇區號
        ; 數據區起始扇區號=根目錄起始扇區號+根目錄所占扇區數-2
        add	cx,	ax
        ; SectorBalance = 根目錄起始扇區號 - 2
        add	cx,	SectorBalance
        ; 配置loader的加載位置 EX:BX
        mov	ax,	BaseOfLoader
        mov	es,	ax
        mov	bx,	OffsetOfLoader
        ; ax = loader起始扇區號
        mov	ax,	cx

Label_Go_On_Loading_File:
        ; 顯示字符 '.'
        push	ax
        push	bx
        mov	ah,	0eh
        mov	al,	'.'
        mov	bl,	0fh
        int	10h
        pop	bx
        pop	ax

        ; 讀入loader
        ; 每讀入一個扇區就通過Func_GetFATEntry模塊獲取下一個FAT表項
        ; 然后繼續讀入數據
        mov	cl,	1
        call	Func_ReadOneSector
        ; 恢復簇號
        pop	ax
        ; 獲取下一個簇號
        call	Func_GetFATEntry
        ; 查看是否已經讀完了
        cmp	ax,	0fffh
        jz	Label_File_Loaded
        ; 否則保存當前獲取到簇號
        push	ax
        ; 計算loader文件的下一個簇的位置
        mov	dx,	RootDirSectors
        add	ax,	dx
        add	ax,	SectorBalance
        add	bx,	[BPB_BytesPerSec]
        ; 繼續讀
        jmp	Label_Go_On_Loading_File

Label_File_Loaded:
	
        jmp $
; =======================================================

Label_FileName_Found模塊中,程序首先會取得目錄DIR_FstClus字段的數值,並且通過配置ES:BX來指定loader程序在內存中的加載位置,再通過loader.bin程序的起始簇號計算出與其對應的扇區號。然后每讀一個扇區前就答應一個字符'.',每讀入一個扇區的數據就通過Func_GetFATEntry模塊獲取下一個FAT表項,直到Func_GetFATEntry返回0xfff為止,當全部讀完之后就跳轉到Label_File_Loaded處,准備執行loader.bin程序。

這段代碼中使用了BIOS中斷服務程序INT 0x10AH=0x0e號功能,它能在屏幕上顯示一個字符詳細的寄存器參數如下:

  • INT 0x10; AH=0x0e功能: 在屏幕上顯示一個字符。
    • AL=待顯示字符
    • BL=前景色

因為並未進入Loader程序的開發,所以就讓程序死循環在Label_File_Loaded這里。

剩下的部分

代碼剩下的部分就是臨時變量和日志信息字符串了:

; ==================臨時變量==================

RootDirSizeForLoop	dw	RootDirSectors
SectorNo		dw	0
Odd			db	0

; ==================打印信息==================

StartBootMessage:	db	"Start Boot"
NoLoaderMessage:	db	"ERROR:No LOADER Found"
LoaderFileName:		db	"LOADER  BIN",0  ; loader文件名

注: NASM編譯器中的單引號和雙引號作用相同,並非C語言規定的雙引號會在結尾添加字符'\0',在NASM中必須自行添加。

再有就是,文件需要填滿512個字節(一個扇區大小),和末尾的0xaa55

; 扇區剩下的字節全部填滿0(除了最后兩個字節)
    times 510 - ($ - $$) db 0
    dw 0xaa55

總體代碼

所以總的代碼看起來應該是這樣的:

; File name: boot.asm
; org 用於指定程序的起始地址
; 如果不使用org指定程序的起始地址
; 那么編譯器會把0x0000作為程序的起始地址。
org 0x7c00

; 設置棧指針的位置
BaseOfStack equ 0x7c00

; Loader的位置(20位)
; BaseOfLoader << 4 + OffsetOfLoader = 0x10000
BaseOfLoader	equ	0x1000
OffsetOfLoader	equ	0x00

; 根目錄占用的扇區數:(BPB_RootEntCnt * 32 + BPB_BytesPerSec - 1) / BPB_BytesPerSec
; (224 * 32 + 512 - 1) / 512 = 14
RootDirSectors	equ	14
; 根目錄的起始扇區號 = 
; 保留扇區數(BPB_RsvdSecCnt) + FAT表扇區數(BPB_FATSz16) + FAT表份數(BPB_NumFATs) = 1 + 9 * 2 = 19
SectorNumOfRootDirStart	equ	19
; FAT表的起始扇區號,引導扇區的扇區號是0
SectorNumOfFAT1Start	equ	1
; 用於平衡文件(或目錄)的起始簇號與數據區起始簇號的差值。
SectorBalance	equ	17	

;****************************************************
        ; 真正的代碼在這開始,前面的都相當於宏定義
        ; 程序一開始就跳轉到Label_Start的位置處執行
        jmp	short Label_Start
        nop
;****************************************************
        ; 生產廠商名(8 Bits)
        BS_OEMName	db	'Testboot'
        ; 每扇區字節數
        BPB_BytesPerSec	dw	512
        ; 每簇扇區數
        BPB_SecPerClus	db	1
        ; 保留扇區數
        BPB_RsvdSecCnt	dw	1
        ; FAT表的份數
        BPB_NumFATs	db	2
        ; 根目錄可容納的的目錄項數
        BPB_RootEntCnt	dw	224
        ; 總扇區數(因為使用的是1.44M的軟盤)
        BPB_TotSec16	dw	2880
        ; 介質描述符
        BPB_Media	db	0xf0
        ; 每FAT扇區數
        BPB_FATSz16	dw	9
        ; 每磁道扇區數
        BPB_SecPerTrk	dw	18
        ; 磁頭數
        BPB_NumHeads	dw	2
        ; 隱藏扇區數
        BPB_HiddSec	dd	0
        ; 如果BPB_FATSz16=0,那么這個值記錄扇區數
        BPB_TotSec32	dd	0
        ; int 0x13的驅動器號
        BS_DrvNum	db	0
        ; 未使用(不用管)
        BS_Reserved1	db	0
        ; 擴展引導標記
        BS_BootSig	db	0x29
        ; 卷序列號
        BS_VolID	dd	0
        ; 卷標(11 Bits)
        BS_VolLab	db	'boot loader'
        ; 文件系統類型(8 Bits)
        BS_FileSysType	db	'FAT12   '

Label_Start:
        ; 初始化一些段寄存器和棧指針
        mov ax, cs
        mov ds, ax
        mov es, ax
        mov ss, ax
        mov sp, BaseOfStack

        ; 清屏
        mov ax, 0600h
        mov bx, 0700h
        mov cx, 0
        mov dx, 184fh
        int 10h     ; 調用0x10號中斷

        ; 設置光標位置
        mov ax, 0200h
        mov bx, 0000h
        mov dx, 0000h
        int 10h
    
        ; 打印字符:Start Bootint......
        mov ax, 1301h
        mov bx, 000fh
        mov dx, 0000h
        mov cx, 10
        push ax
        mov ax, ds
        mov es, ax
        pop ax
        mov bp, StartBootMessage
        int 10h

        ; 初始化軟盤驅動器
        xor ah, ah
        xor dl, dl
        int 13h

; ================查找文件loader.bin================
        ;保存根目錄的起始扇區號([SectorNo]是一個臨時變量地址)
        ; 賦值為:SectorNumOfRootDirStart(根目錄的起始扇區號)
        ; 初始值為:19(前面已算出)
        mov	word	[SectorNo],	SectorNumOfRootDirStart

Lable_Search_In_Root_Dir_Begin:

        ; RootDirSizeForLoop也是一個臨時變量,值是:RootDirSectors(14)
        ; 判斷是否為0,如果是提示找不到loader.bin
        cmp	word	[RootDirSizeForLoop],	0
        jz	Label_No_LoaderBin
        ; 查找的扇區減1
        dec	word	[RootDirSizeForLoop]
    
        ;設置讀扇區模塊的參數 EX:BX、AX、CL
        ; 對應緩沖區起始、待讀取的磁盤起始扇區號、讀入扇區數量
        mov	ax,	00h
        mov	es,	ax
        mov	bx,	8000h
        mov	ax,	[SectorNo]
        mov	cl,	1
        ; 調用讀扇區模塊
        call	Func_ReadOneSector
        ; 開始查找loader.bin
        mov	si,	LoaderFileName  ;文件名源地址
        mov	di,	8000h           ;文件名目標地址
        cld     ;DF復位=0,往高地址遞進
        ; dx記錄每個扇區可容納的目錄項目個數
        mov	dx,	10h
	
Label_Search_For_LoaderBin:

        ;如果dx為0,說明比較完了,開始讀取下一個根目錄扇區
        cmp	dx,	0
        jz	Label_Goto_Next_Sector_In_Root_Dir

        ;否則dx減1(目錄項減1)
        dec	dx
        ; cx保存目錄項文件名長度 11 bits
        mov	cx,	11

Label_Cmp_FileName:

        ; 判斷文件名是否比較完
        ; 即是cx是否為0,能比較完的話,說明已經找到了
        cmp	cx,	0
        jz	Label_FileName_Found

        ;否則沒有比較完,cx -= 1,開始比較下一個字符
        dec	cx
        ; 從DS:SI讀取一個字節到寄存器AL,然后SI += 1(取決於DF)
        lodsb
        ; 比較目標字串中對應的字符與al中的字符,看是否相等
        cmp	al,	byte	[es:di]
        ; 如果相等,繼續比較下一個字符(跳轉到Label_Go_On)
        jz	Label_Go_On
        ; 如果不相等,就下一個文件項
        jmp	Label_Different

Label_Go_On:
	
  ; 目標字串指針加一,然后繼續比較
	inc	di
	jmp	Label_Cmp_FileName

Label_Different:

        ; di的低5位清零,
        and	di,	0ffe0h
        ; di += 32,表示指向下一個目錄項
        add	di,	20h
        ; si指向LoaderFileName的起始地址
        mov	si,	LoaderFileName
        ; 繼續比較
        jmp	Label_Search_For_LoaderBin

Label_Goto_Next_Sector_In_Root_Dir:
	
        ; 在當前扇區找不到loader.bin
        ; 那么就在下一個扇區找,扇區號+1:[SectorNo]+1
        add	word	[SectorNo],	1
        jmp	Lable_Search_In_Root_Dir_Begin


; ========= 打印屏幕信息 : ERROR:No LOADER Found =========

Label_No_LoaderBin:

        mov	ax,	1301h
        mov	bx,	008ch
        mov	dx,	0100h
        mov	cx,	21
        push	ax
        mov	ax,	ds
        mov	es,	ax
        pop	ax
        mov	bp,	NoLoaderMessage
        int	10h
        jmp	$
; =======================================================

; =======================================================
; 在根目錄中找到名為loader.bin的文件了
Label_FileName_Found:
        ;如果找到了的話,當前es:di指向的就是目標表項
        ; AX保存根目錄占用扇區數
        mov	ax,	RootDirSectors
        ; 定位至表項起始簇號的位置(偏移0x1a)
        and	di,	0ffe0h
        add	di,	01ah
        ; 起始簇號保存到cx
        mov	cx,	word	[es:di]
        push	cx
        ; 計算數據區起始扇區號
        ; 數據區起始扇區號=根目錄起始扇區號+根目錄所占扇區數-2
        add	cx,	ax
        ; SectorBalance = 根目錄起始扇區號 - 2
        add	cx,	SectorBalance
        ; 配置loader的加載位置 EX:BX
        mov	ax,	BaseOfLoader
        mov	es,	ax
        mov	bx,	OffsetOfLoader
        ; ax = loader起始扇區號
        mov	ax,	cx

Label_Go_On_Loading_File:
        ; 顯示字符 '.'
        push	ax
        push	bx
        mov	ah,	0eh
        mov	al,	'.'
        mov	bl,	0fh
        int	10h
        pop	bx
        pop	ax

        ; 讀入loader
        ; 每讀入一個扇區就通過Func_GetFATEntry模塊獲取下一個FAT表項
        ; 然后繼續讀入數據
        mov	cl,	1
        call	Func_ReadOneSector
        ; 恢復簇號
        pop	ax
        ; 獲取下一個簇號
        call	Func_GetFATEntry
        ; 查看是否已經讀完了
        cmp	ax,	0fffh
        jz	Label_File_Loaded
        ; 否則保存當前獲取到簇號
        push	ax
        ; 計算loader文件的下一個簇的位置
        mov	dx,	RootDirSectors
        add	ax,	dx
        add	ax,	SectorBalance
        add	bx,	[BPB_BytesPerSec]
        ; 繼續讀
        jmp	Label_Go_On_Loading_File

Label_File_Loaded:
	
    jmp $
; =======================================================

;==============================
; 模塊(可以理解為C語言中的函數)
; 從軟驅中讀取扇區
; 參數說明:
;       AX = 待讀取的磁盤起始扇區號
;       CL= 讀入的扇區數量
;       EX : BX => 數據緩沖區起始地址
Func_ReadOneSector:
	
        push	bp
        mov	bp,	sp
        sub	esp,	2
        mov	byte	[bp - 2],	cl
        push	bx
        mov	bl,	[BPB_SecPerTrk]
        div	bl
        inc	ah
        mov	cl,	ah
        mov	dh,	al
        shr	al,	1
        mov	ch,	al
        and	dh,	1
        pop	bx
        mov	dl,	[BS_DrvNum]
Label_Go_On_Reading:
        mov	ah,	2
        mov	al,	byte	[bp - 2]
        int	13h
        jc	Label_Go_On_Reading
        add	esp,	2
        pop	bp
        ret
;==============================

;==============================
; 模塊(可以理解為C語言中的函數)
; 根據當前FAT表項索引出下一個FAT表項
; 參數說明:
;       AX = FAT表項號(輸入/輸出參數)
Func_GetFATEntry:

        push	es
        push	bx
        push	ax
        mov	ax,	00
        mov	es,	ax
        pop	ax
        ; Odd為奇偶標志變量(奇數1,偶數0)
        mov	byte	[Odd],	0
        mov	bx,	3
        mul	bx
        mov	bx,	2
        div	bx
        ; 判斷余數的奇偶性
        cmp	dx,	0
        ; 如果為偶數,則直接跳轉
        jz	Label_Even
        mov	byte	[Odd],	1

Label_Even:

        ; 讀取FAT表扇區,總共兩個扇區
        xor	dx,	dx
        mov	bx,	[BPB_BytesPerSec]
        div	bx
        push	dx
        mov	bx,	8000h
        add	ax,	SectorNumOfFAT1Start
        mov	cl,	2
        call	Func_ReadOneSector

        pop	dx
        add	bx,	dx
        mov	ax,	[es:bx]
        cmp	byte	[Odd],	1
        jnz	Label_Even_2
        shr	ax,	4

Label_Even_2:
        and	ax,	0fffh
        pop	bx
        pop	es
        ret
;==============================


; ==================臨時變量==================

RootDirSizeForLoop	dw	RootDirSectors
SectorNo		dw	0
Odd			db	0

; ==================打印信息==================

StartBootMessage:	db	"Start Boot"
NoLoaderMessage:	db	"ERROR:No LOADER Found"
LoaderFileName:		db	"LOADER  BIN",0  ; loader文件名


; 扇區剩下的字節全部填滿0(除了最后兩個字節)
        times 510 - ($ - $$) db 0
        dw 0xaa55

編譯運行:

(需要配置環境,之前那篇文章已經配置過了,這里不再重復)
大概的執行步驟是這樣的:

  1. 把上面的代碼保存為boot.asm

  2. 使用NAME編譯它:

> nasm ./boot.asm -o boot.bin
  1. 使用dd把內容寫進boot.img中去:
> dd if=boot.bin of=boot.img bs=512 count=1

使用VSCode的Hexdump擴展查看是否已經寫入進去:

  1. 啟動虛擬機,因為之前已經配置過環境,所以Bochs虛擬機在彈出窗口后,直接點Start按鈕或者回車就行了。
> bochs -f .\bochsrc.bxrc
  1. 查看結果:

因為還沒有編寫loader.bin所以,結果會提示如上圖所示(紅色部分應該是閃爍的,但是由於是圖片,無法展示)。

那么對於讀取Loader文件的功能模塊要等到編寫好Loader文件才能再進行測試了。




  • 參考文獻:
    [1] 田宇.一個64位操作系統的設計與實現[M].北京:人民郵電出版社,2018.5.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM