第3章-完善MBR
这是一个网站有所有小节的代码实现,同时也包含了Bochs等文件
编译器给程序中各符号(变量名或函数名等)分配的地址,就是各符号相对于文件开头的偏移量 。
section 称为节,在有的编译器中,同时支持 segment 和 section 这两个关键字,它们的功能都是在程序中宣称一个区域。
关键字 section 并没有对程序中的地址产生任何影响,即在默认情况下,有没有 section 都一个样, section 中数据的地址依然是相对于整个文件的顺延,仅仅是在逻辑上让开发人员梳理程序之用 。
实模式是指 8086 CPU 的寻址方式、寄存器大小、指令用法等,是用来反应 CPU 在该环境下如何工作的概念 。
段基址在实模式下要乘以 16,在保护模式下只是个选择子(保护模式中会讲〉,但其作用就是指定一片内存的起始地址 。
代码段寄存器 cs 就是用来指向内存中这段指令区域的起始地址。
数据段和代码段类似,只是这段区域中的内容不是指令,而是纯粹的数据,也就是说里面存储的是程序运行所需要的数据,属于指令的操作数 。数据段寄存器 DS 便是用来指向此数据区域的起始地址。
栈段寄存器 SS 就是用来指向此区域的起始地址。
访问内存就要用“段:段内偏移”的形式,所以 cs 寄存器用来存代码段段基址, IP寄存器用来存储代码段段内偏移地址,同 cs 寄存器一样都是 16 位宽。
实模式,还是保护模式,通用寄存器有 8 个,分别是 AX 、 BX 、 CX、DX 、 SI 、 DI 、 BP 、 SP
这些都是16为寄存器,可以扩展为32位寄存器,就是在前面加上e
call 指令用来执行一段新的代码,让 CPU 踏上新的征途
ret (return 指令的功能是在栈顶(寄存器 SS: Sp 所指向的地址)弹出2字节的内容来替换IP寄存器,ret指令使得sp+2
retf ( return far)是从战顶取得4字节,栈顶出的2字节用来替换IP寄存器,另外两个字节用来替换CS寄存器,retf指令使得sp+4
ret 和 retf 的区别便是 ret 用于近返回, retf 用于远返回。
接下来我们要用MBR做点实事了,MBR只有510B,能做的事情非常少,所以不能指望它做完所有事情。所以,我们用它把操作系统的loader加载到指定位置,然后跳转到loader执行,loader由于大小可以比MBR大得多,所以能做的就很多了。所以,MBR要加载loader,就必须要和磁盘打交道。打交道的方式很简单,就是通过in与out指令与磁盘暴露在外的寄存器交互。
磁盘端口寄存器对应的用途:
在我们的系统中主要使用三个命令:
- identify: 0XEC,即硬盘识别
- read sector: 0x20,即读磁盘
- write sector : 0x30,即写磁盘
操作的步骤:
所以当前要写的MBR.s的作用是从磁盘中加载操作系统的loader,该loaer由我们自己写入磁盘
;------------- loader和kernel ----------
LOADER_BASE_ADDR equ 0x900
LOADER_START_SECTOR equ 0x2
;这里起始的loader在磁盘:0道2扇区,MBR在0道1扇区
;主引导程序
%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 ;栈顶,开辟栈往下开辟,上面是BIOS
mov ax,0xb800 ;这个0xb800指向的是用于文本模式显示适配器的地址,用来输出文字
mov gs,ax
mov ax,0600h ;利用0x06号中断清屏
mov bx,0700h
mov cx,0 ;左上角: (0, 0)
mov dx,184fh ;右下角: (80,25),
int 10h
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
mov eax,LOADER_START_SECTOR ;起始扇区lba地质
mov bx,LOADER_BASE_ADDR ;写入的地质
mov cx,1 ;待读入的扇区数
call rd_disk_m_16
jmp LOADER_BASE_ADDR
;------------------------------------------------
;功能:在16位模式下读取硬盘的n个扇区
rd_disk_m_16:
;--------------------------------------------------
;eax=LBA扇区号
;bx=将数据写入的内存地质
;cx=读入的扇区数
mov esi,eax ;备份eax
mov di,cx ;备份cx
;读写硬盘
;第一布:设置要读取的扇区数
mov dx,0x1f2
mov al,cl
out dx,al ;读取的扇区数
mov eax,esi ;恢复ax
;第二步:将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 ;第 3 位为 1 表示硬盘控制器已准备好数据传输
;第 7 位为 1 表示硬盘忙
cmp al,0x08
jnz .not_ready ;若未准备号,继续等待
;第 5 步:从 OxlfO 端口读数据
mov ax,di ;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
加载的loader.s调试代码如下,主要是为了测试当前mbr.s是否成功加载到loader.s
%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR
;输出背景色绿色,前景色红色,并且跳动的字符"1 MBR"
mov byte [gs:0x00],'2'
mov byte [gs:0x01],0xA4
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 $