实现boot loader

CPU 的硬件电路设计成只能运行处于内存中的程序,这是硬件基因问题。
程序是被载入内存运行的,那么这之间要做2件事:
【1】程序被加载器(软件或硬件)加载到内存某个区域
【2】CPU 的 CS : IP 寄存器指向这个程序的起始地址

按下主机 power 键后,第一个运行的软件是BIOS。于是产生了3个问题:
1 它是由谁加载的。
2 它被加载到哪里。
3 它的CS : IP 是谁来更改的。

BIOS

BIOS是计算机上第一个运行的软件,所以它不可能自己加载自己,由此可以知道,它是由硬件加载的。

8086 CPU 有 20 根地址线,可以访问的范围是 1MB ( 0x00000 - 0xFFFFF )。

起始地址 结束地址 大小 用途
FFFF0 FFFFF 16B BIOS入口地址,此地址属于BIOS代码,只是为了强调入口地址
F0000 FFFEF 64KB-16B BIOS区域
... ... ... ...
00000 003FF 1KB Interrupt Vector Table(中断向量表)

BIOS代码所作的工作是一成不变的,而且在正常情况下,其代码以后也是不需要修改的,所以BIOS被写进了ROM,并被映射到1MB的内存顶部。即地址 0xF0000 ~ 0xFFFFF 处。

BIOS本身是个程序,程序要执行,就需要个入口地址,入口地址便是0xFFFF0,知道BIOS在哪里后,CPU如何执行它,即CPU中的 CS:IP 是如何设置成 0xFFFF0 的?
原来在CPU在通电一瞬间,CS:IP 寄存器被强制初始化为 0xF000 : 0xFFF0。所以按照段式内存访问等效地址就是 0xFFFF0。

但是 0xFFFF0 + 16(十进制) = 0x100000,溢出截断为 0x00000。所以这里16字节根本难以容纳BIOS程序,所以这里应该是一条跳转指令:jmp far f000:e05b,所以BIOS的真正代码在0xFE05B。接下来执行BIOS的代码,完成检测内存,显卡等外设等信息,当检测通过,并初始化好硬件后,开始在内存中 0x000 ~ 0x3FF 处建立中段向量表(IVT) 并填写相应的中断例程。

最后BIOS执行最后一步操作就是校验启动盘中位于0盘0道1扇区的内容。
如果此扇区末尾 2个字节分别是魔数 0x550xaa,BIOS便认为此扇区确实存在可执行程序。便将其从磁盘读入到内存0x7C00地址处,然后跳转到此地址,继续执行。这个程序就是主引导记录MBR。

MBR

通常MBR的任务是从磁盘读入某个程序(这个程序一般是内核加载器,很少有直接加载内核)到内存指定位置,并将控制权交给它( jmp 过去)。这个过程完成后,MBR就没有用了。

内核加载器

内核加载器是要完成从实模式到保护模式的过渡。并最终在保护模式下将内核代码加载至内存中,并跳转到内核代码处,然后内核开始工作,接管一切。

代码实现
  • MBR

    • 初始化各个段寄存器
    • 清屏
    • 将内核加载器从硬盘读入到内存中,并跳转到内核加载器代码处
    ; MBR主引导程序
    ;---------------------------
    
    SECTION MBR vstart=0x7c00 ; 实模式下BIOS将MBR加载到0x7c00
        mov ax, cs
        mov ds, ax
        mov es, ax
        mov ss, ax
        mov fs, ax
        mov sp, 0x7c00
        mov ax, 0xb800
        mov gs, ax
    
    ;-------------------------------------
    ; int 0x10 功能号: 0x06 功能描述: 清屏
    ; 输入:
    ; ah=0x06
    ; al = 上卷的行数
    ; bh = 上卷行的属性
    ; (cl, ch) = 窗口左上角位置
    ; (dl, dh) = 窗口右下角位置
    ; 无返回值
    ;--------------------------------------
    mov ax, 0600h
    mov bx, 0700h
    mov cx, 0           ; 左上角: (0, 0)
    mov dx, 184fh       ; 右下角: (80, 25)
    
    int 10h
    
    mov eax, LOADER_START_SECTOR    ; 参数: 待读取的起始扇区lba地址
    mov bx, LOADER_BASE_ADDR        ; 参数: 要写入内存的地址
    mov cx, 4                       ; 参数: 待读入的扇区数
    call rd_disk_m_16   ; 将"内核加载器"读入到内存中
    
    jmp LOADER_BASE_ADDR            ; 跳到内核加载器代码部分
    
    rd_disk_m_16 实现......
    
  • 内核加载器
    目前我们内核加载器并未实现,只是简单的打印字符串"2 loader"。模拟计算机启动过程。

    ;内核加载器
    ;---------------------------------------
    section loader vstart=LOADER_BASE_ADDR    ; LOADER_BASE_ADDR=0x900,内核加载器被加载到的内存地址
    
    ; 输出背景色绿色,前景色红色,并且跳动的字符串"2 LOADER"
    mov byte [gs:0x00],'2'
    mov byte [gs:0x01],0xA4     ; A表示绿色背景闪烁,4表示前景色为红色
    
    mov byte [gs:0x02],' '
    mov byte [gs:0x03],0xA4
    
    mov byte [gs:0x04],'L'
    mov byte [gs:0x05],0xA4   
    
    mov byte [gs:0x06],'O'
    mov byte [gs:0x07],0xA4
    
    mov byte [gs:0x08],'A'
    mov byte [gs:0x09],0xA4
    
    mov byte [gs:0x0a],'D'
    mov byte [gs:0x0b],0xA4
    
    mov byte [gs:0x0c],'E'
    mov byte [gs:0x0d],0xA4
    
    mov byte [gs:0x0e],'R'
    mov byte [gs:0x0f],0xA4
    
    jmp $              ; 通过死循环使程序悬停在此
    

你可能感兴趣的:(实现boot loader)