汇编访问硬盘

大体流程(以读为例)

在这里插入图片描述

CPU和外围设备的通信

I/O总线和I/O接口

CPU和外围设备打交道靠的是I/O总线和I/O接口,I/O总线负责传输数据和CPU发送给外围设备的命令。I/O接口则相当于一个处理器接口,用于对CPU发送的命令进行判断,将CPU的命令翻译成外围设备可以“看懂”的命令,I/O接口可以是芯片或别的什么,只要能处理命令都可以。
I/O接口通过ICH(I/O Controller Hub)与总线连接。每一个外围设备都需要有一个单独的I/O接口,用以和总线收发数据。

I/O端口和端口访问

I/O接口其实是一个笼统的概念,其功能其实比看起来要复杂的。它的主要功能是向总线收发命令和翻译命令。但具体来说,CPU其实是通过端口(Port)和外围设备交流的。
硬件上的端口类似于寄存器,不同的是这些寄存器是处于I/O接口电路中的,而且没有固定的符号来表示他们,也就是说,我们使用这些端口时,使用特定的端口号,也就是数字来访问的。
一个I/O接口可以有多个端口,用于不同的目的,比如用于和硬盘通信的PATA/SATA接口,就具有命令端口、状态端口、参数端口、数据端口。

命令端口:发送0x20表示读数据,0x30表示写数据
状态端口:判断硬盘的工作是否正常,操作是否成功,发生了那种错误等
参数端口:告诉硬盘操作的扇区数量、起始的逻辑扇区号
数据端口:向硬盘收发数据

端口和普通寄存器一样,拥有自己的数据宽度,主要是可以是8位、16位也可以是32位,取决于设计者(本文采用16位)。

端口编址

端口编制普遍有两种
一种是将端口映射到内存地址空间中,称为统一编址,当访问这块内存地址时,实际访问的是I/O接口。
另一种是,独立编址,不依赖于内存。而是处理器不仅直接连接内存,还连接I/O接口。为了放置处理器发送的数据被错误的对象接收,这类处理器还设置了一个特别的引脚M/IO#,#表示低电平。当引脚为低电平时,连接内存的电路关闭,连接I/O的电路打开。反之亦然。

文采用独立编址
所有端口有是统一的编号,如I/O接口Ayou3个端口分别为0x0021~0x0023,I/O接口B有5个端口为,0x0303~0x0307等。intel的十六位端口最多可以支持65536个端口的存在。
个人计算机中和硬盘连接的PATA/SATA,每个则分配了8个端口。ICH中包含了两个PATA/SATA接口,分别用于主硬盘和副硬盘,分别使用端口0x1f0~0x1f7和0x170~0x177。

端口访问指令

从端口读指令是in,一般形式为

in al, dx	;机器码 EC
in ax, dx	;机器码 ED

in指令的目的操作数必须是al、或ax,源操作数也应当是dx。这样生成的操作码是一字节的。操作数可以存放在al或ax中,操作数则可以存放在dx中。
其中存放端口和操作数可以使用mov指令,如

mov ax, 0x1f2	;将16位端口0x1f2移动到ax中
mov dx, 0x20	;将操作数0x20移动到dx中

不过或许为了方便,in指令也可以被编译为二字节的如:

in al, 0xf0	;E4 F0
in ax, 0x03	;E5 03

但是,这种指令的操作数,最多只能是一字节,只能访问0x00~0xff号端口。

与指令in相反,out是向端口写指令,一般形式可以有:

out 0x37, al	;向0x37端口写入(这是一个8位端口)
out 0xf5, ax	;同上(这是一个16位端口)
out dx, al	;端口号在dx寄存器中
out dx, al ;16位端口

目的操作数可以是1字节端口号,或者dx。目的操作数必须是ax或al

具体过程

初始化并决定加载的位置

从硬盘读取数据之前,我们首先需要确定的就是要把读取到的数据存在内存的什么位置。这并不难理解,如果不提前找好数据存储的位置,那我们读出来的数据放哪呀

SECTION mbr align=16 vstart=0x7c00           ;通过vstart直接设置偏移地址0x7c00,可以省去手动加0x7c00的过程                          
;初始化
         ;设置堆栈段和栈指针 
         mov ax,0      
         mov ss,ax
         mov sp,ax

;定义用户加载的地址
         mov ax,[cs:phy_base]            ;计算用于加载数据的逻辑段地址
         mov dx,[cs:phy_base+0x02]	;由于0x10000的值过大,因此分成两半来存放,ax存放0000,dx存放:0001
         mov bx,16                       ;将phy_base向右移动4位,如此一来才可以作为段地址。
         div bx            
         mov ds,ax                       ;设置DS和ES的为上面计算的段地址
         mov es,ax  

;0x10000并不是固定的位置,任何只要是空余的地址都可以作为,加载程序
         phy_base dd 0x10000             ;数据被加载的物理起始地址

访问硬盘

  1. 设置要读取的扇区数量。0x1f2号端口接收端口的数量,这是一个8位端口
mov dx, 0x1f2
mov al, 0x01
out dx, al

注意,如果写入0,就表示要读取256个扇区,每读一个这个端口的数值就会减一。如果读的过程中发生错误,这个端口会包含未读取的扇区数。

  1. 设置起始LBA扇区号

扇区的读写是连续的,因此只需要写入第一个扇区号即可。因为我们使用28位的扇区号,对寄存器来说太长了,因为我们将它分成四段,分别存放在0x1f3~0x1f6号端口。如下

mov dx, 0x1f3
mov al, 0x02	;假设从2号扇区开始,地址0~7
out dx, al
inc dx			;0x1f4
mov al, 0x00	:地址8~15
out dx,al
inc dx			;0x1f5
out dx, al		;地址23~16
inc dx			;0x1f6
mov al , 0xe0	;LBA模式  主硬盘  地址24~27
out dx, al

倒数第二行源操作数表示的内容和前面的操作数有所不同,其表示的内容如下
在这里插入图片描述
3. 请求硬盘读。0x1f7是硬盘的命令端口,向命令端口写入0x20就是请求硬盘读。

mov dx, 0x1f7
mov al, 0x20
out dx, al
  1. 等待读写操作完成。这一步还需要使用端口0x1f7,它不仅是命令端口同时也是状态端口,硬盘的各种状态都可以从0x1f7获得。如下
    在这里插入图片描述
mov dx, 0x1f7
.waits:
	in al, dx
	and al, 0x88	;结合下一行判断,硬盘是否忙碌。
	cmp al, 0x08 ;判断硬盘是否可以进行数据交互,如果结果为零表示硬盘空闲,且可以镜像数据交互
	jnz .waite
  1. 连续读出数据。通过0x1f0端口。
mov cx, 256			;这里单位是字,等于512字节,也就是一个硬盘块的大小
mov dx, 0x1f0
.readw:
	in ax, dx
	mov [bx], ax	;将读取的数据存放在ds:bx位置
	add bx, 2
	loop .readw

当这些步骤完成之后,从硬盘读的过程就算完成了。当然这里可能会有一个问题就是,256个字真的就是一个文件的大小吗?如果不是,那么我们怎样才能知道它的大小,并完整的读出所有内容呢?
其实解决这个问题的方法就是,在创建文件的过程中就把文件的如大小等的基本信息计算好,放在文件开头的256个字节中,这样只要读取了最开头的256个字节就可以更具这里面的信息将之后的内容都都出来了。
具体的细节可以查看《用户程序》

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值