前言
上次说到在屏幕上显示"1 MBR"。我们从BIOS进入MBR,在mbr里面调用BIOS中断,在屏幕上显示"1 MBR"。上次就到这里了。但是mbr的作用显然不止只要这些。接下来我们就是来完善MBR。
实模式地址分配
起始地址 | 结束地址 | 大小 | 用途 |
---|---|---|---|
FFFF0 | FFFFF | 16B | BIOS入口地址 |
F0000 | FFFFF | 64KB | BIOS代码,包括上面介绍的BIOS入口地址 |
C8000 | EFFFF | 160KB | 映射硬件适配器的ROM或者内存映射式I/O |
C0000 | C7FFFF | 32KB | 显示适配器BIOS |
B8000 | BFFFF | 32KB | 用于文本模式显示适配器 |
B0000 | B7FFF | 32KB | 用于黑白显示适配器 |
A0000 | AFFFF | 64KB | 用于彩色显示适配器 |
9FC00 | 9FFFF | 1KB | EBDA(Extended BIOS Data Area)拓展BIOS数据区 |
07E00 | 9FBFF | 622080B | 可用区域 |
07C00 | 07DFF | 512B | MBR被BIOS加载到此处 |
00500 | 07BFF | 30464B | 可用区域 |
00400 | 004FF | 256B | BIOS Data Area(BIOS数据区) |
00000 | 003FF | 1KB | Interrupt Vector Table(中断向量表) |
完善MBR
首先我们要知道MBR的作用是啥?MBR,又名Master Boot Record,即主引导记录,也被称为主引导扇区,主要记录着硬盘本身的相关信息以及硬盘各个分区的大小及位置信息。
书上个mbr介绍了很多,先调用BIOS的子功能显示字符。后面又从更底层,向显存写入数据,然后再屏幕显示字符。具体可以看开头的实模式地址分配。就是向地址B8000写入数据。从而让字符显示出来
;主引导程序 ; ;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
代码先放到这里。
进入loader
到后面我们就进入loader了。进入loader需要mbr帮助,为啥要从mbr到loader呢?因为放到硬盘的0扇区,mbr只有512字节的空间,位置太小,啥事一个干不了。所以mbr需要从硬盘里面读取数据,加载到内存。然后mbr通过jmp指令跳转到刚刚加载的loader程序里面。先把代码贴上
;主引导程序 ;------------------------------------------------------------ %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 mov dx, 256 mul dx mov cx, ax ; di为要读取的扇区数,一个扇区有512字节,每次读入一个字, ; 共需di*512/2次,所以di*256 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
这里的写了rd_disk_m_16这个函数,里面接收三个参数,起始扇区lba地址,写入的地址,待读入的扇区数。就是从第二个扇区读取一个硬盘,也就是512个字节给放到LOADER_BASE_ADDR这个地址上去。LOADER_START_SECTOR和LOADER_BASE_ADDR都是boot.inc里面的。
代码如下
LOADER_BASE_ADDR equ 0x900 LOADER_START_SECTOR equ 0x2
接下来就是从mbr进入loader了。进入loader就可以做很多事情了。
书上是先写了个显示字符的例子,看看是不是真的跳到loader里面去了。并且loader还正确运行了。
%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 $ ; 通过死循环使程序悬停在此
之后,我们再将loader.s和mbr.s写入img。运行看看效果。
dd if=mbr.bin of=hd60M.img bs=512 count=1 seek=2 conv=notrunc
写入完成后,启动虚拟机看看效果。