前引
上一章我们完成了MBR的雏形编写,但是只打印了几个字符,这一章我们才要真正地去完成MBR的功能。
在完成MBR的功能之前我们要先了解一些知识,首先介绍什么是实模式。
书上的内容实在繁杂,简单地说,实模式没有虚拟和抽象这一概念,程序员写的代码,只要写清楚放在什么地方就是放在内存的什么地方,没有系统进行保护,其他程序可以轻松覆盖、内存对于程序员来说是可随意访问的。因此会有很大的操作隐患。但是电脑开机后是先进入实模式的,然后再进入保护模式,这之间的过渡即MBR的作用。
在上一章中BIOS建立了中断向量表,我们可以通过中断打印MBR几个字符;但是中断向量表只能在实模式下存在,等进入保护模式就不能运行了。所以我们需要换一种方式打印字符。
这种方式即直接通过CPU控制显卡的显存来操作显卡;CPU通过操作这部分内存,就可以通过显示器打印字符了;
显示器上每个字在内存中占2字节16位的内存,其中低八位是字符的ASCII码,高八位是控制字符颜色的;具体哪一位对应什么颜色可以搜索资料;
有了这些基础知识,我们就可以对上一章的MBR代码进行改造了;
MBR程序编写
代码逻辑:
1.指定vstart=0x7c00,告诉编译器本程序起始地址请帮我放到0x7c00处
2.查询调用BIOS清屏(后面会直接通过显卡实现)
3.指定段基址,gs=0xb800
4.死循环,填入MBR规定510字节大小剩下的0;固定结尾两字节0x55 和0xaa
bash
;主引导程序
;
;LOADER_BASE_ADDR equ 0xA000
;LOADER_START_SECTOR equ 0x2
;------------------------------------------------------------
SECTION MBR vstart=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
; 清屏
;利用0x06号功能,上卷全部行,则可清屏。
; -----------------------------------------------------------
;INT 0x10 功能号:0x06 功能描述:上卷窗口
;------------------------------------------------------
;输入:
;AH 功能号= 0x06
;AL = 上卷的行数(如果为0,表示全部)
;BH = 上卷行属性
;(CL,CH) = 窗口左上角的(X,Y)位置
;(DL,DH) = 窗口右下角的(X,Y)位置
;无返回值:
mov ax, 0600h
mov bx, 0700h
mov cx, 0 ; 左上角: (0, 0)
mov dx, 184fh ; 右下角: (80,25),
; 因为VGA文本模式中,一行只能容纳80个字符,共25行。
; 下标从0开始,所以0x18=24,0x4f=79
int 10h ; int 10h
; 输出背景色绿色,前景色红色,并且跳动的字符串"1 MBR"
mov byte [gs:0x00],'1'
mov byte [gs:0x01],0xA4 ; A表示绿色背景闪烁,4表示前景色为红色
mov byte [gs:0x02],' '
mov byte [gs:0x03],0xA4
mov byte [gs:0x04],'M'
mov byte [gs:0x05],0xA4
mov byte [gs:0x06],'B'
mov byte [gs:0x07],0xA4
mov byte [gs:0x08],'R'
mov byte [gs:0x09],0xA4
jmp $ ; 通过死循环使程序悬停在此
times 510-($-$$) db 0
db 0x55,0xaa
当然,这并没有完,只是换了一个方式重新打印了MBR字符而已;下面我们要让MBR做一点实事了;即加载操作系统的loader(因为MBR程序被锁死了只有510字节,无法完成太多功能),为进入保护模式做准备;
简单地说,我们要用MBR这个加载器,去加载另一个加载器;另一个加载器(即loader)的功能是加载操作系统;
如何实现呢?我们需要在实模式下,用in 和out 这两个指令与磁盘的一些寄存器进行交互;
这里放一下书上的磁盘IO的寄存器表
操作磁盘的方式方法:
MBR程序再编写
1、include boot.inc ,这里面定义了loader在磁盘中的位置(磁盘2号扇区)和loader加载进内存后的位置(0x900)
2、vstart=0x7c00,告诉编译器将这段代码放到0x7c00
3、按照上面操作磁盘的方法将磁盘数据取出放到指定内存区域
4、跳转到loader位置
5、填充剩下的0,定义结尾0x55和0xaa
bash
;------------- loader和kernel ----------
LOADER_BASE_ADDR equ 0x900
LOADER_START_SECTOR equ 0x2
bash
;主引导程序
;------------------------------------------------------------
%include "boot.inc"
SECTION MBR vstart=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
; 清屏
;利用0x06号功能,上卷全部行,则可清屏。
; -----------------------------------------------------------
;INT 0x10 功能号:0x06 功能描述:上卷窗口
;------------------------------------------------------
;输入:
;AH 功能号= 0x06
;AL = 上卷的行数(如果为0,表示全部)
;BH = 上卷行属性
;(CL,CH) = 窗口左上角的(X,Y)位置
;(DL,DH) = 窗口右下角的(X,Y)位置
;无返回值:
mov ax, 0600h
mov bx, 0700h
mov cx, 0 ; 左上角: (0, 0)
mov dx, 184fh ; 右下角: (80,25),
; 因为VGA文本模式中,一行只能容纳80个字符,共25行。
; 下标从0开始,所以0x18=24,0x4f=79
int 10h ; int 10h
; 输出字符串:MBR
mov byte [gs:0x00],'1'
mov byte [gs:0x01],0xA4
mov byte [gs:0x02],' '
mov byte [gs:0x03],0xA4
mov byte [gs:0x04],'M'
mov byte [gs:0x05],0xA4 ;A表示绿色背景闪烁,4表示前景色为红色
mov byte [gs:0x06],'B'
mov byte [gs:0x07],0xA4
mov byte [gs:0x08],'R'
mov byte [gs:0x09],0xA4
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=LBA扇区号
; ebx=将数据写入的内存地址
; ecx=读入的扇区数
mov esi,eax ;备份eax
mov di,cx ;备份cx
;读写硬盘:
;第1步:选择特定通道的寄存器,设置要读取的扇区数
mov dx,0x1f2
mov al,cl
out dx,al ;读取的扇区数
mov eax,esi ;恢复ax
;第2步:在特定通道寄存器中放入要读取扇区的地址,将LBA地址存入0x1f3 ~ 0x1f6
;LBA地址7~0位写入端口0x1f3
mov dx,0x1f3
out dx,al
;LBA地址15~8位写入端口0x1f4
mov cl,8
shr eax,cl
mov dx,0x1f4
out dx,al
;LBA地址23~16位写入端口0x1f5
shr eax,cl
mov dx,0x1f5
out dx,al
shr eax,cl
and al,0x0f ;lba第24~27位
or al,0xe0 ; 设置7~4位为1110,表示lba模式
mov dx,0x1f6
out dx,al
;第3步:向0x1f7端口写入读命令,0x20
mov dx,0x1f7
mov al,0x20
out dx,al
;第4步:检测硬盘状态
.not_ready:
;同一端口,写时表示写入命令字,读时表示读入硬盘状态
nop
in al,dx
and al,0x88 ;第4位为1表示硬盘控制器已准备好数据传输,第7位为1表示硬盘忙
cmp al,0x08
jnz .not_ready ;若未准备好,继续等。
;第5步:从0x1f0端口读数据
mov ax, di ;di当中存储的是要读取的扇区数
mov dx, 256 ;每个扇区512字节,一次读取两个字节,所以一个扇区就要读取256次,与扇区数相乘,就等得到总读取次数
mul dx ;8位乘法与16位乘法知识查看书p133,注意:16位乘法会改变dx的值!!!!
mov cx, ax ; 得到了要读取的总次数,然后将这个数字放入cx中
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
Loader程序编写检验MBR功能
然后我们随便写个loader检验MBR是否能成功运行
我就直接复制粘贴后面的loader打印字符程序了
bash
%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR
; 输出背景色绿色,前景色红色,并且跳动的字符串"1 MBR"
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 $ ; 通过死循环使程序悬停在此
编译运行
用nasm编译我们的MBR程序和Loader程序
nasm -o mbr mbr.s -I include/
在include文件夹中放入我们的boot.inc
用dd命令将mbr写入磁盘0号分区,loader写入2号分区
dd if=loader of=/bochs/hd60M.img seek=2 bs=512 count=1 conv=notrunc