本讲我们设计一个简单的读硬盘实验。通过一定的方法使硬盘第二个扇区的前3个字节依次为1、2、3,最后3个字节依次为3、2、1,中间的506个字节全为0。然后通过读硬盘程序将硬盘第二个扇区的数据读取到内存0x7e00-0x7fff的地方,也就是内存中MBR之后的512个字节。最后通过QEMU+DGB调试的方式来查看内存中0x7e00-0x7fff的数据,是否与硬盘第二个扇区中的数据一致,如果一致说明读硬盘成功。
本讲代码文件共2个:

  • data1.asm
  • boot1.asm

下面我们开始实验。

一、设置硬盘第二个扇区中的数据

data1.asm代码如下:

db 1
db 2
db 3
times 506 db 0
db 3
db 2
db 1

data1.asm就是生成512字节的数据,前3个字节依次是1、2、3,最后3个字节依次为3、2、1,中间的506个字节全为0。
在PowerShell中输入如下命令:

nasm data1.asm -o data1.bin
hexdump data1.bin -C

上述命令是将data1.asm通过汇编器生成了二进制文件data1.bin,然后通过hexdump命令查看data1.bin是否正确。截图如下:
自己动手从零写桌面操作系统GrapeOS系列教程——20.汇编语言读硬盘实战-LMLPHP

从上面截图可以看到,生成的data.bin文件共512字节,其中前3个字节依次为1、2、3,最后3个字节依次为3、2、1,中间的506个字节全为0。下面将data1.bin文件写入到虚拟硬盘的第二个扇区中。
在PowerShell中输入如下命令:

dd conv=notrunc if=data1.bin of=/media/VMShare/GrapeOS.img seek=1
hexdump /media/VMShare/GrapeOS.img -C

上面dd命令中的参数seek=1意思是在写入GrapeOS.img时,跳过1个写入块,写入块默认大小为512字节,也就将data1.bin写入到虚拟硬盘GrapeOS.img的第二个扇区中。截图如下:
自己动手从零写桌面操作系统GrapeOS系列教程——20.汇编语言读硬盘实战-LMLPHP

从上面截图中可以看到,虚拟硬盘第二个扇区中的数据达到了我们的实验要求。

二、读硬盘程序

boot1.asm的代码如下:

;定义常量
DISK_BUFFER equ 0x7e00 ;读硬盘临时存放数据用的缓存区,放到boot程序之后。

org 0x7c00

;初始化段寄存器
mov ax,cs
mov ds,ax ;ds指向与cs相同的段

mov esi,1 ;读取硬盘的第2个扇区
mov di,DISK_BUFFER
call func_read_one_sector

stop:
hlt
jmp stop 

;读取硬盘1个扇区(主硬盘控制器主盘)
;输入参数:esi,ds:di。
;esi LBA扇区号
;ds:di 将数据写入到的内存起始地址
;输出参数:无。
func_read_one_sector:
;第1步:检查硬盘控制器状态
mov dx,0x1f7
.not_ready1:
nop ;nop相当于稍息 hlt相当于睡觉
in al,dx ;读0x1f7端口
and al,0xc0 ;第7位为1表示硬盘忙,第6位为1表示硬盘控制器已准备好,正在等待指令。
cmp al,0x40 ;当第7位为0,且第6位为1,则进入下一个步。
jne .not_ready1 ;若未准备好,则继续判断。
;第2步:设置要读取的扇区数
mov dx,0x1f2
mov al,1
out dx,al ;读取1个扇区
;第3步:将LBA地址存入0x1f3-0x1f6
mov eax,esi
;LBA地址7-0位写入端口0x1f3
mov dx,0x1f3
out dx,al
;LBA地址15-8位写入端口写入0x1f4
shr eax,8
mov dx,0x1f4
out dx,al
;LBA地址23-16位写入端口0x1f5
shr eax,8
mov dx,0x1f5
out dx,al
;第4步:设置device端口
shr eax,8
and al,0x0f ;LBA第24-27位
or al,0xe0 ;设置7-4位为1110,表示LBA模式,主盘
mov dx,0x1f6
out dx,al
;第5步:向0x1f7端口写入读命令0x20
mov dx,0x1f7
mov al,0x20
out dx,al
;第6步:检测硬盘状态
.not_ready2:
nop ;nop相当于稍息 hlt相当于睡觉
in al,dx ;读0x1f7端口
and al,0x88 ;第7位为1表示硬盘忙,第3位为1表示硬盘控制器已准备好数据传输。
cmp al,0x08 ;当第7位为0,且第3位为1,进入下一步。
jne .not_ready2 ;若未准备好,则继续判断。
;第7步:从0x1f0端口读数据
mov cx,256 ;每次读取2字节,一个扇区需要读256次。
mov dx,0x1f0
.go_on_read:
in ax,dx
mov [di],ax
add di,2
loop .go_on_read
ret

times 510-($-$$) db 0
db 0x55,0xaa

上面的代码主要就是读硬盘函数func_read_one_sector,总共分7个步骤,与上讲中的读硬盘操作步骤一致。结合代码注释和之前讲的知识,大家应该是可以看懂的。
下面我们将编译boot1.asm,并将生成的二进制文件写入到虚拟硬盘的第一个扇区。在PowerShell中输入如下命令:

nasm boot1.asm -o boot1.bin
dd conv=notrunc if=boot1.bin of=/media/VMShare/GrapeOS.img

截图如下:
自己动手从零写桌面操作系统GrapeOS系列教程——20.汇编语言读硬盘实战-LMLPHP

在Windows的cmd命令行中启动QEMU调试模式:

qemu-system-i386 d:\GrapeOS\VMShare\GrapeOS.img -S -s

截图如下:
自己动手从零写桌面操作系统GrapeOS系列教程——20.汇编语言读硬盘实战-LMLPHP

在PowerShell中启动GDB:

[root@CentOS7 Lesson20]# gdb
(gdb) target remote 192.168.10.102:1234
(gdb) b *0x7c00
(gdb) c
Continuing.

Breakpoint 1, 0x00007c00 in ?? ()
(gdb) x /512xb 0x7e00

截图如下:
自己动手从零写桌面操作系统GrapeOS系列教程——20.汇编语言读硬盘实战-LMLPHP

从截图中可以看到,此时程序停在了断点0x7c00处,此时内存中0x7e00-0x7fff中的数据都是0。
我们输入命令c,让程序继续运行几秒钟,然后Ctrl+C,让程序暂停。此时读硬盘程序应该已经执行完了,我们再来查看一下内存0x7e00-0x7fff中的数据。截图如下:
自己动手从零写桌面操作系统GrapeOS系列教程——20.汇编语言读硬盘实战-LMLPHP

自己动手从零写桌面操作系统GrapeOS系列教程——20.汇编语言读硬盘实战-LMLPHP

从截图中可以看到,此时内存中0x7e00-0x7fff的数据和硬盘第二扇区中的数据一致,说明读取成功,实验完毕。


本讲视频版地址:https://www.bilibili.com/video/BV1JY411z7VT/
配套的代码与资料在:https://gitee.com/jackchengyujia/grapeos-course
GrapeOS操作系统交流QQ群:643474045

03-21 10:03