005_读写硬盘

文章详细介绍了CPU如何通过I/O接口与外设如显示器、硬盘进行通信,重点阐述了I/O端口的作用和访问方式,以及硬盘控制器(IDE类型)的端口分配和硬盘操作步骤。通过实际代码展示了在16位实模式下读写硬盘的流程,并提供了在Linux环境下使用NASM编译和测试读写硬盘的实践过程。
摘要由CSDN通过智能技术生成

理论

I/O接口

外设与cpu之间并不是直接通信的,有一个中间的桥梁–I/O接口,比如cpu与显示器之间是通过显卡通信的,cpu与硬盘是通过硬盘控制器通信的

I/O端口

I/O端口是存在于I/O接口上的寄存器,负责和cpu和外设之间的数据交换,对这些端口的访问通常有两种方式。
一是通过内存地址访问,既将内存地址映射到某一寄存器上
另一种是通过端口号访问,每一端口都有一个唯一端口号,x86架构上,是通过两个字节表示的,也就是范围为0-65535

访问端口的代码
; 读
in al,dx
in ax,dx
in al,立即数
in ax,立即数
; 写
out dx,al
out dx,ax
out 立即数,al
out 立即数,ax

需要注意,端口号只能由dx或者立即数表示,表示范围为0-255,也就是一个字节能表示的范围

硬盘控制器

有IDE和SATA两种类型,下面是IDE的,另一种类型没学
电脑中有两个控制器,一个叫主控制器,一个是从控制器,每个控制器又可有两个盘,一个叫主盘,一个叫从盘,所以一台电脑最多就装四个硬盘

硬盘控制器端口

image.png
主控制器的端口号是从0x1f0到0x1f7,从控制器是由0x170到0x177,除了第一个是用来传输数据外都是8位
0x1f0:由于读写硬盘的单位是以扇区为单位的,所以每次读写都要经过这个端口256次
0x1f1:读取出错时会返回一些错误信息
0x1f2:读写的扇区数,当为0时表示256
0x1f3~0x1f5:LBA28位信息中的24位
0x1f6:
image.png
0x1f7:
image.png

硬盘操作步骤

读硬盘

  1. 读取Status端口,如果该端口位7为0,第6位为1,进入下一步,否则循环当前步骤。
  2. 向Sector count端口中写入要读入的扇区数。
  3. 向LBA low、LBA mid、LBA high3个端口依次写入LBA起始扇区号的低24位。
  4. 向Device端口写入LBA起始扇区号的24~27位,并置第4位为0,第6位为1。
  5. 向Command端口写入0x20。
  6. 读取Status端口,如果该端口位7为0,位3为1,则进入下一步,否则循环当前步骤。
  7. 从Data端口读取数据。如果读1个扇区,则循环读取该端口256次。

写硬盘

  1. 读取Status端口,如果该端口位7为0,第6位为1,进入下一步,否则循环当前步骤。
  2. 向Sector count端口中写入要写入的扇区数。
  3. 向LBA low、LBA mid、LBA high3个端口依次写入LBA起始扇区号的低24位。
  4. 向Device端口写入LBA起始扇区号的24~27位,并置第4位为0,第6位为1。
  5. 向Command端口写入0x30。
  6. 读取Status端口,如果该端口位7为0,位3为1,则进入下一步,否则循环当前步骤。
  7. 向Data端口写入数据。如果写入1个扇区,则循环写入该端口256次。

实践

读硬盘–实践

tips: 使用esi的原因:因为使用的是32的模拟器,虽然在实模式下,也用esi


; mbr.asm
DATA_DESTINATION_ADDRESS equ 0x7e00     ; 设置数据读出后的存放地址

org 0x7c00                              

mov ax,cs
mov ds,ax

mov esi,1                               ; 设置读取第二个扇区,这个值后面就是赋给LBA的值
mov di,DATA_DESTINATION_ADDRESS         
call read_one_sector16

stop:
hlt
jmp stop
; 读取一个扇区函数,命名后面有个16是表示在16位实模式下读取扇区的函数
read_one_sector16:  
; 读取状态,看是否第7位是0,第6位是否是1,也就是表示硬盘空闲且就绪的状态 
mov dx,0x1f7        
.not_ready1:
nop             ; 一个短暂的停顿
in al,dx        ; 将端口数据读入al中
and al,0xc0     ; 将al和1100 0000做与运算,其结果是将al的第6,7位保留下来,其他都是0
cmp al,0x40     ; 将al和0100 0000做比较,如果相等,证明硬盘空闲且就绪,如果不是就循环再检测
jne .not_ready1
; 设置要读取扇区的数量
mov dx,0x1f2
mov al,1
out dx,al
; 将LBA的前14位设置到0x1f3~0x1f5端口上
mov eax,esi
mov dx,0x1f3
out dx,al

shr eax,8       ; 右移8位,这样al的值就是第9-16位的LBA值
mov dx,0x1f4
out dx,al

shr eax,8
mov dx,0x1f5
out dx,al
; 设置0x1f6端口,前四位是LBA的最后四位,后四位,依据通过LBA从主设备读取数据的要求,应该设置为1110
shr eax,8
mov dx,0x1f6
and al,0x0f     ; 将al的前四位进行保留,也就是LBA的后四位
or al,0xe0      ; 和1110进行或的结果,就是将al的后四位设置为1110
out dx,al
; 写入读硬盘命令
mov dx,0x1f7
mov al,0x20     ; 0x20是读命令
out dx,al
; 再次检查硬盘状态是否为空闲,且可以从硬盘中读数据的状态,也就是0x1f7应该为10001000
.not_ready2:
nop
in al,dx
and al,0x88     ; 将al的第7和第3位保留
cmp al,0x08     ; 判断第7位是否为0,第三位是否为1
jne .not_ready2

mov cx,256      ; 由于每次只能接受两个字节,所以一个扇区需要读取256次
mov dx,0x1f0
.read_data:
in ax,dx
mov [di],ax     ; 将读出的数据存入0X7e00开始的地址
add di,2
loop .read_data
ret

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

还需要准备一段数据,将这段数据写入第二个扇区,用来证明读出了第二个扇区

; data.asm
db "hard disk operate"  ; 前面写入17个字节的字符数据
times 492 db 0
db 0x01                 ; 后面写113结尾
db 0x01
db 0x03

在linux上进行编译,并装入硬盘

nasm mbr.asm -o mbr.bin
nasm data.asm -o data.bin
dd if=/dev/zero of=mbr.img count=4 bs=1M
dd if=mbr.bin of=mbr.img bs=1M count=4 conv=notrunc
dd if=data.bin of=mbr.img conv=notrunc seek=1

通过hexdump命令可以看见第二个扇区确实是刚才在data.asm中写入的数据
image.png

启动qemu

qemu-system-i386 .\mbr.img -S -s

image.png
连接gdb调试,设置0x7c00的断点,继续执行,然后查看0x7e00开始的512字节内容,可以看见里面只有0x00,不是刚才设置的数据,也就是说这时候还没有读取硬盘
image.png

退出后继续执行,按ctrl+后继续输入命令,可以看见这里的数据就是最初在data.asm中写入的数据
image.png

写硬盘–实践


; mbr.asm
DATA_SOURCE_ADDRESS equ 0x7e00     ; 设置数据源的存放地址

org 0x7c00                              

mov ax,cs
mov ds,ax

mov bx,DATA_SOURCE_ADDRESS     ; 在0x7e00~0x7fff的512个字节的开头和结尾写入一些数据用来写入到扇区中
mov byte [bx+0],'w'
mov byte [bx+1],'r'
mov byte [bx+2],'i'
mov byte [bx+3],'t'
mov byte [bx+4],'e'
mov byte [bx+509],'7'
mov byte [bx+510],'8'
mov byte [bx+511],'9'

mov esi,1                               ; 设置写入第二个扇区,这个值后面就是赋给LBA的值
mov di,DATA_SOURCE_ADDRESS         
call write_one_sector

stop:
hlt
jmp stop
; 写入一个扇区函数
write_one_sector:  
; 读取状态,看是否第7位是0,第6位是否是1,也就是表示硬盘空闲且就绪的状态 
mov dx,0x1f7        
.not_ready1:
nop             ; 一个短暂的停顿
in al,dx        ; 将端口数据读入al中
and al,0xc0     ; 将al和1100 0000做与运算,其结果是将al的第6,7位保留下来,其他都是0
cmp al,0x40     ; 将al和0100 0000做比较,如果相等,证明硬盘空闲且就绪,如果不是就循环再检测
jne .not_ready1
; 设置要写入的扇区的数量
mov dx,0x1f2
mov al,1
out dx,al
; 将LBA的前14位设置到0x1f3~0x1f5端口上
mov eax,esi
mov dx,0x1f3
out dx,al

shr eax,8       ; 右移8位,这样al的值就是第9-16位的LBA值
mov dx,0x1f4
out dx,al

shr eax,8
mov dx,0x1f5
out dx,al
; 设置0x1f6端口,前四位是LBA的最后四位,后四位,依据通过LBA从主设备读取数据的要求,应该设置为1110
shr eax,8
mov dx,0x1f6
and al,0x0f     ; 将al的前四位进行保留,也就是LBA的后四位
or al,0xe0      ; 和1110进行或的结果,就是将al的后四位设置为1110
out dx,al
; 写入读硬盘命令
mov dx,0x1f7
mov al,0x30     ; 0x30是写命令
out dx,al
; 再次检查硬盘状态是否为空闲,且可以从硬盘中写数据的状态,也就是0x1f7应该为10001000
.not_ready2:
nop
in al,dx
and al,0x88     ; 将al的第7和第3位保留
cmp al,0x08     ; 判断第7位是否为0,第三位是否为1
jne .not_ready2

mov cx,256      ; 由于每次只能接受两个字节,所以一个扇区需要写入256次
mov dx,0x1f0
.write_data:
mov ax,[di]     ; 将内存7e00开始的数据赋给ax
out dx,ax
add di,2
loop .write_data
ret

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

对代码进行编译后查看mbr.img虚拟磁盘,可以看到,第二个扇区还是在原来的数据,不是在写硬盘代码中希望写入的数据

uzuos-> nasm mbr.asm -o mbr.bin
[/home/zuos]
uzuos-> dd conv=notrunc if=mbr.bin of=mbr.img
1+0 records in
1+0 records out
512 bytes (512 B) copied, 0.000941566 s, 544 kB/s
[/home/zuos]
uzuos-> hexdump mbr.img -C

image.png

打开qemu执行后再次查看磁盘,可以看到第二个扇区数据已经改变为write开头,789结尾

image.png

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值