前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >历史性的一跳 -- 从启动扇区跳转到 loader

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

作者头像
用户3147702
发布2022-06-27 15:27:50
1.6K0
发布2022-06-27 15:27:50
举报
文章被收录于专栏:小脑斧科技博客

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. 附录 — 系列目录

实现一个操作系统

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-07-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 小脑斧科技博客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 引言
  • 2. 通过 BIOS 中断读取软盘
    • 2.1. 复位软盘
      • 2.2. 读取软盘
        • 2.3. 通过扇区号计算柱面号与磁头号
          • 2.4. 通过汇编程序读取软盘扇区
          • 3. 软盘数据的读取
          • 4. 创建 loader demo
            • 4.1. loader demo
              • 4.2. 放入软盘
              • 5. 让启动程序找到 loader demo
                • 5.1. 复位软驱
                  • 5.2. 循环读取根目录区找到元信息
                    • 5.3. 获取文件在数据区中起始扇区号
                      • 5.4. 读取文件并载入内存
                      • 6. 将控制权交给 loader
                      • 7. 程序运行结果
                      • 8. 加载其他 loader
                        • 10.1. Makefile
                          • 10.2. loader.asm
                            • 10.3. boot.asm
                              • 10.4. bochsrc.bxrc
                              • 11. 附录 — 系列目录
                              领券
                              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档