一、原理
cpu同外围设备打交道,其实是要通过中间的IO接口电路才行。我们读取硬盘的数据,其实也是同中间的IO接口电路打交道。操作硬盘的这个IO接口电路叫做硬盘控制器。我们通过操作硬盘控制器上的端口(寄存器)来读取硬盘数据。
主要端口介绍:
- setctor count寄存器:设置读取或写入的扇区数
- LBA low,LBA mid,LBA high寄存器:分别指定LBA地址的0~7,8~15,16~23位
- device寄存器: 0代表主盘,1代表从盘
- command寄存器:写入命令,包括identify(0xEC),read sector(0x20),write sector(0x30)
- status寄存器
- data寄存器:读取或写入的寄存器
二、代码实现
思路:读取硬盘数据到指定位置,然后跳转到那个位置执行。
;主引导程序
;------------------------------------------------------------
%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
编译:
nasm -I include/ -o mbr.bin mbr.S
写入硬盘:
dd if=mbr.bin of=hd60M.img bs=512 count=1 conv=notrunc
三、编写loader
%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR
; 输出背景色绿色,前景色红色,并且跳动的字符串"1 MBR"
mov byte [gs:0x20],'2'
mov byte [gs:0x21],0xA4 ; A表示绿色背景闪烁,4表示前景色为红色
mov byte [gs:0x22],' '
mov byte [gs:0x23],0xA4
mov byte [gs:0x24],'L'
mov byte [gs:0x25],0xA4
mov byte [gs:0x26],'O'
mov byte [gs:0x27],0xA4
mov byte [gs:0x28],'A'
mov byte [gs:0x29],0xA4
mov byte [gs:0x2a],'D'
mov byte [gs:0x2b],0xA4
mov byte [gs:0x2c],'E'
mov byte [gs:0x2d],0xA4
mov byte [gs:0x2e],'R'
mov byte [gs:0x2f],0xA4
jmp $ ; 通过死循环使程序悬停在此
编译:
nasm -I include/ -o loader.bin loader.S
写入硬盘低2个扇区:
dd if=loader.bin of=../hd60M.img bs=512 count=1 seek=2 conv=notrunc
结果验证: