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


    FAT12

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

    • RIVER.TXT

      riverriverriver
      
      • 1
    • FLOWER.TXT

      flowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflowerflower
      
      • 1
    • TREE.TXT

      treetreetree
      
      • 1

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

    • CAT.TXT

      catcatcat
      
      • 1
    • DOG.TXT

      dogdogdog
      
      • 1

    将文件挂载到软盘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/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    由于根目录区从第 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
    
    • 1

    在这里插入图片描述

    接下来查看数据区的信息,在此之前由于根目录区是不固定的,因此需要进行计算,计算公式如下:
    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
    
    • 1

    在这里插入图片描述

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

    查看 FAT 表:

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

    在这里插入图片描述

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    把 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 $
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    加载 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    栈中的变化:

    在这里插入图片描述

    寻找 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 后,便来到这里。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    上面的任务是:遍历根目录区的每一个扇区中的条目,其实就是遍历根目录区中的所有文件,然后与“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     ; 显示字符串
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    在根目录区找到 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    编写函数 - 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    跳转到已经加载到内存中的 LOADER.BIN
    ;*******************************************************************************
        jmp BaseOfLoader:OffsetOfLoader         ; 跳转到已经加载到内存中的 LOADER.BIN
                                                ; 处开始执行 LOADER.BIN 的代码
                                                ; Boot Sector(引导扇区)的使命到此结束
    ;*******************************************************************************
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315

    loader.asm

    org 0100h
        mov ax, 0B800h
        mov gs, ax
        mov ah, 0Fh
        mov al, 'L'
        mov [gs:((80 * 0 + 39) * 2)], ax
    
        jmp $
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    参考资料

    • 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
  • 相关阅读:
    Elasticsearch
    深入Ansible
    图的遍历 深度优先遍历(爱思创)
    Windows线程
    flink理论干货笔记(3)
    阿里140逆向纯与补
    java计算机毕业设计计算机组成原理教学演示软件源码+数据库+系统+lw文档+mybatis+运行部署
    C# 使用 REST API HTTP 客户端生成器
    全功能WebRTC应用程序AppRTC应用服务阿里云搭建测试总结并docker化提供镜像
    合并报表软件选哪个?这篇文章两分钟告诉你!
  • 原文地址:https://blog.csdn.net/qq_43098197/article/details/126762657