我们在上节博客中学习了如何进行主引导程序的 512 字节的扩展,那么我们本节就继续来学习下如何进行控制权的交接。就是控制权由主引导程序交由下一个将要执行的程序,类似于嵌入式中的 uboot 在启动内核的时候将控制权由 uboot 交由 kernel。下来我们先来看看 BootLoader 的内存布局,如下所示

图片.png

        我们看到在 0x7c00 前还有一段预留的空间,那么这段空间就是用来存放栈信息的。在主引导程序的 512 字节之后,紧接着就是 Fat 表,它大小为 4kb。从 0x9000 地址之后便全部为 Loader 了,也就是我们交由控制权的地方了。我们来看看通过 FAT 表是如何来加载文件内容的,如下图所示

图片.png

        我们看到,先指定 FAT 表的地址,然后指定 DIR_FstClus 成员的入口地址,再间接赋给 dx 寄存器。这个 0xFF7 是哪来的呢?在我们之前用 Qt 编写的代码中就指定了这个大小,这是规定的,后面的流程也是我们之前实现过的流程。我们就来做个实验:1、在虚拟软盘中创建体积较大的文本文件(Loader);2、将 Loader 的内容加载到 BaseOfLoader 地址处;3、打印 Loader 中的文本(用来判断加载是否完全)。具体源码如下

start:
    mov ax, cs
    mov ss, ax
    mov ds, ax
    mov es, ax
    mov sp, BaseOfStack
    
    mov ax, RootEntryOffset
    mov cx, RootEntryLength
    mov bx, Buf
    
    call ReadSector
    
    mov si, Target
    mov cx, TarLen
    mov dx, 0
    
    call FindEntry
    
    cmp dx, 0
    jz output
    
    mov si, bx ; 将起始地址放到 si 中
    mov di, EntryItem
    mov cx, EntryItemLength
    
    call MemCpy
    
    ; 计算 Fat 表所占用的内存
    mov ax, FatEntryLength
    mov cx, [BPB_BytsPerSec]
    mul cx ; 将所占用的内存大小结果保存到 ax 中
    mov bx, BaseOfLoader
    sub bx, ax ; bx 就是 Fat 表在内存中的起始位置了
    
    mov ax, FatEntryOffset
    mov cx, FatEntryLength
    
    call ReadSector
    
    mov dx, [EntryItem + 0x1A] ; 获取目标起始处的位置
    mov si, BaseOfLoader
    
loading:
    mov ax, dx
    add ax, 31
    mov cx, 1
    push dx
    push bx
    mov bx, si
    call ReadSector
    pop bx
    pop cx
    call FatVec
    cmp dx, 0xFF7
    jnb output
    add si, 512
    jmp loading

output:
    mov bp, BaseOfLoader
    mov cx, [EntryItem + 0x1c]
    call Print
    
last:
    hlt
    jmp last

        我们来编译看看

图片.png

        我们看到 make 直接报错,原因是整个主引导程序的大小超出 512 字节的范围了。那么此时我们该怎么办呢?我们就有必要将之前的 push 和 pop 入栈出栈的操作进行删除了,那么我们之前为何要这样做呢?是为了遵守汇编代码的约定,有操作相关寄存器的值就要进行入栈出栈操作。那么我们这块内存已经不够,因此没必要进行这个操作了。我们将下面的入栈出栈操作进行删除,但是要在 FindEntry 这个函数保留 cx 寄存器的入栈出栈的操作,原因是下面不停在改变 cx 寄存器的值。我们在 find 操作中,call MemCmp 操作前后有必要再加上 si 寄存器的入栈出栈操作。我们修改完再来进行 make 看看,是否还会出问题

图片.png

        我们看到已经编译成功,我们来 bochs 调试下,看看 loader 文本的内容是什么

图片.png

        我们看到打印了好多的 D.T.Software。我们来挂载下 data.img 看看 loader 文本的内容是否如此

图片.png

        那么我们将 loader 文本的内容改为我们刚才编写的 boot.asm 的内容,顺便看看它的文件所占内存是多大的,如果大于 512 字节还能正常进行读取并显示,那么就说明我们所编写的功能是没有问题的。

图片.png

        我们看到这个文本的内存是 8 kb,那么它早就超过 512  字节了。看看最后打印的是不是 db 0x55, 0xaa,结果如下

图片.png

        我们看到打印的确实是我们刚才改的内容,也就证明了我们编写的代码是正确的。下来我们来讲讲由 boot 主引导程交由后的第一个程序 Loader:它的起始地址是 0x9000(org 0x9000),通过 int 0x10 号中断在屏幕上打印字符串。在编写 loader.asm 源码之前,我们先来介绍下相关的汇编知识。我们在之前使用了 jz 指令,表示跳转,其实 jxx 代表了一个指令族,功能是根据标志位进行调整,具体如下

图片.png

loader.asm 源码如下

org 0x9000

begin:
    mov si, msg
    
print:
    mov al, [si]
    add si, 1
    cmp al, 0x00
    je end
    mov ah, 0x0E
    mov bx, 0x0F
    int 0x10
    jmp print

end:
    hlt
    jmp end
    
msg:
    db 0x0a, 0x0a
    db "Hello, D.T.OS!"
    db 0x0a, 0x0a
    db 0x00

        我们在上面的代码中使用了 je,我们通过反汇编来看看它在内部是怎样实现的,如下

图片.png

        我们看到在编译器的内部是将 je 指令当做 jz 指令使用了。我们将 loader.asm 编译成 loader,然后将它拷贝至 data.img 中。看看运行的效果

图片.png

        我们看到已经打印出 Hell, D.T.OS! ;我们再将此时的 data.img 放在 window 中,将它作为软盘在我们所创建的虚拟机上,看看效果

图片.png

        我们看到已经成功输出我们所打印的字符串了,虽然效果和我们之前所实现的是一样的。但是此时已经发生了质的变化,此时的 loader.asm 文本大小不再有 512 字节的限制。换句话说,我们可以编写更多的内核机制了。那么我们在完成之后也要将之前的 makefile 重写下,要不然每次都要手动编译 loader.asm 程序。改动后的具体源码如下

.PHONY : all clean rebuild

BOOT_SRC := boot.asm
BOOT_OUT := boot.bin

LOADER_SRC := loader.asm
LOADER_OUT := loader

IMG := data.img
IMG_PATH := /root/DT/wei

RM := rm -rf

all : $(BOOT_OUT) $(LOADER_OUT)
    @echo "Build Success ==> D.T.OS!"
    
$(IMG) :
    bximage $@ -q -fd -size=1.44
    
$(BOOT_OUT) : $(BOOT_SRC)
    nasm $^ -o $@
    dd if=$(BOOT_OUT) of=$(IMG) bs=512 count=1 conv=notrunc
    
$(LOADER_OUT) : $(LOADER_SRC)
    nasm $^ -o $@
    mount -o loop $(IMG) $(IMG_PATH)
    cp $@ $(IMG_PATH)/$@
    umount $(IMG_PATH)

clean :
    $(RM) $(IMG) $(BOOT_OUT) $(LOADER_OUT)
    
rebuild :
    @$(MAKE) clean
    @$(MAKE) all

        我们再来重新 make  下,看看效果

图片.png

        我们将输出字符串在  loader.asm 中改为 Hello, world!看看效果

图片.png

        我们看到已经成功改写,那么我们再来测试下对原来的功能有没有造成什么影响。将原来的 LOADER 在后面加个 a ,看看是否会因查找不到而输出 No LOADER ... 提示性字符串

图片.png

        我们看到已经输出了提示性的字符串,证明我们添加的功能以及改写的 makefile 对原来功能并没有造成任何影响。通过今天对控制权交接的学习,总结如下:1、boot 需要在进行重构保证在 512 字节内完成功能;2、在汇编程序中尽量确保函数调用前后通用寄存器的状态不变;3、boot 成功加载 loader 后将控制权转移;4、loader 程序没有代码体积的限制。