从零构建内核:BootLoader

一个简单的Loader代码

[SECTION .loader]
        org    07c00h
        mov    ax, cs
        mov    ds, ax

        xor    ax, ax
        xor    dl, dl
        int    13

        mov    ax, kernel_base
        mov    es, ax
        mov    ah, 02h
        mov    al, 01h    
        mov    ch, 00h
        mov    cl, 02h
        mov    dh, 00h
        mov    dl, 00h
        mov    bx, 0
        int    13h
        jmp    kernel_base_phy

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

这段代码编译后保存在磁盘的引导扇区。x86架构的计算机在开机阶段BIOS将会将这个扇区的代码复制到0x7c00这个地址开始的内存区域,并跳转到这个地址开始执行。BIOS只会复制这个扇区的内容(512字节的内容)无论代码编译后有多长。这个512字节能实现的功能有限,我们只用这段代码来加载更多的代码。

具体说,该代码调用BIOS功能,初始化磁盘驱动器。然后再次调用BIOS功能,将引导扇区后面几个扇区的代码复制到内存指定地址(kernel_base_phy = kernel_base<<4)。并将控制权交给这些代码(跳转过去)。

如何构造后面扇区的代码?

可以选择在loader后面接着编写代码。因为loader正好是512字节。后面的代码将会被保存在第二个以及后面的扇区。但是代码在编译时,我们用org指令告诉编译器这段代码将会装载到0x7c00的地方执行。然而我们却把后面的指令转移到别处去执行了。这样带来的问题是汇编中的标号(地址)对应不上实际地址。可以通过相对的偏移量解决这个问题,但是过程复杂并容易出错,代码可读性也不好。

那为什么不在后面再写一个org指令指定新的基准呢?汇编语言并不允许一个源文件中存在两个org。虽然行不通,但是给了我们一个启发:写两个源文件————一个文件编译好就是loader放引导区,另一个编译后在磁盘上紧接在引导扇区后面。第二个文件开头:

org    kernel_base_phy
......

我们需要用dd命令将这个文件编译后的机器码拼接成我们需要的形式。

dd if=loader.asm of=a.img conv=notrunc
dd if=other.asm of=a.img conv=notrunc bs=512 seek=1

后一句跳过a.img前512字节,写入other.asm。