mbr简介
大家都知道,在我们按下电脑电源的时候,首先启动的BIOS(基本输入输出系统),那么BIOS又是如何被启动的呢,谁来唤醒他呢,它又在何处运行呢。要了解这些的话,首先得介绍一下我们实模式的内存布局
实模式的内存布局
图中的内容我们现在只需要关注红色框出来的地方,可以看到BIOS的入口地址处只有16BYTE的空间,很显然,这一小块空间肯定存放的不是数据,只能是指令了,图中也写的很明显了
jmp f000:e05b
也就是跳转到了(f000 << 4) + e05b = fe05b处,这里的段基址左移四位的原因是,在实模式下段基址寄存器只有16位,想一下,16位的寄存器最多访问2^16=64KB的空间,我们想访问实模式下1MB的空间的话就需要将段基址左移4位,自然就可以访问到1MB的空间了,这么做的原因也是出于兼容性而采取的曲线救国方式,虽然我们现在的OS都已经到了64位,它也还得向下兼容不是吗
当我们的电脑加电的一瞬间cs:ip就会被强制置位f000:e05b了,接下来就对内存,显卡等外设进行检查,做好它的初始化工作之后就完成它的任务了,在最后的时候,BIOS会通过绝对远跳
jmp 0:0x7c00
将接力棒交由MBR来加载我们的内核,我们初步的工作就是编写MBR。在进行内核加载之前,我们先通过MBR打印一些字符,来验证我们之前所说是否正确
编写MBR验证程序
;主引导程序
;---------------------
SECTION MBR vstart=0x7c00 ;程序开始的地址
mov ax, cs ;使用cs初始化其他的寄存器
mov ds, ax ;因为是通过jmp 0:0x7c00到的MBR开始地址
mov es, ax ;所以此时的cs为0,也就是用0初始化其他寄存器
mov ss, ax ;此类的寄存器不同通过立即数赋值,采用ax中转
mov fs, ax
mov sp, 0x7c00 ;初始化栈指针
;清屏利用0x10中断的0x6号功能
;------------------------
mov ax, 0x600
mov bx, 0x700
mov cx, 0
mov dx, 0x184f
int 0x10
;获取光标位置
;---------------------
mov ah, 3 ; 3号子功能获取光标位置
mov bh, 1 ; bh寄存器存储带获取光标位置的页号,从0开始,此处填1可以看成将光标移动到最开始
int 0x10
;打印字符串
;------------------
mov ax, message
mov bp, ax
mov cx, 6 ;字符串长度,不包括'\0'
mov ax, 0x1301
mov bx, 0x2
int 0x10
jmp $
message db "My MBR"
times 510-($-$$) db 0
db 0x55, 0xaa
这段代码通过0x10号中断直接操控显卡,达到打印字符串的目的
编写好后通过
nasm -o mbr.bin mbr.S
dd if=mbr.bin of=/home/ba/bochs/hd60M.img bs=512 count=1 conv=notrunc
对我们的汇编代码进行编译并写入之前创建的磁盘中,接下来运行bochs,应该可以看到如下结果
现在我们通过bochs的调试看一下程序到底是怎么执行的,和我们之前所说的是否一致
这幅图是在我们开启bochs时显示的结果,很明显可以看到他的cs:ip寄存器的值和我们之前所说的结果一致,在这里进行跳转之后接下来肯定就是一系列的初始化工作了,我们跳过这些初始化的工作,直接进入到MBR执行的开始位置,也就是地址0x7c00处
可以看到,左边是bochs初始化完成之后的输出,这是已经运行到了0x7c00后的结果,看红框标记的地方,有没有感觉很熟悉,这就是我们mbr的第一行代码啦,接下来就会按照我们所写的那样,清屏,打印了。
读取硬盘
前面通过打印字符串对开机启动过程做了个小小的验证,接下来需要让我们的MBR读取硬盘啦,因为加载kernel的话肯定需要从硬盘中读入数据
; 主引导程序
;---------------------
%include "boot.inc"
SECTION MBR vstart=0x7c00 ;程序开始的地址
mov ax, cs ;使用cs初始化其他的寄存器
mov ds, ax ;因为是通过jmp 0:0x7c00到的MBR开始地址
mov es, ax ;所以此时的cs为0,也就是用0初始化其他寄存器
mov ss, ax ;此类的寄存器不同通过立即数赋值,采用ax中转
mov fs, ax
mov sp, 0x7c00 ;初始化栈指针,sp也就是32位下的esp
;清屏利用0x10中断的0x6号功能
;------------------------
mov ax, 0x600
mov bx, 0x700
mov cx, 0
mov dx, 0x184f
int 0x10
;获取光标位置
;---------------------
mov ah, 3 ; 3号子功能获取光标位置
mov bh, 1 ; bh寄存器存储带获取光标位置的页号,从0开始,此处填1可以看成将光标移动到最开始
int 0x10
;打印字符串`
;------------------
mov ax, message
mov bp, ax
mov cx, 6
mov ax, 0x1301
mov bx, 0x2
int 0x10
message db "My MBR"
mov eax, LOADER_START_SECTOR ;起始扇区的lba地址
mov bx, LOADER_BASE_ADDR ;写入的地址
mov cx, 1 ;读入的扇区数
call rd_disk_m_16
jmp LOADER_BASE_ADDR
;读取n个扇区
;---------------------
rd_disk_m_16: ;eax=扇区号,cx=读入的扇区数,bx=将数据写入的内存地址
mov esi, eax ;备份eax和cx
mov di, cx
;设置要读取的扇区数
mov dx, 0x1f2
mov al, al
out dx, al
mov eax, esi
;将lba地址存入0x1f3-0x1f6
;lba地址0-7位写入端口0x1f3
mov dx, 0x1f3
out dx, al
;lba地址8-15位写入端口0x1f4
mov cl, 8
shr eax, cl
mov dx, 0x1f4
out dx, al
;lba地址16-23位写入端口0x1f5
shr eax, cl
mov dx, 0x1f5
out dx, al
shr eax, cl
and al, 0x0f
or al, 0xe0
mov dx, 0x1f6
out dx, al
;向0x1f7端口写入读命令
mov dx, 0x1f7
mov al, 0x20
out dx, al
.not_ready:
nop
in al, dx
and al, 0x88 ;第4位为1表示硬盘控制器已经准备号数据传输,第7位为1表示硬盘忙
cmp al, 0x08
jnz .not_ready
;从0x1f0端口读数据
mov ax, di
mov dx, 256
mul dx
mov cx, ax
mov dx, 0x1f0
.go_on_read:
in ax, dx
mov [bx], ax
add bx, 2
loop .go_on_read
ret
times 510-($-$$) db 0
db 0x55, 0xaa
这是boot.inc中的内容
LOADER_BASE_ADDR equ 0x900
LOADER_START_SECTOR equ 0x2
先看看程序的执行流程
从0x7c00入口处进入mbr
打印My MBR
为读取磁盘操作传递参数,包括读入的扇区数,读取的数据写入的内存地址
将读取到的数据写入0x900,并跳到此处去执行
MBR中的内容差不多就多了,接下来的工作就是逐步完善内核。