历史性的一跳 -- 从启动扇区跳转到 loader

2022-06-27 15:27:50 浏览数 (1)

1. 引言

上一篇文章中,我们详细介绍了 FAT12 文件系统的构成,并且在 linux 环境下构建了我们自己的软盘,虽然这在此前我们已经实现过了很多次。 实战 FAT12 文件系统

本文,我们就来通过上述原理,实现软盘读取,并且加载并让我们的启动盘跳转进入到软盘上文件所写的程序。 这样,我们就终于可以和长期以来伴随我们的 freedos 系统说再见了,终于可以使用原生的 bochs 调试功能了,再也不用使用蹩脚的 magic break 了。

2. 通过 BIOS 中断读取软盘

BIOS 中断对我们来说已经不陌生了,我们曾经使用过 10H 号中断来实现一个字符串的打印。 BIOS 的 13H 号中断就是用来操作软盘的,他通过触发时寄存器的值,实现了下面两个功能。

2.1. 复位软盘

触发时,如果 ah = 0,dl 为 驱动器号,则中断将造成软驱复位。

2.2. 读取软盘

触发时,如果寄存器值如下,则读取相应数据到 es:bx 缓冲区中:

  • ah = 02h
  • al = 读取扇区数
  • ch = 柱面/磁道号
  • cl = 起始扇区号
  • dh = 磁头号
  • dl = 驱动器号
  • es:bx = 数据缓冲区

2.3. 通过扇区号计算柱面号与磁头号

根据上一篇文章中介绍的原理,我们可以知道,对于一个 1.44M 的软盘来说,总共有两个盘面,每面 80 个磁道,每个磁道 18 个扇区,因此 2 18 80 * 512 = 1.44M。 那么,如何获取柱面号、磁头号、盘片起始扇区号呢,根据上面的原理,也很简单:

柱面号 = 扇区号 / 每磁道扇区数 / 2 磁头号 = 扇区号 / 每磁道扇区数 & 1 起始扇区号 = 扇区号 % 每磁道扇区数 1

2.4. 通过汇编程序读取软盘扇区

下面的函数实现了一个软盘指定数量扇区的读取:

代码语言:javascript复制
; ----- 从第 ax 个 Sector 开始, 将 cl 个 Sector 读入 es:bx 中 -----  
ReadSector:  
    push    bp  
    mov    bp, sp  
    sub    esp, 2                     ; 开辟两个字节的堆栈区域保存要读的扇区数: byte [bp-2]  

    mov     byte [bp-2], cl  
    push bx  
    mov     bl, [BPB_SecPerTrk]    ; bl: 每磁道扇区数  
    div    bl                        ; 商保存在 al 中,余数保存在 ah 中  
    inc    ah                        ; 获取其实扇区号  
    mov    cl, ah                    ; cl <- 起始扇区号  
    mov    dh, al  
    shr    al, 1                    ; 获取柱面号  
    mov    ch, al                    ; ch <- 柱面号  
    and    dh, 1                    ; 获取磁头号  
    pop    bx  
    mov    dl, [BS_DrvNum]            ; 驱动器号 (0 表示 A 盘)  
.GoOnReading:  
    mov    ah, 2                    ; 读  
    mov    al, byte [bp-2]            ; 读 al 个扇区  
    int    13h  
    jc    .GoOnReading            ; 如果读取错误 CF 会被置为 1, 这时就不停地读, 直到正确为止  
    add    esp, 2  
    pop    bp  

    ret

3. 软盘数据的读取

通过上面的 ReadSector 函数,我们已经可以实现软盘上一个指定扇区的读取了,但是,一个文件只要大于 512 字节,就会被存储在多个扇区上,此时我们就需要去解析 FAT 扇区中存储的 FAT 项,从而循环跳转到下一个存储文件内容的扇区中,最终实现整个文件的读取。

如上图所示,FAT 项存在一个问题,那就是他是 12 字节的,我们每次读取一个字节的话,要读取三次才能取到两个 FAT 项,因此解析起来存在一定的复杂性。 下面的函数实现了指定 FAT 项的读取,结果存放在 ax 中:

代码语言:javascript复制
GetFATEntry:  
    push    es  
    push    bx  
    push    ax  

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

    ; 判断 ax 奇偶性,赋值 bOdd 变量  
    pop    ax  
    mov    byte [bOdd], 0        ; bOdd 变量用于存放当前是奇数次读取还是偶数次读取  
    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:  
    ; 计算 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 = FAT1 起始扇区号   指定读取扇区号 = FATEntry 所在的扇区号  
    mov    cl, 2  
    call ReadSector         ; 读取 FATEntry 所在的扇区, 一次读两个  

    ; 赋值结果给 ax 并矫正结果  
    pop    dx  
    add    bx, dx  
    mov    ax, [es:bx]  
    cmp    byte [bOdd], 1  
    jnz    LABEL_EVEN_2  
    shr    ax, 4  
LABEL_EVEN_2:  
    and    ax, 0FFFh  

LABEL_GET_FAT_ENRY_OK:  

    pop    bx  
    pop    es  
    ret

4. 创建 loader demo

我们在系列之初,就完成了一个启动盘的创建,输出了 hello world 字符串: 实现一个操作系统

那么,接下来我们要做的就是创建一个 loader.bin 程序,让我们的启动程序找到并加载 loader.bin,由 loader.bin 负责拉起我们后面要写的操作系统。

4.1. loader demo

由于启动程序一旦将控制权交给 loader.bin,loader 就不再受任何限制,我们就可以自由的编写我们所需要的 loader 程序了,因此,本文我们重点在启动程序将控制权交给 loader 的过程,因此 loader 只要能显示一行字符串就可以了。

代码语言:javascript复制
org    0100h  
    mov ax, cs  
    mov ds, ax  
    mov di, (80*3   2) * 2    ; 屏幕第 3 行, 第 2 列  
    mov    ax, 0B800h  
    mov    gs, ax  
    mov    ah, 0Fh                ; 0000: 黑底    1111: 白字  
    mov si, BootMessage  
    xor cx, cx  
    mov cl, byte [MessageLength]  
print_loop:  
    lodsb  
    mov    [gs:di], ax  
    add bl, 1  
    add di, 2  
    loop print_loop  

    jmp    $  

BootMessage:    db  "techlog loader"  
MessageLength:  db $-BootMessage

4.2. 放入软盘

通过上一篇文章中所介绍的,我们直接通过命令将编译后的 loader.bin 放入软盘即可:

nasm -o loader.bin loader.asm mount -o loop boot.img /mnt/floppy cp loader.bin /mnt/floppy umount /mnt/floppy

5. 让启动程序找到 loader demo

5.1. 复位软驱

首先,我们要执行复位中断,复位软驱:

代码语言:javascript复制
xor    ah, ah  
xor    dl, dl  
int    13h

5.2. 循环读取根目录区找到元信息

接下来,我们要循环读取根目录区,找到 loader.bin 在根目录区中的元数据信息:

代码语言:javascript复制
; 在根目录区寻找 LOADER.BIN  
    ; wSectorNo 为根目录区扇区号,初始为 19  
    mov    word [wSectorNo], SectorNoOfRootDirectory  
LABEL_SEARCH_IN_ROOT_DIR_BEGIN:  
    ; 根目录区已读完,则说明未找到  
    cmp    word [wRootDirSizeForLoop], 0  
    jz    LABEL_NO_LOADERBIN  
    dec    word [wRootDirSizeForLoop]  

    ; 读取扇区  
    mov    ax, BaseOfLoader  
    mov    es, ax  
    mov    bx, OffsetOfLoader  
    mov    ax, [wSectorNo]  
    mov    cl, 1  
    call    ReadSector  

    mov    si, LoaderFileName            ; ds:si = "LOADER  BIN"  
    mov    di, OffsetOfLoader            ; es:di = BaseOfLoader:0100  
    cld                                ; df = 0  

    ; 循环读取目录条目  
    mov    dx, 10h                        ; 当前扇区所有目录条目循环次数  
LABEL_SEARCH_FOR_LOADERBIN:  
    ; 循环结束,已完成当前扇区目录条目读取  
    cmp    dx, 0  
    jz    LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR      
    dec    dx  

    ; 比较目录条目中 DIR_Name 是否与 LOADER.BIN 相同  
    mov    cx, 11  
LABEL_CMP_FILENAME:  
    cmp    cx, 0  
    jz    LABEL_FILENAME_FOUND        ; 如果比较了 11 个字符都相等, 表示找到  
    dec    cx  
    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                    ; 让 es:di 指向当前条目起始位置  
    add    di, 20h                        ; 跳至下一条目  
    mov    si, LoaderFileName  
    jmp    LABEL_SEARCH_FOR_LOADERBIN  

; 非当前扇区,跳至下一扇区  
LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:  
    add    word [wSectorNo], 1  
    jmp    LABEL_SEARCH_IN_ROOT_DIR_BEGIN  

; 未找到,终止流程  
LABEL_NO_LOADERBIN:  
    mov    dh, 2                        ; "No LOADER."  
    call DispStr                    ; 显示字符串  
    jmp    $  

; 找到 loader.bin,继续流程  
LABEL_FILENAME_FOUND:

5.3. 获取文件在数据区中起始扇区号

代码语言:javascript复制
; 获取 loader.bin 对应的数据区簇号,保存在栈中  
and    di, 0FFE0h                    ; di = 当前条目起始位置  
add    di, 01Ah                    ; es:di 指向 DIR_FstClus,对应数据区簇号  
mov    cx, word [es:di]  
push cx  

; 获取文件所在扇区号,保存在 cx 中  
mov    ax, RootDirSectors            ; 根目录扇区数  
add    cx, ax                        ; 因为 BPB_SecPerClus 为 1,每簇 1 扇区  
add    cx, DeltaSectorNo            ; 所以,文件所在扇区号 = 根目录起始扇区号   根目录扇区数   文件数据区簇号 - 2

5.4. 读取文件并载入内存

现在,我们已经有了文件在数据区中的起始扇区号,通过 FAT 区中的 FAT 项,我们就可以递归获取整个文件了。

代码语言:javascript复制
    ; es:bx = loader.bin 将要被加载到的内存物理地址  
    mov    ax, BaseOfLoader  
    mov    es, ax  
    mov    bx, OffsetOfLoader  

    ; 循环读取 loader.bin  
    mov    ax, cx                        ; ax <- Sector 号  
LABEL_GOON_LOADING_FILE:  
    ; 打点,表示准备读取一个扇区,展示 Booting....  
    push    ax  
    push    bx  
    mov    ah, 0Eh  
    mov    al, '.'  
    mov    bl, 0Fh  
    int    10h  
    pop    bx  
    pop    ax  

    mov    cl, 1  
    call    ReadSector  
    pop    ax                            ; 取出此 Sector 在 FAT 中的序号  
    call    GetFATEntry                ; 读取 FAT 项值  
    cmp    ax, 0FFFh                    ; 判断是否完成读取  
    jz    LABEL_FILE_LOADED  
    push    ax                        ; 保存 Sector 在 FAT 中的序号  

    ; 读取文件下一簇  
    mov    dx, RootDirSectors  
    add    ax, dx  
    add    ax, DeltaSectorNo  
    add    bx, [BPB_BytsPerSec]  
    jmp    LABEL_GOON_LOADING_FILE  
LABEL_FILE_LOADED:  
    ; 完成文件读取,并全部载入内存  
    mov    dh, 1                        ; "Ready."  
    call    DispStr                    ; 显示字符串

6. 将控制权交给 loader

既然整个 loader.bin 已经被加载到了内存中,那么,我们只需要通过一个跳转指令,跳转到被加载到内存的起始地址,就可以完成控制权的转移,也就是开始 loader 的执行了:

代码语言:javascript复制
jmp    BaseOfLoader:OffsetOfLoader

7. 程序运行结果

运行我们的镜像,可以看到:

8. 加载其他 loader

本文重点就在于我们对启动程序的修改,loader 其实并不重要,也就是说,我们可以把此前我们写过的任何程序作为 loader.bin 来启动。 下面是之前中断相关文章的 demo 演示:

10. 附录 — 完整代码

10.1. Makefile

代码语言:javascript复制
BOOT:=boot.asm  
LDR:=loader.asm  
IDTLDR:=protect.asm  
BOOT_BIN:=$(subst .asm,.bin,$(BOOT))  
LDR_BIN:=$(subst .asm,.bin,$(LDR))  

IMG:=boot.img  
FLOPPY:=/mnt/floppy/  

.PHONY : everything  

everything : $(BOOT_BIN) $(LDR_BIN)  
    # bximg  
    dd if=$(BOOT_BIN) of=$(IMG) bs=512 count=1 conv=notrunc  
    sudo mount -o loop $(IMG) $(FLOPPY)  
    sudo cp $(LDR_BIN) $(FLOPPY) -v  
    sudo umount $(FLOPPY)  

clean :  
    rm -f $(BOOT_BIN) $(LDR_BIN)  

$(BOOT_BIN) : $(BOOT)  
    nasm $< -o $@  

$(LDR_BIN) : $(LDR)  
    nasm $< -o $@  

idt:  
    nasm boot.asm -o boot.bin          
    nasm protect.asm -o loader.bin  
    dd if=$(BOOT_BIN) of=$(IMG) bs=512 count=1 conv=notrunc  
    sudo mount -o loop $(IMG) $(FLOPPY)  
    sudo cp $(LDR_BIN) $(FLOPPY) -v  
    sudo umount $(FLOPPY)

10.2. loader.asm

代码语言:javascript复制
org    0100h  
    mov ax, cs  
    mov ds, ax  
    mov di, (80*3   2) * 2    ; 屏幕第 3 行, 第 39 列  
    mov    ax, 0B800h  
    mov    gs, ax  
    mov    ah, 0Fh                ; 0000: 黑底    1111: 白字  
    mov si, BootMessage  
    xor cx, cx  
    mov cl, byte [MessageLength]  
print_loop:  
    lodsb  
    mov    [gs:di], ax  
    add bl, 1  
    add di, 2  
    loop print_loop  

    jmp    $  

BootMessage:    db  "techlog loader"  
MessageLength:  db $-BootMessage

10.3. boot.asm

代码语言:javascript复制
org  07c00h  

BaseOfStack                equ    07c00h    ; Boot状态下堆栈基地址(栈底, 从这个位置向低地址生长)  
BaseOfLoader            equ    09000h    ; LOADER.BIN 被加载段地址  
OffsetOfLoader            equ    0100h    ; LOADER.BIN 被加载偏移地址  

RootDirSectors            equ    14        ; 根目录区扇区数  
SectorNoOfRootDirectory    equ    19        ; Root Directory 的第一个扇区号  
SectorNoOfFAT1            equ    1        ; FAT1 的第一个扇区号 = BPB_RsvdSecCnt  
DeltaSectorNo            equ    17        ; 用于计算文件的开始扇区号 BPB_RsvdSecCnt   (BPB_NumFATs * FATSz) - 2  

    jmp short LABEL_START            ; Start to boot.  
    nop                                ; jmp 语句 3 字节,nop 补足 4 字节  

    ; 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        ; AH = 6,  AL = 0h  
    mov    bx, 0700h        ; 黑底白字(BL = 07h)  
    mov    cx, 0            ; 左上角: (0, 0)  
    mov    dx, 0184fh        ; 右下角: (80, 50)  
    int    10h  

    mov    dh, 0  
    call    DispStr  

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

; 在根目录区寻找 LOADER.BIN  
    ; wSectorNo 为根目录区扇区号,初始为 19  
    mov    word [wSectorNo], SectorNoOfRootDirectory  
LABEL_SEARCH_IN_ROOT_DIR_BEGIN:  
    ; 根目录区已读完,则说明未找到  
    cmp    word [wRootDirSizeForLoop], 0  
    jz    LABEL_NO_LOADERBIN  
    dec    word [wRootDirSizeForLoop]  

    ; 读取扇区  
    mov    ax, BaseOfLoader  
    mov    es, ax  
    mov    bx, OffsetOfLoader  
    mov    ax, [wSectorNo]  
    mov    cl, 1  
    call    ReadSector  

    mov    si, LoaderFileName            ; ds:si = "LOADER  BIN"  
    mov    di, OffsetOfLoader            ; es:di = BaseOfLoader:0100  
    cld                                ; df = 0  

    ; 循环读取目录条目  
    mov    dx, 10h                        ; 当前扇区所有目录条目循环次数  
LABEL_SEARCH_FOR_LOADERBIN:  
    ; 循环结束,已完成当前扇区目录条目读取  
    cmp    dx, 0  
    jz    LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR      
    dec    dx  

    ; 比较目录条目中 DIR_Name 是否与 LOADER.BIN 相同  
    mov    cx, 11  
LABEL_CMP_FILENAME:  
    cmp    cx, 0  
    jz    LABEL_FILENAME_FOUND        ; 如果比较了 11 个字符都相等, 表示找到  
    dec    cx  
    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                    ; 让 es:di 指向当前条目起始位置  
    add    di, 20h                        ; 跳至下一条目  
    mov    si, LoaderFileName  
    jmp    LABEL_SEARCH_FOR_LOADERBIN  

; 非当前扇区,跳至下一扇区  
LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:  
    add    word [wSectorNo], 1  
    jmp    LABEL_SEARCH_IN_ROOT_DIR_BEGIN  

; 未找到,终止流程  
LABEL_NO_LOADERBIN:  
    mov    dh, 2                        ; "No LOADER."  
    call DispStr                    ; 显示字符串  
    jmp    $  

; 找到 loader.bin,继续流程  
LABEL_FILENAME_FOUND:  
    ; 获取 loader.bin 对应的数据区簇号,保存在栈中  
    and    di, 0FFE0h                    ; di = 当前条目起始位置  
    add    di, 01Ah                    ; es:di 指向 DIR_FstClus,对应数据区簇号  
    mov    cx, word [es:di]  
    push cx  

    ; 获取文件所在扇区号,保存在 cx 中  
    mov    ax, RootDirSectors            ; 根目录扇区数  
    add    cx, ax                        ; 因为 BPB_SecPerClus 为 1,每簇 1 扇区  
    add    cx, DeltaSectorNo            ; 所以,文件所在扇区号 = 根目录起始扇区号   根目录扇区数   文件数据区簇号 - 2  

    ; es:bx = loader.bin 将要被加载到的内存物理地址  
    mov    ax, BaseOfLoader  
    mov    es, ax  
    mov    bx, OffsetOfLoader  

    ; 循环读取 loader.bin  
    mov    ax, cx                        ; ax <- Sector 号  
LABEL_GOON_LOADING_FILE:  
    ; 打点,表示准备读取一个扇区,展示 Booting....  
    push    ax  
    push    bx  
    mov    ah, 0Eh  
    mov    al, '.'  
    mov    bl, 0Fh  
    int    10h  
    pop    bx  
    pop    ax  

    mov    cl, 1  
    call    ReadSector  
    pop    ax                            ; 取出此 Sector 在 FAT 中的序号  
    call    GetFATEntry                ; 读取 FAT 项值  
    cmp    ax, 0FFFh                    ; 判断是否完成读取  
    jz    LABEL_FILE_LOADED  
    push    ax                        ; 保存 Sector 在 FAT 中的序号  

    ; 读取文件下一簇  
    mov    dx, RootDirSectors  
    add    ax, dx  
    add    ax, DeltaSectorNo  
    add    bx, [BPB_BytsPerSec]  
    jmp    LABEL_GOON_LOADING_FILE  
LABEL_FILE_LOADED:  
    ; 完成文件读取,并全部载入内存  
    mov    dh, 1                        ; "Ready."  
    call    DispStr                    ; 显示字符串  

    jmp    BaseOfLoader:OffsetOfLoader    ; 跳转到已加载到内  

; -------------------------- 变量 --------------------------------  
wRootDirSizeForLoop    dw    RootDirSectors        ; Root Directory 占用的扇区数, 在循环中会递减至零.  
wSectorNo            dw    0                    ; 要读取的扇区号  
bOdd                db    0                    ; 奇数还是偶数  
LoaderFileName        db    "LOADER  BIN", 0    ; LOADER.BIN 之文件名  
; 为简化代码, 下面每个字符串的长度均为 MessageLength  
MessageLength        equ    9  
BootMessage:        db    "Booting  "            ; 9字节, 不够则用空格补齐. 序号 0  
Message1            db    "Ready.   "            ; 9字节, 不够则用空格补齐. 序号 1  
Message2            db    "No LOADER"            ; 9字节, 不够则用空格补齐. 序号 2  

; ---- 显示一个字符串, 函数开始时 dh 中应该是字符串序号(0-based) ----  
DispStr:  
    mov    ax, MessageLength  
    mul    dh  
    add    ax, BootMessage  
    mov    bp, ax            ; ┓  
    mov    ax, ds            ; ┣ ES:BP = 串地址  
    mov    es, ax            ; ┛  
    mov    cx, MessageLength    ; CX = 串长度  
    mov    ax, 01301h        ; AH = 13,  AL = 01h  
    mov    bx, 0007h        ; 页号为0(BH = 0) 黑底白字(BL = 07h)  
    mov    dl, 0  
    int    10h            ; int 10h  
    ret  


; ----- 从第 ax 个 Sector 开始, 将 cl 个 Sector 读入 es:bx 中 -----  
ReadSector:  
    push    bp  
    mov    bp, sp  
    sub    esp, 2                     ; 开辟两个字节的堆栈区域保存要读的扇区数: byte [bp-2]  

    mov     byte [bp-2], cl  
    push bx  
    mov     bl, [BPB_SecPerTrk]    ; bl: 每磁道扇区数  
    div    bl                        ; 商保存在 al 中,余数保存在 ah 中  
    inc    ah                        ; 获取其实扇区号  
    mov    cl, ah                    ; cl <- 起始扇区号  
    mov    dh, al  
    shr    al, 1                    ; 获取柱面号  
    mov    ch, al                    ; ch <- 柱面号  
    and    dh, 1                    ; 获取磁头号  
    pop    bx  
    mov    dl, [BS_DrvNum]            ; 驱动器号 (0 表示 A 盘)  
.GoOnReading:  
    mov    ah, 2                    ; 读  
    mov    al, byte [bp-2]            ; 读 al 个扇区  
    int    13h  
    jc    .GoOnReading            ; 如果读取错误 CF 会被置为 1, 这时就不停地读, 直到正确为止  
    add    esp, 2  
    pop    bp  

    ret  

; ---- 读取序号为 ax 的 Sector 在 FAT 中的条目, 放在 ax 中 ----  
GetFATEntry:  
    push    es  
    push    bx  
    push    ax  

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

    ; 判断 ax 奇偶性,赋值 bOdd 变量  
    pop    ax  
    mov    byte [bOdd], 0        ; bOdd 变量用于存放当前是奇数次读取还是偶数次读取  
    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:  
    ; 计算 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 = FAT1 起始扇区号   指定读取扇区号 = FATEntry 所在的扇区号  
    mov    cl, 2  
    call ReadSector         ; 读取 FATEntry 所在的扇区, 一次读两个  

    ; 赋值结果给 ax 并矫正结果  
    pop    dx  
    add    bx, dx  
    mov    ax, [es:bx]  
    cmp    byte [bOdd], 1  
    jnz    LABEL_EVEN_2  
    shr    ax, 4  
LABEL_EVEN_2:  
    and    ax, 0FFFh  

LABEL_GET_FAT_ENRY_OK:  

    pop    bx  
    pop    es  
    ret  

times     510-($-$$)    db    0    ; 填充剩余空间,使生成的二进制代码恰好为512字节  
dw     0xaa55                    ; 结束标志

10.4. bochsrc.bxrc

代码语言:javascript复制
###############################################################  
# Configuration file for Bochs  
###############################################################  

# how much memory the emulated machine will have  
megs: 32  

# filename of ROM images  
romimage: file=$BXSHARE/BIOS-bochs-latest  
vgaromimage: file=$BXSHARE/VGABIOS-lgpl-latest  
vga: extension=vbe, update_freq=15  

magic_break: enabled=1  

# what disk images will be used  
floppya: 1_44=boot.img, status=inserted  

# choose the boot disk.  
boot: a  
# log: stdout.log  

# enable key mapping, using US layout as default.  
keyboard: keymap=$BXSHARE/keymaps/x11-pc-us.map  
mouse: enabled=1  
mouse: type=imps2, enabled=1  
mouse: type=serial, enabled=1  
mouse: enabled=0, toggle=ctrl f10  

magic_break: enabled=1

11. 附录 — 系列目录

实现一个操作系统

0 人点赞