《Orange‘s 一个操作系统的实现》第四章

FAT12

使用 FreeDOS,启动 Bochs 并且格式化 B 盘后,添加如下文件即目录:

  • RIVER.TXT

    riverriverriver
    
  • FLOWER.TXT

    flowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflower
    
  • TREE.TXT

    treetreetree
    

创建 house 目录,并添加如下文件:

  • CAT.TXT

    catcatcat
    
  • DOG.TXT

    dogdogdog
    

将文件挂载到软盘B

mkdir /mnt/floppy/
mount -o loop a.img /mnt/floppy/
cp RIVER.TXT /mnt/floppy/
cp FLOWER.TXT /mnt/floppy/
cp TREE.TXT /mnt/floppy/
umount /mnt/floppy/

由于根目录区从第 19 扇区开始,每个扇区 512 字节,所以第一个字节位偏移 19 ∗ 512 = 9728 = 0 x 2600 19*512=9728=0x2600 19512=9728=0x2600 处。使用 xxd 二进制查看器看看 a.img 偏移 0x2600 处的信息。

xxd -u -a -g 1 -c 16 -s +0x2600 -l 512 a.img

在这里插入图片描述

接下来查看数据区的信息,在此之前由于根目录区是不固定的,因此需要进行计算,计算公式如下:
R o o t D i r S e c t o r s = ⌊ ( B P B _ R o o t E n t C n t × 32 ) + ( B P B _ B y t s P e r S e c − 1 ) B P B _ B y t s P e r S e c ⌋ RootDirSectors = \lfloor\frac{(BPB\_RootEntCnt \times 32) + (BPB\_BytsPerSec - 1)}{BPB\_BytsPerSec} \rfloor RootDirSectors=BPB_BytsPerSec(BPB_RootEntCnt×32)+(BPB_BytsPerSec1)

数据区开始扇区号 = 根目录区开始扇区号 + R o o t D i r S e c t o r s 数据区开始扇区号 = 根目录区开始扇区号 + RootDirSectors 数据区开始扇区号=根目录区开始扇区号+RootDirSectors

偏移量 = 512 × 扇区数 ( 数据区开始扇区号 ) 偏移量 = 512 \times 扇区数(数据区开始扇区号) 偏移量=512×扇区数(数据区开始扇区号)

最终本案例所得结果: ( ( 224 ∗ 32 ) + ( 512 − 1 ) ) / 512 = 14 + 19 = 33 × 512 = 0 x 4200 ((224 * 32) + (512 - 1)) / 512 = 14 + 19 = 33 \times 512 = 0x4200 ((22432)+(5121))/512=14+19=33×512=0x4200

xxd -u -a -g 1 -c 16 -s +0x4400 -l 0x11 a.img

在这里插入图片描述

Tips:这里我貌似足足多偏移了一个扇区(0x200)。

查看 FAT 表:

xxd -u -a -g 1 -c 16 -s +0x200 -l 512 a.img

在这里插入图片描述

FAT表的好处:对于小于 512 字节的文件来说,FAT表用处不大,若大于 512 字节,则需要 FAT 表来找到所有的簇。

FAT 表有两个,分别是 FAT1 和 FAT2,FAT2 可以看成 FAT1 的备份,它们通常是一样的。

12 位称为一个 FAT 项,代表一个簇。第 0 个和第 1 个 FAT 项始终不使用,从第 2 个 FAT 项开始表示数据区的每一个簇。

若 FAT 值大于或等于 0xFF8,则表示当前簇已经是本文件的最后一个簇。

若 FAT 值为 0xFF7,则表示它是个坏簇。

RIVER.TXT 的开始簇号是 2,对应 FAT 表中的值为 0xFF0,计算过程为:2 × 1.5 = 3,偏移 0x0003 个字节,取一个字的数据为 FFF0,取低 12 位为 0xFF0。

注意:一个 FAT 项可能跨越两个扇区,编码中需要注意,在本书代码中一次总是读取两个扇区。

FAT 计算方式:

  1. 簇号 × 1.5 = o f f s e t 簇号 \times 1.5 = offset 簇号×1.5=offset

  2. 取偏移 offset 的一个字的数据

  3. 若簇号为奇数,则结果取高 12 位。
    若簇号为偶数,则结果取低 12 位。

编写引导驱动

DOS 可以识别的引导盘

引导扇区需要 BPB 等头信息才能被微软识别:

%ifdef _BOOT_DEBUG_
        org 0100h ; 调试状态,做成 .com 文件,可调式
%else
        org 07c00h ; Boot 状态,BIOS 将把 Boot Sector(引导扇区) 加载到 0:7C00 处并执行
%endif

    jmp short LABEL_START       ; 跳转到代码开始执行的位置
    nop                         ; 不知道干啥用,但是不能少

    ; FAT12 磁盘的头
    BS_OEMName      DB 'ForrestY'   ; OEM String, 必须 8 个字节
    BPB_BytsPerSec  DW 512          ; 每扇区字节数
    BPB_SecPerClus  DB 1            ; 没簇多少扇区
    BPB_RsvdSecCnt  DW 1            ; Boot 记录多少扇区
    BPB_NumFATs     DB 2            ; 共有多少 FAT 表
    BPB_RootEntCnt  DW 224          ; 根目录文件数最大值
    BPB_TotSec16    DW 2880         ; 逻辑扇区总数
    BPB_Media       DB 0xF0         ; 媒体描述符
    BPB_FATSz16     DW 9            ; 每个 FAT 扇区数
    BPB_SecPerTrk   DW 18           ; 每磁道扇区数
    BPB_NumHeads    DW 2            ; 磁头数(面数)
    BPB_HiddSec     DD 0            ; 隐藏扇区数
    BPB_TotSec32    DD 0            ; wTotalSectorCount 为 0 时这个值记录扇区数
    BS_DrvNum       DB 0            ; 中断 13 的驱动器号
    BS_Reserved1    DB 0            ; 未使用
    BS_BootSig      DB 29h          ; 扩展引导标记(29h)
    BS_VolID        DD 0            ; 卷序列号
    BS_VolLab       DB 'OrangeS0.02'; 卷标,必须 11 个字节
    BS_FileSysType  DB 'FAT12   '   ; 文件系统类型,必须 8 个字节

LABEL_START:

times 510 - ($ - $$) db 0
dw 0xaa55

把 boot.bin 写入磁盘引导扇区,此时软盘已经能被 DOS 和 Linux 识别了。

在这里插入图片描述

一个 最简单的 Loader

名为 loader.asm

org 0100h
    mov ax, 0B800h
    mov gs, ax
    mov ah, 0Fh
    mov al, 'L'
    mov [gs:((80 * 0 + 39) * 2)], ax

    jmp $

加载 Loader 载入内存

加入一个文件入内存,需要读取软盘。

在这里插入图片描述

软盘 1.44MB 的由来: 每面有两个磁头(磁头 0 和 1),每面 80 个磁道(磁道号 0~79),每个磁道有 18 个扇区(扇区号 1~18)。计算容量: 2 × 80 × 18 × 512 = 1.44 M B 2 \times 80 \times 18 \times 512 = 1.44MB 2×80×18×512=1.44MB

在这里插入图片描述

编写函数 - ReadSector
;------------------------------------------------------
; 函数名:ReadSector
;------------------------------------------------------
; 功能:从第 ax 个扇区开始,将 cl 个扇区读入 es:bx 中
;------------------------------------------------------
; 参数
;   ax:扇区号
;   cl:要读取的扇区数
;------------------------------------------------------
; 返回:es:bx 存放着读取扇区的数据
;------------------------------------------------------
ReadSector:
    push    bp
    mov     bp, sp
    sub     esp, 2 ; 开辟两个字节的堆栈空间,保存要读的扇区数:byte [bp - 2]

    mov     byte[bp - 2], cl    ;
    push    bx                  ; 保存 bx
    mov     bl, [BPB_SecPerTrk] ; bl <- 每磁道扇区数
    div     bl                  ; AL(商-Q), AH(余-R)
    inc     ah                  ; R++
    mov     cl, ah              ; cl <- 起始扇区号
    mov     dh, al              ; dh <- 磁头号
    shr     al, 1               ; 柱面号 = Q >> 1
    mov     ch, al              ; ch <- 柱面号
    and     dh, 1               ; 磁头号 = Q & 1
    pop     bx                  ; 还原 bx
    ; 至此计算完成 柱面号、起始扇区、磁头号
    mov     dl, [BS_DrvNum]     ; 驱动器号(0 表示 A 盘)
.GoOnReading:
    mov ah, 2                   ; 读
    mov al, byte [bp - 2]       ; 读取 al 个扇区,[bp - 2] 就是一开始的 cl
    int 13h
    jc .GoOnReading             ; 若读取错误,则 CF = 1,此时会不停地读,直到正确为止

    add esp, 2
    pop bp

    ret

栈中的变化:

在这里插入图片描述

寻找 Loader 和加载 Loader
; 在 A 盘的根目录寻找 Loader.bin
    mov     word [wSectorNo], SectorNoOfRootDirectory
LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
    cmp     word [wRootDirSizeForLoop], 0   ; 判断根目录区是否已读完
    jz      LABEL_NO_LOADERBIN              ; 若读完,则表示没有找到 LOADER.BIN
    dec     word [wRootDirSizeForLoop]      ; 根目录占用的扇区数 -1
    mov     ax, BaseOfLoader
    mov     es, ax                          ; es <- BaseOfLoader
    mov     bx, OffsetOfLoader              ; bx <- OffsetOfLoader
    mov     ax, [wSectorNo]                 ; ax <- Root Director 中的某 Sector 号
    mov     cl, 1
    call    ReadSector                      ; 执行后,加载的结束根目录区中第 wSectorNo 号扇区的数据(加载位置:es:bx)

    mov     si, LoaderFileName              ; ds:si -> "LOADER  BIN"
    mov     di, OffsetOfLoader              ; es:di -> BaseOfLoader:0100
    cld
    mov     dx, 10h                         ; 根目录每个扇区的最大文件数(条目) = 512(一个扇区大小) / 32(条目大小) = 16 = 10H
LABEL_SEARCH_FOR_LOADERBIN:                 ; 遍历扇区中的每个条目
    cmp     dx, 0                           ; 循环次数
    jz      LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR ; 若已经读完了一个扇区,就跳转到下一个扇区
    dec     dx                              ; 循环次数 -1
    mov     cx, 11                          ; 文件名称长度
LABEL_CMP_FILENAME:                         ; 比较文件名
    cmp     cx, 0
    jz      LABEL_FILENAME_FOUND            ; 若文件名相等,则表示找到
    dec     cx                              ; 循环次数 -1
    lodsb                                   ; ds:si -> al
    cmp     al, byte [es:di]
    jz      LABEL_GO_ON                     ; 若字符相同,则继续循环
    jmp     LABEL_DIFFERENT                 ; 字符不相同,表示该条目不是我们要找的

LABEL_GO_ON:
    inc     di
    jmp     LABEL_CMP_FILENAME              ; 继续循环

LABEL_DIFFERENT:                            ; 跳转到下一个条目,然后重新开始比较字符串
    and     di, 0FFE0h                      ; di &= E0 是为了让它指向本条目的开头
    add     di, 20h                         ; di += 20h 表示下一个目录条目
    mov     si, LoaderFileName
    jmp     LABEL_SEARCH_FOR_LOADERBIN

LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:         ; 在根目录区内跳转到下一个扇区
    add     word [wSectorNo], 1             ; 要读取的扇区号 +1
    jmp     LABEL_SEARCH_IN_ROOT_DIR_BEGIN  ; 重新去读取这个扇区

LABEL_NO_LOADERBIN:     ; 找不到 LOADER.BIN
    mov     dh, 2       ; 字符串:No LOADER.
    call DispStr        ; 显示字符串

%ifdef _BOOT_DEBUG_
    mov     ax, 4c00h
    int     21h         ; DEBUG 状态下,没有找到 LOADER.BIN 则回到 DOS
%else
    jmp     $           ; 没有找到 LOADER.BIN 则死循环在此处
%endif

LABEL_FILENAME_FOUND:   ; 找到 LOADER.BIN 后,便来到这里。

上面的任务是:遍历根目录区的每一个扇区中的条目,其实就是遍历根目录区中的所有文件,然后与“LOADER BIN” 比较文件名,直到找到一样的文件名为止,表示找到名为 LOADER.BIN 的文件。

LABEL_FILENAME_FOUND:   ; 找到 LOADER.BIN 后,便来到这里。
    mov     ax, RootDirSectors
    and     di, 0FFE0h              ; di &= E0h 表示当前条目的起始位置
    add     di, 01Ah                ; di += 1Ah 表示对应条目的簇号的偏移量
    mov     cx, word [es:di]        ; 得到簇号(FAT 序号)
    push    cx                      ; 保存簇号
    add     cx, ax
    add     cx, DeltaSectorNo       ; cx <- LOADER.BIN 的起始扇区号 = 簇号 + RootDirSectors + 19 - 2
    mov     ax, BaseOfLoader
    mov     es, ax                  ; es <- BaseOfLoader
    mov     bx, OffsetOfLoader      ; bx <- OffsetOfLoader
    mov     ax, cx                  ; ax <- LOADER.BIN 的起始扇区号
LABEL_GOON_LOADING_FILE:
    ; 每读取一个扇区就在 “Booting  ”后面追加一个点,形成这样的效果:Booting  .....
    push    ax
    push    bx
    mov     ah, 0Eh
    mov     al, '.'
    mov     bl, 0Fh
    int     10h
    pop     bx
    pop     ax

    mov     cl, 1
    call    ReadSector              ; 根据 AX(起始扇区号) 读取数据区 CL 个扇区数
    pop     ax                      ; 恢复簇号(FAT 序号)
    call    GetFATEntry             ; 得到下一个 FAT 序号
    cmp     ax, 0FFFh
    jz      LABEL_FILE_LOADED       ; 判断该 FAT 是否为该文件最后一个簇
    push    ax                      ; 保存得到的 FAT

    ; 根据 FAT 计算 LOADER.BIN 文件的下一个扇区数据
    mov     dx, RootDirSectors
    add     ax, dx
    add     ax, DeltaSectorNo

    add     bx, [BPB_BytsPerSec]    ; +512偏移量,作为新的 es:bx 读取扇区写入

    jmp     LABEL_GOON_LOADING_FILE
LABEL_FILE_LOADED:
    mov     dh, 1       ; "Ready."
    call    DispStr     ; 显示字符串

在根目录区找到 LOADER.BIN 条目后,得到该条目的簇号,然后计算 LOADER.BIN 在数据区中的起始扇区号“起始扇区号 = 簇号 + RootDirSectors + 19 - 2”,接着通过 ReadSector 函数读取 1 个扇区的数据,然后继续或许下一个 FAT 序号,直到当前 FAT 为该文件最后一个簇为止。

编写函数 - DispStr
;------------------------------------------------------
; 函数名:DispStr
;------------------------------------------------------
; 功能:显示字符串
;------------------------------------------------------
; 参数
;   dh:字符串编号
;------------------------------------------------------
DispStr:
    mov     ax, MessageLength
    mul     dh
    add     ax, BootMessage

    ; es:bp = 串地址
    mov     bp, ax
    mov     ax, ds
    mov     es, ax

    mov     cx, MessageLength
    mov     ax, 01301h
    mov     bx, 0007h
    mov     dl, 0
    int     10h

    ret
编写函数 - GetFATEntry
;------------------------------------------------------
; 函数名:GetFATEntry
;------------------------------------------------------
; 功能:找到序号为 ax 的扇区在 FAT 中的条目,结果放在 ax 中
;       注意:中间需要读 FAT 的扇区到 es:bx 处,所以函数一开始保存了 es 和 bx
;------------------------------------------------------
; 参数
;   AX:簇号
;------------------------------------------------------
GetFATEntry:
    push    es
    push    bx
    push    ax

    ; 在 BaseOfLoader 后面留出 4k 空间用于存放 FAT 表
    mov     ax, BaseOfLoader
    sub     ax, 0100h
    mov     es, ax

    pop     ax
    mov     byte [bOdd], 0      ; 默认簇号为偶数
    mov     bx, 3
    mul     bx                  ; dx:ax = ax * 3
    mov     bx, 2
    div     bx                  ; dx:ax / 2 ==> ax <- 商, dx <- 余数
    cmp     dx, 0
    jz      LABEL_EVEN          ; 判断簇号是否为偶数
    mov     byte [bOdd], 1      ; 不是,是奇数
LABEL_EVEN:
    ; 现在 ax 中是 FATEntry 在 FAT 中的偏移量
    ; 计算 FATEntry 在哪个扇区中(FAT 占用可能不止一个扇区)
    xor     dx, dx
    mov     bx, [BPB_BytsPerSec]
    div     bx ; dx:ax / BPB_BytsPerSec
               ; ax <- 商(FATEntry 所在扇区相对于 FAT 的扇区号)
               ; dx <- 余数(FATEntry 在扇区内的偏移)
    push    dx
    mov     bx, 0 ; bx <- 0 于是,es:bx = (BaseOfLoader - 100):00
    add     ax, SectorNoOfFAT1 ; 此句之后的 ax 就是 FATEntry 所在的扇区号
    mov     cl, 2
    call    ReadSector ; 读取 FATEntry 所在的扇区,一次读两个,避免边界问题,因为一个 FATEntry 可能跨越两个扇区

    pop     dx
    add     bx, dx
    mov     ax, [es:bx]
    cmp     byte [bOdd], 1
    jnz     LABEL_EVEN_2    ; 判断簇号是否为偶数
    shr     ax, 4           ; 不是,是奇数,所以右移 4 位
LABEL_EVEN_2:
    and     ax, 0FFFh       ; 是偶数,去掉高 4 位
LABEL_GET_FAT_ENRY_OK:
    pop     bx
    pop     es

    ret
跳转到已经加载到内存中的 LOADER.BIN
;*******************************************************************************
    jmp BaseOfLoader:OffsetOfLoader         ; 跳转到已经加载到内存中的 LOADER.BIN
                                            ; 处开始执行 LOADER.BIN 的代码
                                            ; Boot Sector(引导扇区)的使命到此结束
;*******************************************************************************

总结 —— 引导扇区的编写思路

  1. 因为引导扇区需要有 BPB 等头信息才能被微软识别,因此开头需要添加 BPB 等头信息。
  2. 其次是需要将 Loader 载入内存,需要读取软盘,可通过 int 13h 中断来实现,因为需要频繁使用,因此封装函数为 ReadSector
  3. 在根目录区中遍历所有条目,找到与 Loader 文件名相同的条目。
  4. 找到对应条目后,需要通过 FAT 表读取该文件,通过扇区号(簇号)求 FAT 的功能需要封装一个 GetFATEntry 函数。

寻找 Loader 和加载 Loader 的图解

在这里插入图片描述

本章完整代码

boot.asm


%ifdef _BOOT_DEBUG_
        org 0100h  ; 调试状态,做成 .com 文件,可调式
%else
        org 07c00h ; Boot 状态,BIOS 将把 Boot Sector(引导扇区) 加载到 0:7C00 处并执行
%endif

;===============================================================================
%ifdef _BOOT_DEBUG_
    BaseOfStack     equ 0100h  ; DEBUT 状态下的堆栈基地址
%else
    BaseOfStack     equ 07c00h ; 堆栈基地址
%endif

BaseOfLoader    equ 09000h  ; LOADER.BIN 被加载到的位置 —— 段地址
OffsetOfLoader  equ 0100h   ; LOADER.BIN 被加载到的位置 —— 偏移地址

RootDirSectors  equ 14      ; 根目录占用空间
SectorNoOfRootDirectory equ 19  ; Root Director 的第一个扇区号
SectorNoOfFAT1  equ 1       ; FAT1 的第一个扇区号 = BPB_RsvdSecCnt
DeltaSectorNo   equ 17      ; DeltaSectorNo = BPB_RsvdSecCnt + (BPB_NumFATs * FATSz) - 2
                            ; 文件的起始扇区号 = DirEntry 中的起始扇区号 + 根目录占用的扇区数 + DeltaSectorNo
;===============================================================================

    jmp short LABEL_START       ; 跳转到代码开始执行的位置
    nop                         ; 不知道干啥用,但是不能少

    ; FAT12 磁盘的头
    BS_OEMName      DB 'ForrestY'   ; OEM String, 必须 8 个字节
    BPB_BytsPerSec  DW 512          ; 每扇区字节数
    BPB_SecPerClus  DB 1            ; 没簇多少扇区
    BPB_RsvdSecCnt  DW 1            ; Boot 记录多少扇区
    BPB_NumFATs     DB 2            ; 共有多少 FAT 表
    BPB_RootEntCnt  DW 224          ; 根目录文件数最大值
    BPB_TotSec16    DW 2880         ; 逻辑扇区总数
    BPB_Media       DB 0xF0         ; 媒体描述符
    BPB_FATSz16     DW 9            ; 每个 FAT 扇区数
    BPB_SecPerTrk   DW 18           ; 每磁道扇区数
    BPB_NumHeads    DW 2            ; 磁头数(面数)
    BPB_HiddSec     DD 0            ; 隐藏扇区数
    BPB_TotSec32    DD 0            ; wTotalSectorCount 为 0 时这个值记录扇区数
    BS_DrvNum       DB 0            ; 中断 13 的驱动器号
    BS_Reserved1    DB 0            ; 未使用
    BS_BootSig      DB 29h          ; 扩展引导标记(29h)
    BS_VolID        DD 0            ; 卷序列号
    BS_VolLab       DB 'OrangeS0.02'; 卷标,必须 11 个字节
    BS_FileSysType  DB 'FAT12   '   ; 文件系统类型,必须 8 个字节

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       ; 左上角(0, 0)
    mov     dx, 0184fh  ; 右下角(80, 50)
    int     10h

    mov     dh, 0       ; "Booting  "
    call    DispStr     ; 显示字符串

    xor     ah, ah
    xor     dl, dl  ; 软驱复位
    int     13h

; 在 A 盘的根目录寻找 Loader.bin
    mov     word [wSectorNo], SectorNoOfRootDirectory
LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
    cmp     word [wRootDirSizeForLoop], 0   ; 判断根目录区是否已读完
    jz      LABEL_NO_LOADERBIN              ; 若读完,则表示没有找到 LOADER.BIN
    dec     word [wRootDirSizeForLoop]      ; 根目录占用的扇区数 -1
    mov     ax, BaseOfLoader
    mov     es, ax                          ; es <- BaseOfLoader
    mov     bx, OffsetOfLoader              ; bx <- OffsetOfLoader
    mov     ax, [wSectorNo]                 ; ax <- Root Director 中的某 Sector 号
    mov     cl, 1
    call    ReadSector                      ; 执行后,加载的结束根目录区中第 wSectorNo 号扇区的数据(加载位置:es:bx)

    mov     si, LoaderFileName              ; ds:si -> "LOADER  BIN"
    mov     di, OffsetOfLoader              ; es:di -> BaseOfLoader:0100
    cld
    mov     dx, 10h                         ; 根目录每个扇区的最大文件数(条目) = 512(一个扇区大小) / 32(条目大小) = 16 = 10H
LABEL_SEARCH_FOR_LOADERBIN:                 ; 遍历扇区中的每个条目
    cmp     dx, 0                           ; 循环次数
    jz      LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR ; 若已经读完了一个扇区,就跳转到下一个扇区
    dec     dx                              ; 循环次数 -1
    mov     cx, 11                          ; 文件名称长度
LABEL_CMP_FILENAME:                         ; 比较文件名
    cmp     cx, 0
    jz      LABEL_FILENAME_FOUND            ; 若文件名相等,则表示找到
    dec     cx                              ; 循环次数 -1
    lodsb                                   ; ds:si -> al
    cmp     al, byte [es:di]
    jz      LABEL_GO_ON                     ; 若字符相同,则继续循环
    jmp     LABEL_DIFFERENT                 ; 字符不相同,表示该条目不是我们要找的

LABEL_GO_ON:
    inc     di
    jmp     LABEL_CMP_FILENAME              ; 继续循环

LABEL_DIFFERENT:                            ; 跳转到下一个条目,然后重新开始比较字符串
    and     di, 0FFE0h                      ; di &= E0 是为了让它指向本条目的开头
    add     di, 20h                         ; di += 20h 表示下一个目录条目
    mov     si, LoaderFileName
    jmp     LABEL_SEARCH_FOR_LOADERBIN

LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:         ; 在根目录区内跳转到下一个扇区
    add     word [wSectorNo], 1             ; 要读取的扇区号 +1
    jmp     LABEL_SEARCH_IN_ROOT_DIR_BEGIN  ; 重新去读取这个扇区

LABEL_NO_LOADERBIN:     ; 找不到 LOADER.BIN
    mov     dh, 2       ; 字符串:No LOADER.
    call DispStr        ; 显示字符串

%ifdef _BOOT_DEBUG_
    mov     ax, 4c00h
    int     21h         ; DEBUG 状态下,没有找到 LOADER.BIN 则回到 DOS
%else
    jmp     $           ; 没有找到 LOADER.BIN 则死循环在此处
%endif

LABEL_FILENAME_FOUND:   ; 找到 LOADER.BIN 后,便来到这里。
    mov     ax, RootDirSectors
    and     di, 0FFE0h              ; di &= E0h 表示当前条目的起始位置
    add     di, 01Ah                ; di += 1Ah 表示对应条目的簇号的偏移量
    mov     cx, word [es:di]        ; 得到簇号(FAT 序号)
    push    cx                      ; 保存簇号
    add     cx, ax
    add     cx, DeltaSectorNo       ; cx <- LOADER.BIN 的起始扇区号 = 簇号 + RootDirSectors + 19 - 2
    mov     ax, BaseOfLoader
    mov     es, ax                  ; es <- BaseOfLoader
    mov     bx, OffsetOfLoader      ; bx <- OffsetOfLoader
    mov     ax, cx                  ; ax <- LOADER.BIN 的起始扇区号
LABEL_GOON_LOADING_FILE:
    ; 每读取一个扇区就在 “Booting  ”后面追加一个点,形成这样的效果:Booting  .....
    push    ax
    push    bx
    mov     ah, 0Eh
    mov     al, '.'
    mov     bl, 0Fh
    int     10h
    pop     bx
    pop     ax

    mov     cl, 1
    call    ReadSector              ; 根据 AX(起始扇区号) 读取数据区 CL 个扇区数
    pop     ax                      ; 恢复簇号(FAT 序号)
    call    GetFATEntry             ; 得到 FAT 序号
    cmp     ax, 0FFFh
    jz      LABEL_FILE_LOADED       ; 判断该 FAT 是否为该文件最后一个簇
    push    ax                      ; 保存得到的 FAT

    ; 根据 FAT 计算 LOADER.BIN 文件的下一个扇区数据
    mov     dx, RootDirSectors
    add     ax, dx
    add     ax, DeltaSectorNo

    add     bx, [BPB_BytsPerSec]    ; +512偏移量,作为新的 es:bx 读取扇区写入

    jmp     LABEL_GOON_LOADING_FILE
LABEL_FILE_LOADED:
    mov     dh, 1       ; "Ready."
    call    DispStr     ; 显示字符串

;*******************************************************************************
    jmp BaseOfLoader:OffsetOfLoader         ; 跳转到已经加载到内存中的 LOADER.BIN
                                            ; 处开始执行 LOADER.BIN 的代码
                                            ; Boot Sector(引导扇区)的使命到此结束
;*******************************************************************************

;===============================================================================
; 变量
wRootDirSizeForLoop     dw  RootDirSectors  ; Root Directory 占用的扇区数
                                            ; 在循环中会递减置为 0

wSectorNo               dw  0               ; 要读取的扇区号
bOdd                    db  0               ; 奇数 Or 偶数

; 字符串
LoaderFileName          db  "LOADER  BIN", 0 ; LOADER.BIN 之文件名
; 为方便,下面所有字符串长度均为 9
MessageLength   equ 9           ; 统一字符串长度为 9 字节
BootMessage:    db "Booting  "  ; 9 字节,序号 0
Message1        db "Ready.   "  ; 9 字节,序号 1
Message2        db "No LOADER"  ; 9 字节,序号 2
;===============================================================================

;------------------------------------------------------
; 函数名:DispStr
;------------------------------------------------------
; 功能:显示字符串
;------------------------------------------------------
; 参数
;   dh:字符串编号
;------------------------------------------------------
DispStr:
    mov     ax, MessageLength
    mul     dh
    add     ax, BootMessage

    ; es:bp = 串地址
    mov     bp, ax
    mov     ax, ds
    mov     es, ax

    mov     cx, MessageLength
    mov     ax, 01301h
    mov     bx, 0007h
    mov     dl, 0
    int     10h

    ret

;------------------------------------------------------
; 函数名:ReadSector
;------------------------------------------------------
; 功能:从第 ax 个扇区开始,将 cl 个扇区读入 es:bx 中
;------------------------------------------------------
; 参数
;   ax:扇区号
;   cl:要读取的扇区数
;------------------------------------------------------
; 返回:es:bx 存放着读取扇区的数据
;------------------------------------------------------
ReadSector:
    push    bp
    mov     bp, sp
    sub     esp, 2 ; 开辟两个字节的堆栈空间,保存要读的扇区数:byte [bp - 2]

    mov     byte[bp - 2], cl    ;
    push    bx                  ; 保存 bx
    mov     bl, [BPB_SecPerTrk] ; bl <- 每磁道扇区数
    div     bl                  ; AL(商-Q), AH(余-R)
    inc     ah                  ; R++
    mov     cl, ah              ; cl <- 起始扇区号
    mov     dh, al              ; dh <- 磁头号
    shr     al, 1               ; 柱面号 = Q >> 1
    mov     ch, al              ; ch <- 柱面号
    and     dh, 1               ; 磁头号 = Q & 1
    pop     bx                  ; 还原 bx
    ; 至此计算完成 柱面号、起始扇区、磁头号
    mov     dl, [BS_DrvNum]     ; 驱动器号(0 表示 A 盘)
.GoOnReading:
    mov ah, 2                   ; 读
    mov al, byte [bp - 2]       ; 读取 al 个扇区,[bp - 2] 就是一开始的 cl
    int 13h
    jc .GoOnReading             ; 若读取错误,则 CF = 1,此时会不停地读,直到正确为止

    add esp, 2
    pop bp

    ret

;------------------------------------------------------
; 函数名:GetFATEntry
;------------------------------------------------------
; 功能:找到序号为 ax 的扇区在 FAT 中的条目,结果放在 ax 中
;       注意:中间需要读 FAT 的扇区到 es:bx 处,所以函数一开始保存了 es 和 bx
;------------------------------------------------------
; 参数
;   AX:簇号
;------------------------------------------------------
GetFATEntry:
    push    es
    push    bx
    push    ax

    ; 在 BaseOfLoader 后面留出 4k 空间用于存放 FAT 表
    mov     ax, BaseOfLoader
    sub     ax, 0100h
    mov     es, ax

    pop     ax
    mov     byte [bOdd], 0      ; 默认簇号为偶数
    mov     bx, 3
    mul     bx                  ; dx:ax = ax * 3
    mov     bx, 2
    div     bx                  ; dx:ax / 2 ==> ax <- 商, dx <- 余数
    cmp     dx, 0
    jz      LABEL_EVEN          ; 判断簇号是否为偶数
    mov     byte [bOdd], 1      ; 不是,是奇数
LABEL_EVEN:
    ; 现在 ax 中是 FATEntry 在 FAT 中的偏移量
    ; 计算 FATEntry 在哪个扇区中(FAT 占用可能不止一个扇区)
    xor     dx, dx
    mov     bx, [BPB_BytsPerSec]
    div     bx ; dx:ax / BPB_BytsPerSec
               ; ax <- 商(FATEntry 所在扇区相对于 FAT 的扇区号)
               ; dx <- 余数(FATEntry 在扇区内的偏移)
    push    dx
    mov     bx, 0 ; bx <- 0 于是,es:bx = (BaseOfLoader - 100):00
    add     ax, SectorNoOfFAT1 ; 此句之后的 ax 就是 FATEntry 所在的扇区号
    mov     cl, 2
    call    ReadSector ; 读取 FATEntry 所在的扇区,一次读两个,避免边界问题,因为一个 FATEntry 可能跨越两个扇区

    pop     dx
    add     bx, dx
    mov     ax, [es:bx]
    cmp     byte [bOdd], 1
    jnz     LABEL_EVEN_2    ; 判断簇号是否为偶数
    shr     ax, 4           ; 不是,是奇数,所以右移 4 位
LABEL_EVEN_2:
    and     ax, 0FFFh       ; 是偶数,去掉高 4 位
LABEL_GET_FAT_ENRY_OK:
    pop     bx
    pop     es

    ret

times 510 - ($ - $$) db 0
dw 0xaa55

loader.asm

org 0100h
    mov ax, 0B800h
    mov gs, ax
    mov ah, 0Fh
    mov al, 'L'
    mov [gs:((80 * 0 + 39) * 2)], ax

    jmp $

参考资料

  • https://blog.csdn.net/LinuxArmbiggod/article/details/120538810
  • https://blog.csdn.net/qq_39654127/article/details/88624204
  • https://blog.csdn.net/jj_chen_lian/article/details/84469073
  • https://www.cnblogs.com/wanghj-dz/archive/2011/05/12/2044818.html
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值