文章目录
- 前言
- 一、什么是MBR?
- 二、我们需要什么样的MBR?
- 三、设计我们的MBR!
- 1、打印“1 MBR”
- 2、加载次引导程序——loader
- 四、实践检验!
查看系列文章点这里: 操作系统真象还原
前言
在上一篇文章 第一步 从启动BIOS开始 中,我们介绍了BIOS系统,知道了它只负责做一些硬件检测和初始化的工作,然后控制权就到 MBR 手中了,那我们这一节就来介绍一下 MBR 是什么,又有什么作用叭!
一、什么是MBR?
MBR 也叫做“ 主引导程序 ”,从名字不难看出,它是主要的引导程序。引导的程序就是“ 次引导程序 ”,可以叫做“ 内核加载器 ”,总的来说就是加载内核也就是操作系统的程序。
也就是 MBR 并不加载操作系统,其原因在于有时候我们希望计算机可以在不同的分区使用不同的操作系统,所以 MBR 的工作就只能是挑选合适的“ 次引导程序 ”。至于BIOS为什么不直接完成挑选过程,原因很简单,因为空间太小不够用,心有余力不足。
二、我们需要什么样的MBR?
知道了什么是 MBR 后,我们就来来编写属于我们的 MBR 了,首先我们先来看看真正的 MBR 是什么样的,如下所示:
- 446字节的引导程序及参数;
- 64字节的分区表;
- 2字节的魔数,0x55和0xaa;
这是真正的 MBR ,那我们的 MBR 并不需要和上述那样,因为我们注定只会加载我们自己以后编写的操作系统,因此我们不需要分区表,引导程序要实现的功能也仅仅是把“ 次引导程序 ”从磁盘加载到内存中,并跳转过去就可以了。
但是为了方便我们看到结果,我们再在屏幕上输出一句话“1 MBR”,表示 MBR 被成功加载运行。
解下来我们就要开始编写 MBR 了,如果你还不了解如何在屏幕输出内容请看x86 文本模式显示适配器,如果你还不知道如何操控硬盘,请看x86 汇编如何控制硬盘?。
三、设计我们的MBR!
上一节我们已经讲了我们的 MBR 长什么样了,因此我们的程序的“ 主函数 ”就像下面展示的一样,非常简洁!
mov eax, LOADER_START_SECTOR ;待读入扇区的起始地址(0x2)mov bx, LOADER_BASE_ADDR ;数据从硬盘读入后存放的地址(0x900)mov cx, 4 ;待读入的扇区数目;当前loader只有一点点大小,不过为了方便,直接设置为4call rd_disk_m_16 ;执行该函数在"16位模式下读硬盘"jmp LOADER_BASE_ADDR ;跳转到LOADER程序
可以看到我们将地址设置成了常量,因为以后还有非常多的常量,因此,我在code目录下新建目录include,并新建boot.inc文件,专门用来存储将来会用到的常量。
; 加载器在硬盘上的位置(LBA)
LOADER_START_SECTOR equ 0x2; 加载器加载到内存中的位置
LOADER_BASE_ADDR equ 0x900
本节就一些关键步骤和代码做出解释说明,完整代码见文章末尾。
1、打印“1 MBR”
(1)清屏:
mov ax, 0x0600 ;AL:上卷的行数(为0表示全部),AH:功能号,0x06表示清屏功能mov bx, 0x0700 ;上卷行属性mov cx, 0x0 ;左上角(0,0)mov dx, 0x184f ;右下角(80,25),在VGA文本模式中,一行只能容纳80个字符,一共25行int 0x10 ;调用BIOS的视频服务中断
(2)打印:
;0x1010 0100 -> 前景色(字的颜色)为红色,背景色为绿色,闪烁(字闪烁)mov byte [gs:0x00], '1'mov byte [gs:0x01], 0xA4mov byte [gs:0x02], ' 'mov byte [gs:0x03], 0xA4mov byte [gs:0x04], 'M'mov byte [gs:0x05], 0xA4mov byte [gs:0x06], 'B'mov byte [gs:0x07], 0xA4mov byte [gs:0x08], 'R'mov byte [gs:0x09], 0xA4
2、加载次引导程序——loader
(1)第1步:设置带读入的扇区数
mov dx, 0x1f2mov al,cl ;使用cx寄存器存储待读入的扇区数out dx,al
(2)第2步:设置LBA地址
;LBA地址7~0位写入端口0x1f3mov dx, 0x1f3out dx, al;LBA地址15~8位写入端口0x1f4mov cl, 8shr eax, clmov dx, 0x1f4out dx, al;LBA地址23~16位写入端口0x1f5shr eax, clmov dx, 0x1f5out dx, al;设置deviceshr eax, cland al, 0x0f ;设置LB地址24~27位or al, 0xe0 ;0xe0 -> 11100000 ,设置7~4位为1110,表示为LBA模式mov dx, 0x1f6out dx, al ;将配置信息写入端口0x1f6
(3)第3步:发送读命令
mov dx, 0x1f7mov al, 0x20out dx, al
(4)第4步:检测硬盘状态
.not_ready:mov dx, 0x1f7 ;同一端口,写时表示写入命令,读时表示读入硬盘状态in al, dxand al, 0x88 ;第3位为1表示硬盘控制器已经准备好数据传输;第7位为1表示硬盘忙cmp al, 0x08jnz .not_ready ;若为准备好,则跳回not_ready处继续等待
(5)第5步:从硬盘读数据
mov ax, di ;di为待读入的扇区数mov dx, 256 ;一个扇区512字节,每次读入一字,即两字节,共要读256次mul dx ;乘扇区数mov cx, axmov dx, 0x1f0.go_on_read:in ax, dxmov [bx], ax ;存入内存add bx, 2 ;指向下一个地址loop .go_on_readret ;返回
四、实践检验!
现在我们来检验一下,我们的 MBR 是否编写正确,等一下,我们貌似还没有loader程序,虽然我们还不知道loader要干嘛,不过不妨碍我们验证 MBR,我们直接随便在code目录下新建一个假的loader,打印几个字符“2 LOADER”,宣告一下现在是我LOADER的天下啦!
%include "boot.inc"
; 加载器加载到内存中的位置
SECTION LOADER vstart=LOADER_BASE_ADDRmov byte [gs:0x50], '2'mov byte [gs:0x51], 0xA4mov byte [gs:0x52], ' 'mov byte [gs:0x53], 0xA4mov byte [gs:0x54], 'L'mov byte [gs:0x55], 0xA4mov byte [gs:0x56], 'O'mov byte [gs:0x57], 0xA4mov byte [gs:0x58], 'A'mov byte [gs:0x59], 0xA4mov byte [gs:0x5a], 'D'mov byte [gs:0x5b], 0xA4mov byte [gs:0x5c], 'E'mov byte [gs:0x5d], 0xA4mov byte [gs:0x5e], 'R'mov byte [gs:0x5f], 0xA4jmp $
下面我们来看看完整 MBR 长什么样。
%include "boot.inc"
SECTION MBR vstart=0x7c00;初始化寄存器mov ax, csmov ds, axmov es, axmov ss, axmov fs, axmov sp, 0x7c00;图形卡文本模式的起始地址mov ax, 0xb800mov gs, ax;清屏mov ax, 0x0600 ;AL:上卷的行数(为0表示全部),AH:功能号,0x06表示清屏功能mov bx, 0x0700 ;上卷行属性mov cx, 0x0 ;左上角(0,0)mov dx, 0x184f ;右下角(80,25),在VGA文本模式中,一行只能容纳80个字符,一共25行int 0x10 ;调用BIOS的视频服务中断;在屏幕上显示字符串"1 MBR";0x1010 0100 -> 前景色(字的颜色)为红色,背景色为绿色,闪烁(字闪烁)mov byte [gs:0x00], '1'mov byte [gs:0x01], 0xA4mov byte [gs:0x02], ' 'mov byte [gs:0x03], 0xA4mov byte [gs:0x04], 'M'mov byte [gs:0x05], 0xA4mov byte [gs:0x06], 'B'mov byte [gs:0x07], 0xA4mov byte [gs:0x08], 'R'mov byte [gs:0x09], 0xA4;接下来用eax,bx,cx三个寄存器传递参数,故先将值存进寄存器中mov eax, LOADER_START_SECTOR ;待读入扇区的起始地址(0x2)mov bx, LOADER_BASE_ADDR ;数据从硬盘读入后存放的地址(0x900)mov cx, 4 ;待读入的扇区数目call rd_disk_m_16 ;执行该函数在"16位模式下读硬盘"jmp LOADER_BASE_ADDR ;跳转到LOADER程序; ============================================================
; 功能:读取硬盘n(由cx寄存器决定读几个扇区)个扇区
; ============================================================
rd_disk_m_16:;备份eax,cxmov esi, eaxmov di, cx;第一步:设置要读取的扇区数mov dx, 0x1f2 mov al,clout dx,almov eax, esi;第二步,将LBA地址存入0x1f3 ~ 0x1f6;LBA地址7~0位写入端口0x1f3mov dx, 0x1f3out dx, al;LBA地址15~8位写入端口0x1f4mov cl, 8shr eax, clmov dx, 0x1f4out dx, al;LBA地址23~16位写入端口0x1f5shr eax, clmov dx, 0x1f5out dx, al;设置deviceshr eax, cland al, 0x0f ;设置LB地址24~27位or al, 0xe0 ;0xe0 -> 11100000 ,设置7~4位为1110,表示为LBA模式mov dx, 0x1f6out dx, al ;将配置信息写入端口0x1f6;第三步:向0x1f7端口写入读命令(0x20)mov dx, 0x1f7mov al, 0x20out dx, al;第四步:检测硬盘状态
.not_ready:;同一端口,写时表示写入命令,读时表示读入硬盘状态nopin al, dxand al, 0x88 ;第3位为1表示硬盘控制器已经准备好数据传输;第7位为1表示硬盘忙cmp al, 0x08jnz .not_ready ;若为准备好,则跳回not_ready处继续等待;第五步:从0x1f0端口读入数据mov ax, di ;di为待读入的扇区数mov dx, 256 ;一个扇区512字节,每次读入一字,即两字节,共要读256次mul dxmov cx, axmov dx, 0x1f0.go_on_read:in ax, dxmov [bx], axadd bx, 2loop .go_on_readret;填充空白符,保证整段程序大小为512字节
times 510-($-$$) db 0;定义MBR中最后两个字节的魔数,表示这个扇区包含可加载的程序
db 0x55, 0xaa
接下来就是编译和传输,如下:
nasm -I ./include/ -o mbr.bin mbr.Sdd if=./mbr.bin of=../bochs/hd60M.img bs=512 count=1 conv=notruncnasm -I ./include/ -o loader.bin loader.Sdd if=./loader.bin of=../bochs/hd60M.img bs=512 count=4 seek=2 conv=notrunc
注意,传输loader的时候,跳过两个扇区,避免覆盖了我们的 MBR。
然后就可以运行我们的bochs啦!
可以看到我们的 MBR 已经成功完成任务啦!
持续更新中~~